Fixed Ratio Axes on Subplots

I’m trying to plot a figure with multiple traces on a single row, where each trace happens to have fixed ratio axes (see image below).

Issues arise when the number of subplots changes dynamically through a callback. Sizes and positions of the subplots don’t update correctly and traces overlap each other. The figure below was obtained by rendering 2 traces, and then updating the graph to render 7 traces.

Here is the code to reproduce the issue (run it as a Dash app).

import numpy as np

import dash
from dash import html, dcc, callback, Output, Input
import plotly.graph_objects as go
from plotly.subplots import make_subplots


@callback(
    Output("graph", "figure"),
    Input("input", "value"),
    Input("checklist", "value"),
)
def update_graph(ncols, checklist):
    if ncols is None or ncols == 0:
        return {}
    
    scaleratio_on = "scaleratio" in checklist
    layout_kwargs = {}
    fig = make_subplots(rows=1, cols=ncols)
    for i in range(ncols):
        theta = np.linspace(0, 2*np.pi, 20)
        x = np.sin(theta)
        y = np.cos(theta)
        trace = go.Scatter(x=x, y=y)
        fig.add_trace(trace, 1, i+1)

        layout_kwargs[f"yaxis{i+1}"] = dict(scaleanchor=f"x{i+1}", scaleratio=1)

    if scaleratio_on:
        fig.update_layout(**layout_kwargs)

    return fig


if __name__ == "__main__":
    layout = html.Div(
        [
            dcc.Graph(figure={}, id="graph"),
            dcc.Input(type="number", min=0, max=16, debounce=True, id="input"),
            dcc.Checklist(["scaleratio"], [], id="checklist"),
        ]
    )
    app = dash.Dash(__name__)
    app.layout = layout
    app.run(debug=True)

Use the input to increase the number of traces while leaving ‘scaleratio’ unchecked. Notice that the traces become gradually thinner as the number of subplots increases since axes don’t have a fixed ratio.
Now check ‘scaleratio’ on and do just the same. Traces now preserve their aspect ratio, but their sizes differ and they aren’t spaced evenly.

Note that clearing the graph appears to solve the issue. You should be able to observe this by doing the following:

  • refresh the page to start from a blank state
  • check ‘scaleratio’ on
  • type in 2 in the input component and press Enter (traces render correctly).
  • type in 8 in the input component and press Enter (traces overlap each other).
  • clear the input component and press Enter. Type in 8 again and press Enter (traces render correctly).

This is how I’ve handled the problem so far: changing the number of traces clears the graph and pings a hidden component which triggers the actual rendering. It’s rather convoluted and I’m looking for a better solution !