Plantilla de Tikz para crear diagramas matemáticos: ejemplo de rectas numéricas dobles

Etiquetas: Tikz

(English: Tikz Template for Mathematical Figures: Example with Double Number Lines)

En esta entrada de blog explico cómo diseñé un sistema con TikZ para generar un tipo de diagramas matemáticos de manera eficiente y fácil de mantener. TikZ un lenguaje de programación para crear diagramas matemáticos mediante código. El sistema es una plantilla para construir rectas numéricas dobles, tipo de diagrama muy utilizado en recursos educativos de matemáticas modernos para apoyar el razonamiento proporcional (por ejemplo, porcentajes, razones o recetas).

El objetivo de esta entrada es mostrar cómo estas rectas numéricas dobles se pueden generar “automáticamente”, usando código de programación breve y flexible, que produce diagramas visualmente consistentes y permite centrar la atención en las ideas matemáticas. Una estructura visual coherente facilita el aprendizaje y el razonamiento matemático.

Para ilustrar la utilidad de estos diagrama, considere el siguiente ejemplo:

¿Cuánto es 75% de una cantidad de calcio si 100% es 1200mg?

La recta numérica doble permite visualizar la respuesta, alineando la marca del 75% en una recta con el valor correspondiente en la otra.

Con la plantilla, este es el código que genera el diagrama:

\begin{tikzpicture}

% Parámetros
\def\lineWide{6.3cm} % ancho de la recta (sin incluir la etiqueta)
\def\numMarks{5}
\def\topMarkLabels{0,300,...,1200} 
\def\topLineLabel{calcio (mg)}
\def\bottomMarkLabels{0\%, 25\%, 50\%,75\%, 100\%} 
\def\bottomLineLabel{}

% Dibuja la recta numérica doble
\doubleNumberLine{\lineWide}{\numMarks}{\topMarkLabels}{\topLineLabel}{\bottomMarkLabels}{\bottomLineLabel}

% recuadro alrededor de algunas marcas
\node[fit=(top-mark-4) (bot-mark-4), draw, rectangle, rounded corners=1.5ex, inner sep=2.7ex, densely dashed] {};

\end{tikzpicture}

No es necesario entender todas las líneas de ese código para poder leer esta entrada de blog. Lo importante es entender que, una vez definida la plantilla, crear diagramas como estos resulta rápido y sencillo: basta con modificar unas líneas y se obtiene un diagrama nuevo, bien organizado y con todos los textos y marcas en su sitio.

Antes de continuar, ¡anímese a probar! No es necesario saber programar:

  • ¿Qué cambiaría en el código para que diga magnesio en vez de calcio?
  • ¿Y para que las marcas superiores sean 0,500,1000,1500,2000?

Una buena plantilla separa claramente lo que varía (el contenido del diagrama de recta numérica doble) de lo que permanece igual (la estructura y el diseño). De este modo, todos los diagramas mantienen un estilo coherente y solo cambia la información.

¿A quién va dirigido esto?

Esta entrada de blog está dirigida a cualquier persona interesada en cómo se crean diagramas matemáticos a escala. Espero que sea una lectura interesante incluso si todo lo que sabe de TikZ es que un lenguaje para dibujar diagramas matemáticos (si desea saber más, puede leer aquí).

La intención no es solo explicar cómo dibujar estos diagramas, sino explicar cómo crear un sistema para producir muchos diagramas similares, manteniendo coherencia, claridad y facilidad de adaptación. Esto resulta especialmente útil cuando los recursos educativos se desarrollan en equipo y se ajustan con el paso del tiempo.

Estos son dos ejemplos con el mismo tamaño de recta (longitud de las rectas numéricas), aunque el contenido es diferente:


Paso a paso: cómo se construyó la plantilla

¿Qué inconvenientes busca evitar la plantilla?

