I'm trying to embed multiple, selectable graphs in a single figure using Plotly, using a dropdown figure. I followed the dropdown example from Plotly, but they only show how to change graph characteristics (like visible, or type), not the underlying data. In my situation, I have a static X-axis and want to change the Y-values. Here's a minimal working example that can be run in a jupyter notebook:
import plotly
from plotly import graph_objs as go, offline as po, tools
po.init_notebook_mode()
import numpy as np
import json
x = list(np.linspace(-np.pi, np.pi, 100))
values_1 = list(np.sin(x))
values_2 = list(np.tan(x))
line = go.Scatter(
x=x,
y=values_1
)
updatemenus = [
{
'buttons': [
{
'method': 'restyle',
'label': 'Val 1',
'args': [
{'y': json.dumps(values_1)},
]
},
{
'method': 'restyle',
'label': 'Val 2',
'args': [
{'y': json.dumps(values_2)},
]
}
],
'direction': 'down',
'showactive': True,
}
]
layout = go.Layout(
updatemenus=updatemenus,
)
figure = go.Figure(data=[line], layout=layout)
po.iplot(figure)
However, while the approach seems to work like advertised for general graph attributes (like 'visible'), when I use 'y', it produces a straight line, where y goes from 0 to len(y), instead of the actual data I gave it. Here are images of the initial render, and then what happens when I select the dropdown item for the Tan(X) graph, then go back to the Sin(X):
How do I embed the data for multiple graphs into a single figure so that the user can select which one they want to view?
Updated answer using graph_objects:
As of version 4, you don't have to worry about offline versus online functionality. So drop the from plotly import graph_objs as go, offline as po and po.init_notebook_mode(), and just use import plotly.graph_objects as go. I've updated my original answer with a complete code snippet that shows the whole approach with multiple traces using plotly.graph_objects at the end. The solution to the question as it still stands will still be the same, namely:
'y' in updatemenus does not take a single list as an argument, but rather a list of lists like in 'y' = [values_1] where values_1 is a list in itself. So just replace your lines
{'y': json.dumps(values_1)}, and {'y': json.dumps(values_2)},
with
{'y': [values_1]}, and {'y': [values_2]},
to get these plots for the different options Val 1 and Val 2:
Some Details:
Values_1 is, unsurprisingly, a list of length 100 where each element is of type numpy.float. Replacing json.dumps(values_1) with values_1, and json.dumps(values_2) with values_2 will render the same plots as in your question. The reason why these plots are just straight lines, seems to be that it's the length of your lists that are being plotted, and not the values contained in that list. Or something to that effect.
Setting 'y' = values_1 is the same thing as assigning a single list to 'y'. But 'y' in updatemenus does not take a single list as an argument, but rather a list of lists like in 'y' = [values_1]. Why? Because you might want to plot multiple lists in the same figure like 'y' = [values_1, values_1b]. Have a look:
Plot for dropdown option Var 1:
Plot for dropdown option Var 2
Complete original code:
import plotly
from plotly import graph_objs as go, offline as po, tools
po.init_notebook_mode()
import numpy as np
import json
x = list(np.linspace(-np.pi, np.pi, 100))
values_1 = list(np.sin(x))
values_1b = [elem*-1 for elem in values_1]
values_2 = list(np.tan(x))
values_2b = [elem*-1 for elem in values_2]
line = go.Scatter(
x=x,
y=values_1
)
line2 = go.Scatter(
x=x,
y=values_1b
)
updatemenus = [
{
'buttons': [
{
'method': 'restyle',
'label': 'Val 1',
'args': [
{'y': [values_1, values_1b]},
]
},
{
'method': 'restyle',
'label': 'Val 2',
'args': [
{'y': [values_2, values_2b]},
]
}
],
'direction': 'down',
'showactive': True,
}
]
layout = go.Layout(
updatemenus=updatemenus,
)
figure = go.Figure(data=[line, line2], layout=layout)
po.iplot(figure)
Complete updated code:
# imports
import plotly.graph_objects as go
import numpy as np
# data
x = list(np.linspace(-np.pi, np.pi, 100))
values_1 = list(np.sin(x))
values_1b = [elem*-1 for elem in values_1]
values_2 = list(np.tan(x))
values_2b = [elem*-1 for elem in values_2]
# plotly setup]
fig = go.Figure()
# Add one ore more traces
fig.add_traces(go.Scatter(x=x, y=values_1))
fig.add_traces(go.Scatter(x=x, y=values_1b))
# construct menus
updatemenus = [{'buttons': [{'method': 'update',
'label': 'Val 1',
'args': [{'y': [values_1, values_1b]},]
},
{'method': 'update',
'label': 'Val 2',
'args': [{'y': [values_2, values_2b]},]}],
'direction': 'down',
'showactive': True,}]
# update layout with buttons, and show the figure
fig.update_layout(updatemenus=updatemenus)
fig.show()
Plot with version 4 default layout:
Related
I'm trying to create an interactive PCA plot using plotly-express and graph objects in python (go.Scatter).
The plot should have 2 dropdowns menus (for x-axis and y-axis) to change between the first 5 PCA in the data.
Each data point also belongs to a treatment group either Before, After, or QC.
I was able to plot the PCA1 and PCA2 with plotly-express package but when trying to add the 2 dropdown menus that will update the plot between the 5 PCA it become a mess.
The example data is in my GitHub link,the first 5 columns are the first 5 PCAs.
The code the generate PC1 vs PC2 is:
labels={'0': 'PC 1 (22.0%)',
'1': 'PC 2 (19.6%)',
'2': 'PC 3 (11.1%)',
'3': 'PC 4 (8.2%)',
'4': 'PC 5 (3.9%)',
'color': 'Group'}
fig1 = px.scatter(components_df, x=0 , y=2 ,
color = 'Class',
width=1000, height=700,
template='presentation',
labels=labels,
title="PCA Score Plot (PC{} vs. PC{})".format(1, 2) ,
hover_data=['idx', 'SampleID']
)
fig1.show()
and it looks like this :
I'm trying to add 2 dropdown menus like I draw above to update the x-axis and the y-axis with the different PC's.
So first step was to add_trace on the figure to add other PCs to the figure but dont know how to add graph object to plotly-express to that what i did:
fig = go.Figure()
for Class, group in components_df.groupby("Class"):
# print(group[0])
fig.add_trace(go.Scatter(x=group[0], y=group[1], name=Class, mode='markers',
hovertemplate="Class=%s<br>PC1=%%{x}<br>PC2=%%{y}<extra></extra>"% Class))
for Class, group in components_df.groupby("Class"):
# print(group[0])
fig.add_trace(go.Scatter(x=group[0], y=group[2], name=Class, mode='markers',
hovertemplate="Class=%s<br>PC1=%%{x}<br>PC3=%%{y}<extra></extra>"% Class))
fig.update_layout(
updatemenus=[go.layout.Updatemenu(
active=0,
buttons=list(
[dict(label = 'All',
method = 'update',
args = [{'visible': [True, True, True, True,True]},
{'title': 'All',
'showlegend':True}]),
dict(label = 'PC1 PC1',
method = 'update',
args = [{'visible': [True, False, False, False, False]}, # the index of True aligns with the indices of plot traces
{'title': 'PC1 PC1',
'showlegend':True}]),
dict(label = 'PC1 PC2',
method = 'update',
args = [{'visible': [False, True, False, False, False]},
{'title': 'AAPL',
'showlegend':True}]),
dict(label = 'PC1 PC3',
method = 'update',
args = [{'visible': [False, False, True, False, False]},
{'title': 'AMZN',
'showlegend':True}]),
])
)
])
and that is the result:
There are many problems with that:
when changing the different options in the dropdown menu also the legends change (they suppose the stay fixed)
when changing the different options in the dropdown menu it does not lools like the data should be
it does not look nice like in the plotly-express.
there is only one dropdown
The code is base on many explanations in the documentation and blogs:
How to change plot data using dropdowns
Dropdown Menus in Python
Adding interactive filters
Setting the Font, Title, Legend Entries, and Axis Titles in Python
Any hint will be appreciated on how to add correct add_trac or correct dropdown menu
Thank you!!!
it's all about being highly structured and systematic. Plotly Express does generate a decent base chart. Use fig1.to_dict() to view graph object structures it has built
challenge I found with adding updatemenus to Plotly Express figure - it's a multi-trace figure with trace defining marker color. This can be simplified to a single trace figure with an array defining marker color
then it's a case of building updatemenus. This I have done as nested list comprehensions. Outer loop axis (each menu), inner loop principle component (each menu item)
Updates
magic colors - fair critique. I had used a hard coded dict for color mapping. Now programmatically build cmap Reverted back to static definition of cmap as dict comprehension is not wanted. Changed to a pandas approach to building cmap with lambda function
"y": 1 if ax == "x" else 0.9 We are building two drop downs, one for xaxis and one for yaxis. Hopefully it's obvious that the positions of these menus needs to be different. See docs: https://plotly.com/python/reference/layout/updatemenus/ For similar reason active property s being set. Make sure drop downs show what is actually plotted in the figure
legend refer back to point I made about multi-trace figures. Increases complexity! Have to use synthetic traces and this technique Plotly: How to update one specific trace using updatemenus?
import pandas as pd
import plotly.graph_objects as go
import plotly.express as px
components_df = pd.read_csv(
"https://raw.githubusercontent.com/TalWac/stakoverflow-Qustion/main/components_df.csv"
)
labels = {
"0": "PC 1 (22.0%)",
"1": "PC 2 (19.6%)",
"2": "PC 3 (11.1%)",
"3": "PC 4 (8.2%)",
"4": "PC 5 (3.9%)",
"color": "Group",
}
# cmap = {
# cl: px.colors.qualitative.Plotly[i]
# for i, cl in enumerate(
# components_df.groupby("Class", as_index=False).first()["Class"]
# )
# }
# revert back to static dictionary as dynamic building is not wanted
# cmap = {'After': '#636EFA', 'Before': '#EF553B', 'QC': '#00CC96'}
# use lambda functions instead of dict comprehension
df_c = components_df.groupby("Class", as_index=False).first()
df_c["color"] = df_c.apply(lambda r: px.colors.qualitative.Plotly[r.name], axis=1)
cmap = df_c.set_index("Class").loc[:,"color"].to_dict()
fig1 = go.Figure(
go.Scatter(
x=components_df["0"],
y=components_df["1"],
customdata=components_df.loc[:, ["idx", "SampleID", "Class"]],
marker_color=components_df["Class"].map(cmap),
mode="markers",
hovertemplate="Class=%{customdata[2]}<br>x=%{x}<br>y=%{y}<br>idx=%{customdata[0]}<br>SampleID=%{customdata[1]}<extra></extra>",
)
).update_layout(
template="presentation",
xaxis_title_text=labels["0"],
yaxis_title_text=labels["1"],
height=700,
)
fig1.update_layout(
updatemenus=[
{
"active": 0 if ax == "x" else 1,
"buttons": [
{
"label": f"{ax}-PCA{pca+1}",
"method": "update",
"args": [
{ax: [components_df[str(pca)]]},
{f"{ax}axis": {"title": {"text": labels[str(pca)]}}},
[0],
],
}
for pca in range(5)
],
"y": 1 if ax == "x" else 0.9,
}
for ax in ["x", "y"]
]
).update_traces(showlegend=False)
# add a legend by using synthetic traces. NB, this will leave markers at 0,0
fig1.add_traces(
px.scatter(
components_df.groupby("Class", as_index=False).first(),
x="0",
y="1",
color="Class",
color_discrete_map=cmap,
)
.update_traces(x=[0], y=[0])
.data
)
I'm using Plotly Express to draw a Gantt graph.
I don't know in advance the number of horizontal lines (the number of "tasks").
I would like the bars to be always the same thickness. (I believe the attribute is width, but since the bars are horizontal, "width" might be ambiguous.)
When plotting the figure directly, it takes the whole space on the page and when resizing the page, the bars change size accordingly.
import datetime as dt
import plotly.express as px
data = [
{'Task': '1-2', 'Start': '2021-09-26', 'Finish': '2021-09-27', 'Resource': 'R1'},
{'Task': '2-1', 'Start': '2021-09-23', 'Finish': '2021-09-24', 'Resource': 'R2'},
{'Task': '2-2', 'Start': '2021-09-26', 'Finish': '2021-09-27', 'Resource': 'R2'},
{'Task': '3-1', 'Start': '2021-09-30', 'Finish': '2021-10-01', 'Resource': 'R3'},
{'Task': '3-2', 'Start': '2021-11-26', 'Finish': '2021-11-27', 'Resource': 'R3'},
]
fig = px.timeline(
data,
x_start="Start",
x_end="Finish",
y="Resource",
)
fig.show()
There is a width attribute on each bar but it controls how much of the allowed space the bar uses, so it doesn't actually fix the width.
A cheap trick is to set the graph height according to the number of lines but one must take into account the surroundings (legend, title, etc.) so it is not really linear. And anyway, it would be at best a sorry workaround.
When I integrate the graph in a page like
Python code
return plotly.io.to_json(fig)
HTML page
<script src="https://cdn.plot.ly/plotly-2.0.0.min.js"></script>
<script type="text/javascript">
let graph = {{ gantt_json|safe }};
Plotly.newPlot('gantt_graph', graph, {});
</script>
the graph doesn't take the whole page, but still the bar width changes when the number of bar changes.
So I can control the total graph size, and the width of each bar relative to its allowed space, but I can't seem to find a way to get a fixed width in pixels.
(I thought this issue and closing PR would help but they didn't.)
Am I missing something?
thinking laterally this can be achieved by controlling domain of yaxis
have simulated data, which will generate a random number of timelines
based on number of timelines set domain min value
import pandas as pd
import numpy as np
import plotly.express as px
T = 8
data = pd.DataFrame(
{
"Start": pd.date_range("1-jan-2021", freq="2D", periods=T),
"Finish": pd.date_range("14-jan-2021", freq="2D", periods=T),
"Resource": np.random.choice(
[chr(i) for i in range(ord("A"), ord("A") + T)], T
),
}
)
color_map = {chr(ord("A") + i): px.colors.qualitative.Alphabet[i%len(px.colors.qualitative.Alphabet)] for i in range(T)}
fig = px.timeline(
data,
x_start="Start",
x_end="Finish",
y="Resource",
color="Resource",
color_discrete_map=color_map,
# height=10 * len(data) # This sucks
)
fig.update_xaxes(
side="top",
dtick="D1",
tickformat="%e\n%b\n%Y",
ticklabelmode="period",
tickangle=0,
fixedrange=True,
)
fig.update_yaxes(
title="",
tickson="boundaries",
fixedrange=True,
)
BARHEIGHT = .1
fig.update_layout(
yaxis={"domain": [max(1 - (BARHEIGHT * len(fig.data)), 0), 1]}, margin={"t": 0, "b": 0}
)
I'm plotting datasets with plotly express as sunburst charts.
One thing I'm trying to achieve is the possibility to select the values to be plotted so that the plot gets updated if the values change meaning that a different column in the dataframe is selected.
I've created an example based on this sunburst example in the official docs
https://plotly.com/python/sunburst-charts/#sunburst-of-a-rectangular-dataframe-with-plotlyexpress
There the column 'total_bill' is selected for plotting and that works. I can recreate the plot in that example.
Now I would like to use updatemenus to switch that to the 'tip' column that also holds floats and should be usable.
The example code I've tried:
import plotly.express as px
df = px.data.tips()
updatemenus = [{'buttons': [{'method': 'update',
'label': 'total_bill',
'args': [{'values': 'total_bill'}]
},
{'method': 'update',
'label': 'tip',
'args': [{'values': 'tip'}]
}],
'direction': 'down',
'showactive': True,}]
fig = px.sunburst(df, path=['day', 'time', 'sex'], values='total_bill')
fig.update_layout(updatemenus=updatemenus)
fig.show()
Now this will successfully plot the same plot as in the example, but when I select back and forth between the two updatemenu options, it doesn't behave properly.
I've also tried to use Series everywhere, but the results is the same.
I've also looked at this example, which has a similar focus
Plotly: How to select graph source using dropdown?
but the answers there didn't solve my problem either, since the sunburst in some way seems to behave differently from the scatter plot?
Any idea how to get this working?
similar to solution you arrived at. Use Plotly Express to build all the figures, collect into a dict
menu can now be built with dict comprehension
import plotly.express as px
df = px.data.tips()
# construct figures for all columns that can provide values
figs = {
c: px.sunburst(df, path=["day", "time", "sex"], values=c)
for c in ["total_bill", "tip"]
}
# choose a column that becomes the figure
fig = figs["total_bill"]
# now build menus, that use parameters that have been pre-built using plotly express
fig.update_layout(
updatemenus=[
{
"buttons": [
{
"label": c,
"method": "restyle",
"args": [
{
"values": [figs[c].data[0].values],
"hovertemplate": figs[c].data[0].hovertemplate,
}
],
}
for c in figs.keys()
]
}
]
)
Ok, I found one solution that works, but maybe someone could point me towards a "cleaner" solution, if possible.
I stumbled across this question that is actually unrelated:
Plotly: How to create sunburst subplot using graph_objects?
There, one solution was to save the figure data of a plotly express figure and reuse it in a graph objects figure.
This gave me an idea, that I could maybe save the data of each figure and then reuse (and update/modify) it in a third figure.
And, as it turns out, this works!
So here is the working example:
import plotly.express as px
df = px.data.tips()
# create two figures based on the same data, but with different values
fig1 = px.sunburst(df, path=['day', 'time', 'sex'], values='total_bill')
fig2 = px.sunburst(df, path=['day', 'time', 'sex'], values='tip')
# save the data of each figure so we can reuse that later on
ids1 = fig1['data'][0]['ids']
labels1 = fig1['data'][0]['labels']
parents1 = fig1['data'][0]['parents']
values1 = fig1['data'][0]['values']
ids2 = fig2['data'][0]['ids']
labels2 = fig2['data'][0]['labels']
parents2 = fig2['data'][0]['parents']
values2 = fig2['data'][0]['values']
# create updatemenu dict that changes the figure contents
updatemenus = [{'buttons': [{'method': 'update',
'label': 'total_bill',
'args': [{
'names': [labels1],
'parents': [parents1],
'ids': [ids1],
'values': [values1]
}]
},
{'method': 'update',
'label': 'tip',
'args': [{
'names': [labels2],
'parents': [parents2],
'ids': [ids2],
'values': [values2]
}]
}],
'direction': 'down',
'showactive': True}]
# create the actual figure to be shown and modified
fig = px.sunburst(values=values1, parents=parents1, ids=ids1, names=labels1, branchvalues='total')
fig.update_layout(updatemenus=updatemenus)
fig.show()
I am trying to plot 3 pie charts side by side. I don't understand why the following code is making the pie charts go across the page diagonally left to write rather than horizontally left to write in one line.
Here's my code:
app.layout = html.Div([
html.Div([
dcc.Graph(id='TPiePlot',
figure={
'data': [go.Pie(labels=labels1,
values=values1,
marker=dict(colors=colors, line=dict(color='#fff', width=1)),
hoverinfo='label+value+percent', textinfo='value',
domain={'x': [0, .25], 'y': [0, 1]}
)
],
'layout': go.Layout(title='T',
autosize=True
)
}
),
dcc.Graph(id='RPiePlot',
figure={
'data': [go.Pie(labels=labels2,
values=values2,
marker=dict(colors=colors, line=dict(color='#fff', width=1)),
hoverinfo='label+value+percent', textinfo='value',
domain={'x': [0.30, .55], 'y': [0, 1]}
)
],
'layout': go.Layout(title='R',
autosize=True
)
}
),
dcc.Graph(id='RoPiePlot',
figure={
'data': [go.Pie(labels=labels3,
values=values3,
marker=dict(colors=colors, line=dict(color='#fff', width=1)),
hoverinfo='label+value+percent', textinfo='value',
domain={'x': [0.60, 0.85], 'y': [0, 1]}
)
],
'layout': go.Layout(title='Ro',
autosize=True
)
}
)
])
])
Here is what's happening with option 1 from accepted answer (which is the one I need to go with). I'm getting three different sizes plus legend covering some of the pie chart:
I'm struggling to understand how to re-size dash graphs using CSS because the whole container increases in size rather than the actual graph and I don't know how to target just the graphs themself to make size bigger. Is there a way around this?
Plotly's domain is used for subplots. In your case you are plotting three individual plots one after the other and for each you are setting the domain separately.
You have at least two options:
Use the approach you are using now, i.e. 3 individual plots, and use CSS to define their position
Create one plot with three figures and use domain to adjust their position.
Option 1
import dash
import flask
import dash_html_components as html
import plotly.graph_objs as go
import dash_core_components as dcc
server = flask.Flask('app')
app = dash.Dash('app', server=server,
external_stylesheets=['https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css'])
labels = [['monkeys', 'elephants'],
['birds', 'dinosaurs'],
['unicorns', 'giraffes']]
values = [[50, 40],
[100, 10],
[100, 20]]
data = []
for label, value in zip(labels, values):
data.append(html.Div([dcc.Graph(figure={'data': [go.Pie(labels=label,
values=value,
hoverinfo='label+value+percent', textinfo='value'
)]})
], className='col-sm-4'))
app.layout = html.Div(data, className='row')
app.run_server()
Option 2
import dash
import flask
import dash_html_components as html
import plotly.graph_objs as go
import dash_core_components as dcc
server = flask.Flask('app')
app = dash.Dash('app', server=server)
labels = [['monkeys', 'elephants'],
['birds', 'dinosaurs'],
['unicorns', 'giraffes']]
values = [[50, 40],
[100, 10],
[100, 20]]
data = []
x1 = 0
x2 = 0.25
for label, value in zip(labels, values):
data.append(go.Pie(labels=label,
values=value,
hoverinfo='label+value+percent', textinfo='value',
domain={'x': [x1, x2], 'y': [0, 1]}
)
)
x1 = x1 + 0.30
x2 = x1 + 0.25
app.layout = html.Div([
html.Div([dcc.Graph(figure={'data': data})])
])
app.run_server()
I am recently exploring Plotly and I wonder if there is a way for sharing a plot and let the viewer switch between a logarithmic axis and linear axis.
Any suggestion?
Plotly has a dropdown feature which allows the user to dynamically update the plot styling and/or the traces being displayed. Below is a minimal working example of a plot where the user can switch between a logarithmic and linear scale.
import plotly
import plotly.graph_objs as go
x = [1, 2, 3]
y = [1000, 10000, 100000]
y2 = [5000, 10000, 90000]
trace1 = go.Bar(x=x, y=y, name='trace1')
trace2 = go.Bar(x=x, y=y2, name='trace2', visible=False)
data = [trace1, trace2]
updatemenus = list([
dict(active=1,
buttons=list([
dict(label='Log Scale',
method='update',
args=[{'visible': [True, True]},
{'title': 'Log scale',
'yaxis': {'type': 'log'}}]),
dict(label='Linear Scale',
method='update',
args=[{'visible': [True, False]},
{'title': 'Linear scale',
'yaxis': {'type': 'linear'}}])
]),
)
])
layout = dict(updatemenus=updatemenus, title='Linear scale')
fig = go.Figure(data=data, layout=layout)
plotly.offline.iplot(fig)
I added two traces to the data list to show how traces can also be added or removed from a plot. This can be controlled by the visible list in updatemenus for each button.