✊🏿 Black Lives Matter. Please consider donating to Black Girls Code today.
🧬 Learn how to build RNA-Seq data apps with Python & Dash. Register for the May 20 Webinar!

Dcc.slider min/ max not updating with dropdown - Help pls!

Hi,
I have been working on this problem for a while now. Essentially my code uploads a data set and they can choose certain dropdowns to populate a graph. I have now added a range slider to adjust the range of the colour bar - (which allows the user to remove outliers in their color scale). The range slider works fine HOWEVER it is using a range of [0,100] which is the default I am assuming? It does not pick up the ‘max’ and ‘min’ of the range slider that I have put in my code. I have printed these to double-check these values and they do provide the min and max of the selected drop-down component however these values do not correspond to the range slider. I am unsure about how to fix this and would, therefore, welcome any help if available. My code and respective data set is below:
Thank you
Best
Tilly

import base64
import io
from flask import Flask
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import plotly_express as px
import pandas as pd

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
server = Flask(__name__)
app = dash.Dash(external_stylesheets=external_stylesheets, server=server)

app.layout = html.Div([dcc.Upload(
    id='data-table-upload',
    children=html.Div([html.Button('Upload File')],
                      style={'width': '49%', 'height': "60px", 'borderWidth': '1px',
                             'borderRadius': '5px',
                             'textAlign': 'center',

                             }),
    multiple=False
),
    html.Div([html.Div([html.Div([dcc.Graph(id='my-3D-graph', animate=False)], style={'display': 'inline-block',
                                                                                      'width': '74%',
                                                                                      }),
                        html.Div([
                            html.Div([html.Label(
                                ["Select X variable:",
                                 dcc.Dropdown(
                                     id='xaxis-anim-3D',
                                     multi=False,
                                     placeholder="Select an "
                                                 "option "
                                                 "for X")],
                            )],
                                style={
                                    'padding': 10}),
                            html.Div([html.Label(
                                ["Select Y variable:",
                                 dcc.Dropdown(
                                     id='yaxis-anim-3D',
                                     multi=False,
                                     placeholder='Select an option '
                                                 'for Y')],
                            ), ],
                                style={
                                    'padding': 10}),
                            html.Div([html.Label(
                                ["Select color variable:",
                                 dcc.Dropdown(
                                     id='caxis-anim-3D',
                                     multi=False,
                                     placeholder='Select an option for color')],
                            )], style={
                                'padding': 10}),
                            html.Div([
                                html.Label(["Select color bar "
                                            "range:",
                                            dcc.RangeSlider(
                                                id='colorbar-slider',
                                            ), html.Div(id='slider-output-container')])
                            ], style={'fontSize': 14,
                                      'font-family': 'Arial',
                                      'height': '20%',
                                      'padding': 15,
                                     })
                        ],
                            style={
                                'display': 'inline-block',
                                'width': '25%',
                                'float': 'right',
                                'fontSize': 14,
                                'font-family': 'Arial',
                                'backgroundColor': '#ffffff'})
                        ], className='container',
                       style={'padding': 40,
                              'backgroundColor': '#ffffff'})])
])


def parse_contents(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))
        elif 'txt' or 'tsv' in filename:
            df = pd.read_csv(io.StringIO(decoded.decode('utf-8')), delimiter=r'\s+'
                             )
    except Exception as e:
        print(e)
        return html.Div([
            'There was an error processing this file.'
        ])
    return df


# POPULATE X AXIS DROPDOWN
@app.callback(Output('xaxis-anim-3D', 'options'),
              [Input('data-table-upload', 'contents')],
              [State('data-table-upload', 'filename')])
def populate_xaxis_dropdown_anim(contents, filename):
    df = parse_contents(contents, filename)
    return [{'label': i, 'value': i} for i in df.columns]


# POPULATE Y AXIS DROPDOWN
@app.callback(Output('yaxis-anim-3D', 'options'),
              [Input('data-table-upload', 'contents')],
              [State('data-table-upload', 'filename')])
def populate_yaxis_dropdown_anim(contents, filename):
    df = parse_contents(contents, filename)
    return [{'label': i, 'value': i} for i in df.columns]


# POPULATE C AXIS DROPDOWN
@app.callback(Output('caxis-anim-3D', 'options'),
              [Input('data-table-upload', 'contents')],
              [State('data-table-upload', 'filename')])
def populate_saxis_dropdown_anim(contents, filename):
    df = parse_contents(contents, filename)
    return [{'label': i, 'value': i} for i in df.columns]


# POPULATE COLORBAR SLIDER SCATTER
@app.callback([Output('colorbar-slider', 'min'),
               Output('colorbar-slider', 'max'),
               Output('colorbar-slider', 'step')
               ],
              [Input('data-table-upload', 'contents'),
               Input('caxis-anim-3D', 'value')
               ],
              [State('data-table-upload', 'filename')])