Al crear rectas numéricas dobles, suelen surgir varios inconvenientes:

  • Los textos a la izquierda de la recta a veces son largos, y esto causa que los diagramas sean más anchos que el espacio disponible. En estos casos es necesario acortar las rectas para lograr que quepan en el espacio disponible y así evitar que haya un cambio de escala en el diagrama (pues estos producen inconsistencias en los tamaños de fuente y grosores de las líneas en los diagramas).
  • Estos ajustes a la longitud de la recta obligan a modificar la posición de las marcas y de sus etiquetas, lo que puede ser tedioso y repetitivo.
  • Si se necesita cambiar el número de marcas, hay que ajustar la posición de todas las marcas y sus etiquetas.
  • Si se utiliza código y el contenido no está bien separado del estilo, este se vuelve difícil de mantener si se reutiliza o copia. Si se emplea software gráfico, todos estos ajustes resultan engorrosos y repetitivos.

La plantilla resuelve todos estos inconvenientes automatizándolos y, al mismo tiempo, aplicando algunos principios de diseño:

  • El texto aparece siempre con el mismo tamaño de fuente en todos los diagramas, ya que la longitud de la recta (sin contar las etiquetas a la izquierda) se puede controlar explícitamente, permitiendo ajustar el diagrama al espacio disponible.
  • Las etiquetas de las rectas se colocan a una distancia fija y consistente a la izquierda.
  • Las marcas están espaciadas uniformemente.
  • Las etiquetas de las marcas están centradas perfectamente encima o debajo de las marcas, a una distancia vertical constante.
  • El código es limpio y legible, por lo que resulta fácil de adaptar.

Cada paso a continuación ilustra una decisión que ayuda a separar la estructura y la identidad visual del contenido.

Paso 1: Dibujar la recta

Suponga que se desea una longitud determinada, por ejemplo \def\lineWide{6.3cm}. Se puede dibujar la recta así:

\def\lineWide{6.3cm}
\coordinate (leftEnd) at (0, 0);
\coordinate (rightEnd) at (\lineWide, 0);
\draw[->, >=stealth] (leftEnd) -- (rightEnd);

En ese fragmento de código:

  • el comando \coordinate da nombre a los extremos de la recta numérica.
  • el comando \draw dibuja la recta entre los dos puntos (leftEnd) -- (rightEnd).
  • las opciones [->, >=stealth] indican:
    • -> dibuja una flecha al final del segmento
    • >=stealth utiliza el estilo de flecha “stealth”.

El resultado es el siguiente diagrama:

Paso 2: Añadir la etiqueta de la recta

Para que la etiqueta situada a la izquierda de la recta mantenga siempre una posición uniforme, sin importar la longitud del texto, conviene fijar su ubicación de manera automática. El siguiente método resulta especialmente práctico:

\node[anchor=east, xshift=-0.15cm] at (leftEnd) {acá va la etiqueta};

La opción anchor=east alinea el borde derecho de la etiqueta con (leftEnd), y xshift la desplaza ligeramente a la izquierda. Así la etiqueta queda alineada visualmente, aunque el texto cambie. El resultado es:

Para separar el comando que agrega la etiqueta y especifica su posición del texto mismo de la etiqueta, se puede definir la maco \def\lineLabel con el texto, y después invocarlo. Así:

% etiqueta de la recta
\def\lineLabel{acá va la etiqueta}

% agregar la etiqueta de la recta
\node[anchor=east, xshift=-0.15cm] at (leftEnd) {\lineLabel};

Paso 3: Añadir las marcas

Para evitar que las marcas queden demasiado cerca de los extremos, se deja un pequeño espacio a cada lado. Las marcas se colocan usando un ciclo.

Por ejemplo, para 5 marcas:

\def\numLineExtra{0.3cm}

% dibuja las marcas como nodos (mark-n). La primera marca es (mark-1).
\foreach \x in {1,...,5}{%
    \node[draw] (mark-\x) at ({\numLineExtra+(\x-1)*(\lineWide-2.5\numLineExtra)/(5-1)},0) {};
}

El resultado es el siguiente:

Ese fragmento de código coloca nodos sin texto (el texto va adentro de los {}) en los puntos igualmente espaciados (mark-1),…,(mark-5), dejando \numLineExtra de espacio a la izquierda y derecha de la recta numérica (en realidad, un poco más a la derecha). El comando node tiene la opción [draw], que dibuja el borde del nodo. Dar nombre a los nodos como (mark-1),…,(mark-5) es útil para después ubicar etiquetas u otros elementos alrededor de cada marca.

Paso 4: Dar estilo a las marcas

