Example of building Bokeh panels with complex layouts? - python

I am trying to make Bokeh Panels that have relatively complex layouts, so I tried moving half of my current layout into one Panel and half into another just to play around, like so:
selects = HBox(top_users_select, new_users_select, t1_users_select, t2_users_select, top_recent_users_select)
tab1 = Panel(inputs)
tab2 = Panel(VBox(HBox(plot2, plot1, plot3, plot4), HBox(plot5, plot6, plot7, plot8), data_table))
tabs = tabs(tab1, tab2)
show(tabs)
However this is giving me the following error:
File "main_panel.py", line 589, in <module>:
tab1 = Panel(inputs) Traceback (most recent call last):
File "/Users/joe/anaconda3/lib/python3.5/site-packages/bokeh/application/handlers/code_runner.py", line 71, in run
exec(self._code, module.__dict__)
File "/Users/joe/Desktop/scripts/src/main/python/Bokeh apps/insights/main_panel.py", line 589, in <module>
tab1 = Panel(inputs)
TypeError: __init__() takes 1 positional argument but 2 were given
I am fairly new to Bokeh, and looking at the docs I don't know exactly how to parse this error and get around it. Can someone point me to an example of laying out fairly complex grids in a Bokeh panel or tell me what the error means and how I can address it?

You can use row or column to create complex layouts within tabs.
You can even mix them like row(column(button1, button2), button3)
Example:
from bokeh.models.widgets import Panel, Tabs, Toggle, TextInput
from bokeh.io import output_notebook, show
from bokeh.plotting import figure
from bokeh.layouts import column, row
p1 = figure(plot_width=300, plot_height=300)
p1.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color="navy", alpha=0.5)
p2 = figure(plot_width=300, plot_height=300)
p2.line([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], line_width=3, color="navy", alpha=0.5)
toggle1 = Toggle(label="Foo", button_type="success")
toggle2 = Toggle(label="Foo", button_type="warning")
text_input = TextInput(value="default", title="Label:")
tab1 = Panel(child=row(p1,toggle2), title="circle")
tab2 = Panel(child=column(p2,toggle1, text_input), title="line")
tabs = Tabs(tabs=[ tab1, tab2 ])
output_notebook()
show(tabs)
See https://docs.bokeh.org/en/latest/docs/user_guide/layout.html for more details

I think you want to write selects instead of inputs, but there are basically some things wrong with your declaration.
Look at the examples at http://docs.bokeh.org/en/latest/docs/user_guide/interaction/widgets.html#tabs
from bokeh.models.widgets import Panel, Tabs
from bokeh.io import output_file, show
from bokeh.plotting import figure
output_file("slider.html")
p1 = figure(plot_width=300, plot_height=300)
p1.circle([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], size=20, color="navy", alpha=0.5)
tab1 = Panel(child=p1, title="circle")
p2 = figure(plot_width=300, plot_height=300)
p2.line([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], line_width=3, color="navy", alpha=0.5)
tab2 = Panel(child=p2, title="line")
tabs = Tabs(tabs=[ tab1, tab2 ])
show(tabs)
Your input in Panel should be assigned to child= and a Panel needs a title=.
Also change
tabs = tabs(tab1, tab2)
to
tabs = Tabs(tabs=[tab1,tab2,tab3,tab4])

Related

Python callback in Bokeh issue

I am trying to get a callback function to work in bokeh based on this simple example:
from bokeh.plotting import figure, curdoc
from bokeh.layouts import column
from bokeh.models import ColumnDataSource
TOOLS = "tap,reset"
p = figure(title="Some Figure", tools=TOOLS)
source = ColumnDataSource(dict(x=[[1, 3, 2], [3, 4, 6, 6]],
y=[[2, 1, 4], [4, 7, 8, 5]], name=['A', 'B']))
pglyph = p.patches('x', 'y', source=source)
def callback(attr, old, new):
# The index of the selected glyph is : new['1d']['indices'][0]
print("In callback")
patch_name = source.data['name'][new['1d']['indices'][0]]
print("TapTool callback executed on Patch {}".format(patch_name))
pglyph.data_source.on_change('selected',callback)
curdoc().add_root(column(p))
When I load the page and click on a polygon, I do not see the callback getting executed.
What is missing?
That's because the selected attribute value is not changed. The contained object is changed instead, and Bokeh doesn't detect deep changes.
Try replacing the callback function and the next line with:
def callback(attr, old, new):
print("In callback")
patch_name = source.data['name'][new[0]]
print("TapTool callback executed on Patch {}".format(patch_name))
pglyph.data_source.selected.on_change('indices', callback)

