- PVSM.RU - https://www.pvsm.ru -

Отзывчивые столбчатые диаграммы с Bokeh, Flask и Python 3

От переводчика

Недавно наткнулся в python digest [1] на туториал по Flask+Bokeh. Туториал ориентирован на новичков, не требуется даже знать синтаксис Python и HTML. Примеры работают под Ubuntu 16.04, на Windows немного отличается работа с виртуальными окружениями.
image

Вступление

Bokeh — это мощная библиотека с открытым исходным кодом, которая позволяет визуализировать данные для веб-приложений, не написав ни строчки на javascript. Изучение библиотек для визуализации вроде d3.js может оказаться полезным, но гораздо легче написать несколько строк кода на Python, чтобы решить задачу.
С Bokeh мы можем создавать поразительно детальные интерактивные визуализации или же более простые вещи, вроде столбчатых диаграмм.
Давайте разберёмся, как можно использовать Flask и Bokeh для визуализации данных в веб-приложении.

Инструменты

Всё, что описано далее, работает как на Python 2, так и на Python 3, однако, рекомендуется использовать Python 3 для новых приложений. Я использовал Python 3.6.1 на момент написания этой статьи. Помимо самого Python, нам потребуются следующие зависимости:

  • Веб-фреймворк Flask [2], версия 0.12.2
  • Библиотека визуализации данных Bokeh [3], версия 0.12.5
  • Библиотека анализа данных pandas [4], версия 0.20.1
  • pip [5] и virtualenv [6] поставляются вместе с Python 3. Они понадобятся, чтобы изолировать Flask, Bokeh и pandas от остальных проектов.

Если вам нужны дополнительные сведения по настройке окружения разработки, можете обратиться к руководству [7].
Весь код примеров доступен по лицензии MIT на GitHub.

Установка Bokeh и Flask

Создайте чистое виртуальное окружение для проекта. Как правило, я запускаю эту команду в отдельной папке venvs, где которой находятся все мои виртуальные окружения.

python3 -m venv barchart

Активируйте виртуальное окружение.

source barchart/bin/activate

После активации виртуального окружения изменится приглашение командной строки:
image
Не забывайте, что вам понадобится активировать виртуальное окружение в каждом новом окне терминала, из которого вы захотите запустить своё приложение.
Теперь можно установить Bokeh и Flask в созданное виртуальное окружение. Выполните эту команду, чтобы установить Bokeh и Flask подходящих версий.

pip install bokeh==0.12.5 flask==0.12.2 pandas==0.20.1

После загрузки и установки необходимые библиотеки будут доступны в виртуальном окружении. Проверьте вывод, чтобы удостовериться, что всё установилось.

Installing collected packages: six, requests, PyYAML, python-dateutil, MarkupSafe, Jinja2, numpy, tornado, bokeh, Werkzeug, itsdangerous, click, flask, pytz, pandas
  Running setup.py install for PyYAML ... done
  Running setup.py install for MarkupSafe ... done
  Running setup.py install for tornado ... done
  Running setup.py install for bokeh ... done
  Running setup.py install for itsdangerous ... done
Successfully installed Jinja2-2.9.6 MarkupSafe-1.0 PyYAML-3.12 Werkzeug-0.12.2 bokeh-0.12.5 click-6.7 flask-0.12.2 itsdangerous-0.24 numpy-1.12.1 pandas-0.20.1 python-dateutil-2.6.0 pytz-2017.2 requests-2.14.2 six-1.10.0 tornado-4.5.1

Теперь мы можем перейти непосредственно к нашему приложению.

Запуск приложения на Flask

Мы напишем простое Flask-приложение и добавим столбчатую диаграмму на страницу.
Создайте папку для своего прокта с файлом app.py с таким содержанием:

from flask import Flask, render_template

app = Flask(__name__)

