Query Regarding slider

I am new to Dash and am working on creating a dashboard on multi-objective optimization in Dash plotly. I am creating interactive graphs where if I click on any data point in the scatter plot (f1 vs f2), the slider (x1, x2, x3, x4) should adjust the value. I read about the slider tutorial, but what I need is the reverse of that: clicking on any data point in the scatter plot, the slider values of x1, x2, x3, and x4 should show up.

Here is the code that I am using. Please let me know if it’s feasible in Dash and if yes, what changes in the code I need to make in order to get the desired result.

import base64
import datetime
import io

import dash
from dash.dependencies import Input, Output, State
from dash import Dash, dcc, html, Patch
import plotly.express as px
import plotly.graph_objs as go
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import figure
import dash_bootstrap_components as dbc

app = dash.Dash(
    __name__,
    external_stylesheets=[dbc.themes.BOOTSTRAP],
    suppress_callback_exceptions=True,
)

server = app.server

app.layout = html.Div([
    html.H1(
        children="MOO Visualization",
        style={
            "fontFamily": "Gill Sans",
            "fontWeight": "400",
            "textAlign": "center",
        },
    ),
    dcc.Upload(
        id="upload-data",
        children=html.Div(["Drag and Drop or ",
                           html.A("Select Files")]),
        style={
            "width": "100",
            "height": "60px",
            "lineHeight": "60px",
            "borderWidth": "1px",
            "borderStyle": "dashed",
            "borderRadius": "5px",
            "textAlign": "center",
            "margin": "10px",
        },
        # Allow multiple files to be uploaded
        multiple=True,
    ),
    html.Div([
        html.Div([
            html.Div(id="graph-container",
                     style={
                         'display': 'inline-block',
                         'width': '60%',
                     }),
            html.Div(
                [
                    html.Div([
                        html.Label([
                            "x1",
                            dcc.Slider(min=0, max=1, step=0.1, id="slider1")
                        ], )
                    ],
                    ),
                    html.Div([
                        html.Label([
                            "x2",
                            dcc.Slider(min=0, max=1, step=0.1, id="slider2")
                        ], )
                    ],
                             ),
                    html.Div([
                        html.Label([
                            "x3",
                            dcc.Slider(min=0, max=1, step=0.1, id="slider3")
                        ], )
                    ],
                             ),
                    html.Div(
                        [
                            html.Label([
                                "x4",
                                dcc.
                                Slider(min=0, max=1, step=0.1, id="slider4")
                            ], )
                        ],
                        )
                ],
                style={
                    'display': 'inline-block',
                    'width': '40%',
                    'padding-top': '4%',
                    'float': 'right',
                    'fontSize': '14',
                    'font-family': 'Arial',
                    'backgroundColor': '#ffffff'
                })
        ] )
    ])
])


# File upload function
def parse_data(contents, filename):
    content_type, content_string = contents.split(",")

    decoded = base64.b64decode(content_string)
    try:
        if "csv" in filename:
            # Assume that the user uploaded a CSV file
            df = pd.read_csv(io.StringIO(decoded.decode("utf-8")))
        elif "xls" in filename:
            # Assume that the user uploaded an excel file
            df = pd.read_excel(io.BytesIO(decoded))
    except Exception as e:
        print(e)
        return html.Div(["There was an error processing this file."])
    return df


