построение нескольких строк потоковых данных в серверном приложении bokeh - PullRequest
1 голос
/ 19 апреля 2020

Я пытаюсь создать приложение bokeh с потоковыми данными, которые отслеживают несколько «стратегий», поскольку они генерируются в модели, основанной на агенте дилерма-заключенный. Я столкнулся с проблемой, пытаясь заставить мои линейные графики НЕ соединять все точки данных в одну линию. Я собрал этот небольшой демонстрационный скрипт, который повторяет проблему. Я прочитал много документации по линейному и многострочному рендерингу на боке-графиках, но я просто не нашел того, что, кажется, соответствует моему простому случаю. Вы можете запустить этот код, и он автоматически откроет сервер bokeh на localhost: 5004 ...

from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.plotting import figure, ColumnDataSource
from bokeh.models import Button
from bokeh.layouts import column  
import random

def make_document(doc):

    # Create a data source
    data_source = ColumnDataSource({'step': [], 'strategy': [], 'ncount': []})

    # make a list of groups
    strategies = ['DD', 'DC', 'CD', 'CCDD']

    # Create a figure
    fig = figure(title='Streaming Line Plot',
                 plot_width=800, plot_height=400)
    fig.line(x='step', y='ncount', source=data_source)
    global step
    step = 0

    def button1_run():
        global callback_obj
        if button1.label == 'Run':
            button1.label = 'Stop'
            button1.button_type='danger'
            callback_obj = doc.add_periodic_callback(button2_step, 100)
        else:
            button1.label = 'Run'
            button1.button_type = 'success'
            doc.remove_periodic_callback(callback_obj)

    def button2_step():
        global step
        step = step+1
        for i in range(len(strategies)):
            new = {'step': [step],
                   'strategy': [strategies[i]],
                   'ncount': [random.choice(range(1,100))]}
            fig.line(x='step', y='ncount', source=new)
            data_source.stream(new)


    # add on_click callback for button widget
    button1 = Button(label="Run", button_type='success', width=390)
    button1.on_click(button1_run)
    button2 = Button(label="Step", button_type='primary', width=390)
    button2.on_click(button2_step)

    doc.add_root(column(fig, button1, button2))
    doc.title = "Now with live updating!"

apps = {'/': Application(FunctionHandler(make_document))}

server = Server(apps, port=5004)
server.start()

if __name__ == '__main__':
    server.io_loop.add_callback(server.show, "/")
    server.io_loop.start()

Я надеялся, что, пройдя по 4 "стратегиям" в примере (после нажатия кнопки 2), Я мог бы транслировать новые данные, поступающие из симуляции, в линейный график только для этой одной стратегии и шага. Но то, что я получаю, это одна строка со всеми четырьмя значениями, соединенными вертикально, затем одно из них соединяется с первым на следующем этапе. Вот как это выглядит после нескольких шагов: enter image description here

Я заметил, что если я переместу data_source.stream(new) из для l oop, я получу хороший однолинейный сюжет , но, конечно, это только для последней стратегии, выходящей из l oop.

