Controlling which elements receive hover tooltip in Bokeh - python-3.x

I'm currently trying to find a way to use an boolean array in order to decide which elements recive HoverTooltips in bokeh while only using one dictonary for the plot.
I allready tryed to use the bokeh rendering function but this didn't quite work.
source.data = dict(
x=data_source.loc[:, 'x_col_name'],
y=data_source.loc[:, 'y_col_name'],
color=color_selection(selected), # Translates bool to color
alpha=alpha_selection(selected), # Translates bool to transparency
active=selection # boolean array of selected elements
)
Only active datapoints should recieve a HoverTooltip

As a temporary work-around you could hide the tooltips in a callback like this (works for Bokeh v1.3.0):
from bokeh.plotting import figure, show
from bokeh.models import CustomJS
data = dict(
x=[1, 2, 3, 4, 5],
y=[1, 2, 3, 4, 5],
color=['red', 'green', 'red', 'green', 'red'],
active=[False, True, False, True, False],
)
p = figure(tooltips=[('x','#x'),('y', '#y'), ('active', '#active')])
p.circle('x', 'y', color='color', size=10, source = data)
code_hover = '''
if (cb_data.index.indices.length > 0) {
var active_index = cb_data.index.indices[0]
var data = cb_data.renderer.data_source.data
var show_tooltip = data['active'][active_index]
var tooltip_index = 0
if (show_tooltip) {
document.getElementsByClassName('bk-tooltip')[tooltip_index].style.display = 'block';
}
else {
document.getElementsByClassName('bk-tooltip')[tooltip_index].style.display = 'none';
}
}
'''
p.hover.callback = CustomJS(code = code_hover)
show(p)
Please note the tooltip_index in the callback. If you have more tooltips you need to change that index. See also this post
Result:

As of Bokeh 1.x there is no built-in capability to filter hover results. That feature is planned for the 2.0 release, you can follow this issue here: https://github.com/bokeh/bokeh/issues/9087

Related

Capture which items are displayed on Python Plotly Express chart based on legend selection

DISTRIB_DESCRIPTION="Ubuntu 20.04.5 LTS"
Streamlit, version 1.12.2
plotly==5.10.0
I have a Plotly Express px.scatter chart being generated in a Streamlit page. The different data points available to be shown are set by the color= parameter in ...
fig = px.scatter(x=df[x_column_name],
y=df[y_column_name],
color=df[color_column_name])
Which data (color) points are actually shown on the chart can be selected in the legend (see images.)
Is there a way to detect in the code (via the fig or something else) which data points (colors) have actually been selected in the legend to appear on the chart? I.e. In the example pictures, for the Streamlit (Python) code to know that only DMP, OTP, and BP are currently being seen on the plotly chart?
All selected
None selected
DMP, OTP, BP selected
FULL CODE
def control_chart_by_compound(df,
x_column_name,
y_column_name,
color_column_name,
chart_width = 800,
marker_line_width = 1,
standard_deviation = False,
stddev_colors = ["#CCFF00","#FFCC99","#FF9966"],
average = False,
average_color = "green",
custom_marker_lines = [],
custom_marker_lines_colors = []
):
if custom_marker_lines_colors == []:
custom_marker_lines_colors = CSS_blues()
fig = px.scatter(x=df[x_column_name],
y=df[y_column_name],
color=df[color_column_name],
width=chart_width,
labels={
"x": x_column_name,
"y": y_column_name,
color_column_name: "Compounds"
},
)
# Adds buttons select or deselect all amongst the legend (default the compounds as different colors)
fig.update_layout(dict(updatemenus=[
dict(
type = "buttons",
direction = "left",
buttons=list([
dict(
args=["visible", "legendonly"],
label="Deselect All compounds",
method="restyle"
),
dict(
args=["visible", True],
label="Select All compounds",
method="restyle"
)
]),
pad={"r": 10, "t": 10},
showactive=False,
x=1,
xanchor="right",
y=1.1,
yanchor="top"
),
]
))
if average != False:
fig.add_hline(y=np.average(df[y_column_name]),
line_color=average_color,
line_width=marker_line_width,
line_dash="dash")
# Add zero hline
fig.add_hline(y=0, line_color="gainsboro")
### Standard deviations
if standard_deviation != False:
stddev = df[y_column_name].std()
for idx, color in enumerate(stddev_colors):
fig.add_hline(y=stddev * (idx+1), line_color=color, line_width=marker_line_width,)
fig.add_hline(y=-stddev * (idx+1), line_color=color, line_width=marker_line_width,)
for idx, line in enumerate(custom_marker_lines):
fig.add_hline(y=line, line_color=custom_marker_lines_colors[idx], line_width=marker_line_width,)
fig.add_hline(y=-line, line_color=custom_marker_lines_colors[idx], line_width=marker_line_width,)
# Background to clear
fig.update_layout({
'plot_bgcolor': 'rgba(0, 0, 0, 0)',
'paper_bgcolor': 'rgba(0, 0, 0, 0)',
})
fig.update_layout(xaxis=dict(showgrid=False),
yaxis=dict(showgrid=False))
return fig