@app.callback(
    Output("graph-container", "children"),
    #Output("slider1","value"),
    #Output("slider2","value"),
    #Output("slider3","value"),
    #Output("slider4","value"),
    [Input("upload-data", "contents"),
     Input("upload-data", "filename")],
)
def update_output(contents, filename):
    if contents is not None:
        contents = contents[0]
        filename = filename[0]
        df = parse_data(contents, filename)

        cols1 = ["f1", "f2"]
        cols2 = ["f1", "f2", "f3"]
        cols3 = ["f1", "f2", "f3", "f4"]

        for c in df.columns:
            if c in cols1:
                fig = go.Figure(px.scatter(
                    df,
                    x=df["f1"],
                    y=df["f2"],
                    labels={
                        "df[f1]": "f1",
                        "df[f2]": "f2",
                    },
                ),
                layout=go.Layout(width=50, height=50))
                graph = dcc.Graph(figure=fig)
            elif c in cols2:
                fig = go.Figure(
                    px.scatter_3d(
                        df,
                        x=df["f1"],
                        y=df["f2"],
                        z=df["f3"],
                        labels={
                            "df[f1]": "f1",
                            "df[f2]": "f2",
                            "df[f3]": "f3",
                        },
                    ))
                graph = dcc.Graph(figure=fig)
            elif c in cols3:
                fig = go.Figure(
                    px.scatter_3d(
                        df,
                        x=df["f1"],
                        y=df["f2"],
                        z=df["f3"],
                        color=df["f4"],
                        labels={
                            "df[f1]": "f1",
                            "df[f2]": "f2",
                            "df[f3]": "f3",
                            "df[f4]": "f4",
                        },
                    ))

                graph = dcc.Graph(figure=fig)

        return graph

#@app.callback(
#    Output(),
#    Input()
#)

#def slider_output(graph_point):
#    return slider1, slider2, slider3, slider4

if __name__ == "__main__":
    app.run_server(debug=True)

Thank you.

Hi @PMSB
Good question. There are a few things that need to be modified in your code.

First, remove the sliders from within the html.Label. They need to be located outside the label, such as:

html.Div([
    html.Label([
        "x1",
    ], ),
    dcc.Slider(min=0, max=1, step=0.1, id="slider1")
],
),

Second, you need to assign an ID to each graph you make inside the callback function. Otherwise, you won’t be able to refer to the clickData to read the data of the graph’s marker that the user clicks on. For example:

graph = dcc.Graph(figure=fig, id='graph1')

Assuming you’re using the clickData to set the min and max props of the slider, the final callback at the very end of the code could look like this:

@app.callback(
    Output('slider1','min'),
    Output('slider1','max'),
    Input('graph1','clickData')
)

def slider_output(graph_point):
    if graph_point:
        print(graph_point)
        min_as_f1 = graph_point['points'][0]['x']
        max_as_f2 = graph_point['points'][0]['y']
        return min_as_f1, max_as_f2
    else:
        return no_update

The complete code would be:

import base64
import datetime
import io

import dash
from dash.dependencies import Input, Output, State
from dash import Dash, dcc, html, Patch, no_update
import plotly.express as px
import plotly.graph_objs as go
import pandas as pd
import numpy as np
# import matplotlib.pyplot as plt
# from matplotlib import figure
import dash_bootstrap_components as dbc

app = dash.Dash(
    __name__,
    external_stylesheets=[dbc.themes.BOOTSTRAP],
    suppress_callback_exceptions=True,
)

server = app.server

app.layout = html.Div([
    html.H1(
        children="MOO Visualization",
        style={
            "fontFamily": "Gill Sans",
            "fontWeight": "400",
            "textAlign": "center",
        },
    ),
    dcc.Upload(
        id="upload-data",
        children=html.Div(["Drag and Drop or ",
                           html.A("Select Files")]),
        style={
            "width": "100",
            "height": "60px",
            "lineHeight": "60px",
            "borderWidth": "1px",
            "borderStyle": "dashed",
            "borderRadius": "5px",
            "textAlign": "center",
            "margin": "10px",
        },
        # Allow multiple files to be uploaded
        multiple=True,
    ),
    html.Div([
        html.Div([
            html.Div(id="graph-container",
                     style={
                         'display': 'inline-block',
                         'width': '60%',
                     }),
            html.Div(
                [
                    html.Div([
                        html.Label([
                            "x1",
                        ], ),
                        dcc.Slider(min=0, max=1, step=0.1, id="slider1")
                    ],
                    ),
                    html.Div([
                        html.Label([
                            "x2",
                            dcc.Slider(min=0, max=1, step=0.1, id="slider2")
                        ], )
                    ],
                             ),
                    html.Div([
                        html.Label([
                            "x3",
                            dcc.Slider(min=0, max=1, step=0.1, id="slider3")
                        ], )
                    ],
                             ),
                    html.Div(
                        [
                            html.Label([
                                "x4",
                                dcc.
                                Slider(min=0, max=1, step=0.1, id="slider4")
                            ], )
                        ],
                        )
                ],
                style={
                    'display': 'inline-block',
                    'width': '40%',
                    'padding-top': '4%',
                    'float': 'right',
                    'fontSize': '14',
                    'font-family': 'Arial',
                    'backgroundColor': '#ffffff'
                })
        ] )
    ])
])