Ahora es necesario dar formato a esos nodos para que realmente parezcan marcas y no pequeños cuadrados. Esto se logra definiendo un estilo en TikZ y luego aplicándolo a los nodos correspondientes.

Así se define el estilo:

% Define the "numLineTick" style
numLineTick/.style={
  draw,               % Draw tickmark node
  minimum width=0pt,  % No width for the node box
  minimum height=7pt, % Height of the tickmark
  line cap=round,     % Rounded ends for ticks
  inner sep=0pt,      % No inner padding
  outer sep=2pt,      % Space around the tick
  line width=0.35pt,  % Thickness of the tick line
},

Para aplicarlo, basta con usar el estilo en lugar de [draw] al crear los nodos:

% dibuja las marcas como nodos (mark-n). La primera marca es (mark-1).
\foreach \x in {1,...,5}{%
    \node[numLineTick] (mark-\x) at ({\numLineExtra+(\x-1)*(\lineWide-2.5\numLineExtra)/(5-1)},0) {};
}

El resultado es:

Paso 5: Generalizar la cantidad de marcas

Una de las ventajas de esta plantilla es que permite generar tantas marcas como se necesiten. Como el código ya está preparado para ello, solo hay que cambiar el 5 por un nuevo parámetro \numMarks y todo se adapta de forma automática.

% dibuja las marcas como nodos (mark-n). La primera marca es (mark-1).
\foreach \x in {1,...,\numMarks}{%
    \node[numLineTick] (mark-\x) at ({\numLineExtra+(\x-1)*(\lineWide-2.5\numLineExtra)/(\numMarks-1)},0) {};
}

Por ejemplo, si se define \def\numMarks{9}, el código produce 9 marcas igualmente espaciadas:

Paso 6: Añadir etiquetas a las marcas

Al tener nombres para todas las marcas, etiquetarlas resulta sencillo. Por ejemplo:

\node[above,font=\small] at (mark-4) {above};
\node[below,font=\small] at (mark-4) {(mark-4)};

Otra opción es usar un ciclo y ajustar la distancia para las etiquetas superiores con above=3pt:

\foreach \num [count=\i] in {0,20,...,100} {
    \node[above=3pt,font=\scriptsize] at (mark-\i) {\num\%};
}

Para facilitar la reutilización, lo más práctico es definir las etiquetas en una macro de LaTeX y luego asignarlas a cada marca con un ciclo. Así:

\def\labels{0\%,20\%, , 60\% , ,100\%}
\foreach \label [count=\i] in \labels {
    \node[above=3pt,font=\scriptsize] at (mark-\i) {\label};
}

Observe lo fácil que resulta dejar etiquetas vacías usando la lista separada por comas en \labels. Esto es útil para estos diagramas en el contexto educativo, donde a veces es importante omitir ciertas etiquetas.

Paso 7: Definir estilos para las etiquetas

Nuevamente, para evitar repeticiones innecesarias y facilitar el mantenimiento del código, es mejor separar el estilo y la posición de las etiquetas del contenido. Se pueden definir dos estilos de TikZ distintos: uno para las etiquetas que van arriba de las marcas y otro para las que van debajo.

tickLabelAbove/.style={
    font=\scriptsize,   % Font size for tick labels
    anchor=mid,         % Baseline align text vertically across nodes
    yshift=2.0ex,       % Vertical offset for labels above the line
},
tickLabelBelow/.style={
    font=\scriptsize,   % Font size for tick labels
    anchor=mid,         % Baseline align text vertically across nodes
    yshift=-2.3ex,      % Slightly larger offset below the line to account 
                        % for the label's baseline alignment (ensures visual balance).
},

Así, el código se simplifica a:

\def\labels{0\%,20\%, , 60\% , ,100\%}
\foreach \label [count=\i] in \labels {
    \node[tickLabelAbove] at (mark-\i) {\label};
}

Plantilla final: \doubleNumberLine

Con el código en esta forma, se ha logrado separar completamente la estructura del contenido. Ahora se pueden declarar los parámetros para un diagrama específico de la siguiente manera:

\def\lineWide{6.3cm} % ancho de la recta (sin incluir la etiqueta)
\def\numMarks{5}
\def\topMarkLabels{0,300,...,1200} 
\def\topLineLabel{calcio (mg)}
\def\bottomMarkLabels{0\%, 25\%, 50\%,75\%, 100\%} 
\def\bottomLineLabel{}