def populate_pressure_slider(contents, color, filename):
    df = parse_contents(contents, filename)
    min = round(int(df[color].min())),
    max = round(int(df[color].max())),
    step = 0.5
    return min, max, step


@app.callback(
    Output('slider-output-container', 'children'),
    [Input('colorbar-slider', 'value')]
)
def update_output(value):
    return 'You have selected "{}"'.format(value)


@app.callback(Output('my-3D-graph', 'figure'),
              [Input('data-table-upload', 'contents'),
               Input('xaxis-anim-3D', 'value'),
               Input('yaxis-anim-3D', 'value'),
               Input('caxis-anim-3D', 'value'),
               Input('colorbar-slider', 'value'),
                ],
              [State('data-table-upload', 'filename')]
              )
def update_figure(contents, x, y, color, color_value, filename):
    df = parse_contents(contents, filename)
    color_val_float = []
    for i in range(0, len(color_value), 1):
        color_val_float.append(float(color_value[i]))
    color_val = color_val_float
    print(color_val)
    return px.scatter(df, x=df[x], y=df[y], title="", animation_frame="Pressure",
                      animation_group=df.columns[0],
                      hover_name=df.columns[0],
                      hover_data={}, template="none", color=df[color],
                      color_continuous_scale='Viridis', range_color=color_val
                      ).update_xaxes(showgrid=False, title=x, autorange=True, ticks='outside',
                                     showline=True, showspikes=True, spikethickness=1, spikedash='solid',
                                     mirror=True, tickformat=".1f").update_yaxes(spikedash='solid',
                                                                                 showgrid=False,
                                                                                 title=dict(text=y,
                                                                                            standoff=5),
                                                                                 autorange=True, ticks='outside',
                                                                                 showspikes=True, spikethickness=1,
                                                                                 showline=True, mirror=True,
                                                                                 tickformat=".1f").update_layout(
        clickmode='event+select', hovermode='closest', margin={'l': 80}, autosize=True, font=dict(family='Helvetica',
                                                                                                  ),
        coloraxis_colorbar=dict(title=dict(text=color, side='right'), ypad=0),
    ).update_traces(marker=dict(size=10,
                                opacity=0.7,
                                showscale=False,
                                line=dict(width=0.7, color='DarkSlateGrey'),

                                colorscale="Viridis"))


if __name__ == '__main__':
    app.run_server()

DATASET USED:

Thank you!

I may be able to run your code this evening, but is the problem the slider step values not syncing after you update the min/max values? If so, you may also need to update marks when you update min and max. In my case, I found it easier to return a new slider component vice having an Output with several components to update.

Hi @flyingcujo
Thank you for responding!
My problem is that my min and max values are not updating. I select the x, y and color variable dropdowns which works great. I then drag the range slider which corresponds to range of the color bar which also works fine. The issue is that the range of my range slider (i.e. the min and max values) are not corresponding with the selected color dropdown. So the aim of the code below is that after the color variable dropdown is populated the selected column of data from that column will be used to determine the minimum and maximum of the range slider (by taking the minimum and maximum values from that column). When I print min and max they provide me with the correct numbers they are just not syncing to the range slider (the min and max on the range slider stay the same [0,100]).
Hope this makes things clearer.
Thanks again,
Best,
Tilly

# POPULATE COLORBAR SLIDER SCATTER
@app.callback([Output('colorbar-slider', 'min'),
               Output('colorbar-slider', 'max'),
               Output('colorbar-slider', 'step')
               ],
              [Input('data-table-upload', 'contents'),
               Input('caxis-anim-3D', 'value')
               ],
              [State('data-table-upload', 'filename')])
def populate_pressure_slider(contents, color, filename):
    df = parse_contents(contents, filename)
    min = round(int(df[color].min())),
    max = round(int(df[color].max())),
    step = 0.5
    return min, max, step

Gotcha. I’ll do my best to run you code this evening.

1 Like

So…hopefully what I am observing is what you are expecting. I noticed the slider updating correctly and the heatmap range updating as the slider value changed.

The heatmap is updated when the slider updates (I think this may have been caused by me modifying a callback - altering its Input to prevent a start-up error)

First, are you using the latest version of Dash? I am and had to do fix a few things to make it work.

dash                      1.9.0     
dash-core-components      1.8.0     
dash-html-components      1.0.2     

Also, Dash fires all callbacks at app startup, so you will notice some error checks at the start of your callbacks, similar to the following:

if not data:
  return dash.no_update

I also added a dcc.Store to your layout that handles converting the csv file to a pandas df. I noticed that your callbacks were doing this conversion for each dropdown which could be problematic based on how complex your app becomes.

