Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
538 views
in Technique[技术] by (71.8m points)

python - Plotly: How to toggle traces with a button similar to clicking them in legend?

I'm using python and creating standalone html files with interactive plots (no Dash). I have been able to build a plotly plot with buttons that can toggle the visibility of traces in the plot. However, this functionality removes the traces from the legend as well. What I would like is to be able to keep the functionality of the legend (click a single trace to toggle visibility) but also have a set of buttons that extends that functionality to a group of traces that I define.

The goal is to be able toggle everything (or a select group) to invisible but add individual items from that group back to visible as needed.

Below is an example (using modified code from this answer by vestland) to show what I am currently attempting.

import numpy as np
import pandas as pd
import plotly.graph_objects as go
import datetime

# mimic OP's datasample

NPERIODS = 200

np.random.seed(123)
df = pd.DataFrame(np.random.randint(-10, 12, size=(NPERIODS, 4)),
                  columns=list('ABCD'))
datelist = pd.date_range(datetime.datetime(2020, 1, 1).strftime('%Y-%m-%d'),
                         periods=NPERIODS).tolist()
df['dates'] = datelist 
df = df.set_index(['dates'])
df.index = pd.to_datetime(df.index)
df.iloc[0] = 0
df = df.cumsum()

# set up multiple traces
traces = []
buttons = []
for col in df.columns:
    traces.append(go.Scatter(x=df.index,
                            y=df[col],
                            visible=True,
                            name=col)
                )
    buttons.append(dict(method='update',
                        label=col,
                        visible=True,
                        args=[{'visible':[x == col for x in df.columns]}],
                        args2=[{'visible':[x != col for x in df.columns]}]
                        )
                )

# create the layout 
layout = go.Layout(
    updatemenus=[
        dict(
            type='buttons',
            direction='right',
            x=0.7,
            y=1.3,
            showactive=True,
            buttons=buttons
        )
    ],
    title=dict(text='Toggle Traces',x=0.5),
    showlegend=True
)

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

# add dropdown menus to the figure
fig.show()

That example does not work how I would like. Below is a screenshot of what it looks like at first.

stockplot

The problem is that if I use one of those buttons, it does hide all the other traces but it also removes them from the legend so they can't be toggled back to visible.

onlyBvisible

So my question becomes, is there a different value in the args list/dictionary that can be given for the functionality to match that of simply clicking a trace in the legend?

Sort of related, is there some way to get the current state of visibility for each trace?

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

In order to make it possible to toggle any trace on and off without affecting the others, it seems you'll have to include one updatemenu per button. There might be other ways to do it, but the code snippet below will produce the following plot:

Plot 1 - At launch all are selected

enter image description here

Plot 2 - C and D toggled off

enter image description here

Plot 3 - All off

enter image description here

Plot 4 - All on

enter image description here

Complete code:

import numpy as np
import pandas as pd
import plotly.graph_objects as go
import datetime
import plotly.express as px

periods = 200
cols = list('ABCD')

np.random.seed(123)
df = pd.DataFrame(np.random.randint(-10, 12, size=(periods, len(cols))),
                  columns=cols)
datelist = pd.date_range(datetime.datetime(2020, 1, 1).strftime('%Y-%m-%d'),
                         periods=periods).tolist()
df['dates'] = datelist 
df = df.set_index(['dates'])
df.index = pd.to_datetime(df.index)
df.iloc[0] = 0
df = df.cumsum()

# # plotly
fig = go.Figure()
colors = px.colors.qualitative.Plotly

# set up multiple traces
for col in df.columns:
    fig.add_trace(go.Scatter(x=df.index,
                             y=df[col],
                             name  = col,
                             visible=True
                            )
                 )

um = [ {} for _ in range(len(df.columns)) ]
buttons = []
menuadjustment = 0.15

buttonX = -0.1
buttonY = 1 + menuadjustment
for i, col in enumerate(df.columns):
    button = dict(method='restyle',
                  label=col,
                  visible=True,
                  args=[{'visible':True,
                         'line.color' : colors[i]}, [i]],
                  args2 = [{'visible': False,
                            'line.color' : colors[i]}, [i]],
                 )
    
    # adjust some button features
    buttonY = buttonY-menuadjustment
    um[i]['buttons'] = [button]
    um[i]['showactive'] = False
    um[i]['y'] = buttonY
    um[i]['x'] = buttonX

# add a button to toggle all traces on and off
button2 = dict(method='restyle',
               label='All',
               visible=True,
               args=[{'visible':True}],
               args2 = [{'visible': False}],
               )
# assign button2 to an updatemenu and make some adjustments
um.append(dict())
um[i+1]['buttons'] = [button2]
um[i+1]['showactive'] = True
um[i+1]['y']=buttonY - menuadjustment
um[i+1]['x'] = buttonX
    
# add dropdown menus to the figure
fig.update_layout(showlegend=True, updatemenus=um)

# adjust button type
for m in fig.layout.updatemenus:
    m['type'] = 'buttons'

f = fig.full_figure_for_development(warn=False)
fig.show()

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...