From 14e612a4acfd557f472563ee18cce328189ca9ab Mon Sep 17 00:00:00 2001 From: Ya-Fan Chen <20377719+Lexachoc@users.noreply.github.com> Date: Sun, 3 Aug 2025 02:31:46 +0200 Subject: [PATCH 1/5] fix incorrect autobin size when data changes --- src/traces/histogram/calc.js | 5 ++-- test/jasmine/tests/histogram_test.js | 37 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/src/traces/histogram/calc.js b/src/traces/histogram/calc.js index e705ddd36fa..aa995f6c138 100644 --- a/src/traces/histogram/calc.js +++ b/src/traces/histogram/calc.js @@ -308,10 +308,11 @@ function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) { // Edge case: single-valued histogram overlaying others // Use them all together to calculate the bin size for the single-valued one - // Don't re-calculate bin width if user manually specified it (checing in bingroup=='' or xbins is defined) + // Don't re-calculate bin width if user manually specified it + // Check if bingroup or trace[binAttr] (xbins or ybins) is defined if(isOverlay && !Registry.traceIs(trace, '2dMap') && newBinSpec._dataSpan === 0 && pa.type !== 'category' && pa.type !== 'multicategory' && - trace.bingroup === '' && (typeof trace.xbins === 'undefined')) { + trace.bingroup === '' && trace._input?.[binAttr]?.size === undefined) { // Several single-valued histograms! Stop infinite recursion, // just return an extra flag that tells handleSingleValueOverlays // to sort out this trace too diff --git a/test/jasmine/tests/histogram_test.js b/test/jasmine/tests/histogram_test.js index d6136772b5f..08bf9082b5d 100644 --- a/test/jasmine/tests/histogram_test.js +++ b/test/jasmine/tests/histogram_test.js @@ -1335,6 +1335,43 @@ describe('Test histogram', function() { }) .then(done, done.fail); }); + + it('should correctly recalculate autobin size for single-value overlays on Plotly.react', function(done) { + var initialData = [ + {x: ['1457'], type: 'histogram'}, + {x: ['820'], type: 'histogram'}, + {x: ['720'], type: 'histogram'} + ]; + + var layout = { + barmode: 'overlay' + }; + + Plotly.newPlot(gd, initialData, layout) + .then(function() { + // minDiff = 820 - 720 = 100 + console.log("gd._fullData=", gd._fullData); + expect(gd._fullData[0].xbins.size).toBe(100); + expect(gd._fullData[1].xbins.size).toBe(100); + expect(gd._fullData[2].xbins.size).toBe(100); + + // Use a new trace data object + var newData = [ + {x: ['1400'], type: 'histogram'}, + {x: ['800'], type: 'histogram'}, + {x: ['600'], type: 'histogram'} + ]; + + return Plotly.react(gd, newData, layout); + }) + .then(function() { + // minDiff = 800 - 600 = 200. + expect(gd._fullData[0].xbins.size).toBe(200); + expect(gd._fullData[1].xbins.size).toBe(200); + expect(gd._fullData[2].xbins.size).toBe(200); + }) + .then(done, done.fail); + }); }); }); From 17613d2eadcfa9d0107471f867d867c2e6935c35 Mon Sep 17 00:00:00 2001 From: Ya-Fan Chen <20377719+Lexachoc@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:27:31 +0200 Subject: [PATCH 2/5] Create 7507_fix.md --- draftlogs/7507_fix.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 draftlogs/7507_fix.md diff --git a/draftlogs/7507_fix.md b/draftlogs/7507_fix.md new file mode 100644 index 00000000000..3ac7d1b5cd7 --- /dev/null +++ b/draftlogs/7507_fix.md @@ -0,0 +1 @@ +Fix `histogram` autobin size for single-point traces in `overlay` mode on data updates via `Plotly.react` [[#7507](https://github.com/plotly/plotly.js/pull/7507)] From f3bc51f1ffd0ffe17a42783f93bdeb8289338112 Mon Sep 17 00:00:00 2001 From: Ya-Fan Chen <20377719+Lexachoc@users.noreply.github.com> Date: Sun, 3 Aug 2025 13:37:29 +0200 Subject: [PATCH 3/5] Update calc.js comment --- src/traces/histogram/calc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/histogram/calc.js b/src/traces/histogram/calc.js index aa995f6c138..7268e3eaab2 100644 --- a/src/traces/histogram/calc.js +++ b/src/traces/histogram/calc.js @@ -309,7 +309,7 @@ function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) { // Edge case: single-valued histogram overlaying others // Use them all together to calculate the bin size for the single-valued one // Don't re-calculate bin width if user manually specified it - // Check if bingroup or trace[binAttr] (xbins or ybins) is defined + // Check if bingroup or bin size is defined if(isOverlay && !Registry.traceIs(trace, '2dMap') && newBinSpec._dataSpan === 0 && pa.type !== 'category' && pa.type !== 'multicategory' && trace.bingroup === '' && trace._input?.[binAttr]?.size === undefined) { From 38fd2f99355fcd59e623730962a357b9f9204cca Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Mon, 13 Jul 2026 11:34:36 -0600 Subject: [PATCH 4/5] Refactor conditional check and update draftlog --- draftlogs/7507_fix.md | 2 +- src/traces/histogram/calc.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/draftlogs/7507_fix.md b/draftlogs/7507_fix.md index 3ac7d1b5cd7..2da2ac29e8d 100644 --- a/draftlogs/7507_fix.md +++ b/draftlogs/7507_fix.md @@ -1 +1 @@ -Fix `histogram` autobin size for single-point traces in `overlay` mode on data updates via `Plotly.react` [[#7507](https://github.com/plotly/plotly.js/pull/7507)] +Fix `histogram` autobin size for single-point traces in `overlay` mode on data updates via `Plotly.react` [[#7507](https://github.com/plotly/plotly.js/pull/7507)], with thanks to @Lexachoc for the contribution! diff --git a/src/traces/histogram/calc.js b/src/traces/histogram/calc.js index d36f10f2e90..8f57a3d5365 100644 --- a/src/traces/histogram/calc.js +++ b/src/traces/histogram/calc.js @@ -328,10 +328,9 @@ function calcAllAutoBins(gd, trace, pa, mainData, _overlayEdgeCase) { // Edge case: single-valued histogram overlaying others // Use them all together to calculate the bin size for the single-valued one // Don't re-calculate bin width if user manually specified it - // Check if bingroup or bin size is defined if(isOverlay && !Registry.traceIs(trace, '2dMap') && newBinSpec._dataSpan === 0 && pa.type !== 'category' && pa.type !== 'multicategory' && - trace.bingroup === '' && trace._input?.[binAttr]?.size === undefined) { + trace.bingroup === '' && !trace._input[binAttr]?.size) { // Several single-valued histograms! Stop infinite recursion, // just return an extra flag that tells handleSingleValueOverlays // to sort out this trace too From e87fee19d5cb4a5cd62a93f34a06b1e284b01e40 Mon Sep 17 00:00:00 2001 From: Cameron DeCoster Date: Mon, 13 Jul 2026 11:43:08 -0600 Subject: [PATCH 5/5] Add additional test --- test/jasmine/tests/histogram_test.js | 34 +++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/test/jasmine/tests/histogram_test.js b/test/jasmine/tests/histogram_test.js index 08bf9082b5d..7f28f468b25 100644 --- a/test/jasmine/tests/histogram_test.js +++ b/test/jasmine/tests/histogram_test.js @@ -1350,7 +1350,6 @@ describe('Test histogram', function() { Plotly.newPlot(gd, initialData, layout) .then(function() { // minDiff = 820 - 720 = 100 - console.log("gd._fullData=", gd._fullData); expect(gd._fullData[0].xbins.size).toBe(100); expect(gd._fullData[1].xbins.size).toBe(100); expect(gd._fullData[2].xbins.size).toBe(100); @@ -1372,6 +1371,39 @@ describe('Test histogram', function() { }) .then(done, done.fail); }); + + it('should preserve user-specified xbins.size for single-value overlays on Plotly.react', function(done) { + var initialData = [ + {x: ['1457'], type: 'histogram', xbins: {size: 50}}, + {x: ['820'], type: 'histogram', xbins: {size: 50}}, + {x: ['720'], type: 'histogram', xbins: {size: 50}} + ]; + + var layout = { + barmode: 'overlay' + }; + + Plotly.newPlot(gd, initialData, layout) + .then(function() { + expect(gd._fullData[0].xbins.size).toBe(50); + expect(gd._fullData[1].xbins.size).toBe(50); + expect(gd._fullData[2].xbins.size).toBe(50); + + var newData = [ + {x: ['1400'], type: 'histogram', xbins: {size: 50}}, + {x: ['800'], type: 'histogram', xbins: {size: 50}}, + {x: ['600'], type: 'histogram', xbins: {size: 50}} + ]; + + return Plotly.react(gd, newData, layout); + }) + .then(function() { + expect(gd._fullData[0].xbins.size).toBe(50); + expect(gd._fullData[1].xbins.size).toBe(50); + expect(gd._fullData[2].xbins.size).toBe(50); + }) + .then(done, done.fail); + }); }); });