Как я могу сбросить фигуру боке в исходное состояние с помощью кнопки обратного вызова «сброс»? - PullRequest
0 голосов
/ 24 апреля 2020

Я работал над проектом, использующим визуализацию bokeh для отображения результатов моделирования на основе агентов (ABM). В недавнем посте я получил помощь для правильной передачи моих данных в очень упрощенной версии моего моделирования. Моей следующей задачей, которая, как я думал, было легко, было добавить кнопку «сброс» в макет, чтобы я мог вернуть свою фигуру в исходное состояние и снова запустить симуляцию с «шага 0». Удивительно, но, кажется, не существует простого способа сделать это. Я пробовал несколько разных вещей, включая повторную инициализацию всех моих данных и повторное заполнение моих ColumnDataSources, но я не могу заставить данные предыдущих прогонов симуляции исчезнуть. Вот автономный пример кода, который иллюстрирует проблему:

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
    #num_colors = len(colors)
    # 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=1400, 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)        

    def button3_reset():
        step = 0
        fig.title.text = 'Streaming Line Plot - Step '+str(step)

        for i in range(len(strategies)):
            init_data = dict(step=[step], 
                            strategy = [strategies[i]],
                            ncount=[random.choice(range(1, 100))])
            reset_source = ColumnDataSource(init_data)
            print(init_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=reset_source, color=color, line_width=2)
            # add this CDS to the sources list
            sources.append(reset_source)


    # 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)
    button3 = Button(label="Reset", button_type='warning', width=390)
    button3.on_click(button3_reset)

    doc.add_root(column(fig, button1, button2, button3))
    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()

В моем коде button3_reset я пытаюсь повторить инициализацию в верхней части функции make_document. Но даже несмотря на то, что этот код работает идентично (как видно из вывода на печать, застрявшего в середине шага button3), я не могу вернуть рисунок в исходное пустое состояние. Я прочитал множество постов о переполнении стека и другую документацию по боке и не нашел простого ответа на простой вопрос: как вернуть линейный график боке в исходное состояние, чтобы вы могли запустить поток данных снова с начальной точки?

Я использую bokeh 1.4.0 (anaconda не разрешит мне обновить), python 3.7.6, spyder 4.0.1 и оба Chrome & Brave браузеры для визуализации.

Ответы [ 2 ]

0 голосов
/ 24 апреля 2020

Как обычно, я сильно усложнял вещи. Вот код для button3_reset, который выполняет эту работу.

 def button3_reset():
        nonlocal step
        step = 0
        data = []
        fig.title.text = 'Streaming Line Plot - Step '+str(step)

        for i in range(len(strategies)):
            init_data = dict(step=[step], 
                            strategy = [strategies[i]],
                            ncount=[random.choice(range(1, 100))])
            data.append(init_data)

        for source, data in zip(sources, data):
            source.data = data

То, что я делал раньше, создавало новые CDS, но старые все еще были встроены в рисунок. Еще раз благодаря совету Евгения, я понял, что мне нужно только переназначить атрибут .data существующих CDS, а не создавать новые. Затем, чтобы привести все в порядок, мне пришлось обновить заголовок до «Step 0», а затем сделать step нелокальной переменной, чтобы button2_step знал, чтобы перезапустить нумерацию шагов с 1. При этом, сбросить то, что он должен делать. Еще раз спасибо за ответы.

0 голосов
/ 24 апреля 2020

Ваш код button3_reset ничего не очищает - он просто добавляет новые вещи поверх существующих.

Вместо этого вам просто нужно перебрать список sources и установить data атрибут каждого источника к начальному значению, используемому в самом первом l oop в вашем коде. Это значит, что вам также придется где-то сохранять эти данные.

...