Bokeh chart with slider and checkbox? - python

I have data in format of given image.
I'm finding it difficult to add "Checkbox" of variables and "Slider" for index date range.
I have researched through StackOverflow a lot but since I have no knowledge on jQuery or js I couldn't figure out what to do.
My code :
from bokeh.palettes import Spectral11
from bokeh.plotting import figure, show, output_file
from bokeh.models import Legend, LegendItem
from bokeh.models import ColumnDataSource, HoverTool
from bokeh.models import CustomJS, Slider
from bokeh.layouts import row, widgetbox
from bokeh.models import CustomJS, Slider
from bokeh.plotting import figure, output_file, show, ColumnDataSource
output_file('temp.html')
numlines=len(data.columns)
mypalette=Spectral11[0:numlines]
p = figure(width=1100, height=650, x_axis_type="datetime")
r =p.multi_line(xs=[data.index.values]*numlines,
ys=[data[name].values for name in data],
line_color=mypalette,
line_width=3)
legend = Legend(items=[
LegendItem(label=f"{X_t_list[:1]}", renderers=[r], index=0),
LegendItem(label=f"{X_t_list[1:2]}", renderers=[r], index=1),
LegendItem(label=f"{X_t_list[2:3]}", renderers=[r], index=2),
LegendItem(label=f"{X_t_list[3:4]}", renderers=[r], index=3),
LegendItem(label=f"{X_t_list[4:5]}", renderers=[r], index=4),
LegendItem(label=f"{X_t_list[5:6]}", renderers=[r], index=5),
LegendItem(label=f"{X_t_list[6:7]}", renderers=[r], index=6),
LegendItem(label=f"{X_t_list[7:8]}", renderers=[r], index=7),
LegendItem(label=f"{X_t_list[8:9]}", renderers=[r], index=8),
LegendItem(label=f"{X_t_list[9:10]}", renderers=[r], index=9),
LegendItem(label=f"{X_t_list[10:11]}", renderers=[r], index=10),])
p.add_layout(legend)
show(p)
Bokeh plot I'm able to plot:
Can someone help me with this?

Instead of adding checkboxes you could use the interactive legend. I added this in my example instead of checkboxes, saves some code/space. Lines should disappear when you click on a legend item. I also added a DateRangeSlider.
Since I'm using callback functions in this code you'll have to run it as a bokeh server: bokeh serve script.py --show.
import pandas as pd
from bokeh.palettes import Spectral11
from bokeh.plotting import figure, show, curdoc
from bokeh.models.widgets import DateRangeSlider
from bokeh.layouts import layout
df = pd.read_csv('Data.csv')
df['Index'] = pd.to_datetime(df['Index'])
tooltips = [
("Value", "$y"),
("Variable", "$name")
]
p = figure(width=1100, height=650, x_axis_type="datetime", tooltips=tooltips)
for column, color in zip(list(df)[1:], Spectral11):
p.line(x=df['Index'], y=df[column], color=color, legend=column, line_width=3, name=column)
def updateRange(attr, old, new):
p.x_range.start = new[0]
p.x_range.end = new[1]
slider = DateRangeSlider(title="Period", start=min(df['Index']), end=max(df['Index']), value=(min(df['Index']),max(df['Index'])), width=500)
slider.on_change('value', updateRange)
p.legend.click_policy="hide"
curdoc().add_root(layout(slider,p))

Related

Fix right-side of plot in place when zooming in/out

When I zoom out the following Bokeh plot:
A bunch of whitespace is added to both sides of the data:
How can I make it so that the right-hand side of the plot is fixed, so that zooming out will only create whitespace on the left-hand side? For example, this is my desired zoom-out look:
This is the code that initializes the plot:
from bokeh.plotting import figure, curdoc
from bokeh.driving import linear
from bokeh.models.tools import PanTool, WheelZoomTool
p = figure(sizing_mode="stretch_both", y_axis_location="right", x_axis_type="datetime")
pan_tool = p.select(dict(type=PanTool))
pan_tool.dimensions="width"
zoom_tool = p.select(dict(type=WheelZoomTool))
zoom_tool.dimensions="width"
This can be done with a CustomJS callback. Define a ColumnDataSource as source and look for the maximum x value on thof the source. If you have multiple renderers or multiple sources, you have to find the mximum of the maximums.
Minimal Example
from bokeh.plotting import show, figure, output_notebook
from bokeh.models import CustomJS, Line, ColumnDataSource
output_notebook()
p = figure(width=500, height=300)
source = ColumnDataSource(dict(x=data, y=data))
p.line(x='x', y='y', source=source)
callback = CustomJS(
args=dict(p=p, source=source),
code="""
p.x_range.end = Math.max(...source.data['x'])
p.change.emit();
"""
)
p.x_range.js_on_change("end", callback)
show(p)

How to work with ColumnDataSource and LegendItem together in bokeh?

