I am trying to create plots in python using bokeh that allow dynamic visualization of data in bins. It's worth knowing that I am relatively new to python, very new to bokeh, and I know ZERO javascript. I have consulted this:
Link a Span or Cursor in between plots with Bokeh in Python
and this:
http://docs.bokeh.org/en/latest/docs/user_guide/interaction/callbacks.html
but am having trouble implementing the necessary parts of each. Here is my code prior to adding the requested capabilities:
from bokeh.layouts import column, widgetbox
from bokeh.models.widgets import Slider
from bokeh.models import Span, CustomJS
output_file('Raw_Spectra_and_Spillover_Data.html')
# widgets for bin setup
Pix1_LowLow = Slider(start = self.StartDAC, end = self.EndDAC, value = 129, step = 1, title = 'Pixel-1 - Low Bin - Low Thresh')
Pix1_LowHigh = Slider(start = self.StartDAC, end = self.EndDAC, value = 204, step = 1, title = 'Pixel-1 - Low Bin - High Thresh')
Pix1_HighLow = Slider(start = self.StartDAC, end = self.EndDAC, value = 218, step = 1, title = 'Pixel-1 - High Bin - Low Thresh')
Pix1_HighHigh = Slider(start = self.StartDAC, end = self.EndDAC, value = 500, step = 1, title = 'Pixel-1 - High Bin - High Thresh')
plot1spect = figure(width=700, height=250, title='pixel-1 Spectrum')
plot1spect.line(self.SpectDACvals1[0], self.SpectrumData1[0], line_width=2)
plot1spect_LowLowSpan = Span(location=Pix1_LowLow.value, dimension = 'height')
plot1spect_LowHighSpan = Span(location=Pix1_LowHigh.value, dimension = 'height')
plot1spect_HighLowSpan = Span(location=Pix1_HighLow.value, dimension = 'height')
plot1spect_HighHighSpan = Span(location=Pix1_HighHigh.value, dimension = 'height')
plot1spect.renderers.extend([plot1spect_LowLowSpan, plot1spect_LowHighSpan, plot1spect_HighLowSpan, plot1spect_HighHighSpan])
plot1spill = figure(width=700, height=250, title='pixel-1 Spillover')
plot1spill.line(self.SpillDACvals1[0], self.SpillData1[0], line_width=2)
plot1spill_LowLowSpan = Span(location=Pix1_LowLow.value, dimension = 'height')
plot1spill_LowHighSpan = Span(location=Pix1_LowHigh.value, dimension = 'height')
plot1spill_HighLowSpan = Span(location=Pix1_HighLow.value, dimension = 'height')
plot1spill_HighHighSpan = Span(location=Pix1_HighHigh.value, dimension = 'height')
plot1spill.renderers.extend([plot1spill_LowLowSpan, plot1spill_LowHighSpan, plot1spill_HighLowSpan, plot1spill_HighHighSpan])
show(row(plot1spect,plot1spill, widgetbox(Pix1_LowLow, Pix1_LowHigh, Pix1_HighLow, Pix1_HighHigh)))
This code gives me this:
If someone can show me how get Pix1_LowLow slider to dynamically control the location of plot1spect_LowLowSpan, then I can extend the technique to the other sliders and spans. Many thanks in advance!
python 3.5.2 - bokeh 12.0
Here is a minimal complete example. Note that the recommended way to add annotations like Span is with plot.add_layout as shown below:
from bokeh.layouts import row, widgetbox
from bokeh.models import Slider, Span, CustomJS
from bokeh.plotting import figure, output_file, show
slider = Slider(start=0, end=10, value=3, step=0.1, title='Slider')
plot = figure(width=700, height=250, x_range=(0,10), y_range=(-1, 1))
span = Span(location=slider.value, dimension='height')
plot.add_layout(span)
callback = CustomJS(args=dict(span=span), code="""
span.location = cb_obj.value
""")
slider.js_on_change('value', callback)
output_file('span_slider.html')
show(row(plot, widgetbox(slider)))
Thanks to #bigreddot for providing the answer. This is the code that implemented my solution specifically... Now how to do this programmatically for 128 data files... hmmmm..
from bokeh.layouts import row, widgetbox
from bokeh.models import Span, CustomJS, Slider
output_file('Raw_Spectra_and_Spillover_Data.html')
# widgets for bin setup
Pix1_LowLow = Slider(start = self.StartDAC, end = self.EndDAC, value = 129, step = 1, title = 'Pixel-1 - Low Bin - Low Thresh')
Pix1_LowHigh = Slider(start = self.StartDAC, end = self.EndDAC, value = 204, step = 1, title = 'Pixel-1 - Low Bin - High Thresh')
Pix1_HighLow = Slider(start = self.StartDAC, end = self.EndDAC, value = 218, step = 1, title = 'Pixel-1 - High Bin - Low Thresh')
Pix1_HighHigh = Slider(start = self.StartDAC, end = self.EndDAC, value = 500, step = 1, title = 'Pixel-1 - High Bin - High Thresh')
plot1spect = figure(width=700, height=250, title='pixel-1 Spectrum')
plot1spect.line(self.SpectDACvals1[0], self.SpectrumData1[0], line_width=2)
plot1spect_LowLowSpan = Span(location=Pix1_LowLow.value, dimension = 'height')
plot1spect.add_layout(plot1spect_LowLowSpan)
plot1spect_LowHighSpan = Span(location=Pix1_LowHigh.value, dimension = 'height')
plot1spect.add_layout(plot1spect_LowHighSpan)
plot1spect_HighLowSpan = Span(location=Pix1_HighLow.value, dimension = 'height')
plot1spect.add_layout(plot1spect_HighLowSpan)
plot1spect_HighHighSpan = Span(location=Pix1_HighHigh.value, dimension = 'height')
plot1spect.add_layout(plot1spect_HighHighSpan)
#plot1spect.renderers.extend([plot1spect_LowLowSpan, plot1spect_LowHighSpan, plot1spect_HighLowSpan, plot1spect_HighHighSpan])
plot1spill = figure(width=700, height=250, title='pixel-1 Spillover')
plot1spill.line(self.SpillDACvals1[0], self.SpillData1[0], line_width=2)
plot1spill_LowLowSpan = Span(location=Pix1_LowLow.value, dimension = 'height')
plot1spill.add_layout(plot1spill_LowLowSpan)
plot1spill_LowHighSpan = Span(location=Pix1_LowHigh.value, dimension = 'height')
plot1spill.add_layout(plot1spill_LowHighSpan)
plot1spill_HighLowSpan = Span(location=Pix1_HighLow.value, dimension = 'height')
plot1spill.add_layout(plot1spill_HighLowSpan)
plot1spill_HighHighSpan = Span(location=Pix1_HighHigh.value, dimension = 'height')
plot1spill.add_layout(plot1spill_HighHighSpan)
#plot1spill.renderers.extend([plot1spill_LowLowSpan, plot1spill_LowHighSpan, plot1spill_HighLowSpan, plot1spill_HighHighSpan])
Pix1_LowLow.callback = CustomJS(args=dict(span1 = plot1spect_LowLowSpan,
span2 = plot1spill_LowLowSpan,
slider = Pix1_LowLow),
code = """span1.location = slider.value; span2.location = slider.value""")
Pix1_LowHigh.callback = CustomJS(args=dict(span1 = plot1spect_LowHighSpan,
span2 = plot1spill_LowHighSpan,
slider = Pix1_LowHigh),
code = """span1.location = slider.value; span2.location = slider.value""")
Pix1_HighLow.callback = CustomJS(args=dict(span1 = plot1spect_HighLowSpan,
span2 = plot1spill_HighLowSpan,
slider = Pix1_HighLow),
code = """span1.location = slider.value; span2.location = slider.value""")
Pix1_HighHigh.callback = CustomJS(args=dict(span1 = plot1spect_HighHighSpan,
span2 = plot1spill_HighHighSpan,
slider = Pix1_HighHigh),
code = """span1.location = slider.value; span2.location = slider.value""")
Here is a repeat of the plots, but now each slider manipulates the respective span in both plots...
Related
I have a fonction that create a rugby field :
from matplotlib.patches import Rectangle
from matplotlib.patches import Arc
from matplotlib.patches import ConnectionPatch
def draw_pitch_horizontal():
# focus on only half of the pitch
#Pitch Outline & Centre Line
fig=plt.figure() #set up the figures
fig.set_size_inches(10, 7)
ax=fig.add_subplot(1,1,1)
Pitch = Rectangle([0,0], width = 120, height = 70, fill = False)
essai1 = Rectangle([0,0], width = 10, height = 70, fill = False, color='gray',hatch='/')
essai2 = Rectangle([110,0], width = 1070, height = 70, fill = False, color='gray',hatch='/')
en_but1 = ConnectionPatch([10,0], [10,80], "data", "data")
en_but2 = ConnectionPatch([110,0], [110,70], "data", "data")
cinq_metres1 = ConnectionPatch([15,0], [15,70], "data", "data",ls='--',color='gray')
cinq_metres2 = ConnectionPatch([105,0], [105,70], "data", "data",ls='--',color='gray')
midline = ConnectionPatch([60,0], [60,70], "data", "data")
vingtdeux_metres1 = ConnectionPatch([32,0], [32,70], "data", "data")
vingtdeux_metres2 = ConnectionPatch([88,0], [88,70], "data", "data")
dix_metres1 = ConnectionPatch([50,0], [50,70], "data", "data",ls='--',color='gray')
dix_metres2 = ConnectionPatch([70,0], [70,70], "data", "data",ls='--',color='gray')
centreCircle = plt.Circle((60,35),0.5,color="black", fill = True)
poteau1a = plt.Circle((10,32.2),0.5,color="black", fill = True)
poteau1b = plt.Circle((10,37.8),0.5,color="black", fill = True)
poteau2a = plt.Circle((110,32.2),0.5,color="black", fill = True)
poteau2b = plt.Circle((110,37.8),0.5,color="black", fill = True)
element = [essai1, essai2, Pitch, en_but1, en_but2, cinq_metres1, cinq_metres2, midline, vingtdeux_metres1,
vingtdeux_metres2,centreCircle,poteau1a,poteau1b,poteau2a,poteau2b,dix_metres1,dix_metres2]
for i in element:
ax.add_patch(i)
return fig,ax
I created a function with a team in argument and the function plots the average start and end of a sequence on a pitch thanks to the previous function.
def plot_sequence_moyenne(TEAM):
df_team = df_summary[((df_summary.hometeam == TEAM)|(df_summary.awayteam == TEAM))].reset_index(drop=True)
df_timeline = pd.concat([prg.Match(df_team['url'][i]).timeline for i in range(len(df_team))],axis=0)
df_timeline_team = df_timeline[df_timeline.team_name == TEAM].reset_index(drop=True)
print(df_timeline_team.x_coord.mean())
print(df_timeline_team.x_coord_end.mean())
(fig,ax) = draw_pitch_horizontal() #overlay our different objects on the pitch
plt.ylim(-2, 72)
plt.xlim(-0.2, 120.4)
ax = plt.gca()
ax.set_facecolor('#c2dff4')
plt.axis('off')
zone_sequence = Rectangle([df_timeline_team.x_coord.mean(),0], width = (df_timeline_team.x_coord_end.mean()-df_timeline_team.x_coord.mean()), height = 70, fill = True, color='darkseagreen',alpha=0.7,edgecolor='black')
ax.add_patch(zone_sequence)
plt.title("TEAM 1" + " - Séquences moyennes")
plt.show()
This is what I obtain with this :
However, I would like to know how to create a huge subplot in order to have a graph with 10 subplots of pitch if I have ten teams for exemple.
Thanks in advance for your help and your time
What I want to do:
I made a dynamic map using bokeh, pandas and geopandas. The data to be displayed is loaded from a table, then mapped to a country per year. The year to be displayed is determined by a bokeh slider. You can also hover over a country and get its value. I now want to be able to change the data source by selecting a radio button. To display the data correctly, I want to change the color palette, rescale it (a range from e.g. 50 to 100 instead of 0 to 4.5), update the scaling on the slider to the new lowest year to highest year, and then display the world-map with the new data. I also want to update the title of the map from e.g. "Fertility" to "Life expectancy".
What I already have:
I have a working dynamic map with Slider and Hover tool. I also have a list from which the data to be used is taken (datapath, title to be used, color palette to be used, highest and lowest year, highest and lowest value). I have a radio button group, with three different data sources to choose from. All paths are relative, the data is provided with a consistent structure. I had the map changing the data below and displaying the new stuff, but than I did something and it stopped working. I also had the Hover tool display the right values, but with a wrong (old) description.
What I need help with:
Updating the color bar to accomodate the new palette and the new range
Updating the slider to accomodate a changed range
Updating the title displayed to show what's actually displayed
What I already tried:
I've put the whole loading and displaying in the function executed when the radio button group is changed. The first thing this function does is clearing the layout and then rebuilding it. Unfortunately, this is neither efficient, nor working, since I only get the radio button Group and an empty space below, no matter what I do. I've searched for a solution, but all I found (and tried) didn't do what I needed.
I can provide the actual code, if needed (though some of the variables and documentations are in German), but since I'm pretty new to the whole python thing, I don't now, what exactly of that about 300 lines of code you need. Just let me now, and I'll try and provide.
Hope you can help me with that.
Thanks in advance,
Asd789
EDIT: As correctly pointed out in the comments, some code to help understand what I did.
I'll cut all the imports for the sake of brevity, as a mistake there would have shown up as error in my terminal. Also leaving out comments.
geoFrame #dataframe for geopanda shapefile
configList = [0, "Fertility", 'YlGnBu', 'Year' ]
df #dataframe for the .csv file
higStep = df['step'].max()
lowStep = df['step'].min()
configList.append(higStep)
higVal = df['valueInterest'].max()
merged = geoFrame.merge(df, left_on = 'country_code', right_on = 'code')
merged_json = json.loads(merged.to_json())
json_data = json.dumps(merged_json)
geosource = GeoJSONDataSource(geojson = json_data)
palette = brewer[configList[2]][8]
color_mapper = LinearColorMapper(palette = palette, low = 0, high = 4)
color_bar = ColorBar(color_mapper=color_mapper, label_standoff=8,width = 500, height = 20,
border_line_color=None,location = (0,0), orientation = 'horizontal')
p = figure(title = configList[1], plot_height = 600 , plot_width = 950, toolbar_location = None)
p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = None
p.patches('xs','ys', source = geosource,fill_color = {'field' :'valueInterest', 'transform' : color_mapper},
line_color = 'black', line_width = 0.25, fill_alpha = 1)
p.add_layout(color_bar, 'below')
df_curr = df[df['step'] == higStep]
color_mapper = LinearColorMapper(palette = palette, low = 0, high = 40, nan_color = '#d9d9d9')
def json_data(selectedStep):
st = selectedStep
df_st = df[df['step'] == st]
merged = geoFrame.merge(df_st, left_on = 'country_code', right_on = 'code', how = 'left')
merged_json = json.loads(merged.to_json())
json_data = json.dumps(merged_json)
return json_data
geosource = GeoJSONDataSource(geojson = json_data(higStep))
palette = brewer[configList[2]][8]
palette = palette[::-1]
color_mapper = LinearColorMapper(palette = palette, low = 0, high = higVal/2, nan_color = '#d9d9d9')
hover = HoverTool(tooltips = [ ('Country/region','#country'),(configList[1], '#valueInterest')])
color_bar = ColorBar(color_mapper=color_mapper, label_standoff=8,width = 500, height = 20,
border_line_color=None,location = (0,0), orientation = 'horizontal')
p = figure(title = configList[1], plot_height = 600 , plot_width = 950, toolbar_location = None, tools = [hover])
p.xgrid.grid_line_color = None
p.ygrid.grid_line_color = None
p.patches('xs','ys', source = geosource,fill_color = {'field' :'valueInterest', 'transform' : color_mapper},
line_color = 'black', line_width = 0.25, fill_alpha = 1)
p.add_layout(color_bar, 'below')
def update_plot(attr, old, new):
st = slider.value
new_data = json_data(st)
geosource.geojson = new_data
p.title.text = configList[1] %st
slider = Slider(title = configList[3],start = 1950, end = 2015, step = 1, value = 2015)
slider.on_change('value', update_plot)
def radHandler(attr, new, old):
if radio.active == 0:
datapath = os.path.join(dataloc, 'children-per-woman-UN.csv')
configList = [0, "Fertility", 'YlGnBu', 'Year']
elif radio.active == 1:
#see above with diferent data
elif radio.active == 2:
#see above with different data
curdoc().clear()
higStep = df['step'].max()
lowStep = df['step'].min()
configList.append(higStep)
update_plot(attr, new, old)
hover = HoverTool(tooltips = [ ('Country/region','#country'),(configList[1], '#valueInterest')])
palette = brewer[configList[2]][8]
palette = palette[::-1]
olor_mapper = LinearColorMapper(palette = palette, low = 0, high = higVal/2, nan_color = '#d9d9d9')
color_bar = ColorBar(color_mapper=color_mapper, label_standoff=8,width = 500, height = 20,
border_line_color=None,location = (0,0), orientation = 'horizontal'
p = figure(title = configList[1]+' '+str(configList[4]), plot_height = 600 , plot_width = 950, toolbar_location = None, tools = [hover])
layout = column(widgetbox(radio),p,widgetbox(slider))
curdoc().add_root(layout)
radio = RadioButtonGroup(labels=['Fertility', 'Life expectancy', 'Covid-19 total cases'], active=0)
radio.on_change('active',radHandler)
layout = column(p, widgetbox(radio),widgetbox(slider))
curdoc().title = configList[1]
curdoc().add_root(layout)
Sorry I could'nt cook it down further, but I don't know what's essential and what's just fancy stuff around.
This code works until I touch the radio button group. After that, the plot itself is just blank, without any prompts anywhere. The code itself is not entirely my fault, I got it to fix and maybe expand on it, the expansion being the ability to switch between data sources as decribed above.
The following script creates a worksheet with two columns. From these two columns I created a chart. Then I tried to place a legend to the right of the axis but for some reason the legend is not seen. Can you please tell me why don't I see the legend ?
BTW, I have tried to methods to do that without any success (one of the is marked as a note)
chart = LineChart()
chart.title = "Device Time drift (2 samples/hour)"
chart.legend.position = 'r'
# chart.legend.layout = Layout(
# manualLayout=ManualLayout(
# yMode='edge',
# xMode='edge',
# x=0, y=0.9,
# h=0.1, w=0.5
# )
# )
chart.style = 10
chart.y_axis.title = 'Time Delta [mSec]'
chart.x_axis.title = 'Sample No.'
chart.legend = None
chart.height = 20 # default is 7.5
chart.width = 40 # default is 15
font = Font(typeface='Verdana')
size = 1200 # 14 point size
cp = CharacterProperties(latin=font, sz=size, b=False) # Not bold
pp = ParagraphProperties(defRPr=cp)
# X and Y axes titles
chart.x_axis.title.tx.rich.p[0].pPr = pp
chart.y_axis.title.tx.rich.p[0].pPr = pp
data = Reference(worksheet=ws_write_timedel, min_col=1, min_row=1,
max_row=len(ws_write_timedel['A']), max_col=2, )
chart.add_data(data, titles_from_data=True,)
s1 = chart.series[0]
s1.graphicalProperties.line.solidFill = "00AAAA"
s1.graphicalProperties.line.dashStyle = "sysDot"
s1.graphicalProperties.line.width = 100050 # width in EMUs
s2 = chart.series[1]
s2.smooth = True
ws_write_timedel.add_chart(chart, "D1")enter code here
The following script is the fixed version and now the legend is added to the chart
chart = LineChart()
chart.title = "Device Time drift (2 samples/hour)"
chart.legend.position = 'r'
chart.style = 10
chart.y_axis.title = 'Time Delta [mSec]'
chart.x_axis.title = 'Sample No.'
chart.height = 20 # default is 7.5
chart.width = 40 # default is 15
font = Font(typeface='Verdana')
size = 1200 # 14 point size
cp = CharacterProperties(latin=font, sz=size, b=False) # Not bold
pp = ParagraphProperties(defRPr=cp)
# X and Y axes titles
chart.x_axis.title.tx.rich.p[0].pPr = pp
chart.y_axis.title.tx.rich.p[0].pPr = pp
data = Reference(worksheet=ws_write_timedel, min_col=1, min_row=1,
max_row=len(ws_write_timedel['A']), max_col=2, )
chart.add_data(data, titles_from_data=True,)
s1 = chart.series[0]
s1.graphicalProperties.line.solidFill = "00AAAA"
s1.graphicalProperties.line.dashStyle = "sysDot"
s1.graphicalProperties.line.width = 100050 # width in EMUs
s2 = chart.series[1]
s2.smooth = True
ws_write_timedel.add_chart(chart, "D1")enter code here
Using an on_change callback, I can get the numerical row index of a selection within a DataTable, in Bokeh.
Is it possible to:
a) Get the column index
b) Get the values of the indexes (column and row headers)
Example code:
from bokeh.io import curdoc
from bokeh.layouts import row, column
import pandas as pd
from bokeh.models import ColumnDataSource, ColorBar, DataTable, DateFormatter, TableColumn, HoverTool, Spacer, DatetimeTickFormatter
'''
Pandas
'''
df = pd.DataFrame(data = {'Apples': [5,10], 'Bananas': [16,15], 'Oranges': [6,4]})
df.rename(index={0:'A',1:'B'}, inplace=True)
'''
BOKEH
'''
sourceTableSummary = ColumnDataSource(df)
Columns = [TableColumn(field=colIndex, title=colIndex) for colIndex in df.columns]
data_table = DataTable(columns=Columns, source=sourceTableSummary, index_position = 0, width = 1900, height = 200, fit_columns=False)
'''
Funcs
'''
def return_value(attr, old, new):
selectionRowIndex=sourceTableSummary.selected.indices[0]
print("Selected Row Index ", str(selectionRowIndex))
selectionValue=sourceTableSummary.data['Apples'][selectionRowIndex]
print("Selected value for Apples ", str(selectionValue))
# selectionColumnIndex?
# selectionRowHeader?
# selectionColumnHeader?
sourceTableSummary.on_change('selected', return_value)
curdoc().add_root(column(children=[data_table]))
This gives the following which can returns rows, and the values within the selection. This is ideal if I always want a single column returned. However the selection UI (dotted line) seems to suggest that the specific column is known, not just the row.
If there's no way of attaining the selected column, can I look it up using both the Row Index and the Cell Value?
Local Server Output & Table
The following code uses JS callback to show the row and column index as well as the cell contents. The second Python callback is a trick to reset the indices so that a click on the same row can be detected (tested with Bokeh v1.0.4). Run with bokeh serve --show app.py
from random import randint
from datetime import date
from bokeh.models import ColumnDataSource, TableColumn, DateFormatter, DataTable, CustomJS
from bokeh.layouts import column
from bokeh.models.widgets import TextInput
from bokeh.plotting import curdoc
source = ColumnDataSource(dict(dates = [date(2014, 3, i + 1) for i in range(10)], downloads = [randint(0, 100) for i in range(10)]))
columns = [TableColumn(field = "dates", title = "Date", formatter = DateFormatter()), TableColumn(field = "downloads", title = "Downloads")]
data_table = DataTable(source = source, columns = columns, width = 400, height = 280, editable = True, reorderable = False)
text_row = TextInput(value = None, title = "Row index:", width = 420)
text_column = TextInput(value = None, title = "Column Index:", width = 420)
text_date = TextInput(value = None, title = "Date:", width = 420)
text_downloads = TextInput(value = None, title = "Downloads:", width = 420)
test_cell = TextInput(value = None, title = "Cell Contents:", width = 420)
source_code = """
var grid = document.getElementsByClassName('grid-canvas')[0].children;
var row, column = '';
for (var i = 0,max = grid.length; i < max; i++){
if (grid[i].outerHTML.includes('active')){
row = i;
for (var j = 0, jmax = grid[i].children.length; j < jmax; j++)
if(grid[i].children[j].outerHTML.includes('active'))
{ column = j }
}
}
text_row.value = String(row);
text_column.value = String(column);
text_date.value = String(new Date(source.data['dates'][row]));
text_downloads.value = String(source.data['downloads'][row]);
test_cell.value = column == 1 ? text_date.value : text_downloads.value; """
def py_callback(attr, old, new):
source.selected.update(indices = [])
source.selected.on_change('indices', py_callback)
callback = CustomJS(args = dict(source = source, text_row = text_row, text_column = text_column, text_date = text_date, text_downloads = text_downloads, test_cell = test_cell), code = source_code)
source.selected.js_on_change('indices', callback)
curdoc().add_root(column(data_table, text_row, text_column, text_date, text_downloads, test_cell))
Result:
import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook, push_notebook
from bokeh.plotting import figure
from bokeh.models import CategoricalColorMapper, HoverTool, ColumnDataSource, Panel
from bokeh.models.widgets import CheckboxGroup, Slider, RangeSlider, Tabs
from bokeh.layouts import column, row, WidgetBox
from bokeh.palettes import Category20_16
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
output_notebook()
def histogram_tab(webs):
def make_dataset(params_list, range_start = 0.0, range_end = 1, bin_width = 0.005):
#check to make sure the start is less than the end
assert range_start < range_end, "Start must be less than end!"
#by_params = pd.DataFrame(columns=[ ,'Max', 'Avarage', 'Min','color'])
by_params = pd.DataFrame(columns=[ 'left','right', 'proportion', 'p_proportion','p_interval', 'name', 'w_name','color'])
#
range_extent = range_end - range_start
values = ['Min', "Avarage", 'Max']
# Iterate through all the parameters
for i, para_name in enumerate(params_list):
#print para_name
# Subset to the parameter
subset = webs[para_name]
# note: subset have to be a list of values
# [webs.columns[i%6]]
# Create a histogram with specified bins and range
arr_hist, edges = np.histogram(subset,
bins = int(range_extent / bin_width),
range = [range_start, range_end])
# Divide the counts by the total to get a proportion and create df
arr_df= pd.DataFrame({'proportion': arr_hist ,
'left': edges[:-1], 'right': edges[1:]}) #/ np.sum(arr_hist)
# Format the proportion
arr_df['p_proportion'] = ['%0.00005f' % proportion for proportion in arr_df['proportion']]
# Format the interval
arr_df['p_interval'] = ['%d to %d scale' % (left, right) for left,
right in zip(arr_df['left'], arr_df['right'])]
# Assign the parameter for labels
arr_df['name'] = para_name
arr_df['w_name'] = webs['Site name']
# Color each parametr differently
arr_df['color'] = Category20_16[i]
# Add to the overall dataframe
by_params = by_params.append(arr_df)
# Overall dataframe
by_params = by_params.sort_values(['name','left'])
return ColumnDataSource(by_params)
def style(p):
# Title
p.title.align = 'center'
p.title.text_font_size ='20pt'
p.title.text_font = 'serif'
# Axis titles
p.xaxis.axis_label_text_font_size = '14pt'
p.xaxis.axis_label_text_font_style = 'bold'
p.yaxis.axis_label_text_font_size = '14pt'
p.yaxis.axis_label_text_font_style = 'bold'
# Tick labels
p.xaxis.major_label_text_font_size = '12pt'
p.yaxis.major_label_text_font_size = '12pt'
return p
def make_plot(src):
# Blank plot with correct labels
p = figure(plot_width = 700, plot_height = 700,
title = "Histogram of Parametes for the websites",
x_axis_label = 'parameters', y_axis_label = "values")
# Quad glyphs to create a histogram
p.quad(source=src, bottom =0,left = 'left', right = 'right', color ='color', top= 'proportion',fill_alpha = 0.7, hover_fill_color = 'color', legend = 'name',
hover_fill_alpha = 1.0, line_color = 'white') #top='proportion',
# Hover tool with vline mode
hover = HoverTool(tooltips=[('Parameter','#name'),
('Website','#w_name'),
('Proportion','p_proportion')
],
mode='vline')
p.add_tools(hover)
# Stypling
p = style(p)
return p
# Update function takes three default parameters
def update(attr, old, new):
# Get the list of parameter for the graph
parameter_to_plot = [para_selection.labels[i] for i in para_selection.active]
# Make a new dataset based on the selected parameter and the
# make_dataset function defined earlier
new_src = make_dataset(parameter_to_plot, range_start = 0, range_end = 1, bin_width = 0.005) # note range are not specified
# Convert dataframe to column data source
new_src = ColumnDataSource(new_src)
# Update the source used the quad glpyhs
src.data.update(new_src.data)
list_of_params = list(webs.columns[1:].unique())
list_of_params.sort()
para_selection = CheckboxGroup(labels=list_of_params, active = [0,1])
para_selection.on_change('active',update)
binwidth_select = Slider(start =0, end = 1,
step = 0.00025, value = 0.0005,
title = 'Change in parameter')
binwidth_select.on_change('value', update)
range_select = RangeSlider(start=0, end=1, value =(0,1),
step=0.00025, title = 'Change in range')
range_select.on_change('value', update)
initial_params = [para_selection.labels[i] for i in para_selection.active]
src = make_dataset(initial_params,
range_start = range_select.value[0],
range_end = range_select.value[1],
bin_width = binwidth_select.value)
p = make_plot(src)
#show(p)
# Put controls in a single element
controls = WidgetBox(para_selection, binwidth_select, range_select)
# Create a row layout
layout = row(controls, p)
# Make a tab with the layout
tab = Panel(child = layout, title = 'Histogram')
#return tab
tabs = Tabs(tabs=[tab])
webs.add_root(tabs)
# Set up an application
handler = FunctionHandler(histogram_tab(webs))
app = Application(handler)
add_root is a method on Document, you are trying to call it on a DataFrame called webs, apparently, which is why you get that message. The structure of a Bokeh app in a notebook should look like this:
# create a function to define the app, must accept "doc" as the parameter
def myfunc(doc):
# make Bokeh objects
# add stuff to doc
doc.add_root(stuff)
# pass the function, but *don't* execute it
handler = FunctionHandler(myfunc)
app = Application(handler)
Note that the last two lines are not necessary in recent version of Bokeh, you can just call:
show(myfunc)
directly. There is a full example in the repo:
https://github.com/bokeh/bokeh/blob/master/examples/howto/server_embed/notebook_embed.ipynb
Your code should be structured very similarly to that.