What's the correct way to set layout properties for each subplot?

Hi,

Just made a basic 2 by 2 subplot:

fig = make_subplots(
    rows=2, cols=2, 
    subplot_titles=[
        'Receiver Operating Characteristic', 
        'TPR vs FPR', 
        'Precision-Recall Curve', 
        'Precision vs Recall'
    ],
)

and after adding traces to each subplot I want to update the layout for each subplot, common properties like plot width, height, xaxis, yaxis, title, etc.

fig.update_layout(    
    {    
        'xaxis':{'range': [0, 1], 'dtick': 0.2,},
        'yaxis':{'range': [0, 1], 'dtick': 0.2,},
        'xaxis2':{'range': [0, 1], 'dtick': 0.2,},
        'yaxis2':{'range': [0, 1], 'dtick': 0.2,},
        'xaxis3':{'range': [0, 1], 'dtick': 0.2,},
        'yaxis3':{'range': [0, 1], 'dtick': 0.2,},
        'xaxis4':{'range': [0, 1], 'dtick': 0.2,},
        'yaxis4':{'range': [0, 1], 'dtick': 0.2,},
        'height':800,
        'width':800,
        'template':'plotly_white'
    }
)

So my questions are:

  1. How do I update layout for each subplot programatically, instead of the brutal way of copying & pasting mannually? Something like a template layout for each subplot? The naming of xaxis, xaxis2, xaxis3 feels especially suspicious to me.
  2. Why do we specify subplot title subplot_titles as a list in make_subplots, instead of title, title1 inside fig.update_layout when xaxis, xaxis2 is part of the design language.
  3. Coming from the matplotlib world, where each subplot is a standalone figure, which can be modified however one wants, I do wander if there is similar way to do this in Plotly. Thanks
1 Like

Hi Jingw222! Have you had the opportunity to look at This page? I think it may answer most if not all of your questions!

1 Like

Hi @jingw222 maybe you indeed found some answers in the tutorial suggested by @Krichardson. If you want to update all or some axes, you can use the fig.update_xaxes method as described in https://plot.ly/python/subplots/#customizing-subplot-axes. For your example, that would be

import plotly.graph_objects as go
fig = make_subplots(
    rows=2, cols=2, 
    subplot_titles=[
        'Receiver Operating Characteristic', 
        'TPR vs FPR', 
        'Precision-Recall Curve', 
        'Precision vs Recall'
    ],
)
fig.add_trace(go.Scatter(x=[0, 1], y=[0, 1]), 1, 1)
fig.add_trace(go.Scatter(x=[0, 1], y=[0, 1]), 1, 2)
fig.add_trace(go.Scatter(x=[0, 1], y=[0, 1]), 2, 1)
fig.add_trace(go.Scatter(x=[0, 1], y=[0, 1]), 2, 2)

fig.update_xaxes(range=[0, 1], dtick=0.2)
fig.update_yaxes(range=[0, 1], dtick=0.2)
fig.update_layout(height=800, width=600, template='plotly_white')
fig.show()

Titles of subplots are in fact annotations (https://plot.ly/python/text-and-annotations/), so passing them in the make_subplots function is a convenient way to have their location computed automatically for you (this is done in python and not by the javascript library which is doing all the plotting).

For updating subplots, you can use fig.update_xaxes and fig.update_traces which take a row and col parameter so that you can modify each subplot independently.

3 Likes

Thank you for clarifying that. Makes a lot sense now. If both update_traces and update_xaxes can be applied to each individual subplot separately, that seems promising and exactly what Iโ€™ve been looking for.

1 Like

Iโ€™ve been trying to do this for scatter3D in a subplot, but canโ€™t seem to figure it out. What is the correct way to call it in that case?

Performance comparison between โ€˜for loopโ€™ and โ€˜updateโ€™:


Here is some code to demo how to add shapes with โ€˜updateโ€™ in one figture and subfigures.

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

shapes_data = [
    {"x0": 1, "y0": 1, "x1": 2, "y1": 2, "color": "red"},
    {"x0": 3, "y0": 2, "x1": 4, "y1": 3, "color": "blue"},
    {"x0": 2, "y0": 4, "x1": 3, "y1": 5, "color": "green"}
]

# Method 1: Using Update Layout to add shapes to single plot
fig1 = go.Figure()
fig1.add_trace(go.Scatter(x=[0, 5], y=[0, 5], mode='markers', name='Base Data'))
shapes_list = []
for shape in shapes_data:
    shapes_list.append(
        dict(
            type="rect",
            x0=shape["x0"], y0=shape["y0"],
            x1=shape["x1"], y1=shape["y1"],
            fillcolor=shape["color"],
            opacity=0.5,
            line=dict(color=shape["color"], width=2)
        )
    )

fig1.update_layout(
    shapes=shapes_list,
    title="Method 2: Adding Shapes with Update Layout",
    xaxis_title="X Axis",
    yaxis_title="Y Axis"
)


# Method 2: Adding shapes to subplots
fig2 = make_subplots(
    rows=1, cols=2,
    subplot_titles=("Left Subplot", "Right Subplot")
)
fig2.add_trace(go.Scatter(x=[0, 5], y=[0, 5], mode='markers', name='Left Data'), row=1, col=1)
fig2.add_trace(go.Scatter(x=[0, 5], y=[0, 5], mode='markers', name='Right Data'), row=1, col=2)
left_shapes = []
right_shapes = []
for i, shape in enumerate(shapes_data):
    if i < 2:  # Add first two shapes to left subplot
        left_shapes.append(dict(
            type="rect",
            x0=shape["x0"], y0=shape["y0"],
            x1=shape["x1"], y1=shape["y1"],
            fillcolor=shape["color"],
            opacity=0.5,
            line=dict(color=shape["color"], width=2),
            # MAIN DIFFERENCE:
            xref="x", yref="y"  # Reference to first subplot
        ))
    else:  # Add remaining shapes to right subplot
        right_shapes.append(dict(
            type="rect",
            x0=shape["x0"], y0=shape["y0"],
            x1=shape["x1"], y1=shape["y1"],
            fillcolor=shape["color"],
            opacity=0.5,
            line=dict(color=shape["color"], width=2),
            xref="x2", yref="y2"  # Reference to second subplot
        ))

all_shapes = left_shapes + right_shapes
fig2.update_layout(
    shapes=all_shapes,
    title="Method 3: Adding Shapes to Subplots Example"
)

fig1.show()
fig2.show()