hvplot not refreshing properly when dependency changes - python

I am trying to establish a dashboard using jupyter, panel and hvplot. This is the code snippet.
import panel.widgets as pnw
import panel as pn
pn.extension()
# some data cleansing yields a df containing the data of interest
products = df["product_name"].drop_duplicates()
def histogram_of_variant_revenue(selected_product=None):
selection = df[df["product_name"] == selected_product]
selection["revenue"] = selection["revenue"].astype(float)
print(selection.shape)
return selection[["name", "revenue"]].drop_duplicates().set_index("name").hvplot.bar()
def histogram_of_variant_customers(selected_product=None):
selection = df[df["product_name"] == selected_product]
selection["customers"] = selection["customers"].astype(float)
print(selection.shape)
return selection[["name", "customers"]].drop_duplicates().set_index("name").hvplot.bar()
selected_product = pnw.Select(name='Product', options=sorted(list(products)))
customers = pn.bind(histogram_of_variant_customers, selected_product)
revenue = pn.bind(histogram_of_variant_revenue, selected_product)
combined_panel = pn.Column(selected_product, customers, revenue)
combined_panel
At the default suggestion.
After the next use of the drop-down selection. Notice that instead of getting a new chart - the old one seems to have moved to the right and the new one placed into the figure.
Any idea on how I can get a new histogram after selecting from the drop-down?

Related

PyQt5 update QVBoxLayout inside scrollArea

I do have a problem while updating my content on-demand.
My scenario: I want to create a GUI which stores information about some products and it should be displayed via a scroll are. This is working fine, but when I want to update the information like the quantity of my item, the GUI or the layout of my QVBoxLayout does not update the new information on the screen.
My code for the update function:
#pyqtSlot(GroupContent)
def _updateData(self, box: GroupContent) -> None:
prompt = UpdatePrompt(self._conn, box)
prompt.exec()
amount = self._top_layout.count()
for i in range(amount):
tmp = self._top_layout.itemAt(i).widget()
if tmp.id != box.id:
continue
tmp.setTitle(box.title)
tmp.lblPrice.setText(str(box.price))
tmp.lblQuantity.setText(str(box.quantity))
The param box does already get the updated information from a QDialog.
The self._top_layout variable will be created with self._top_layout = QVBoxLayout().
I already tried to call update on the Mainwindow and also on the top layout.
Also tried directly accessing the widget with self._top_layout.itemAt(i).widget().setTitle('test') for example.
If this information is necessary, here is my function to dynamic generate the groupboxes:
def _populate(self, insert: Boolean = False) -> None:
data = self._getDBData(insert)
for row in data:
group_box = GroupContent()
group_box.storage_id.connect(self._updateData)
group_box.id = row.storage_id
group_box.title = row.name
group_box.price = row.price
group_box.quantity = row.quantity
group_box.image = row.image
self._top_layout.addWidget(group_box)

How to update existing plot with Panel?

