From dbc9a519ef666e32d0794c9dd8a71dc7b495f580 Mon Sep 17 00:00:00 2001 From: vizansh Date: Sun, 28 Jun 2026 01:04:42 +0530 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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],