Multiple traces with a single slider in plotly


#1

I’m trying to create a single slider which controls two traces using Python 3 and Plotly. I am using Spyder although I’ve also tried the same code modified for Jupyter without success.

This is a sample of code:

import numpy as np
#import pandas as pd
#import matplotlib.pyplot as plt

import plotly as py
import plotly.graph_objs as go
#import ipywidgets as widgets

py.offline.init_notebook_mode(connected=True) 



'''
Plot traces
'''
trace1 = [dict(
        type = 'scatter',
        visible = False,
        name='Demand',
        x = np.arange(0,365,1),
        y = np.sin(step*np.arange(0,365,1))) for step in range(0,365,1)]


trace2 = [dict(
        type = 'scatter',
        visible = False,
        name='Demand',
        x = np.arange(0,365,1),
        y = np.sin(step*np.arange(0,365,1)) + 5) for step in range(0,365,1)]


#data = trace2

data = [list(a) for a in zip(trace1, trace2)]


steps = []
for i in range(len(data)):
    step = dict(
        method = 'restyle',  
        args = ['visible', [False] * len(data)],
        label = ""
    )
    step['args'][1][i] = True # Toggle i'th trace to "visible"
    steps.append(step)

sliders = [dict(
    active = 10,#What's happening here?
    currentvalue = {"prefix": "Day: "}, #????
    #pad = {"t": 50},
    steps = steps
)]

#Add styling
layout = go.Layout(sliders=sliders,
    title='Sample slider plot',
    xaxis=dict(
        title=‘Test’,
        titlefont=dict(
            family='Courier New, monospace',
            size=18,
            color='#7f7f7f'
        )
    ),
    yaxis=dict(
        title=‘Unit’,
        titlefont=dict(
            family='Courier New, monospace',
            size=18,
            color='#7f7f7f'
        )
    )
)


fig = go.Figure(data=data, layout=layout)

py.offline.plot(fig, filename='Test3')

If I plot either of the traces individually i.e.

data = trace1

it works. However, once I combine trace1 and trace2 i.e.

data = [list(a) for a in zip(trace1, trace2)]

I get the following error:

The 'data' property is a tuple of trace instances
    that may be specified as:
      - A list or tuple of trace instances
        (e.g. [Scatter(...), Bar(...)])
      - A list or tuple of dicts of string/value properties where:
        - The 'type' property specifies the trace type
            One of: ['area', 'bar', 'barpolar', 'box',
                     'candlestick', 'carpet', 'choropleth', 'cone',
                     'contour', 'contourcarpet', 'heatmap',
                     'heatmapgl', 'histogram', 'histogram2d',
                     'histogram2dcontour', 'mesh3d', 'ohlc',
                     'parcats', 'parcoords', 'pie', 'pointcloud',
                     'sankey', 'scatter', 'scatter3d',
                     'scattercarpet', 'scattergeo', 'scattergl',
                     'scattermapbox', 'scatterpolar',
                     'scatterpolargl', 'scatterternary', 'splom',
                     'streamtube', 'surface', 'table', 'violin']

        - All remaining properties are passed to the constructor of
          the specified trace type

        (e.g. [{'type': 'scatter', ...}, {'type': 'bar, ...}])

Checking type(trace1) shows that each trace is a list of 365 dict objects, which plotly can handle. However, once I combine the traces into the ‘data’ object as described above, I get a list of lists, which it cannot.

So my question is: how do I combine trace1 and trace2 into an object that has the right length for the steps of the slider (i.e. 365) and which plotly can deal with - such as a list of dicts or trace instances, rather than a list of lists?

I have looked at plotly - multiple traces using a shared slider variable and also Plot.ly. Using slider control with multiple plots. Haven’t had success unfortunately. The first link works if the trace objects are dicts, but not if you have to enclose them in square brackets to make a list for the ‘for’ loop to work. The second link just adds the trace1 + trace2 to create an object with index length 730 - that is, it joins the two traces successively instead of displaying them together.

Thanks very much in advance for help!


#2

Hi @sumins,

You will need to append all of your traces into a single (730 element) list. The trick is to make the arguments to your slider aware of the ordering of traces. You want the first step to enable traces 0 and 365, the second to enable 1 and 366, etc. Here’s an example with three slider steps and 6 total traces

import plotly.graph_objs as go
from plotly.offline import init_notebook_mode, iplot
init_notebook_mode()

num_steps = 3
trace_list1 = [
    go.Scatter(y=[1, 2, 3], visible=True, line={'color': 'red'}),
    go.Scatter(y=[3, 1, 1.5], visible=False, line={'color': 'red'}),
    go.Scatter(y=[2, 2, 1], visible=False, line={'color': 'red'})
]

trace_list2 = [
    go.Scatter(y=[1, 3, 2], visible=True, line={'color': 'blue'}),
    go.Scatter(y=[1.5, 2, 2.5], visible=False, line={'color': 'blue'}),
    go.Scatter(y=[2.5, 1.2, 2.9], visible=False, line={'color': 'blue'})
]

fig = go.Figure(data=trace_list1+trace_list2)

steps = []
for i in range(num_steps):
    # Hide all traces
    step = dict(
        method = 'restyle',  
        args = ['visible', [False] * len(fig.data)],
    )
    # Enable the two traces we want to see
    step['args'][1][i] = True
    step['args'][1][i+num_steps] = True
    
    # Add step to step list
    steps.append(step)

sliders = [dict(
    steps = steps,
)]

fig.layout.sliders = sliders

iplot(fig, show_link=False)

Hope that helps!
-Jon


#3

Hi Jon,
This worked like a dream. I was really struggling. Thank you!

S