Comunidad de diseño web y desarrollo en internet

Gráficos estadísticos en Flex con LineChart y TabNavigator

A muchos nos gustaría saber como juntar la acción de estos componentes en una aplicación real de ingeniería. Tuve la oportunidad de desarrollar este pequeño programa para la obtención de perfiles de profundidad en el análisis de resistencia de suelos, o resistividad eléctrica de terrenos, y pues dado que aprendí gran cantidad de conceptos mientras lo hice, pues les comparto mi experiencia. Trataré de detallarlo a pedazos y al final público el código completo. Para entender mejor el ejemplo tendré que explicar algunos sencillos conceptos de electricidad que se usan en el procedimiento. No tiene conexión a base de datos o algún servicio, así que puedo asegurarles que funcionará perfecto en sus PCs.

El sistema andando, entregando el perfil de profundidad (para algunos datos ficticios), luce así:

En la gráfica hay 3 curvas, dos de ellas (la azul y la verde) pertenecen a resistividades calculadas en dos regiones diferentes del terreno (N-S significa de Norte a Sur, W-E significa Este – Oeste), la curva naranja pertenece al promedio de estas dos curvas. El eje horizontal corresponde a la profundidad del suelo; así entonces por ejemplo en el punto indicado donde se cruzan las 3 curvas, a una profundidad de 3.75m, tenemos 502.65 ohmios-metro.

Pero vamos entonces a lo que nos interesa. Primero vamos a indicarle al sistema los 8 puntos de resistencia tomados de norte a sur, y los otros 8 de este a oeste. Lo vamos a hacer a través de componentes TextInput. El formato de inserción de datos debe quedarnos así:

Concentremos la mirada en las dos columnas de la derecha. Son sencillos TextInput puestos en cierto orden que nos permiten (a los que trabajamos en ingeniería eléctrica) relacionar los campos con un valor de profundidad ya prevista junto con una separación de electrodos. Son entonces, 8 medidas de resistencia de Norte a Sur, y otras 8 de Este a Oeste. La nomenclatura usada para los TextInput es: para los de norte a sur los llamamos rns_1, rns_2, etc. hasta rns_8; lo análogo para los de este a oeste: rwe_1, rwe_2… hasta rw8. El código para estos TextInput dentro de un Panel es:

<mx:Panel x="10" y="10" width="496" height="296" layout="absolute" id="mypanel0" title="Ingreso de Datos">
 <mx:TextInput x="224.25" y="19" width="58" id="rns_1" textAlign="center"/>
 <mx:TextInput x="224.25" y="49" width="58" id="rns_2" textAlign="center"/>
 <mx:TextInput x="224.25" y="79" width="58" id="rns_3" textAlign="center"/>
 <mx:TextInput x="224.25" y="109" width="58" id="rns_4" textAlign="center"/>
 <mx:TextInput x="224.25" y="139" width="58" id="rns_5" textAlign="center"/>
 <mx:TextInput x="224.25" y="169" width="58" id="rns_6" textAlign="center"/>
 <mx:TextInput x="224.25" y="199" width="58" id="rns_7" textAlign="center"/>
 <mx:TextInput x="224.25" y="229" width="58" id="rns_8" textAlign="center"/>
 <mx:TextInput x="320.25" y="19" width="58" id="rwe_1" textAlign="center"/>
 <mx:TextInput x="320.25" y="49" width="58" id="rwe_2" textAlign="center"/>
 <mx:TextInput x="320.25" y="79" width="58" id="rwe_3" textAlign="center"/>
 <mx:TextInput x="320.25" y="109" width="58" id="rwe_4" textAlign="center"/>
 <mx:TextInput x="320.25" y="139" width="58" id="rwe_5" textAlign="center"/>
 <mx:TextInput x="320.25" y="169" width="58" id="rwe_6" textAlign="center"/>
 <mx:TextInput x="320.25" y="199" width="58" id="rwe_7" textAlign="center"/>
 <mx:TextInput x="320.25" y="229" width="58" id="rwe_8" textAlign="center"/>
</mx:Panel>

