QML y gráficas
QML y gráficas
Introducción
El objetivo de este capítulo es mostrar la forma de dibujar gráficas usando QML + JavaScript en una aplicación para Ubuntu Touch (UBPorts).
Requisitos
Para seguir este capítulo hay que tener configurado el kit "Desktop" en el SDK de Ubuntu y tener un conocimiento básico de QML y JavaScript. Para tener más información puedes consultar los primeros capítulos del curso.
También es necesario tener unos conocimientos básicos del lenguaje SQL y de la forma de acceder a una base de datos SQLite desde QML. Si no conoces la forma de hacerlo puedes consultar el capítulo anterior.
Premisas
Este capítulo usa la aplicación de ejemplo que se creó en el capítulo anterior relacionada con QML y el acceso a una base de datos SQlite. De forma resumida, la aplicación de ejemplo se llamaba "WeatherRecorder" y se programó para guardar y gestionar los valores de temperatura de una ciudad.
Ahora, en este capítulo, se mejorará la aplicación para añadir una función de analítica: mostrar las temperaturas de un mes en una gráfica.
Gráficas con QML
La solución presentada utiliza la librería JavaScript de código abierto Chart.js como librería de dibujo que utiliza los nuevos elementos HTML5. Para usarla hay que usar un "binding", que es una librería intermedia entre Charts.js y el código QML. Este binding utiliza otra librería de código abierto llamada qchart.js. qchart.js define un componente QML personalizado que muestra una gráfica que envuelve el objeto Chart.js
Hay que agradecer a Jwintz, el autor de qchart.js, el gran trabajo que ha realizado. No es necesario conocer a bajo nivel el funcionamiento de la librería para poder usarla.
Configuración del proyecto para usar gráficas
Si importas la aplicación de ejemplo en el IDE de Ubuntu se pueden saltar los siguientes pasos. En el caso de crear una aplicación nueva de QML desde cero hay que añadir los archivos para poder representar gráficas.
Para dibujar gráficas desde la aplicación QML es necesario incluir dos archivos (se pueden encontrar todos los archivos necesarios en https://github.com/jwintz/qchart.js):
Qchart.js
Es la librería de dibujo de JavaScript que contiene las funciones que permiten dibujar la gráfica, calcular la escala de los ejes, etc. Es una versión de la librería Chart.js empaquetada con la librería "qchart.js" (seguramente con alguna modificación adicional).
Para usar la librería hay que importarla:
Nota: la sintaxis de importación anterior es la misma que se usa para importar un archivo externo de JavaScript en QML.
QChart.qml
Define un nuevo componente QML (llamado "QChart") que se puede usar en el código QML para dibujar una gráfica. Se usa en un bloque de código parecido al siguiente:
Como último paso hay que incluir los nuevos archivos en el archivo .rqc (es un archivo del proyecto que define todos los archivos que componen el proyecto). A partir de este punto la aplicación QML puede dibujar gráficas.
Introducción a la aplicación de ejemplo
Como se ha comentado antes, se usará como base la aplicación programada en el capítulo anterior y se mejorará. Para mantener separados los códigos fuentes de ambas aplicaciones, y evitar confusiones, se ha creado una aplicación nueva llamada "WeatherRecorderChart". Esta aplicación usa como base la aplicación "WeatherRecorder".
Si importas el proyecto "WeatherRecorderChart" en el IDE de Ubuntu, se verá una imagen similar a la siguiente:
Al ejecutar la aplicación por primera vez (pulsando el botón verde con el icono de "Play") se mostrará la pantalla de configuración de la aplicación:
Al aceptar (o actualizar) la configuración que propone la aplicación se llegará a la nueva pantalla inicial:
Nota: la aplicación sólo muestra la pantalla de configuración en el primer inicio (se ha descrito lo que ocurre cuando la aplicación se inicia en el capítulo anterior).
Importante: tanto la aplicación nueva como la antigua usan una base de datos SQLite para guardar los valores de temperatura. La localización del archivo de la base de datos es diferente, ya que la ruta está relacionada con el campo "applicationName" que hay en el archivo Main.qml. Por favor, revisa el capítulo anterior para conocer más detalles.
Si se compara con la aplicación "WeatherRecorder", se puede distinguir un nuevo apartado al final de la pantalla llamado "Analytic section". Al pulsar en el botón "Chart" aparece una nueva pantalla que permite elegir el mes y visualizar una gráfica de barras con los valores de las temperaturas:
En la parte derecha se encuentra la leyenda de la gráfica, que muestra los valores que se han usado para dibujar la gráfica. Si no se encuentra un valor, la gráfica se muestra vacía en ese día (éste es el caso cuando el mes no tiene ninguna temperatura guardada). Introduce algún valor de temperatura e intenta visualizar de nuevo la gráfica.
Detalles de la nueva aplicación
En el apartado anterior se ha mostrado la nueva pantalla que contiene la gráfica. En este apartado se describirán las modificaciones en el código original de forma más detallada.
En el interfaz de usuario hay una nueva sección, cuyo código se puede encontrar en el archivo Main.qml. Esta sección está formada por dos componentes Row con identificadores "chartSectionHeader" y "chartRow" (se pueden buscar con CTRL + F) que se han añadido al componente que ya existía.
Para tener una pantalla separada para la gráfica, y su leyenda, se ha añadido un componente nuevo: PageStack. Este componente permite navegar entre pantallas, es decir, actúa como una pila (stack). La pantalla que está en la parte superior de la pila es la que se muestra. Para añadir o eliminar una pantalla de la pila se usan los métodos "push" y "pop".
Cuando se inicia la aplicación, es necesario añadir (push) la primera pantalla en el componente PageStack. Ésto se realiza en el evento "onComplete" del propio componente. Puedes consultar el capítulo anterior para tener más información sobre el funcionamiento del evento "onComplete".
La inicialización se hace con el siguiente código:
Con el código anterior se añade (push) en la pila la pantalla con el id "mainPage". Este identificador es el que tiene la pantalla inicial de la aplicación. Prueba a buscar el id en el código.
Ahora se puede buscar el punto de entrada a la pantalla de la gráfica: el botón "Chart". Su código, que se encuentra en el archivo Main.qml es el siguiente:
Cuando el usuario pulsa el botón (es decir, se dispara el evento "onClick") se añade una nueva pantalla a la parte superior de la pila del componente PageStack. Al hacerlo se remplaza la pantalla que tuviera por la pantalla nueva. ¿Qué pantalla se muestra? Es la pantalla con el id "chartPage". Prueba a pulsar CTRL sobre la variable pageStack para acceder al código. Otra opción es buscar el código con CTRL + F).
Al usar cualquiera de los dos métodos anteriores se llegará al siguiente código:
Esa sentencia define e importa un componente personalizado que está contenido en el archivo ChartPage.qml (prueba a pulsar CTRL + botón izquierdo sobre ChartPage{} para abrir el archivo).
Nota: el listado 03 muestra el componente personalizado, que está definido en un archivo externo y se puede importar en otros archivos QML. En nuestro caso, el componente QML es la pantalla (Page) con el id "chartPage".
En ChartPage.qml se encuentra la siguiente implementación:
La pantalla chartPage en detalle
El flujo de navegación de la aplicación ya está explicado. Ahora nos podemos centrar en la pantalla que crea la gráfica, cuyo código fuente se encuentra en el archivo ChartPage.qml.
Nota: se ha omitido parte del código porque contiene componentes que ya se han presentado antes en el curso (selector de fecha, botones, etc.).
La lógica de ejecución comienza cuando el usuario pulsa el botón "Show Chart". El código que se ejecuta cuando el evento "onClick" se dispara es el siguiente:
Cuando el usuario elige un mes en el selector de fechas, se guarda este valor en la variable de JavaScript llamada "chartPage". La variable guarda la fecha en el formato yyyy-mm-dd (año, mes, día). Puedes consultar los detalles en el componente "popoverTargetMonthPicker".
Después de dividir la fecha (ver el listado 05), se crea un objeto Date de JavaScript. Se usarán los métodos nativos de este objeto para calcular el primer y el último día del mes elegido. Estos cálculos se tienen que hacer en tiempo de ejecución porque no se conoce inicialmente la elección del usuario.
Los días primero y último del mes se pasan como parámetros en el método de JavaScript "getChartData", que crea el dataset XY para la gráfica:
El método devuelve un dataset que se guarda en el campo "chartData" del componente QML "temperatureChart" (que corresponde a la gráfica):
El listado 07 muestra la declaración del componente QML de la gráfica, que está definido en Qchart.js y se ha descrito al principio del capítulo. Observa cómo se ha indicado que es una gráfica de barras.
Creación de la gráfica en detalle
Lo que ocurre a nivel de componente QML se ha descrito en el apartado anterior. Ahora se estudiará el código JavaScript que configura el dataset de la gráfica.
El archivo ChartUtils.js (que se ha importado en el archivo QML) contiene todos los métodos que se han usado para crear la gráfica de temperatura. Observa que el archivo ChartUtils.js se ha creado en esta aplicación y NO pertenece a ninguna librería.
Revisemos el archivo.
El punto de entrada es el método function getChartData(fromDate, toDate)
, que se llama cuando se pulsa el botón "Show Chart". En ese método se calculan el día inicial y el día final del mes.
El método se divide en varios pasos:
Paso 1
En el primer paso se calculan los días que tiene el mes elegido (de 28 a 31) usando el método "getDifferenceInDays" que está definido en el archivo DateUtils.js:
Paso 2
Se prepara un array de JavaScript (con una pareja clave - valor) cuyo tamaño es el número de días. Ésta será la base para el dataset de la gráfica. La clave en el array es la fecha del mes elegido mientras que el valor es el valor de la temperatura. Todos los valores de entrada se inicializan a 0 (cero). Esta decisión es necesaria para gestionar los casos en los que no hay un valor, por ejemplo, el usuario ha olvidado introducirlo. Los valores que no tienen temperatura se mostrarán como "N/A" en el interfaz de usuario.
Nota: la elección de "cero" es una decisión de diseño, pero es posible elegir otro valor. El código del paso 2 es el siguiente:
Paso 3
En el tercer paso se extraen, de la base de datos SQLite, los valores de temperatura del mes elegido. Después se actualizan los valores clave - valor del array que se ha preparado en el apartado anterior. Para conocer la forma de acceder a una base de datos de SQLite usando QML te invitamos a leer el capítulo anterior.
Esta acción la realiza el método de JavaScript function updateXYdataset(fromDate, toDate, xyDataSet)
. Se omite el código completo para simplificar la explicación.
Cuando ha finalizado la actualización del array, el dataset de la gráfica contendrá valores diferentes de cero en los días que ha actualizado el usuario. En los otros valores se mostrará el valor por defecto que se asignó en la inicialización. Ahora ya está preparado el dataset XY para la gráfica.
Paso 4
En el cuarto paso hay que separar los valores X e Y en dos conjuntos distintos. Para hacerlo se utilizan los métodos de JavaScript:
function getXaxisValue(xyDataSet)
: extrae las claves del array, corresponde a los valores de las X.function getYaxisValue(xyDataSet)
: extrae los valores del array, corresponden a los valores de las Y.
Esta división del dataset es necesaria porque el objeto JavaScript que utiliza la librería de gráficas trabaja de esta forma (ver el siguiente paso).
Paso 5
En el último paso se crea el objeto JavaScript que se asigna al componente QChart (se ha declarado en el listado 7).
La asignación al componente QChart se hace con la sentencia temperatureChart.chartData = ChartUtils.getChartData(firstDayMonth,lastDayMonth);
donde "temperatureChart" es el identificador de nuestro componente QChart (listado 7). "charData" es un campo que tiene los datos del dataset.
Nota: en algunas ocasiones, cuando el dataset de la gráfica se conoce previamente o no depende de elecciones del usuario (el mes elegido en nuestro caso), se puede inicializar el campo "chartData" cuando se declara el componente QChart (en el listado 7) en lugar de hacerlo después.
Leyenda de la gráfica
En la parte derecha de la gráfica se muestra una leyenda con los valores XY que se han usado para dibujar la gráfica. No es obligatorio usarla, se ha añadido para completar la gráfica y dar al lector nuevas ideas para personalizar la gráfica.
La leyenda se crea en ChartPage.qml usando un componente ListModel de QML. Este componente actúa como un contenedor para los valores que hay que mostrar. El modelo se rellena en el siguiente método (ver ChartUtils.js).
Se itera sobre todos los valores XY del dataset y se extraen los valores (como un objeto clave - valor de JavaScript) para usarlos en el ListModel. Os invitamos a consultar la documentación oficial para tener más información acerca de ListModel. Se ha elegido no profundizar en el funcionamiento porque se queda fuera del objetivo de este capítulo.
Para mostrar los valores de un ListModel se utiliza el componente UbuntuListView Component. La implementación y la configuración del componente UbuntuListView se puede encontrar en el archivo ChartPage.qml. Aquí se pueden ver las partes más importantes, se ha omitido el resto por brevedad.
Sólo un par de detalles sobre el funcionamiento de UbuntuListView: recibe una entrada (con un campo llamado "model)") que contiene los valores a mostrar y un componente QML con el layout que se utilizará para mostrarlos en la pantalla (campo "delegate"). El componente "delegate" accede a los valores del ListModel usando las claves que se han usado al guardarlos ("date" y "temp", en el listado 11).
Conclusiones
Se ha mostrado como dibujar gráficas utilizando una aplicación con QML + JavaScript que usa una base de datos SQLite. Para conseguirlo se han tomado algunas decisiones de diseño, como usar una gráfica de barras o el valor "cero" en los valores de temperatura que estaban vacíos. Puede ser que algunas decisiones sean discutibles pero el objetivo es proporcionar al lector un conocimiento acerca de la forma de dibujar gráficas con QML y sugerir nuevas ideas para nuevas aplicaciones.
Como ejercicio, el lector puede probar otro tipo de gráficas o añadir otra gráfica para mostrar los valores de presión de la ciudad...
Invitamos al lector a consultar el código fuente (y los comentarios) de la aplicación para tener más detalles del funcionamiento
Referencias
Algunos enlaces relacionados con los conceptos explicados en este capítulo:
Personas que han colaborado
Fulvio Russo: autor.
Miguel Menéndez: traducción al español.
Last updated