# File upload function
def parse_data(contents, filename):
    content_type, content_string = contents.split(",")

    decoded = base64.b64decode(content_string)
    try:
        if "csv" in filename:
            # Assume that the user uploaded a CSV file
            df = pd.read_csv(io.StringIO(decoded.decode("utf-8")))
        elif "xls" in filename:
            # Assume that the user uploaded an excel file
            df = pd.read_excel(io.BytesIO(decoded))
    except Exception as e:
        print(e)
        return html.Div(["There was an error processing this file."])
    return df


@app.callback(
    Output("graph-container", "children"),
    #Output("slider1","value"),
    #Output("slider2","value"),
    #Output("slider3","value"),
    #Output("slider4","value"),
    [Input("upload-data", "contents"),
     Input("upload-data", "filename")],
)
def update_output(contents, filename):
    if contents is not None:
        contents = contents[0]
        filename = filename[0]
        df = parse_data(contents, filename)

        cols1 = ["f1", "f2"]
        cols2 = ["f1", "f2", "f3"]
        cols3 = ["f1", "f2", "f3", "f4"]

        for c in df.columns:
            if c in cols1:
                fig = go.Figure(px.scatter(
                    df,
                    x=df["f1"],
                    y=df["f2"],
                    labels={
                        "df[f1]": "f1",
                        "df[f2]": "f2",
                    },
                ),
                layout=go.Layout(width=50, height=50))
                graph = dcc.Graph(figure=fig, id='graph1')

            elif c in cols2:
                fig = go.Figure(
                    px.scatter_3d(
                        df,
                        x=df["f1"],
                        y=df["f2"],
                        z=df["f3"],
                        labels={
                            "df[f1]": "f1",
                            "df[f2]": "f2",
                            "df[f3]": "f3",
                        },
                    ))
                graph = dcc.Graph(figure=fig, id='graph1')

            elif c in cols3:
                fig = go.Figure(
                    px.scatter_3d(
                        df,
                        x=df["f1"],
                        y=df["f2"],
                        z=df["f3"],
                        color=df["f4"],
                        labels={
                            "df[f1]": "f1",
                            "df[f2]": "f2",
                            "df[f3]": "f3",
                            "df[f4]": "f4",
                        },
                    ))

                graph = dcc.Graph(figure=fig, id='graph1')

        return graph

@app.callback(
    Output('slider1','min'),
    Output('slider1','max'),
    Input('graph1','clickData')
)

def slider_output(graph_point):
    if graph_point:
        print(graph_point)
        min_as_f1 = graph_point['points'][0]['x']
        max_as_f2 = graph_point['points'][0]['y']
        return min_as_f1, max_as_f2
    else:
        return no_update

if __name__ == "__main__":
    app.run_server(debug=True)

Hi @adamschroeder,
Thank you for your quick reply. I have 2 questions:

  1. When I click on any point in the graph, it is not showing the correct corresponding value for x1. Also, min and max for the slider are not required. I only need to show the corresponding value of x1.
  2. I tried implementing the same for the rest of 3 sliders, but it’s showing Callback error.

