KansaiRobot
KansaiRobot

Reputation: 10022

How can I put different legends to subplots in plotly?

I am still designing what kind of plot with subplots I want to do but when we see the example in the documentation Multiple Subplots with Titles we have

from plotly.subplots import make_subplots
import plotly.graph_objects as go

fig = make_subplots(
    rows=2, cols=2,
    subplot_titles=("Plot 1", "Plot 2", "Plot 3", "Plot 4"))

fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6]),
              row=1, col=1)

fig.add_trace(go.Scatter(x=[20, 30, 40], y=[50, 60, 70]),
              row=1, col=2)

fig.add_trace(go.Scatter(x=[300, 400, 500], y=[600, 700, 800]),
              row=2, col=1)

fig.add_trace(go.Scatter(x=[4000, 5000, 6000], y=[7000, 8000, 9000]),
              row=2, col=2)

fig.update_layout(height=500, width=700,
                  title_text="Multiple Subplots with Titles")

fig.show()

which gives

plot with subplots

which is fine but notice that there is only one place for the legends.(trace 0, trace 1.etc)

In my design the upper left plot has one legend and the other three share some other legend . Is there a way to individualize or customize the legends of subplots?

Upvotes: 4

Views: 17070

Answers (2)

micycle
micycle

Reputation: 3820

Here's a solution using the same technique as the existing answer - creating a new legend for the traces of each subplot and positioning it accordingly - that also handles row-based positioning, and manages subplots with varying numbers of rows and columns, and adjusts for the horizontal and vertical spacing between subplots.

Code

from plotly.subplots import make_subplots
import plotly.graph_objects as go
from itertools import product
import numpy as np

rows = 3
cols = 5
horizontal_spacing = 0.05
vertical_spacing = 0.1
legend_horizontal_spacing = -0.005 # vertical buffer between legend and subplot

subplot_height = (1 - (rows - 1) * vertical_spacing) / rows
subplot_width = (1 - (cols - 1) * horizontal_spacing) / cols

fig = make_subplots(rows=rows, cols=cols, 
                    vertical_spacing=vertical_spacing, 
                    horizontal_spacing=horizontal_spacing,
                    # shared_yaxes=True, 
                    shared_xaxes=True
                   )

for i, (row, col) in enumerate(product(range(1, rows+1), range(1, cols+1))): # rows & cols are 1-indexed
    x_data = np.arange(100)
    y_data = np.convolve(np.random.normal(0, 50, 100), np.ones(10), mode='same')
    fig.append_trace(go.Scatter(x=x_data, y=y_data, name=f"Trace {i}"), row=row, col=col)

    # Add second series to some subplots
    if np.random.choice(3) == 0:  # Condition to add a second series
        y_data2 = np.convolve(np.random.normal(0, 50, 100), np.ones(10), mode='same')
        fig.add_trace(go.Scatter(x=x_data, y=y_data2, name=f"Trace {i}b"), row=row, col=col)
    
    legend_name = f"legend{i+2}" # legend1 is the theme's default. start at legend2 to avoid.
    x = ((col - 1) * (subplot_width + horizontal_spacing)) + (subplot_width / 2)
    y = 1 - ((row - 1) * (subplot_height + vertical_spacing)) + legend_horizontal_spacing
    fig.update_traces(row=row, col=col, legend=legend_name)
    fig.update_layout({legend_name: dict(x=x, y=y, xanchor='center', yanchor="bottom", bgcolor='rgba(0,0,0,0)')})


fig.update_layout(height=600, title_text="<b>Unique Legend Per Subplot Demonstration")

fig.show()

Output

enter image description here

Upvotes: 3

Derek O
Derek O

Reputation: 19610

This topic was discussed on the plotly forum here, and it seems that multiple legends aren't possible in plotly. However, in the same thread @Jaydeep Mistry gives a partial workaround.

He uses legend groups to group traces together, then uses the parameter legend_tracegroupgap in the update_layout method to give the legend the appearance of being more than one legend. However, this spacing only works vertically so your multiple legends will still be vertically spaced apart on the right side of the plot. For example:

from plotly.subplots import make_subplots
import plotly.graph_objects as go

fig = make_subplots(
    rows=2, cols=2,
    subplot_titles=("Plot 1", "Plot 2", "Plot 3", "Plot 4"))

fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 5, 6], legendgroup = '1'),
              row=1, col=1)

fig.add_trace(go.Scatter(x=[20, 30, 40], y=[50, 60, 70], legendgroup = '2'),
              row=1, col=2)

fig.add_trace(go.Scatter(x=[300, 400, 500], y=[600, 700, 800], legendgroup = '2'),
              row=2, col=1)

fig.add_trace(go.Scatter(x=[4000, 5000, 6000], y=[7000, 8000, 9000], legendgroup = '2'),
              row=2, col=2)

fig.update_layout(height=500, width=700,
                  title_text="Multiple Subplots with Titles",
                  legend_tracegroupgap=180)

fig.show()

enter image description here

Alternatively, you could add another legend by using an annotation to draw a box with the accompanying text, but it wouldn't have any functionality.

Upvotes: 9

Related Questions