Notxor tiene un blog

Defenestrando la vida

Dibujando figuras con Emacs

Notxor
2024-12-26

Llevo unos días aprendiendo un poco más sobre SVG y cómo generar gráficos escribiendo texto. Tengo entre manos un proyecto cuya salida debería generar algún que otro gráfico, principalmente son estadísticas y resultados de algunos cálculos. No son, por tanto, gráficos complicados, más allá de dibujar cuatro figuras: como establecer los ejes y una rejilla de fondo y sobre ellos dibujar unas líneas o unos gráficos de barras. También es posible que haya que dibujar alguna tarta con la distribución de porcentajes y poco más. Mi primer impulso fue utilizar algo sencillo como Pikchr pero, puesto que los informes que iba a generar el chismático saldrán de la churrera directamente en HTML, por qué no utilizar una salida a SVG directa, como si fuera una etiqueta HTML más. Investigando sobre SVG y sus cosas, me tropecé con svg.el, un paquete que viene incluido en Emacs y puesto que ya tengo parte del proyecto prototipado en elisp, me puse a investigarlo. De estas cosas vengo a hablar en este artículo.

Sobre el gráfico SVG

Lo primero que hay que comprender es cómo funcionan las dimensiones del gráfico. Éstas se determinan en la misma etiqueta que envuelve nuestro gráfico, la <svg>...</svg>. El eje de coordenadas comienza por la esquina superior izquierda como punto \((0,0)\). Puesto que mi intención inicial es que cada gráfico se pueda visualizar para imprimirse en un A4 apaisado, las dimensiones físicas serán de \(297mm \times 210mm\).

El código quedaría como:

<svg width="297mm" height="210mm">
  ...
</svg>

Por defecto, las dimensiones dentro de un .svg se cuentan por píxeles o px, por tanto hay que añadir mm para que las unidades se tengan en cuenta. Faltaría indicarle las dimensiones internas con viewBox. Puesto que no necesito demasiada precisión, me bastaría con dividir cada milímetro en tres puntos teóricos lo que serían \(891 \times 630\), por lo que la definición quedará así:

<svg width="297mm" height="210mm" viewBox="0 0 891 630">
  ...
</svg>

Ya, son unas medidas un tanto raras y seguramente no las más fáciles de gestionar. Pero, en general, todos los gráficos llevarán estas dimensiones. En todo caso, si hay que cambiar algo, el valor de viewBox debería mantener la relación \(ancho \times alto\) para no deformar el gráfico y que las escalas, tanto \(x\) como \(y\), sean idénticas.

Elementos básicos de dibujo

En general, cualquier elemento SVG puede dibujarse con el más general path. Sin embargo, tanta flexibilidad acarrea una definición mucho más compleja que no necesito desentrañar en este momento, teniendo en cuenta además, que todo lo que quiero dibujar son algunos elementos muy simples, como líneas, rectángulos y círculos. Prefiero, por tanto ponerle un poco de atención a estos elementos simples, dejando el citado elemento path para otro momento cuando necesite complicar las cosas.

Líneas y polilíneas

La forma más sencilla que se puede dibujar en SVG es una línea recta que va del punto 1 al punto 2. La etiqueta SVG tiene la siguiente forma:

<line x1="núm" y1="núm" x2="nún" y2="num" stroke="color" />

Dibujar algunas líneas escribiendo directamente las etiquetas no es muy complicado. Por ejemplo:

<svg width="200" height="200" viewBox="0 0 200 200">
  <line x1="50" y1="25" x2="175" y2="25" stroke="blue" />
  <line x1="50" y1="50" x2="175" y2="175" stroke="black" />
  <line x1="25" y1="50" x2="25" y2="175" stroke="red" />
</svg>

Se visualizará como1:

Por supuesto, también se pueden especificar colores por sus valores rgb(rojo, verde, azul) y rgba(rojo, verde, azul, opacidad), o mediante los formatos hexadecimales #rrggbb y #rgb o #rrggbbaa y #rgba. También podríamos haber establecido el valor de stroke dentro de un parámetro de style, igual que se puede hacer en HTML.