what do you mean @PMSB ?
You want f1 to represent the string value of the x1 html.Label, or the slider value?

I want to show the values of x1, x2, x3. and x4 on the slider when I click on any point in the graph. For example if I click on a point where f1 = 1.4990522373294648e-10, and f2 =1.000210133354911, the slider values of x1 should show 0.9999999999990459, x2 = 0.4993066998193298, x3 = 0.5022333968055839, and x4 = 0.48569389541212166, on the slider.
The slider should remain the same (no need of max and min props), only the pointer should move to the corresponding values of x1, x2, x3 and x4. I hope you understood the point. If not, please let me know.

It’s becoming clearer, but I’m not sure how you got from f1 = 1.4990522373294648e-10, and f2 =1.000210133354911 to the x1 through x4 values. How do those two f1-f2 values of the graph translate to the four values from x1-x4?

For now, the code below just returns the static values you shared in the post above. We need to make that dynamic.

@app.callback(
    Output('slider1','value'),
    Output('slider2','value'),
    Output('slider3', 'value'),
    Output('slider4', 'value'),
    Input('graph1','clickData')
)

def slider_output(graph_point):
    if graph_point:
        print(graph_point)
        min_as_f1 = graph_point['points'][0]['x']
        max_as_f2 = graph_point['points'][0]['y']
        return 0.9999999999990459, 0.4993066998193298, 0.5022333968055839, 0.48569389541212166
    else:
        return no_update


If you refer the file (“Data.jpeg”), we have corresponding values of x1, x2, x3 and x4 for f1 and f2. We need to make the code dynamic so it can return the values of x1,x2,x3 and x4 when we click on any data point. Right now if we refer the Graph.png file, when I click on a point in the graph, its not showing the required values. Please let me know if I am able to clear your doubt.

1 Like

now I see what you mean. Thanks for clarifying. You can use the dcc.Store to store the df onto the clientside, and then retrieve it in the last callback to return the corresponding values.
The below code should work, assuming that the x1 values are all unique. Otherwise, you would need to update line 207 from dff = df[df.f1==f1_point] TO:

dff = df[(df.f1==f1_point) & (df.f2==f2_point)].

Here’s the full code:

import base64
import datetime
import io

import dash
from dash.dependencies import Input, Output, State
from dash import Dash, dcc, html, Patch, no_update
import plotly.express as px
import plotly.graph_objs as go
import pandas as pd
import numpy as np
# import matplotlib.pyplot as plt
# from matplotlib import figure
import dash_bootstrap_components as dbc

app = dash.Dash(
    __name__,
    external_stylesheets=[dbc.themes.BOOTSTRAP],
    suppress_callback_exceptions=True,
)

server = app.server

app.layout = html.Div([
    dcc.Store(id="stored-df"),
    html.H1(
        children="MOO Visualization",
        style={
            "fontFamily": "Gill Sans",
            "fontWeight": "400",
            "textAlign": "center",
        },
    ),
    dcc.Upload(
        id="upload-data",
        children=html.Div(["Drag and Drop or ",
                           html.A("Select Files")]),
        style={
            "width": "100",
            "height": "60px",
            "lineHeight": "60px",
            "borderWidth": "1px",
            "borderStyle": "dashed",
            "borderRadius": "5px",
            "textAlign": "center",
            "margin": "10px",
        },
        # Allow multiple files to be uploaded
        multiple=True,
    ),
    html.Div([
        html.Div([
            html.Div(id="graph-container",
                     style={
                         'display': 'inline-block',
                         'width': '60%',
                     }),
            html.Div(
                [
                    html.Div([
                        html.Label([
                            "x1",
                        ], ),
                        dcc.Slider(min=0, max=1, step=0.1, id="slider1")
                    ],
                    ),
                    html.Div([
                        html.Label([
                            "x2",
                        ], ),
                        dcc.Slider(min=0, max=1, step=0.1, id="slider2")
                    ],
                             ),
                    html.Div([
                        html.Label([
                            "x3",
                        ], ),
                        dcc.Slider(min=0, max=1, step=0.1, id="slider3")
                    ],
                             ),
                    html.Div(
                        [
                            html.Label([
                                "x4",
                            ], ),
                            dcc.Slider(min=0, max=1, step=0.1, id="slider4")
                        ],
                        )
                ],
                style={
                    'display': 'inline-block',
                    'width': '40%',
                    'padding-top': '4%',
                    'float': 'right',
                    'fontSize': '14',
                    'font-family': 'Arial',
                    'backgroundColor': '#ffffff'
                })
        ] )
    ])
])