Adding form to Dash/Plotly app

Ok, So I want to go one step further. I don't know if it is possible with a dash.
I want to create a form ( probably WTForm from Flask ).
The form should have date and annotation/comments section.
When someone submits a form, it will save to the database.
Then dash will read it and show on the graph.
My graph looks like that:
On the x-axis will be date from FlaskForm representing Event that it was stored in the database, and annotation will be showed in the graph itself when I hover to that exact date
Something similar to this one:
And now, can you please tell me if it's possible? What tools should I use it? It's just a concept, but I think will be helpful for everyone.
In plotly you can display text using annotation. Example:
import plotly.plotly as py
import plotly.graph_objs as go
trace1 = go.Scatter(
x=[0, 1, 2, 3, 4, 5, 6, 7, 8],
y=[0, 1, 3, 2, 4, 3, 4, 6, 5]
)
trace2 = go.Scatter(
x=[0, 1, 2, 3, 4, 5, 6, 7, 8],
y=[0, 4, 5, 1, 2, 2, 3, 4, 2]
)
data = [trace1, trace2]
layout = go.Layout(
showlegend=False,
annotations=[
dict(
x=2,
y=5,
xref='x',
yref='y',
text='max',
showarrow=True,
arrowhead=7,
ax=0,
ay=-40
)
]
)
fig = go.Figure(data=data, layout=layout)
iplot(fig)
Ref : https://plot.ly/python/text-and-annotations
Hope that answers your question. Also refer to mode='lines+markers+text' in scatter plot(Adding Text to Data in Line and Scatter Plots section of plotly doc)
Pretty late here, but check out dbc.Input from dash-bootstrap-components.
https://dash-bootstrap-components.opensource.faculty.ai/l/components/input
There are form types, inputs, etc.
How I would do it is add a few inputs, etc. as well as a submit button. The submit button would trigger the function that creates the graphs, adds the relevant things, and returns the figure to the graph.

Using multiple bokeh HoverTool instances together with the models API

I'd like to use multiple Hovertools in one plot together with Hovertool's names attribute to selectivly apply each tool. Take for instance
hover1 = HoverTool(tooltips=[("group", "1")], names = ['line1'])
hover2 = HoverTool(tooltips=[("group", "2")], names = ['lines2'])
and the two data sources:
source1 = ColumnDataSource(data=dict(
xs=[[1, 3, 2], [3, 4, 6, 6]],
ys=[[2, 1, 4], [4, 7, 8, 5]],
))
source2 = ColumnDataSource(data=dict(
xs=[[1, 3, 2], [6, 7, 9, 8]],
ys=[[-1, 0, 1], [1, 1, 2, 1]]
))
I'd though the following (using the bokeh.models API) should do what I want
p = figure(plot_width=400, plot_height=400)
l1 = MultiLine(xs='xs', ys='ys', name='lines1')
l2 = MultiLine(xs='xs', ys='ys', name='lines2')
p.add_tools(hover)
p.add_tools(hover2)
p.add_glyph(source1, l1)
p.add_glyph(source2, l2)
show(p)
Alas the Hovertools in the resulting plot do not work (i.e. no tooltips are shown). Using the bokeh.plotting API as follows and everything works as expected:
p = figure(plot_width=400, plot_height=400, tools=[hover, hover2])
p.multi_line(xs='xs', ys='ys', source=source1, name='lines1')
p.multi_line(xs='xs', ys='ys', source=source2, name='lines2')
show(p)
Question: How does one replicate the result of the bokeh.plotting API with the bokeh.models API?
The names attribute of the HoverTool model in the Bokeh Documentation:
names: property type: List ( String )
A list of names to query for. If set, only renderers that have a matching value for their name attribute will be used.
With this
l1 = MultiLine(xs='xs', ys='ys', name='lines1')
You are assigning the name to the Multiline object and that is a glyph, not a renderer. So try this instead
from bokeh.io import output_notebook, show
output_notebook()
import numpy as np
from bokeh.plotting import figure
from bokeh.models.sources import ColumnDataSource
from bokeh.models.glyphs import MultiLine
from bokeh.layouts import row
from bokeh.models.tools import HoverTool
source = ColumnDataSource(data=dict(
xs1=[[1, 2, 3], [5, 6, 7]],
ys1=[[1, 2, 3], [6, 5, 7]],
xs2=[[7, 8, 9], [1, 2, 3]],
ys2=[[4, 5, 7], [6, 7, 2]],
)
)
hover1 = HoverTool(tooltips=[("group", "1")], names = ['lines1'])
hover2 = HoverTool(tooltips=[("group", "2")], names = ['lines2'])
p = figure(plot_width=400, plot_height=400)
l1 = MultiLine(xs='xs1', ys='ys1')
l2 = MultiLine(xs='xs2', ys='ys2')
r1 = p.add_glyph(source, l1, name='lines1') # the name is assigned to the renderer
r2 = p.add_glyph(source, l2, name='lines2')
# r1.name = 'lines1' # or you could assign the name like this as well
# r2.name = 'lines2'
p.add_tools(hover1)
p.add_tools(hover2)
# p = figure(plot_width=400, plot_height=400, tools=[hover1, hover2])
# p.multi_line(xs='xs1', ys='ys1', source=source, name='lines1')
# p.multi_line(xs='xs2', ys='ys2', source=source, name='lines2')
show(p)

