Dash Visualization to Highlight a Table Row Based on Hover Data
I Am Trying to Build an Interactive Dash Plotly Visual, Containing a Bunch of Images and a Table. the Following Code Generates Some Synthetic Data, with X...
I am trying to build an interactive dash plotly visual , containing a bunch of images and a table. The following code generates some synthetic data, with X having 536 normalized heatmaps of dimension 96x96 and y containing integers 0 to 9. Thus 0 has 200 samples , 1 has 10 samples and so on. For each element in y , there's a 96x96 heatmap with values element/10.
from dash import Dash, dcc, html, Input, Output,callback
from dash import dash_table
import plotly.express as px
import plotly.graph_objects as go
import pandas as pd
import numpy as np
import math
samples = [200,10,5,40,60,100,2,100,9,10]
idx = [n for n in range(10)]
for i,smp in zip(idx,samples):
if i == 0:
X = np.full((smp,96, 96), i/10)
y = np.full((smp,),i)
else:
tempx = np.full((smp,96, 96), i/10)
tempy = np.full((smp,),i)
X = np.r_[X,tempx]
y = np.r_[y,tempy]}
Now when I build the visualization, I get the expected behavior. When I select a number from the dropdown , the corresponding images show up along with a data-frame showing the chosen digit and the sample index. I want to add a behavior, such that when I hover on a image, the corresponding row in data-frame should get highlighted.
app = Dash(__name__)
tbl_cols = ['Choice','Samples']
app.layout = html.Div([
dcc.Dropdown([i for i in range(10)],
0,
id='my_dropdown'),
html.Div([
html.Div([
dcc.Graph(id='my_picbox',style={'display':'inline-block'})
],style={'width': '40%', 'display': 'inline-block'}),
html.Div([
dash_table.DataTable(
id = 'table',
columns = [{'name': i, 'id': i} for i in tbl_cols]),
],style={'width': '60%', 'display': 'inline-block'})
],style={'display': 'flex'})
])
@callback(
[Output(component_id='my_picbox', component_property='figure'),
Output(component_id='table', component_property='data')],
Input(component_id='my_dropdown', component_property='value')
)
def update_plot(digit):
if digit is not None:
samples = np.where(y==digit)[0]
if len(samples)>20:
samples = np.random.choice(samples,20)
imgs = np.empty((20,96,96),)
imgs[:]= np.nan
imgs[0:len(samples)]=X[samples, :, :]
fig = px.imshow(imgs[:, :, :],
binary_string=False,
zmin=0,
zmax=1,
facet_col=0,
aspect = 'auto',
facet_col_wrap=5,
facet_row_spacing = 0,
color_continuous_scale='rdylgn')
for i in fig.layout.annotations:
n = int(i['text'].split('=')[1])
try:
i['text']=str(samples[n])
except:
i['text']=' '
fig.update_layout(margin = dict(t=70, l=50, r=0, b=5),
#coloraxis_showscale = False,
height = 600,
width = 600,)
df = pd.DataFrame(data = {'Samples':samples})
df['Choice'] = digit
data = df[tbl_cols].to_dict('records')
return fig,data
if __name__ == '__main__':
app.run_server(debug=True, port=8056)
1 Answer
Here is an example of a Dash app where the live hover_data attribute of a Plotly graph synchronously highlights the respective row in a Dash data table
import math
import numpy as np
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from dash import Dash, Input, Output
from dash import callback, ctx, dash_table
from dash import dcc, html
from dash.exceptions import PreventUpdate
samples = [200, 10, 5, 40, 60, 100, 2, 100, 9, 10]
idx = [n for n in range(10)]
for i, smp in zip(idx, samples):
if i == 0:
X = np.full((smp, 96, 96), i / 10)
y = np.full((smp,), i)
else:
tempx = np.full((smp, 96, 96), i / 10)
tempy = np.full((smp,), i)
X = np.concatenate([X, tempx], axis=0)
y = np.concatenate([y, tempy], axis=0)
app = Dash(__name__)
tbl_cols = ["Choice", "Samples"]
app.layout = html.Div(
[
dcc.Dropdown([i for i in range(10)], 0, id="my_dropdown"),
html.Div(
[
html.Div(
[
dcc.Graph(
id="my_picbox",
)
],
),
html.Div(
[
dash_table.DataTable(
id="table",
columns=[{"name": i, "id": i} for i in tbl_cols],
style_cell={"textAlign": "center"}
),
],
style={"width": "60%"},
),
],
style={"display": "flex"},
),
]
)
# Add an additional output for the table's style_data_conditional property
@app.callback(
[
Output(component_id="my_picbox", component_property="figure"),
Output(component_id="table", component_property="data"),
Output(component_id="table", component_property="style_data_conditional"),
],
[
Input(component_id="my_dropdown", component_property="value"),
Input(component_id="my_picbox", component_property="hoverData"),
],
)
def update_plot(digit, hover_data):
if digit is None:
raise PreventUpdate
else:
samples = np.where(y == digit)[0]
if len(samples) > 20:
# samples = np.random.choice(samples, 20)
samples = samples[:20]
imgs = np.empty((20, 96, 96),)
imgs[:] = np.nan
imgs[0 : len(samples)] = X[samples, :, :]
fig = px.imshow(
imgs[:, :, :],
binary_string=False,
zmin=0,
zmax=1,
facet_col=0,
aspect="auto",
facet_col_wrap=5,
facet_row_spacing=0,
color_continuous_scale="rdylgn",
)
for i in fig.layout.annotations:
n = int(i["text"].split("=")[1])
try:
i["text"] = str(samples[n])
except:
i["text"] = " "
triggered_component = ctx.triggered_id
# Extract hover data
hovered_sample = None
if hover_data and triggered_component == "my_picbox":
curve_number = hover_data["points"][0]["curveNumber"]
if curve_number < len(samples):
hovered_sample = samples[curve_number]
fig.update_layout(
margin=dict(t=70, l=50, r=0, b=5),
height=600,
width=600,
)
df = pd.DataFrame(data={"Samples": samples})
df["Choice"] = digit
data = df[tbl_cols].to_dict("records")
# Generate conditional styling based on hover data
style_data_conditional = []
if hovered_sample is not None:
style_data_conditional = [
{
"if": {
"filter_query": "{{Samples}} = {}".format(hovered_sample)
},
"backgroundColor": "lightblue",
}
]
return fig, data, style_data_conditional
if __name__ == "__main__":
app.run_server(debug=True, port=8056)
gives, for example, the following app functionality: