<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>plotly.js splom + marker.colorbar restyle bug</title>
<!-- any plotly.js >= 2.35 works: use your own bundle or the CDN below -->
<script src="https://cdn.plot.ly/plotly-3.5.0.min.js"></script>
<style>
body { font-family: -apple-system, "Segoe UI", Roboto, sans-serif; margin: 16px; }
.row { display: flex; gap: 12px; align-items: flex-start; flex-wrap: wrap; }
h3 { margin: 10px 0 4px; font-size: 15px; }
pre { background: #f6f8fa; border-radius: 6px; padding: 10px; font-size: 12px;
white-space: pre-wrap; max-width: 780px; }
code { background: #f6f8fa; padding: 1px 4px; border-radius: 4px; }
</style>
</head>
<body>
<h1>splom layout collapses when <code>marker.colorbar.thicknessmode='pixels'</code>
is applied with <code>Plotly.restyle</code></h1>
<p>Same data + layout, same final <code>marker.colorbar</code> value. Difference is only <b>when</b>
it is applied:</p>
<ul>
<li><b>Path A (normal):</b> <code>marker.colorbar = {thicknessmode: 'pixels'}</code> is in the
figure at <code>newPlot</code> time.</li>
<li><b>Path B (collapsed):</b> figure starts with <code>marker.colorbar = {}</code> (no explicit
<code>thickness</code>), then <code>Plotly.restyle(gd, {'marker.colorbar.thicknessmode':'pixels'}, [1])</code>.</li>
</ul>
<p>After Path B, plotly.js writes <b>thickness: 16290</b> back into the trace's colorbar and the
splom panels collapse from ~125px to ~15px per axis.</p>
<div class="row">
<div>
<h3>Path A — set at newPlot time (normal)</h3>
<div id="A" class="gd" style="width:750px;height:900px"></div>
<pre id="mA">…</pre>
</div>
<div>
<h3>Path B — set via Plotly.restyle (collapsed)</h3>
<div id="B" class="gd" style="width:750px;height:900px"></div>
<pre id="mB">…</pre>
</div>
</div>
<script>
var DATA = [{"dimensions": [{"axis": {"matches": true}, "label": "sepal_length", "values": {"dtype": "f8", "bdata": "ZmZmZmZmFECamZmZmZkTQM3MzMzMzBJAZmZmZmZmEkAAAAAAAAAUQJqZmZmZmRVAZmZmZmZmEkAAAAAAAAAUQJqZmZmZmRFAmpmZmZmZE0CamZmZmZkVQDMzMzMzMxNAMzMzMzMzE0AzMzMzMzMRQDMzMzMzMxdAzczMzMzMFkCamZmZmZkVQGZmZmZmZhRAzczMzMzMFkBmZmZmZmYUQJqZmZmZmRVAZmZmZmZmFEBmZmZmZmYSQGZmZmZmZhRAMzMzMzMzE0AAAAAAAAAUQAAAAAAAABRAzczMzMzMFEDNzMzMzMwUQM3MzMzMzBJAMzMzMzMzE0CamZmZmZkVQM3MzMzMzBRAAAAAAAAAFkCamZmZmZkTQAAAAAAAABRAAAAAAAAAFkCamZmZmZkTQJqZmZmZmRFAZmZmZmZmFEAAAAAAAAAUQAAAAAAAABJAmpmZmZmZEUAAAAAAAAAUQGZmZmZmZhRAMzMzMzMzE0BmZmZmZmYUQGZmZmZmZhJAMzMzMzMzFUAAAAAAAAAUQA=="}}, {"axis": {"matches": true}, "label": "sepal_width", "values": {"dtype": "f8", "bdata": "AAAAAAAADEAAAAAAAAAIQJqZmZmZmQlAzczMzMzMCEDNzMzMzMwMQDMzMzMzMw9AMzMzMzMzC0AzMzMzMzMLQDMzMzMzMwdAzczMzMzMCECamZmZmZkNQDMzMzMzMwtAAAAAAAAACEAAAAAAAAAIQAAAAAAAABBAmpmZmZmZEUAzMzMzMzMPQAAAAAAAAAxAZmZmZmZmDkBmZmZmZmYOQDMzMzMzMwtAmpmZmZmZDUDNzMzMzMwMQGZmZmZmZgpAMzMzMzMzC0AAAAAAAAAIQDMzMzMzMwtAAAAAAAAADEAzMzMzMzMLQJqZmZmZmQlAzczMzMzMCEAzMzMzMzMLQGZmZmZmZhBAzczMzMzMEEDNzMzMzMwIQJqZmZmZmQlAAAAAAAAADEDNzMzMzMwIQAAAAAAAAAhAMzMzMzMzC0AAAAAAAAAMQGZmZmZmZgJAmpmZmZmZCUAAAAAAAAAMQGZmZmZmZg5AAAAAAAAACEBmZmZmZmYOQJqZmZmZmQlAmpmZmZmZDUBmZmZmZmYKQA=="}}, {"axis": {"matches": true}, "label": "petal_length", "values": {"dtype": "f8", "bdata": "ZmZmZmZm9j9mZmZmZmb2P83MzMzMzPQ/AAAAAAAA+D9mZmZmZmb2PzMzMzMzM/s/ZmZmZmZm9j8AAAAAAAD4P2ZmZmZmZvY/AAAAAAAA+D8AAAAAAAD4P5qZmZmZmfk/ZmZmZmZm9j+amZmZmZnxPzMzMzMzM/M/AAAAAAAA+D/NzMzMzMz0P2ZmZmZmZvY/MzMzMzMz+z8AAAAAAAD4PzMzMzMzM/s/AAAAAAAA+D8AAAAAAADwPzMzMzMzM/s/ZmZmZmZm/j+amZmZmZn5P5qZmZmZmfk/AAAAAAAA+D9mZmZmZmb2P5qZmZmZmfk/mpmZmZmZ+T8AAAAAAAD4PwAAAAAAAPg/ZmZmZmZm9j8AAAAAAAD4PzMzMzMzM/M/zczMzMzM9D8AAAAAAAD4P83MzMzMzPQ/AAAAAAAA+D/NzMzMzMz0P83MzMzMzPQ/zczMzMzM9D+amZmZmZn5P2ZmZmZmZv4/ZmZmZmZm9j+amZmZmZn5P2ZmZmZmZvY/AAAAAAAA+D9mZmZmZmb2Pw=="}}, {"axis": {"matches": true}, "label": "petal_width", "values": {"dtype": "f8", "bdata": "mpmZmZmZyT+amZmZmZnJP5qZmZmZmck/mpmZmZmZyT+amZmZmZnJP5qZmZmZmdk/MzMzMzMz0z+amZmZmZnJP5qZmZmZmck/mpmZmZmZuT+amZmZmZnJP5qZmZmZmck/mpmZmZmZuT+amZmZmZm5P5qZmZmZmck/mpmZmZmZ2T+amZmZmZnZPzMzMzMzM9M/MzMzMzMz0z8zMzMzMzPTP5qZmZmZmck/mpmZmZmZ2T+amZmZmZnJPwAAAAAAAOA/mpmZmZmZyT+amZmZmZnJP5qZmZmZmdk/mpmZmZmZyT+amZmZmZnJP5qZmZmZmck/mpmZmZmZyT+amZmZmZnZP5qZmZmZmbk/mpmZmZmZyT+amZmZmZm5P5qZmZmZmck/mpmZmZmZyT+amZmZmZm5P5qZmZmZmck/mpmZmZmZyT8zMzMzMzPTPzMzMzMzM9M/mpmZmZmZyT8zMzMzMzPjP5qZmZmZmdk/MzMzMzMz0z+amZmZmZnJP5qZmZmZmck/mpmZmZmZyT+amZmZmZnJPw=="}}], "hovertemplate": "species=setosa<br>%{xaxis.title.text}=%{x}<br>%{yaxis.title.text}=%{y}<extra></extra>", "legendgroup": "setosa", "marker": {"color": "#636efa", "symbol": "circle"}, "name": "setosa", "showlegend": true, "type": "splom"}, {"dimensions": [{"axis": {"matches": true}, "label": "sepal_length", "values": {"dtype": "f8", "bdata": "AAAAAAAAHECamZmZmZkZQJqZmZmZmRtAAAAAAAAAFkAAAAAAAAAaQM3MzMzMzBZAMzMzMzMzGUCamZmZmZkTQGZmZmZmZhpAzczMzMzMFEAAAAAAAAAUQJqZmZmZmRdAAAAAAAAAGEBmZmZmZmYYQGZmZmZmZhZAzczMzMzMGkBmZmZmZmYWQDMzMzMzMxdAzczMzMzMGEBmZmZmZmYWQJqZmZmZmRdAZmZmZmZmGEAzMzMzMzMZQGZmZmZmZhhAmpmZmZmZGUBmZmZmZmYaQDMzMzMzMxtAzczMzMzMGkAAAAAAAAAYQM3MzMzMzBZAAAAAAAAAFkAAAAAAAAAWQDMzMzMzMxdAAAAAAAAAGECamZmZmZkVQAAAAAAAABhAzczMzMzMGkAzMzMzMzMZQGZmZmZmZhZAAAAAAAAAFkAAAAAAAAAWQGZmZmZmZhhAMzMzMzMzF0AAAAAAAAAUQGZmZmZmZhZAzczMzMzMFkDNzMzMzMwWQM3MzMzMzBhAZmZmZmZmFEDNzMzMzMwWQA=="}}, {"axis": {"matches": true}, "label": "sepal_width", "values": {"dtype": "f8", "bdata": "mpmZmZmZCUCamZmZmZkJQM3MzMzMzAhAZmZmZmZmAkBmZmZmZmYGQGZmZmZmZgZAZmZmZmZmCkAzMzMzMzMDQDMzMzMzMwdAmpmZmZmZBUAAAAAAAAAAQAAAAAAAAAhAmpmZmZmZAUAzMzMzMzMHQDMzMzMzMwdAzczMzMzMCEAAAAAAAAAIQJqZmZmZmQVAmpmZmZmZAUAAAAAAAAAEQJqZmZmZmQlAZmZmZmZmBkAAAAAAAAAEQGZmZmZmZgZAMzMzMzMzB0AAAAAAAAAIQGZmZmZmZgZAAAAAAAAACEAzMzMzMzMHQM3MzMzMzARAMzMzMzMzA0AzMzMzMzMDQJqZmZmZmQVAmpmZmZmZBUAAAAAAAAAIQDMzMzMzMwtAzczMzMzMCEBmZmZmZmYCQAAAAAAAAAhAAAAAAAAABEDNzMzMzMwEQAAAAAAAAAhAzczMzMzMBEBmZmZmZmYCQJqZmZmZmQVAAAAAAAAACEAzMzMzMzMHQDMzMzMzMwdAAAAAAAAABEBmZmZmZmYGQA=="}}, {"axis": {"matches": true}, "label": "petal_length", "values": {"dtype": "f8", "bdata": "zczMzMzMEkAAAAAAAAASQJqZmZmZmRNAAAAAAAAAEEBmZmZmZmYSQAAAAAAAABJAzczMzMzMEkBmZmZmZmYKQGZmZmZmZhJAMzMzMzMzD0AAAAAAAAAMQM3MzMzMzBBAAAAAAAAAEEDNzMzMzMwSQM3MzMzMzAxAmpmZmZmZEUAAAAAAAAASQGZmZmZmZhBAAAAAAAAAEkAzMzMzMzMPQDMzMzMzMxNAAAAAAAAAEECamZmZmZkTQM3MzMzMzBJAMzMzMzMzEUCamZmZmZkRQDMzMzMzMxNAAAAAAAAAFEAAAAAAAAASQAAAAAAAAAxAZmZmZmZmDkCamZmZmZkNQDMzMzMzMw9AZmZmZmZmFEAAAAAAAAASQAAAAAAAABJAzczMzMzMEkCamZmZmZkRQGZmZmZmZhBAAAAAAAAAEECamZmZmZkRQGZmZmZmZhJAAAAAAAAAEEBmZmZmZmYKQM3MzMzMzBBAzczMzMzMEEDNzMzMzMwQQDMzMzMzMxFAAAAAAAAACEBmZmZmZmYQQA=="}}, {"axis": {"matches": true}, "label": "petal_width", "values": {"dtype": "f8", "bdata": "ZmZmZmZm9j8AAAAAAAD4PwAAAAAAAPg/zczMzMzM9D8AAAAAAAD4P83MzMzMzPQ/mpmZmZmZ+T8AAAAAAADwP83MzMzMzPQ/ZmZmZmZm9j8AAAAAAADwPwAAAAAAAPg/AAAAAAAA8D9mZmZmZmb2P83MzMzMzPQ/ZmZmZmZm9j8AAAAAAAD4PwAAAAAAAPA/AAAAAAAA+D+amZmZmZnxP83MzMzMzPw/zczMzMzM9D8AAAAAAAD4PzMzMzMzM/M/zczMzMzM9D9mZmZmZmb2P2ZmZmZmZvY/MzMzMzMz+z8AAAAAAAD4PwAAAAAAAPA/mpmZmZmZ8T8AAAAAAADwPzMzMzMzM/M/mpmZmZmZ+T8AAAAAAAD4P5qZmZmZmfk/AAAAAAAA+D/NzMzMzMz0P83MzMzMzPQ/zczMzMzM9D8zMzMzMzPzP2ZmZmZmZvY/MzMzMzMz8z8AAAAAAADwP83MzMzMzPQ/MzMzMzMz8z/NzMzMzMz0P83MzMzMzPQ/mpmZmZmZ8T/NzMzMzMz0Pw=="}}], "hovertemplate": "species=versicolor<br>%{xaxis.title.text}=%{x}<br>%{yaxis.title.text}=%{y}<extra></extra>", "legendgroup": "versicolor", "marker": {"color": "#EF553B", "symbol": "circle"}, "name": "versicolor", "showlegend": true, "type": "splom"}, {"dimensions": [{"axis": {"matches": true}, "label": "sepal_length", "values": {"dtype": "f8", "bdata": "MzMzMzMzGUAzMzMzMzMXQGZmZmZmZhxAMzMzMzMzGUAAAAAAAAAaQGZmZmZmZh5AmpmZmZmZE0AzMzMzMzMdQM3MzMzMzBpAzczMzMzMHEAAAAAAAAAaQJqZmZmZmRlAMzMzMzMzG0DNzMzMzMwWQDMzMzMzMxdAmpmZmZmZGUAAAAAAAAAaQM3MzMzMzB5AzczMzMzMHkAAAAAAAAAYQJqZmZmZmRtAZmZmZmZmFkDNzMzMzMweQDMzMzMzMxlAzczMzMzMGkDNzMzMzMwcQM3MzMzMzBhAZmZmZmZmGECamZmZmZkZQM3MzMzMzBxAmpmZmZmZHUCamZmZmZkfQJqZmZmZmRlAMzMzMzMzGUBmZmZmZmYYQM3MzMzMzB5AMzMzMzMzGUCamZmZmZkZQAAAAAAAABhAmpmZmZmZG0DNzMzMzMwaQJqZmZmZmRtAMzMzMzMzF0AzMzMzMzMbQM3MzMzMzBpAzczMzMzMGkAzMzMzMzMZQAAAAAAAABpAzczMzMzMGECamZmZmZkXQA=="}}, {"axis": {"matches": true}, "label": "sepal_width", "values": {"dtype": "f8", "bdata": "ZmZmZmZmCkCamZmZmZkFQAAAAAAAAAhAMzMzMzMzB0AAAAAAAAAIQAAAAAAAAAhAAAAAAAAABEAzMzMzMzMHQAAAAAAAAARAzczMzMzMDECamZmZmZkJQJqZmZmZmQVAAAAAAAAACEAAAAAAAAAEQGZmZmZmZgZAmpmZmZmZCUAAAAAAAAAIQGZmZmZmZg5AzczMzMzMBECamZmZmZkBQJqZmZmZmQlAZmZmZmZmBkBmZmZmZmYGQJqZmZmZmQVAZmZmZmZmCkCamZmZmZkJQGZmZmZmZgZAAAAAAAAACEBmZmZmZmYGQAAAAAAAAAhAZmZmZmZmBkBmZmZmZmYOQGZmZmZmZgZAZmZmZmZmBkDNzMzMzMwEQAAAAAAAAAhAMzMzMzMzC0DNzMzMzMwIQAAAAAAAAAhAzczMzMzMCEDNzMzMzMwIQM3MzMzMzAhAmpmZmZmZBUCamZmZmZkJQGZmZmZmZgpAAAAAAAAACEAAAAAAAAAEQAAAAAAAAAhAMzMzMzMzC0AAAAAAAAAIQA=="}}, {"axis": {"matches": true}, "label": "petal_length", "values": {"dtype": "f8", "bdata": "AAAAAAAAGEBmZmZmZmYUQJqZmZmZmRdAZmZmZmZmFkAzMzMzMzMXQGZmZmZmZhpAAAAAAAAAEkAzMzMzMzMZQDMzMzMzMxdAZmZmZmZmGEBmZmZmZmYUQDMzMzMzMxVAAAAAAAAAFkAAAAAAAAAUQGZmZmZmZhRAMzMzMzMzFUAAAAAAAAAWQM3MzMzMzBpAmpmZmZmZG0AAAAAAAAAUQM3MzMzMzBZAmpmZmZmZE0DNzMzMzMwaQJqZmZmZmRNAzczMzMzMFkAAAAAAAAAYQDMzMzMzMxNAmpmZmZmZE0BmZmZmZmYWQDMzMzMzMxdAZmZmZmZmGECamZmZmZkZQGZmZmZmZhZAZmZmZmZmFEBmZmZmZmYWQGZmZmZmZhhAZmZmZmZmFkAAAAAAAAAWQDMzMzMzMxNAmpmZmZmZFUBmZmZmZmYWQGZmZmZmZhRAZmZmZmZmFECamZmZmZkXQM3MzMzMzBZAzczMzMzMFEAAAAAAAAAUQM3MzMzMzBRAmpmZmZmZFUBmZmZmZmYUQA=="}}, {"axis": {"matches": true}, "label": "petal_width", "values": {"dtype": "f8", "bdata": "AAAAAAAABEBmZmZmZmb+P83MzMzMzABAzczMzMzM/D+amZmZmZkBQM3MzMzMzABAMzMzMzMz+z/NzMzMzMz8P83MzMzMzPw/AAAAAAAABEAAAAAAAAAAQGZmZmZmZv4/zczMzMzMAEAAAAAAAAAAQDMzMzMzMwNAZmZmZmZmAkDNzMzMzMz8P5qZmZmZmQFAZmZmZmZmAkAAAAAAAAD4P2ZmZmZmZgJAAAAAAAAAAEAAAAAAAAAAQM3MzMzMzPw/zczMzMzMAEDNzMzMzMz8P83MzMzMzPw/zczMzMzM/D/NzMzMzMwAQJqZmZmZmfk/ZmZmZmZm/j8AAAAAAAAAQJqZmZmZmQFAAAAAAAAA+D9mZmZmZmb2P2ZmZmZmZgJAMzMzMzMzA0DNzMzMzMz8P83MzMzMzPw/zczMzMzMAEAzMzMzMzMDQGZmZmZmZgJAZmZmZmZm/j9mZmZmZmYCQAAAAAAAAARAZmZmZmZmAkBmZmZmZmb+PwAAAAAAAABAZmZmZmZmAkDNzMzMzMz8Pw=="}}], "hovertemplate": "species=virginica<br>%{xaxis.title.text}=%{x}<br>%{yaxis.title.text}=%{y}<extra></extra>", "legendgroup": "virginica", "marker": {"color": "#00cc96", "symbol": "circle"}, "name": "virginica", "showlegend": true, "type": "splom"}];
var LAYOUT = {"legend": {"title": {"text": "species"}, "tracegroupgap": 0}, "margin": {"t": 60}, "dragmode": "select", "xaxis": {"matches": "y"}, "xaxis2": {"matches": "y2"}, "xaxis3": {"matches": "y3"}, "xaxis4": {"matches": "y4"}, "height": 900, "width": 750, "selections": [{"x0": 3, "x1": 4, "xref": "x2", "y0": 8, "y1": 6, "yref": "y"}, {"x0": 5, "x1": 1, "xref": "x3", "y0": 5, "y1": 4, "yref": "y"}]};
function clone(o) { return JSON.parse(JSON.stringify(o)); }
function cb(gd) { return JSON.stringify(gd.data[1].marker.colorbar || {}); }
function ax(gd) { return gd._fullLayout.xaxis ? Math.round(gd._fullLayout.xaxis._length) + 'px' : '?'; }
function dump(gd, el) {
var cbs = gd.querySelectorAll('.colorbar');
var w = cbs.length ? Math.round(cbs[cbs.length-1].getBoundingClientRect().width) + 'px' : 'none';
el.textContent = 'trace1.marker.colorbar = ' + cb(gd) +
'\npanel width (xaxis._length) = ' + ax(gd) +
'\nrendered colorbar width = ' + w;
}
// Path A
var gdA = document.getElementById('A');
var dataA = clone(DATA);
dataA[1].marker.colorbar = { thicknessmode: 'pixels' };
Plotly.newPlot(gdA, dataA, clone(LAYOUT)).then(function () {
dump(gdA, document.getElementById('mA'));
});
// Path B
var gdB = document.getElementById('B');
var dataB = clone(DATA);
dataB[1].marker.colorbar = {};
Plotly.newPlot(gdB, dataB, clone(LAYOUT)).then(function () {
dump(gdB, document.getElementById('mB'));
return Plotly.restyle(gdB, { 'marker.colorbar.thicknessmode': 'pixels' }, [1]);
}).then(function () {
dump(gdB, document.getElementById('mB'));
});
</script>
</body>
</html>
(Note: with random synthetic splom data the exact numbers differ — e.g. thickness:17250,
axis len ~15 — but the collapse reproduces identically.)
Description
For a
splomtrace whosemarker.colorbarexists but has no explicitthickness(plotly.js defaults it), applying
marker.colorbar.thicknessmode='pixels'afterwards withPlotly.restylecollapses the whole splom layout:thickness: 16290back into the user-visible trace(
gd.data[1].marker.colorbar.thickness),Setting the exact same value at
newPlottime (Path A in the repro) renders fine.Reproduced on every version I could test: 2.35.2, 3.5.0, 3.5.1, and current 3.8.2.
Steps to reproduce
Save the following as
index.htmland open it in a browser(uses
https://cdn.plot.ly/plotly-3.5.0.min.js; the bundled copy from any release works too).The page renders both paths, then prints the colorbar object and the panel width under each plot.
Actual vs expected (measured with plotly 3.8.2, headless Chrome)
newPlot)restyle)trace[1].marker.colorbarafter render{"thicknessmode":"pixels"}{"thickness":16290,"thicknessmode":"pixels"}_fullLayout.xaxis._length)Expected: Path B behaves like Path A — thickness defaulted to ~30 px for
pixelsmode,panels stay 125 px.
(Note: with random synthetic splom data the exact numbers differ — e.g.
thickness:17250,axis len ~15— but the collapse reproduces identically.)Analysis
colorbar.thicknessis size-dependent when left at its default: infractionmode thedefault is ~
30 / plotHeight(a small fraction), converted to pixels only at calc time.When
Plotly.restyleflipsthicknessmodetopixelson a colorbar that still holds thefraction-mode default, the default is apparently not re-coerced for the new mode — the
old tiny fraction survives, is interpreted as raw pixels on the next colorbar calc, and the
result (~16k px) is also written back into
gd.data(user-visible trace), not just_fullLayout.Evidence that fits this reading:
thicknesstogether withthicknessmode(
{'marker.colorbar.thickness': 20, 'marker.colorbar.thicknessmode': 'pixels'}) worksfine — an explicit value bypasses the buggy default path.
(
gd.data[1].marker.colorbar.thickness === 16290), so a defaults-coercion result leaksback into
gd.data.First reported at plotly/plotly.py#5615;
the plotly.py maintainers asked for a pure-JS reproduction here.
Workaround
Include an explicit
thicknessin the same restyle call: