Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions draftlogs/7822_fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Prevent outside bar text labels from overlapping tilted axis ticks [[#7872](https://github.com/plotly/plotly.js/pull/7872)]
65 changes: 62 additions & 3 deletions src/traces/bar/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -707,7 +708,10 @@ 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
zeroBarDir: zeroBarDir
});
} else {
constrained = trace.constraintext === 'both' || trace.constraintext === 'inside';
Expand Down Expand Up @@ -957,12 +961,35 @@ 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;
}
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;
}

Expand Down Expand Up @@ -999,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');
Expand Down
28 changes: 28 additions & 0 deletions test/jasmine/tests/bar_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down