I plot lines from some columns of a dataframe. I would like the hover tool to display the name of the column that originated that data and also some information from other columns not plotted.
For example, in the code below I would like the hover tool to display "Name = A; Aux = 0.1" when the mouse is over the central point on line A. This value is stored in column A1. Conversely, when over the central point on line B the tool should display "Name = B; Aux = 0.3"
from bokeh.io import show
from bokeh.models import HoverTool, ColumnDataSource
from bokeh.plotting import figure
import pandas as pd
df = pd.DataFrame({'x': [1, 2, 3], 'A' : [1, 5, 3], 'A1': [0.2, 0.1, 0.2],
'B' : [2, 4, 3], 'B1':[0.1, 0.3, 0.2]})
tools_to_show = 'box_zoom,save,hover,reset'
p = figure(plot_height =300, plot_width = 1200,
toolbar_location='above',
tools=tools_to_show)
columns = ['A', 'B']
source = ColumnDataSource(df)
for col in columns:
p.line('x', col, source=source)
hover = p.select(dict(type=HoverTool))
hover.tooltips = [("Name","#col"), ("Aux", "#col1")]
hover.mode = 'mouse'
show(p)
Thanks!
There is a recent feature to support this. With Bokeh 0.13.0 or newer, you can set the name on a glyph and refer to that name in a tooltip with $name. Additionally, you can refer to a column with that name with #$name. However, the "indirection" column has to be the one specified in name, so you will have to re-arrange your column names to this expectation:
from bokeh.io import show
from bokeh.models import ColumnDataSource
from bokeh.plotting import figure
import pandas as pd
df = pd.DataFrame({'x': [1, 2, 3], 'A_y' : [1, 5, 3], 'A': [0.2, 0.1, 0.2],
'B_y' : [2, 4, 3], 'B':[0.1, 0.3, 0.2]})
tools_to_show = 'box_zoom,save,hover,reset'
p = figure(plot_height =300, plot_width = 1200,
toolbar_location='above', tools=tools_to_show,
# "easy" tooltips in Bokeh 0.13.0 or newer
tooltips=[("Name","$name"), ("Aux", "#$name")])
columns = ['A', 'B']
source = ColumnDataSource(df)
for col in columns:
# have to use different colnames for y-coords so tooltip can refer to #$name
p.line('x', col + "_y", source=source, name=col)
show(p)
Related
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="..."))
I'm trying to create a heatmap using the Altair lib, but I'm looking to filter my data with a slider for different views. The slider works fine with the standard color only heatmap, but when I try to add text to the boxes to describe the values in each cell I get the javascript error below. (Adding the text to the heatmap works fine without any filter slider.)
import altair as alt
import pandas as pd
source = pd.DataFrame({'year':[2017, 2017, 2018, 2018],
'age':[1, 2, 1, 2],
'y':['a', 'a', 'a', 'a'],
'n':[1, 2, 3, 4]})
slider = alt.binding_range(min=2017, max=2018, step=1)
select_year = alt.selection_single(name="my_year_slider", fields=['year'], bind=slider)
base = alt.Chart(source).add_selection(select_year).transform_filter(select_year)
heatmap = base.mark_rect().encode(
x='age:O',
y='y:O',
color='n:Q')
text = base.mark_text(baseline='middle').encode(
x='age:O',
y='y:O',
text='n:Q')
heatmap + text
This returns Javascript Error: Duplicate signal name: "my_year_slider_tuple"
Because you added the selection to the base chart and then layered two copies of it, the selection is defined twice. The solution is to only define the selection once; something like this:
import altair as alt
import pandas as pd
source = pd.DataFrame({'year':[2017, 2017, 2018, 2018],
'age':[1, 2, 1, 2],
'y':['a', 'a', 'a', 'a'],
'n':[1, 2, 3, 4]})
slider = alt.binding_range(min=2017, max=2018, step=1)
select_year = alt.selection_single(name="my_year_slider", fields=['year'], bind=slider)
base = alt.Chart(source).encode(
x='age:O',
y='y:O',
).transform_filter(select_year)
heatmap = base.mark_rect().encode(color='n:Q').add_selection(select_year)
text = base.mark_text(baseline='middle').encode(text='n:Q')
heatmap + text
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;
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)
Really can't get to grips with how to plot a summary table of a pandas df. I'm sure this is not a case for a pivot table, or maybe a transposed method of displaying the data. Best I could find was : Plot table and display Pandas Dataframe
My code attempts are just not getting there:
dc = pd.DataFrame({'A' : [1, 2, 3, 4],'B' : [4, 3, 2, 1],'C' : [4, 3, 2, 1]})
data = dc['A'],dc['B'],dc['C']
ax = plt.subplot(111, frame_on=False)
ax.xaxis.set_visible(False)
ax.yaxis.set_visible(False)
cols=["A", "B", "C"]
row_labels=[0]
the_table = plt.table(cellText=data,colWidths = [0.5]*len(cols),rowLabels=row_labels, colLabels=cols,cellLoc = 'center', rowLoc = 'center')
plt.show()
All I would like to do, is produce a table plot, with A B C in the first column, and the total and mean in the rows next to them (see below). Any help or guidance would be great...feeling really stupid... (excuse the code example, it doesn't yet have the total and mean yet included...)
Total Mean
A x x
B x x
C x x
import pandas as pd
import matplotlib.pyplot as plt
dc = pd.DataFrame({'A' : [1, 2, 3, 4],'B' : [4, 3, 2, 1],'C' : [3, 4, 2, 2]})
plt.plot(dc)
plt.legend(dc.columns)
dcsummary = pd.DataFrame([dc.mean(), dc.sum()],index=['Mean','Total'])
plt.table(cellText=dcsummary.values,colWidths = [0.25]*len(dc.columns),
rowLabels=dcsummary.index,
colLabels=dcsummary.columns,
cellLoc = 'center', rowLoc = 'center',
loc='top')
fig = plt.gcf()
plt.show()
Does the dataFrame.describe() function helps you?
http://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.describe.html
Sorry, not enough points for comments.