I would like to create a categorial scatter plot with these functions using bokeh:
Hover Tooltips
Legend outside the plot area with click policy = "hide"
After numerous searches, I can implement the function #1 and #2 separately.
But I don't know how to make these 2 functions work at the same time.
Tooltips only needs one glyph object, but legend outside plot area needs a for-loop to create a list of glyph objects which is used to generate legend_items.
Thanks.
Code Example:
Hover Tooltips can be achieved by using ColumnDataSources
(https://docs.bokeh.org/en/latest/docs/user_guide/categorical.html#heat-maps)
import pandas as pd
from bokeh.sampledata.stocks import AAPL
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure, output_file, show
df = pd.DataFrame(AAPL)
output_file("test.html")
p = figure(tools="hover")
source = ColumnDataSource(df)
p.scatter("high", "low", source=source)
p.hover.tooltips = [("Date","#date")]
show(p)
Legend outside the plot
(Position the legend outside the plot area with Bokeh)
import pandas as pd
from bokeh.models import Legend, LegendItem
from bokeh.palettes import Spectral4
from bokeh.plotting import figure, output_file, show
from bokeh.sampledata.stocks import AAPL, GOOG, IBM, MSFT
output_file("test2.html")
p = figure(x_axis_type="datetime")
p_list = []
for data, name, color in zip([AAPL, IBM, MSFT, GOOG], ["AAPL", "IBM", "MSFT", "GOOG"], Spectral4):
df = pd.DataFrame(data)
df['date'] = pd.to_datetime(df['date'])
p_list.append(p.line(df['date'], df['close'], line_width=2, color=color, alpha=0.8))
legend_items = [LegendItem(label=x, renderers=[p_list[i]]) for i, x in enumerate(["AAPL", "IBM", "MSFT", "GOOG"])]
legend_ = Legend(items=legend_items, click_policy="hide", location="center", label_text_font_size="12px")
p.add_layout(legend_, "right")
show(p)
import pandas as pd
from bokeh.models import Legend, LegendItem, ColumnDataSource
from bokeh.palettes import Spectral4
from bokeh.plotting import figure, show
from bokeh.sampledata.stocks import AAPL, GOOG, IBM, MSFT
p = figure(x_axis_type="datetime", tools='hover')
p.hover.tooltips = [("Date", "#date{%F}"),
("Open", "#open"),
("Close", "#close")]
p.hover.formatters = {'#date': 'datetime'}
legend_items = []
for data, name, color in zip([AAPL, IBM, MSFT, GOOG],
["AAPL", "IBM", "MSFT", "GOOG"],
Spectral4):
data['date'] = pd.to_datetime(data['date']).values
ds = ColumnDataSource(data)
renderer = p.line('date', 'close', source=ds,
line_width=2, color=color, alpha=0.8)
legend_items.append(LegendItem(label=name, renderers=[renderer]))
legend = Legend(items=legend_items, click_policy="hide",
location="center", label_text_font_size="12px")
p.add_layout(legend, "right")
show(p)
If you want to have different tooltips for different stocks, you can create one hover tool per each stock and specify them in the tools argument. The downside - there will be multiple hover tool buttons on the toolbar.

Strange Labels on Bokeh Pie/Donut Chart

I'm following the response in this question: Adding labels in pie chart wedge in bokeh
I'm trying to add labels to my Bokeh chart so users can see data values. For some reason, the chart is rendering like this:
I'm not sure why this is happening. I tried commenting out the z variable to see if this made a difference.. it does not.
Here is my code:
import os
import pandas as pd
import pyodbc
from bokeh.plotting import figure, show
from bokeh.io import export_png
from bokeh.models import LabelSet, ColumnDataSource
from bokeh.palettes import Category20
import matplotlib as plt
from math import pi
from bokeh.transform import cumsum
lst = ['On_Time', 'All']
lst2 = [8, 85]
df = pd.DataFrame(list(zip(lst, lst2)),
columns =['Column', 'Value'])
df
df['angle'] = df['value']/df['value'].sum() * 2*pi
df['angle']
df['color'] = ['#084594', '#2171b5']
#z=110*(df['value']/df['value'].sum())
#df['value']=z
#df
p = figure(plot_height=350, title="", toolbar_location=None,
tools="", x_range=(-.5, .5))
p.annular_wedge(x=0, y=1, inner_radius=0.15, outer_radius=0.25, direction="anticlock",
start_angle=cumsum('angle', include_zero=True), end_angle=cumsum('angle'),
line_color="white", fill_color='color', legend='column', source=df)
df["value"] = df['value'].astype(str)
df["value"] = df["value"].str.pad(35, side = "left")
source = ColumnDataSource(df)
labels = LabelSet(x=0, y=1, text='value', level='glyph',
angle=cumsum('angle', include_zero=True), source=source, render_mode='canvas')
p.add_layout(labels)
p.axis.axis_label=None
p.axis.visible=False
p.grid.grid_line_color = None
show(p)
The labels are "underneath" because you have set
level='glyph'
in the call to LabelSet. There is generally not very many reasons to disturb the default render levels. If you remove this, the label will show up "on top" the way annotations such as LabelSet are intended to.
Also note the "str padding" that the other answer used to position the label alignment. That's slightly hacky but serviceable. Font differences between viewers' browser might make small differences. The alternative is to compute actual, exact x, y positions around the circle where you want the labels to go.

Plotting in Bokeh using Custom Function?

does anyone know if/how one can use a "custom" function to plot in Bokeh using the Bokeh server? For example, I know you can use something like
plot = figure(toolbar_location=None)
plot.vbar(x='x', width=0.5, bottom=0, top='y', source=source)
But how can you plot using something like
def mplot(source):
p = pd.DataFrame()
p['aspects'] = source.data['x']
p['importance'] = source.data['y']
plot = Bar(p, values='importance', label='aspects', legend=False)
return plot
My current attempt is, here:
http://pastebin.com/7Zk9ampq
but it doesn't run. I'm not worried about getting the function "update_samples_or_dataset" working yet, just the initial plot to show. Any help would be much appreciated. Thanks!
Is this what you want? Note that I did not use the Bar function imported from bokeh.charts as this does not update upon updating the data source.
If you want to stick with using Bar from bokeh.charts you need to recreate the plot each time.
Note: to run this and have updating work - you need to execute bokeh serve --show plotfilename.py from the command line.
from bokeh.io import curdoc
from bokeh.layouts import layout
from bokeh.models.widgets import Button
from bokeh.plotting import ColumnDataSource, figure
import random
def bar_plot(fig, source):
fig.vbar(x='x', width=0.5, bottom=0,top='y',source=source, color="firebrick")
return fig
def update_data():
data = source.data
data['y'] = random.sample(range(0,10),len(data['y']))
source.data =data
button = Button(label="Press here to update data", button_type="success")
button.on_click(update_data)
data = {'x':[0,1,2,3],'y':[10,20,30,40]}
source = ColumnDataSource(data)
fig = figure(plot_width=650,
plot_height=500,
x_axis_label='x',
y_axis_label='y')
fig = bar_plot(fig, source)
layout = layout([[button,fig]])
curdoc().add_root(layout)
EDIT: See below a method that plots a bokeh plot but uses data from a dataframe as you wanted. It also will update the plot on each button press. Still you need to use the command bokeh serve --show plotfilename.py
from bokeh.io import curdoc
from bokeh.layouts import layout
from bokeh.models.widgets import Button
from bokeh.plotting import ColumnDataSource
from bokeh.charts import Bar
import random
import pandas as pd
def bar_plot(source):
df = pd.DataFrame(source.data)
fig = Bar(df, values='y', color="firebrick")
return fig
def update_data():
data = {'x':[0,1,2,3],'y':random.sample(range(0,10),4)}
source2 = ColumnDataSource(data)
newfig = bar_plot(source2)
layout.children[0].children[1] = newfig
button = Button(label="Press here to update data", button_type="success")
button.on_click(update_data)
data = {'x':[0,1,2,3],'y':[10,20,30,40]}
source = ColumnDataSource(data)
fig = bar_plot(source)
layout = layout([[button,fig]])
curdoc().add_root(layout)
I think you still have to attach your Bar instance to a Figure instance; a Figure is a set of plots, essentially, with niceties like the toolbar.

Migrating code from Bokeh 0.10.0 to 0.11.0

I have the below example of real time streaming in Bokeh 0.10.0 from reddit thread.
import time
from random import shuffle
from bokeh.plotting import figure, output_server, cursession, show
# prepare output to server
output_server("animated_line")
p = figure(plot_width=400, plot_height=400)
p.line([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], name='ex_line')
show(p)
# create some simple animation..
# first get our figure example data source
renderer = p.select(dict(name="ex_line"))
ds = renderer[0].data_source
while True:
# Update y data of the source object
shuffle(ds.data["y"])
# store the updated source on the server
cursession().store_objects(ds)
time.sleep(0.5)
I know that there is no cursession from 0.11.0 version. What would the code be like in Bokeh 0.11.0? Here is my attempt at it. Am I missing something? Basically, what I want the below code to do is to run as an app so that when I provide live streaming data, I can update the source and plot it realtime.
from bokeh.models import ColumnDataSource, HoverTool, HBox, VBoxForm
from bokeh.plotting import Figure, output_file, save
from bokeh.embed import file_html
from bokeh.models import DatetimeTickFormatter, HoverTool, PreText
from bokeh.io import curdoc
from bokeh.palettes import OrRd9, Greens9
plot = Figure(logo=None, plot_height=400, plot_width=700, title="",
tools=["resize,crosshair"])
source = ColumnDataSource(data=dict(x=[], y=[]))
plot.line([1,2,3], [10,20,30], source=source, legend='Price', line_width=1, line_color=OrRd9[0])
curdoc().add_root(HBox(plot, width=1100))
you probably want to add a periodic callback, something like:
def update():
ds.data["y"] = shuffle(y)
curdoc().add_periodic_callback(update, 500)
But also you actually need to put the data into the column data source, , and tell line the columns you want to use, instead of passing list literals to figure:
source = ColumnDataSource(data=dict(x=[1,2,3], y=[10,20,30]))
plot.line('x', 'y', source=source, legend='Price',
line_width=1, line_color=OrRd9[0])

Categories