@app.route("/<int:bars_count>/")
def chart(bars_count):
    if bars_count <= 0:
        bars_count = 1
    return render_template("chart.html", bars_count=bars_count)

if __name__ == "__main__":
    app.run(debug=True)

Это простое Flask-приложение, в котором есть функция chart. chart принимает целое число, которое позже будет использоваться для определения количества данных для отрисовки. Функция render_template внутри chart будет использовать шаблонизатор Jinja2 [8] для генерации HTML.
Последние 2 строки позволяют нам запустить приложение из консоли на 5000 порту в режиме отладки. Никогда не используйте режим отладки в продакшене, для этого существуют WSGI-серверы проде Gunicorn.
Создайте папку templates внутри папки проекта. Внутри неё создайте файл chart.html. Он нужен функции chart из app.py, поэтому без него приложение не будет работать правильно. Заполните chart.html Jinja2-разметкой.

<!DOCTYPE html>
<html>
  <head>
    <title>Bar charts with Bokeh!</title>
  </head>
  <body>
    <h1>Bugs found over the past {{ bars_count }} days</h1>
  </body>
</html>

Заготовка chart.html будет показывать количество столбцов, переданное в функцию chart через URL.
Сообщение внутри тега h1 отвечает теме нашего приложения. Мы будем строить график количества багов, найденных систимой автоматического тестирования за каждый день.
Теперь мы можем протестировать наше приложение.
Убедитесь, что виртуальное окружение всё ещё активно и вы находитесь в папке с app.py. Запустите app.py с помощью команды python.

$(barchart) python app.py

Перейдите на localhost:5000/16/ [9]. Вы должны увидеть большое сообщение, которой меняется, когда вы меняете URL.
Отзывчивые столбчатые диаграммы с Bokeh, Flask и Python 3 - 3
Наше приложение уже запускается, но пока не впечатляет. Пришло время добавить график.

Генерация столбчатой диаграммы

Нам нужно всего лишь добавить немного кода, который будет использовать Bokeh.
Откройте app.py и добавьте в него строки сверху.

import random
from bokeh.models import (HoverTool, FactorRange, Plot, LinearAxis, Grid,
                          Range1d)
from bokeh.models.glyphs import VBar
from bokeh.plotting import figure
from bokeh.charts import Bar
from bokeh.embed import components
from bokeh.models.sources import ColumnDataSource
from flask import Flask, render_template

Остальная часть файла будет использовать Bokeh вместе с модулем random для генерации данных и столбчатой диаграммы.
Данные для диаграммы будут генерироваться заново при каждой перезагрузке страницы. В реальном приложении используйте более надёжный и полезный источник данных!
Продолжайте изменять app.py. Код после импортов должен выглядеть следующим образом.

app = Flask(__name__)

@app.route("/<int:bars_count>/")
def chart(bars_count):
    if bars_count <= 0:
        bars_count = 1

    data = {"days": [], "bugs": [], "costs": []}
    for i in range(1, bars_count + 1):
        data['days'].append(i)
        data['bugs'].append(random.randint(1,100))
        data['costs'].append(random.uniform(1.00, 1000.00))

    hover = create_hover_tool()
    plot = create_bar_chart(data, "Bugs found per day", "days",
                            "bugs", hover)
    script, div = components(plot)

    return render_template("chart.html", bars_count=bars_count,
                           the_div=div, the_script=script)

Функция chart сгенерирует списки с данными с помощью встроенного модуля random.
chart вызывает 2 функции: create_hover_tool и create_bar_chart. Мы пока не написали эти функции, поэтому продолжим добавлять код после функции chart:

def create_hover_tool():
    # эту функцию мы напишем чуть позже
    return None

