Dash update colors after figure is created - python

If I create a plotly express figure like so:
fig = px.line(data, color_discrete_map={"Gold": "gold","Silver": "silver"}),
it works fine.
But if I want to update the colors after the figure is created, like so:
fig = px.line(data)
fig.update_layout(color_discrete_map={"Gold": "gold", "Silver": "silver"})
I get
AttributeError: 'Figure' object has no attribute 'color'
I have also tried with update_traces() with no success.
What is the correct way to do this please?

When you create a figure with plotly.express, you receive a plotly.graph_objs figure.
You can pass the parameter color_discrete_map, which is used in the constructor of the express figure to set the colors of the different lines, but afterwards you only can change them through their plotly.graph_objects properties.
It becomes a lot clearer when you do this:
fig1 = px.line(data, color_discrete_map={"Gold": "gold","Silver": "silver"})
fig2 = px.line(data)
print(fig1)
print(fig2)
You will have to change the line_color property of the respective line. A solution could be to do it somewhat like this:
import plotly.express as px
import pandas as pd
data = pd.DataFrame({"Gold":[1, 2, 3], "Silver":[2, 1, 3]})
fig = px.line(data)
colors = {"Gold":"gold", "Silver":"silver"}
for linename, linecolor in colors.items():
for figline in fig.data:
if figline.name == linename:
figline.line.color = linecolor
fig.show()

Related

How to format plotly legend when using marker color?

I want to follow up on this post: Plotly: How to colorcode plotly graph objects bar chart using Python?.
When using plotly express, and specifying 'color', the legend is correctly produced as seen in the post by vestland.
This is my plotly express code:
data = {'x_data': np.random.random_sample((5,)),
'y_data': ['A', 'B', 'C', 'D', 'E'],
'c_data': np.random.randint(1, 100, size=5)
}
df = pd.DataFrame(data=data)
fig = px.bar(df,
x='x_data',
y='y_data',
orientation='h',
color='c_data',
color_continuous_scale='YlOrRd'
)
fig.show()
But when using go.Bar, the legend is incorrectly displayed as illustrated here:
This is my code using graph objects:
bar_trace = go.Bar(name='bar_trace',
x=df['x_data'],
y=df['y_data'],
marker={'color': df['c_data'], 'colorscale': 'YlOrRd'},
orientation='h'
)
layout = go.Layout(showlegend=True)
fig = go.FigureWidget(data=[bar_trace], layout=layout)
fig.show()
I'm learning how to use FigureWidget and it seems it can't use plotly express so I have to learn how to use graph objects to plot. How do I link the legend to the data such that it works like the plotly express example in vestland's post.
This really comes down to understanding what a high level API (plotly express) does. When you specify color in px if it is categorical it creates a trace per value of categorical. Hence the below two ways of creating a figure are mostly equivalent. The legend shows an item for each trace, not for each color.
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import numpy as np
df = pd.DataFrame({"x":np.linspace(0,10,10), "y":np.linspace(5,15,10), "color":np.random.choice(list("ABCD"),10)})
px.bar(df, x="x", y="y", color="color", orientation="h").show()
fig = go.Figure()
for g in df.groupby("color"):
fig.add_trace(go.Bar(x=g[1]["x"], y=g[1]["y"], name=g[0], orientation="h"))
fig
supplementary based on comments
you do not have to use graph objects if you are using FigureWidget() as demonstrated by second figure, create with plotly express and then generate FigureWidget()
for continuous data normal pattern is to use a single trace and a colorbar (also demonstrated in second figure). However if you want a discrete legend, create a trace per value in c_data and use https://plotly.com/python-api-reference/generated/plotly.colors.html sample_colorscale()
import plotly.express as px
import plotly.colors
import plotly.graph_objects as go
import numpy as np
import pandas as pd
# simulate data frame...
df = pd.DataFrame(
{
"x_data": np.linspace(0, 10, 10),
"y_data": np.linspace(5, 15, 10),
"c_data": np.random.randint(0, 4, 10),
}
)
# build a trace per value in c_data using graph objects ... correct legend !!??
bar_traces = [
go.Bar(
name="bar_trace",
x=d["x_data"],
y=d["y_data"],
marker={
"color": plotly.colors.sample_colorscale(
"YlOrRd",
d["c_data"] / df["c_data"].max(),
)
},
orientation="h",
)
for c, d in df.groupby("c_data")
]
layout = go.Layout(showlegend=True)
fig = go.FigureWidget(data=bar_traces, layout=layout)
fig.show()
fig = px.bar(
df,
x="x_data",
y="y_data",
color="c_data",
orientation="h",
color_continuous_scale="YlOrRd",
)
fig = go.FigureWidget(data=fig.data, layout=fig.layout)
fig.show()

How to update the global font color for all Plotly charts?