y definir el código de la plantilla preferiblemente en un archivo aparte. El siguiente fragmento de código define el comando \doubleNumberLine, que se encarga de dibujar todo:

\newcommand{\doubleNumberLine}[6]{%
% Macro para dibujar una recta numérica doble con marcas y etiquetas a la izquierda
% El comando crea varias coordenadas:
% - Extremos de la recta superior e inferior: (top-leftEnd), (top-rightEnd), (bot-leftEnd), (bot-rightEnd)
% - Nodos de las marcas superiores: (top-mark-1), ...,(top-mark-\numMarks)
% - Nodos de las marcas inferiores: (bot-mark-1), ...,(bot-mark-\numMarks)
% 
%   Parámetros:
%   #1: Longitud de la recta (sin la etiqueta)
%   #2: Número de marcas
%   #3: Etiquetas de las marcas superiores (separadas por comas)
%   #4: Etiqueta de la recta superior (a la izquierda)
%   #5: Etiquetas de las marcas inferiores (separadas por comas)
%   #6: Etiqueta de la recta inferior (a la izquierda)

   % Constantes internas
  \def\numLineExtra{0.3cm} % Espacio extra fijo en los extremos
  
  %%% Recta numérica superior
  \begin{scope}
    % Coordenadas de los extremos
    \coordinate (top-leftEnd) at (0, 0);
    \coordinate (top-rightEnd) at (#1, 0);

    % Dibuja la recta y añade la etiqueta a la izquierda
    \draw[->, >=stealth] (top-leftEnd) -- (top-rightEnd);
    \node[anchor=east, xshift=-1.5ex] at (top-leftEnd) {#4};

    % Dibuja las marcas como nodos (top-mark-n). La primera marca es (top-mark-1).
    \foreach \x in {1,...,#2}{%
        \node[numLineTick] (top-mark-\x) at ({\numLineExtra+(\x-1)*(#1-2.5*\numLineExtra)/(#2-1)},0) {};
    }

    % Añade las etiquetas de las marcas
    \foreach \num [count=\i] in #3 {
        \node[tickLabelAbove] at (top-mark-\i) {\num};
    }
  \end{scope}

  %%% Recta numérica inferior
  \begin{scope}[yshift=-6ex]
    % Coordenadas de los extremos
    \coordinate (bot-leftEnd) at (0, 0);
    \coordinate (bot-rightEnd) at (#1, 0);

    % Dibuja la recta y añade la etiqueta a la izquierda
    \draw[->, >=stealth] (bot-leftEnd) -- (bot-rightEnd);
    \node[anchor=east, xshift=-1.5ex] at (bot-leftEnd) {#6};

    % Dibuja las marcas como nodos (bot-mark-n). La primera marca es (bot-mark-1).
    \foreach \x in {1,...,#2}{%
        \node[numLineTick] (bot-mark-\x) at ({\numLineExtra+(\x-1)*(#1-2.5*\numLineExtra)/(#2-1)},0) {};
    }

    % Añade las etiquetas de las marcas
    \foreach \num [count=\i] in #5 {
        \node[tickLabelBelow] at (bot-mark-\i) {\num};
    }
  \end{scope}
}

Y el siguiente fragmento de código contiene todos los estilos utilizados por \doubleNumberLine:

%  Estilos TikZ para rectas numéricas
\tikzset{
  tickLabelAbove/.style={
      font=\small,        % Tamaño de fuente para las etiquetas
      anchor=mid,         % Alineación vertical de las etiquetas
      yshift=2.0ex,       % Desplazamiento vertical para etiquetas arriba
  },
  tickLabelBelow/.style={
      font=\small,        % Tamaño de fuente para las etiquetas
      anchor=mid,         % Alineación vertical de las etiquetas
      yshift=-2.3ex,      % Desplazamiento mayor para etiquetas abajo
  },
  numLineTick/.style={
      draw,               % Dibuja el nodo de la marca
      minimum width=0pt,  % Sin ancho para el cuadro del nodo
      minimum height=7pt, % Altura de la marca
      line cap=round,     % Extremos redondeados
      inner sep=0pt,      % Sin espacio interno
      outer sep=2pt,      % Espacio alrededor de la marca
      line width=0.35pt,  % Grosor de la línea
  },
}

Ya con esto, pequeños fragmentos de código pueden generar diagramas con un estilo visual consistente.

Ejemplos

Por ejemplo, la siguiente línea de código

% \begin{tikzpicture}
\doubleNumberLine{6.3cm}{7}{0,1,...,6}{número de libros}{\$0, \$15, \$30, \$45, \$60, \$75, \$90}{costo total}
% \end{tikzpicture}

produce el diagrama:

Esta otra línea de código

% \begin{tikzpicture}
\doubleNumberLine{5cm}{5}{0,50,,,200}{número de tarjetas}{0\%, 10\%, , , ? }{porcentaje}
% \end{tikzpicture}

genera:

Cómo hacer la plantilla más fácil de usar

Es interesante saber que se puede especificar una diagrama de recta numérica doble con una sola línea de código. Sin embargo, la brevedad no siempre implica facilidad de uso. De hecho, no es sencillo recordar el significado de los seis argumentos {}{}{}{}{}{} en \doubleNumberLine, por lo que resulta más claro estructurar la plantilla de la siguiente forma que invoque \doubleNumberLine y haga explícito el significado de cada argumento. Son bastantes más líneas de código, pero es mucho más fácil de usar.

\begin{tikzpicture}
% Parámetros
\def\lineWide{3in} % ancho de la recta (sin incluir la etiqueta)
\def\numMarks{7}
\def\topMarkLabels{0,1,...,6} 
\def\topLineLabel{número de libros}
\def\bottomMarkLabels{\$0, \$15, \$30, \$45, \$60, \$75, \$90} 
\def\bottomLineLabel{costo total}

% Dibuja la recta numérica doble
% crea varias coordenadas como (top-leftEnd) y (bot-mark-2). Más detalles en la plantilla.
\doubleNumberLine{\lineWide}{\numMarks}{\topMarkLabels}{\topLineLabel}{\bottomMarkLabels}{\bottomLineLabel}
\end{tikzpicture}

Ejemplo completo, con código

Este es un ejemplo completo, con todo el código en un solo archivo (enlace a la galería TikZ de este blog). Para usarlo como plantilla, todos los estilos y la definición de la macro \doubleNumberLine deben trasladarse a un archivo .sty o a un archivo de LaTeX aparte, incluido en el preámbulo mediante el comando \include (como en este ejemplo que cree en Overleaf).

(Imagen creada con TikZ. Código fuente acá.)

Posibles mejoras

Número de marcas

La macro \doubleNumberLine podría deducir automáticamente el valor de \numMarks a partir de la longitud de \topMarkLabels. Esto no es trivial debido a la forma como LaTeX funciona internamente, pero es posible de lograr.

Por ejemplo, se podría añadir el siguiente fragmento cerca del inicio del código de doubleNumberLine:

% Calcular el número de marcas a partir de #3 y almacenarlo en \numMarks
\pgfmathsetmacro{\numMarks}{0}% 
\foreach \i in #3{%
  \pgfmathtruncatemacro{\numMarks}{\numMarks+1}% 
  \global\let\numMarks\numMarks%
}%

Esto eliminaría un argumento de entrada en \doubleNumberLine, pero no estoy seguro de que valga la pena tener un código de LaTeX más complejo (por el uso de \global y \pgfmathtruncatemacro). Encontrar una versión confiable de este código no fue sencillo; varios intentos fallaron, lo que sugiere que puede ser difícil de mantener si cambian TikZ o LaTeX.

Simplificar los parámetros

Para evitar que se usen los seis argumentos sin tener claridad de cuál es cuál, es posible que se puede crear interfaz más amigable con pgfkeys. De lograrse, la llamada a la plantilla podría verse así:

\doubleNumberLine[
  length=7.6cm,
  topmarks={0,50,...,200},
  toplabel={calcio (mg)},
  bottommarks={},
  bottomlabels={0\%,25\%,50\%,75\%,100\%}
]

Suscríbase

¿Quiere recibir un email cuando haya una nueva entrada de blog? Suscríbase acá.