def create_bar_chart(data, title, x_name, y_name, hover_tool=None,
                     width=1200, height=300):
    """Создаёт столбчатую диаграмму.
        Принимает данные в виде словаря, подпись для графика,
        названия осей и шаблон подсказки при наведении.
    """
    source = ColumnDataSource(data)
    xdr = FactorRange(factors=data[x_name])
    ydr = Range1d(start=0,end=max(data[y_name])*1.5)

    tools = []
    if hover_tool:
        tools = [hover_tool,]

    plot = figure(title=title, x_range=xdr, y_range=ydr, plot_width=width,
                  plot_height=height, h_symmetry=False, v_symmetry=False,
                  min_border=0, toolbar_location="above", tools=tools,
                  responsive=True, outline_line_color="#666666")

    glyph = VBar(x=x_name, top=y_name, bottom=0, width=.8,
                 fill_color="#e12127")
    plot.add_glyph(source, glyph)

    xaxis = LinearAxis()
    yaxis = LinearAxis()

    plot.add_layout(Grid(dimension=0, ticker=xaxis.ticker))
    plot.add_layout(Grid(dimension=1, ticker=yaxis.ticker))
    plot.toolbar.logo = None
    plot.min_border_top = 0
    plot.xgrid.grid_line_color = None
    plot.ygrid.grid_line_color = "#999999"
    plot.yaxis.axis_label = "Bugs found"
    plot.ygrid.grid_line_alpha = 0.1
    plot.xaxis.axis_label = "Days after app deployment"
    plot.xaxis.major_label_orientation = 1
    return plot

Здесь много кода, с которым нужно разобраться. Функция create_hover_tool пока только возвращает None, потому что пока нам не нужно отображать подсказки при наведении.
Внутри функции create_bar_chart мы преобразуем сгенерированные данные в объект типа ColumnDataSource, который мы можем передать на вход функциям Bokeh для построения графика. Мы задаём диапазоны осей x и y.
Пока у нас не настроены подсказки при наведении, список tools пуст. Вся магия происходит в вызове функции figure. Мы передаём ей всё необходимое для построения графика: размер, панель инструментов, границы, настройки поведения графика при изменении размера окна браузера.
Мы создаём вертикальные столбцы с помощью класса VBar и добавляем их на график с помощью функции add_glyph, которая задаёт правила, по которым данные превращаются в столбцы.
Последние 2 строки изменяют оформление графика. Для примера я убрал логотип Bokeh при помощи plot.toolbar.logo = None и добавил подписи к обеим осям. Я рекомендую держать документацию bokeh.plotting [10] перед глазами, чтобы знать, как можно кастомизировать визуализацию.
Нам нужно сделать всего пару изменений в файле templates/chart.html, чтобы отобразить график. Откройте файл и замените его содержимое следующим.

<!DOCTYPE html>
<html>
  <head>
    <title>Bar charts with Bokeh!</title>
    <link href="http://cdn.pydata.org/bokeh/release/bokeh-0.12.5.min.css" rel="stylesheet">
    <link href="http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.0.min.css" rel="stylesheet">
  </head>
  <body>
    <h1>Bugs found over the past {{ bars_count }} days</h1>
    {{ the_div|safe }}
    <script src="http://cdn.pydata.org/bokeh/release/bokeh-0.12.5.min.js"></script>
    <script src="http://cdn.pydata.org/bokeh/release/bokeh-widgets-0.12.5.min.js"></script>
    {{ the_script|safe }}
  </body>
</html>

2 из 6 добавленных строк нужны для загрузки CSS-файлов Bokeh, ещё 2 для загрузки его скриптов, и ещё 2 — для генерации графика.
Всё готово, давайте проверим наше приложение. Flask должен автоматически перезгрузить приложение, когда вы сохраните изменения в app.py. Если вы остановили веб-сервер, вы можете снова его запустить командой python app.py.
Откройте в браузере localhost:5000/4/ [11].
Отзывчивые столбчатые диаграммы с Bokeh, Flask и Python 3 - 4
Выглядит немного пусто, но мы можем изменить количество столбцов на 16, если перейдём на /localhost:5000/16/ [9].
Отзывчивые столбчатые диаграммы с Bokeh, Flask и Python 3 - 5
Ещё в 4 раза больше... [12]
Отзывчивые столбчатые диаграммы с Bokeh, Flask и Python 3 - 6
Выглядит довольно неплохо. Но что, если мы добавим подсказки при наведении, чтобы можно было подробно изучить каждый столбец? Чтобы добавить подсказки, нам нужно поменять функцию create_hover_tool.