I have a notebook with around ~20 different plots.
Currently, I'm manually changing the color with this command in each plot function:
fig.update_layout(font = dict(color = '#051c2c'))
return fig
This seems a bit redundant, is there a way to define a global color or font option for all of Plotly?
If you were to use Plotly Dash or JupyterDash, this could be done through stylesheets. To my knowledge, your only option seems to be to edit your figure objects directly through locals(). Just make sure that all your figures follow some sort of system when it comes to naming, like fig1, fig2 etc:
for l in list(locals().keys()):
if l[:3] == 'fig':
locals()[l].update_layout(font_family = 'Old Standard TT', font_size = 24)
Here are four figures to show that this little snippet will in fact edit the fonts for a collection of figures fig1 and fig2 following the locic above.
Plot:
Complete code:
import pandas as pd
import plotly.express as px
# data
df = pd.DataFrame({'a': {0: 1000, 1: 996, 2: 996},
'b': {0: 1000, 1: 1007, 2: 998},
'c': {0: 1000, 1: 1009, 2: 999}})
# figure setup and display
fig1 = px.scatter(df, x = 'a', y='c', title = 'Figure 1')
fig2 = px.bar(df, x = 'a', y='c', title = 'Figure 2')
fig2.update_layout(font=dict(family = 'Courier New'))
fig1.show()
fig2.show()
# some brute force editing
keys = list(locals().keys())
for l in keys:
if l[:3] == 'fig':
locals()[l].update_layout(font_color = 'firebrick', font_size = 16)
# display after editing
fig1.show()
fig2.show()
One option is to create your own theme. This can be combined with other themes, meaning you can override the bits of the config you care about and let Plotly handle the rest. The chained themes are handled like CSS, so later themes override earlier themes.
import plotly.io as pio
import plotly.graph_objects as go
# Register your theme as a named template
pio.templates['my_theme'] = go.layout.Template(
layout=dict(
font=dict(
color='#051c2c',
),
),
)
# Combine your theme with plotly's default
pio.templates.default = 'plotly+my_theme'
For more information, check out Plotly's docs on the matter
https://plotly.com/python/templates/#creating-themes

How to create secondary y-axes from a plotly express facetted figure?

I would like to modify a facetted plotly.express figure so that each trace has its own secondary y-axis. I don't want to re-create the figure from scratch using the standard Plotly-python api if possible. See exmaple below.
import plotly.express as px
input_df = px.data.tips()
fig = px.scatter(input_df,
x = 'total_bill',
y = 'tip',
color = 'day',
facet_row = 'smoker',
facet_col = 'sex',
)
fig.layout.width = 800
fig.show()
I would like to convert the above so each trace (or color) has its own secondary y-axis. So in this case, I would like 3 additional y-axes for each facet. This is my attempt but it doesn't work. There must be a better way. I would appreciate any ideas.
import plotly.graph_objects as go
yaxes = []
for trace in fig.data:
yaxisLabel = trace['yaxis']
if trace['yaxis'] in yaxes:
if yaxisLabel == 'y':
axisnumber = 0
else:
axisnumber = int(trace['yaxis'][1:])
newAxis_num = axisnumber + 100 * yaxes.count(yaxisLabel)
exec(f"fig.layout.update(yaxis{newAxis_num} = go.layout.YAxis(overlaying='y', side='right'))")
trace.update({'yaxis': f'y{newAxis_num}'})
yaxes.append(yaxisLabel)

Change hover text of a plotly express treemap

I want to show in a treemap just the label and value of each item, not parent or ID. I have defined it with plotly express. No matter how much I have tinkered with it, I haven’t been able to restrict hover text to the fields I need. Check the code and capture
import plotly.express as px
fig = px.treemap(dfconcepto, path=['type','name'],
values = 'count',
width=900, height=900,
hover_data = ['count'],
)
fig.show()
I also have tried to create it with non-express treemap. Hovertext is what I want, but then a treemap with two levels is rendered asymmetric.
What I want is something like the hovertext of non-express treemap, but balanced and symmetric as in express treemap
What can I do?
Thanks in advance!
It seems to me you should overwrite your hover template
import pandas as pd
import plotly.express as px
url = "https://gist.githubusercontent.com/jlchulilla/3b4e40f68ba73b5dbcb661a1d861f308/raw/e564973db30a4612aba60c5b26dd108edc98f048/test2sof.csv"
df = pd.read_csv(url).drop("Unnamed: 0", axis=1)
fig = px.treemap(df, path=['type','name'],
values = 'coincidencia',
width=900, height=900,
)
# Now your hovertemplate looks like
# fig.data[0].hovertemplate
# 'labels=%{label}<br>coincidencia=%{value}<br>parent=%{parent}<br>id=%{id}<extra></extra>'
# But it seems to me you want something like
fig.data[0].hovertemplate = '%{label}<br>%{value}'
fig.show()

how can i create subplots with plotly express?