error somewhere in the code while adding colorscale in dash barplot

I have written some code to represent a barplot with a colorscale, in dash. The expected result looks something like this but with only one dropdown menu
https://brandatastudio.shinyapps.io/visualization-practica/
, therefore I wrote the following code:
#!/usr/bin/env python
import dash
import dash_html_components as html
import dash_core_components as dcc
import plotly.offline as pyo
import plotly.graph_objs as go
import pandas as pd
from dash.dependencies import Input, Output
app = dash.Dash()
datause = pd.read_csv("datawind.csv" , sep = ",")
app.layout = html.Div([
dcc.Graph(
id = 'barplot1'),
dcc.Dropdown(
id="dropmenu" ,
options=[{'label' : i , 'value' : i } for i in datause.columns.values
],
value='Total Investment ($ Millions)')
],
)
#app.callback(Output(component_id = 'barplot1', component_property = 'figure'),
[Input(component_id = 'dropmenu' , component_property = 'value')])
def update_figure(dropmenuinput):
potato = dropmenuinput
return {
'data': [
go.Bar(
x = datause["State"],
y = datause[potato],
mode = 'markers',
marker = {
"color" : datause[potato],
"colorbar" : dict(title = "hola"),
"colorscale" : 'Viridis'
})],
'layout': go.Layout(
title = "pene"
,xaxis={'title': "state"},
yaxis ={'title' : potato}) }
if __name__ == '__main__':
app.run_server(debug=True)
Instead, the obtained results ends up looking like this
After testing and retesting, I discover that the main error of the code seems to be in this section
mode = 'markers',
marker = dict(
size=16,
color = datause['Total Investment ($ Millions)'],
colorbar = dict(title = "hola"),
colorscale = 'Viridis'
))]
If I remove that section of the code, the dashboard looks something like it should, without the colorscale of course.
So, the question is, what is failing in my insertion of the colorscale? I have been checking different posts of how to add them
https://community.plot.ly/t/add-a-colorbar-to-scatter-plot-in-python/13349
https://plot.ly/python/colorscales/#custom-contour-plot-colorscale
and I find no examples applied to dash dashboard, or barplots, only here can I hope to find help.
Notes: Heres is the information regarding to the enviroment and packages affecting:
chardet==3.0.4
click==6.7
Cython==0.28.2
dash==0.21.0
dash-core-components==0.22.1
dash-html-components==0.10.0
dash-renderer==0.12.1
decorator==4.3.0
nbformat==4.4.0
numpy==1.14.2
pandas==0.22.0
pandas-datareader==0.6.0
plotly==2.5.1
python-dateutil==2.7.2
pytz==2018.4
requests==2.18.4
urllib3==1.22
Werkzeug==0.14.1
Here is the dataset Iḿ using https://drive.google.com/open?id=1Cr74jKf2FHDA7XVAblQX3d9_dVHfXyVG
The only problem I see is the line
mode = 'markers',
inside the go.Bar. If you go to the reference you will see that Bar traces do not have this attribute. If it still doesn't work I suggest updating plotly, as version 3 was a major upgrade to the library (actually I would recommend updating even if it works).
Here is a minimal example from a notebook:
trace = go.Bar(
x = [1, 2, 3, 4],
y = [1, 2, 3, 4],
marker = {
"color" : [1, 2, 3, 4],
"colorbar" : dict(),
"colorscale" : 'Viridis',
"showscale": True
}
)
I have had a similar issue when adding a color scale to a Map using Dash and the way I resolved this was by finding out the underlying colour schemes by running the code:
import plotly.express as px
print(px.colors.sequential.Plasma)
['#0d0887', '#46039f', '#7201a8', '#9c179e', '#bd3786', '#d8576b', '#ed7953', '#fb9f3a', '#fdca26', '#f0f921']
Then my command in Dash is:
"colorscale":[[0.0,'#0d0887'], [0.1,'#46039f'], [0.2,'#7201a8'], [0.3,'#9c179e'], [0.4,'#bd3786'], [0.5,'#d8576b'], [0.6,'#ed7953'], [0.7,'#fb9f3a'], [0.8,'#fdca26'], [1.0,'#f0f921']]

