Update subplots column_widths

I would like to know if there is a method to update the column_widths attribute of subplots. For instance, let’s say I have a figure initialized as

    fig = make_subplots(
        rows=1, cols=2,
        specs=[[{"type": "scatter"}, {"type": "scatter"}]],

is there something like



hi @vincentm
Welcome to the community. Good question.

You can uniformly control column width through udpate_traces().

Hi and thanks. This didn’t work for me. To give more context, here are the versions of the plotly and dash libraries I’m using

Python 3.8.10 (default, Mar 15 2022, 12:22:08) 
[GCC 9.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import plotly
>>> import dash
>>> plotly.__version__
>>> dash.__version__

If I do

    fig = make_subplots(
        rows=1, cols=2,
        # column_widths=[split, 1. - split],
        specs=[[{"type": "scatter"}, {"type": "scatter"}]],
    fig.update_traces(column_widths=[split, 1. - split])

where split is in [0,1], the subplot figure is created with split = .5, the default I assume. Then my code adds a bunch of traces to each plots in the figure. If I instead call update_traces just before returning

    fig.update_traces(column_widths=[split, 1. - split])
    return fig

I get an error like

Traceback (most recent call last):
  File "/home/vincentm/repos/es_app/es/app.py", line 201, in update_graph
    fig = plotly_plot.plot_bs_dos(bsdict=bsdicts, dosdict=dosdicts,
  File "/home/vincentm/repos/es_app/es/plotting.py", line 77, in plot_bs_dos
    fig.update_traces(column_widths=[split, 1. - split])
  File "/home/vincentm/repos/es_app/venv/lib/python3.8/site-packages/plotly/basedatatypes.py", line 1376, in update_traces
    trace.update(patch, overwrite=overwrite, **kwargs)
  File "/home/vincentm/repos/es_app/venv/lib/python3.8/site-packages/plotly/basedatatypes.py", line 5099, in update
    BaseFigure._perform_update(self, kwargs, overwrite=overwrite)
  File "/home/vincentm/repos/es_app/venv/lib/python3.8/site-packages/plotly/basedatatypes.py", line 3887, in _perform_update
    raise err
ValueError: Invalid property specified for object of type plotly.graph_objs.Scatter: 'column'

Did you mean "fill"?

    Valid properties:

Basically, I would like an efficient way to resplit the figure with a callback in a dash app. So I was wondering whether it is possible to just reset column_widths without regenerating the whole figure.

hi @vincentm

Are you having trouble replicating this example code from the docs?

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

app = Dash(__name__)

app.layout = html.Div([
    html.H4('Live adjustable subplot-width'),
    html.P("Subplots Width:"),
        id='slider-width', min=.1, max=.9, 
        value=0.5, step=0.1)

    Output("graph", "figure"), 
    Input("slider-width", "value"))
def customize_width(left_width):
    fig = make_subplots(rows=1, cols=2, 
        column_widths=[left_width, 1 - left_width])

    fig.add_trace(row=1, col=1,
        trace=go.Scatter(x=[1, 2, 3], y=[4, 5, 6])) # replace with your own data source

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


You need to update the column_widths inside the make_subplots(). Don’t do it with fig.update_traces()


I can replicate this, but in this example, the callback regenerates the figure, doesn’t merely updates it. I was wondering whether there is a quick way to just update column_widths in the case when the figure is more costly to generate.



Changing column_widths implies a fig.layout_update , i.e. for each subplot cell of spec type ‘xy’ or ‘scene’, must be updated the corresponding xaxis domain (for ‘xy’ type), respectively the correspondin scene, x domain. The range of each domain is computed taking into account a few of make_subplots settings . The custom function new_xdomains() , defined below, computes the new ranges for these domains. It is defined such that to be used in subplots like yours, i.e. subplots with specs of type in [‘xy’, ‘scene’], or specs type, the trace name, but not in [‘polar’, ‘ternary’, ‘mapbox’, ‘domain’]. Also no cell pads are allowed in specs definition, and are considered only the default settings:


Any modified settings among those listed above, involves a special treatment.

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

def new_xdomains(rows, cols, col_w, horizontal_spacing, specs=None):
    #rows, cols -integers passed to make_subplots()
    #col_w - list of new column widths
    #specs - None or list of specs passed to make_subplots()
    #returns new xaxis domain for each subplot cell, corresponding to the new column_width, i.e. col_w
    if specs is None:
        specs = [[{} for c in range(cols)] for r in range(rows)]
        if len(specs) != rows or len(specs[0]) != cols:
            raise ValueError('bad length of the list specs')
            for srow in specs:
                for spec in srow:
                    if spec['type']  in ['polar','ternary','mapbox', 'domain']:
                        raise ValueError('this function works only for spec type "xy", and "scene"')
                    else: continue    
    if len(col_w) != cols:
        raise ValueError('your list of new column widths must have the length f"{col}"')
        cs = float(sum(col_w))
    xd_widths=[] #actual lengths of new columns,ie taking into account horizontal_spacing, too
    for w in col_w:
        xd_widths.append((1 - horizontal_spacing * (cols - 1)) * (w/cs))
    #these settings for col_list and row_list are valid when subplot cells
    #are indexed as matrices, i.e (1,1) is the upper left cell

    left_xdom =  [[sum(xd_widths[:c]) + c * horizontal_spacing for c in col_list] for r in row_list] 
    xds= []
    for r, spec_row in enumerate(specs):
        for c, spec in enumerate(spec_row):

            if spec is None:  

            xl = left_xdom[r][c] 
            xr = left_xdom[r][c] + xd_widths[c]
            xds.append([xl, xr])
    return xds   #xds is the list of x domains for  the cells enumerated line by line   (in our example (1,1), (1,2), (2,1), (2,2)

Let us give a general example of subplot with admissible specs:

#vars whose values are used to set    keywords in  make_subplots and to are passed to the function  new_xdomain
specs=[[{"type": "xy"}, {"type": "heatmap"}], 
       [{"type": "scene"}, {}]]
cols = 2
#end  vars definitions
fig = make_subplots(subplot_titles=("Title (1,1)", "Title (1,2)", "Title (2,1)(3D)", "Title (2,2)"),
        rows=rows, cols=cols,
print(fig.layout)  #print the initial layout to decide what we'll update

fig.add_trace(go.Scatter(x=[1,2,3], y=[-1 , 1.35, 0.7], mode="lines"), 1,1)
fig.add_trace(go.Heatmap(z=np.random.randint(2, 13, (5,5)), showscale=False), 1, 2)
fig.add_trace(go.Surface(x=[1,2,3,4], y=[2,3,4], z= np.random.randint(1, 15, (3, 4)), showscale=False), 2, 1)
fig.add_trace(go.Scatter(x=2*np.random.rand(8), y=1+3*np.random.rand(8), mode="markers"), 2,2)

fig.update_layout(width=770, height=600, showlegend=False)

This is the initial subplot fig:

new_column_widths = [0.55, 0.45]

xd_lims=new_xdomains(rows, cols, new_column_widths,  horizontal_spacing, specs=specs)
fig.update_layout(xaxis_domain= xd_lims[0], 
fig.update_layout({'xaxis2': {'domain': xd_lims[1]},
            'xaxis3': {'domain': xd_lims[3]}

The new figure after changing column_widths by new_column_widths:

Conclusion: We cannot update column_widths via fig.update_traces(), but via fig.update_layout() after calling the above auxilliary function, that returns the new xaxes domains.