Добавление подсказок при наведении

Внутри app.py измените функцию create_hover_tool

def create_hover_tool():
    """Generates the HTML for the Bokeh's hover data tool on our graph."""
    hover_html = """
      <div>
        <span class="hover-tooltip">$x</span>
      </div>
      <div>
        <span class="hover-tooltip">@bugs bugs</span>
      </div>
      <div>
        <span class="hover-tooltip">$@costs{0.00}</span>
      </div>
    """
    return HoverTool(tooltips=hover_html)

Встраивание HTML в приложение на Python может показаться довольно странным, но именно так мы определяем вид подсказки. Мы используем $x, чтобы показать x-координату столбца, @bugs, чтобы показать поле "bugs" источника данных и $@costs{0.00}, чтобы показать поле "costs" как числ с 2 знаками после запятой.
Убедитесь, что вы заменили return None на return HoverTool(tooltips=hover_html).
Вернитесь в браузер и перезагрузить страницу localhost:5000/128/ [12].
Отзывчивые столбчатые диаграммы с Bokeh, Flask и Python 3 - 7
Отличная работа! Попробуйте поиграть с количеством столбцов в URL и посмотрите как график будет выглядеть.
График выглядит заполненным примерно при 100 столбцах, но вы можете попробовать задать любое значение. На 50,000 получается грустная картина:
Отзывчивые столбчатые диаграммы с Bokeh, Flask и Python 3 - 8
Да уж, похоже, нам нужно сделать ещё что-то, чтобы можно было отображать больше пары сотен столбцов за раз.

Что дальше?

Мы создали отличный график на Bokeh. Далее вы можете изменить цветовую схему, подключить другой источник данных, попробовать другие типы графиков или придумать, как отображать огромные объёмы данных.
С помощью Bokeh можно делать гораздо больше, так что стоит посмотреть официальную документацию [3], репозиторий на GitHub [13], страничку Bokeh на Full Stack Python [14].
Есть вопросы? Задайте мне из через GitHub [15] либо через Twitter @fullstackpython [16] или @mattmakai [17].

Автор: Георгий Седометов

Источник [18]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/257659

Ссылки в тексте:

[1] python digest: https://pythondigest.ru/

[2] Flask: http://flask.pocoo.org/

[3] Bokeh: http://bokeh.pydata.org/en/latest/

[4] pandas: http://pandas.pydata.org/

[5] pip: https://pypi.python.org/pypi/pip

[6] virtualenv: https://virtualenv.pypa.io/en/stable/

[7] руководству: https://virtualenv.pypa.io/en/stable/userguide/

[8] Jinja2: http://jinja.pocoo.org/

[9] localhost:5000/16/: http://localhost:5000/16/

[10] bokeh.plotting: http://bokeh.pydata.org/en/latest/docs/reference/plotting.html#bokeh-plotting

[11] localhost:5000/4/: http://localhost:5000/4/

[12] Ещё в 4 раза больше...: http://localhost:5000/128/

[13] репозиторий на GitHub: https://github.com/bokeh/bokeh

[14] страничку Bokeh на Full Stack Python: https://www.fullstackpython.com/bokeh.html

[15] GitHub: https://github.com/mattmakai/fullstackpython.com/issues

[16] @fullstackpython: https://twitter.com/fullstackpython

[17] @mattmakai: https://twitter.com/mattmakai

[18] Источник: https://habrahabr.ru/post/330706/?utm_source=habrahabr&utm_medium=rss&utm_campaign=best