Vamos a construir de una vez el navegador con pestañas TabNavigator, un sistema muy sencillo y eficiente para organizar información en espacios pequeños. La aplicación tiene 3 áreas para mostrar al usuario, una con la pestaña ‘Valores de Resistencia’, otra ‘Grilla de Cálculos’ y la otra ‘Perfiles de Resistividad’, que ya vimos arriba en acción. La clave es, luego de abrir el componente, usar en el interior elementos ‘Canvas’ que nos van a contener todo lo visible en cualquiera de las 3 áreas de mostrar al usuario. Entonces en este caso, requerimos 3, así:

 <mx:TabNavigator width="518" height="349" id="bigcontainer" selectedIndex="0" horizontalCenter="8" top="24">
   <mx:Canvas label="Valores de Resistencia" width="100%" height="100%">
    <!-- Aquí va el contenido de la interface 1 -->
   </mx:Canvas>
   <mx:Canvas label="Grilla de Calculos" width="100%" height="100%">
    <!-- Aquí va el contenido de la interface 2 -->
</mx:Canvas>
   <mx:Canvas label="Perfiles de Resistividad" width="100%" height="100%">
    <!-- Aquí va el contenido de la interface 3 -->

   </mx:Canvas>
 </mx:TabNavigator>

En cada uno de estos estados vamos a incluir los componentes que necesitamos. Se supone que al hacer clic en la pestaña correspondiente se mostrarán los componentes contenidos en el Canvas actual del TabNavigator. No hay que hacer nada especial, el componente se encarga de todo.

Entonces, voy a detallar rápidamente los componentes que hay en los 3 Canvas del TabNavigator:

Canvas ‘Valores de Resistencia’:

  • Un contenedor Panel (solo para fines estéticos)
  • 16 TextInput
  • 16 Label para indicar cierta información

Canvas ‘Grilla de Cálculos’:

  • Un Panel
  • Un Datagrid con 7 columnas

Canvas ‘Perfiles de Resistividad’

  • Un Panel
  • Un componente LineChart para graficación
  • Un componente Legend para acompañar al LineChart

Toda la configuración de estos componentes puedes encontrarla abajo en el código y analizarla por ti mismo, no es tan complicada.

En el Canvas ‘Grilla de Cálculos’ tenemos un componente DataGrid enlazado a un ArrayCollection que le suministra la información. Esto se indica en la propiedad dataProvider="{mydata}" del componente DataGrid. Aquí vemos entre ‘llaves’ a la variable mydata. Esto sucede ya que queremos que cualquier cambio en el contenido de mydata se transmita inmediatamente al DataGrid. Para hacer eso, en alguna parte del ActionScript tenemos que indicar que ‘mydata’ es [Bindable], o, vinculable. El DataGrid nos queda:

<mx:DataGrid x="6" y="10" width="464" height="224" id="myGrid" dataProvider="{mydata}">
 <mx:columns>
   <mx:DataGridColumn headerText="Prof.(m)" dataField="profundidad" headerStyleName="center" textAlign="center"/>
   <mx:DataGridColumn headerText="Sep.(m)" dataField="separacion" textAlign="center"/>
   <mx:DataGridColumn headerText="R-NS" dataField="resistencia_ns" headerStyleName="center" textAlign="center"/>
   <mx:DataGridColumn headerText="R-EO" dataField="resistencia_we" textAlign="center"/>
   <mx:DataGridColumn headerText="p-NS" dataField="resistividad_ns" textAlign="center"/>
   <mx:DataGridColumn headerText="p-EO" dataField="resistividad_we" textAlign="center"/>
   <mx:DataGridColumn headerText="Prom." dataField="promedio" textAlign="center"/>
 </mx:columns>
</mx:DataGrid>

Prestemos atención a los ‘dataField’. Cada uno de estos campos va a estar enlazado a cada uno de los componentes de la variable ‘mydata’, es decir, en la estructura de mydata, vamos a encontrar a ‘profundidad’, ‘separacion’, ‘resistencia_ns’, etc. El resto de parámetros son solo ajustes de visualización.

Este señor debe verse al final así:

