Pull to refresh

Рисуем графики (диаграммы) в Django

Reading time 5 min
Views 32K
django-google-charts

Многие веб-разработчики время от времени сталкиваются с необходимостью визуализировать сравнительно большое количество данных при помощи диаграмм (далее я буду называть их графиками, хоть это и не совсем верно). Задача не нова, и в сети есть множество готовых решений: работающие на стороне сервера и на стороне клиента, использующие изображения, 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 %}
];

Почему это плохо?

  • Код тяжело читается, вследствие чего легко допустить ошибку. Вы же заметили ошибку в моем примере? :)
  • Сложно масштабировать. Когда нужно добавить второй график на страницу, обычно копируют этот же кусок кода еще раз и меняют имена переменных. В результате получается нечитаемая каша, отлаживать это и вносить изменения достаточно неприятно.
  • Сильное связывание с контекстом. Повторное использование такого шаблона ограничено.
Для временного решения такая конструкция еще годится, но не более.

Постановка задачи


Хорошее решение для рисования графиков:
  1. Позволяет просто и понятно добавлять новые графики на сайт и убирать ненужные;
  2. Соблюдает принцип DRY для определения общих элементов (например, цветовой схемы);
  3. Можно повторно использовать;
  4. Не изобретает нового 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

Выглядит тестовый проект вот так:

django-google-charts

Ограничения первого публичного релиза (ложка дегтя)

  1. Только один тег {% googlecharts %} на странице;
  2. (Почти) нет проверки ошибок, особенно в JavaScript;
  3. Исчезающе мало документации.
Спасибо всем, кто дочитал. А как вы рисуете графики?
Tags:
Hubs:
+56
Comments 27
Comments Comments 27

Articles