I am trying to build a dropdown menu for plotly timeline plot. Here is my code:
import pandas as pd
import plotly.express as px
def main():
d = {
'T_ID': ['T1', 'T2', 'T3', 'T1'],
'TYPE': ['Type1', 'Type2', 'Type3', 'Type2'],
'SYS_START_TIME': ['2021-06-20 06:05', '2021-06-23 15:13', '2021-06-27 13:01', '2021-06-29 14:02'],
'SYS_END_TIME': ['2021-06-20 11:39', '2021-06-23 15:25', '2021-06-27 13:09', '2021-06-29 15:09'],
}
df = pd.DataFrame(data=d)
df['SYS_START_TIME'] = pd.to_datetime(df['SYS_START_TIME'])
df['SYS_END_TIME'] = pd.to_datetime(df['SYS_END_TIME'])
labels = df["T_ID"].unique()
buttonsLabels = [dict(label="All",
method="update",
visible=True,
args=[
{'x_start': [df.SYS_START_TIME]},
{'x_end': [df.SYS_END_TIME]},
{'y': [df.T_ID]},
]
)]
for label in labels:
buttonsLabels.append(dict(label=label,
method="update",
visible=True,
args=[
{'x_start': [df.loc[df.T_ID == label, "SYS_START_TIME"]]},
{'x_end': [df.loc[df.T_ID == label, "SYS_END_TIME"]]},
{'y': [df.loc[df.T_ID == label, "T_ID"]]},
]
))
fig = px.timeline(
df,
x_start="SYS_START_TIME",
x_end="SYS_END_TIME",
y="T_ID",
color="TYPE",
hover_data=['SYS_START_TIME', 'SYS_END_TIME', 'TYPE']
)
fig.update_layout(xaxis=dict(
title='Date',
tickformat='%Y-%m-%d %H:%M',
dtick="D",
showgrid=True
), updatemenus=[dict(buttons=buttonsLabels, showactive=True)])
fig.show()
if __name__ == '__main__':
main()
When I run the code, it shows all the data but when I press the buttons in the menu it doesn't update the plot, it still shows all the data. Can anyone help me?
I think the issue is that the args x_start
and x_end
that you're passing to your buttons aren't going to be recognized by plotly. If you look at fig.data
, you'll see that under the hood, px.timeline
creates go.Bar
traces for each T_ID
using the arguments "base"
and "x"
to draw the horizontal bars, so you would want to pass these same arguments when creating your buttons. However, I would suggest a simpler approach.
Since you are already plotting traces for each "Type"
of your data, you can instead create buttons that toggle only one trace to be visible depending on the type you select from the dropdown. One trick we can use is that customdata[0]
contains the information about the type – if the user selects "Type1"
from the dropdown, then the following list comprehension:
["Type1" in trace['customdata'][0][0] for trace in fig.data]
... will return [True, False, False]
and we can pass this information along to the "visible"
argument for the "Type1"
button.
To do this for all Type
buttons, we can try something like the following code (and you'll notice that I modified the xaxis range when each button is clicked, but you can change the padding
to whatever you think looks best):
import pandas as pd
import plotly.express as px
def main():
d = {
'T_ID': ['T1', 'T2', 'T3'],
'TYPE': ['Type1', 'Type2', 'Type3'],
'SYS_START_TIME': ['2021-06-20 06:05', '2021-06-23 15:13', '2021-06-27 13:01'],
'SYS_END_TIME': ['2021-06-20 11:39', '2021-06-23 15:25', '2021-06-27 13:09'],
}
df = pd.DataFrame(data=d)
df['SYS_START_TIME'] = pd.to_datetime(df['SYS_START_TIME'])
df['SYS_END_TIME'] = pd.to_datetime(df['SYS_END_TIME'])
labels = df["T_ID"].unique()
fig = px.timeline(
df,
x_start="SYS_START_TIME",
x_end="SYS_END_TIME",
y="T_ID",
color="TYPE",
hover_data=['SYS_START_TIME', 'SYS_END_TIME', 'TYPE']
)
start_ts = pd.to_datetime(min(df['SYS_START_TIME']))
end_ts = pd.to_datetime(max(df['SYS_END_TIME']))
xaxis_range = [start_ts-(end_ts - start_ts)/16, end_ts+(end_ts - start_ts)/2]
buttonsLabels = [dict(label="All",
method="update",
visible=True,
args=[
{'visible': [True]*len(fig.data)},
{'xaxis.range': xaxis_range}
]
)]
for type_name in df.TYPE.unique():
start_ts = pd.to_datetime(min(df[df['TYPE'] == type_name]['SYS_START_TIME']))
end_ts = pd.to_datetime(max(df[df['TYPE'] == type_name]['SYS_END_TIME']))
padding = pd.Timedelta("1d")
xaxis_range = [start_ts-padding, end_ts+padding]
visible_traces = [type_name in trace['customdata'][0][0] for trace in fig.data]
buttonsLabels.append(dict(label=type_name,
method="update",
visible=True,
args=[
{'visible': visible_traces},
{'xaxis.range': xaxis_range}
]
))
fig.update_layout(xaxis=dict(
title='Date',
tickformat='%Y-%m-%d %H:%M',
dtick="D",
showgrid=True
), updatemenus=[dict(buttons=buttonsLabels, showactive=True)])
return fig
if __name__ == '__main__':
fig = main()
fig.show()