Las dos primeras columnas son valores puestos ‘a mano’ en la estructura de mydata. Ya lo veremos en el ActionScript. Las dos siguientes se absorben de los TextInput (las columnas R-NS y R-EO que significan Resistencia Norte-Sur y Este-Oeste). Las p-Ns y p-EO (resistividades norte-sur, y este-oeste) son valores que se calculan en el momento en que se presionan las pestañas del TabNavigator a través del evento change del mismo; este evento change aparece en la línea donde están las propiedades del TabNav. La función puede verse en el código y se llama ‘calculate()’.

En el ActionScript empezamos entonces con importar la clase que nos permite trabajar con ArrayCollections, y luego, definir el objeto usando [Bindable] como anunciamos arriba. Junto con esto, hay una función que creé llamada init() que deseo corra apenas la aplicación sea cargada (solo quiero que mydata se inicialice una vez). Para esto introducimos el evento creationComplete="init()" en el <mx:Application ...

    import mx.collections.ArrayCollection;

    [Bindable] 
    private var mydata:ArrayCollection; 
    private function init():void {
      mydata = new ArrayCollection();
    }  

<mx:Application ... blablabla queda así:

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()">

Las funciones

Tenemos en total 4 pequeñas funciones que nos permiten hacer lo que necesitamos, y son:

  • init()
  • calculate()
  • setNewItem()
  • myround()

Vamos una por una:

init()

Ya hablamos de ella arriba. Nos permite inicializar el proveedor de datos del sistema (mydata) en el momento de la carga de la aplicación.

calculate()

No es precisamente la función que ejecuta los cálculos Riendo, pero se encarga de llamar 8 veces a la función que si lo hace (setNewItem()) y también, antes de eso, limpia el proveedor de datos con removeAll().

setNewItem()

Esta si es la que ejecuta los cálculos. A partir de las resistencias enviadas desde calculate(), se determinan las resistividades a través de una fórmula sencilla (2*PI*SeparacionElectrodos*Resistencia). Como sólo queremos dos decimales luego de la coma, se hizo una función llamada round() que se encarga de eso. Se pudo haber usado otro método pero creí este como el más conveniente para conservar el tipo de datos en la estructura de mydata.

round()

Ya quedó explicado arriba, redondea el número que se le envía a dos decimales.

Y para finalizar, el plato fuerte, el LineChart.

Hay varios componentes Chart en el IDE de Flex, yo decidí usar el LineChart ya que era el que más se ajustaba al objetivo. Hay algunas cosas en la configuración que considero importantes para mencionar:

  • LineChart requiere de un proveedor de datos. En este caso lo vinculamos a mydata quien suministra los datos en toda la aplicación. Lo puedes observar en la línea del LineChart:
<mx:LineChart x="6" y="2" id="mychart" height="220" width="464" dataProvider="{mydata}" showDataTips="true">
  • Al ajustar showDataTips en ‘true’ como se puede ver arriba, podemos obtener los tooltips en los puntos de muestreo ingresados.
  • Es bueno detallarle al LineChart quien va a a proveer los datos del eje horizontal. Esto se hace con mx:horizontalAxis en la forma:
<mx:horizontalAxis>
 <mx:CategoryAxis categoryField="profundidad"/>
</mx:horizontalAxis>

Efectivamente, el campo ‘profundidad’ (de la estructura mydata) es el que queremos que proporcione los datos del eje horizontal. Hacerlo así nos asegura un eje ordenado con los puntos que ingresamos. Si decides probar con xField en los LineSeries (explicados abajo) tal vez obtendrás un resultado que no te guste, pero que igual, funciona.

  • Como son 3 gráficas las que vamos a incluir en el LineChart, necesitamos incluir tres elementos <mx:LineSeries />. Cada uno de ellos debe ir enlazado a los campos que corresponden a las resistividades calculadas (y a la promedio por supuesto) a través de los campos de mydata: promedio, resistividad_ns y resistividad_we. Recordemos que en la función setNewItem() se construyó así la estructura, debemos usar exactamente los mismos nombres proporcionados a mydata.
  • Debajo del LineChart está un componente llamado Legend. Observa aquí que el proveedor de datos de este componente es precisamente el LineChart (que en este caso se llama ‘mychart’).