How to use a CDS column to set the "line_dash" of a Multiline glyph?

I am plotting Multilines with the method multi_line.
from bokeh.plotting import figure, show
from bokeh.models import ColumnDataSource
source = ColumnDataSource(data=dict(
x=[3, 3],
y=[4, 4],
xs1=[[1, 2, 3], [2, 3, 4]],
ys1=[[6, 7, 2], [4, 5, 7]],
xs2=[[8, 9], [10, 11, 12]],
ys2=[[6, 7], [7, 8, 9]],
color=['red', 'green'],
width=[5, 1],
dash=['solid', 'dashed']
)
)
p = figure(
plot_width=400,
plot_height=400,
tools='lasso_select,pan,wheel_zoom'
)
p.multi_line(
xs='xs1',
ys='ys1',
source=source,
color='color',
line_join='round',
line_width='width', # this is working with the column name, despite the documentatio say nothing
# line_dash='dash' # this is not working
)
show(p)
A column for color, alpha or line_width can be set on multilines source CDS in order to plot each line in a different way. But this cannot be applied to the line_dash attribute. I would like to make the main line solid and the rest dashed.
If I use the glyph line for this main lines then I will lose performance because I need to update more than one glyph at the same time on each plots.
On the other hand I think there are something missing in the documentation about line_width because a CDS column can be assigned to this method argument and it works:
alpha (float) – an alias to set all alpha keyword args at once
color (Color) – an alias to set all color keyword args at once
>> line_width (float) - should this be added because it works with CDS columns?
Is there a way to assign a column for the line_dash attribute?
I didn´t test the rest of attributes in depth.
Although you are asking in relation to MultiLine, this has the same answer as Can the line dash of a segment plot be defined by source data? which is that "vectorizing" the line_dash property is not currently supported (as of 0.12.16) If you want to have different line dashes for now you will have to make separate calls to line.

Python Bokeh: remove toolbar from chart

Note from maintainers: The specifics of this question concern the bokeh.charts API which is obsolete and was removed several years ago. In modern Bokeh, specify toolbar_location:
p = figure(toolbar_location=None)
OBSOLETE:
I don't seem to be able to remove the toolbar from a bokeh Bar chart. Despite setting the tools argument to None (or False or '') I always end up with the bokeh logo and a grey line, e.g. with this code:
from bokeh.charts import Bar, output_file, show
# prepare some data
data = {"y": [6, 7, 2, 4, 5], "z": [1, 5, 12, 4, 2]}
# output to static HTML file
output_file("bar.html")
# create a new line chat with a title and axis labels
p = Bar(data, cat=['C1', 'C2', 'C3', 'D1', 'D2'], title="Bar example",
xlabel='categories', ylabel='values', width=400, height=400,
tools=None)
# show the results
show(p)
However, when I try the same with a bokeh plot, it works perfectly fine and the toolbar is gone, e.g. with this code:
from bokeh.plotting import figure, output_file, show
output_file("line.html")
p = figure(plot_width=400, plot_height=400, toolbar_location=None)
# add a line renderer
p.line([1, 2, 3, 4, 5], [6, 7, 2, 4, 5], line_width=2)
show(p)
Does anyone know what I'm doing wrong?
If you want to remove the logo and the toolbar you can do:
p.toolbar.logo = None
p.toolbar_location = None
Hope this resolves your problem
On any Bokeh plot object you can set:
p.toolbar_location = None

Categories

Resources