Dibujando figuras con Emacs
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)))
Analicemos un poco el código:
- La primera línea es una llamada a que vamos a necesitar el módulo
svg.el
para que lo vaya cargando. - La definición del SVG se realiza con la función
svg-create
que devuelve un objeto, en este casomi-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. - 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:
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:
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.
Comentarios