Las polilíneas son más complejas, en el sentido de que conllevan definir una lista de puntos, que se unirán mediante líneas rectas por estricto orden de definición:

<polyline points="lista de puntos" />

La lista de puntos consiste en una lista de pares de coordenadas, separadas por espacios o comas. Como ambos separadores se pueden mezclar, me gusta o debería decir me resulta más legible a mí en particular. separar un punto de otro con espacios y la coordenada \(x\) de la coordenada \(y\) mediante una coma.

Es importante establecer el valor de fill a none, pues por defecto, SVG rellenará la figura y quizá no de la manera que nos gustaría. Por ejemplo, el siguiente código:

<svg width="200" height="200" viewBox="0 0 200 200">
  <polyline points="0,0 25,100 50,0 75,100 100,0 125,100" style="fill:none; stroke:#0000ff; stroke-width:2;" />
  <polyline points="0,100 25,200 50,100 75,200 100,100 125,200" style="stroke:#ff0000; stroke-width:2;" />
</svg>

se visualizará como:

ambas polilíneas son idénticas, salvo que la segunda está desplazada en \(y\) para visualizarla más abajo y no se ha especificado none como relleno. La visualización es, por tanto, un poco más extraña, pues como se puede observar, une el primer punto y el último y rellena los espacios generados con el color negro, que es el color por defecto.

Rectángulos y círculos

Otras figuras básicas que voy a necesitar son los rectángulos y los círculos. Una diferencia básica entre ellos es que mientras que en el círculo debes proporcionar las coordenadas del centro de la figura, en el rectángulo, la coordenada que estableces es la posición de la esquina superior izquierda.

Además, en el caso del rectángulo, se pueden especificar dos radios para definir la curvatura de las esquinas, tanto en el eje \(x\) como en el eje \(y\). La definición por tanto sería:

<rect x="núm" y="núm" width="núm" height="núm" rx="núm" ry="núm" />
<circle cx="núm" cy="núm" r="núm" />

Como decía, los dos radios(rx y ry) son opcionales, si no se proporcionan, se entiende que su valor es \(0\). Sin embargo, si sólamente se especifica uno, se entiende que el otro tiene un radio idéntico.

Por ejemplo, el código

<svg width="200" height="200" viewBox="0 0 200 200">
  <rect x=10 y=10 width=95 height=95 rx=10 style="fill:#c8c9ff;stroke:black;stroke-width:2"/>
  <rect x=35 y=120 width=40 height=40 style="stroke:blue;fill:none" />
  <rect x=120 y=120 width=40 height=40 rx=10 ry=5 style="stroke:blue;fill:none" />
  <circle cx=100 cy=100 r=25 style="fill:#ffc9ff;stroke:red;stroke-width:2"/>
</svg>

se visualizará como:

Con estos mimbres ya puedo hacer mi cesto.

Usando Emacs

Tengo una prueba de concepto algo avanzada y bastante funcional que se ejecuta sobre Emacs. Para dibujar la salida, me he encontrado con que nuestro editor favorito cuenta, de manera innata con el paquete svg.el, que permite generar código SVG desde el código elisp, sin despeinarse mucho.

La ayuda que he encontrado es un poco escasa, se circunscribe a un apartado del manual de elisp y, además, he notado que algunas cosas que SVG soporta, no están implementadas en svg.el. Pero para lo básico, que es lo que necesito, sí me sirve dicha herramienta.

Un poco de código para poner un ejemplo, generando la misma salida que el último ejemplo de SVG:

