El último paso de la corrección del test:
Como se puede ver, el experimento ha terminado. Emacs aún siendo un editor de texto tiene ciertas capacidades gráficas cuando no lo lanzamos para la consola.
El gráfico
En el pasado «articulillo» un poco de las capacidades gráficas de
Emacs aunque me guardé la parte sobre SVG para hablarlo cuando
terminara el experimento con la generación del gráfico final. Cuando
pensé originalmente esta parte utilizaba siempre Emacs en consola
con la opción -nw
, y no me percaté de esas capacidades hasta más
adelante... una grata sorpresa.
Mi primera idea fue que el programa lo dibujara todo, pero me pareció
un poco chapuza reiterativa y opté por coger un gráfico SVG y ponerle
en su interior algo que permitiera dibujar la línea de forma
automática en base a los datos. A base de utilizarlo sólo para texto y
en consola desconocía las capacidades gráficas de Emacs y había
pensado que al fin y al cabo un SVG es un archivo XML. Pensé en lo
sencillo que sería abrir un fichero de texto (XML en este caso)
modificar los valores oportunos y guardarlo para abrirlo con otra
aplicación o visor. Se pondrían etiquetas del estilo {{etiqueta}}
en
los lugares adecuados y con una simple regla de tres se calcula la
coordenada Y de cada punto en la línea.
Sin embargo, he tenido algunos problemas para hacerlo. Las coordenadas de los puntos y los ejes me han obligado a repasar varias veces los algoritmos de cálculo y cómo procesarlos. En este caso no me ha servido la socorrida regla de tres que esperaba hacer, ha sido todo algo más complejo aunque no lo suficiente para desanimarme.
Os cuento los pasos uno por uno:
Cargando datos
La primera parte de la función se ocupa de cargar datos y preparar variables que luego se utilizarán en los cálculos. Carga la plantilla en un buffer y lo renombra creando un fichero nuevo.
(defun minimult-ver-grafica ()
"Dibuja la gráfica del MiniMult."
(interactive)
;; Guardamos el estado del buffer actual
(save-excursion
(let ((nombre (concat "MiniMult-"
(org-entry-get (point) "Nombre" t) ".svg"))
(sexo (org-entry-get (point) "Sexo" t))
(L (org-entry-get (point) "L" t))
(F (org-entry-get (point) "F" t))
(K (org-entry-get (point) "K" t))
(Hs (org-entry-get (point) "Hs" t))
(D (org-entry-get (point) "D" t))
(Hy (org-entry-get (point) "Hy" t))
(Pd (org-entry-get (point) "Pd" t))
(Pa (org-entry-get (point) "Pa" t))
(Pt (org-entry-get (point) "Pt" t))
(Sc (org-entry-get (point) "Sc" t))
(Ma (org-entry-get (point) "Ma" t))
(Si (org-entry-get (point) "Si" t))
(delta-control 0)
(valor-antes 0)
(buffer (find-file-noselect "~/proyectos/minimult/plantilla.svg")))
(set-buffer buffer)
;; Activamos escritura en el buffer
(setq inhibit-read-only t)
;; Guardar la plantilla con otro nombre
(set-visited-file-name nombre)
;; Ir al principio del buffer
(goto-char (point-min))
;; Hacer las sustituciones
(if (search-forward "{{sexo}}")
(replace-match sexo))
(if (search-forward "{{L}}")
(replace-match L))
...
(if (search-forward "{{Si}}")
(replace-match Si))
...)
Lo primero que hace la función es guardar la posición del punto en el
fichero org que estamos trabajando con save-excursion
por si se
mueve. Luego, en el let
se cargan las variables obteniéndolas de las
propiedades del org. También carga un fichero de plantilla desde el
disco. Resumo la parte de reemplazar los números que aparecen abajo en
el gráfico con los valores de las escalas. La etiqueta {{L}}
se
sustituye con el valor de la variable L
que hemos cargado en el
let
; y se hace de forma directa porque L
es un string
.
Coordenadas Y de los puntos del perfil
La conversión del valor que cada escala ha alcanzado en las coordenadas era –en principio– una aplicación más de la fórmula para ajustar intervalos que ya vimos en otro artículo anterior. Pero puede parecer más complicado porque el eje Y interno del SVG gráfico crece hacia abajo, mientras que los valores representados en él crecen hacia arriba. Es decir, están invertidos uno respecto al otro.
Por ejemplo, la cota de la puntuación 20 de una escala estará en el 495.5 y la
cota de la puntuación 120 en el 31.5. Aún así se podría haber
utilizado la misma función para hacer los cálculo aunque hubiéramos
pasado al parámetro min-t
el valor 495.5 y al parámetro max-t
el
valor 31.5.
Para ahorrar tiempo de cálculo y como todos los datos son siempre los mismos decidí resumir la función a otra más sencilla:
(defun minimult-calcular-perfil (puntos)
"Calcula la posición en el gráfico de una puntuación."
(+ 495.5 (* -4.641 (- puntos 20.0))))
Después de devolver el punto al principio del buffer para realizar una nueva búsqueda se realizan los cálculos y sustituciones. Pongo sólo los de las tres escalas de control para no eternizarme con un churro de código, pero con ello puedo explicar uno de los muchos fiascos que me he llevado haciendo el gráfico.
;; Ir al principio del buffer
(goto-char (point-min))
;; Calcular la posicion de las escalas de control
(setq delta-control (minimult-calcular-perfil (string-to-number L)))
(if (search-forward "{{dL}}")
(replace-match (number-to-string delta-control)))
(setq valor-antes delta-control)
(setq delta-control (minimult-calcular-perfil (string-to-number F)))
(if (search-forward "{{dF}}")
(replace-match (number-to-string (- delta-control valor-antes))))
(setq valor-antes delta-control)
(setq delta-control (minimult-calcular-perfil (string-to-number K)))
(if (search-forward "{{dK}}")
(replace-match (number-to-string (- delta-control valor-antes))))
Como se puede apreciar, los cálculos que se realizan para unas escalas
no sirven para otras. Eso depende de si el punto que queremos calcular
es el que inicia la línea o es una continuación. ¿Por qué? Pues
porque los valores de esos puntos no son absolutos sino relativos. El
primer punto de la línea (y hay tres que debemos calcular en el
gráfico) tiene un valor absoluto. Por eso, una vez calculada su
coordenada Y guardándola en la variable delta-control
la
reemplazamos directamente. Bueno directamente tampoco, recordemos que
los datos que se cargaban desde el org son cadenas y necesitamos
convertirlos en números para operar con ellos, para eso utilizo
string-to-number
y cuando los queremos escribir tenemos que
devolverlos como number-to-string
.
Como decía, el primer punto de cada línea tiene unas coordenadas
(x,y)
absolutas. El siguiente punto en la línea, sin embargo, se
define utilizando el anterior como si fuera el origen de las
coordenadas. Esto me obliga a calcular la posición de cada punto
guardando el valor anterior (valor-antes
) para restarlo a la
posición calculada en delta-control
. Este proceso se repite también
para las otras dos líneas. La idea de pasarle a una función una lista
con los valores y que los fuera calculando y sustituyendo se truncó y
el código ha quedado hecho un chorizo que necesita una
refactorización de manera urgente.
La última chapuza es el refresco del buffer del SVG. Cuando cargo la
plantilla la imagen se dibuja sin las líneas del perfil y sin los
textos cambiados. He estado haciendo un búsqueda de cómo hacer un
refresco gráfico. Como no encontré nada lo que hago es un refresco a
las bravas (o a lo tonto)... llamo dos veces seguidas a
image-toggle-display
la primera cambia el buffer a modo texto y la
segunda lo devuelve a modo gráfico y después cambio al buffer del
gráfico en otra ventana:
;; Reseteamos el buffer pasándolo a modo texto y de nuevo a gráfico
(image-toggle-display) ;; es un poco chapuza pero no encuentro
(image-toggle-display) ;; cómo hacer el refresco de otro modo
;; Mostramos el buffer en otra ventana
(switch-to-buffer-other-window buffer)
Conclusiones
La verdad es que he estado aprendiendo mucho. El programa en sí es una chapuza (siendo suave con la terminología), aunque funciona como versión pre-pre-pre-alfa de lo que debería ser.
Acabo de decir que funciona pero no lo hará salvo en momentos controlados. Porque no se ha hecho prácticamente ninguna comprobación para que todo funcione correctamente. Por ejemplo, no se puede saber qué ocurriría si se llamara a la función de corrección sin tener una variable de respuestas completa. Tampoco se han tenido en cuenta los límites de las escalas. ¿Qué ocurriría si llegara un valor por debajo del mínimo calculado para una escala? ¿o por encima del máximo? Esto último puede pasar en varias escalas que se ajustan mediante K. Se puede dar el caso de una puntuación alta en la escala en cuestión se junta con una puntuación alta en K y el valor puede sobrepasar el límite máximo (vale, en la vida real sería un caso excepcional, pero podría darse).
Seguramente, si empezara a hacerlo ahora, lo plantearía de otra manera. Lisp es un lenguaje sencillo de entender: (casi) todo es una lista encerrada entre paréntesis, Lisp intenta evaluar el primer elemento de una lista con los demás como parámetros, a no ser que tenga una comilla (quote) delante. Eso lo pillas en medio minuto pero luego la forma de pensar los algoritmos es completamente distinta a los lenguajes en los que me encuentro más cómodo. La POO debe de haber anquilosado mis neuronas. Las ha dejado rígidas y sin cintura para flexibilizar mi razonamiento al programar.
Pero creo que poco a poco me iré acostumbrando a los usos y costumbres de Lisp. De hecho, me parece un lenguaje bonito e interesante. Si reflexiono un poco sobre los lenguajes que más me gustan lo pondría en segundo lugar, justo después de smalltalk. Los dos primeros de la lista son pues lenguajes muy «viejunos». No sé si será por la edad o no, pero tienen chispa. En smalltalk todo es un objeto y en lisp todo es una lista y sobre ese supuesto se montan todo un sistema completo sin necesitar una interminable lista de palabras reservadas del lenguaje, sólo un supuesto y sus consecuencias lógicas.