Search code examples
pythonplotly-dashplotly-python

Plotly Dash: How to change image on button click


I had unknowingly added in the answer section of the following thread as I was trying to use that example to complete my usecase:

Plotly: How to display graph after clicking a button?

I am looking for a very similar solution and I tried suggestion 2 (from the above thread), because I want to display images depending upon user's choice of clicking button. I have image_1, image_2, image_3 and image_4 under one directory. I have tried so far like below:

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go
from dash.dependencies import Input, Output
import numpy as np
from plotly.subplots import make_subplots
import plotly.express as px

pd.options.plotting.backend = "plotly"
from datetime import datetime

palette = px.colors.qualitative.Plotly

# sample data
df = pd.DataFrame({'Prices': [1, 10, 7, 5, np.nan, np.nan, np.nan],
                   'Predicted_prices': [np.nan, np.nan, np.nan, 5, 8, 6, 9]})

# app setup
app = dash.Dash()

# controls
controls = dcc.Card(
    [dcc.FormGroup(
        [
            dcc.Label("Options"),
            dcc.RadioItems(id="display_figure",
                           options=[{'label': 'None', 'value': 'Nope'},
                                    {'label': 'Figure 1', 'value': 'Figure1'},
                                    {'label': 'Figure 2', 'value': 'Figure2'},
                                    {'label': 'Figure 3', 'value': 'Figure3'}
                                    ],
                           value='Nope',
                           labelStyle={'display': 'inline-block', 'width': '10em', 'line-height': '0.5em'}
                           )
        ],
    ),
        dcc.FormGroup(
            [dcc.Label(""), ]
        ),
    ],
    body=True,
    style={'font-size': 'large'})

app.layout = dcc.Container(
    [
        html.H1("Button for predictions"),
        html.Hr(),
        dcc.Row([
            dcc.Col([controls], xs=4),
            dcc.Col([
                dcc.Row([
                    dcc.Col(dcc.Graph(id="predictions")),
                ])
            ]),
        ]),
        html.Br(),
        dcc.Row([

        ]),
    ],
    fluid=True,
)


@app.callback(
    Output("predictions", "figure"),
    [Input("display_figure", "value"),

     ],
)
def make_graph(display_figure):
    # main trace
    y = 'Prices'
    y2 = 'Predicted_prices'
    #     print(display_figure)
    if 'Nope' in display_figure:
        fig = go.Figure()
        fig.update_layout(plot_bgcolor='rgba(0,0,0,0)', paper_bgcolor='rgba(0,0,0,0)',
                          yaxis=dict(showgrid=False, zeroline=False, tickfont=dict(color='rgba(0,0,0,0)')),
                          xaxis=dict(showgrid=False, zeroline=False, tickfont=dict(color='rgba(0,0,0,0)')))
        return fig

    if 'Figure1' in display_figure:
        fig = go.Figure(go.Scatter(name=y, x=df.index, y=df[y], mode='lines'))
        fig.add_traces(go.Scatter(name=y, x=df.index, y=df[y2], mode='lines'))
        fig.update_layout(template='plotly_dark')

    # prediction trace
    if 'Figure2' in display_figure:
        fig = go.Figure((go.Scatter(name=y, x=df.index, y=df[y], mode='lines')))
        fig.add_traces(go.Scatter(name=y, x=df.index, y=df[y2], mode='lines'))
        fig.update_layout(template='seaborn')

    if 'Figure3' in display_figure:
        fig = go.Figure((go.Scatter(name=y, x=df.index, y=df[y], mode='lines')))
        fig.add_traces(go.Scatter(name=y, x=df.index, y=df[y2], mode='lines'))
        fig.update_layout(template='plotly_white')

    # Aesthetics
    fig.update_layout(margin={'t': 30, 'b': 0, 'r': 0, 'l': 0, 'pad': 0})
    fig.update_layout(hovermode='x')
    fig.update_layout(showlegend=True, legend=dict(x=1, y=0.85))
    fig.update_layout(uirevision='constant')
    fig.update_layout(title="Prices and predictions")

    return (fig)


app.run_server(debug=True)

but I got the following error and couldn't proceed further.

line 24, in <module>
    controls = dcc.Card(
AttributeError: module 'dash_core_components' has no attribute 'Card'

Solution

  • The problem is that the different components used in your code are defined in different libraries (dash_core_components, dash_html_components and dash_bootstrap_components), while you are trying to get all the components from the same library (dash_core_components). For instance, dcc.Card should be replaced with dbc.Card where dbc stands for dash_bootstrap_components, see the code below.

    import pandas as pd
    import numpy as np
    import dash
    import dash_core_components as dcc
    import dash_html_components as html
    import dash_bootstrap_components as dbc
    from dash.dependencies import Input, Output
    import plotly.graph_objs as go
    
    # sample data
    df = pd.DataFrame({
        'Prices': [1, 10, 7, 5, np.nan, np.nan, np.nan],
        'Predicted_prices': [np.nan, np.nan, np.nan, 5, 8, 6, 9]
    })
    
    # app setup
    app = dash.Dash()
    
    # controls
    controls = dbc.Card([
    
            dbc.FormGroup([
    
                html.Label('Options'),
    
                dcc.RadioItems(
                    id='display_figure',
                    options=[
                        {'label': 'None', 'value': 'None'},
                        {'label': 'Figure 1', 'value': 'Figure1'},
                        {'label': 'Figure 2', 'value': 'Figure2'},
                        {'label': 'Figure 3', 'value': 'Figure3'}
                    ],
                    value='None',
                    labelStyle={
                        'display': 'inline-block',
                        'width': '10em',
                        'line-height': '0.5em'
                    }
                )
    
            ]),
    
        ],
    
        body=True,
        style={'font-size': 'large'}
    
    )
    
    app.layout = dbc.Container([
    
            html.H1('Button for predictions'),
    
            html.Hr(),
    
            dbc.Row([
    
                dbc.Col([controls], xs=4),
    
                dbc.Col([
                    dbc.Row([
                        dbc.Col(dcc.Graph(id='predictions')),
                    ])
    
                ]),
            ]),
    
        ],
    
        fluid=True,
    
    )
    
    
    @app.callback(
        Output('predictions', 'figure'),
        [Input('display_figure', 'value')],
    )
    def make_graph(display_figure):
    
        y = 'Prices'
        y2 = 'Predicted_prices'
    
        if 'Figure1' in display_figure:
            fig = go.Figure(go.Scatter(name=y, x=df.index, y=df[y], mode='lines'))
            fig.add_trace(go.Scatter(name=y, x=df.index, y=df[y2], mode='lines'))
            fig.update_layout(template='plotly_dark')
            fig.update_layout(title='Prices and predictions')
    
        elif 'Figure2' in display_figure:
            fig = go.Figure((go.Scatter(name=y, x=df.index, y=df[y], mode='lines')))
            fig.add_trace(go.Scatter(name=y, x=df.index, y=df[y2], mode='lines'))
            fig.update_layout(template='seaborn')
            fig.update_layout(title='Prices and predictions')
    
        elif 'Figure3' in display_figure:
            fig = go.Figure((go.Scatter(name=y, x=df.index, y=df[y], mode='lines')))
            fig.add_trace(go.Scatter(name=y, x=df.index, y=df[y2], mode='lines'))
            fig.update_layout(template='plotly_white')
            fig.update_layout(title='Prices and predictions')
    
        else:
            fig = go.Figure()
            fig.update_layout(
                plot_bgcolor='rgba(0,0,0,0)',
                paper_bgcolor='rgba(0,0,0,0)',
                yaxis=dict(showgrid=False, zeroline=False, tickfont=dict(color='rgba(0,0,0,0)')),
                xaxis=dict(showgrid=False, zeroline=False, tickfont=dict(color='rgba(0,0,0,0)'))
            )
    
        fig.update_layout(margin={'t': 30, 'b': 0, 'r': 0, 'l': 0, 'pad': 0})
        fig.update_layout(hovermode='x')
        fig.update_layout(showlegend=True, legend=dict(x=1, y=0.85))
        fig.update_layout(uirevision='constant')
    
        return fig
    
    if __name__ == '__main__':
        app.run_server(host='127.0.0.1', debug=True)
    

    Once you have fixed this issue, you can update the app to display the static PNG files instead of the interactive graphs as shown below. Note that the dcc.Graph component has been replaced by the html.Img component, and that the figure property has been replaced by the src property.

    import dash
    import dash_core_components as dcc
    import dash_html_components as html
    import dash_bootstrap_components as dbc
    from dash.dependencies import Input, Output
    
    # app setup
    app = dash.Dash()
    
    # controls
    controls = dbc.Card([
    
            dbc.FormGroup([
    
                html.Label('Options'),
    
                dcc.RadioItems(
                    id='display_figure',
                    options=[
                        {'label': 'None', 'value': 'None'},
                        {'label': 'Figure 1', 'value': 'Figure1'},
                        {'label': 'Figure 2', 'value': 'Figure2'},
                        {'label': 'Figure 3', 'value': 'Figure3'}
                    ],
                    value='None',
                    labelStyle={
                        'display': 'inline-block',
                        'width': '10em',
                        'line-height': '0.5em'
                    }
                )
    
            ]),
    
        ],
    
        body=True,
        style={'font-size': 'large'}
    
    )
    
    app.layout = dbc.Container([
    
            html.H1('Button for predictions'),
    
            html.Hr(),
    
            dbc.Row([
    
                dbc.Col([controls], xs=4),
    
                dbc.Col([
                    dbc.Row([
                        dbc.Col(html.Img(id='predictions')),
                    ])
    
                ]),
            ]),
    
        ],
    
        fluid=True,
    
    )
    
    @app.callback(
        Output('predictions', 'src'),
        [Input('display_figure', 'value')],
    )
    def make_graph(display_figure):
    
        if 'Figure1' in display_figure:
            return app.get_asset_url('image_1.png')
    
        elif 'Figure2' in display_figure:
            return app.get_asset_url('image_2.png')
    
        elif 'Figure3' in display_figure:
            return app.get_asset_url('image_3.png')
    
        else:
            return None
    
    if __name__ == '__main__':
        app.run_server(host='127.0.0.1', debug=True)
    

    Note also that the PNG files will need to be saved in the assets folder, as shown below:

    enter image description here