Advertisement
  1. Code
  2. D3

Aplicación de visualización de datos con GAE Python, D3.js y Google BigQuery: Parte 3

Scroll to top
Read Time: 12 min
This post is part of a series called Data Visualization App Using GAE Python, D3.js and Google BigQuery.
Data Visualization App Using GAE Python, D3.js and Google BigQuery: Part 2
Data Visualization App Using GAE Python, D3.js and Google BigQuery: Part 4

Spanish (Español) translation by Luis Chiabrera (you can also view the original English article)

En la parte previa de este tutorial, vimos como empezar a usar D3.js, y crear escalas dinámicas y ejes para nuestro gráfico de visualización usando un conjunto de datos de muestra. En esta parte del tutorial, vamos a trazar el gráfico utilizando el conjunto de datos de muestra.

Para empezar, clona el codigo fuente del previo tutorial desde GitHub.

1
git clone https://github.com/jay3dec/PythonD3jsMashup_Part2.git

Navega al directorio SDK de Google App Engine (GAE) y arranca el servidor.

1
./dev_appserver.py PythonD3jsMashup_Part2/

Apunta tu navegador a http://localhost:8080/displayChart y podrás ver los Ejes X y Y que creamos en el tutorial anterior.

Antes de comenzar, crea una nueva platilla llamada displayChart_3.html que será igual a la displayChart.html. También agrega una ruta para displayChart_3.html. Esto se hace solo para mantener la demostración del tutorial anterior intacta. dado que estaremos hospedandola en el mismo URL.

1
class DisplayChart3(webapp2.RequestHandler):
2
  def get(self):
3
    template_data = {}
4
    template_path = 'Templates/displayChart_3.html'
5
    self.response.out.write(template.render(template_path,template_data))
6
    
7
8
application = webapp2.WSGIApplication([
9
    ('/chart',ShowChartPage),
10
    ('/displayChart',DisplayChart),
11
    ('/displayChart3',DisplayChart3),
12
    ('/', ShowHome),
13
], debug=True)

Creando el gráfico de visualización ( con datos de muestra)

En nuestro conjunto de datos de muestra, tenemos un número de count que se trazará a través de un conjunto de year corespondiente.

1
var data = [{
2
    "count": "202",
3
    "year": "1590"
4
}, {
5
    "count": "215",
6
    "year": "1592"
7
}, {
8
    "count": "179",
9
    "year": "1593"
10
}, {
11
    "count": "199",
12
    "year": "1594"
13
}, {
14
    "count": "134",
15
    "year": "1595"
16
}, {
17
    "count": "176",
18
    "year": "1596"
19
}, {
20
    "count": "172",
21
    "year": "1597"
22
}, {
23
    "count": "161",
24
    "year": "1598"
25
}, {
26
    "count": "199",
27
    "year": "1599"
28
}, {
29
    "count": "181",
30
    "year": "1600"
31
}, {
32
    "count": "157",
33
    "year": "1602"
34
}, {
35
    "count": "179",
36
    "year": "1603"
37
}, {
38
    "count": "150",
39
    "year": "1606"
40
}, {
41
    "count": "187",
42
    "year": "1607"
43
}, {
44
    "count": "133",
45
    "year": "1608"
46
}, {
47
    "count": "190",
48
    "year": "1609"
49
}, {
50
    "count": "175",
51
    "year": "1610"
52
}, {
53
    "count": "91",
54
    "year": "1611"
55
}, {
56
    "count": "150",
57
    "year": "1612"
58
}];

Representaremos cada uno de los puntos de datos como círculos en nuestro gráfico de visualización. D3.js provee métodos API para crear varias formas y tamaños.

Primero, usaremos d3.selectAll para seleccionar los circulos dentro del elemento de visualización. Si no se encuentran elementos, retornará un marcador de posición vacío donde podamos anexar los círculos después.

1
var circles = vis.selectAll("circle");

Luego, ataremos nuestro conjunto de datos a la selección de circles.

1
var circles = vis.selectAll("circle").data(data);

Dado que nuestra selección de círculo existente esta vacía, usaremos enter para crear nuevos círculos.

