So I am trying to have a table/graph combo where I have a table with multiple columns, something like: (time, weight, name) and a graph that plots each person’s weight over time. You can assume that time is an int (0,1,2,3,4,5) etc.
For example, this is what the table might look like:
+------+------+--------+
| time | name | weight |
+------+------+--------+
| 0 | john | 200 |
+------+------+--------+
| 1 | john | 220 |
+------+------+--------+
| 0 | amy | 200 |
+------+------+--------+
| 1 | amy | 210 |
+------+------+--------+
| 2 | john | 230 |
+------+------+--------+
| 3 | john | 240 |
+------+------+--------+
You can imagine a corresponding plot with two lines, one for john
and one for amy
.
Now, I’m trying to get it so that if a user selects a point on the graph, and the graph’s selectedData
callback is called, I can then update the table’s selected-rows
callback so that the appropriate person’s entry in the table can be highlighted.
The problem is that selected data gives me information in the following format:
{'points': [{'curveNumber': 5,
'pointIndex': 9,
'pointNumber': 9,
'x': 250,
'y': 1.236}]}
Now, I don’t have that curve information and which person each curve corresponds to. More importantly, the table can be filtered, so I can have some people not even be visible on the table.
The way I have it right now is that I disregard the Input(Graph_ID, 'selectedData')
callback and use the state of the graph instead: State(Graph_ID, 'figure')
. This provides more information, specifically I get this bit of info for each curve on the plot:
{
# ...
# Some figure data....
# ....
{'data':
[{'line': {'color': 'rgba(133, 133, 133,1)', 'dash': 'dash'},
'marker': {'color': 'rgba(133, 133, 133,1)', 'symbol': 'o'},
'mode': 'lines+markers',
'name': 'john',
'selectedpoints': [9],
'type': 'scatter',
'unselected': {'line': {'opacity': 0.3}, 'marker': {'opacity': 0.3}},
'x': [0, 2, 7, 15, 30, 60, 90, 150, 180, 250, 365, 550],
'y': [1.62563,
1.38846,
1.30052,
1.34375,
1.35601,
1.31275,
1.27629,
1.236,
1.236,
1.236,
1.2345,
1.23321]}
}
## Bunch of other figure data....
}
So, if I know the x-axis’ corresponding column name in the table, I pass that into the callback as well, convert the table to a pandas DataFrame, and then do something like:
def get_row_from_table(dff, point_idx, curve_name):
# Get just john's rows (obviously can be someone other than john, but this is just for example)
john_rows = dff[dff['name'] == curve_name]
# Get the index of the row that matches the given point index using pandas' iloc, which
# retrieves a table row by it's row index, which is the same as point_index after filtering
# and reseting the index.
return john_rows.reset_index().iloc[point_idx,:]
@app.callback(
[Output(TABLE_ID, 'selected_rows')],
[Input(GRAPH_ID, 'selectedData')],
[State(SVI_TABLE_ID, 'data'),
State(SVI_GRAPH_ID, 'figure')])
def svi_update_style_on_user_input(_, table_data, graph_data):
dff = pd.DataFrame(table_data)
# Get Selected points:
curve_points = []
for curve in graph_data['data']:
selected_points = curve.get('selectedpoints', [])
if len(selected_points) > 0:
curve_points.append((curve['name'], selected_points))
table_selected_rows = []
for curveName, selectedPoints in curve_points:
for point_idx in selected_points:
row_df = get_row_from_table(point_idx, curve_name)
if len(row_df) > 0:
table_selected_rows.append(list(row_df)[0])
return table_selected_rows
Does dash have a better way to handle this? Any ideas if there are any built in components that can handle this for me? This works right now, but imagine scaling up.
Like for example, the actual get_row_from_table
function that I use is wayy more complex because my curve names are very different than the actual name
column’s values.
Any ideas what the proper behavior should be?