Use customized markers in FastMarkerCluster in Python folium?

I'm able to add data to a folium map using MarkerCluster and vary the parameters of the marker according to parameters for each point.
###Toy Example 1###
import pandas as pd
import numpy as np
import folium
import folium.plugins as plugins
lats = np.asarray([-54.4, -54.5, -54.6])
longs = np.asarray([-69.5, -69.5, -69.5])
data = np.asarray([70, 90, 5])
colour = np.asarray(['green', 'orange', 'red'])
dummy = [list(a) for a in zip(lats, longs, data, colour)]
dummy2 = pd.DataFrame(dummy, columns=['lat', 'lng', 'data', 'colour'])
dmap = folium.Map(location = [-54.5, -69.5], zoom_start = 7)
mc=plugins.MarkerCluster()
for a, row in dummy2.iterrows():
folium.CircleMarker(location=[row[0],row[1]],
radius=row[2], color=row[3], fill=True).add_to(mc)
mc.add_to(dmap)
dmap
As my use case has thousands of points, I would like to do something similar to the above with FastMarkerCluster. The farthest I've gone to achieving this is is:
###Toy Example 2###
callback = """\
function (row) {
var marker;
marker = L.circle(new L.LatLng(row[0], row[1]), {color:'red'});
return marker;
};
"""
lats = np.asarray([-54.4, -54.5, -54.6])
longs = np.asarray([-69.5, -69.5, -69.5])
data = np.asarray([70, 90, 5])
colour = np.asarray(['green', 'orange', 'red'])
dummy = [list(a) for a in zip(lats, longs)]
dummy2 = pd.DataFrame(dummy, columns=['lat', 'lng'])
dmap = folium.Map(location = [-54.5, -69.5], zoom_start = 7)
plugins.FastMarkerCluster(dummy, callback=callback).add_to(dmap)
dmap
I can envision adding {radius: row[2], color: row[3]} to the callback to get the functionality I want. However, if I add the data and/or colour arrays to dummy (as in Toy Example 1), I get TypeError: must be real number, not numpy.str_. If I use dummy2 in Toy Example 2, I get TypeError: must be real number, not str.
Any help on this is greatly appreciated.
f
Cheers,
-R
This is now solved in the Folium master branch on github thanks to Conegmo over there. There had been an issue where the column names were being read as part of the data. At the moment, installing Folium using the following means that all of the dummy examples I posted will work.
pip install git+https://github.com/python-visualization/folium.git

How can I use the plotly dropdown menu feature to update the z value in my choropleth map?

