Skip to content

Commit 9b3cb12

Browse files
authored
Merge pull request #3538 from plotly/polar-better-angular-ticklabels
Polar better angular tick label placement
2 parents 8098c71 + 1f1c490 commit 9b3cb12

32 files changed

+153
-115
lines changed

src/components/colorbar/draw.js

+1-4
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,6 @@ module.exports = function draw(gd, id) {
434434

435435
var vals = Axes.calcTicks(cbAxisOut);
436436
var transFn = Axes.makeTransFn(cbAxisOut);
437-
var labelFns = Axes.makeLabelFns(cbAxisOut, shift);
438437
var tickSign = Axes.getTickSigns(cbAxisOut)[2];
439438

440439
Axes.drawTicks(gd, cbAxisOut, {
@@ -448,9 +447,7 @@ module.exports = function draw(gd, id) {
448447
vals: vals,
449448
layer: axisLayer,
450449
transFn: transFn,
451-
labelXFn: labelFns.labelXFn,
452-
labelYFn: labelFns.labelYFn,
453-
labelAnchorFn: labelFns.labelAnchorFn
450+
labelFns: Axes.makeLabelFns(cbAxisOut, shift)
454451
});
455452
},
456453
function() {

src/plots/cartesian/axes.js

+48-63
Original file line numberDiff line numberDiff line change
@@ -1802,14 +1802,11 @@ axes.drawOne = function(gd, ax, opts) {
18021802
// TODO: mirror labels, esp for subplots
18031803

18041804
seq.push(function() {
1805-
var labelFns = axes.makeLabelFns(ax, mainLinePosition);
18061805
return axes.drawLabels(gd, ax, {
18071806
vals: vals,
18081807
layer: mainAxLayer,
18091808
transFn: transFn,
1810-
labelXFn: labelFns.labelXFn,
1811-
labelYFn: labelFns.labelYFn,
1812-
labelAnchorFn: labelFns.labelAnchorFn,
1809+
labelFns: axes.makeLabelFns(ax, mainLinePosition)
18131810
});
18141811
});
18151812

@@ -1821,8 +1818,6 @@ axes.drawOne = function(gd, ax, opts) {
18211818
seq.push(function() {
18221819
labelLength += getLabelLevelSpan(ax, axId + 'tick') + pad;
18231820
labelLength += ax._tickAngles[axId + 'tick'] ? ax.tickfont.size * LINE_SPACING : 0;
1824-
var secondaryPosition = mainLinePosition + labelLength * sgn;
1825-
var secondaryLabelFns = axes.makeLabelFns(ax, secondaryPosition);
18261821

18271822
return axes.drawLabels(gd, ax, {
18281823
vals: getSecondaryLabelVals(ax, vals),
@@ -1831,9 +1826,7 @@ axes.drawOne = function(gd, ax, opts) {
18311826
repositionOnUpdate: true,
18321827
secondary: true,
18331828
transFn: transFn,
1834-
labelXFn: secondaryLabelFns.labelXFn,
1835-
labelYFn: secondaryLabelFns.labelYFn,
1836-
labelAnchorFn: secondaryLabelFns.labelAnchorFn,
1829+
labelFns: axes.makeLabelFns(ax, mainLinePosition + labelLength * sgn)
18371830
});
18381831
});
18391832

@@ -2178,66 +2171,79 @@ axes.makeTickPath = function(ax, shift, sgn, len) {
21782171
* @param {number} shift
21792172
* @param {number} angle [in degrees] ...
21802173
* @return {object}
2181-
* - {fn} labelXFn
2182-
* - {fn} labelYFn
2183-
* - {fn} labelAnchorFn
2184-
* - {number} labelStandoff
2185-
* - {number} labelShift
2174+
* - {fn} xFn
2175+
* - {fn} yFn
2176+
* - {fn} anchorFn
2177+
* - {fn} heightFn
2178+
* - {number} labelStandoff (gap parallel to ticks)
2179+
* - {number} labelShift (gap perpendicular to ticks)
21862180
*/
21872181
axes.makeLabelFns = function(ax, shift, angle) {
21882182
var axLetter = ax._id.charAt(0);
2189-
var pad = (ax.linewidth || 1) / 2;
21902183
var ticksOnOutsideLabels = ax.tickson !== 'boundaries' && ax.ticks === 'outside';
21912184

2192-
var labelStandoff = ticksOnOutsideLabels ? ax.ticklen : 0;
2185+
var labelStandoff = 0;
21932186
var labelShift = 0;
21942187

2188+
if(ticksOnOutsideLabels) {
2189+
labelStandoff += ax.ticklen;
2190+
}
21952191
if(angle && ax.ticks === 'outside') {
21962192
var rad = Lib.deg2rad(angle);
21972193
labelStandoff = ax.ticklen * Math.cos(rad) + 1;
21982194
labelShift = ax.ticklen * Math.sin(rad);
21992195
}
2200-
22012196
if(ax.showticklabels && (ticksOnOutsideLabels || ax.showline)) {
22022197
labelStandoff += 0.2 * ax.tickfont.size;
22032198
}
2199+
labelStandoff += (ax.linewidth || 1) / 2;
22042200

2205-
// Used in polar angular label x/y functions
2206-
// TODO generalize makeLabelFns so that it just work for angular axes
22072201
var out = {
22082202
labelStandoff: labelStandoff,
22092203
labelShift: labelShift
22102204
};
22112205

22122206
var x0, y0, ff, flipIt;
2207+
22132208
if(axLetter === 'x') {
22142209
flipIt = ax.side === 'bottom' ? 1 : -1;
22152210
x0 = labelShift * flipIt;
2216-
y0 = shift + (labelStandoff + pad) * flipIt;
2211+
y0 = shift + labelStandoff * flipIt;
22172212
ff = ax.side === 'bottom' ? 1 : -0.2;
22182213

2219-
out.labelXFn = function(d) { return d.dx + x0; };
2220-
out.labelYFn = function(d) { return d.dy + y0 + d.fontSize * ff; };
2221-
out.labelAnchorFn = function(a) {
2214+
out.xFn = function(d) { return d.dx + x0; };
2215+
out.yFn = function(d) { return d.dy + y0 + d.fontSize * ff; };
2216+
out.anchorFn = function(d, a) {
22222217
if(!isNumeric(a) || a === 0 || a === 180) {
22232218
return 'middle';
22242219
}
22252220
return (a * flipIt < 0) ? 'end' : 'start';
22262221
};
2222+
out.heightFn = function(d, a, h) {
2223+
return (a < -60 || a > 60) ? -0.5 * h :
2224+
ax.side === 'top' ? -h :
2225+
0;
2226+
};
22272227
} else if(axLetter === 'y') {
22282228
flipIt = ax.side === 'right' ? 1 : -1;
2229-
x0 = labelStandoff + pad;
2229+
x0 = labelStandoff;
22302230
y0 = -labelShift * flipIt;
22312231
ff = Math.abs(ax.tickangle) === 90 ? 0.5 : 0;
22322232

2233-
out.labelXFn = function(d) { return d.dx + shift + (x0 + d.fontSize * ff) * flipIt; };
2234-
out.labelYFn = function(d) { return d.dy + y0 + d.fontSize * MID_SHIFT; };
2235-
out.labelAnchorFn = function(a) {
2233+
out.xFn = function(d) { return d.dx + shift + (x0 + d.fontSize * ff) * flipIt; };
2234+
out.yFn = function(d) { return d.dy + y0 + d.fontSize * MID_SHIFT; };
2235+
out.anchorFn = function(d, a) {
22362236
if(isNumeric(a) && Math.abs(a) === 90) {
22372237
return 'middle';
22382238
}
22392239
return ax.side === 'right' ? 'start' : 'end';
22402240
};
2241+
out.heightFn = function(d, a, h) {
2242+
a *= ax.side === 'left' ? 1 : -1;
2243+
return a < -30 ? -h :
2244+
a < 30 ? -0.5 * h :
2245+
0;
2246+
};
22412247
}
22422248

22432249
return out;
@@ -2412,9 +2418,11 @@ axes.drawZeroLine = function(gd, ax, opts) {
24122418
* - {boolean} repositionOnUpdate (set to true to reposition update selection)
24132419
* - {boolean} secondary
24142420
* - {fn} transFn
2415-
* - {fn} labelXFn
2416-
* - {fn} labelYFn
2417-
* - {fn} labelAnchorFn
2421+
* - {object} labelFns
2422+
* + {fn} xFn
2423+
* + {fn} yFn
2424+
* + {fn} anchorFn
2425+
* + {fn} heightFn
24182426
*/
24192427
axes.drawLabels = function(gd, ax, opts) {
24202428
opts = opts || {};
@@ -2423,9 +2431,7 @@ axes.drawLabels = function(gd, ax, opts) {
24232431
var axLetter = axId.charAt(0);
24242432
var cls = opts.cls || axId + 'tick';
24252433
var vals = opts.vals;
2426-
var labelXFn = opts.labelXFn;
2427-
var labelYFn = opts.labelYFn;
2428-
var labelAnchorFn = opts.labelAnchorFn;
2434+
var labelFns = opts.labelFns;
24292435
var tickAngle = opts.secondary ? 0 : ax.tickangle;
24302436
var lastAngle = (ax._tickAngles || {})[cls];
24312437

@@ -2445,7 +2451,7 @@ axes.drawLabels = function(gd, ax, opts) {
24452451
var newPromise = gd._promises.length;
24462452

24472453
thisLabel
2448-
.call(svgTextUtils.positionText, labelXFn(d), labelYFn(d))
2454+
.call(svgTextUtils.positionText, labelFns.xFn(d), labelFns.yFn(d))
24492455
.call(Drawing.font, d.font, d.fontSize, d.fontColor)
24502456
.text(d.text)
24512457
.call(svgTextUtils.convertToTspans, gd);
@@ -2469,47 +2475,26 @@ axes.drawLabels = function(gd, ax, opts) {
24692475
if(opts.repositionOnUpdate) {
24702476
tickLabels.each(function(d) {
24712477
d3.select(this).select('text')
2472-
.call(svgTextUtils.positionText, labelXFn(d), labelYFn(d));
2478+
.call(svgTextUtils.positionText, labelFns.xFn(d), labelFns.yFn(d));
24732479
});
24742480
}
24752481

2476-
// How much to shift a multi-line label to center it vertically.
2477-
function getAnchorHeight(lineCount, lineHeight, angle) {
2478-
var h = (lineCount - 1) * lineHeight;
2479-
if(axLetter === 'x') {
2480-
if(angle < -60 || 60 < angle) {
2481-
return -0.5 * h;
2482-
} else if(ax.side === 'top') {
2483-
return -h;
2484-
}
2485-
} else {
2486-
angle *= ax.side === 'left' ? 1 : -1;
2487-
if(angle < -30) {
2488-
return -h;
2489-
} else if(angle < 30) {
2490-
return -0.5 * h;
2491-
}
2492-
}
2493-
return 0;
2494-
}
2495-
24962482
function positionLabels(s, angle) {
24972483
s.each(function(d) {
24982484
var thisLabel = d3.select(this);
24992485
var mathjaxGroup = thisLabel.select('.text-math-group');
2500-
var anchor = labelAnchorFn(angle, d);
2486+
var anchor = labelFns.anchorFn(d, angle);
25012487

25022488
var transform = opts.transFn.call(thisLabel.node(), d) +
25032489
((isNumeric(angle) && +angle !== 0) ?
2504-
(' rotate(' + angle + ',' + labelXFn(d) + ',' +
2505-
(labelYFn(d) - d.fontSize / 2) + ')') :
2490+
(' rotate(' + angle + ',' + labelFns.xFn(d) + ',' +
2491+
(labelFns.yFn(d) - d.fontSize / 2) + ')') :
25062492
'');
25072493

2508-
var anchorHeight = getAnchorHeight(
2509-
svgTextUtils.lineCount(thisLabel),
2510-
LINE_SPACING * d.fontSize,
2511-
isNumeric(angle) ? +angle : 0
2512-
);
2494+
// how much to shift a multi-line label to center it vertically.
2495+
var nLines = svgTextUtils.lineCount(thisLabel);
2496+
var lineHeight = LINE_SPACING * d.fontSize;
2497+
var anchorHeight = labelFns.heightFn(d, isNumeric(angle) ? +angle : 0, (nLines - 1) * lineHeight);
25132498

25142499
if(anchorHeight) {
25152500
transform += ' translate(0, ' + anchorHeight + ')';

src/plots/polar/polar.js

+19-43
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,6 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) {
404404

405405
var vals = Axes.calcTicks(ax);
406406
var valsClipped = Axes.clipEnds(ax, vals);
407-
var labelFns = Axes.makeLabelFns(ax, 0);
408407
var tickSign = Axes.getTickSigns(ax)[2];
409408

410409
Axes.drawTicks(gd, ax, {
@@ -427,9 +426,7 @@ proto.updateRadialAxis = function(fullLayout, polarLayout) {
427426
vals: vals,
428427
layer: layers['radial-axis'],
429428
transFn: transFn,
430-
labelXFn: labelFns.labelXFn,
431-
labelYFn: labelFns.labelYFn,
432-
labelAnchorFn: labelFns.labelAnchorFn
429+
labelFns: Axes.makeLabelFns(ax, 0)
433430
});
434431
}
435432

@@ -556,36 +553,31 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) {
556553

557554
var out = Axes.makeLabelFns(ax, 0);
558555
var labelStandoff = out.labelStandoff;
559-
var labelShift = out.labelShift;
560-
var offset4fontsize = (angularLayout.ticks !== 'outside' ? 0.7 : 0.5);
561-
var pad = (ax.linewidth || 1) / 2;
556+
var labelFns = {};
562557

563-
var labelXFn = function(d) {
558+
labelFns.xFn = function(d) {
564559
var rad = t2g(d);
565-
566-
var offset4tx = signSin(rad) === 0 ?
567-
0 :
568-
Math.cos(rad) * (labelStandoff + pad + offset4fontsize * d.fontSize);
569-
var offset4tick = signCos(rad) * (d.dx + labelStandoff + pad);
570-
571-
return offset4tx + offset4tick;
560+
return Math.cos(rad) * labelStandoff;
572561
};
573562

574-
var labelYFn = function(d) {
563+
labelFns.yFn = function(d) {
575564
var rad = t2g(d);
565+
var ff = Math.sin(rad) > 0 ? 0.2 : 1;
566+
return -Math.sin(rad) * (labelStandoff + d.fontSize * ff) +
567+
Math.abs(Math.cos(rad)) * (d.fontSize * MID_SHIFT);
568+
};
576569

577-
var offset4tx = d.dy + d.fontSize * MID_SHIFT - labelShift;
578-
var offset4tick = -Math.sin(rad) * (labelStandoff + pad + offset4fontsize * d.fontSize);
579-
580-
return offset4tx + offset4tick;
570+
labelFns.anchorFn = function(d) {
571+
var rad = t2g(d);
572+
var cos = Math.cos(rad);
573+
return Math.abs(cos) < 0.1 ?
574+
'middle' :
575+
(cos > 0 ? 'start' : 'end');
581576
};
582577

583-
// TODO maybe switch angle, d ordering ??
584-
var labelAnchorFn = function(angle, d) {
578+
labelFns.heightFn = function(d, a, h) {
585579
var rad = t2g(d);
586-
return signSin(rad) === 0 ?
587-
(signCos(rad) > 0 ? 'start' : 'end') :
588-
'middle';
580+
return -0.5 * (1 + Math.sin(rad)) * h;
589581
};
590582

591583
var newTickLayout = strTickLayout(angularLayout);
@@ -623,6 +615,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) {
623615

624616
if(ax.visible) {
625617
var tickSign = ax.ticks === 'inside' ? -1 : 1;
618+
var pad = (ax.linewidth || 1) / 2;
626619

627620
Axes.drawTicks(gd, ax, {
628621
vals: vals,
@@ -645,9 +638,7 @@ proto.updateAngularAxis = function(fullLayout, polarLayout) {
645638
layer: layers['angular-axis'],
646639
repositionOnUpdate: true,
647640
transFn: transFn,
648-
labelXFn: labelXFn,
649-
labelYFn: labelYFn,
650-
labelAnchorFn: labelAnchorFn
641+
labelFns: labelFns
651642
});
652643
}
653644

@@ -1413,18 +1404,3 @@ function strTranslate(x, y) {
14131404
function strRotate(angle) {
14141405
return 'rotate(' + angle + ')';
14151406
}
1416-
1417-
// because Math.sign(Math.cos(Math.PI / 2)) === 1
1418-
// oh javascript ;)
1419-
function sign(v) {
1420-
return Math.abs(v) < 1e-10 ? 0 :
1421-
v > 0 ? 1 : -1;
1422-
}
1423-
1424-
function signCos(v) {
1425-
return sign(Math.cos(v));
1426-
}
1427-
1428-
function signSin(v) {
1429-
return sign(Math.sin(v));
1430-
}

src/plots/ternary/ternary.js

+1-5
Original file line numberDiff line numberDiff line change
@@ -459,15 +459,11 @@ proto.drawAx = function(ax) {
459459
crisp: false
460460
});
461461

462-
var labelFns = Axes.makeLabelFns(ax, 0, counterAngle);
463-
464462
Axes.drawLabels(gd, ax, {
465463
vals: vals,
466464
layer: axLayer,
467465
transFn: transFn,
468-
labelXFn: labelFns.labelXFn,
469-
labelYFn: labelFns.labelYFn,
470-
labelAnchorFn: labelFns.labelAnchorFn
466+
labelFns: Axes.makeLabelFns(ax, 0, counterAngle)
471467
});
472468
};
473469

10 Bytes
Loading
453 Bytes
Loading
227 Bytes
Loading
57 Bytes
Loading
130 Bytes
Loading
103 Bytes
Loading
Loading

test/image/baselines/polar_blank.png

165 Bytes
Loading
9 Bytes
Loading

test/image/baselines/polar_dates.png

-273 Bytes
Loading
-128 Bytes
Loading

test/image/baselines/polar_fills.png

-180 Bytes
Loading
-184 Bytes
Loading

test/image/baselines/polar_hole.png

-23 Bytes
Loading

test/image/baselines/polar_line.png

-386 Bytes
Loading
Loading
-2 Bytes
Loading
102 Bytes
Loading
135 Bytes
Loading
11 Bytes
Loading
14 Bytes
Loading

test/image/baselines/polar_sector.png

-231 Bytes
Loading
229 Bytes
Loading
-55 Bytes
Loading

test/image/baselines/polar_ticks.png

604 Bytes
Loading
31 Bytes
Loading
5 Bytes
Loading

0 commit comments

Comments
 (0)