1
circles.enter().append("svg:circle")

Después, definiremos ciertas propiedades como la distancia de los centros de los círculos desde los ejes X (cx) y Y (cy), sus colores, sus radios, etc. Para traer cx y xy, usaremos xScale y yScale para transformar los datos de conteo y año dentro del espacio de trazado y dibujar el círculo en el area SVG. Asi es como se verá el código:

1
var circles = vis.selectAll("circle").data(data);
2
circles.enter()
3
    .append("svg:circle")
4
    
5
    .attr("stroke", "black")      // sets the circle border

6
    
7
    .attr("r", 10)                // sets the radius 

8
    
9
    .attr("cx", function(d) {     // transforms the year data so that it 

10
        return xScale(d.year);    // can be plotted in the svg space 

11
    })
12
    
13
    .attr("cy", function(d) {     // transforms the count data so that it 

14
        return yScale(d.count);   // can be plotted in the svg space 

15
    })
16
    
17
    .style("fill", "red")         // sets the circle color

Guarda los cambions y recarga tu página. Deberías poder ver la imagen mostrada a continuación:

Chart with circles plottedChart with circles plottedChart with circles plotted

Modificando Google BIgQuery para extraer los datos relevantes

En la primera parte de esta serie, cuando buscamos datos desde BigQuery, seleccionamos unas 1.000 palabras.

1
SELECT word FROM [publicdata:samples.shakespeare] LIMIT 1000

Tenemos un conjunto de datos que contienen una lista de todas las palabras las cuales aparecen en todo el trabajo de Shakespeare. Así que, para hacer que aplicación de visualización revele informacion útil, modificaremos nuestra consulta para que seleccione el numero de veces que una palabra aparece en el trabajo de Shakespeare a través de diferentes años, como por ejemplo la palabra Caesar.

Así que, inicia sesión en Google BigQuery y tendremos una pantalla como la que se muestra a continuación:

Google BigQuery page showing query historyGoogle BigQuery page showing query historyGoogle BigQuery page showing query history

Despues de haber iniciado sesión en Google BigQuery, tendremos una interfaz donde podemos componer y revisar nuestras consultas de SQL. Queremos seleccionar el número de veces que una palabra en particular aparece a través de todo el trabajo de Shakespeare.

Así que nuestra consulta básica debería verse como esto:

1
SELECT SUM(word_count) as WCount,corpus_date FROM [publicdata:samples.shakespeare] WHERE word="Caesar" GROUP BY corpus_date ORDER BY WCount

La consulta mostrada anteriormente muestra un conjunto de resultados como se muestra a continuación:

Google BigQuery query and resultGoogle BigQuery query and resultGoogle BigQuery query and result

Incluyamos también el grupo de trabajos correspondientes al Word Count. Modifica la consulta como se muestra para incluir el corpus:

1
SELECT SUM(word_count) as WCount,corpus_date,group_concat(corpus) as Work FROM [publicdata:samples.shakespeare] WHERE word="Caesar" and corpus_date>0 GROUP BY corpus_date ORDER BY WCount

El conjunto de resultados obtenido está mostrado a continuación:

Google BigQuery result setGoogle BigQuery result setGoogle BigQuery result set

Trazando los datos desde Google BigQuery

Luego, abre app.py y crea una nueva clase llamada GetChartData. Dentro de ella, incluye la declaración de la consulta que creamos arriba.

1
queryData = {'query':'SELECT SUM(word_count) as WCount,corpus_date,group_concat(corpus) as Work FROM '
2
'[publicdata:samples.shakespeare] WHERE word="God" and corpus_date>0 GROUP BY corpus_date ORDER BY WCount'}

Luego,  crea un servicio BigQuery contra el que ejecutaremos nuestra queryData.

1
tableData = bigquery_service.jobs()

Ahora, ejecuta el queryData contra el servicio de BigQuery e imprime el resultado a la página.

1
dataList = tableData.query(projectId=PROJECT_NUMBER,body=queryData).execute()
2
self.response.out.write(dataList)

