2016-07-24 15 views
9

Moje pytanie jest podobne do another thread przy użyciu bokeh 0.7.1, ale interfejs API dla serwerów bokeh zmienił się wystarczająco w 0.12.0, że walczę aby dostosować tę odpowiedź do nowej wersji.Dynamicznie dodawaj/usuwaj fabułę za pomocą "bokeh serve" (bokeh 0.12.0)

Podsumowując, mam stronę z siatką wykresów przebiegu czasowego, pobierającą dane z pliku, który jest ciągle aktualizowany. Strona zawiera menu MultiSelect, które zawiera listę wszystkich zmiennych w moim pliku. Chcę móc wybrać różne zmienne w menu, nacisnąć przycisk, a następnie znikną wykresy istniejącej zmiennej i zastąpić je nowymi strumieniami czasowymi, w których liczba wykresów może być inna. Używam skryptu o opakowaniu bokeh serve --show script.py.

W mojej pierwszej próbie tego, przypisałem program obsługi zdarzeń do przycisku, który usuwa "curdoc", a następnie dodaje wykresy dla nowo wybranych zmiennych z MultiSelect. To działa, ale liczba działek nie aktualizuje się. Najwyraźniej brakuje mi połączenia, które mówi serwerowi, aby jakoś odświeżył układ strony.

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) 

Odpowiedz

6

Podobny problem został rozwiązany na stronie Gimet Bokeh here.

Zasadniczo, zamiast mieszać z curdoc(), modyfikuje się elementy potomne obiektu układu, np. someLayoutHandle.children.

Prostym przykładem jest za pomocą przycisku przełączania, aby dodać lub usunąć wykres:

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

część, która dała mi najwięcej problemów było upewnienie się, że fabuła chciałem dodać była częścią curdoc(), który dlatego definicja znajduje się w funkcji wywołania zwrotnego. Jeśli nie znajduje się on w obrębie wywołania zwrotnego, za każdym razem, gdy działka 2 zostanie usunięte, nie można go znaleźć w backendzie bokeh. Aby to sprawdzić, odkomentuj instrukcję drukowania w funkcji wywołania zwrotnego.

Mam nadzieję, że to pomoże!

+1

Każdy pomysł, jak to zrobić w przypadku aplikacji serwerowej? Wszystko jest jasne, ale session.loop_until_closed() nie działa w przypadku bokeh. –

+0

Wygląda na to, że odradza się użycie 'loop_until_closed()': https://github.com/bokeh/bokeh/pull/7339 –

Powiązane problemy