How to use custom JS to change source of line plot? - python

My dataframe has several columns. I want to use a dropdown to trigger a change in the y-values.
So far I have
import pandas as pd
from bokeh.io import output_file, save
from bokeh.models import ColumnDataSource, Dropdown, CustomJS
from bokeh.layouts import column
from bokeh.plotting import figure
if __name__ == '__main__':
df = pd.DataFrame(data={"a": [1, 2, 3], "b": [2, 2, 2], "c": [3, 2, 1]})
source = ColumnDataSource(df)
fig = figure()
line = fig.line(x="a", y="b", source=source)
dropdown = Dropdown(label="Choose y", menu=[("b", "b"), ("c", "c")])
code = """
// how to set the line source equal to "this.item"?
"""
update_line_callback = CustomJS(args={"dropdown": dropdown, "line": line, "source": source}, code=code)
dropdown.js_on_event("menu_item_click", update_line_callback)
output_file("out.html")
save(column(children=[fig, dropdown]))
How do I link the dropdown item with the line source?

One working option is to create a column in your data source you want to show and change this values if the Dropdown is called by the selected name. The new name is this.item if you call dropdown.js_on_event("menu_item_click", *callback)
Here is the working code:
import pandas as pd
from bokeh.io import output_file, save
from bokeh.models import ColumnDataSource, Dropdown, CustomJS
from bokeh.layouts import column
from bokeh.plotting import figure, show, output_notebook
output_notebook()
# create some source
df = pd.DataFrame(data={"a": [1, 2, 3], "b": [2, 2, 2], "c": [3, 2, 1]})
df["show"] = df["b"]
source = ColumnDataSource(df)
# figure
fig = figure()
line = fig.line(x="a", y="show", source=source)
dropdown = Dropdown(label="Choose y", menu=[("b", "b"), ("c", "c")])
# overwrite show values with new selection
code = """
let s = source.data
s["show"] = s[this.item]
source.change.emit();
"""
dropdown.js_on_event("menu_item_click", CustomJS(args={"source": source}, code=code))
# output figure
show(column(children=[fig, dropdown]))

Related

Bokeh highlight values in second plot and show data table

I have two plots and a data table. I want to select a value in plot 1 and then the corresponding value of plot two should be highlighted in plot 2. In addition I would like to show a data table under both plots with the selected values. Here is what I have so far:
from bokeh.plotting import figure
from bokeh.models import ColumnDataSource
from bokeh.io import show
import numpy as np
import pandas as pd
from bokeh.layouts import row
from bokeh.models.widgets import DataTable, TableColumn
df2 = pd.DataFrame(np.array([[1, 3.280, 3.3925], [2, 3.3012, 3.4303], [3, 3.5972, 3.8696]]),
columns=['abspos', 'val1', 'val1_q'])
source = ColumnDataSource(data=df2)
p1 = figure(title="Plot1",
plot_width=1500,
plot_height=900,
x_range=[0, 5],
y_range=[0, 5])
p1.circle('abspos', 'val1', source=source, line_color=None, color='red', size=6)
pq1 = figure(title="Plot2",plot_width=900, plot_height=900)
pq1.circle('val1_q', 'val1', source=source, line_color=None, size=6)
columns = [
TableColumn(field="abspos", title="abspos"),
TableColumn(field="val1", title="val1"),
TableColumn(field="val1_q", title="val1_q")
]
data_table = DataTable(source=source, columns=columns, width=300, height=280)
def plot_both(plot1, plot2):
show(row(plot1, plot2))
plot_both(p1, pq1)
If I select the data point [2, 3.3012] in plot 1, the data point [3.3012, 3.4303] should be highlighted in plot 2. The data table underneath should show [2, 3.3012, 3.4303].
Question 1: How to achieve a highlight in plot 2 according to selected point in plot 1 (and vice versa).
Question 2: How to display a Table under both plots which shows the data of the selected data points.
The simplest way would be to use the tap tool (code below works for Bokeh v2.1.1)
from bokeh.plotting import show, figure
from bokeh.models import ColumnDataSource, Row, Column, CustomJS, DataTable, TableColumn
import numpy as np
import pandas as pd
df2 = pd.DataFrame(np.array([[1, 3.280, 3.3925], [2, 3.3012, 3.4303], [3, 3.5972, 3.8696]]),
columns=['abspos', 'val1', 'val1_q'])
source = ColumnDataSource(data=df2)
p1 = figure(title="Plot1",plot_width=900, plot_height=500, tools="tap,pan,box_zoom,wheel_zoom,save,reset")
p1.circle('abspos', 'val1', source=source, line_color=None, color='blue', size=10)
p2 = figure(title="Plot2",plot_width=900, plot_height=500, tools="tap,pan,box_zoom,wheel_zoom,save,reset")
p2.circle('val1_q', 'val1', source=source, line_color=None, color='blue', size=10)
columns = [
TableColumn(field="abspos", title="abspos"),
TableColumn(field="val1", title="val1"),
TableColumn(field="val1_q", title="val1_q")
]
dt1 = DataTable(source=source, columns=columns, width=900, height=300)
dt2 = DataTable(source=source, columns=columns, width=900, height=300)
show(Column(Row(p1, p2), Row(dt1, dt2)))
If you need more functionality you would need to add a JS callback like p1.js_on_event('tap', CustomJS(args={...}, code="..."))

