Related
Currently the Bokeh hovertool spits out my date values as a 13-digit number. How do I change the format of how it's displayed?
Below is my code...also I've specified which line of code the date/time is in.
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool
from bokeh.io import output_notebook
x = dff.P2_VWC
y = dff.P2_EC
bok = figure(title="Simple line example", x_axis_label='x', y_axis_label='y')
bok.line(x, y, legend_label="Hysteresis", line_width=2)
show(bok)
output_notebook()
#Specify the selection tools to be made available
select_tools = ['box_select', 'lasso_select', 'poly_select', 'tap', 'reset']
# Format the tooltip
tooltips = [
('VWC', '#P2_VWC'),
('EC', '#P2_EC'),
('Date', '#date_time') # !THIS DATE NEEDS FORMATTED!
]
p = figure(plot_width=800, plot_height=400, x_axis_type='linear', title = "VWC v. Bulk EC")
p.line(x="P2_VWC", y="P2_EC", source=dff)
p.xaxis.axis_label = 'VWC'
p.yaxis.axis_label = 'Bulk EC'
p.add_tools(HoverTool(tooltips=tooltips))
show(p)
Thank you for your time!
Only a few lines of code needed to be changed but they are below. {%F %T} can be added to the date/time code to specify what you want.
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource, HoverTool
from bokeh.io import output_notebook
output_notebook()
#Specify the selection tools to be made available
select_tools = ['box_select', 'lasso_select', 'poly_select', 'tap', 'reset']
# Format the tooltip
# https://docs.bokeh.org/en/latest/docs/reference/models/formatters.html#bokeh.models.formatters.DatetimeTickFormatter
tooltips = [
('VWC', '#P2_VWC'),
('EC', '#P2_EC'),
('Date', '#date_time{%F %T}'),
]
p = figure(plot_width=800, plot_height=800, x_axis_type='linear', title = "VWC v. Bulk EC")
p.line(x="P2_VWC", y="P2_EC", source=df1)
p.xaxis.axis_label = 'VWC'
p.yaxis.axis_label = 'Bulk EC'
p.add_tools(HoverTool(tooltips=tooltips, formatters={'#date_time': 'datetime'}))
show(p)
I am trying to show two plots vertically. I get an error message at show(column(west_fig, east_fig))
Could you please help me understand the error message?
Version: BokehJS 1.4.0 successfully loaded.
Note: I am running this code in Jupyter notebook
## Detailed error message:
--------------------------------------------------------------------------- RuntimeError Traceback (most recent call
last) in
8
9 # Plot the two visualizations in a vertical configuration
---> 10 show(column(west_fig, east_fig))
RuntimeError: Models must be owned by only a single document,
DaysTicker(id='2330', ...) is already in a doc
# Bokeh libraries
from bokeh.io import output_file, output_notebook
from bokeh.plotting import figure, show
from bokeh.layouts import column
# output to notebook
output_notebook()
# Plot the two visualizations in a vertical configuration
show(column(west_fig, east_fig))
####### west_fig #######
# Bokeh libraries
from bokeh.plotting import figure, show
from bokeh.io import output_file, output_notebook
from bokeh.models import ColumnDataSource, CDSView, GroupFilter
# Output to notebook
output_notebook()
# Convert `stDate` column to datetime column
west_top_2['stDate'] = pd.to_datetime(west_top_2['stDate'], format = '%Y-%m-%d')
# Create a ColumnDataSource
west_cds = ColumnDataSource(west_top_2)
# Create views for each team
rockets_view = CDSView(source = west_cds,
filters = [GroupFilter(column_name = 'teamAbbr', group = 'HOU')]
)
warriors_view = CDSView(source = west_cds,
filters = [GroupFilter(column_name = 'teamAbbr', group = 'GS')]
)
# Create and configure the figure
west_fig = figure(x_axis_type = 'datetime',
plot_height = 500,
plot_width = 600,
title = 'Western Conference Top 2 Teams Wins Race, 2017-18',
x_axis_label = 'Date',
y_axis_label = 'Wins',
toolbar_location = None
)
# Render the race as step lines
west_fig.step('stDate', 'gameWon', source = west_cds, view = rockets_view, color = '#CE1141', legend = 'Rockets')
west_fig.step('stDate', 'gameWon', source = west_cds, view = warriors_view, color = '#006BB6', legend = 'Warriors')
# Move the legend to the upper top-left corner
west_fig.legend.location = "top_left"
# Show the plot
show(west_fig)
####### east_fig #########
import pandas as pd
# Bokeh libraries
from bokeh.plotting import figure, show
from bokeh.io import output_file, output_notebook
from bokeh.models import ColumnDataSource, CDSView, GroupFilter
# Output to notebook
output_notebook()
# Convert stDate to datetime column
standings['stDate'] = pd.to_datetime(standings['stDate'], format = '%Y-%m-%d')
# Create a ColumnDataSource
standings_cds = ColumnDataSource(standings)
# Create views for each team
celtics_view = CDSView(source=standings_cds,
filters = [GroupFilter(column_name = 'teamAbbr', group = 'BOS')]
)
raptors_view = CDSView(source=standings_cds,
filters = [GroupFilter(column_name = 'teamAbbr', group = 'TOR')]
)
# Configure Figure object
east_fig = figure(x_axis_type = 'datetime',
plot_height = 500,
plot_width = 600,
title = 'Eastern Conference Top 2 Teams Wins Race, 2017-18',
x_axis_label = 'Date',
y_axis_label = 'Wins',
toolbar_location = None
)
# Render the race as step lines
east_fig.step('stDate', 'gameWon', color = '#007A33', legend = 'Celtics',
source = standings_cds, view = celtics_view)
east_fig.step('stDate', 'gameWon', color = '#CE1141', legend = 'Raptors',
source = standings_cds, view = raptors_view)
# Move the legend to the upper left hand corner
east_fig.legend.location = "top_left"
# Show the plot
show(east_fig)
Make only one show statement at the end of your code. This implies deleting show(column(west_fig, east_fig)), show(west_fig), show(east_fig) lines and adding at the end of the code show(column(west_fig, east_fig))
I would love some help, I'm going in circles here. I know I'm doing something stupid but my plot isn't updating. I can't debug to see if my filter function isn't working or there's a problem that my inputs for the plot aren't linked the dynamic source input. Since even the starting plot doesn't take the initialized parameters I think it's something there. PS- any advice on having a select all, including all in the categorical choices for the select boxes would be amazing too.
Cheers,
Tom
import pandas as pd
import numpy as np
from bokeh.io import show, output_notebook, push_notebook, curdoc
from bokeh.plotting import figure
from bokeh.models import CategoricalColorMapper, HoverTool, ColumnDataSource, Panel, Div
from bokeh.models.widgets import (CheckboxGroup, Slider, Select, TextInput, RangeSlider, Tabs, CheckboxButtonGroup, TableColumn, DataTable, Select)
from bokeh.layouts import layout, column, row, Widgetbox
from bokeh.layouts import widgetbox, row, column
from bokeh.palettes import Category20_16
from bokeh.application.handlers import FunctionHandler
from bokeh.application import Application
weather = pd.read_csv('YYYYYY.csv', dayfirst=True, parse_dates=True, index_col=[1], encoding = "ISO-8859-1")
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):
p = figure(plot_height=600, plot_width=700, title="'2018'", toolbar_location="below", tooltips=TOOLTIPS)
p.circle(x="Deal_Number", y="USD_Base", source=src, size=7, line_color=None)
p = style(p)
return p
TOOLTIPS=[
("Name", "#Target"),
("$", "#Round"),
("Country", "#CC")
]
def get_dataset(deal_num, ccstring, descstring, vertstring):
df_filter = weather[weather['USD_Base'] >=(deal_num) & weather['CC'].str.contains(ccstring) & weather['Description'].str.contains(descstring) & weather['Vertical Market'].str.contains(vertstring)]
return ColumnDataSource(df_filter)
def update_plot(attr, old, new):
deal_num = int(deal_select.value)
ccstring = str(cc_select.value)
descstring = str(description_select.value)
vertstring = str(vert_select.value)
new_src = get_dataset(deal_num, ccstring, descstring, vertstring)
src.data.update(new_src.data)
# Create Input controls
deal_select = Slider(title="$ Invested", value=0, start=0, end=200, step=2)
cclist = weather["CC"].unique().tolist()
cc_select = Select(title="Country Name:", options= cclist, value='GB')
description_select = TextInput(title="Company description contains")
vertlist = weather["Vertical Market"].unique().tolist()
vert_select = Select(title="Vertical:", options= ['All'] + vertlist, value='None')
controls = widgetbox(deal_select, cc_select, description_select, vert_select)
deal_select.on_change('value', update_plot)
cc_select.on_change('value',update_plot)
description_select.on_change('value',update_plot)
vert_select.on_change('value',update_plot)
# Make the deal data source
src = get_dataset(deal_num = deal_select.value,
ccstring = cc_select.value,
descstring = description_select.value,
vertstring = vert_select.value)
# Make the deal plot
p = make_plot(src)
layout = row(controls, p)
# Make a tab with the layout
tab = Panel(child=layout, title = '2018')
# Put all the tabs into one application
tabs = Tabs(tabs = [tab])
# Put the tabs in the current document for display
curdoc().add_root(tabs)
If you are updating a glyph, you need to change the datasource for that glyph directly. In your case, you should assign the circle glyph to a variable, such as:
circle = p.circle(x="Deal_Number", y="USD_Base", source=src, size=7, line_color=None)
Then in your update_plot(attr, old, new) function try this:
circle = p.select_one({'name':'circle'})
circle.data_source.data = new_src.data
For selecting all, possibly the MultiSelect Widget would work?
I'm trying to create a bokeh plot of the US States, and color each of the state according to some data. Now using this tutorial I managed to create this, but I also want to enhance it, and add a slider to it, to change the values displayed. For example like displaying separate years.
With the help of this tutorial, I managed to add the slider, and the underlying data does change, according to the hover text, but the colors aren't recalculated, and so the visual representation does not match the values.
This is the code I've used, from a Jupyter notebook, so anybody who wants to try can reproduce
from bokeh.io import show, output_notebook
from bokeh.models import (
ColumnDataSource,
HoverTool,
LogColorMapper,
Range1d, CustomJS, Slider
)
from bokeh.palettes import Inferno256 as palette
from bokeh.plotting import figure
from bokeh.layouts import row, widgetbox
from bokeh.sampledata.us_counties import data as counties
from bokeh.sampledata.us_states import data as states
from bokeh.sampledata.unemployment import data as unemployment
import pandas as pd
import random
output_notebook()
palette.reverse()
states_accumulated ={}
available_state_codes = states.keys()
for key, value in counties.items():
state_name = value["state"].upper()
if state_name in states.keys() and "number" not in states[state_name]:
states[state_name]["number"] = key[0]
for key,state in states.items():
state["code"] = key
state_list = []
for key,state in states.items():
state_list.append(state)
unemployment_transf = []
for key,value in unemployment.items():
unemployment_transf.append({
"State":key[0],
"County":key[1],
"Value":value
})
unemp_df = pd.DataFrame(unemployment_transf)
unemp_sum = unemp_df.groupby("State").mean()["Value"]
unemp_sum = unemp_sum.sort_index()
unemp_sum_flat = {key:value for key, value in unemp_sum.items()}
for state in state_list:
state["value"] = unemp_sum_flat[state["number"]]
state_df = pd.DataFrame(state_list)
color_mapper = LogColorMapper(palette=palette)
state_xy = (list(state_df["lons"].values),list(state_df["lats"].values))
max_x = max([max(l) for l in state_xy[0]])
max_y = max([max(l) for l in state_xy[1]])
min_x = min([min(l) for l in state_xy[0]])
min_y = min([min(l) for l in state_xy[1]])
data=dict(
x=state_xy[0],
y=state_xy[1],
name=list(state_df["name"].values),
used = list(state_df["value"].values)
)
data['1999'] = list(state_df["value"].values)
data['2000'] = [random.randrange(0,10) for i in range(len(state_xy[0]))]
source = ColumnDataSource(data)
TOOLS = "pan,wheel_zoom,reset,hover,save"
p = figure(
title="States", tools=TOOLS,
x_axis_location=None, y_axis_location=None
)
p.width=450
p.height = 450
p.x_range= Range1d(-170,-60)
p.y_range = Range1d(min_y-10,max_y+10)
p.grid.grid_line_color = None
renderer = p.patches('x', 'y', source=source,
fill_color={'field': 'used', 'transform': color_mapper},
fill_alpha=0.7, line_color="white", line_width=0.5)
hover = p.select_one(HoverTool)
hover.point_policy = "follow_mouse"
hover.tooltips = [
("Name", "#name"),
("Unemployment rate)", "#used%"),
("(Long, Lat)", "($x, $y)"),
]
callback = CustomJS(args=dict(source=source,plot=p,color_mapper = color_mapper,renderer = renderer), code="""
var data = source.data;
var year = year.value;
used = data['used']
should_be = data[String(year)]
for (i = 0; i < should_be.length; i++) {
used[i] = should_be[i];
}
""")
year_slider = Slider(start=1999, end=2000, value=1999, step=1,
title="year", callback=callback)
callback.args["year"] = year_slider
layout = row(
p,
widgetbox(year_slider),
)
show(layout)
Sample images of the plot:
What I would like to accomplish, is that when I change the slider, the colors on the plot should change. Now I think the JS callback should call some kind of redraw or recalculate, but I haven't found any documentation about it. Is there a way to do this?
append source.change.emit() to the Javascipt code to trigger the change event.
Appending source.trigger("change"); to the CustomJS seems to solve the problem, now as the slider changes, the colors change.
I am trying to produce a dashboard like interactions for my bar chart using callback function without using bokeh serve functionality. Ultimately, I would like to be able to change the plot if any of the two drop-down menus is changed. So far this only works when threshold value is hard-coded. I only know how to extract cb_obj value but not from dropdown that is not actually called. I have looked at this and this answer to formulate first attempt.
Here is my code:
from bokeh.io import show, output_notebook, output_file
from bokeh.models import ColumnDataSource, Whisker
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
from bokeh.models import CustomJS, ColumnDataSource, Slider, Select
from bokeh.layouts import column
import numpy as np
import pandas as pd
def generate_data(factor=10):
rawdata = pd.DataFrame(np.random.rand(10,4)*factor, columns = ["A","B","C","D"])
idx = pd.MultiIndex.from_product([["Exp "+str(i) for i in range(5)],
[20,999]],names=["Experiment","Threshold"])
rawdata.index = idx
return rawdata.reset_index()
# Generate data
output_notebook()
count_data = generate_data()
error_data = generate_data(factor=2)
groups = ["A","B","C","D"]
initial_counts = count_data[(count_data.Experiment == "Exp 0")
& (count_data.Threshold == 20)][["A","B","C","D"]].values[0]
initial_errors = error_data[(error_data.Experiment == "Exp 0")
& (error_data.Threshold == 20)][["A","B","C","D"]].values[0]
# Create primary sources of data
count_source = ColumnDataSource(data=count_data)
error_source = ColumnDataSource(data=error_data)
# Create plotting source of data
source = ColumnDataSource(data=dict(groups=groups, counts=initial_counts,
upper=initial_counts+initial_errors,
lower=initial_counts-initial_errors))
# Bar chart and figure
p = figure(x_range=groups, plot_height=350, toolbar_location=None, title="Values", y_range=(0,20))
p.vbar(x='groups', top='counts', width=0.9, source=source, legend="groups",
line_color='white', fill_color=factor_cmap('groups', palette=["#962980","#295f96","#29966c","#968529"],
factors=groups))
# Error bars
p.add_layout(
Whisker(source=source, base="groups", upper="upper", lower="lower", level="overlay")
)
def callback(source=source, count_source = count_source, error_source=error_source, window=None):
def slicer(data_source, experiment, threshold, dummy_col, columns):
""" Helper function to enable lookup of data."""
count = 0
for row in data_source[dummy_col]:
if (data_source["Experiment"][count] == experiment) & (data_source["Threshold"][count] == threshold):
result = [data_source[col][count] for col in columns]
count+=1
return result
# Initialise data sources
data = source.data
count_data = count_source.data
error_data = error_source.data
# Initialise values
experiment = cb_obj.value
threshold = 20
counts, upper, lower = data["counts"], data["upper"], data["lower"]
tempdata = slicer(count_data, experiment, threshold,"Experiment", ["A","B","C","D"])
temperror = slicer(error_data, experiment, threshold,"Experiment", ["A","B","C","D"])
# Select values and emit changes
for i in range(len(counts)):
counts[i] = tempdata[i]
for i in range(len(counts)):
upper[i] = counts[i]+temperror[i]
lower[i] = counts[i]-temperror[i]
source.change.emit()
exp_dropdown = Select(title="Select:", value="Exp 0", options=list(count_data.Experiment.unique()))
thr_dropdown = Select(title="Select:", value="12", options=list(count_data.Threshold.astype(str).unique()))
exp_dropdown.callback = CustomJS.from_py_func(callback)
p.xgrid.grid_line_color = None
p.legend.orientation = "horizontal"
p.legend.location = "top_center"
layout = column(exp_dropdown,thr_dropdown, p)
show(layout)
The solution to the question is that Select menu needs to be defined before callback function. This code works:
from bokeh.io import show, output_notebook, output_file
from bokeh.models import ColumnDataSource, Whisker
from bokeh.plotting import figure
from bokeh.transform import factor_cmap
from bokeh.models import CustomJS, ColumnDataSource, Slider, Select
from bokeh.layouts import column
import numpy as np
import pandas as pd
def generate_data(factor=10):
rawdata = pd.DataFrame(np.random.rand(10,4)*factor, columns = ["A","B","C","D"])
idx = pd.MultiIndex.from_product([["Exp "+str(i) for i in range(5)],
[20,999]],names=["Experiment","Threshold"])
rawdata.index = idx
return rawdata.reset_index()
# Generate data
output_notebook()
count_data = generate_data()
error_data = generate_data(factor=2)
groups = ["A","B","C","D"]
initial_counts = count_data[(count_data.Experiment == "Exp 0")
& (count_data.Threshold == 20)][["A","B","C","D"]].values[0]
initial_errors = error_data[(error_data.Experiment == "Exp 0")
& (error_data.Threshold == 20)][["A","B","C","D"]].values[0]
# Create primary sources of data
count_source = ColumnDataSource(data=count_data)
error_source = ColumnDataSource(data=error_data)
# Create plotting source of data
source = ColumnDataSource(data=dict(groups=groups, counts=initial_counts,
upper=initial_counts+initial_errors,
lower=initial_counts-initial_errors))
# Bar chart and figure
p = figure(x_range=groups, plot_height=350, toolbar_location=None, title="Values", y_range=(0,20))
p.vbar(x='groups', top='counts', width=0.9, source=source, legend="groups",
line_color='white', fill_color=factor_cmap('groups', palette=["#962980","#295f96","#29966c","#968529"],
factors=groups))
# Error bars
p.add_layout(
Whisker(source=source, base="groups", upper="upper", lower="lower", level="overlay")
)
exp_dropdown = Select(title="Select:", value="Exp 0", options=list(count_data.Experiment.unique()))
thr_dropdown = Select(title="Select:", value="20", options=list(count_data.Threshold.astype(str).unique()))
def callback(source=source, count_source = count_source, error_source=error_source, exp_dropdown = exp_dropdown,
thr_dropdown=thr_dropdown,window=None):
def slicer(data_source, experiment, threshold, dummy_col, columns):
""" Helper function to enable lookup of data."""
count = 0
for row in data_source[dummy_col]:
if (data_source["Experiment"][count] == experiment) & (data_source["Threshold"][count] == threshold):
result = [data_source[col][count] for col in columns]
count+=1
return result
# Initialise data sources
data = source.data
count_data = count_source.data
error_data = error_source.data
# Initialise values
experiment = exp_dropdown.value
threshold = thr_dropdown.value
counts, upper, lower = data["counts"], data["upper"], data["lower"]
tempdata = slicer(count_data, experiment, threshold,"Experiment", ["A","B","C","D"])
temperror = slicer(error_data, experiment, threshold,"Experiment", ["A","B","C","D"])
# Select values and emit changes
for i in range(len(counts)):
counts[i] = tempdata[i]
for i in range(len(counts)):
upper[i] = counts[i]+temperror[i]
lower[i] = counts[i]-temperror[i]
source.change.emit()
exp_dropdown.callback = CustomJS.from_py_func(callback)
thr_dropdown.callback = CustomJS.from_py_func(callback)
p.xgrid.grid_line_color = None
p.legend.orientation = "horizontal"
p.legend.location = "top_center"
layout = column(exp_dropdown,thr_dropdown, p)
show(layout)