From bd64e2357cd6287445100ace7138cba9421fc386 Mon Sep 17 00:00:00 2001 From: Tornacyclo Date: Tue, 10 Nov 2026 11:32:44 +0100 Subject: [PATCH 1/2] Merge the located rangebreaks that one new break overlaps addBreak widened every located break that a new break overlapped, but it did not merge them. A holiday break between two overnight breaks thus counted 24 hours two times in the axis scale and in the autorange break length. The axis lost one day per holiday, or the l2p slope became negative and the trace did not appear. --- draftlogs/XXXX_fix.md | 1 + src/plots/cartesian/set_convert.js | 26 +++++++------ test/jasmine/tests/axes_test.js | 59 +++++++++++++++++++++++++++++- 3 files changed, 73 insertions(+), 13 deletions(-) create mode 100644 draftlogs/XXXX_fix.md diff --git a/draftlogs/XXXX_fix.md b/draftlogs/XXXX_fix.md new file mode 100644 index 00000000000..94fba664ba6 --- /dev/null +++ b/draftlogs/XXXX_fix.md @@ -0,0 +1 @@ + - Fix `rangebreaks` when one break overlaps two other breaks, which hid the plot or drew later data over earlier data [[#XXXX](https://github.com/plotly/plotly.js/pull/XXXX)] diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js index b4ea95f235d..c268a267eed 100644 --- a/src/plots/cartesian/set_convert.js +++ b/src/plots/cartesian/set_convert.js @@ -774,22 +774,24 @@ module.exports = function setConvert(ax, fullLayout) { max = Lib.constrain(max, r0, r1); if(min === max) return; - var isNewBreak = true; - for(var j = 0; j < rangebreaksOut.length; j++) { - var brkj = rangebreaksOut[j]; + let merged; + for(let j = 0; j < rangebreaksOut.length; j++) { + const brkj = rangebreaksOut[j]; if(min < brkj.max && max >= brkj.min) { - if(min < brkj.min) { - brkj.min = min; - } - if(max > brkj.max) { - brkj.max = max; + if(merged) { + // The axis code sums the break lengths, so the located breaks must not overlap. + merged.min = Math.min(merged.min, brkj.min); + merged.max = Math.max(merged.max, brkj.max); + rangebreaksOut.splice(j, 1); + j--; + } else { + merged = brkj; + merged.min = Math.min(merged.min, min); + merged.max = Math.max(merged.max, max); } - isNewBreak = false; } } - if(isNewBreak) { - rangebreaksOut.push({min: min, max: max}); - } + if(!merged) rangebreaksOut.push({min, max}); }; for(i = 0; i < rangebreaksIn.length; i++) { diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js index 38b959c36d0..0754bd80fa9 100644 --- a/test/jasmine/tests/axes_test.js +++ b/test/jasmine/tests/axes_test.js @@ -13,7 +13,7 @@ var Cartesian = require('../../../src/plots/cartesian'); var Axes = require('../../../src/plots/cartesian/axes'); var Fx = require('../../../src/components/fx'); var supplyLayoutDefaults = require('../../../src/plots/cartesian/layout_defaults'); -const {BADNUM, MINUS_SIGN, ONEDAY, ONEWEEK} = require('../../../src/constants/numerical'); +const {BADNUM, MINUS_SIGN, ONEDAY, ONEHOUR, ONEWEEK} = require('../../../src/constants/numerical'); var createGraphDiv = require('../assets/create_graph_div'); var destroyGraphDiv = require('../assets/destroy_graph_div'); @@ -6062,6 +6062,63 @@ describe('Test axes', function() { }) .then(done, done.fail); }); + + it('should merge a holiday rangebreak that overlaps two located breaks', (done) => { + const assertBreaks = (msg, exp) => { + const xa = gd._fullLayout.xaxis; + const brks = xa._rangebreaks.map((brk) => [brk.min, brk.max]); + + for(let i = 1; i < brks.length; i++) { + expect(brks[i][0]) + .withContext(`${msg} | break ${i} starts before break ${i - 1} ends`) + .not.toBeLessThan(brks[i - 1][1]); + } + expect(brks).withContext(`${msg} | merged break`).toContain(exp.merged.map(Lib.dateTime2ms)); + expect(xa._lBreaks).withContext(`${msg} | total break length`).toBe(exp.lBreaks); + expect(xa._m2).withContext(`${msg} | l2p slope`).toBeGreaterThan(0); + }; + const layout = (range, rangebreaks) => ({xaxis: {range, rangebreaks}}); + + // Thanksgiving 2023 is a Thursday. Its break bridges the overnight breaks before and after it. + const thanksgivingRange = ['2023-11-20 09:30', '2023-11-24 13:00']; + const thanksgiving = [ + {pattern: 'day of week', bounds: [6, 1]}, + {pattern: 'hour', bounds: [16, 9.5]}, + {values: ['2023-11-23']} + ]; + const thanksgivingBreaks = { + merged: ['2023-11-22 16:00', '2023-11-24 09:30'], + lBreaks: 76.5 * ONEHOUR + }; + + // Christmas 2020 is a Friday. Its break bridges the Thursday overnight break and the weekend break. + const christmasRange = ['2020-12-14 09:00', '2020-12-31 15:00']; + const christmas = [ + {pattern: 'hour', bounds: [15, 9]}, + {pattern: 'day of week', bounds: [6, 1]}, + {values: ['2020-12-25']} + ]; + + Plotly.newPlot(gd, [{x: thanksgivingRange}], layout(thanksgivingRange, thanksgiving)) + .then(() => { + assertBreaks('Thanksgiving', thanksgivingBreaks); + + const reversed = thanksgiving.slice().reverse(); + return Plotly.react(gd, [{x: thanksgivingRange}], layout(thanksgivingRange, reversed)); + }) + .then(() => { + assertBreaks('Thanksgiving, reversed rangebreaks', thanksgivingBreaks); + + return Plotly.react(gd, [{x: christmasRange}], layout(christmasRange, christmas)); + }) + .then(() => { + assertBreaks('Christmas', { + merged: ['2020-12-24 15:00', '2020-12-28 09:00'], + lBreaks: 336 * ONEHOUR + }); + }) + .then(done, done.fail); + }); }); describe('during calcTicks', function() { From 368133017cbdd0ca60eafed11cd790d09730d0f7 Mon Sep 17 00:00:00 2001 From: Tornacyclo Date: Tue, 10 Nov 2026 12:12:12 +0100 Subject: [PATCH 2/2] Rename the draftlog to the pull request number --- draftlogs/8167_fix.md | 1 + draftlogs/XXXX_fix.md | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 draftlogs/8167_fix.md delete mode 100644 draftlogs/XXXX_fix.md diff --git a/draftlogs/8167_fix.md b/draftlogs/8167_fix.md new file mode 100644 index 00000000000..541c11ea856 --- /dev/null +++ b/draftlogs/8167_fix.md @@ -0,0 +1 @@ +- Fix `rangebreaks` when one break overlaps two other breaks, which hid the plot or drew later data over earlier data [[#8167](https://github.com/plotly/plotly.js/pull/8167)] \ No newline at end of file diff --git a/draftlogs/XXXX_fix.md b/draftlogs/XXXX_fix.md deleted file mode 100644 index 94fba664ba6..00000000000 --- a/draftlogs/XXXX_fix.md +++ /dev/null @@ -1 +0,0 @@ - - Fix `rangebreaks` when one break overlaps two other breaks, which hid the plot or drew later data over earlier data [[#XXXX](https://github.com/plotly/plotly.js/pull/XXXX)]