Bokeh Plotting: Enable tooltips for only some glyphs - python

I have a figure with some glyphs, but only want tooltips to display for certain glyphs. Is there currently a way to accomplish this in Bokeh?
Alternatively, is there a way to plot two figures on top of each other? It seems like that would let me accomplish what I want to do.

Thanks to this page in Google Groups I figured out how this can be done.
Link here
Edit 2015-10-20: looks like the google group link doesn't work anymore unfortunately. It was a message from Sarah Bird #bokehplot.
Edit 2017-01-18: Currently this would add multiple hover tool icons to the tool bar. This may cause problems. There is already an issue filed at github here. Alternatively, try #tterry's solution in the answer below.
Essentially you need to (bokeh version 0.9.2):
not add hover in your tools when you create the figure
create glyphs individually
add glyphs to your figure
set up the hover tool for this set of glyphs
add the hover tool to your figure
Example:
import bokeh.models as bkm
import bokeh.plotting as bkp
source = bkm.ColumnDataSource(data=your_frame)
p = bkp.figure(tools='add the tools you want here, but no hover!')
g1 = bkm.Cross(x='col1', y='col2')
g1_r = p.add_glyph(source_or_glyph=source, glyph=g1)
g1_hover = bkm.HoverTool(renderers=[g1_r],
tooltips=[('x', '#col1'), ('y', '#col2')])
p.add_tools(g1_hover)
# now repeat the above for the next sets of glyphs you want to add.
# for those you don't want tooltips to show when hovering over, just don't
# add hover tool for them!
Also if you need to add legend to each of the glyphs you are adding, try using bokeh.plotting_helpers._update_legend() method. github source Eg:
_update_legend(plot=p, legend_name='data1', glyph_renderer=g1_r)

You need to name your glyph with the name= attribute on the glyph that you are interested in having the hover tool active for and then set that name in the hover tool's names= attribute. (Note the name= attribute of the fig.line glyph in the example below.
hover = HoverTool( mode='vline', line_policy='nearest', names=['ytd_ave'],
tooltips=[
("Week Number", "#WeekNumber"),
("OH for the Week", "#OverHead{0.00}%"),
("OH Average", "#AveOverHead{0.00}%"),
("Non-Controllable Hours", "#NonControllableHours{0.0}"),
("Controllable Hours", "#ControllableHours{0.0}"),
("Total Hours", "#TotalHours{0.0}"),
]
)
fig = Figure(title='Weekly Overhead', plot_width=950, plot_height=400,
x_minor_ticks=2, tools=['pan', 'box_zoom', 'wheel_zoom', 'save',
'reset', hover])
ch = fig.vbar('WeekNumber', top='ControllableHours', name='Over Head',
color='LightCoral', source=sources, width=.5)
nch = fig.vbar('WeekNumber', bottom='ControllableHours', top='TotalOHHours',
name='Non-Controllable Over Head', color='LightGray',
source=sources, width=.5)
bh = fig.vbar('WeekNumber', bottom='TotalOHHours', top='TotalHours',
name='Project Hours', color='LightGreen', source=sources,
width=.5)
ave = fig.line('WeekNumber', 'AveOverHead', source=sources, color='red',
y_range_name='Percent_OH', name='ytd_ave')

Will Zhang's answer will work, but you would end up with multiple hover tools. If this is undesirable, you can add renderers to an existing hover tool:
from bokeh import plotting
from bokeh.models import HoverTool, PanTool, ResetTool, WheelZoomTool
hover_tool = HoverTool(tooltips=[('col', '#x'),('row', '#y')]) # instantiate HoverTool without its renderers
tools = [hover_tool, WheelZoomTool(), PanTool(), ResetTool()] # collect the tools in a list: you can still update hover_tool
plot = plotting.figure(tools=tools)
plot.line(x_range, y_range) # we don't want to put tooltips on the line because they can behave a little strange
scatter = plot.scatter(x_range, y_range) # we assign this renderer to a name...
hover_tool.renderers.append(scatter) # ...so we can add it to hover_tool's renderers.
So the differences here:
You can create your glyph in a high level way using the plotting interface and this will still work.
You don't have to create a new HoverTool (unless you want different tooltips) each time, just add it to the existing tool's renderers.