Can bokeh dynamically update the number of columns in a gridplot?

I have 2 bokeh rows. The top row contains a DataTable and a TextInput, both of which are able to stretch_width in order to fit the width of the browser. The bottom row contains a gridplot, which is able to stretch_width, but only does so by distorting the scale of the image. Ideally, I would like the gridplot to update the amount of columns displayed based on the size of the browser.
Consider the following example:
import pandas as pd
from bokeh.models.widgets import DataTable, TableColumn
from bokeh.models import ColumnDataSource, TextInput
from bokeh.plotting import figure, output_file, save
from bokeh.layouts import row, column, gridplot
def get_datatable():
"""this can stretch width without issue"""
df = pd.DataFrame({'a': [0, 1, 2], 'b': [2, 3, 4]})
source = ColumnDataSource(df)
Columns = [TableColumn(field=i, title=i) for i in df.columns]
data_table = DataTable(columns=Columns, source=source, sizing_mode='stretch_width', max_width=9999)
return data_table
def get_text_input():
"""this can stretch width without issue"""
return TextInput(value='Example', title='Title', sizing_mode="stretch_width", max_width=9999)
def get_gridplot():
"""
this requires columns to be hard-coded
stretch_width is an option, but only distorts the images if enabled
"""
figs = []
for _ in range(30):
fig = figure(x_range=(0,10), y_range=(0,10))
_ = fig.image_rgba(image=[], x=0, y=0)
figs.append(fig)
return gridplot(children=figs, ncols=2)
top_row = row([get_datatable(), get_text_input()], max_width=9999, sizing_mode='stretch_width')
bottom_row = row(get_gridplot())
col = column(top_row, bottom_row, sizing_mode="stretch_width")
output_file("example.html")
save(col)
My end goal is to have the gridplot automatically update the amount of columns based on the width of the browser. Is there a way to do this natively in bokeh? If not, is it possible to do this via a CustomJs javascript callback?
Solution
Consider using sizing_mode=“scale_width” when calling figure.
fig = figure(x_range=(0,10), y_range=(0,10), sizing_mode=“scale_width”)
Note
It may be preferable to use scale_width instead of stretch_width more generally.
Bokeh Doc Example: https://docs.bokeh.org/en/latest/docs/user_guide/layout.html#multiple-objects

Setting absolute screen position of Bokeh Charts in Web App

I am trying to set the absolute position of a Bokeh Chart inside a Layout so that one of the plots is shown on top of another plot. Right now when I am plotting something like this:
from bokeh.io import curdoc
from bokeh.plotting import figure
from bokeh.layouts import layout
import numpy as np
x = np.arange(1,10.1,0.1)
y = [i**2 for i in x]
categories = ['A', 'B']
values = [1000, 1500]
fig1 = figure(width=600,plot_height=600, title="First Plot")
fig1.line(x=x, y=y)
fig2 = figure(width=200,plot_height=250,x_range=categories,
title="Second Plot") fig2.vbar(x=categories, top=values, width=0.2)
l = layout([[fig1,fig2]])
curdoc().add_root(l)
The result will be this:
What I am searching for is some way to make it look like that:
How can this result be achieved?
Thank you!
This is what I came up with (works for Bokeh v1.0.4). You need to move your mouse over the plot to get the other one jump inside but you could also copy the JS code from the callback and manually add it to the HTML generated by Bokeh so you achieve the same result.
from bokeh.plotting import figure, show
from bokeh.layouts import Row
from bokeh.models import ColumnDataSource, CDSView, BooleanFilter, CustomJS, BoxSelectTool, HoverTool
import pandas as pd
plot = figure(tools = 'hover', tooltips = [("x", "#x"), ("y", "#y")])
circles = plot.circle('x', 'y', size = 20, source = ColumnDataSource({'x': [1, 2, 3], 'y':[1, 2, 3]}))
inner_plot = figure(name = 'inner_plot', plot_width = 200, plot_height = 200)
lines = inner_plot.line('x', 'y', source = ColumnDataSource({'x': [8, 9, 10], 'y':[8, 6, 8]}))
code = """ div = document.getElementsByClassName('bk-root')[0];
tooltip_plot = div.children[0].children[1]
tooltip_plot.style = "position:absolute; left: 340px; top: 350px;"; """
callback = CustomJS(code = code)
plot.js_on_event('mousemove', callback)
show(Row(plot, inner_plot))
Result:

