Mohamed Ait Ahmed
Mohamed Ait Ahmed

Reputation: 49

How to modify data points values in a database by click on a plotly graph

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

Answers (1)

Derek O
Derek O

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:

  • we should use 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 click
  • clicking points on the figure will update the clicked points we store, and also populate a textbox so the user can see which points they are removing. clicking the same point again will remove that point from storage (and the textbox)
  • there are two buttons: the update button will remove the clicked points from the stored points and update the figure. there is also a button to clear all points we want to remove from storage and the textbox (this is because it appears dash cannot process selecting and then immediately deselecting a point, so we'll use this button instead)

import 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)

enter image description here

Upvotes: 1

Related Questions