También incluye una nueva ruta para GetChartData como se muestra.

1
application = webapp2.WSGIApplication([
2
    ('/chart',ShowChartPage),
3
    ('/displayChart',DisplayChart),
4
    ('/displayChart3',DisplayChart3),
5
    ('/getChartData',GetChartData),
6
    ('/', ShowHome),
7
], debug=True)

FInalmente actualiza el código a la plataforma GAE.

1
./appcfg.py update PythonD3jsMashup_Part2/

Apunta tu buscador a http://YourAppspotUrl.com/getChartData el cual debería mostrar los datos resultantes del BigQuery.

Luego, trataremos de analizar los datos recibidos de Google BigQuery y convertirlos en un objeto de datos JSON y pasarlos al lado de cliente para procesar usando D3.js.

Primero, revisaremos si hay alguna columna devuelta en dataList. Si no hay columnas, estableceremos la respuesta como nula o cero.

1
if 'rows' in dataList:

2
  # parse dataList

3
else:

4
  resp.append({'count':'0','year':'0','corpus':'0'})

Luego, analizaremos dataList haciendo un bucle en cada fila y recogiendo el recuento, el año y el corpus y creando nuestro objeto JSON requerido.

1
resp = []
2
if 'rows' in dataList:
3
  for row in dataList['rows']:
4
    for key,dict_list in row.iteritems():
5
        count = dict_list[0]
6
        year = dict_list[1]
7
        corpus = dict_list[2]
8
        resp.append({'count': count['v'],'year':year['v'],'corpus':corpus['v']})
9
else:
10
  resp.append({'count':'0','year':'0','corpus':'0'})

Dado que estaremos retornando los datos analizados en formato JSON, importa la librería JSON

1
import json

Y retorna la respuesta creada como una respuesta JSON.

1
self.response.headers['Content-Type'] = 'application/json'
2
self.response.out.write(json.dumps(resp))

Tambien hagamos la búsqueda de palabra clave dinámica, tal que pueda ser pasada como un parámetro.

1
inputData = self.request.get("inputData")
2
queryData = {'query':'SELECT SUM(word_count) as WCount,corpus_date,group_concat(corpus) as Work FROM '
3
'[publicdata:samples.shakespeare] WHERE word="'+inputData+'" and corpus_date>0 GROUP BY corpus_date ORDER BY WCount'}

He aqui como la clase GetChartData se ve finalmente:

1
class GetChartData(webapp2.RequestHandler):
2
  def get(self):
3
    inputData = self.request.get("inputData")
4
    queryData = {'query':'SELECT SUM(word_count) as WCount,corpus_date,group_concat(corpus) as Work FROM '
5
'[publicdata:samples.shakespeare] WHERE word="'+inputData+'" GROUP BY corpus_date ORDER BY WCount'}
6
    tableData = bigquery_service.jobs()
7
    dataList = tableData.query(projectId=PROJECT_NUMBER,body=queryData).execute()
8
    
9
    resp = []
10
    if 'rows' in dataList:
11
      for row in dataList['rows']:
12
        for key,dict_list in row.iteritems():
13
          count = dict_list[0]
14
          year = dict_list[1]
15
          corpus = dict_list[2]
16
          resp.append({'count': count['v'],'year':year['v'],'corpus':corpus['v']})
17
    else:
18
      resp.append({'count':'0','year':'0','corpus':'0'})
19
    
20
     
21
    self.response.headers['Content-Type'] = 'application/json'
22
    self.response.out.write(json.dumps(resp))

Actualiza la aplicación en Gae y apunta tu buscador a http://YourAppspotUrl.com/getChartData y puedes ver la respuesta JSON retornada.

Returned JSON responseReturned JSON responseReturned JSON response

Luego, crearemos una interfaz para consultar el conjunto de datos de Google BigQuery desde nuestra aplicación de manera dinámica. Abre Templates/displayChart_3.html e incluye un cuadro de entrada donde meteremos palabras claves a la consulta del conjunto de datos.

1
<div align="center">
2
    <input id="txtKeyword" type="text" class="span3" placeholder="Type something…">
