Black Lives Matter. Please consider donating to Black Girls Code today.

Dash: Multiple Ranges for Scatter Plot

I am new to Dash and am running into some issues with trying to have a visualization filter based off the ranges/values from Range sliders. I have attached the code block I am using below but I cant seem to get to work. Thanks for any help.

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_table_experiments as dt
import pandas as pd
import plotly
import plotly.graph_objs as go


#Define Dash App
app=dash.Dash()

df =  pd.DataFrame({
    'Range1': [1, 2, 3, 4, 5, 6],
    'Range2': [6, 7, 8, 9, 10, 11],
})


app.layout = html.Div([
    html.Div([
        html.H2('Sliders'),

        html.H4('Range1'),
        dcc.RangeSlider(
                        id='rangeslider_range1',
                        min=df['Range1'].min(),
                        max=df['Range1'].max(),
                        marks={str(range1): str(range1) for range1 in df['Range1'].unique()},
                        value = [df['Range1'].min(),df['Range1'].max()]
                        ),

        html.H4('Range2'),
        dcc.RangeSlider(
                        id='rangeslider_range2',
                        min=df['Range2'].min(),
                        max=df['Range2'].max(),
                        marks={str(range2): str(range2) for range2 in df['Range2'].unique()},
                        value = [df['Range2'].min(),df['Range2'].max()]
                        ),


        ],style={'width': '30%', 'display': 'inline-block'}),

    html.Div([
        dcc.Graph(id='graph_test'),
        ],
        style={'width': '60%', 'display': 'inline-block', 'float': 'right'})
    ]
)

@app.callback(
     dash.dependencies.Output('graph_test', 'figure'),
    [dash.dependencies.Input('rangeslider_range1', 'value'),
     dash.dependencies.Input('rangeslider_range2', 'value')#,

     ])

def update_graph(
                 rangeslider_range1,
                 rangeslider_range2
                 ):

    dff = df[df['Range1'] == rangeslider_range1 and df['Range2'] == rangeslider_range2]

    return {
        'data': [go.Scatter(
            x=dff['Range1'],
            y=dff['Range2'],
            mode='markers',
        )],
        'layout': go.Layout(
            xaxis={
                'title': 'Range1',
            },
            yaxis={
                'title': 'Range2',
            },
            hovermode='closest'
        )
    }


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

Hi @jerburt,

First, I want to confirm that I got what you want to achieve; you seem to want to have two filters, that change the values of the df and plot a graph accordingly. I hope that is correct.

I think there are two main things to take into consideration:

  1. RangeSlider: this component provides a list of length 2, giving the lower and higher values of the slider that the user selects. There fore when you say df['Range1'] == rangeslider_range1 you are asking pandas to check if the pd.Series df[‘Range1’] is equal to a list, which does not work. What you can do with pandas is use the the method pd.Series.isin() and that should work fine for your example.
    So, the filter would become: df['Range1'].isin(rangeslider_range1)

  2. The logic of using two filters: you are asking for a plot that has two lists of numbers, of potentially different lengths. This won’t work simply because the length of the x and y values need to be the same. For this, I think the logical thing to do is to provide one filter, and that would filter the df as mentioned above, then you can assign the y value to be dff[‘Range2’] which would already be filtered and would have the same length as all other series in the df.

Hope that helps! :slight_smile:

Good luck…

1 Like

[EDIT] In point 2 above I said that it doesn’t make sense to have a graph of two lists of different lengths. This is true but I don’t think this is a problem in your case. You are basically trying to apply two filters to the data frame. I think the only think needed is using pd.Series.isin method as many times as needed and I think it should be fine.