been loving the plotly express graphs but want to create a dashboard with them now. Did not find any documentation for this. Is this possible?
I was struggling to find a response on this as well so I ended up having to create my own solution (see my full breakdown here: How To Create Subplots Using Plotly Express)
Essentially make_subplots() takes in plot traces to make the subplots instead of figure objects like that which Express returns. So what you can do is, after creating your figures in Express, is break apart the Express figure objects into their traces and then re-assemble their traces into subplots.
Code:
import dash_core_components as dcc
import plotly.express as px
import plotly.subplots as sp
# Create figures in Express
figure1 = px.line(my_df)
figure2 = px.bar(my_df)
# For as many traces that exist per Express figure, get the traces from each plot and store them in an array.
# This is essentially breaking down the Express fig into it's traces
figure1_traces = []
figure2_traces = []
for trace in range(len(figure1["data"])):
figure1_traces.append(figure1["data"][trace])
for trace in range(len(figure2["data"])):
figure2_traces.append(figure2["data"][trace])
#Create a 1x2 subplot
this_figure = sp.make_subplots(rows=1, cols=2)
# Get the Express fig broken down as traces and add the traces to the proper plot within in the subplot
for traces in figure1_traces:
this_figure.append_trace(traces, row=1, col=1)
for traces in figure2_traces:
this_figure.append_trace(traces, row=1, col=2)
#the subplot as shown in the above image
final_graph = dcc.Graph(figure=this_figure)
Output:
Working off #mmarion's solution:
import plotly.express as px
from plotly.offline import plot
from plotly.subplots import make_subplots
figures = [
px.line(df1),
px.line(df2)
]
fig = make_subplots(rows=len(figures), cols=1)
for i, figure in enumerate(figures):
for trace in range(len(figure["data"])):
fig.append_trace(figure["data"][trace], row=i+1, col=1)
plot(fig)
This is easily extended into the column dimension.
From the docs:
**facet_row**
(string: name of column in data_frame) Values from this column are used to assign marks to facetted subplots in the vertical direction.
**facet_col**
(string: name of column in data_frame) Values from this column are used to assign marks to facetted subplots in the horizontal direction.
Get here some examples too.
https://medium.com/#plotlygraphs/introducing-plotly-express-808df010143d
Unfortunately, it is not at the moment. See the following issue to get updated: https://github.com/plotly/plotly_express/issues/83
I solved it by combining all the data in a single dataframe,
with a column called "type" that distinguishes the two plots.
Then I used facet_col to create (some kind of) subplot:
px.scatter(df3, x = 'dim1', y = 'dim2', color = 'labels', facet_col='type')
Try this function out. You have to pass in the plotly express figures into the function and it returns a subplot figure.
#quick_subplot function
def quick_subplot(n,nrows,ncols, *args): #n:number of subplots, nrows:no.of. rows, ncols:no of cols, args
from dash import dcc
import plotly.subplots as sp
from plotly.subplots import make_subplots
fig=[] #list to store figures
for arg in args:
fig.append(arg)
combined_fig_title=str(input("Enter the figure title: "))
tok1=int(input("Do you want to disable printing legends after the first legend is printed ? {0:Disable, 1:Enable} : "))
fig_traces={} #Dictionary to store figure traces
subplt_titles=[]
#Appending the traces of the figures to a list in fig_traces dictionary
for i in range(n):
fig_traces[f'fig_trace{i}']=[]
for trace in range(len(fig[i]["data"])):
fig_traces[f'fig_trace{i}'].append(fig[i]["data"][trace])
if(i!=0 & tok1==0):
fig[i]["data"][trace]['showlegend'] = False #Disabling other legends
subplt_titles.append(str(input(f"Enter subplot title for subplot-{i+1}: ")))
#Creating a subplot
#Change height and width of figure here if necessary
combined_fig=sp.make_subplots(rows = nrows, cols = ncols, subplot_titles = subplt_titles)
combined_fig.update_layout(height = 500, width = 1200, title_text = '<b>'+combined_fig_title+'<b>', title_font_size = 25)
#Appending the traces to the newly created subplot
i=0
for a in range(1,nrows+1):
for b in range(1, ncols+1):
for traces in fig_traces[f"fig_trace{i}"]:
combined_fig.append_trace(traces, row=a, col=b)
i+=1
#Setting axis titles
#X-axis
combined_fig['layout']['xaxis']['title']['font']['color']='blue'
tok2=int(input("Separate x-axis titles?{0:'No',1:'Yes'}: "))
for i in range(max(nrows,ncols)):
if i==0:
combined_fig['layout']['xaxis']['title']=str(input(
f"Enter x-axis's title: "))
if tok2 & i!=0:
combined_fig['layout'][f'xaxis{i+1}']['title']=str(input(
f"Enter x-axis {i+1}'s title: "))
combined_fig['layout'][f'xaxis{i+1}']['title']['font']['color']='blue'
#Y-axis
combined_fig['layout']['yaxis']['title']['font']['color']='blue'
tok3=int(input("Separate y-axis titles?{0:'No',1:'Yes'}: "))
for i in range(max(nrows,ncols)):
if i==0:
combined_fig['layout']['yaxis']['title']=str(input(
f"Enter y-axis's title: "))
if tok3 & i!=0:
combined_fig['layout'][f'yaxis{i+1}']['title']=str(input(
f"Enter y-axis {i+1}'s title: "))
combined_fig['layout'][f'yaxis{i+1}']['title']['font']['color']='blue'
combined_fig['layout']['xaxis']['title']['font']['color']='blue'
combined_fig['layout']['yaxis']['title']['font']['color']='blue'
return combined_fig
f=quick_subplot(2,1,2,fig1,fig2)
f.show()

Categories