Select line color in a bokeh server

I'm trying to allow the users to select line colors in a bokeh server. My try, adapting this answer, is the following. However, the line color doesn't update. Anyone knows how to fix it?
Also, is ti possible to pass some dictionary for the select, so the options won't be hex code/
from bokeh.io import show, curdoc
from bokeh.models import ColumnDataSource, Legend, CustomJS, Select
from bokeh.plotting import figure
from bokeh.palettes import Category10
from bokeh.layouts import row
import pandas as pd
df0 = pd.DataFrame({'x': [1, 2, 3], 'Ay' : [1, 5, 3], 'A': [0.2, 0.1, 0.2], 'By' : [2, 4, 3], 'B':[0.1, 0.3, 0.2]})
columns = ['A', 'B']
tools_to_show = 'box_zoom,save,hover,reset'
p = figure(plot_height =300, plot_width = 1200,
toolbar_location='above',
tools=tools_to_show)
legend_it = []
color = Category10[10]
columns = ['A', 'B']
source = ColumnDataSource(df0)
c = []
for i, col in enumerate(columns):
c.append(p.line('x', col, source=source, name=col, color=color[i]))
legend_it.append((col, [c[i]]))
legend = Legend(items=legend_it, location=(5,114))#(0, -60))
p.add_layout(legend, 'right')
select = Select(title="color", value=color[0],
options = color)
callbacks = CustomJS(args=dict(renderer=c[0], select=select), code ="""
renderer.glyph.color.value = select.value;
renderer.trigger('change')
""")
select.callback = callbacks
layout = row(select, p)
curdoc().add_root(layout)
the JavaScript code is wrong, please change it to :
renderer.glyph.line_color = select.value;

Remove bokeh logo from a list of figures

You can easily remove Bokeh logo from a single figure doing the following:
from bokeh.plotting import figure, show
from bokeh.models.tools import PanTool, SaveTool
p = figure()
p.line([1, 2, 3, 4],[1, 4, 3, 0])
p.toolbar.logo = None
p.tools = [SaveTool(), PanTool()]
show(p)
or just using p.toolbar_location = None
I, however, didn't manage to hide it when having multiple figures:
from bokeh.plotting import figure, show
from bokeh.models.tools import PanTool, SaveTool
from bokeh.layouts import gridplot
from bokeh.models import ColumnDataSource, BoxZoomTool, WheelZoomTool, LassoSelectTool, BoxSelectTool, ResetTool, \
PanTool, TapTool, SaveTool
tools = [PanTool(), BoxZoomTool(match_aspect=True), WheelZoomTool(), BoxSelectTool(),
ResetTool(), TapTool(), SaveTool()]
figures = [figure(plot_width=800, plot_height=800,
tools=tools, output_backend="webgl", match_aspect=True) for i in range(2)]
figures[0].line([1, 2, 3, 4], [1, 4, 3, 0])
figures[0].toolbar.logo = None
figures[1].line([1, 2, 3, 4], [1, 4, 3, 0])
figures[1].toolbar.logo = None
show(gridplot([figures], merge_tools=True, sizing_mode='scale_height'))
I've also tried figures.toolbar.logo = None but of course it doesn't work as it's a list and it has no such attribute. How can i do that?
You can configured toolbar options to gridplot by passing a toolbar_options argument to gridplot:
grid = gridplot([figures], merge_tools=True, sizing_mode='scale_height',
toolbar_options=dict(logo=None))
show(grid)
This is documented in the Reference Guide entry for gridplot
logo=None didn't work for me. This css declaration did however:
<style>
.bk-logo {
display:none !important;
}
</style>

Categories

Resources