Reputation: 11
To preface, I have never used JavaScript. After hours of searching I still do not quite understand how to solve my particular problem.
I have been utilizing bokeh to make interactive plots for a little while, and have used pandas to filter the data as seen in the code below.
import pandas as pd
import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource
df = pd.DataFrame()
df['name'] = ['a','a','a','b','b','b','c','c','c']
df['sample'] =np.random.randint(1,3,9)
df['x'] = np.random.randint(0,10,9)
df['y'] = np.random.randint(0,10,9)
plot = figure(width=400, height=400)
for name in df.name.unique():
source = ColumnDataSource(df[df['name']==name].groupby(['sample']).median())
plot.circle('x', 'y', source=source, legend_label=name)
plot.legend.click_policy="hide"
plot.legend.background_fill_alpha = 0.25
show(plot)
The problem I have with this is that if I have too many names then the legend gets long and the plot gets messy. I tried to resolve this by adding a MultiChoice widget where the user selects which names to filter the data by and plot, while using a CustomJS callback to adjust the data source. I run in to a problem since I need to filter the data by 'name', then group by 'sample' and find that median of those points.
I suppose my lack of JS knowledge is leaving me a little lost here. Could anyone point me in the right direction?
EDIT (Updates and clarification): Here is another attempt that I haven't gotten to work. Through the console log it appears that the source changes as I make selections on the MultiChoice widget. My plot however does not change. Also I need this to work with html, I can't use a bokeh server in this case.
import pandas as pd
import numpy as np
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, CustomJS, MultiChoice
from bokeh.layouts import row
df = pd.DataFrame()
df['name'] = ['a','a','a','b','b','b','c','c','c']
df['sample'] =np.random.randint(1,3,9)
df['x'] = np.random.randint(0,10,9)
df['y'] = np.random.randint(0,10,9)
plot = figure(width=400, height=400)
source = ColumnDataSource(df)
initial_value = [df.name[0]]
options = list(df.name.unique())
multi_choice = MultiChoice(value=initial_value, options=options, max_items=3, title='Selection:')
temp_choice_dict = {}
for name in df.name.unique():
if name == 'a':
choice_dict = {'name': name}
else:
temp_choice_dict = {'name': name}
for key, value in temp_choice_dict.items():
choice_dict=set_key(choice_dict,key,value)
source_name = ColumnDataSource(name_dict)
if source_name.data['name']==[]:
source_empty = ColumnDataSource({'x':[],'y':[]})
plot.circle('x', 'y', source=source_empty)
else:
for i in range(len(source_name.data['name'])):
source = ColumnDataSource(df[df['name']==source_name.data['name'][i]].groupby(['sample']).median())
plot.circle('x', 'y', source=source, legend_label=source_name.data['name'][i])
plot.circle('x', 'y', source=source)
callback = CustomJS(args={'source_name':source_name},code="""
console.log(' changed selected option', cb_obj.value);
source_name.data['name'] = cb_obj.value;
console.log(' source change', source_name.data['name']);
source_name.change.emit();
""")
multi_choice.js_on_change('value', callback)
plot.legend.background_fill_alpha = 0.25
show(row(plot,multi_choice))
Upvotes: 0
Views: 1626
Reputation: 11
I was able to find some help over on the bokeh forum. The solution was to adjust the visibility of the selected glyphs and legend labels. A full solution is down below.
import pandas as pd
import numpy as np
from bokeh.plotting import figure, show, row
from bokeh.models import ColumnDataSource, CustomJS, MultiChoice
df = pd.DataFrame()
df['name'] = ['a','a','a','b','b','b','c','c','c']
df['sample'] =np.random.randint(1,3,9)
df['x'] = np.random.randint(0,10,9)
df['y'] = np.random.randint(0,10,9)
plot = figure(width=400, height=400)
name_dict={'name':[],'legend':[],'label':[]}
for name in df.name.unique():
source = ColumnDataSource(df[df['name']==name].groupby(['sample']).median())
name_glyph = plot.circle('x', 'y', source=source, legend_label=name)
name_dict['name'].append(name_glyph)
name_dict['label'].append(name)
name_legend_dict={'name':[]}
for label in range(len(df.name.unique())):
name_dict['legend'].append(plot.legend.items[label])
# Set up MultiChoice widget
initial_value = [df.name[0]]
options = list(df.name.unique())
multi_choice = MultiChoice(value=initial_value, options=options, max_items=3, title='Selection:')
for i in range(len(options)):
if name_dict['label'][i] in initial_value:
name_dict['name'][i].visible = True;
name_dict['legend'][i].visible = True;
else:
name_dict['name'][i].visible = False;
name_dict['legend'][i].visible = False;
callback = CustomJS(args=dict(name_dict=name_dict, multi_choice=multi_choice), code="""
var selected_vals = multi_choice.value;
var index_check = [];
for (var i = 0; i < name_dict['name'].length; i++) {
index_check[i]=selected_vals.indexOf(name_dict['label'][i]);
if ((index_check[i])>= 0) {
name_dict['name'][i].visible = true;
name_dict['legend'][i].visible = true;
}
else {
name_dict['name'][i].visible = false;
name_dict['legend'][i].visible = false;
}
}
""")
multi_choice.js_on_change('value', callback)
plot.legend.background_fill_alpha = 0.25
show(row(plot,multi_choice))
Upvotes: 1