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:



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:



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:



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:



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.



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.



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:



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.