Search code examples
pythonpython-3.xplotlyplotly-dashplotly-python

Issue adding second Graph into Dash app.layout


The code below will run and display graphs in the 127.0.0.1 address, but only the first graph displays correctly. The second graph is a scatter_mapbox that displays the price in the given county filtered by the number of bedrooms that the user selects.

Current output: output

What the second graph should look like: what second graph should look like

app.layout = html.Div([
    html.Div([
        # Stuff that will be going on the top of the page

        html.H1('King County Housing Data/Graphs'),  # Heading 1
    ]),

    html.Div([
        html.H5('King County sqft-living / price Plot: 1900-2015'),
        dcc.Dropdown(id='grade-selector',
                     options=[{'label': i, 'value': i} for i in df['grade'].unique()],
                     value=7,  # Default Value
                     multi=False,  # Does  not allow you to select multiple grades
                     style={'width': '40%'}),  # Styling of the dropdown
        html.Div(id='output-container'),  # Callbacks Output #1
        dcc.Graph(id='grade-price')  # Callback's Output #2
    ]),
    # Interactive Graph using a map
    html.Div([
        html.H5('Geospatial heatmap of King County: 1900-2015'),
        dcc.Dropdown(id='Number-Of-Bedrooms',
                     options=[{'label': b, 'value': b} for b in df['bedrooms'].unique()],
                     value=2,
                     multi=False,
                     style={'width': '30%'},
                     ),
        html.Div(),
        dcc.Graph(id='bedrooms-selector')
    ])
])


# ----------------------------------------------------------------------------------------------------------------------
# App Callback & Function for Scatter Plot

@app.callback(
    [Output(component_id='output-container', component_property='children'),  # Outputs the html.Div
     Output(component_id='grade-price', component_property='figure')],  # Calls the graph dcc.Graph
    [Input(component_id='grade-selector', component_property='value')]  # Takes the input you put in with the
    # dcc.Dropdown and passes it into the Output
)
def gupdate_output_div(grade_selector):  # Makes the graph function

    container = 'The grade the user selected was {}'.format(grade_selector)  # Prints out the grade the user selected

    dff = df.copy()  # Makes a copy of the DataFrame, best practice
    dff = dff[dff['grade'] == grade_selector]  # Selects the grade that the user inputs

    # Creates the Plotly Graph
    fig = px.scatter(dff, x='sqft_living', y='price', marginal_y='violin', marginal_x='box', trendline='ols',
                     template='ggplot2')

    return [container, fig]


# ----------------------------------------------------------------------------------------------------------------------
# App Callback & Function for Map

@app.callback(
    [Output(component_id='bedrooms-selector', component_property='figure'),
     Input(component_id='Number-Of-Bedrooms', component_property='value')])


def update_output_div1(bedroomFilter):
    dff = df.copy()  # Makes a copy of the DataFrame, best practice
    dff = dff[dff['bedrooms'] == bedroomFilter]  # Selects the number of bedrooms

    color_by = 'price'  # Setting the scale for teh price
    color_upper = dff['price'].quantile(.05)  # Excluding the bottom .05%
    color_lower = dff['price'].quantile(.95)  # Excluding the top .05%

    fig = px.scatter_mapbox(dff, lat="lat", lon="lon",
                            animation_frame="decade",
                            # animation_group="country",
                            hover_name="price", hover_data=['price'],
                            color_continuous_scale=px.colors.sequential.Viridis,
                            color=color_by, range_color=[color_lower, color_upper],
                            opacity=0.4,
                            zoom=8.5, height=600)
    fig.update_layout(mapbox_style="open-street-map")
    fig.update_geos(fitbounds="locations")

    fig.update_layout(
        title={
            'text': "Housing Price",
            'y': 1,
            'x': 0.5,
            'xanchor': 'center',
            'yanchor': 'auto'},
        legend=dict(
            title="Home Price",
            orientation="h",
            yanchor="top")
)
    return fig


if __name__ == '__main__':
    app.run_server(debug=False, host='127.0.0.1')

Data:

           price  sqft_living  grade  bedrooms  decade
0      13.180632     7.501082      7         5    1900
1      13.515081     8.384804      8         3    1900
2      13.345507     7.306531      7         2    1900
3      13.296317     7.319865      7         2    1900
4      13.091904     7.244228      7         4    1900
          ...          ...    ...       ...     ...
21603  14.253765     8.169053      9         3    2010
21604  12.345835     6.946976      9         3    2010
21605  13.233905     7.408531      8         2    2010
21606  12.873774     7.365180      8         3    2010
21607  14.346139     8.281471     11         4    2010

Solution

  • The main problem is the syntax of your second callback:

    @app.callback(
        [
            Output(component_id="bedrooms-selector", component_property="figure"),
            Input(component_id="Number-Of-Bedrooms", component_property="value"),
        ]
    )
    def update_output_div1(bedroomFilter):
        # ...
        return fig
    

    This gives:

    dash.exceptions.InvalidCallbackReturnValue: The callback ..bedrooms-selector.figure.. is a multi-output. Expected the output type to be a list or tuple but got Figure...

    Remove the list surrounding both the Output and Input.

    So do something like this instead:

    @app.callback(
        Output(component_id="bedrooms-selector", component_property="figure"),
        Input(component_id="Number-Of-Bedrooms", component_property="value"),
    )
    def update_output_div1(bedroomFilter):
        # ...
        return fig
    

    If you then run the app and select a value from the second dropdown, the map will show up.