pythonplotly

Plotly Python: How to properly add shapes to subplots


How does plotly add shapes to figures with multiple subplots and what best practices are around that?

Let's take the following example:

from plotly.subplots import make_subplots
fig = make_subplots(rows=2, cols=1, shared_xaxes=True)
fig.add_vrect(x0=1, x1=2, row=1, col=1, opacity=0.5, fillcolor="grey")
fig.add_scatter(x=[1,3], y=[3,4], row=1, col=1)
fig.add_scatter(x=[2,2], y=[3,4], row=2, col=1)

plotly figure without vrect

If we add_vrect at the end, the rectangle is visualized as I would expect.

fig = make_subplots(rows=2, cols=1, shared_xaxes=True)
fig.add_scatter(x=[1,3], y=[3,4], row=1, col=1)
fig.add_scatter(x=[2,2], y=[3,4], row=2, col=1)
fig.add_vrect(x0=1, x1=2, row=1, col=1, opacity=0.5, fillcolor="grey")

plotly figure with vrect

Now, when I move away from the dummy example to a more complex plot (3 subplots, multiple y axes, logarithmic scaling, datetime x axis), adding the rectangles last does not help either. I don't manage to visualize them for two of the three subplots.

Thus, I'm trying to better understand how plotly handles this under the hood. From what I have gathered so far, the rectangles are shapes and thus, not part of figure.data, but figure.layout. In the above dummy examples, the shapes are only added to the layout in the second take. Why?
Is it more advisable to use fig.add_shape(type="rect") when working with more complex plots?
Or should I give up and just manually wrangle with fig.layout.shapes instead of using the function calls?

Examples are made with plotly 5.15.0.


Solution

  • The problem with shapes in subplots arises from how plotly assigns axis references (xref and yref). add_vrect automatically maps shapes to subplots using row and col which can fail in complex layouts. For more control, use add_shape.

    For a rectangle that always stretches vertically across the entire plot, similar to vrect, define it with reference to a secondary y-axis. This ensures it remains fixed from top to bottom even when zooming.

    Here is an example:

    import plotly.graph_objects as go
    from plotly.subplots import make_subplots
    import pandas as pd
    
    data = {
        "time": pd.date_range("2023-01-01", periods=10, freq="D"),
        "value1": [10, 15, 20, 25, 30, 35, 40, 45, 50, 55],
        "value2": [5, 10, 15, 10, 5, 10, 15, 10, 5, 10],
    }
    df = pd.DataFrame(data)
    
    fig = make_subplots(rows=2, cols=1, shared_xaxes=True, specs=[[{"secondary_y": True}], [{}]])
    
    fig.add_shape(
        type="rect",
        x0="2023-01-03", x1="2023-01-06",
        y0=0, y1=1,
        xref="x1",
        yref="y2",
        secondary_y=True,
        fillcolor="blue", opacity=0.3, line_width=0,
    )
    
    fig.add_scatter(x=df["time"], y=df["value1"], row=1, col=1, name="Value 1")
    fig.add_scatter(x=df["time"], y=df["value2"], row=2, col=1, name="Value 2")
    
    fig.update_yaxes(secondary_y=True, range=[0, 1], fixedrange=True, showgrid=False, visible=False,  row=1, col=1)
    fig.update_layout(title="Shapes with Multiple Subplots")
    fig.show()
    
    
    

    enter image description here