# File upload function
def parse_data(contents, filename):
    content_type, content_string = contents.split(",")

    decoded = base64.b64decode(content_string)
    try:
        if "csv" in filename:
            # Assume that the user uploaded a CSV file
            df = pd.read_csv(io.StringIO(decoded.decode("utf-8")))
        elif "xls" in filename:
            # Assume that the user uploaded an excel file
            df = pd.read_excel(io.BytesIO(decoded))
    except Exception as e:
        print(e)
        return html.Div(["There was an error processing this file."])
    return df


@app.callback(
    Output("graph-container", "children"),
    Output("stored-df", "data"),
    #Output("slider1","value"),
    #Output("slider2","value"),
    #Output("slider3","value"),
    #Output("slider4","value"),
    [Input("upload-data", "contents"),
     Input("upload-data", "filename")],
    prevent_initial_call=True
)
def update_output(contents, filename):
    if contents is not None:
        contents = contents[0]
        filename = filename[0]
        df = parse_data(contents, filename)

        cols1 = ["f1", "f2"]
        cols2 = ["f1", "f2", "f3"]
        cols3 = ["f1", "f2", "f3", "f4"]

        for c in df.columns:
            if c in cols1:
                fig = go.Figure(px.scatter(
                    df,
                    x=df["f1"],
                    y=df["f2"],
                    labels={
                        "df[f1]": "f1",
                        "df[f2]": "f2",
                    },
                ),
                layout=go.Layout(width=50, height=50))
                graph = dcc.Graph(figure=fig, id='graph1')

            elif c in cols2:
                fig = go.Figure(
                    px.scatter_3d(
                        df,
                        x=df["f1"],
                        y=df["f2"],
                        z=df["f3"],
                        labels={
                            "df[f1]": "f1",
                            "df[f2]": "f2",
                            "df[f3]": "f3",
                        },
                    ))
                graph = dcc.Graph(figure=fig, id='graph1')

            elif c in cols3:
                fig = go.Figure(
                    px.scatter_3d(
                        df,
                        x=df["f1"],
                        y=df["f2"],
                        z=df["f3"],
                        color=df["f4"],
                        labels={
                            "df[f1]": "f1",
                            "df[f2]": "f2",
                            "df[f3]": "f3",
                            "df[f4]": "f4",
                        },
                    ))

                graph = dcc.Graph(figure=fig, id='graph1')

        return graph, df.to_dict('records')

@app.callback(
    Output('slider1','value'),
    Output('slider2','value'),
    Output('slider3', 'value'),
    Output('slider4', 'value'),
    Input('graph1','clickData'),
    State('stored-df','data')
)

def slider_output(graph_point, my_data):
    if graph_point:
        df = pd.DataFrame(my_data)
        print(df.head())
        f1_point = graph_point['points'][0]['x']
        f2_point = graph_point['points'][0]['y']
        dff =  df[df.f1==f1_point]
        return dff.x1.values[0],dff.x2.values[0],dff.x3.values[0],dff.x4.values[0]
    else:
        return no_update

if __name__ == "__main__":
    app.run_server(debug=True)