I just want to create a menu on the plot where I'm able to change the z-value in data only. I tried looking at other examples on here: https://plot.ly/python/dropdowns/#restyle-dropdown but it was hard since the examples were not exactly similar to my plot.
import plotly
import plotly.plotly as py
import plotly.graph_objs as go
import pandas as pd
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/2014_world_gdp_with_codes.csv')
data = [go.Choropleth(
locations = df['CODE'],
z = df['GDP (BILLIONS)'],
text = df['COUNTRY'],
colorscale = [
[0, "rgb(5, 10, 172)"],
[0.35, "rgb(40, 60, 190)"],
[0.5, "rgb(70, 100, 245)"],
[0.6, "rgb(90, 120, 245)"],
[0.7, "rgb(106, 137, 247)"],
[1, "rgb(220, 220, 220)"]
],
autocolorscale = False,
reversescale = True,
marker = go.choropleth.Marker(
line = go.choropleth.marker.Line(
color = 'rgb(180,180,180)',
width = 0.5
)),
colorbar = go.choropleth.ColorBar(
tickprefix = '$',
title = 'GDP<br>Billions US$'),
)]
layout = go.Layout(
title = go.layout.Title(
text = '2014 Global GDP'
),
geo = go.layout.Geo(
showframe = False,
showcoastlines = False,
projection = go.layout.geo.Projection(
type = 'equirectangular'
)
),
annotations = [go.layout.Annotation(
x = 0.55,
y = 0.1,
xref = 'paper',
yref = 'paper',
text = 'Source: <a href="https://www.cia.gov/library/publications/the-world-factbook/fields/2195.html">\
CIA World Factbook</a>',
showarrow = False
)]
)
fig = go.Figure(data = data, layout = layout)
py.iplot(fig, filename = 'd3-world-map')
It's been a while since this was asked, but I figured it was still worth answering. I can't speak to how this might have changed since it was asked in 2019, but this works today.
First, I'll provide the code I used to create the new z values and the dropdown menu, then I'll provide all of the code I used to create these graphs in one chunk (easier to cut and paste...and all that).
This is the data I used for the alternate data in the z field.
import plotly.graph_objects as go
import pandas as pd
import random
z2 = df['GDP (BILLIONS)'] * .667 + 12
random.seed(21)
random.shuffle(z2)
df['z2'] = z2 # example as another column in df
print(df.head()) # validate as expected
z3 = df['GDP (BILLIONS)'] * .2 + 1000
random.seed(231)
random.shuffle(z3) # example as a series outside of df
z4 = df['GDP (BILLIONS)']**(1/3) * df['GDP (BILLIONS)']**(1/2)
random.seed(23)
random.shuffle(z4)
z4 = z4.tolist() # example as a basic Python list
To add buttons to change z, you'll add updatemenus to your layout. Each dict() is a separate dropdown option. At a minimum, each button requires a method, a label, and args. These represent what is changing (method for data, layout, or both), what it's called in the dropdown (label), and the new information (the new z in this example).
args for changes to data (where the method is either restyle or update) can also include the trace the change applies to. So if you had a bar chart and a line graph together, you may have a button that only changes the bar graph.
Using the same structure you have:
updatemenus = [go.layout.Updatemenu(
x = 1, xanchor = 'right', y = 1.15, type = "dropdown",
pad = {'t': 5, 'r': 20, 'b': 5, 'l': 30}, # around all buttons (not indiv buttons)
buttons = list([
dict(
args = [{'z': [df['GDP (BILLIONS)']]}], # original data; nest data in []
label = 'Return to the Original z',
method = 'restyle' # restyle is for trace updates
),
dict(
args = [{'z': [df['z2']]}], # nest data in []
label = 'A different z',
method = 'restyle'
),
dict(
args = [{'z': [z3]}], # nest data in []
label = 'How about this z?',
method = 'restyle'
),
dict(
args = [{'z': [z4]}], # nest data in []
label = 'Last option for z',
method = 'restyle'
)])
)]
All code used to create this graph in one chunk (includes code shown above).
import plotly.graph_objs as go
import pandas as pd
import ssl
import random
# to collect data without an error
ssl._create_default_https_context = ssl._create_unverified_context
# data used in plot
df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/2014_world_gdp_with_codes.csv')
# z values used in buttons
z2 = df['GDP (BILLIONS)'] * .667 + 12
random.seed(21)
random.shuffle(z2)
df['z2'] = z2 # example as another column in the data frame
print(df.head()) # validate as expected
z3 = df['GDP (BILLIONS)'] * .2 + 1000
random.seed(231)
random.shuffle(z3) # example as a series outside of the data frame
z4 = df['GDP (BILLIONS)']**(1/3) * df['GDP (BILLIONS)']**(1/2)
random.seed(23)
random.shuffle(z4)
z4 = z4.tolist() # example as a basic Python list
data = [go.Choropleth(
locations = df['CODE'], z = df['GDP (BILLIONS)'], text = df['COUNTRY'],
colorscale = [
[0, "rgb(5, 10, 172)"],
[0.35, "rgb(40, 60, 190)"],
[0.5, "rgb(70, 100, 245)"],
[0.6, "rgb(90, 120, 245)"],
[0.7, "rgb(106, 137, 247)"],
[1, "rgb(220, 220, 220)"]],
reversescale = True,
marker = go.choropleth.Marker(
line = go.choropleth.marker.Line(
color = 'rgb(180,180,180)', width = 0.5)),
colorbar = go.choropleth.ColorBar(
tickprefix = '$',
title = 'GDP<br>Billions US$',
len = .6) # I added this for aesthetics
)]
layout = go.Layout(
title = go.layout.Title(text = '2014 Global GDP'),
geo = go.layout.Geo(
showframe = False, showcoastlines = False,
projection = go.layout.geo.Projection(
type = 'equirectangular')
),
annotations = [go.layout.Annotation(
x = 0.55, y = 0.1, xref = 'paper', yref = 'paper',
text = 'Source: <a href="https://www.cia.gov/library/publications/the-world-factbook/fields/2195.html">\
CIA World Factbook</a>',
showarrow = False
)],
updatemenus = [go.layout.Updatemenu(
x = 1, xanchor = 'right', y = 1.15, type = "dropdown",
pad = {'t': 5, 'r': 20, 'b': 5, 'l': 30},
buttons = list([
dict(
args = [{'z': [df['GDP (BILLIONS)']]}], # original data; nest data in []
label = 'Return to the Original z',
method = 'restyle' # restyle is for trace updates only
),
dict(
args = [{'z': [df['z2']]}], # nest data in []
label = 'A different z',
method = 'restyle'
),
dict(
args = [{'z': [z3]}], # nest data in []
label = 'How about this z?',
method = 'restyle'
),
dict(
args = [{'z': [z4]}], # nest data in []
label = 'Last option for z',
method = 'restyle'
)])
)]
)
fig = go.Figure(data = data, layout = layout)
fig.show()

