Неблокирующая отрисовка и обновление графиков с помощью bokeh

в 23:37, , рубрики: bokeh, python, визуализация данных, графики, отладка

image

Есть у меня один Python-скрипт с расчётами. Там был цикл примерно на 2000 итераций, каждая из которых считалась несколько минут.

И решил я, чтобы ловчее отлаживать тот скрипт, выводить график кой-каких метрик от номера итерации. И как посчитается очередная итерация, так оный график и обновлять.

Проще всего проделать это с помощью bokeh. Точнее, с помощью bokeh-сервера для отрисовки графиков. Как — сейчас расскажу.

Сначала запускаем сервер: сервачок идёт из коробки вместе с самим bokeh, так что после pip install bokeh достаточно набрать в консоли bokeh serve — и сервер запущен.

Зачем он нужен? А затем, чтобы показ графиков

  • не блокировал исполнение остального кода (ибо происходит в браузере, в отдельном процессе),
  • чтобы график реагировал на изменение размеров окна (или на свёртывание-развёртывание)
  • и чтобы при этом мы в любой момент могли этот график изменить как захотим, прямо из нашего же Python-процесса!

Делается это примерно так:

import time
import sys
from bokeh.plotting import figure
from bokeh.client import pull_session
from bokeh.models import ColumnDataSource

# Перед запуском этого скрипта -- не забудь запустить сервер-отрисовщик, набрав в консоли bokeh serve
# Please run "bokeh serve" in console before start!


if __name__ == "__main__":
    # Создаём браузерную сессию (вкладку в браузере, где мы будем рисовать графики)
    session = pull_session()

    # Создаём т.н. документ, который будем показывать на сессии (фигуру с осями и графиками)
    fig = figure(title=("Total TBS (in bits)"), plot_height=300, plot_width=800)

    # Созадём кривую и пополняемый источник данных к ней
    datasource = ColumnDataSource(data={"x": [], "y": []})
    line = fig.line(x="x", y="y", source=datasource, line_width=2, legend=("Super dooper line from hell"))

    # Браузер откроет новую вкладку с пустыми осями
    session.show(fig)

    # Начинаем изменять состояние графика
    for i in range(10000):

        # Здесь  мы всего лишь добавляем к графику ещё одну точку. При изменении datasource от кривой кривая перерисуется
        # Вы можете изменить график и посильнее = )
        datasource.stream({"x": [i], "y": [i ** 2]})

        # Без вызова этого метода примерно через 30-40 изменений график в табе перестанет обновляться, будьте осторожны
        session.force_roundtrip()


# Удачной отладки!

Раньше мне тоже приходилось делать подобное, но предыдущие решения были, мягко говоря, не столь хороши. Чего я только за свою жизнь не перепробовал…

Осторожно, мозговой балласт!
Можно использовать matplotlib в неблокинующем режиме, вручную дёргая plt.draw() на каждой итерации. Правда, собственной обработки сообщений от GUI в неблокирующем режиме у matplotlib нету, и если окошко свёрнётся или закроется другим окном, то надо ждать следующей итерации, чтобы его перерисовали. Так себе костыль, но для отладки сойдёт.

Можно по-негритянски генерировать картинку с графиком тем же matplotlib и дампить на диск. Тоже лютый костыль, но на безрыбьи и сойдёт. Или на удалённой машине без графики.

Можно сделать и по-крутому: воспользоваться PyQt, завернуть расчётный код в QObject, задвинуть его в отдельный поток, завернуть matplotlib-графики в QWidget (или воспользоваться графиками из Qt Data Visualizaion, или из PyQtGraph), соединить математику с графикой через сигнал со слотом, и будет счастье. Правда, на быстрое решение для отладки это слабо похоже, да и Qt учить надо, но я такое пару раз делал.

Можно поднять в отдельным процессом маааленькое серверное приложение для отрисовки графиков (например, с помощью aiohttp + PyQt + PyQtGraph), к которому стучаться через REST API из главного процесса. Когда-то я делал и такое, но на быстрое-решение-для-отладки это тоже не тянуло.

Можно писать в какую-нибудь БД (что там у нас сейчас в моде?), а потом напускать на это модную же Grafan’у. Правда, нужно ставить и БД, и Grafan’у, настраивать их, и вообще заморачиваться записью в БД. Через файл, наверно, тоже можно, но для двух графиков на тыщу точек каждый — это как из пушки по воробьям…

Или можно разбираться в plotly.dash, выносить математику в отдельный поток, заворачивать в dash-приложение, и делать ещё чёртову уйму всякой фигни. Этого я уже не осилил, хотя и надо бы.

Короче, удачной отладки!

Автор: Кролик

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js