Draw graphs (charts) in Django

Many web developers from time to time are faced with the need to visualize a relatively large amount of data using diagrams (hereinafter I will call them graphs, although this is not entirely true). The task is not new, and there are many ready-made solutions on the network: working on the server side and on the client side, using images, Canvas, SVG, Flash, Silverlight ...
In this article I will talk about django-google-charts and some features of using Google Chart Tools to plot on a site running Django.
Often, when you need to add a graph to a page, the developer follows the path of least resistance: copies JavaScript from an example on the Internet and somehow displays data from the application into it. It turns out something like:
var chart_data = [
{% for row in chart_data %}
[{{ row.0 }}, {{ row.1 }}],
{% endfor %}
];
Why is that bad?
- The code is hard to read, making it easy to make a mistake. Did you notice an error in my example? :)
- It's hard to scale. When you need to add a second graph to the page, they usually copy the same piece of code again and change the variable names. The result is an unreadable mess, debugging it and making changes is rather unpleasant.
- Strong context binding. Reuse of such a template is limited.
Formulation of the problem
A good solution for drawing graphs:
- Allows you to simply and clearly add new graphics to the site and remove unnecessary;
- Complies with the DRY principle for determining common elements (for example, color scheme);
- Can be reused;
- Does not invent a new API.
Solution option
The django-google-charts charting application has grown out of a small set of hacks in my current project. Alarma: this is the first public release, bizarre bugs are possible. Corrections and comments are welcome.
Installation
$ pip install django-google-charts
# или
$ easy_install django-google-charts
Add
'googlecharts'to INSTALLED_APPS.How it works (general view)
{% load googlecharts %}
{% googlecharts %}
{% data переменная "необязательное имя" %} {# (именованный) набор данных #}
{% col "тип" "название" %}...{% endcol %} {# формат #}
{% col "тип" "название" %}...{% endcol %} {# еще формат #}
...
{% enddata %}
{% options "необязательное имя" %} {# (именованный) набор опций #}
...
{% endoptions %}
{% graph "#id" "данные" "опции" %} {# точка сборки #}
{% endgooglecharts %}
I'll tell you a little about the purpose of each tag.
{% googlecharts %}...{% endgooglecharts %}
It connects the necessary scripts, is a container for the entire structure. Nothing interesting.
{% data переменная "имя" %}...{% enddata %}
A named dataset. You can omit the name (it will turn out
"default").{% col "тип" "название" %}...{% endcol %}
Format. The data types in the Google Visualization API are:
'string' 'number' 'boolean' 'date' 'datetime' 'timeofday'
Read more in the documentation .
A special variable is passed
valinside the tag , in this place you can format it:{% col "string" "Date" %}'{{ val|date:"M j" }}'{% endcol %}
{# или, например #}
{% col "number" %}{{ val|floatformat:2 }}{% endcol %}
The result should correspond to the declared type; quotes around the string themselves will not be put.
Example. Suppose
{% data %}we passed a context variable to the block :[['foo', 32], ['bar', 64], ['baz', 96]]
We need two tags
{% col %}, according to the number of elements in the input line. The first will receive the input 'foo', 'bar' and 'baz'; the second, respectively, 32, 64 and 96. The implementation (the simplest) may look like this:{% col "string" "Name" %}"{{ val }}"{% endcol %}
{% col "number" "Value" %}{{ val }}{% endcol %}
{% options "имя" %}...{% endoptions %}
Chart Options.
{% options %}
kind: "LineChart",
options: {
width: 300,
height: 240
// ...
}
{% endoptions %}
Inside the tag is a JavaScript object, you can use global variables and call functions. Chart types and supported options for each type are listed here .
{% graph "id_элемента" "данные" "опции" %}
We display the graph in an element on the page. The last two parameters can be omitted, it will turn out
"default" "default".Usage example
Suppose we have a model like this:
class Payment(models.Model):
amount = models.DecimalField(max_digits=11, decimal_places=4)
datetime = models.DateTimeField()
You can use django-qsstats-magic to prepare the data for display in a graph .
from qsstats import QuerySetStats
def view_func(request):
start_date = ...
end_date = ...
queryset = Payment.objects.all()
# считаем количество платежей...
qsstats = QuerySetStats(queryset, date_field='datetime', aggregate=Count('id'))
# ...в день за указанный период
values = qsstats.time_series(start_date, end_date, interval='days')
return render_to_response('template.html', {'values': values})
The method
time_seriesreturns data in the following form:[[date, value], [date, value], ...]
In the template.html file:
{% load googlecharts %}
{% googlecharts %}
{% data values "count" %}
{% col "string" "Date" %}"{{ val|date:"M j" }}"{% endcol %}
{% col "number" "# of payments" %}{{ val }}{% endcol %}
{% enddata %}
{% options %}
kind: "LineChart",
options: {
backgroundColor: "#f9f9f9",
colors: ["#09f"],
gridlineColor: "#ddd",
legend: "none",
vAxis: {minValue: 0},
chartArea: {left: 40, top: 20, width: 240, height: 180},
width: 300,
height: 240
}
{% endoptions %}
{% graph "count_graph" "count" %} {# используем опции по умолчанию #}
{% endgooglecharts %}
Scaling
Now, to add another graph to the page, you need to do the following:
- Collect data (views.py):
# сумма всех платежей в день за указанный период
summary = qsstats.time_series(start_date, end_date, interval='days', aggregate=Sum('amount'))
return render_to_response('template.html', {'values': values, 'summary': summary})
- Add to template.html:
...
{% data summary "sum" %}
{% col "string" "Date" %}"{{ val|date:"M j" }}"{% endcol %}
{% col "number" "Paid amount, USD" %}{{ val|floatformat:2 }}{% endcol %}
{% enddata %}
...
{% graph "count_sum" "sum" %} {# используем опции по умолчанию, снова #}
Github
The source code for django-google-charts also contains a demo project. To start it, just do:
$ python manage.py syncdb --noinput # создает базу данных sqlite в /tmp
$ python manage.py populatedb # наполняет ее случайными данными
$ python manage.py runserver
The test project looks like this:

Limitations of the first public release (fly in the ointment)
- Only one tag
{% googlecharts %}per page; - (Almost) no error checking, especially in JavaScript;
- Vanishingly little documentation.