Bokeh: One url per glyph

I have a set of datapoints, each with a url unique to it. What I want to do is to be able to scatter plot my data, and then open the associated url when clicking the glyph. I have read the discussion here and followed the example here, but neither gets me where I want to be.
I have, somewhat arbitrarily and haphazardly, tried to save the urls in the tag property, to be recalled by the TapTool:
from bokeh.models import OpenURL, TapTool
from bokeh.plotting import figure, show
p = figure(plot_width = 1200,
plot_height = 700,
tools = 'tap')
p.circle(data_x,
data_y,
tags = list(data_urls))
taptool = p.select(type = TapTool, arg = "tag")
taptool.callback = OpenURL(url = '#tag')
show(p)
I have not been able to find any place in the Bokeh documentation that explains the nuts and bolts needed to assemble the behaviour that i want. At least not in terms I can understand.
Could someone please point me in the right direction? Thanks!
The tags property is not relevant, and largely disused. You need to put the URLs in a column in the plot data source, so that the OpenURL callback can access it:
from bokeh.models import ColumnDataSource, OpenURL, TapTool
from bokeh.plotting import figure, show
p = figure(plot_width=400, plot_height=400,
tools="tap", title="Click the Dots")
source = ColumnDataSource(data=dict(
x=[1, 2, 3, 4, 5],
y=[2, 5, 8, 2, 7],
color=["navy", "orange", "olive", "firebrick", "gold"]
))
p.circle('x', 'y', color='color', size=20, source=source)
# use the "color" column of the CDS to complete the URL
# e.g. if the glyph at index 10 is selected, then #color
# will be replaced with source.data['color'][10]
url = "http://www.colors.commutercreative.com/#color/"
taptool = p.select(type=TapTool)
taptool.callback = OpenURL(url=url)
show(p)
This example is documented (and live) here:
https://docs.bokeh.org/en/latest/docs/user_guide/interaction/callbacks.html#openurl

Resources