I have a dashboard application that works with Bokeh. I am trying to change it to use Panel and Geoviews. I am using the Panel Callback API, as this seems most like my existing code with Bokeh. I am running a regular Python script with the Panel server.
When my callback creates the new plot for the widgets selection then Panel displays an additional plot instead of updating the existing plot. Using "servable" causes an additional plot in the existing browser window, using "show" displays an additional window. How do I update the existing plot?
Here is some test code. (The full application displays a choropleth map with Geo data, and has many more widgets with code that reads different data, but this code illustrates the problem.)
import census_read_data as crd
import census_read_geopandas as crg
import pandas as pd
import geopandas as gpd
import geoviews as gv
from bokeh.plotting import show
from bokeh.models import PrintfTickFormatter
import panel as pn
import hvplot.pandas
# Get Census Merged Ward and Local Authority Data
# Replaced by test DataFrame
geography = pd.DataFrame(data=[
['E36007378', 'Chiswick Riverside', 'E09000018', 'Hounslow'],
['E36007379', 'Cranford', 'E09000018', 'Hounslow'],
['E36007202', 'Ealing Broadway', 'E09000009', 'Ealing'],
['E36007203', 'Ealing Common', 'E09000009', 'Ealing'],
['E36007204', 'East Acton', 'E09000009', 'Ealing'],
['E09000018', 'Hounslow', 'E09000018', 'Hounslow'],
['E09000009', 'Ealing', 'E09000009', 'Ealing']
], columns=["GeographyCode", "Name", "LAD11CD", "LAD11NM"])
# Get London Ward GeoPandas DataFrame
# Replaced by test DataFrame
london_wards_data_gdf = pd.DataFrame(data=[
['E36007378', 'E09000018', 378],
['E36007379', 'E09000018', 379],
['E36007202', 'E09000009', 202],
['E36007203', 'E09000009', 203],
['E36007204', 'E09000009', 204]
], columns=["cmwd11cd", "lad11cd", "data"])
# Get LAD GeoPandas DataFrame
# Replaced by test DataFrame
london_lads_data_gdf = pd.DataFrame(data=[
['E09000018', 757],
['E09000009', 609]
], columns=["lad11cd", "data"])
locationcol = "GeographyCode"
namecol = "Name"
datacol = 'data'
# Panel
pn.extension('bokeh')
gv.extension('bokeh')
lad_max_value = london_lads_data_gdf[datacol].max()
ward_max_value = london_wards_data_gdf[datacol].max()
title = datacol + " by Local Authority"
local_authorities = geography['LAD11CD'].unique()
granularities = ['Local Authorities', 'Wards']
# Create Widgets
granularity_widget = pn.widgets.RadioButtonGroup(options=granularities)
local_authority_widget = pn.widgets.Select(name='Wards for Local Authority',
options=['All'] +
[geography[geography[locationcol] == lad][namecol].iat[0]
for lad in local_authorities],
value='All')
widgets = pn.Column(granularity_widget, local_authority_widget)
layout = widgets
def update_graph(event):
# Callback recreates map when granularity or local_authority are changed
global layout
granularity = granularity_widget.value
local_authority_name = local_authority_widget.value
print(f'granularity={granularity}')
if granularity == 'Local Authorities':
gdf = london_lads_data_gdf
max_value = lad_max_value
title = datacol + " by Local Authority"
else:
max_value = ward_max_value
if local_authority_name == 'All':
gdf = london_wards_data_gdf
title = datacol + " by Ward"
else:
local_authority_id = geography[geography['Name'] ==
local_authority_name].iloc[0]['GeographyCode']
gdf = london_wards_data_gdf[london_wards_data_gdf['lad11cd'].str.match(
local_authority_id)]
title = datacol + " by Ward for " + local_authority_name
# Replace gv.Polygons with hvplot.bar for test purposes
map = gdf.hvplot.bar(y=datacol, height=500)
layout = pn.Column(widgets, map)
# With servable, a new plot is added to the browser window each time the widgets are changed
# layout.servable()
# With servable, a new browser window is shown each time the widgets are changed
layout.show()
granularity_widget.param.watch(update_graph, 'value')
local_authority_widget.param.watch(update_graph, 'value')
update_graph(None)
# panel serve panel_test_script.py --show
I ended up implementing my solution using Params, rather than callbacks, which worked great. However, I eventually saw Dynamically updating a Holoviz Panel layout which showed me a solution to my original question.
The callback should not show() a new layout (with the new map), but should simply update the existing layout, replacing the existing map with the new map. As I write this it seems obvious!
This code fragment shows the solution:
...
widgets = pn.Column(granularity_widget, local_authority_widget)
empty_map = pn.pane.Markdown('### Map placeholder...')
layout = pn.Column(widgets, empty_map)
def update_graph(event):
...
# Replace gv.Polygons with hvplot.bar for test purposes
map = gdf.hvplot.bar(y=datacol, height=500)
# Update existing layout with new map
layout[1] = map
granularity_widget.param.watch(update_graph, 'value')
local_authority_widget.param.watch(update_graph, 'value')
# Invoke callback to show map for initial widget values
update_graph(None)
layout.show()

