pythonplotlygraphing

Appending Traces to the same "step" using Plotly's sliders in Python


I want to create a figure that changes between 20 different graphs using Plotly's sliders. I am modelling a moving average, where the slider changes the number of days used in the aggregation of data. The second piece of data I want to visualize, is a savgol filter of that moving average. I am trying to graph the two separate arrays of data on each graph, but as of now, it currently appends the the arrays on separate steps. I was hoping for 20 steps of graphs with 2 different functions, rather than 40 steps with just one function on each.

My mock data is:

# Imports
import pandas as pd
import numpy as np
import plotly
import plotly.express as px
import plotly.graph_objs as go
import sys
import random

Year = 2000
Date = pd.Series(pd.date_range(str(Year) + "-01-01", str(Year) + "-12-31", freq="D"))
Day = Date.diff().astype("timedelta64[D]").fillna(1).cumsum()
dftest = pd.DataFrame({"Day": Day})
dftest = dftest.set_index(Date).reset_index()
dftest = dftest.rename(columns={"index": "Date"})
dftest["Qty"] = [random.randint(1, 10000) for k in dftest.index]
TrendDF = dftest

My code is:

def TrendMAn(n):
TrendDF["MAn"] = TrendDF["Qty"].rolling(window=n).mean().set_axis(TrendDF.index)
return TrendDF["MAn"]

fig = go.Figure()

for step in range(1, 20):
fig.add_trace(
    go.Scatter(
        line=dict(width=2),
        x=TrendDF["Date"],
        y=TrendMAn(step),
        mode="lines",
        visible=False,
    )
) 
fig.add_trace(
    go.Scatter(
        line=dict(width=2),
        x=TrendDF["Date"],
        y=savgol_filter(TrendMAn(step), 51, 3),
        mode="lines",
        visible=False,
    )
)

fig.data[14].visible = True

steps = []
for i in range(len(fig.data)):
step = dict(
    method="update",
    args=[
        {"visible": [False] * len(fig.data)},
        {"title": "Slider switched to step: " + str(i)},
    ],  # layout attribute
)
step["args"][0]["visible"][i] = True  # Toggle i'th trace to "visible"
steps.append(step)

sliders = [
dict(
    active=10,
    currentvalue={"prefix": "Rolling Average n: "},
    pad={"t": 50},
    steps=steps,
)
]
fig.update_layout(sliders=sliders)

fig.show()

The results gives me 40 steps instead of 20, as the "for loop" is creating 40 traces which makes sense. But, I want the two traces I am calling in the "for loop" on the same step.


Solution

  • Since the filter function is not presented, I reused the moving average function and changed the parameters to create two different graphs. fig.data is lined up with the first graph, the second graph, the graph at the next index after the first, and the graph at the second index, so you can show or hide the Add one line of control for display and hide.

    import pandas as pd
    import numpy as np
    import plotly
    import plotly.express as px
    import plotly.graph_objs as go
    import sys
    import random
    
    random.seed(20231129)
    
    Year = 2000
    Date = pd.Series(pd.date_range(str(Year) + "-01-01", str(Year) + "-12-31", freq="D"))
    Day = Date.diff().astype("timedelta64[D]").fillna(1).cumsum()
    dftest = pd.DataFrame({"Day": Day})
    dftest = dftest.set_index(Date).reset_index()
    dftest = dftest.rename(columns={"index": "Date"})
    dftest["Qty"] = [random.randint(1, 10000) for k in dftest.index]
    TrendDF = dftest
    
    def TrendMAn(n):
        TrendDF["MAn"] = TrendDF["Qty"].rolling(window=n).mean().set_axis(TrendDF.index)
        return TrendDF["MAn"]
    
    fig = go.Figure()
    
    for step in range(1, 20):
        fig.add_trace(
            go.Scatter(
                line=dict(width=2),
                x=TrendDF["Date"],
                y=TrendMAn(step),
                mode="lines",
                visible=False,
            )
        ) 
        fig.add_trace(
            go.Scatter(
                line=dict(width=2),
                x=TrendDF["Date"],
                #y=savgol_filter(TrendMAn(step), 51, 3),
                y=TrendMAn(step+30),
                mode="lines",
                visible=False,
            )
        )
    
    fig.data[14].visible = True
    fig.data[28].visible = True
    
    steps = []
    for i in range(1,20):
        step = dict(
            method="update",
            args=[
                {"visible": [False] * len(fig.data)},
                {"title": "Slider switched to step: " + str(i)},
            ],  # layout attribute
        )
        step["args"][0]["visible"][i] = True  # Toggle i'th trace to "visible"
        step["args"][0]["visible"][i+1] = True # Update
        steps.append(step)
        
    sliders = [
    dict(
        active=10,
        currentvalue={"prefix": "Rolling Average n: "},
        pad={"t": 50},
        steps=steps,
    )
    ]
    fig.update_layout(sliders=sliders)
    
    fig.show()
    

    enter image description here