Kai Aeberli
Kai Aeberli

Reputation: 1220

order plotly legend by custom order

I have a plotly figure to which I add values from two dataframes, df_a and df_b. I display both dataframes in different subplots, but they share a legend. How can I order the shared legend? The expected order is: [a, b, c, f, g]. Please see below for the current implementation - it seems to pick the order from input data in some way.

import pandas as pd
from plotly.subplots import make_subplots
import plotly.graph_objects as go
import plotly.express as px
from plotly.offline import iplot

df_a = pd.DataFrame(columns=["date", "a", "b", "c"], data=[
    [pd.to_datetime("31Jan20"), 3, 4, 5],
    [pd.to_datetime("31Mar20"), 3, 4, 5],
    [pd.to_datetime("30Jun20"), 3, 4, 5],
])

df_b = pd.DataFrame(columns=["date", "a", "g", "f"], data=[
    [pd.to_datetime("31Jan20"), 8, 5, 4],
    [pd.to_datetime("31Mar20"), 3, 4, 5],
    [pd.to_datetime("30Jun20"), 3, 4, 5],
])

buckets = ["a", "b", "c", "f", "g"]

fig_subplots = make_subplots(rows=2, cols=1, shared_xaxes=True, subplot_titles=["df_a", "df_b"])

def get_chart(df, buckets, d_legend):
    fig = go.Figure()
    colorscale = px.colors.qualitative.Pastel
    i_color = 0
    unique_dates = [str(dt) for dt in df.date.unique()]
    for bucket in sorted(buckets, reverse=True):

        y_values = df[df['variable'] == bucket]["value"].to_list()
        enable_legend_for_bucket = False if bucket in d_legend else True

        fig.add_trace(go.Bar(
            name=bucket,
            x=unique_dates,
            y=y_values,
            marker_color=colorscale[i_color],
            legendgroup=bucket,   
            showlegend=enable_legend_for_bucket

        ))

        if len(y_values) != 0:
            d_legend[bucket] = True # store first time this bucket was populated for legend  

        i_color += 1
      
    fig.update_layout(barmode="stack")       
    return fig, d_legend

list_df = [df_a.melt(id_vars="date"), df_b.melt(id_vars="date")]
d_legend = {}
iRow = 1
for df in list_df:
    fig, d_legend = get_chart(df, buckets, d_legend)
    
    for el in fig['data']:
        fig_subplots.append_trace(el, iRow, 1)
    iRow += 1
fig_subplots.update_layout(barmode='stack', legend={'traceorder':'normal'})    

enter image description here

Upvotes: 2

Views: 3093

Answers (2)

Kai Aeberli
Kai Aeberli

Reputation: 1220

There is also an approach with plotly express:

import pandas as pd
import plotly.express as px

df_a = pd.DataFrame(columns=["date", "a", "b", "c"], data=[
    [pd.to_datetime("31Jan20"), 3, 4, 5],
    [pd.to_datetime("31Mar20"), 3, 4, 5],
    [pd.to_datetime("30Jun20"), 3, 4, 5],
])

df_b = pd.DataFrame(columns=["date", "a", "g", "f"], data=[
    [pd.to_datetime("31Jan20"), 8, 5, 4],
    [pd.to_datetime("31Mar20"), 3, 4, 5],
    [pd.to_datetime("30Jun20"), 3, 4, 5],
])

df_a["id"] = "df_a"
df_b["id"] = "df_b"
df_c = df_a.append(df_b)
px.bar(df_c.melt(id_vars=["date", "id"]), x="date", y="value", color="variable", facet_col="id", range_y=[0,20], facet_col_wrap=1)

enter image description here

Upvotes: 0

thesubjectsteve
thesubjectsteve

Reputation: 56

Without finding an easy built-in method to order the legend, and to have the same stacking of values with the legend sorted in any desired order.

new_order = sorted(['a','b','c','g','f'],reverse=True)
print(new_order)

 
ordered_object_list =[]
for i in new_order:
    item = [obj for obj in fig_subplots.data if obj['name'] == i]
    ordered_object_list += item

 
fig_subplots.data = ordered_object_list
fig_subplots.update_layout(legend={'traceorder':'reversed'})

Upvotes: 4

Related Questions