2016-07-24 16 views
9

Meine Frage ist ziemlich ähnlich zu another thread mit Bokeh 0.7.1, aber die API für Bokeh-Server hat genug in 0.12.0 geändert, dass ich kämpfe um diese Antwort auf die neue Version anzupassen.Dynamisch hinzufügen/entfernen Plot mit 'Bokeh dienen' (Bokeh 0.12.0)

Um es zusammenzufassen, ich habe eine Seite mit einem Raster von Zeitstrom Plots aus einer Datei ziehen Daten, die kontinuierlich aktualisiert wird. Die Seite verfügt über ein MultiSelect-Menü, das alle Variablen in meiner Datei auflistet. Ich möchte in der Lage sein, verschiedene Variablen im Menü auszuwählen, einen Knopf zu drücken und dann die Plots der vorhandenen Variablen verschwinden zu lassen und durch die neuen Zeitströme ersetzt zu werden, wo die Anzahl der Plots unterschiedlich sein kann. Ich führe mein Skript mit dem bokeh serve --show script.py Wrapper.

In meinem ersten Versuch dazu, ich einen Event-Handler eine Taste zugewiesen, die ‚curdoc‘ und füge dann Plots für die neu gewählten Variablen aus der Multiselect löscht. Dies wird ausgeführt, aber die Anzahl der Plots wird nicht aktualisiert. Offensichtlich fehlt mir der Anruf, der dem Server mitteilt, das Seitenlayout irgendwie zu aktualisieren.

import numpy as np 

from bokeh.driving import count 
from bokeh.plotting import figure, curdoc 
from bokeh.layouts import gridplot 
from bokeh.models import Slider, Column, Row, ColumnDataSource, MultiSelect, Button 
from netCDF4 import Dataset 
import datetime 

# data 
#data = Dataset('/daq/spt3g_software/dfmux/bin/output.nc', 'r', format='NETCDF4') 
data = Dataset('20160714_warm_overbiased_noise.nc', 'r', format='NETCDF4') 
vars = data.variables.keys()[1:11] 

# plots 
d = {('y_%s'%name):[] for name in vars} 
d['t'] = [] 
source = ColumnDataSource(data=d) 

figs = [figure(x_axis_type="datetime", title=name) for name in vars] 
plots = [f.line(x='t', y=('y_%s'%f.title.text), source=source, color="navy", line_width=1) for f in figs] 
grid = gridplot(figs, ncols=3, plot_width=500, plot_height=250) 

# UI definition 
npoints = 2000 
slider_npoints = Slider(title="# of points", value=npoints, start=1000, end=10000, step=1000.) 
detector_select = MultiSelect(title="Timestreams:", value=[], options=vars) 
update_detector_button = Button(label="update detectors", button_type="success") 

# UI event handlers 
def update_detector_handler(): 
    global figs, plots, grid, source 
    d = {('y_%s'%name):[] for name in detector_select.value} 
    d['t'] = [] 
    source = ColumnDataSource(data=d) 

    figs = [figure(x_axis_type="datetime", title=name) for name in detector_select.value] 
    plots = [f.line(x='t', y=('y_%s'%f.title.text), source=source, color="navy", line_width=1) for f in figs] 
    grid = gridplot(figs, ncols=3, plot_width=500, plot_height=250) 
    curdoc().clear() 
    curdoc().add_root(Column(Row(slider_npoints, Column(detector_select, update_detector_button)), grid)) 

update_detector_button.on_click(update_detector_handler) 

# callback updater 
@count() 
def update(t): 
    data = Dataset('20160714_warm_overbiased_noise.nc', 'r', format='NETCDF4') 
    #data = Dataset('/daq/spt3g_software/dfmux/bin/output.nc', 'r', format='NETCDF4') 

    npoints = int(slider_npoints.value) 
    new_data = {('y_%s'%f.title.text):data[f.title.text][-npoints:] for f in figs} 
    new_data['t'] = data['Time'][-npoints:]*1e3 

    source.stream(new_data, npoints) 

# define HTML layout and behavior 
curdoc().add_root(Column(Row(slider_npoints, Column(detector_select, update_detector_button)), grid)) 
curdoc().add_periodic_callback(update, 500) 

Antwort

6

Es wurde ein ähnliches Problem auf der Seite Github Bokeh beantwortet here.

Wesentlichen statt mit curdoc() von Unordnung Sie stattdessen die Kinder des Layout-Objekt ändern z.B. someLayoutHandle.children.

Ein einfaches Beispiel wird mit einem Toggle-Button eine Grafik hinzufügen und entfernen:

from bokeh.client import push_session 
from bokeh.layouts import column, row 
from bokeh.models import Toggle 
from bokeh.plotting import figure, curdoc 
import numpy as np 
# Create an arbitrary figure 
p1 = figure(name = 'plot1') 

# Create sin and cos data 
x = np.linspace(0, 4*np.pi, 100) 
y1 = np.sin(x) 
y2 = np.cos(x) 

# Create two plots 
r1 = p1.circle(x,y1) 

# Create the toggle button 
toggle = Toggle(label = 'Add Graph',active=False) 

mainLayout = column(row(toggle,name='Widgets'),p1,name='mainLayout') 
curdoc().add_root(mainLayout) 
session = push_session(curdoc()) 
# Callback which either adds or removes a plot depending on whether the toggle is active 
def toggleCallback(attr): 
    # Get the layout object added to the documents root 
    rootLayout = curdoc().get_model_by_name('mainLayout') 
    listOfSubLayouts = rootLayout.children 

    # Either add or remove the second graph 
    if toggle.active == False: 
     plotToRemove = curdoc().get_model_by_name('plot2') 
     listOfSubLayouts.remove(plotToRemove) 

    if toggle.active == True: 
     if not curdoc().get_model_by_name('plot2'): 
      p2 = figure(name='plot2') 
      plotToAdd = p2 
      p2.line(x,y2) 
      # print('Remade plot 2') 
     else: 
      plotToAdd = curdoc().get_model_by_name('plot2') 
     listOfSubLayouts.append(plotToAdd) 

# Set the callback for the toggle button 
toggle.on_click(toggleCallback) 

session.show() 
session.loop_until_closed() 

den Teil, der mir am meisten Mühe gab, war dafür zu sorgen, dass die Handlung Ich war Teil curdoc() hinzufügen wollte, das Deshalb ist die Definition in der Callback-Funktion. Wenn es nicht innerhalb des Callbacks ist, kann jedes Mal, wenn Plot2 entfernt wird, es vom Bokeh-Backend nicht gefunden werden. Um dies zu überprüfen, müssen Sie die print-Anweisung in der Callback-Funktion auskommentieren.

Ich hoffe, das hilft!

+1

Haben Sie eine Idee, wie dies für die Serveranwendung möglich ist? Alles ist klar, aber session.loop_until_closed() scheint nicht für bokeh serve zu funktionieren. –

+0

Es sieht so aus, als ob die Verwendung von 'loop_until_closed() 'jetzt nicht empfohlen wird: https://github.com/bokeh/bokeh/pull/7339 –