Reputation: 49
I am looking for a clever/interactive way to modify the wrong values in a database by clicking on the plotly graph showing them. In other words, I want to add a sort of data modification zone, preferably on top of my plotly graphs (maybe a form with submit button or so ... ? ) For now, I am able to access the data point informations when clicking on it using the clickData property of the graph. Here is a simplified version of the callback function I used.
@app.callback(
Output('output', 'children'),
[Input('graph_interaction', 'clickData')])
def update_output(clickData):
if clickData:
point_index = clickData['points'][0]['pointIndex']
point_value = clickData['points'][0]['y']
print(clickData)
# update the point value in the database using SQLAlchemy
# ...
return 'You clicked on data point {} with value {}'.format(point_index, point_value)
return ''
Any insights on how to add a modification area (form ?) to interact with the database and modify wrong values ? Thank you
Upvotes: 0
Views: 892
Reputation: 19590
I've written a dash app that allows the user to interactively select and deselect data points to remove from a sample dataset. The main considerations are the following:
dcc.Store
to store data because global variables will break your app (see this example on storing data in the documentation). we can store both the dataframe (in the form of a dictionary with the index as keys, which will guarantee uniqueness), and also store the index of the points we clickimport numpy as np
import pandas as pd
import plotly.express as px
import dash
from dash import Input, Output, dcc, html, ctx
from typing import List
app = dash.Dash(__name__)
df = pd.DataFrame({
'x': list(range(5,10)),
'y': list(range(1,6))
})
fig = px.scatter(df, x='x', y='y')
app.layout = html.Div([
html.Div(
[
dcc.Textarea(id='selected-points-textbox'),
html.Br(),
html.Button('Clear Selection', id='clear-textbox', n_clicks=0),
html.Button('Update Data', id='update-data', n_clicks=0),
],
style={"padding-left": "80px"},
),
html.Div([
dcc.Graph(figure=fig, id='graph-interaction'),
dcc.Store(id='store-selected-points'),
dcc.Store(id='store-data')
])
])
@app.callback(
Output('store-selected-points','data'),
Output('store-data','data'),
Output('selected-points-textbox','value'),
Output('graph-interaction','figure'),
[Input('graph-interaction', 'clickData'),
Input('clear-textbox', 'n_clicks'),
Input('update-data', 'n_clicks'),
Input('store-selected-points','data'),
Input('store-data','data'),
Input('graph-interaction','figure'),
])
def show_selection(clickData, clearButton, updateButton, storedSelectedPoints, storedData, fig):
## initialize storedSelectedPoints and storedData
## we will store the pointIndex in the storedSelectedPoints
if storedSelectedPoints is None:
storedSelectedPoints = []
if storedData is None:
storedData = df.to_dict('index')
## storedData is in the following format:
# {
# 0: {'x': 5, 'y': 1},
# 1: {'x': 6, 'y': 2},
# 2...
# }
if ctx.triggered_id == "clear-textbox":
storedSelectedPoints = []
storedSelectedPointsText = '[]'
elif ctx.triggered_id == "update-data":
for p in storedSelectedPoints:
del storedData[p]
storedSelectedPoints = []
storedSelectedPointsText = '[]'
print(f"storedData with points removed: {storedData}")
df_new = pd.DataFrame.from_dict(storedData, orient='index')
fig = px.scatter(df_new, x='x', y='y')
## update the point value in the database using SQLAlchemy
elif clickData is not None:
## note that these index values will need to be strings
point_index = str(clickData['points'][0]['pointIndex'])
if point_index not in storedSelectedPoints:
storedSelectedPoints.append(point_index)
else:
storedSelectedPoints.remove(point_index)
storedSelectedPointsText = str(
[[storedData[p]['x'], storedData[p]['y']] for p in storedSelectedPoints]
)
return storedSelectedPoints, storedData, storedSelectedPointsText, fig
storedSelectedPointsText = str(storedSelectedPoints)
return storedSelectedPoints, storedData, storedSelectedPointsText, fig
if __name__ == "__main__":
app.run_server(debug=True)
Upvotes: 1