This dcc.Store is then an input to a combined callback that updates all 3 of your dropdowns; I noticed the output of each callback was the same so I combined into 1.

For additional debugging, I turned on debug via

app.run_server(debug=True)

NOTE:

  1. You should add some error checks around the color variable as not all df[color] results in numeric outputs.
  2. You can use dash.callback_context in your update_figure callback to determine which input triggered the callback; this way you can determine if all conditions are met in order to plot.

Here is the modified code (also applied formatting to help me read it):

import base64
import io
from flask import Flask
import dash
from dash.dependencies import Input, Output, State
import dash_core_components as dcc
import dash_html_components as html
import plotly_express as px
import pandas as pd

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
server = Flask(__name__)
app = dash.Dash(external_stylesheets=external_stylesheets, server=server)

app.layout = html.Div(
    [
        dcc.Store(
            id='csv-data',
            storage_type='session',
            data=None,
        ),

        dcc.Upload(
            id='data-table-upload',
            children=html.Div(
                [
                    html.Button('Upload File')
                ],
                style={
                    'width': '49%', 'height': "60px", 'borderWidth': '1px',
                    'borderRadius': '5px',
                    'textAlign': 'center',
                }
            ),
            multiple=False
        ),

        html.Div(
            [
                html.Div(
                    [
                        html.Div(
                            [
                                dcc.Graph(id='my-3D-graph',
                                          animate=False)
                            ],
                            style={
                                'display': 'inline-block',
                                'width': '74%',
                            }
                        ),
                        html.Div(
                            [
                                html.Div(
                                    [
                                        html.Label(
                                            [
                                                "Select X variable:",
                                                dcc.Dropdown(
                                                    id='xaxis-anim-3D',
                                                    multi=False,
                                                    placeholder="Select an option for X")
                                            ],
                                        )
                                    ],
                                    style={
                                        'padding': 10
                                    }
                                ),
                                html.Div(
                                    [
                                        html.Label(
                                            [
                                                "Select Y variable:",
                                                 dcc.Dropdown(
                                                     id='yaxis-anim-3D',
                                                     multi=False,
                                                     placeholder='Select an option for Y'
                                                 )
                                            ],
                                        ),
                                    ],
                                    style={'padding': 10}
                                ),
                                html.Div(
                                    [
                                        html.Label(
                                            [
                                                "Select color variable:",
                                                dcc.Dropdown(
                                                    id='caxis-anim-3D',
                                                    multi=False,
                                                    placeholder='Select an option for color')
                                            ],
                                        )
                                    ],
                                    style={'padding': 10}
                                ),
                                html.Div(
                                    [
                                        html.Label(
                                            [
                                                "Select color bar ange:",
                                                dcc.RangeSlider(
                                                    id='colorbar-slider',
                                                ),
                                                html.Div(id='slider-output-container')
                                            ]
                                        )
                                    ],
                                    style={
                                        'fontSize': 14,
                                        'font-family': 'Arial',
                                        'height': '20%',
                                        'padding': 15,
                                    }
                                )
                            ],
                            style={
                                'display': 'inline-block',
                                'width': '25%',
                                'float': 'right',
                                'fontSize': 14,
                                'font-family': 'Arial',
                                'backgroundColor': '#ffffff'}
                        )
                    ],
                    className='container',
                    style={
                        'padding': 40,
                        'backgroundColor': '#ffffff'
                    }
                )
            ]
        )
    ]
)


def parse_contents(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))
        elif 'txt' or 'tsv' in filename:
            df = pd.read_csv(io.StringIO(decoded.decode('utf-8')), delimiter=r'\s+'
                             )
    except Exception as e:
        print(e)
        return html.Div([
            'There was an error processing this file.'
        ])
    return df


@app.callback(Output('csv-data', 'data'),
              [Input('data-table-upload', 'contents')],
              [State('data-table-upload', 'filename')])
def parse_uploaded_file(contents, filename):
    if not filename:
        return dash.no_update

    df = parse_contents(contents, filename)
    return df.to_json(date_format='iso', orient='split')


# POPULATE X AXIS DROPDOWN
@app.callback([Output('xaxis-anim-3D', 'options'),
               Output('yaxis-anim-3D', 'options'),
               Output('caxis-anim-3D', 'options')],
              [Input('csv-data', 'data')])
def populate_axis_dropdown_anim(data):
    if not data:
        return dash.no_update, dash.no_update, dash.no_update

    df = pd.read_json(data, orient='split')
    options = [{'label': i, 'value': i} for i in df.columns]

    return options, options, options