UPDATE from maintainters: hover IS now supported on both lines and images
OBSOLETE:
Hover is not currently supported for image type glyphs and line glyphs. So, using one of these glyphs in combination with glyphs that support hover tool tip, might be a work around.
See:
http://docs.bokeh.org/en/latest/docs/user_guide/objects.html#hovertool

Related

How to check Altair chart 's Zoom in/out functionality in a programatic way each time when i Zoom in/out in the front end

I have written a streamlit code where I am using altair to display a chart in the front end. The made that chart to have zoom in/out functionality. I used ".interactive()" to get this done like below.
chart = alt.Chart(embd_1).mark_circle(size=30).encode(
x = 'dimention1:Q',
y = 'dimention2:Q',
tooltip=['col1'] ,
color=color).properties(width=600,height=600).add_selection(selected).interactive()
But, I am facing a problem when I do zoom in/out in my app. It's taking lot of time to get updated. once i start scrolling the mouse, it takes literally more than 10 secs to update the zoomed chart in the front end.
I was just wondering this may be because streamlit is running all the code which is beneath the altairs code as i don't know how to skip or avoid a certain code when i use zoom in/out functionality.
So, The question is how to programatically define whether the user is zooming in/out ?
like below :
if CheckZoom_SomeThingIDnotKnowYet == True:
logic to execute code1
else:
logic to execute code2
Additional Info On data and Altair Code used:
def altair_graph(embd_1):
selected = alt.selection_single(on="click", empty="none")
dom = ['Other IPs', 'Slected IP','Sel Dims']
rng_clr = ['lightgrey', 'red','blue']
color_point=alt.Color('color', scale=alt.Scale(domain=dom, range=rng_clr))
color = alt.condition(selected, alt.value('red'), color_point,legend=None)
chart = alt.Chart(embd_1).mark_circle(size=30).encode(
x = 'dimention1:Q',
y = 'dimention2:Q',
tooltip=['dimention1','dimention2'] ,
color=color
).properties(width=600,height=600).add_selection(selected).interactive()
return chart
And Sample for the above function Can be created like below:
dimention1=np.random.rand(1,100000).squeeze()
dimention2=np.random.rand(1,100000).squeeze()
colr_values = ['Other Ids', 'Slected Id','Sel Dims']
color = np.random.choice(colr_values, 100000, p=[0.9, 0.05, 0.05])
sample = pd.DataFrame({'dimention1':dimention1,'dimention2':dimention2,'color':color})
altair_graph(sample)
As shown in the example, My real time data is more than 150k data points.
Even When I have executed the above code outside streamlit, This is taking significant time to zoom in/out. Could ssomeone please provide a workaround for this problem.
Altair/VegaLite is a the moment not very performant with that many data points (I think it slows down around 20-40k somewhere). You can try alt.data_transformers.enable('data_server') for potential minor improvements and see my answer here for some more details and discussion in the comments https://stackoverflow.com/a/67349827/2166823.

Dash datatable interactive with Mapbox