(require 'svg)
(let ((mi-svg (svg-create 200 200 :stroke-width 2)))
  (svg-rectangle mi-svg 10 10 95 95 :rx 10
                 :fill-color "#c8c9ff" :stroke-color "black")
  (svg-rectangle mi-svg 35 120 40 40
                 :stroke-color "blue" :fill-color "none" :stroke-width 1)
  (svg-rectangle mi-svg 120 120 40 40 :rx 10 :ry 5
                 :stroke-color "blue" :fill-color "none" :stroke-width 1)
  (svg-circle mi-svg 100 100 25
              :stroke-color "red" :fill-color "#ffc9ff")
  (with-temp-buffer
    (svg-print mi-svg)
    (buffer-string)))
toqueteo.svg
Figura 1: La imagen generada con el código anterior.

Analicemos un poco el código:

  1. La primera línea es una llamada a que vamos a necesitar el módulo svg.el para que lo vaya cargando.
  2. La definición del SVG se realiza con la función svg-create que devuelve un objeto, en este caso mi-svg donde el siguiente código irá dibujando. Además podemos establecer un valor de ancho de línea por defecto. Allá donde no se especifique :stroke-width ese valor será 2.
  3. Por otra parte, en el bloque de código se ha especificado el fichero donde se guardará el resultado. Una vez dibujada cada forma establecemos un buffer temporal donde guardaremos el resultado mediante la función svg-print.

Si se quiere visualizar de una manera más interactiva, podemos ir haciéndolo paso a paso en el REPL. Si lanzamos el comando ielm:

ELISP> (require 'svg)
svg

Visitar un buffer temporal.

ELISP> (find-file "buffer-temporal")
#<buffer buffer-temporal>

Al hacerlo, nos llevará directamente al nuevo buffer. Podemos dividir el marco para tenerlo a la vista mientras escribimos en ielm.

Lo siguiente que debemos hacer es crear el objeto SVG en el que dibujaremos:

ELISP> (setf mi-svg (svg-create 200 200 :stroke-width 2))
(svg
 ((width . 200) (height . 200) (version . "1.1")
  (xmlns . "http://www.w3.org/2000/svg")
  (xmlns:xlink . "http://www.w3.org/1999/xlink") (stroke-width . 2)))

Una vez que lo hemos creado, podemos asociarlo con nuestro buffer temporal:

ELISP> (svg-insert-image mi-svg)
((:image . #<marker at 1 in buffer-temporal>) (width . 200)
 (height . 200) (version . "1.1")
 (xmlns . "http://www.w3.org/2000/svg")
 (xmlns:xlink . "http://www.w3.org/1999/xlink") (stroke-width . 2))

A partir de aquí, cuando modifiquemos algo desde el ielm lo iremos visualizando directamente en nuestro buffer.

ELISP> (svg-rectangle mi-svg 10 10 95 95 :rx 10 :fill-color "#c8c9ff" :stroke-color "black")
nil

Como ilustración del modo de trabajo, se puede observar la siguiente imagen:

Captura_dibujando-con-ielm.png
Figura 2: Captura de la ventana de Emacs con tres buffers abiertos. El texto del borrador de este artículo a la izquierda, el buffer temporal arriba a la derecha y abajo a la derecha el ielm donde se introducen todos los comandos.

Para dibujos temporales, no necesitamos más, pero si lo que queremos es guardar nuestro dibujo, tenemos que conseguir la cadena SVG generada por el módulo. La forma más rápida que he encontrado es escribir el SVG en el buffer temporal mediante:

ELISP> (svg-print mi-svg)
nil

Luego iremos a dicho buffer y guardaremos el archivo con C-x C-w poniéndole el nombre que queramos.

Conclusiones

No es una forma cómoda de dibujar y si lo que quieres conseguir son dibujos mucho más complejos, te recomiendo que utilices Inkscape para hacerlo, ganarás en tiempo y en salud.

Sin embargo, para generar gráficos sencillos desde Emacs, el paquete svg.el, que por otro lado ya está instalado con nuestro editor, nos sirve para realizar algunas imágenes o programar alguna salida visual para nuestros datos.

Notas al pie de página:

1

En la visualización he añadido un marco blanco de fondo para facilitar mejor la visualización. El código SVG está directamente escrito en el HTML y se visualiza sin necesidad de enlazar una imagen.

Categoría: svg emacs

Comentarios

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en esta cuenta de Mastodon, también en esta otra cuenta de Mastodon y en Diaspora con el nick de Notxor.

Si usas habitualmente XMPP (si no, te recomiendo que lo hagas), puedes encontrar también un pequeño grupo en el siguiente enlace: notxor-tiene-un-blog@salas.suchat.org

Disculpen las molestias.

Escrito enteramente por mi Estulticia Natural sin intervención de ninguna IA.