Cannot populate a WPF DataGrid using Python

I have a problem binding data to a WPF DataGrid using Python.NET
The code is shown below, and I've tried three different approaches to binding data - each fails and the error message is included as a comment in the code below
If I do not attempt to add data, the datagarid displays correctly with headers. But I'm unable to populate the grid with any data.
Any help will be gratefully received!!!
Doug
import clr
#.NET references
import System
import System.Windows.Controls as WPFControls
import System.Windows.Data as WPFBindings
def getCustomToolPropertyContent():
#Create a Grid
my_Grid = WPFControls.Grid()
#Add 1 Row and One Column
my_Grid.RowDefinitions.Add(WPFControls.RowDefinition())
my_Grid.ColumnDefinitions.Add(WPFControls.ColumnDefinition())
# Create a DataGrid
myDataGrid = WPFControls.DataGrid()
#Create three columns
column_1 = WPFControls.DataGridTextColumn()
column_1.Header = "ID"
column_1.Binding = WPFBindings.Binding("id")
column_2 = WPFControls.DataGridTextColumn()
column_2.Header = "Title"
column_2.Binding = WPFBindings.Binding("title")
column_3 = WPFControls.DataGridTextColumn()
column_3.Header = "Content"
column_3.Binding = WPFBindings.Binding("content")
#Add the three columns to the datagrid
myDataGrid.Columns.Add(column_1)
myDataGrid.Columns.Add(column_2)
myDataGrid.Columns.Add(column_3)
# Data table approach....
# Fails with
# AttributeError : DataTable
#Create a DataTable
data_table = WPFBindings.DataTable("MyDataTable")
data_table.Columns.Add("id")
data_table.Columns.Add("title")
data_table.Columns.Add("content")
#Add data
data_table.Rows.Add("Andre", "Piratas", "1973")
data_table.Rows.Add("Andres", "Piratass", "1973s")
#DataTable to DataGrid
myDataGrid.DataContext = data_table.DefaultView
# Item Source Approach
# Fails with
# TypeError: 'list' value cannot be converted to System.Collections.IEnumerable
# items = []
# items.append(Student(id="1", title="Piratas", content="1973"))
# items.append(Student(id="2", title="XXXX", content="1974"))
# myDataGrid.ItemsSource = items
# Items.Add approach
# Fails with
# TypeError: No method matches given arguments
# myDataGrid.Items.Add(Student(id="1", title="Piratas", content="1973"))
# Position the DataGrid in the first row, column of the Grid
WPFControls.Grid.SetRow(myDataGrid, 0)
WPFControls.Grid.SetColumn(myDataGrid, 0)
#Add the DataGrid to the Grid
my_Grid.Children.Add(myDataGrid)
# Return the Grid
return my_Grid
OK,
I got it - I had the wrong import for the DataTable.
Changed
"import System.Windows.Data as WPFBindings"
to
"import System.Data as WPFData"
Then took the advice of Hamas and changed,
"myDataGrid.DataContext = data_table.DefaultView"
to
myDataGrid.ItemsSource= data_table.DefaultView
Thanks Hammas!!
I don't see you creating data_Table instance.
DataTable data_table = CreateDataTable();
Plus try this !
myDataGrid.ItemsSource = data_table.DefaultView;
and in XAML
<DataGrid Name="myDataGrid" ItemsSource="{Binding}">
For completeness, here is the working code.
Note that by using the DataTable I was able to remove all of the manual column, header and Binding setup
import clr
#.NET references
import System.Windows.Controls as WPFControls
import System.Data as WPFData
def getCustomToolPropertyContent():
#Create a Grid
my_Grid = WPFControls.Grid()
#Add 1 Row and One Column
my_Grid.RowDefinitions.Add(WPFControls.RowDefinition())
my_Grid.ColumnDefinitions.Add(WPFControls.ColumnDefinition())
# Create a DataGrid
myDataGrid = WPFControls.DataGrid()
# Data table approach....
#Create a DataTable
data_table = WPFData.DataTable("MyDataTable")
data_table.Columns.Add("ID")
data_table.Columns.Add("Title")
data_table.Columns.Add("Content")
#Add data
data_table.Rows.Add("Andre", "Piratas", "1973")
data_table.Rows.Add("Andres", "Piratass", "1973s")
myDataGrid.ItemsSource = data_table.DefaultView
# Position the DataGrid in the first row, column of the Grid
WPFControls.Grid.SetRow(myDataGrid, 0)
WPFControls.Grid.SetColumn(myDataGrid, 0)
#Add the DataGrid to the Grid
my_Grid.Children.Add(myDataGrid)
# Return the Grid
return my_Grid