Muy bien les dejo el código completo abajo.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init()">
<mx:Script>
 <![CDATA[ 
        import mx.collections.ArrayCollection;

        [Bindable] 
        private var mydata:ArrayCollection; 
        private var pns:Number = new Number();
        private var pwe:Number = new Number();
        private var prom:Number = new Number();
        private function init():void {
            mydata = new ArrayCollection();
        } 
        private function calculate():void {
            mydata.removeAll();
            setNewItem(0.75, 1, rns_1.text, rwe_1.text);
            setNewItem(1.5, 2, rns_2.text, rwe_2.text);
            setNewItem(2.25, 3, rns_3.text, rwe_3.text);
            setNewItem(3, 4, rns_4.text, rwe_4.text);
            setNewItem(3.75, 5, rns_5.text, rwe_5.text);
            setNewItem(4.5, 6, rns_6.text, rwe_6.text);
            setNewItem(5.25, 7, rns_7.text, rwe_7.text);
            setNewItem(6, 8, rns_8.text, rwe_8.text);
        }
        private function setNewItem(prf:Number, sep:Number, rns:String, rwe:String):void {
            pns = myround(2*(Math.PI)*sep*(new Number(rns)));
            pwe = myround(2*(Math.PI)*sep*(new Number(rwe)));
            prom = myround((pns + pwe)/2);
            mydata.addItem({profundidad: prf, separacion: sep, resistencia_ns: rns, 
                resistencia_we: rwe, resistividad_ns: pns, resistividad_we: pwe, promedio: prom});
        }
        private function myround(numToRound:Number):Number {
            numToRound = (Math.round(numToRound*100))/100;
            return numToRound;
        }
 ]]>