3
</div>

Incluye un script jQuery en la página y en el evento listo DOM, consultaremos el método Python GetChartData al presionar la tecla Enter.

1
$(document).ready(function() {
2
    $("#txtKeyword").keyup(function(event) {
3
        if (event.keyCode == 13) {  // If enter key press
4
            DisplayChart();
5
        }
6
    });
7
    InitChart();                    // Init Chart with Axis
8
});

Crea otra funcion DisplayChart en el lado de cliente, dentro de la cual haremos un llamado Ajax al método Python GetChartData

1
function DisplayChart() {
2
    var keyword = $('#txtKeyword').val();
3
    $.ajax({
4
        type: "GET",
5
        url: "/getChartData",
6
        data: {
7
            inputData: keyword
8
        },
9
        dataType: "json",
10
        success: function(response) {
11
            console.log(response);
12
        },
13
        error: function(xhr, errorType, exception) {
14
            console.log('Error occured');
15
        }
16
    });
17
}

Actualiza el código a GAE y apunta tu buscador a http://YourAppspotUrl.com/displayChart3. Escribe una palabra clave, digamos Caesar, y presiona Enter. Revisa la consola de tu buscador y deberías ver la respuesta JSON retornada.

Luego, tracemos los círculos usando una respuesta retornada. Así que crea otra funcion JavaScript llamada CreateChart. Esta función es similar a la función InitChar pero los datos serán pasados como parámetro. Así es como se ve:

1
function CreateChart(data) {
2
    var vis = d3.select("#visualisation"),
3
        WIDTH = 1000,
4
        HEIGHT = 500,
5
        MARGINS = {
6
            top: 20,
7
            right: 20,
8
            bottom: 20,
9
            left: 50
10
        },
11
        xScale = d3.scale.linear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([d3.min(data, function(d) {
12
                return (parseInt(d.year, 10) - 5);
13
            }),
14
            d3.max(data, function(d) {
15
                return parseInt(d.year, 10);
16
            })
17
        ]),
18
        yScale = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([d3.min(data, function(d) {
19
                return (parseInt(d.count, 10) - 5);
20
            }),
21
            d3.max(data, function(d) {
22
                return parseInt(d.count, 10);
23
            })
24
        ]),
25
        xAxis = d3.svg.axis() 
26
        .scale(xScale), 
27
28
        yAxis = d3.svg.axis() 
29
        .scale(yScale)
30
        .orient("left");
31
32
33
34
35
    vis.append("svg:g") 
36
        .attr("class", "x axis") 
37
        .attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")")
38
        .call(xAxis); 
39
40
    vis.append("svg:g")
41
        .attr("class", "y axis")
42
        .attr("transform", "translate(" + (MARGINS.left) + ",0)")
43
        .call(yAxis);
44
45
46
    var circles = vis.selectAll("circle").data(data);
47
    circles.enter()
48
        .append("svg:circle")
49
        .attr("stroke", "black")
50
        .attr("r", 10)
51
        .attr("cx", function(d) {
52
            return xScale(d.year);
53
        })
54
        .attr("cy", function(d) {
55
            return yScale(d.count);
56
        })
57
        .style("fill", "red")
58
59
}

Desde la función InitChart, remueve la porción de de creación del círculo dado que no será necesario ahora. Así es como InitChart se ve:

