ReactJS y el uso de los componentes en la web (Parte 4: Gráfica de barras con D3 y RectJS)

react

Parte 3

Conforme vamos avanzando en el desarrollo de componentes con ReacJS, vamos a ir viendo casos de uso más útiles en el desarrollo web.

La visualización de información es uno de los aspectos más importantes en el desarrollo web. Actualmente es dificil encontrar una aplicación web que no te ofrezca gráficas con analytics de información para hacer la compresión más sencillas, es por eso que vamos a aprender un poco acerca de D3 (Data-Drivem Document), una librería de Javascript enfocada en la visualización de información, a través de una interfaz que si bien al principio no es sencilla, nos ayuda a personalizar nuestras gráficas.

Si quieren aprender más de D3 y el poder que tiene, puede revisar el sitio oficial y ver las maravillas que pueden crear con esta herramienta.

 

Pero bueno, tengo también pensado escribir algo en el blog acerca de D3.

Lo que vamos a hacer es una Gráfica de Barras con datos aleatorios generados por una función también creada en Javascript.

La gráfica que va a resultar es algo así:

barras

Tenemos una gráfica de barras con un eje Y proporcionada por fechas, mientras que el eje X es con valores decimales.

El código es el siguiente:

<!DOCTYPE HTML>
<html lang="es">
  <head>
    <meta charset="utf-8">

<style>
      .axis text {
        font: 10px sans-serif;
      }
      .axis path,
      .axis line {
        fill: none;
        stroke: #000;
        shape-rendering: crispEdges;
      }
    </style>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/JSXTransformer.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
  </head>
  <body>

<div id="container">
      
    </div>

    <script type="text/jsx" src="/static/d3.js.jsx"></script>
  </body>
</html>

Vemos que la plantilla HTML sigue siendo básicamente la misma, pero ahora con la diferencia de que en nuestras etiquetas// <![CDATA[
tenemos también importada la librería de d3 desde un CDN.

Ahora vamos a ver el archivo JS de esta vista:

/** @jsx React.DOM */

function getRandomInt(min, max) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

$(document).ready(function() {
  //Define los datos aleatorios con fechas subsecuentes para la gráfica
  var data = [];
  var tamanio = 20;

  for (var i = 0; i < tamanio; i++) {
    data.push({
      fecha: new Date(2015, 1, i),
      valor: getRandomInt(0, 100),
    });
  }

  //Crea el componente
  D3ChartComponent = React.createClass({
    //Calcula el valor mínimo y máximo del arreglo para definir 
    //dominio de las gráficas
    getMinMax: function() {
      var data = this.props.data;
      var menor = 0;
      var mayor = 0;
      data.forEach(function(elemento, index) {
        if (elemento.valor < menor) {
          menor = elemento.valor;
        }
        if (elemento.valor > mayor) {
          mayor = elemento.valor;
        }
      });

      return ([menor, mayor]);
    },
    //Valores por default del props
    getDefaultProps: function() {
      return ({
        margin: {
          bottom: 10,
          left: 60,
          right: 60,
          top: 10,
        },
        height: 400,
        width: 800,
      });
    },
    componentDidMount: function() {
      //Manda a llamar a todas las funciones necesarias para la gráfica
      this.inciarGrafica();
      this.renderEjes();
      this.renderBarras();
    },
    inciarGrafica: function() {
      var svg = this.refs.chart.getDOMNode();
      this.conf = {};

      this.conf.svg = d3.select(svg)
        .attr('height', this.props.height)
        .attr('width', this.props.width);
    },
    renderEjes: function() {
      var data =        this.props.data;
      var minDate =     this.props.data[0].fecha;
      var maxDate =     this.props.data[this.props.data.length - 1].fecha;
      var minMax =      this.getMinMax();
      var scaleWidth =  this.props.width - this.props.margin.left - this.props.margin.right;
      var scaleHeigth = this.props.height - this.props.margin.top - this.props.margin.bottom;

      //Define las escalas con sus rangos y dominios de valores
      var xScale = d3.time.scale()
        .domain([new Date(minDate), new Date(maxDate)])
        .range([0, scaleWidth]);

      var yScale = d3.scale.linear()
        .domain([
          minMax[1],
          minMax[0],
        ])
        .range([0, scaleHeigth]);

      //Define la configuración de los ejes (ticks y posición)
      var xAxis = d3.svg.axis()
        .scale(xScale)
        .orient('bottom');

      var yAxis = d3.svg.axis()
        .scale(yScale)
        .orient('left');

      //Agrega contenedor de gráfica
      var gContent = this.conf.svg
        .append('g')
        .attr('transform', 'translate(60, -10)');

      //Dibuja las lineas de las gráficas
      gContent.append('g')
        .attr('class', 'x axis')
        .attr('transform', 'translate(0, 380)')
        .call(xAxis);

      gContent.append('g')
        .attr('class', 'y axis')
        .call(yAxis);

      this.conf.xScale =    xScale;
      this.conf.yScale =    yScale;
      this.conf.gContent =  gContent;
    },
    renderBarras: function() {
      var _this =       this;
      var data =        this.props.data;
      var scaleHeigth = this.props.height - this.props.margin.top - this.props.margin.bottom;

      //Agrega las barras (Una por cada elemento del arreglo data)
      this.conf.gContent
        .selectAll('rect')
        .data(data)
        .enter()
        .append('rect')
        .attr('x', function(d, i) {
          return _this.conf.xScale(new Date(d.fecha));
        })
        .attr('height', function(d, i) {
          return _this.conf.yScale(d.valor);
        })
        .attr('width', 20)
        .style('fill', 'orange')
        .attr('y', function(d, i) {
          return scaleHeigth - (_this.conf.yScale(d.valor));
        });
    },
    render: function() {
      return (<div className='chart-container'>
        <svg ref='chart' className='d3-chart'>
        </svg>
      </div>);
    }
  });

  //Manda a renderizar el component Chart con 'data' como props
  React.render(
    <D3ChartComponent 
      data={data} />,
    document.getElementById('container')
  );
});

