Skip to content

Commit 63ca067

Browse files
committed
Update logic to correctly avoid bbox of fixed label when placing auto labels
1 parent e2cd6ef commit 63ca067

3 files changed

Lines changed: 48 additions & 21 deletions

File tree

‎src/components/drawing/index.js‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,7 @@ function textPointHash(node) {
13681368

13691369
/**
13701370
* Position a text label relative to its point, and join its leader line.
1371+
* Sets `d._tpOffset` for a label with a fixed position.
13711372
*
13721373
* @param s - d3 selection of one `<text>` element, inside its own `<g>`
13731374
* @param d - the calcdata point
@@ -1397,6 +1398,8 @@ drawing.textPointPosition = function (s, d, trace, markerRadius, dontTouchParent
13971398
markerRadius,
13981399
svgTextUtils.lineCount(s)
13991400
);
1401+
// The *auto* placement reads it, so it blocks with the box this draw gives the label
1402+
if (!isAuto) d._tpOffset = offset;
14001403
}
14011404

14021405
// fix the overall text group position

‎src/traces/scatter/auto_text_position.js‎

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ module.exports = function autoTextPosition(gd, plotinfo, traceGroups) {
8585
const rank = Lib.extractOption(d, trace, 'tR', 'textrank');
8686
const x = xaxis.c2p(d.x);
8787
const y = yaxis.c2p(d.y);
88+
const bb = Drawing.textPointBBox(tx);
8889
const label = {
8990
tx,
9091
trace,
@@ -101,13 +102,14 @@ module.exports = function autoTextPosition(gd, plotinfo, traceGroups) {
101102
// `_tpAuto` survives a relayout, so a pan or zoom keeps the labels in place where it can
102103
prefer: d._tpAuto || undefined,
103104
preferGap: d._tpAutoGap,
104-
rect: labelRect(d, trace, x, y, Drawing.textPointBBox(tx))
105+
rect: labelRect(d, trace, x, y, bb)
105106
};
106107

107108
if (pos === 'auto') {
108109
labels.push(label);
109110
} else {
110-
const box = label.rect(pos, 0);
111+
// `textPointPosition` stored the geometry that draws the fixed label
112+
const box = boxAt(x, y, bb, d._tpOffset);
111113
box.owner = d;
112114
fixed.push(box);
113115
}
@@ -230,25 +232,25 @@ function hasAutoTextPosition(trace) {
230232
return tp === 'auto' || (Lib.isArrayOrTypedArray(tp) && tp.includes('auto'));
231233
}
232234

233-
// the box of a label at a given position and gap, plus its leader line, in the plot frame
234-
function labelRect(d, trace, x, y, bb) {
235-
const symbol = d.mx || (trace.marker || {}).symbol;
235+
// The box of a label at a given offset, plus its leader line, in the plot frame
236+
function boxAt(x, y, bb, offset) {
237+
let x0 = x + offset.dx;
238+
if (offset.anchor === 'end') x0 -= bb.width;
239+
else if (offset.anchor === 'middle') x0 -= bb.width / 2;
240+
const y0 = y + offset.dy + bb.top;
236241

237-
return (pos, gap) => {
238-
const offset = Drawing.textPointBoxOffset(pos, d.mrc, symbol, bb, gap);
239-
240-
let x0 = x + offset.dx;
241-
if (offset.anchor === 'end') x0 -= bb.width;
242-
else if (offset.anchor === 'middle') x0 -= bb.width / 2;
243-
const y0 = y + offset.dy + bb.top;
244-
245-
const seg = offset.leader;
246-
return {
247-
x0,
248-
y0,
249-
x1: x0 + bb.width,
250-
y1: y0 + bb.height,
251-
leader: seg ? [x + seg[0], y + seg[1], x + seg[2], y + seg[3]] : null
252-
};
242+
const seg = offset.leader;
243+
return {
244+
x0,
245+
y0,
246+
x1: x0 + bb.width,
247+
y1: y0 + bb.height,
248+
leader: seg ? [x + seg[0], y + seg[1], x + seg[2], y + seg[3]] : null
253249
};
254250
}
251+
252+
// The box of an *auto* label at a given position and gap
253+
function labelRect(d, trace, x, y, bb) {
254+
const symbol = d.mx || (trace.marker || {}).symbol;
255+
return (pos, gap) => boxAt(x, y, bb, Drawing.textPointBoxOffset(pos, d.mrc, symbol, bb, gap));
256+
}

‎test/jasmine/tests/scatter_test.js‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -988,6 +988,28 @@ describe('end-to-end scatter tests', function() {
988988
expect(cd[0]._tpAuto).toBeUndefined();
989989
expect(cd[1]._tpAuto).toBe('bottom center');
990990
expect(countOverlaps(visibleLabelBoxes())).toBe(0);
991+
992+
// A corner label sits farther from a large marker than a side label does.
993+
// The second auto label has no free spot above its point, so it takes the strip over the fixed label.
994+
return Plotly.newPlot(gd, [{
995+
mode: 'markers+text',
996+
x: [1],
997+
y: [1],
998+
text: ['Fixed label'],
999+
textposition: 'top right',
1000+
marker: {size: 80}
1001+
}, {
1002+
mode: 'markers+text',
1003+
x: [1.61, 1.61],
1004+
y: [1.67, 1.8],
1005+
text: ['auto Q', 'auto B'],
1006+
textposition: 'auto',
1007+
marker: {size: 8}
1008+
}], layout);
1009+
})
1010+
.then(() => {
1011+
expect(visibleLabelBoxes().length).toBe(3, 'visible labels');
1012+
expect(countOverlaps(visibleLabelBoxes())).toBe(0, 'overlaps with a fixed corner label');
9911013
})
9921014
.then(done, done.fail);
9931015
});

0 commit comments

Comments
 (0)