I am trying to make my dash datatable interactive with my Mapbox. So when I click on “A” highlighted as the image shown below, it should show me the latitude/longitude of the point on mapbox. Correct me if I am wrong but I need to use the callback function - clickData. But I tried a few times using clickData but it did not work. Was wondering if there is any code I can refer to or any website out there that talks about dash datatable interactive with clickData . Thank you!
This is my table:
This is my coding for mapbox:
fig = px.scatter_mapbox(df4_1.to_dict('records'), lat="Latitude", lon="Longitude", hover_name="Name", hover_data=["Id"],color_discrete_sequence=["purple"], zoom=9, height=450)
fig.update_layout(mapbox_style="open-street-map")
fig.update_layout(margin={"r":0,"t":0,"l":0,"b":0})
clickData is not a property of a dash datatable, what you have, is an active_cell property. There are examples on how to use the active cell to update plots. The caveat is that you don't get the value inside of the selected cell, but the row, and column id. This active_cell is not reliable if you have sorting enabled, or editing.
They give an example on how to get the value if you have a static datasource(df), but If you don't have a static datasource then you need to use a sort of data sharing solution between callbacks like a dcc.Store. https://dash.plotly.com/datatable/interactivity
Anyway at this point the problem just increased in complexity so I would suggest a different simpler model:
In that case I would say to separate the mapbox and the datatable entirely and just have a text input that generates the plot and have a simple callback:
#put this before your mapbox
dcc.Input(
id="text_input_id",
type="text,
placeholder="input name of mapbox")
#app.callback(
Output("out-all-types", "children"),
Input("text_input_id", "value"),
)
def render_mapbox(val):
data=get_data(val) #do your stuff, get the data, make the plot
fig= ....
return fig

How to update plotly express treemap to have both label as well as the value inside the plot?

Currently, plotly express treemap shows only label inside treemap. How to include the value alongside the label?
That's why I don't like express, there are too many limitations and to make these kinds of changes you have to access the trace either way. From my point of view it is better and more code-transparent to use plain plotly instead.
That being said, you can access the textinfo attribute of the trace to do this. From the reference:
Determines which trace information appear on the graph.
Any combination of "label", "text", "value", "current path", "percent root", "percent entry", "percent parent" joined with a "+" OR "none".
Taking an example from the site:
df = px.data.tips()
fig = px.treemap(df, path=['day', 'time', 'sex'], values='total_bill')
# this is what I don't like, accessing traces like this
fig.data[0].textinfo = 'label+text+value+current path'
fig.layout.hovermode = False
fig.show()
Also take a look at the texttemplate attribute for formatting options.

Remove border around vegaEmbed geoshape generated by altair?

In the below image, observe the border around a map generated from Chart.save() to either HTML or JSON canvas (the border is inside the canvas, not CSS styled).
For any other type of mark, one would expect to be able to use Chart.configure_view() to set strokeWidth=0 to remove the border, but this does not appear to affect this geoshape chart.
The vegaEmbed embed options do not appear to document what creates this border.
Is it possible to style or remove the border?
The way to remove the border is using configure_view(strokeWidth=0).
Here is an example, using the most recent version of Altair and the most recent version of Vega-Lite:
import altair as alt
from vega_datasets import data
counties = alt.topo_feature(data.us_10m.url, 'counties')
source = data.unemployment.url
alt.Chart(counties).mark_geoshape().encode(
color='rate:Q'
).transform_lookup(
lookup='id',
from_=alt.LookupData(source, 'id', ['rate'])
).project(
type='albersUsa'
).configure_view(
strokeWidth=0
)
If you see different results, it might be that your frontend renderer is out of date, and you should make certain you're using the most recent version of Vega-Lite to render your chart.

How do I get a space as thousands separator in my axis ticks of bokeh graph

With NumeralTickFormatter(format='0,0') I was able to get comma as thousands separator for example but I couldn't find a way to use a space.
I also tried to use the language='fi' parameter but that didn't seem to help or I used it the wrong way.
As far as I know the built in NumeralTickFormatter does not support this, since the underlying third party JavaScript library that it is built on does not have any mode to display with spaces. However, Bokeh has a FuncTickFormatter that allows you to provide your own JS snippet to do whatever formatting you like:
from bokeh.models import FuncTickFormatter
from bokeh.plotting import figure, show, output_file
output_file("formatter.html")
p = figure(plot_width=500, plot_height=500)
p.circle([0, 10], [10, 1000000], size=30)
p.yaxis.formatter = FuncTickFormatter(code="""
parts = tick.toString().split(".");
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, " ");
return parts.join(".");
""")
show(p)
Results in:

Categories