Ahora vamos a ver rápidamente cada una de las funciones que se ejecutan para entender qué es lo que hace nuestro componente para definir esa gráfica que vimos al principios:

    render: function() {
      return (<div className='chart-container'>
        <svg ref='chart' className='d3-chart'>
        </svg>
      </div>);
    }

La función render es la que se ejecuta para mostrar un elemento en el DOM. Lo que hace esta función es generar un div con la clase chart-container, además de renderizar adentro de éste una etiqueta SVG que es la que contenerá la gráfica.

Después de la función render, según el ciclo de vida de los componentes en React, se ejecuta la gunción componentDidMount, y con eso viene el siguiente código:

    componentDidMount: function() {
      //Manda a llamar a todas las funciones necesarias para la gráfica
      this.inciarGrafica();
      this.renderEjes();
      this.renderBarras();
    },

Esta función o que hace es mandar a llamar a todas las funciones que construirán nuestra gráfica paso por paso.
Primero inciará la gráfica con sus valores globales y después renderizará los ejes (Si es que existe esa palabra). Además después mandará a llamar a la función que renderizará las barras y habrá terminado su proceso.

Así que vamos a ver la función que iniciará las gráfica:

    inciarGrafica: function() {
      var svg = this.refs.chart.getDOMNode();
      this.conf = {};

      this.conf.svg = d3.select(svg)
        .attr('height', this.props.height)
        .attr('width', this.props.width);
    },

Aquí se incializa la gráfica con su ancho y su alto con el que se va a crear, además de definir un objeto global llamado this.conf donde guardaremos los valores importantes en nuestra gráfica a través de toda su construcción.

Ahora veamos la función para crear los ejes:

    renderEjes: function() {
      var data =        this.props.data;
      var minDate =     this.props.data[0].fecha;
      var maxDate =     this.props.data[this.props.data.length - 1].fecha;
      var minMax =      this.getMinMax();
      var scaleWidth =  this.props.width - this.props.margin.left - this.props.margin.right;
      var scaleHeigth = this.props.height - this.props.margin.top - this.props.margin.bottom;

      //Define las escalas con sus rangos y dominios de valores
      var xScale = d3.time.scale()
        .domain([new Date(minDate), new Date(maxDate)])
        .range([0, scaleWidth]);

      var yScale = d3.scale.linear()
        .domain([
          minMax[1],
          minMax[0],
        ])
        .range([0, scaleHeigth]);

      //Define la configuración de los ejes (ticks y posición)
      var xAxis = d3.svg.axis()
        .scale(xScale)
        .orient('bottom');

      var yAxis = d3.svg.axis()
        .scale(yScale)
        .orient('left');

      //Agrega contenedor de gráfica
      var gContent = this.conf.svg
        .append('g')
        .attr('transform', 'translate(60, -10)');

      //Dibuja las lineas de las gráficas
      gContent.append('g')
        .attr('class', 'x axis')
        .attr('transform', 'translate(0, 380)')
        .call(xAxis);

      gContent.append('g')
        .attr('class', 'y axis')
        .call(yAxis);

      this.conf.xScale =    xScale;
      this.conf.yScale =    yScale;
      this.conf.gContent =  gContent;
    },

Esta función es un poco maś complicada, pero básicamente lo que hace es obtener el valor de las escalas, ya sea su dominio y su rango, haciendo esto, configura su comportamiento y el número de sus ticks en cada uno. Esta función es muy importante porque define la función que va enviarle a las barras con un valor para dibujar la gráfica. Espero que quede luego un poco más claro cuando escriba el tutorial de D3. Por el momento dejémoslo así.

La función para dibujar las barras es la siguiente:

    renderBarras: function() {
      var _this =       this;
      var data =        this.props.data;
      var scaleHeigth = this.props.height - this.props.margin.top - this.props.margin.bottom;

      //Agrega las barras (Una por cada elemento del arreglo data)
      this.conf.gContent
        .selectAll('rect')
        .data(data)
        .enter()
        .append('rect')
        .attr('x', function(d, i) {
          return _this.conf.xScale(new Date(d.fecha));
        })
        .attr('height', function(d, i) {
          return _this.conf.yScale(d.valor);
        })
        .attr('width', 20)
        .style('fill', 'orange')
        .attr('y', function(d, i) {
          return scaleHeigth - (_this.conf.yScale(d.valor));
        });
    },

Esta función básicamente toma los valores del props que le enviamos cuando construimos cuando instanciamos nuestro componente y a través de un ciclo al estilo de D3, va dibujando una barra por cada elemento en el arreglo.

Tenemos que prestar gran atención a la siguiente funcion que define valores por default del objeto props.

    getDefaultProps: function() {
      return ({
        margin: {
          bottom: 10,
          left: 60,
          right: 60,
          top: 10,
        },
        height: 400,
        width: 800,
      });
    },

Este esfuezo para podemos mostrar la cantidad de csas que se pueden hacer con React y D3. Lo importante de esto es que ahora cada vez que tengamos que escribir una gráfica de barras en nuestros proyecto, reutilizar lo que acabamos de hacer, en lugar de tener que escribir una desde cero. Por lo general se busca que nuestros componentes sean lo más generales que se pueda, y por eso D3 y React es una combinación perfecta.

Seguiré escribiendo de D3 y más posts de ReactJS así como juntar mi curso de React con el de Django, así que espero sigas leyendo lo que viene.

GG.

 

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google photo

Estás comentando usando tu cuenta de Google. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s