
Многие веб-разработчики время от времени сталкиваются с необходимостью визуализировать сравнительно большое количество данных при помощи диаграмм (далее я буду называть их графиками, хоть это и не совсем верно). Задача не нова, и в сети есть множество готовых решений: работающие на стороне сервера и на стороне клиента, использующие изображения, Canvas, SVG, Flash, Silverlight…
В этой статье я расскажу про django-google-charts и некоторые особенности использования Google Chart Tools для построения графиков на сайте под управлением Django.
Часто, когда нужно добавить график на страницу, разработчик идет по пути наименьшего сопротивления: копирует JavaScript из примера в интернете и как-нибудь выводит в него данные из приложения. Получается что-то наподобие:
var chart_data = [ {% for row in chart_data %} [{{ row.0 }}, {{ row.1 }}], {% endfor %} ];
Почему это плохо?
- Код тяжело читается, вследствие чего легко допустить ошибку. Вы же заметили ошибку в моем примере? :)
- Сложно масштабировать. Когда нужно добавить второй график на страницу, обычно копируют этот же кусок кода еще раз и меняют имена переменных. В результате получается нечитаемая каша, отлаживать это и вносить изменения достаточно неприятно.
- Сильное связывание с контекстом. Повторное использование такого шаблона ограничено.
Постановка задачи
Хорошее решение для рисования графиков:
- Позволяет просто и понятно добавлять новые графики на сайт и убирать ненужные;
- Соблюдает принцип DRY для определения общих элементов (например, цветовой схемы);
- Можно повторно использовать;
- Не изобретает нового API.
Вариант решения
Приложение для компоновки графиков django-google-charts выросло из небольшого набора хаков в моем текущем проекте. Алярма: это первый публичный релиз, возможны причудливые баги. Исправления и замечания всячески приветствуются.
Установка
$ pip install django-google-charts # или $ easy_install django-google-charts
Добавляем
'googlecharts' в INSTALLED_APPS.Как это работает (общий вид)
{% load googlecharts %} {% googlecharts %} {% data переменная "необязательное имя" %} {# (именованный) набор данных #} {% col "тип" "название" %}...{% endcol %} {# формат #} {% col "тип" "название" %}...{% endcol %} {# еще формат #} ... {% enddata %} {% options "необязательное имя" %} {# (именованный) набор опций #} ... {% endoptions %} {% graph "#id" "данные" "опции" %} {# точка сборки #} {% endgooglecharts %}
Расскажу немного про назначение каждого тега.
{% googlecharts %}...{% endgooglecharts %}
Подключает необходимые скрипты, является контейнером для всей конструкции. Ничего интересного.
{% data переменная "имя" %}...{% enddata %}
Именованный набор данных. Имя можно не указывать (получится
"default").{% col "тип" "название" %}...{% endcol %}
Формат. Типы данных в Google Visualization API вот такие:
'string' 'number' 'boolean' 'date' 'datetime' 'timeofday'
Подробнее можно почитать в документации.
Внутрь тега передается специальная переменная
val, в этом месте ее можно форматировать:{% col "string" "Date" %}'{{ val|date:"M j" }}'{% endcol %} {# или, например #} {% col "number" %}{{ val|floatformat:2 }}{% endcol %}
Результат должен соответствовать заявленному типу; кавычки вокруг строки сами не поставятся.
Пример. Допустим, в блок
{% data %} мы передали такую переменную контекста:[['foo', 32], ['bar', 64], ['baz', 96]]
Нам нужны два тега
{% col %}, по количеству элементов в строке входных данных. Первый получит на вход 'foo', 'bar' и 'baz'; второй, соответственно, 32, 64 и 96. Реализация (самая простая) может выглядеть так:{% col "string" "Name" %}"{{ val }}"{% endcol %} {% col "number" "Value" %}{{ val }}{% endcol %}
{% options "имя" %}...{% endoptions %}
Параметры графика.
{% options %} kind: "LineChart", options: { width: 300, height: 240 // ... } {% endoptions %}
Внутри тега объект JavaScript, можно использовать глобальные переменные и вызывать функции. Типы графиков и поддерживаемые опции для каждого типа перечислены вот здесь.
{% graph "id_элемента" "данные" "опции" %}
Выводим график в элемент на странице. Последние два параметра можно не указывать, получится
"default" "default".Пример использования
Предположим, у нас есть такая модель:
class Payment(models.Model): amount = models.DecimalField(max_digits=11, decimal_places=4) datetime = models.DateTimeField()
Для подготовки данных к отображению в виде графика можно использовать django-qsstats-magic.
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})
Метод
time_series возвращает данные в таком виде:[[date, value], [date, value], ...]
В файле template.html:
{% load googlecharts %} <div id="count_graph"></div> {% 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 %}
Масштабирование
Теперь, чтобы добавить еще один график на страницу, нужно сделать следующее:
– Собрать данные (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})
– Добавить в template.html:
<div id="count_sum"></div> ... {% 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
Исходный код django-google-charts содержит также демонстрационный проект. Чтобы его запустить, достаточно выполнить:
$ python manage.py syncdb --noinput # создает базу данных sqlite в /tmp $ python manage.py populatedb # наполняет ее случайными данными $ python manage.py runserver
Выглядит тестовый проект вот так:

Ограничения первого публичного релиза (ложка дегтя)
- Только один тег
{% googlecharts %}на странице; - (Почти) нет проверки ошибок, особенно в JavaScript;
- Исчезающе мало документации.
