简体   繁体   English

在散景服务器应用程序中绘制多行流数据

[英]plotting multiple lines of streaming data in a bokeh server application

I'm trying to build a bokeh application with streaming data that tracks multiple "strategies" as they are generated in a prisoners-dilemma agent based model.我正在尝试使用流数据构建一个散景应用程序,该应用程序跟踪多个“策略”,因为它们是在基于 model 的囚徒困境代理中生成的。 I've run into a problem trying to get my line plots NOT to connect all the data points in one line.我遇到了一个问题,试图让我的线图不将所有数据点连接在一条线上。 I put together this little demo script that replicates the issue.我把这个复制问题的小演示脚本放在一起。 I've read lots of documentation on line and multi_line rendering in bokeh plots, but I just haven't found something that seems to match my simple case.我已经阅读了大量关于散景图中线和多线渲染的文档,但我只是没有找到与我的简单案例相匹配的东西。 You can run this code & it will automatically open a bokeh server at localhost:5004...您可以运行此代码,它会自动在 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()

My hope was that by looping thru the 4 "strategies" in the example (after clicking button2), I could stream the new data coming out of the simulation into a line plot for that one strategy and step only.我希望通过循环遍历示例中的 4 个“策略”(在单击按钮 2 后),我可以 ZF7B44CFFAFD5C52223D5498196C8A2E7BZ 将模拟出来的新数据放入一行 plot 中,仅用于该策略和步骤。 But what I get is one line with all four values connected vertically, then one of them connected to the first one at the next step.但我得到的是一条所有四个值都垂直连接的线,然后其中一个在下一步连接到第一个值。 Here's what it looks like after a few steps:以下是几个步骤后的样子: 在此处输入图像描述

I noticed that if I move data_source.stream(new) out of the for loop, I get a nice single line plot, but of course it is only for the last strategy coming out of the loop.我注意到,如果我将data_source.stream(new)移出 for 循环,我会得到一个很好的单行 plot,但当然它只是针对最后一个策略退出循环。

In all the bokeh multiple line plotting examples I've studied (not the multi_line glyph, which I can't figure out and which seems to have some issues with the Hover tool), the instructions seem pretty clear: if you want to render a second line, you add another fig.line renderer to an existing figure , and it draws a line with the data provided in source=data_source for this line.在我研究过的所有散景多线绘图示例中(不是多线字形,我无法弄清楚它似乎与multi_line工具有一些问题),说明看起来很清楚:如果你想渲染第二行,您将另一个fig.line渲染器添加到现有的figure中,它会使用source=data_source中为该行提供的数据绘制一条线。 But even though my for-loop collects and adds data separately for each strategy, I don't get 4 line plots, I get only one.但即使我的 for 循环为每个策略分别收集和添加数据,我也没有得到 4 个线图,我只得到一个。

Hoping I'm missing something obvious.希望我错过了一些明显的东西。 Thanks in advance.提前致谢。

Seems like you need a line per strategy, not a line per step.似乎您需要每个策略一行,而不是每一步一行。 If so, here's how I would do it:如果是这样,我会这样做:

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()

Thank you, Eugene.谢谢你,尤金。 Your solution got me back on the right track.你的解决方案让我回到了正确的轨道上。 I played around with it a bit more and ended up with the following:我玩了更多,最终得到以下结果:

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()

Result is just what I was looking for...结果正是我想要的... 在此处输入图像描述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM