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

How do I change the data that loads based on user input in a plotly dashboard?


I have this dashboard-

import datetime
import dash
from dash import dcc, html
import plotly
from dash.dependencies import Input, Output
from pyorbital.orbital import Orbital
satellite = Orbital('TERRA')

app = dash.Dash(__name__)
app.layout = html.Div(
    html.Div([
        html.H4('TERRA Satellite Live Feed'),
        html.Div(id='live-update-text'),
        dcc.Graph(id='live-update-graph'),
        dcc.Interval(
            id='interval-component',
            interval=1*1000, # in milliseconds
            n_intervals=0
        )
    ])
)

@app.callback(Output('live-update-text', 'children'),
              Input('interval-component', 'n_intervals'))
def update_metrics(n):
    lon, lat, alt = satellite.get_lonlatalt(datetime.datetime.now())
    style = {'padding': '5px', 'fontSize': '16px'}
    return [
        html.Span('Longitude: {0:.2f}'.format(lon), style=style),
        html.Span('Latitude: {0:.2f}'.format(lat), style=style),
        html.Span('Altitude: {0:0.2f}'.format(alt), style=style)
    ]

# Multiple components can update everytime interval gets fired.
@app.callback(Output('live-update-graph', 'figure'),
              Input('interval-component', 'n_intervals'))
def update_graph_live(n):
    satellite = Orbital('TERRA')
    data = {
        'time': [],
        'Latitude': [],
        'Longitude': [],
        'Altitude': []
    }

    # Collect some data
    for i in range(180):
        time = datetime.datetime.now() - datetime.timedelta(seconds=i*20)
        lon, lat, alt = satellite.get_lonlatalt(
            time
        )
        data['Longitude'].append(lon)
        data['Latitude'].append(lat)
        data['Altitude'].append(alt)
        data['time'].append(time)

    # Create the graph with subplots
    fig = plotly.tools.make_subplots(rows=2, cols=1, vertical_spacing=0.2)
    fig['layout']['margin'] = {
        'l': 30, 'r': 10, 'b': 30, 't': 10
    }
    fig['layout']['legend'] = {'x': 0, 'y': 1, 'xanchor': 'left'}

    fig.append_trace({
        'x': data['time'],
        'y': data['Altitude'],
        'name': 'Altitude',
        'mode': 'lines+markers',
        'type': 'scatter'
    }, 1, 1)
    fig.append_trace({
        'x': data['Longitude'],
        'y': data['Latitude'],
        'text': data['time'],
        'name': 'Longitude vs Latitude',
        'mode': 'lines+markers',
        'type': 'scatter'
    }, 2, 1)
    fig.update_layout(
        xaxis=dict(
            rangeselector=dict(
            buttons=list([
                dict(count=1,
                    label="1d",
                    step="day",
                    stepmode="backward")
            ])
            )
            type="date"
        ))
    
    # Add dropdown
    fig.update_layout(
        updatemenus=[
            dict(
                type = "buttons",
                direction = "left",
                buttons=list([
                    dict(
                        args=["type", "surface"],
                        label="Terra",
                        method="restyle"
                    ),
                    dict(
                        args=["type", "heatmap"],
                        label="Earth",
                        method="restyle"
                    )
                ]),
                pad={"r": 10, "t": 10},
                showactive=True,
                x=0.11,
                xanchor="left",
                y=1.1,
                yanchor="top"
            ),
        ]
    )

    return fig


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

I want to add a button that changes which dataset loads. Right now, the dataset that loads is called 'TERRA' and I want the user to be able to click a button and then the dashboard loads a different dataset. Ideally, there would be one button called terra that loads the terra data and another called earth that loads the earth data.

In the first case, the dashboard runs Orbital('TERRA') and in the second, the dashboard runs Orbital('Earth'). I was able to add the button onto the dashboard and I'm having a hard time passing the variable from the button to the Orbital() function.


Solution

  • You want to define a button or dropdown with your dataset options, and then pass its id to an extra input for update_graph_live, where it will switch dataset based on the dropdown.

    I suggest RadioItems since you only want one option to work.

    Dropdown examples show how to use them as input to a function

    Close-to-minimal example Based on my implementation of a sign viewer

    import dash
    import pandas as pd
    import plotly.express as px
    from dash import dcc
    from dash import html
    from dash.dependencies import Input, Output
    
    app = dash.Dash(__name__)
    server = app.server
    
    datasets_public = [
        {'label': '21K Nether (2019)', 'value': '21k_nether.csv'},
        {'label': '21K End (2019)', 'value': '21k_end.csv'},
        {'label': '20K Overworld (2017)', 'value': '20k_overworld.csv'},
    ]
    
    app.layout = html.Div(id="parentdiv", children=[
        html.Div(children=[
            html.Div(dcc.RadioItems(id='dflist', options=datasets_public, value='20k_overworld.csv')),
            html.Div([dcc.Graph(id='scatter-plot')])])])
    
    
    @app.callback(
        Output("scatter-plot", "figure"),
        Input("dflist", "value"))
    def update_bar_chart(dflist):
        df = pd.read_csv("data/" + dflist)
        df = df.fillna("")
        fig = px.scatter(df, x="x", y="z", hover_data=['x', 'y', 'z', 'line'])
        return fig
    
    
    if __name__ == '__main__':
        app.run_server(debug=True)