Replacing figure and table in layout when using global ColumnDataSource

﹥>﹥吖頭↗ 提交于 2019-12-11 15:29:22

问题


I am using bokeh 0.12.9. I have a table and a figure which I replace in the global layout on callback. I usually build the ColumnDataSource right before I build the new figure/table. Now I wanted to try and see if I can have a global ColumnDataSource so that I can adjust the data via a CDSView (no need to replace table/figure then).

Unfortunately even keeping a separate CDS and view for table and plot fails. When clicking the radio button a couple of times I receive the following javascript error: Uncaught TypeError: Cannot read property 'data' of undefined

from datetime import date
from random import randint

from bokeh.models import Line
import numpy as np
import pandas as pd

from bokeh.plotting import figure, output_file, show
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import DataTable, DateFormatter, TableColumn
import bokeh.layouts as layouts
import bokeh.models.widgets as widgets
from bokeh.io import curdoc
from bokeh.models import CustomJS, Slider
from bokeh import palettes
from bokeh.layouts import layout
from bokeh.models import ColumnDataSource, CDSView, IndexFilter
from bokeh.models import widgets


def gen_plot(source=None, view=None):
    p = figure(title='test',
               x_axis_type="datetime",
               plot_width=600, plot_height=400)
    colors = palettes.Category10[10]
    cols = [str(col) for col in source.column_names]
    for ix, col in enumerate(cols):
        if col == 'index':
            continue
        r = p.line(x='index', y=col, source=source, view=view,
                   legend='_' + col,
                   color=colors[ix])
    p.legend.location = "bottom_left"
    return p


def gen_table(source=None, view=None):
    columns = [TableColumn(field=ele, title=ele) for ele
               in source.column_names]
    tab = widgets.DataTable(source=source, view=view, columns=columns,
                            selectable=False,
                            reorderable=False,
                            width=600, height=400)
    return tab



def update(attr, old, new):
    p = gen_plot(source=cdss[0], view=vs[0])
    t = gen_table(source=cdss[1], view=vs[1])

    print l.children
    l.children[1] = p
    l.children[2].children[0] = t


# set up data
cols = ['col1', 'col2', 'col3', 'col4']
df1 = pd.DataFrame(pd.util.testing.getTimeSeriesData())
df1.columns = cols
df2 = pd.DataFrame(pd.util.testing.getTimeSeriesData())
df2.columns = cols
dfs = [df1, df2]
cds1 = ColumnDataSource(df1)
cds2 = ColumnDataSource(df2)
cdss = [cds1, cds2]
filters = [IndexFilter([0, 1, 2, 4])]
filters = []
v1 = CDSView(source=cds1, filters=filters)
v2 = CDSView(source=cds2, filters=filters)
vs = [v1, v2]


# initialize items to replace
p = gen_plot(source=cdss[0], view=vs[0])
t = gen_table(source=cdss[1], view=vs[1])

# initialize controls
radio_wghting = widgets.RadioButtonGroup(labels=["Equal", "Exponential"],
                                         active=0)
radio_wghting.on_change('active', update)

# set up layout
sizing_mode = 'fixed'
l = layout([radio_wghting, p, t], sizing_mode=sizing_mode)

curdoc().add_root(l)
curdoc().title = 'blub'


# call callback initially
update('value', 0, 0)

Any hints are much appreciated!


回答1:


Now I wanted to try and see if I can have a global ColumnDataSource so that I can adjust the data via a CDSView (no need to replace table/figure then).

The code you are showing is the one in which you are trying to replace the figure and table.

When you replace the child of a layout object in that way, you are not actually removing the previous figures from curdoc, and other elements in the document still have the old figures and tables in their references.

You could try something like that to update the sources directly.

for rend in p.renderers:
    try:
        rend.data_source
    except AttributeError:
        pass
    else:
        rend.data_source.data.update(new_data_dictionary)

and

t.source.data.update(new_data_dictionary)

EDIT to answer the comment

from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource, Button
from bokeh.layouts import gridplot, widgetbox

from random import random, choice

import numpy as np

my_data = {1:{'x':[],'y':[],'colo':[],'size':[]}}

kelly_colors = [    '#F3C300','#875692', '#F38400', '#A1CAF1','#BE0032', '#C2B280', '#848482','#008856', '#E68FAC', '#0067A5',
                    '#F99379', '#604E97', '#F6A600','#B3446C', '#DCD300', '#882D17','#8DB600', '#654522', '#E25822','#2B3D26',      ]

x = np.arange(0,50,0.1)

def rand_dict():

    rand_x = [choice(x) for i in range(7)]

    return {'x':rand_x,'y':np.array([random()*100 for i in rand_x]),'colo':np.array([choice(kelly_colors) for i in rand_x]),'size':np.array([(5+int(random()*50)) for i in rand_x])}

def add_stuff():

    global my_data

    my_data[max(my_data.keys())+1] = rand_dict()

    make_doc()

def change_stuff():

    global my_data

    myfig = curdoc().select_one({"name":"myfig"})

    for i,rend in enumerate(myfig.renderers):
        try:
            rend.data_source
        except AttributeError:
            pass
        else:
            my_data[i+1] = rand_dict()
            rend.data_source.data.update(my_data[i+1])

def clear_stuff():

    global my_data

    my_data = {1:{'x':[],'y':[],'colo':[],'size':[]}}

    make_doc()

def make_doc():

    curdoc().clear()

    myfig = figure(plot_width=1000,plot_height=800,outline_line_alpha=0,name='myfig')
    myfig.x_range.start = -5
    myfig.x_range.end = 55
    myfig.y_range.start = -10
    myfig.y_range.end = 110

    myfig.renderers = []

    add_button = Button(label='add stuff',width=100)
    change_button = Button(label='change stuff',width=100)
    clear_button = Button(label='clear stuff',width=100)

    add_button.on_click(add_stuff)
    change_button.on_click(change_stuff)
    clear_button.on_click(clear_stuff)

    grid = gridplot([[myfig,widgetbox(add_button,change_button,clear_button)]],toolbar_location=None)

    curdoc().add_root(grid)

    update_doc()

def update_doc():

    myfig = curdoc().select_one({"name":"myfig"})

    for key in my_data:
        myfig.scatter(x='x',y='y',color='colo',size='size',source=ColumnDataSource(data=my_data[key]))

curdoc().title = 'mytitle'

make_doc()

what I like about doing this is that you can just save the my_data dictionary with numpy, load it later and keep changing your plots from there.

def load_data():

    global my_data

    my_data = np.load(path_to_saved_data).item()

    make_doc()

You can probably do something similar using pandas dataframes, I am just more comfortable with plain dictionaries.



来源:https://stackoverflow.com/questions/46697867/replacing-figure-and-table-in-layout-when-using-global-columndatasource

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!