</mx:Script>
 <mx:TabNavigator width="518" height="349" id="bigcontainer" selectedIndex="0" horizontalCenter="8" top="24"
   change="calculate()">
   <mx:Canvas label="Valores de Resistencia" width="100%" height="100%">
    <mx:Panel x="10" y="10" width="496" height="296" layout="absolute" id="mypanel0" title="Ingreso de Datos">
      <mx:TextInput x="224.25" y="19" width="58" id="rns_1" textAlign="center"/>
      <mx:TextInput x="224.25" y="49" width="58" id="rns_2" textAlign="center"/>
      <mx:TextInput x="224.25" y="79" width="58" id="rns_3" textAlign="center"/>
      <mx:TextInput x="224.25" y="109" width="58" id="rns_4" textAlign="center"/>
      <mx:TextInput x="224.25" y="139" width="58" id="rns_5" textAlign="center"/>
      <mx:TextInput x="224.25" y="169" width="58" id="rns_6" textAlign="center"/>
      <mx:TextInput x="224.25" y="199" width="58" id="rns_7" textAlign="center"/>
      <mx:TextInput x="224.25" y="229" width="58" id="rns_8" textAlign="center"/>
      <mx:TextInput x="320.25" y="19" width="58" id="rwe_1" textAlign="center"/>
      <mx:TextInput x="320.25" y="49" width="58" id="rwe_2" textAlign="center"/>
      <mx:TextInput x="320.25" y="79" width="58" id="rwe_3" textAlign="center"/>
      <mx:TextInput x="320.25" y="109" width="58" id="rwe_4" textAlign="center"/>
      <mx:TextInput x="320.25" y="139" width="58" id="rwe_5" textAlign="center"/>
      <mx:TextInput x="320.25" y="169" width="58" id="rwe_6" textAlign="center"/>
      <mx:TextInput x="320.25" y="199" width="58" id="rwe_7" textAlign="center"/>
      <mx:TextInput x="320.25" y="229" width="58" id="rwe_8" textAlign="center"/>
      <mx:Label x="155.75" y="20" text="1" textAlign="center"/>
      <mx:Label x="155.75" y="50" text="2" textAlign="center"/>
      <mx:Label x="155.75" y="80" text="3" textAlign="center"/>
      <mx:Label x="155.75" y="110" text="4" textAlign="center"/>
      <mx:Label x="155.75" y="140" text="5" textAlign="center"/>
      <mx:Label x="155.75" y="170" text="6" textAlign="center"/>
      <mx:Label x="155.75" y="200" text="7" textAlign="center"/>
      <mx:Label x="155.75" y="228" text="8" textAlign="center"/>
      <mx:Label x="87.75" y="20" text="0.75" textAlign="center"/>
      <mx:Label x="91.25" y="50" text="1.5" textAlign="center"/>
      <mx:Label x="87.75" y="80" text="2.25" textAlign="center"/>
      <mx:Label x="93.25" y="110" text="3" textAlign="center"/>
      <mx:Label x="87.75" y="140" text="3.75" textAlign="center"/>
      <mx:Label x="91.25" y="170" text="4.5" textAlign="center"/>
      <mx:Label x="87.75" y="200" text="5.25" textAlign="center"/>
      <mx:Label x="93.25" y="228" text="6" textAlign="center"/>
      <mx:Label x="73.75" y="1" text="Prof. (m)" fontWeight="bold"/>
      <mx:Label x="139.75" y="1" text="Sep. (m)" fontWeight="bold"/>
      <mx:Label x="207.25" y="1" text="Resistencia N-S" fontWeight="bold"/>
      <mx:Label x="305.25" y="1" text="Resistencia W-E" fontWeight="bold"/>
    </mx:Panel>
   </mx:Canvas>
   <mx:Canvas label="Grilla de Calculos" width="100%" height="100%">
    <mx:Panel x="10" y="10" width="496" height="296" layout="absolute" id="mypanel1" title="Cuadro de Calculo de Resistividades">
      <mx:DataGrid x="6" y="10" width="464" height="224" id="myGrid" dataProvider="{mydata}">
       <mx:columns>
         <mx:DataGridColumn headerText="Prof.(m)" dataField="profundidad" headerStyleName="center" textAlign="center"/>
         <mx:DataGridColumn headerText="Sep.(m)" dataField="separacion" textAlign="center"/>
         <mx:DataGridColumn headerText="R-NS" dataField="resistencia_ns" headerStyleName="center" textAlign="center"/>
         <mx:DataGridColumn headerText="R-EO" dataField="resistencia_we" textAlign="center"/>
         <mx:DataGridColumn headerText="p-NS" dataField="resistividad_ns" textAlign="center"/>
         <mx:DataGridColumn headerText="p-EO" dataField="resistividad_we" textAlign="center"/>
         <mx:DataGridColumn headerText="Prom." dataField="promedio" textAlign="center"/>
       </mx:columns>
      </mx:DataGrid>
    </mx:Panel>
   </mx:Canvas>
   <mx:Canvas label="Perfiles de Resistividad" width="100%" height="100%">
    <mx:Panel x="10" y="10" width="496" height="296" layout="absolute" id="mypanel2" title="Grafico de Perfiles de Resistividad">
    <mx:LineChart x="6" y="2" id="mychart" height="220" width="464" dataProvider="{mydata}" showDataTips="true">
      <mx:horizontalAxis>
         <mx:CategoryAxis categoryField="profundidad"/>
       </mx:horizontalAxis>
      <mx:series>
       <mx:LineSeries displayName="Resistividad Promedio" yField="promedio" form="curve"/>
       <mx:LineSeries displayName="Resistividad N-S" yField="resistividad_ns" form="curve"/>
       <mx:LineSeries displayName="Resistividad W-E" yField="resistividad_we" form="curve"/>
      </mx:series>
    </mx:LineChart>
    <mx:Legend dataProvider="{mychart}" x="32" y="225" direction="horizontal"/>
    </mx:Panel>
   </mx:Canvas>
 </mx:TabNavigator>
</mx:Application>

Copiar y pegar les asegura que va a compilar correctamente, pero la idea es analizar cada una de las estrategias usadas aquí, pues precisamente para aprender. Se que el ejemplo amerita una explicación mas detallada (y seguramente más ordenada), pero aún así espero que les ayude en su aprendizaje de Flex. ¡Saludos!

¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.

Publica tu comentario

El autor de este artículo ha cerrado los comentarios. Si tienes preguntas o comentarios, puedes hacerlos en el foro

Entra al foro y participa en la discusión

o puedes...

¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?

¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?

Registrate