1
function InitChart() {
2
    var data = [{
3
        "count": "202",
4
        "year": "1590"
5
    }, {
6
        "count": "215",
7
        "year": "1592"
8
    }, {
9
        "count": "179",
10
        "year": "1593"
11
    }, {
12
        "count": "199",
13
        "year": "1594"
14
    }, {
15
        "count": "134",
16
        "year": "1595"
17
    }, {
18
        "count": "176",
19
        "year": "1596"
20
    }, {
21
        "count": "172",
22
        "year": "1597"
23
    }, {
24
        "count": "161",
25
        "year": "1598"
26
    }, {
27
        "count": "199",
28
        "year": "1599"
29
    }, {
30
        "count": "181",
31
        "year": "1600"
32
    }, {
33
        "count": "157",
34
        "year": "1602"
35
    }, {
36
        "count": "179",
37
        "year": "1603"
38
    }, {
39
        "count": "150",
40
        "year": "1606"
41
    }, {
42
        "count": "187",
43
        "year": "1607"
44
    }, {
45
        "count": "133",
46
        "year": "1608"
47
    }, {
48
        "count": "190",
49
        "year": "1609"
50
    }, {
51
        "count": "175",
52
        "year": "1610"
53
    }, {
54
        "count": "91",
55
        "year": "1611"
56
    }, {
57
        "count": "150",
58
        "year": "1612"
59
    }];
60
61
62
    var color = d3.scale.category20();
63
    var vis = d3.select("#visualisation"),
64
        WIDTH = 1000,
65
        HEIGHT = 500,
66
        MARGINS = {
67
            top: 20,
68
            right: 20,
69
            bottom: 20,
70
            left: 50
71
        },
72
        xScale = d3.scale.linear().range([MARGINS.left, WIDTH - MARGINS.right]).domain([d3.min(data, function(d) {
73
                return (parseInt(d.year, 10) - 5);
74
            }),
75
            d3.max(data, function(d) {
76
                return parseInt(d.year, 10);
77
            })
78
        ]),
79
        yScale = d3.scale.linear().range([HEIGHT - MARGINS.top, MARGINS.bottom]).domain([d3.min(data, function(d) {
80
                return (parseInt(d.count, 10) - 5);
81
            }),
82
            d3.max(data, function(d) {
83
                return parseInt(d.count, 10);
84
            })
85
        ]),
86
        xAxis = d3.svg.axis() 
87
        .scale(xScale), 
88
89
        yAxis = d3.svg.axis()
90
        .scale(yScale)
91
        .orient("left");
92
93
94
95
96
    vis.append("svg:g") 
97
        .attr("class", "x axis") 
98
        .attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")")
99
        .call(xAxis); 
100
101
    vis.append("svg:g")
102
        .attr("class", "y axis")
103
        .attr("transform", "translate(" + (MARGINS.left) + ",0)")
104
        .call(yAxis);
105
}

De ahora en adelante, cuando cargamos la página /displayChart3, los círculos no se mostrarán. Los círculos solo aparecen una vez que la palabra clave ha sido buscada. Así que, en el exitoso llamado de vuelta del llamado AJAX DisplayChart, pasa la respuesta a la función CreateChart.

1
success: function(response) {
2
    console.log(response);
3
    CreateChart(response);
4
}

Actualiza el codigo a GAE e intenta buscar la palabra clave Caesar. OK, ahora podremos ver el resultado en forma de círculos en el gráfico. Pero hay un problema: ambos ejes se sobreescriben.

Chart with overlapping axesChart with overlapping axesChart with overlapping axes

Así que en orden de evitar eso debemos revisar dentro de la función CreateChart si los ejes ya están ahí o no.

1
var hasAxis = vis.select('.axis')[0][0];
2
3
if (!hasAxis) {
4
   
5
    vis.append("svg:g") 
6
        .attr("class", "x axis") 
7
        .attr("transform", "translate(0," + (HEIGHT - MARGINS.bottom) + ")") 
8
        .call(xAxis); 
9
10
    vis.append("svg:g")
11
        .attr("class", "y axis")
12
        .attr("transform", "translate(" + (MARGINS.left) + ",0)")
13
        .call(yAxis);
14
}

Como puedes ver, acabamos de revisar si el elemento SVG tiene ejes, sino los creamos otra vez. Actualiza el código a GAE e intenta buscar otra vez la palabra clave, y deberías ver algo como esto:

Final chart with axes correctly displayedFinal chart with axes correctly displayedFinal chart with axes correctly displayed

Resumiendo

A pesar de que todo se ve bien ahora, todavia hay algunos detalles los cuales mostraremos en la siguiente parte de este tutorial. Tambien introduciremos transiciones D3.js y algunas otras características a nuestro gráfico D3.js, y tratar de hacerlo más interactivo.

El código fuente de este tutorial está disponible en GitHub.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.