# POPULATE Y AXIS DROPDOWN
# @app.callback(Output('yaxis-anim-3D', 'options'),
#               [Input('data-table-upload', 'contents')],
#               [State('data-table-upload', 'filename')])
# def populate_yaxis_dropdown_anim(contents, filename):
#     if not filename:
#         return dash.no_update
#
#     df = parse_contents(contents, filename)
#     return [{'label': i, 'value': i} for i in df.columns]


# POPULATE C AXIS DROPDOWN
# @app.callback(Output('caxis-anim-3D', 'options'),
#               [Input('data-table-upload', 'contents')],
#               [State('data-table-upload', 'filename')])
# def populate_saxis_dropdown_anim(contents, filename):
#     if not filename:
#         return dash.no_update
#
#     df = parse_contents(contents, filename)
#     return [{'label': i, 'value': i} for i in df.columns]


# POPULATE COLORBAR SLIDER SCATTER
@app.callback([Output('colorbar-slider', 'min'),
               Output('colorbar-slider', 'max'),
               Output('colorbar-slider', 'step')
               ],
              [Input('csv-data', 'data'),
               Input('caxis-anim-3D', 'value')
               ],
              [State('csv-data', 'data')])
def populate_pressure_slider(_, color, data):
    if not data or not color:
        return dash.no_update, dash.no_update, dash.no_update

    df = pd.read_json(data, orient='split')
    min_v = round(int(df[color].min()))
    max_v = round(int(df[color].max()))
    step = 0.5
    return min_v, max_v, step


@app.callback(
    Output('slider-output-container', 'children'),
    [Input('colorbar-slider', 'value')])
def update_output(value):
    return 'You have selected "{}"'.format(value)


@app.callback(Output('my-3D-graph', 'figure'),
              [Input('xaxis-anim-3D', 'value'),
               Input('yaxis-anim-3D', 'value'),
               Input('caxis-anim-3D', 'value'),
               Input('colorbar-slider', 'value')],
              [State('csv-data', 'data')])
def update_figure(x, y, color, color_value, data):
    if not data or not color_value:
        return dash.no_update

    df = pd.read_json(data, orient='split')
    color_val_float = []
    for i in range(0, len(color_value), 1):
        color_val_float.append(float(color_value[i]))
    color_val = color_val_float
    print(color_val)
    return px.scatter(df,
                      x=df[x],
                      y=df[y],
                      title="",
                      animation_frame="Pressure",
                      animation_group=df.columns[0],
                      hover_name=df.columns[0],
                      hover_data={},
                      template="none",
                      color=df[color],
                      color_continuous_scale='Viridis',
                      range_color=color_val
                      ).update_xaxes(showgrid=False,
                                     title=x,
                                     autorange=True,
                                     ticks='outside',
                                     showline=True,
                                     showspikes=True,
                                     spikethickness=1,
                                     spikedash='solid',
                                     mirror=True,
                                     tickformat=".1f").update_yaxes(spikedash='solid',
                                                                    showgrid=False,
                                                                    title=dict(text=y,standoff=5),
                                                                    autorange=True,
                                                                    ticks='outside',
                                                                    showspikes=True,
                                                                    spikethickness=1,
                                                                    showline=True,
                                                                    mirror=True,
                                                                    tickformat=".1f").update_layout(
        clickmode='event+select',
        hovermode='closest',
        margin={'l': 80},
        autosize=True,
        font=dict(family='Helvetica',),
        coloraxis_colorbar=dict(title=dict(text=color, side='right'), ypad=0),
    ).update_traces(marker=dict(size=10,
                                opacity=0.7,
                                showscale=False,
                                line=dict(width=0.7, color='DarkSlateGrey'),
                                colorscale="Viridis"))


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

Hi,
Thank you for helping me with that!! Your modified code did the trick :smiley:

1 Like

@flyingcujo
Hi again,
I was wondering if you could point me in the right direction with regards to doing error checks for df[color] as I am new to python.
Thank you!

Basically, error checking is just ensuring your code behaves as you expect it to. When you try selecting other params for the color options, you will notice errors related to df[color].min() (or your .max()) statements. This is due to df[color] returning output - sometimes strings or other non-numerics - which don’t have a min or max. But you know what your data is like so you can account for this when needed…if your slider is based on max/min values, then you can filter the options presented to the user in the dropdown so only the correct fields are permitted - i.e. only those column(s) with numerics.

Proper error checking is like writing comments in your code…we all know it should be done but many programmers are bad about doing it…myself included…I have the mentality of “i’ll get to it later”. But good testing is a great way to identify where error checks should be done…if you select all possible options and a couple give errors, then the errors will let you know if you have bad code or whether you just need to check for a few conditions that need to be satisfied.

1 Like

Thank you for your help! I have decided to use dbc.modal to incorporate error checking into my code as the end use of this tool would be for my research group.

Sounds good! Glad to have helped!

dash.no_update literally just made my day, especially when combined with multiple outputs. Thank you!