Во всех примерах с многострочным построением боке, которые я изучал (не глиф multi_line, который я могу ' (и, похоже, у него есть некоторые проблемы с инструментом Hover), инструкции кажутся довольно ясными: если вы хотите визуализировать вторую строку, вы добавляете другой рендерер fig.line к существующему figure, и это dr aws строка с данными, указанными в source=data_source для этой строки. Но хотя мой for-l oop собирает и добавляет данные отдельно для каждой стратегии, я не получаю 4 линейных графика, я получаю только один.

Надеюсь, я упускаю что-то очевидное! Заранее спасибо.

Ответы [ 2 ]

1 голос
/ 19 апреля 2020

Похоже, вам нужна строка для стратегии, а не строка за шаг. Если так, вот как бы я это сделал:

import random

from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.layouts import column
from bokeh.models import Button
from bokeh.palettes import Dark2
from bokeh.plotting import figure, ColumnDataSource
from bokeh.server.server import Server

STRATEGIES = ['DD', 'DC', 'CD', 'CCDD']


def make_document(doc):
    step = 0

    def new_step_data():
        nonlocal step
        result = [dict(step=[step],
                       ncount=[random.choice(range(1, 100))])
                  for _ in STRATEGIES]
        step += 1
        return result

    fig = figure(title='Streaming Line Plot', plot_width=800, plot_height=400)
    sources = []
    for s, d, c in zip(STRATEGIES, new_step_data(), Dark2[4]):
        # Generate the very first step right away
        # to avoid having a completely empty plot.
        ds = ColumnDataSource(d)
        sources.append(ds)
        fig.line(x='step', y='ncount', source=ds, color=c)

    callback_obj = None

    def button1_run():
        nonlocal callback_obj
        if callback_obj is None:
            button1.label = 'Stop'
            button1.button_type = 'danger'
            callback_obj = doc.add_periodic_callback(button2_step, 100)
        else:
            button1.label = 'Run'
            button1.button_type = 'success'
            doc.remove_periodic_callback(callback_obj)

    def button2_step():
        for src, data in zip(sources, new_step_data()):
            src.stream(data)

    # add on_click callback for button widget
    button1 = Button(label="Run", button_type='success', width=390)
    button1.on_click(button1_run)
    button2 = Button(label="Step", button_type='primary', width=390)
    button2.on_click(button2_step)

    doc.add_root(column(fig, button1, button2))
    doc.title = "Now with live updating!"


apps = {'/': Application(FunctionHandler(make_document))}

server = Server(apps, port=5004)

if __name__ == '__main__':
    server.io_loop.add_callback(server.show, "/")
    server.start()
    server.io_loop.start()
0 голосов
/ 23 апреля 2020

Спасибо, Евгений. Ваше решение вернуло меня на правильный путь. Я немного поиграл с этим и в итоге получил следующее:

import colorcet as cc
from bokeh.server.server import Server
from bokeh.application import Application
from bokeh.application.handlers.function import FunctionHandler
from bokeh.plotting import figure, ColumnDataSource
from bokeh.models import Button
from bokeh.layouts import column
import random

def make_document(doc):

    # make a list of groups
    strategies = ['DD', 'DC', 'CD', 'CCDD']

    # initialize some vars
    step = 0
    callback_obj = None  
    colors = cc.glasbey_dark
    # create a list to hold all CDSs for active strategies in next step
    sources = []

    # Create a figure container
    fig = figure(title='Streaming Line Plot - Step 0', plot_width=800, plot_height=400)

    # get step 0 data for initial strategies
    for i in range(len(strategies)):
        step_data = dict(step=[step], 
                        strategy = [strategies[i]],
                        ncount=[random.choice(range(1, 100))])
        data_source = ColumnDataSource(step_data)
        color = colors[i]
        # this will create one fig.line renderer for each strategy & its data for this step
        fig.line(x='step', y='ncount', source=data_source, color=color, line_width=2)
        # add this CDS to the sources list
        sources.append(data_source)

    def button1_run():
        nonlocal callback_obj
        if button1.label == 'Run':
            button1.label = 'Stop'
            button1.button_type='danger'
            callback_obj = doc.add_periodic_callback(button2_step, 100)
        else:
            button1.label = 'Run'
            button1.button_type = 'success'
            doc.remove_periodic_callback(callback_obj)

    def button2_step():
        nonlocal step
        data = []
        step += 1
        fig.title.text = 'Streaming Line Plot - Step '+str(step)
        for i in range(len(strategies)):
            step_data = dict(step=[step], 
                            strategy = [strategies[i]],
                            ncount=[random.choice(range(1, 100))])
            data.append(step_data)
        for source, data in zip(sources, data):
            source.stream(data)        


    # add on_click callback for button widget
    button1 = Button(label="Run", button_type='success', width=390)
    button1.on_click(button1_run)
    button2 = Button(label="Step", button_type='primary', width=390)
    button2.on_click(button2_step)

    doc.add_root(column(fig, button1, button2))
    doc.title = "Now with live updating!"

apps = {'/': Application(FunctionHandler(make_document))}

server = Server(apps, port=5004)
server.start()

if __name__ == '__main__':
    server.io_loop.add_callback(server.show, "/")
    server.io_loop.start()

Результат - это то, что я искал ... enter image description here

...