Dynamically adding and removing traces to/from multiple axes

Hello, I am a beginner with Dash but I’m loving it, so thank you for providing this awesome tool.

I am having difficulty with dynamically adding and removing traces to multiple axes on a scatter plot and I would greatly appreciate help.

In the code below, I would like to be able to add and remove options in the dropdown list (without updating the graph) and then re-plot the chosen traces by clicking the button. I find this approach works repeatedly and indefinitely only if all the traces are being plotted to a single y axis.

I would like to be able to plot any combination of the dropdown traces and have them assigned to separate axes and then repeat the process for alternate combinations of the same traces. However, I can’t figure out why the callback returning the figure in the code below breaks after 1 or 2 combinations have been plotted. Depending on the sequence of selections that the user selects, the callback will quickly fail to plot the data with the updated trace list within a few presses. Usually one of the selected channels will fail to be added to the plot and sometimes the chart itself seems to disappear altogether.

Ideally, I would like all 4 axes to persist from startup and even when no data are being graphed. Also, I do not want to have to pre-load hidden data upon initialisation. This is a mock up of a more detailed app I am making, in which ‘a’, ‘b’, ‘c’, ‘d’ are pandas series with over 1M pts each. In that app, it is nice to think carefully about which channels are required before hitting the button to plot them.

Am I doing something obviously wrong here in the axis assignment or something? Any help or alternatives gratefully received.

Thanks!

import dash
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
from dash.dependencies import Input, Output, State
import plotly.graph_objs as go

app = dash.Dash()

optionsList=[{'label': 'a', 'value': 'a'}, {'label': 'b', 'value': 'b'}, {'label': 'c', 'value': 'c'}, {'label': 'd', 'value': 'd'}]

firstVal=''

sales = [{'timestamp':'2008', 'a': 12, 'b': 4, 'c': 2, 'd': 6},
         {'timestamp':'2009', 'a': 5, 'b': 15, 'c': 3, 'd': 7},
         {'timestamp':'2010', 'a': 3,  'b': 6,  'c': 4, 'd': 13},
         {'timestamp':'2011', 'a': 8,  'b': 5,  'c': 1, 'd': 5}]
df = pd.DataFrame(sales)
df = df.set_index(pd.DatetimeIndex(df['timestamp']))
print(df)

app.layout = html.Div([
    html.Div([html.Label('SELECT OPTION'),
              dcc.Dropdown(id='list-dropdown',options=optionsList,value=firstVal,multi=True)],
             style={'padding':10, 'width':'15%'}
            ),
    html.Div([html.Button(id='plot-button', n_clicks=0, children='PLOT SELECTED CHANNEL(S)')], style={'padding':10}),
    html.Div(id='text-area'),
    html.Hr(),
    html.Div(dcc.Graph(
        id='time-series',
        figure={
            'data': [],
            'layout':{}}
    ))
])

# Function to return the correct axis for the chosen trace.
def lookupY(val):
    y_result=''
    try:
        if 'a' in val.lower():
            y_result='y'
        elif 'b' in val.lower():
            y_result = 'y2'
        elif 'c' in val.lower():
            y_result = 'y3'
        elif 'd' in val.lower():
            y_result = 'y4'
    except:
            y_result = 'y'
    return y_result

# This callback updates the figure when the 'PLOT SELECTED CHANNELS' button is pressed.
@app.callback(
    Output(component_id='time-series', component_property='figure'),
    [Input(component_id='plot-button', component_property='n_clicks')],
    [State(component_id='list-dropdown', component_property='value')]
)
def callback_c(numClicks, listValue):
    print('callback_c')
    print(listValue)
    print([lookupY(val) for val in listValue])
    dictFig = {'data': [go.Scattergl(x=df.index, y=df[val], opacity=0.4, name=val, yaxis=lookupY(val)) for val in listValue],
     'layout': go.Layout(
         xaxis={'title': 'x', 'domain': [0.14, 1]},
         yaxis={'title': 'y', 'position': 0, 'visible': True},  # 'y' for go.Scattergl
         yaxis2={'title': 'y2', 'position': 0.04, 'visible': True},  # 'y2' for go.Scattergl
         yaxis3={'title': 'y3', 'position': 0.08, 'visible': True},  # 'y3' for go.Scattergl
         yaxis4={'title': 'y4', 'position': 0.12, 'visible': True},  # 'y4' for go.Scattergl
        )}
    return dictFig

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