Bokeh - How to have the same widget (or duplicate a widget) in two different tabs?

I'm trying to create a widget filter (made up of TextInput and MultiSelect) that is replicated on two different Bokeh Tabs. The desired functionality is that filtering results should be preserved between tabs, regardless of which filter receives the text to filter off of.
The code below(it is working code) builds the Filter widget which is instantiated as filter1 and filter2. The callback is the update function which does the actual filtering and updates the MultiSelect part of the filter.
from bokeh.io import curdoc
from bokeh.layouts import column, widgetbox, row, layout, gridplot
from bokeh.models import Slider, Select, TextInput, MultiSelect
from bokeh.models.widgets import Panel, Tabs
import pandas as pd
from functools import partial
df = pd.DataFrame(["apples", "oranges", "grapes"], columns=["fruits"])
multiselect = None
input_box = None
def update(widget, attr, old, new):
print("df['fruits']: {}".format(list(df['fruits'])))
print("{} : {} changed: Old [ {} ] -> New [ {} ]".format(widget, attr, old, new))
if widget == 'input':
col_data = list(df[df['fruits'].str.contains(new)]['fruits'])
print("col_date: {}".format(col_data))
multiselect.update(options = sorted(list(col_data)))
def init():
global multiselect
multiselect = MultiSelect(title = 'multiselect',
name = 'multiselect',
value = [],
options = list(df["fruits"]))
multiselect.on_change('value', partial(update, multiselect.name))
global input_box
input_box = TextInput(title = 'input',
name ='input',
value='Enter you choice')
input_box.on_change('value', partial(update, input_box.name))
class Filter:
def __init__(self):
self.multiselect = multiselect
self.input_box = input_box
self.widget = widgetbox(self.input_box, self.multiselect)
init()
filter1 = Filter().widget
filter2 = Filter().widget
curdoc().add_root(row(filter1, filter2))
The code above produces/assembles the widget as shown here:
Also, the functionality of the two mirrored filters is as desired; when text is entered in one of the boxes, the results are displayed on both filters.
Now, and here is where I need help, I want the same filters with the same functionality but I need them in two different tabs; one filter in one tab and the other filter in the other tab.
The code used to build the two tabs structure is:
p1 = Panel(child = filter1, title = "Panel1")
p2 = Panel(child = filter2, title = "Panel2")
tabs = Tabs(tabs=[ p1, p2 ])
curdoc().add_root(layout(tabs))
On the results side, the code preserves the desired functionality but filters are displayed on the same page. More than that, panels/tabs are not even being built.
Any idea what's missing? (If you want to play with the code it should work right off the bat if you have bokeh installed.)
I do not think your example should even build a document, both your textinputs and multiselect models have the same id, which may be why the display of tabs gets messed up.
My solution is similar to HYRY's, but with a more general function to share attributes using two different things:
model.properties_with_values()
Can be used with any bokeh model and returns a dictionary of all the attribute:value pairs of the model. It's mostly useful in ipython to explore bokeh objects and debug
Document.select({'type':model_type})
Generator of all the widgets of the desired type in the document
Then I just filter out the widgets that do not share the same tags as the input widget, which would avoid "syncing" other inputs/multiselect not generated with box_maker(). I use tags because different models cannot have the same name.
When you change a TextInput value, it will change the associated Multiselect in the update function, but it will also change all the other TextInputs and trigger their update in the same way too. So each Input triggers update once and changes the options of their respective multiselect (and not multiplte times each because it's a "on_change" callback, if you give the same value for the new input it does not trigger).
For the Multiselect the first trigger of update will do the job, but since it changed the values of the other Multiselect it still triggers as many times as there are Multiselect widgets.
from bokeh.io import curdoc
from bokeh.layouts import widgetbox
from bokeh.models import TextInput, MultiSelect
from bokeh.models.widgets import Panel, Tabs
import pandas as pd
from functools import partial
df = pd.DataFrame(["apples", "oranges", "grapes"], columns=["fruits"])
def sync_attr(widget):
prop = widget.properties_with_values() # dictionary of attr:val pairs of the input widget
for elem in curdoc().select({'type':type(widget)}): # loop over widgets of the same type
if (elem!=widget) and (elem.tags==widget.tags): # filter out input widget and those with different tags
for key in prop: # loop over attributes
setattr(elem,key,prop[key]) # copy input properties
def update(attr,old,new,widget,other_widget):
print("\ndf['fruits']: {}".format(list(df['fruits'])))
print("{} : {} changed: Old [ {} ] -> New [ {} ]".format(str(widget),attr, old, new))
if type(widget)==TextInput:
col_data = list(df[df['fruits'].str.contains(new)]['fruits'])
print("col_date: {}".format(col_data))
other_widget.update(options = sorted(list(col_data)))
sync_attr(widget)
def box_maker():
multiselect = multiselect = MultiSelect(title = 'multiselect',tags=['in_box'],value = [],options = list(df["fruits"]))
input_box = TextInput(title = 'input',tags=['in_box'],value='Enter you choice')
multiselect.on_change('value',partial(update,widget=multiselect,other_widget=input_box))
input_box.on_change('value',partial(update,widget=input_box,other_widget=multiselect))
return widgetbox(input_box, multiselect)
box_list = [box_maker() for i in range(2)]
tabs = [Panel(child=box,title="Panel{}".format(i)) for i,box in enumerate(box_list)]
tabs = Tabs(tabs=tabs)
curdoc().add_root(tabs)
Note that the highlighting of the options in multiselect may not look consistent, but that just seems to be visual as the values/options of each of them are changing correctly.
But unless you are particularly attached to the layout look when you put the widgets inside the panels, you could just put one input and multiselect outside, and write their callbacks to deal with what will be in the different panels.
You can't use the same widget model to create multiple views. You can create new widgets in every tabs and link the value:
from bokeh.io import curdoc
from bokeh.layouts import column, widgetbox, row, layout, gridplot
from bokeh.models import Slider, Select, TextInput, MultiSelect, CustomJS
from bokeh.models.widgets import Panel, Tabs
import pandas as pd
from functools import partial
df = pd.DataFrame(["apples", "oranges", "grapes"], columns=["fruits"])
class Filter:
def __init__(self):
self.multiselect = MultiSelect(title = 'multiselect',
name = 'multiselect',
value = [],
options = list(df["fruits"]))
self.multiselect.on_change('value', self.selection_changed)
self.input_box = TextInput(title = 'input',
name ='input',
value='Enter you choice')
self.input_box.on_change('value', self.input_box_updated)
self.widget = widgetbox(self.input_box, self.multiselect)
def input_box_updated(self, attr, old, new):
print(attr, old, new)
col_data = list(df[df['fruits'].str.contains(new)]['fruits'])
self.multiselect.update(options = sorted(list(col_data)))
def selection_changed(self, attr, old, new):
print(new)
filter1 = Filter()
filter2 = Filter()
def link_property(property_name, *widgets):
wb = widgetbox(*widgets)
wb.tags = [property_name, 0]
def callback(widgets=wb):
if widgets.tags[1] != 0:
return
widgets.tags[1] = 1
for widget in widgets.children:
widget[widgets.tags[0]] = cb_obj.value
widgets.tags[1] = 0
jscallback = CustomJS.from_py_func(callback)
for widget in widgets:
widget.js_on_change(property_name, jscallback)
link_property("value", filter1.input_box, filter2.input_box)
link_property("value", filter1.multiselect, filter2.multiselect)
p1 = Panel(child = filter1.widget, title = "Panel1")
p2 = Panel(child = filter2.widget, title = "Panel2")
tabs = Tabs(tabs=[ p1, p2 ])
curdoc().add_root(layout(tabs))
It seems that there is a bug in MultiSelect that doesn't deselect previous items.

Simple Bokeh app: Chart does not update as expected

So I have been trying to build a little something from the bokeh example there:
https://demo.bokeh.org/weather.
My dataset is really similar and this should be very straight forward, yet I have a problem that I cannot explain.
import os , pickle
import pandas as pd
from bokeh.io import curdoc
from bokeh.layouts import row, column
from bokeh.models import ColumnDataSource, Select
from bokeh.plotting import figure
base_path = '/Users/xxxxxx/Desktop/data/'
domain = 'IEM_Domain'
metric = 'total_area_burned'
def get_dataset(dic , selection , scenario):
def _get_mean(thing):
_df = pd.DataFrame(thing)
_df = _df.mean(axis = 1).cumsum(axis=0)
return _df
data = { model : _get_mean( dic[model] ) for model in dic.keys() if all([scenario in model , selection in model])}
df = pd.DataFrame(data)
return ColumnDataSource(data=df)
def make_plot(source, title):
plot = figure(x_axis_type="datetime", plot_width=800, tools="")
plot.title.text = title
for _df in source :
for col in _df.to_df().columns :
if 'index' not in col :
plot.line( _df.to_df()['index'] , _df.to_df()[col] , source = _df)
else : pass
# fixed attributes
plot.xaxis.axis_label = 'Year'
plot.yaxis.axis_label = "Area burned (km)"
plot.axis.axis_label_text_font_style = "bold"
return plot
def update_plot(attrname, old, new):
rcp45 = rcp45_select.value
rcp85 = rcp85_select.value
src45 = get_dataset(dic , rcp45 , 'rcp45')
src85 = get_dataset(dic , rcp85 , 'rcp85')
source45.data = src45.data
source85.data = src85.data
rcp45 = 'CCSM4_rcp45'
rcp85 = 'CCSM4_rcp85'
dic = pickle.load(open(os.path.join(base_path , "_".join([domain , metric ]) + '.p'), 'rb'),encoding='latin1')
rcp45_models = [ i for i in dic.keys() if 'rcp45' in i]
rcp85_models = [ i for i in dic.keys() if 'rcp85' in i]
rcp45_select = Select(value=rcp45, title='RCP 45', options=sorted(rcp45_models))
rcp85_select = Select(value=rcp85, title='RCP 85', options=sorted(rcp85_models))
source45 = get_dataset(dic , rcp45 , 'rcp45')
source85 = get_dataset(dic , rcp85 ,'rcp85')
print(source45.data)
plot = make_plot([source45 , source85], "Total area burned ")
rcp45_select.on_change('value', update_plot)
rcp85_select.on_change('value', update_plot)
controls = column(rcp45_select, rcp85_select)
curdoc().add_root(row(plot, controls))
curdoc().title = "Total Area burned"
Everything runs find until I try to change the value in the dropdown, I can see that the function update_plot() is doing the job, updating the data when the dropdown is used. But for some reason the plot doesn't change , the example works fine though. I have been digging everywhere in the code but can't seem to find what I am doing wrong.
I have tried to simplify the make_plot() to see if it could come from there but that didn't change anything so I am out of ideas.
I found that but couldn't apply it : Bokeh: chart from pandas dataframe won't update on trigger
Edit after first answer
I tried to get ride of the columndatasource and replaced it by a traditionnal dictionnary but still run into the same issue.
Here is the updated code :
import os , pickle
import pandas as pd
from bokeh.io import curdoc
from bokeh.layouts import row, column
from bokeh.models import ColumnDataSource, Select
from bokeh.plotting import figure
base_path = '/Users/julienschroder/Desktop/data/'
domain = 'IEM_Domain'
metric = 'total_area_burned'
scenarios = ['rcp45','rcp85']
def get_dataset(dic ,selection , scenario = scenarios):
#function taking the raw source as dic and a selection of models, it return a dictionnary
# like this {scenario : pd.Dataframe(models)} that way i can plot each scenario on their own
def _get_mean_cumsum(df ,name):
#Extract, average and cumsum the raw data to a dataframe
_df = pd.DataFrame(df)
_df = _df.mean(axis = 1).cumsum(axis=0)
_df = _df.to_frame(name=name)
return _df
#Just one model at a time for now but hoping to get multilines and so multi models in the future
data = { scenario : pd.concat([_get_mean_cumsum(dic[model] , model) for model in selection if scenario in model ] ,axis=1) for scenario in scenarios }
return data
def make_plot(source, title):
plot = figure(x_axis_type="datetime", plot_width=800, tools="")
plot.title.text = title
#for now it will just deal with one model at a time but in the future I hope to have some multiline plotting hence the for loops
for col in source['rcp45']:
plot.line(source['rcp45'].index,source['rcp45'][col] )
for col in source['rcp85']:
plot.line(source['rcp85'].index , source['rcp85'][col])
# fixed attributes
plot.xaxis.axis_label = 'Year'
plot.yaxis.axis_label = "Area burned (km)"
plot.axis.axis_label_text_font_style = "bold"
return plot
def update_plot(attrname, old, new):
rcp45 = rcp45_select.value
rcp85 = rcp85_select.value
source = get_dataset(dic,[rcp45 ,rcp85])
#check to see if source gets updated
print(source) # <- gets updated properly after dropdown action
rcp45 = 'CCSM4_rcp45'
rcp85 = 'CCSM4_rcp85'
# dic = pickle.load(open(os.path.join(base_path , "_".join([domain , metric ]) + '.p'), 'rb'),encoding='latin1')
dic = pickle.load(open('IEM_Domain_total_area_burned.p', 'rb'),encoding='latin1') #data available there : https://github.com/julienschroder/Bokeh_app/tree/master/1
rcp45_models = [ i for i in dic.keys() if 'rcp45' in i]
rcp85_models = [ i for i in dic.keys() if 'rcp85' in i]
rcp45_select = Select(value=rcp45, title='RCP 45', options=sorted(rcp45_models))
rcp85_select = Select(value=rcp85, title='RCP 85', options=sorted(rcp85_models))
source = get_dataset(dic,[rcp45 ,rcp85])
plot = make_plot(source , "Total area burned ")
rcp45_select.on_change('value', update_plot)
rcp85_select.on_change('value', update_plot)
controls = column(rcp45_select, rcp85_select)
curdoc().add_root(row(plot, controls))
curdoc().title = "Total Area burned"
I get my two first lines but nothing happen when using the dropdown.
I uploaded a smaller dataset on this github page if someone wants to try out the data
https://github.com/julienschroder/Bokeh_app/tree/master/1
Well, I can't say with 100% certainty since I can't run the code without the data, but I have a decent good idea what the problem might be. The .data attribute one ColumnDataSource is not actually a simple Python dictionary:
In [4]: s = ColumnDataSource(data=dict(a=[1,2], b=[3,4]))
In [5]: type(s.data)
Out[5]: bokeh.core.property.containers.PropertyValueDict
It's actually a specially wrapped dictionary that can automatically emit event notifications when its contents are changed. This is part of the machinery that let's Bokeh respond and update things automatically in such a handy way. I'm guessing that setting the .data of one source by using the .data of another source is what is somehow causing the problem. I'm hypothesizing that setting .data with something besides a real Python dicts prevents the event handlers not getting wired up correctly.
So, a suggestion for an immediate-term workaround: Don't construct a ColumnDataSource in get_dataset. Only construct and return a plain Python dictionary. It's possible that df.to_dict will just give you e exactly the right sort of dict. Or else you can make a dict by hand, putting in the columns you need.
And a request: It's possible that this limitation can be fixed up. Or if not, it's certainly possible that a loud warning can be made if a user does this. Please file a bug report on the GitHub issue tracker with all this information.

Categories

Resources