От переводчика
Недавно наткнулся в python digest на туториал по Flask+Bokeh. Туториал ориентирован на новичков, не требуется даже знать синтаксис Python и HTML. Примеры работают под Ubuntu 16.04, на Windows немного отличается работа с виртуальными окружениями.
Вступление
Bokeh — это мощная библиотека с открытым исходным кодом, которая позволяет визуализировать данные для веб-приложений, не написав ни строчки на javascript. Изучение библиотек для визуализации вроде d3.js может оказаться полезным, но гораздо легче написать несколько строк кода на Python, чтобы решить задачу.
С Bokeh мы можем создавать поразительно детальные интерактивные визуализации или же более простые вещи, вроде столбчатых диаграмм.
Давайте разберёмся, как можно использовать Flask и Bokeh для визуализации данных в веб-приложении.
Инструменты
Всё, что описано далее, работает как на Python 2, так и на Python 3, однако, рекомендуется использовать Python 3 для новых приложений. Я использовал Python 3.6.1 на момент написания этой статьи. Помимо самого Python, нам потребуются следующие зависимости:
- Веб-фреймворк Flask, версия 0.12.2
- Библиотека визуализации данных Bokeh, версия 0.12.5
- Библиотека анализа данных pandas, версия 0.20.1
- pip и virtualenv поставляются вместе с Python 3. Они понадобятся, чтобы изолировать Flask, Bokeh и pandas от остальных проектов.
Если вам нужны дополнительные сведения по настройке окружения разработки, можете обратиться к руководству.
Весь код примеров доступен по лицензии MIT на GitHub.
Установка Bokeh и Flask
Создайте чистое виртуальное окружение для проекта. Как правило, я запускаю эту команду в отдельной папке venvs, где которой находятся все мои виртуальные окружения.
python3 -m venv barchart
Активируйте виртуальное окружение.
source barchart/bin/activate
После активации виртуального окружения изменится приглашение командной строки:
Не забывайте, что вам понадобится активировать виртуальное окружение в каждом новом окне терминала, из которого вы захотите запустить своё приложение.
Теперь можно установить 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 для генерации 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/. Вы должны увидеть большое сообщение, которой меняется, когда вы меняете URL.
Наше приложение уже запускается, но пока не впечатляет. Пришло время добавить график.
Генерация столбчатой диаграммы
Нам нужно всего лишь добавить немного кода, который будет использовать 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 перед глазами, чтобы знать, как можно кастомизировать визуализацию.
Нам нужно сделать всего пару изменений в файле 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/.
Выглядит немного пусто, но мы можем изменить количество столбцов на 16, если перейдём на /localhost:5000/16/.
Ещё в 4 раза больше...
Выглядит довольно неплохо. Но что, если мы добавим подсказки при наведении, чтобы можно было подробно изучить каждый столбец? Чтобы добавить подсказки, нам нужно поменять функцию 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/.
Отличная работа! Попробуйте поиграть с количеством столбцов в URL и посмотрите как график будет выглядеть.
График выглядит заполненным примерно при 100 столбцах, но вы можете попробовать задать любое значение. На 50,000 получается грустная картина:
Да уж, похоже, нам нужно сделать ещё что-то, чтобы можно было отображать больше пары сотен столбцов за раз.
Что дальше?
Мы создали отличный график на Bokeh. Далее вы можете изменить цветовую схему, подключить другой источник данных, попробовать другие типы графиков или придумать, как отображать огромные объёмы данных.
С помощью Bokeh можно делать гораздо больше, так что стоит посмотреть официальную документацию, репозиторий на GitHub, страничку Bokeh на Full Stack Python.
Есть вопросы? Задайте мне из через GitHub либо через Twitter @fullstackpython или @mattmakai.
Автор: Георгий Седометов