From dbc9a519ef666e32d0794c9dd8a71dc7b495f580 Mon Sep 17 00:00:00 2001 From: vizansh Date: Sun, 28 Jun 2026 01:04:42 +0530 Subject: [PATCH 01/13] fix(bar): prevent outside text labels from overlapping tilted axis ticks --- src/traces/bar/plot.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 37653f9c3a8..d74e192ebed 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -707,7 +707,9 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op transform = toMoveOutsideBar(x0, x1, y0, y1, textBB, { isHorizontal: isHorizontal, constrained: constrained, - angle: angle + angle: angle, + xa: xa, // Pass the X-Axis configuration + ya: ya // Pass the Y-Axis configuration }); } else { constrained = trace.constraintext === 'both' || trace.constraintext === 'inside'; @@ -957,12 +959,32 @@ function toMoveOutsideBar(x0, x1, y0, y1, textBB, opts) { var anchorX = 0; var anchorY = 0; + // Dynamic presentation safety buffer to clear tilted axis tick labels + var axisPad = 0; + if (!isHorizontal && opts.xa && opts.xa.side === 'top') { + if (opts.xa._g && opts.xa._g.node()) { + var axisBB = opts.xa._g.node().getBBox(); + if (axisBB && axisBB.height > 0) { + // Shift exactly past the bounding height of the tilted labels plus a clean 6px visual gap + axisPad = axisBB.height + 6; + } + } + } else if (isHorizontal && opts.ya && opts.ya.side === 'right') { + if (opts.ya._g && opts.ya._g.node()) { + var axisBB = opts.ya._g.node().getBBox(); + if (axisBB && axisBB.width > 0) { + // Shift exactly past the bounding width of the side labels plus a clean 6px visual gap + axisPad = axisBB.width + 6; + } + } + } + var dir = isHorizontal ? dirSign(x1, x0) : dirSign(y0, y1); if (isHorizontal) { - targetX = x1 - dir * textpad; + targetX = x1 - dir * (textpad + axisPad); anchorX = dir * extrapad; } else { - targetY = y1 + dir * textpad; + targetY = y1 + dir * (textpad + axisPad); anchorY = -dir * extrapad; } From 8c78dd6a80e0960b0dccaf8f12fbe8ab756cedb0 Mon Sep 17 00:00:00 2001 From: vizansh Date: Thu, 2 Jul 2026 17:20:22 +0530 Subject: [PATCH 02/13] chore: add draftlog for bar text overlap fix --- draftlogs/7822_fix.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 draftlogs/7822_fix.md diff --git a/draftlogs/7822_fix.md b/draftlogs/7822_fix.md new file mode 100644 index 00000000000..bab294af9d9 --- /dev/null +++ b/draftlogs/7822_fix.md @@ -0,0 +1 @@ +- Prevent outside bar text labels from overlapping tilted axis ticks From 9ea39bcb70aca960df01ebc124b962860afb573e Mon Sep 17 00:00:00 2001 From: vizansh Date: Fri, 3 Jul 2026 18:45:27 +0530 Subject: [PATCH 03/13] fix: add required PR link formatting to draftlog --- draftlogs/7822_fix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/draftlogs/7822_fix.md b/draftlogs/7822_fix.md index bab294af9d9..3b4e4a25e56 100644 --- a/draftlogs/7822_fix.md +++ b/draftlogs/7822_fix.md @@ -1 +1 @@ -- Prevent outside bar text labels from overlapping tilted axis ticks +- Prevent outside bar text labels from overlapping tilted axis ticks [[#7872](https://github.com/plotly/plotly.js/pull/7872)] \ No newline at end of file From 6b1363e5a43a996007038188e51c0525691331b6 Mon Sep 17 00:00:00 2001 From: vizansh Date: Thu, 16 Jul 2026 04:50:08 +0530 Subject: [PATCH 04/13] fix(bar): keep zero-value outside labels on one side --- src/traces/bar/plot.js | 39 +++++++++++++++++++++++++++++++++- test/jasmine/tests/bar_test.js | 28 ++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index d74e192ebed..3b13b62c352 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -554,6 +554,7 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op // get trace attributes var trace = cd[0].trace; var isHorizontal = trace.orientation === 'h'; + var zeroBarDir = getZeroBarDir(cd, isHorizontal, xa, ya); var text = getText(fullLayout, cd, i, xa, ya); @@ -709,7 +710,8 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op constrained: constrained, angle: angle, xa: xa, // Pass the X-Axis configuration - ya: ya // Pass the Y-Axis configuration + ya: ya, // Pass the Y-Axis configuration + zeroBarDir: zeroBarDir }); } else { constrained = trace.constraintext === 'both' || trace.constraintext === 'inside'; @@ -980,6 +982,9 @@ function toMoveOutsideBar(x0, x1, y0, y1, textBB, opts) { } var dir = isHorizontal ? dirSign(x1, x0) : dirSign(y0, y1); + if ((isHorizontal ? x0 === x1 : y0 === y1) && opts.zeroBarDir) { + dir = opts.zeroBarDir; + } if (isHorizontal) { targetX = x1 - dir * (textpad + axisPad); anchorX = dir * extrapad; @@ -1021,6 +1026,38 @@ function getTextPosition(trace, index) { return helpers.coerceEnumerated(attributeTextPosition, value); } +function getZeroBarDir(cd, isHorizontal, xa, ya) { + var hasPositive = false; + var hasNegative = false; + + for (var i = 0; i < cd.length; i++) { + var s = cd[i].s; + + if (s > 0) { + hasPositive = true; + } else if (s < 0) { + hasNegative = true; + } + + if (hasPositive && hasNegative) { + return 0; + } + } + + var axis = isHorizontal ? xa : ya; + var positiveDir = -dirSign(axis.range[0], axis.range[1]); + + if (!hasNegative) { + return positiveDir; + } + + if (!hasPositive) { + return -positiveDir; + } + + return 0; +} + function calcTexttemplate(fullLayout, cd, index, xa, ya) { var trace = cd[0].trace; var texttemplate = Lib.castOption(trace, index, 'texttemplate'); diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js index 107021ba1cd..e77a7a00820 100644 --- a/test/jasmine/tests/bar_test.js +++ b/test/jasmine/tests/bar_test.js @@ -1411,6 +1411,34 @@ describe('A bar plot', function() { .then(done, done.fail); }); + it('should keep zero-value outside labels on the same side as negative bars', function(done) { + var data = [{ + y: [-10, 0, -30], + type: 'bar', + text: ['a', 'zero', 'c'], + textposition: 'outside' + }]; + + Plotly.newPlot(gd, data).then(function() { + var traceNodes = getAllTraceNodes(gd); + var barNodes = getAllBarNodes(traceNodes[0]); + var foundTextNodes; + + for(var i = 0; i < barNodes.length; i++) { + var barNode = barNodes[i]; + var pathNode = barNode.querySelector('path'); + var textNode = barNode.querySelector('text'); + if(textNode) { + foundTextNodes = true; + assertTextIsBelowPath(textNode, pathNode); + } + } + + expect(foundTextNodes).toBe(true); + }) + .then(done, done.fail); + }); + it('should show bar texts (horizontal case)', function(done) { var data = [{ x: [10, -20, 30], From 73c8ab510269b0c8e691c40a9f6b1d4603a95a71 Mon Sep 17 00:00:00 2001 From: Vansh Goyal Date: Sat, 18 Jul 2026 01:12:15 +0530 Subject: [PATCH 05/13] fix: export toMoveOutsideBar --- src/traces/bar/plot.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 3b13b62c352..7e82a635516 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -1219,5 +1219,6 @@ function calcTextinfo(cd, index, xa, ya) { module.exports = { plot: plot, - toMoveInsideBar: toMoveInsideBar + toMoveInsideBar: toMoveInsideBar, + toMoveOutsideBar: toMoveOutsideBar }; From a61530b35a991c8a68fbb9545658705e20d8db08 Mon Sep 17 00:00:00 2001 From: Vansh Goyal Date: Tue, 28 Jul 2026 18:27:56 +0530 Subject: [PATCH 06/13] fix: rename draftlog file to match PR 7872 --- draftlogs/{7822_fix.md => 7872_fix.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename draftlogs/{7822_fix.md => 7872_fix.md} (50%) diff --git a/draftlogs/7822_fix.md b/draftlogs/7872_fix.md similarity index 50% rename from draftlogs/7822_fix.md rename to draftlogs/7872_fix.md index 3b4e4a25e56..a26177d7d5c 100644 --- a/draftlogs/7822_fix.md +++ b/draftlogs/7872_fix.md @@ -1 +1 @@ -- Prevent outside bar text labels from overlapping tilted axis ticks [[#7872](https://github.com/plotly/plotly.js/pull/7872)] \ No newline at end of file +- Prevent outside bar text labels from overlapping tilted axis ticks [[#7872](https://github.com/plotly/plotly.js/pull/7872)] From f291d70fb79d2e5b077aecf8eb05ec335437329a Mon Sep 17 00:00:00 2001 From: Vansh Goyal Date: Thu, 30 Jul 2026 18:36:08 +0530 Subject: [PATCH 07/13] refactor: apply reviewer suggestions for zeroBarDir Co-authored-by: Emily KL <4672118+emilykl@users.noreply.github.com> --- draftlogs/7872_fix.md | 2 +- src/traces/bar/plot.js | 23 ----------------------- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/draftlogs/7872_fix.md b/draftlogs/7872_fix.md index a26177d7d5c..f7c8c63eade 100644 --- a/draftlogs/7872_fix.md +++ b/draftlogs/7872_fix.md @@ -1 +1 @@ -- Prevent outside bar text labels from overlapping tilted axis ticks [[#7872](https://github.com/plotly/plotly.js/pull/7872)] +- Prevent outside bar text for zero-length bars from overlapping axis tick labels [[#7872](https://github.com/plotly/plotly.js/pull/7872)] diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 7e82a635516..134f2e7b5da 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -708,9 +708,6 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op transform = toMoveOutsideBar(x0, x1, y0, y1, textBB, { isHorizontal: isHorizontal, constrained: constrained, - angle: angle, - xa: xa, // Pass the X-Axis configuration - ya: ya, // Pass the Y-Axis configuration zeroBarDir: zeroBarDir }); } else { @@ -961,26 +958,6 @@ function toMoveOutsideBar(x0, x1, y0, y1, textBB, opts) { var anchorX = 0; var anchorY = 0; - // Dynamic presentation safety buffer to clear tilted axis tick labels - var axisPad = 0; - if (!isHorizontal && opts.xa && opts.xa.side === 'top') { - if (opts.xa._g && opts.xa._g.node()) { - var axisBB = opts.xa._g.node().getBBox(); - if (axisBB && axisBB.height > 0) { - // Shift exactly past the bounding height of the tilted labels plus a clean 6px visual gap - axisPad = axisBB.height + 6; - } - } - } else if (isHorizontal && opts.ya && opts.ya.side === 'right') { - if (opts.ya._g && opts.ya._g.node()) { - var axisBB = opts.ya._g.node().getBBox(); - if (axisBB && axisBB.width > 0) { - // Shift exactly past the bounding width of the side labels plus a clean 6px visual gap - axisPad = axisBB.width + 6; - } - } - } - var dir = isHorizontal ? dirSign(x1, x0) : dirSign(y0, y1); if ((isHorizontal ? x0 === x1 : y0 === y1) && opts.zeroBarDir) { dir = opts.zeroBarDir; From 33e435731e63127ce71f39b127491d4ffc9e0d41 Mon Sep 17 00:00:00 2001 From: Vansh Goyal Date: Thu, 30 Jul 2026 18:53:27 +0530 Subject: [PATCH 08/13] Apply suggestions from code review Co-authored-by: Emily KL <4672118+emilykl@users.noreply.github.com> --- src/traces/bar/plot.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 134f2e7b5da..cdc9195d126 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -958,9 +958,11 @@ function toMoveOutsideBar(x0, x1, y0, y1, textBB, opts) { var anchorX = 0; var anchorY = 0; - var dir = isHorizontal ? dirSign(x1, x0) : dirSign(y0, y1); + var dir; if ((isHorizontal ? x0 === x1 : y0 === y1) && opts.zeroBarDir) { dir = opts.zeroBarDir; + } else { + dir = isHorizontal ? dirSign(x1, x0) : dirSign(y0, y1); } if (isHorizontal) { targetX = x1 - dir * (textpad + axisPad); @@ -1197,5 +1199,4 @@ function calcTextinfo(cd, index, xa, ya) { module.exports = { plot: plot, toMoveInsideBar: toMoveInsideBar, - toMoveOutsideBar: toMoveOutsideBar }; From a318a800166bfeb1292dd93749001cad15df8338 Mon Sep 17 00:00:00 2001 From: Vansh Goyal Date: Thu, 30 Jul 2026 19:34:04 +0530 Subject: [PATCH 09/13] fix: remove trailing comma in export object --- src/traces/bar/plot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index cdc9195d126..4320c1c3fe3 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -1198,5 +1198,5 @@ function calcTextinfo(cd, index, xa, ya) { module.exports = { plot: plot, - toMoveInsideBar: toMoveInsideBar, + toMoveInsideBar: toMoveInsideBar }; From 0758f06d348da81dd4bf44712ca3a65e1bcafd2c Mon Sep 17 00:00:00 2001 From: Vansh Goyal Date: Thu, 30 Jul 2026 23:16:20 +0530 Subject: [PATCH 10/13] Update src/traces/bar/plot.js Co-authored-by: Emily KL <4672118+emilykl@users.noreply.github.com> --- src/traces/bar/plot.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 4320c1c3fe3..be1df31559a 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -708,6 +708,7 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op transform = toMoveOutsideBar(x0, x1, y0, y1, textBB, { isHorizontal: isHorizontal, constrained: constrained, + angle: angle, zeroBarDir: zeroBarDir }); } else { From 1579ed896efd63518b43d5a70d0de36904eafb59 Mon Sep 17 00:00:00 2001 From: Vansh Goyal Date: Thu, 30 Jul 2026 23:16:33 +0530 Subject: [PATCH 11/13] Update src/traces/bar/plot.js Co-authored-by: Emily KL <4672118+emilykl@users.noreply.github.com> --- src/traces/bar/plot.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index be1df31559a..0c6d88ee52b 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -710,6 +710,7 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op constrained: constrained, angle: angle, zeroBarDir: zeroBarDir + zeroBarDir: zeroBarDir }); } else { constrained = trace.constraintext === 'both' || trace.constraintext === 'inside'; From 79c20f8ed47ddc8c9ca66859e5aee489860063b1 Mon Sep 17 00:00:00 2001 From: Vansh Goyal Date: Thu, 30 Jul 2026 23:39:12 +0530 Subject: [PATCH 12/13] fix: remove duplicate zeroBarDir in plot.js Removed redundant zeroBarDir assignment and adjusted targetX/Y calculations to eliminate axisPad. --- src/traces/bar/plot.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js index 0c6d88ee52b..60f7154cf73 100644 --- a/src/traces/bar/plot.js +++ b/src/traces/bar/plot.js @@ -710,7 +710,6 @@ function appendBarText(gd, plotinfo, bar, cd, i, x0, x1, y0, y1, r, overhead, op constrained: constrained, angle: angle, zeroBarDir: zeroBarDir - zeroBarDir: zeroBarDir }); } else { constrained = trace.constraintext === 'both' || trace.constraintext === 'inside'; @@ -967,10 +966,10 @@ function toMoveOutsideBar(x0, x1, y0, y1, textBB, opts) { dir = isHorizontal ? dirSign(x1, x0) : dirSign(y0, y1); } if (isHorizontal) { - targetX = x1 - dir * (textpad + axisPad); + targetX = x1 - dir * textpad; anchorX = dir * extrapad; } else { - targetY = y1 + dir * (textpad + axisPad); + targetY = y1 + dir * textpad; anchorY = -dir * extrapad; } From d0ded0decb4db9e4872021fbee6900bb9740e1fa Mon Sep 17 00:00:00 2001 From: Emily KL <4672118+emilykl@users.noreply.github.com> Date: Thu, 30 Jul 2026 14:37:49 -0400 Subject: [PATCH 13/13] add attribution to draftlog --- draftlogs/7872_fix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/draftlogs/7872_fix.md b/draftlogs/7872_fix.md index f7c8c63eade..67ae0fe4153 100644 --- a/draftlogs/7872_fix.md +++ b/draftlogs/7872_fix.md @@ -1 +1 @@ -- Prevent outside bar text for zero-length bars from overlapping axis tick labels [[#7872](https://github.com/plotly/plotly.js/pull/7872)] +- Prevent outside bar text for zero-length bars from overlapping axis tick labels [[#7872](https://github.com/plotly/plotly.js/pull/7872)], with thanks to @vizansh for the contribution!