Skip to content

Commit b31d45b

Browse files
authored
Merge pull request #6790 from my-tien/autotickangles
Add autotickangles property
2 parents 24b6f75 + b293a11 commit b31d45b

29 files changed

+152
-35
lines changed

draftlogs/6790_add.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- Add `autotickangles` to cartesian and radial axes [[#6790](https://github.com/plotly/plotly.js/pull/6790)], with thanks to @my-tien for the contribution!

src/components/colorbar/defaults.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,11 @@ module.exports = function colorbarDefaults(containerIn, containerOut, layout) {
109109
handleTickValueDefaults(colorbarIn, colorbarOut, coerce, 'linear');
110110

111111
var font = layout.font;
112-
var opts = {outerTicks: false, font: font};
112+
var opts = {
113+
noAutotickangles: true,
114+
outerTicks: false,
115+
font: font
116+
};
113117
if(ticklabelposition.indexOf('inside') !== -1) {
114118
opts.bgColor = 'black'; // could we instead use the average of colors in the scale?
115119
}

src/components/colorbar/draw.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,7 @@ function mockColorBarAxis(gd, opts, zrange) {
10011001
var axisOptions = {
10021002
letter: letter,
10031003
font: fullLayout.font,
1004+
noAutotickangles: letter === 'y',
10041005
noHover: true,
10051006
noTickson: true,
10061007
noTicklabelmode: true,

src/plots/cartesian/axes.js

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3472,13 +3472,13 @@ axes.drawLabels = function(gd, ax, opts) {
34723472

34733473
var fullLayout = gd._fullLayout;
34743474
var axId = ax._id;
3475-
var axLetter = axId.charAt(0);
34763475
var cls = opts.cls || axId + 'tick';
34773476

34783477
var vals = opts.vals.filter(function(d) { return d.text; });
34793478

34803479
var labelFns = opts.labelFns;
34813480
var tickAngle = opts.secondary ? 0 : ax.tickangle;
3481+
34823482
var prevAngle = (ax._prevTickAngles || {})[cls];
34833483

34843484
var tickLabels = opts.layer.selectAll('g.' + cls)
@@ -3719,21 +3719,22 @@ axes.drawLabels = function(gd, ax, opts) {
37193719
// check for auto-angling if x labels overlap
37203720
// don't auto-angle at all for log axes with
37213721
// base and digit format
3722-
if(vals.length && axLetter === 'x' && !isNumeric(tickAngle) &&
3722+
if(vals.length && ax.autotickangles &&
37233723
(ax.type !== 'log' || String(ax.dtick).charAt(0) !== 'D')
37243724
) {
3725-
autoangle = 0;
3725+
autoangle = ax.autotickangles[0];
37263726

37273727
var maxFontSize = 0;
37283728
var lbbArray = [];
37293729
var i;
3730-
3730+
var maxLines = 1;
37313731
tickLabels.each(function(d) {
37323732
maxFontSize = Math.max(maxFontSize, d.fontSize);
37333733

37343734
var x = ax.l2p(d.x);
37353735
var thisLabel = selectTickLabel(this);
37363736
var bb = Drawing.bBox(thisLabel.node());
3737+
maxLines = Math.max(maxLines, svgTextUtils.lineCount(thisLabel));
37373738

37383739
lbbArray.push({
37393740
// ignore about y, just deal with x overlaps
@@ -3780,12 +3781,31 @@ axes.drawLabels = function(gd, ax, opts) {
37803781
var pad = !isAligned ? 0 :
37813782
(ax.tickwidth || 0) + 2 * TEXTPAD;
37823783

3783-
var rotate90 = (tickSpacing < maxFontSize * 2.5) || ax.type === 'multicategory' || ax._name === 'realaxis';
3784+
// autotickangles
3785+
var adjacent = tickSpacing;
3786+
var opposite = maxFontSize * 1.25 * maxLines;
3787+
var hypotenuse = Math.sqrt(Math.pow(adjacent, 2) + Math.pow(opposite, 2));
3788+
var maxCos = adjacent / hypotenuse;
3789+
var autoTickAnglesRadians = ax.autotickangles.map(
3790+
function(degrees) { return degrees * Math.PI / 180; }
3791+
);
3792+
var angleRadians = autoTickAnglesRadians.find(
3793+
function(angle) { return Math.abs(Math.cos(angle)) <= maxCos; }
3794+
);
3795+
if(angleRadians === undefined) {
3796+
// no angle with smaller cosine than maxCos, just pick the angle with smallest cosine
3797+
angleRadians = autoTickAnglesRadians.reduce(
3798+
function(currentMax, nextAngle) {
3799+
return Math.abs(Math.cos(currentMax)) < Math.abs(Math.cos(nextAngle)) ? currentMax : nextAngle;
3800+
}
3801+
, autoTickAnglesRadians[0]
3802+
);
3803+
}
3804+
var newAngle = angleRadians * (180 / Math.PI /* to degrees */);
37843805

3785-
// any overlap at all - set 30 degrees or 90 degrees
37863806
for(i = 0; i < lbbArray.length - 1; i++) {
37873807
if(Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1], pad)) {
3788-
autoangle = rotate90 ? 90 : 30;
3808+
autoangle = newAngle;
37893809
break;
37903810
}
37913811
}
@@ -3807,7 +3827,7 @@ axes.drawLabels = function(gd, ax, opts) {
38073827
// by rotating 90 degrees, do not attempt to re-fix its label overlaps
38083828
// as this can lead to infinite redraw loops!
38093829
if(ax.automargin && fullLayout._redrawFromAutoMarginCount && prevAngle === 90) {
3810-
autoangle = 90;
3830+
autoangle = prevAngle;
38113831
seq.push(function() {
38123832
positionLabels(tickLabels, prevAngle);
38133833
});

src/plots/cartesian/layout_attributes.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -809,6 +809,20 @@ module.exports = {
809809
'vertically.'
810810
].join(' ')
811811
},
812+
autotickangles: {
813+
valType: 'info_array',
814+
freeLength: true,
815+
items: {
816+
valType: 'angle'
817+
},
818+
dflt: [0, 30, 90],
819+
editType: 'ticks',
820+
description: [
821+
'When `tickangle` is set to *auto*, it will be set to the first',
822+
'angle in this array that is large enough to prevent label',
823+
'overlap.'
824+
].join(' ')
825+
},
812826
tickprefix: {
813827
valType: 'string',
814828
dflt: '',

src/plots/cartesian/layout_defaults.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
242242
visibleDflt: visibleDflt,
243243
reverseDflt: reverseDflt,
244244
autotypenumbersDflt: autotypenumbersDflt,
245-
splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId]
245+
splomStash: ((layoutOut._splomAxes || {})[axLetter] || {})[axId],
246+
noAutotickangles: axLetter === 'y'
246247
};
247248

248249
coerce('uirevision', layoutOut.uirevision);

src/plots/cartesian/tick_label_defaults.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,12 @@ module.exports = function handleTickLabelDefaults(containerIn, containerOut, coe
4040
coerce('ticklabelstep');
4141
}
4242

43-
if(!options.noAng) coerce('tickangle');
43+
if(!options.noAng) {
44+
var tickAngle = coerce('tickangle');
45+
if(!options.noAutotickangles && tickAngle === 'auto') {
46+
coerce('autotickangles');
47+
}
48+
}
4449

4550
if(axType !== 'category') {
4651
var tickFormat = coerce('tickformat');

src/plots/gl3d/layout/axis_defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) {
4141
letter: axName[0],
4242
data: options.data,
4343
showGrid: true,
44+
noAutotickangles: true,
4445
noTickson: true,
4546
noTicklabelmode: true,
4647
noTicklabelstep: true,

src/plots/polar/layout_attributes.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,8 @@ var radialAxisAttrs = {
105105
].join(' ')
106106
},
107107

108+
autotickangles: axesAttrs.autotickangles,
109+
108110
side: {
109111
valType: 'enumerated',
110112
// TODO add 'center' for `showline: false` radial axes

src/plots/polar/layout_defaults.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,8 @@ function handleDefaults(contIn, contOut, coerce, opts) {
167167
color: dfltFontColor,
168168
size: dfltFontSize,
169169
family: dfltFontFamily
170-
}
170+
},
171+
noAutotickangles: axName === 'angularaxis'
171172
});
172173

173174
handleTickMarkDefaults(axIn, axOut, coerceAxis, {outerTicks: true});

src/plots/smith/layout_defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ function handleDefaults(contIn, contOut, coerce, opts) {
8989
}
9090

9191
handleTickLabelDefaults(axIn, axOut, coerceAxis, axOut.type, {
92+
noAutotickangles: true,
9293
noTicklabelstep: true,
9394
noAng: !isRealAxis,
9495
noExp: true,

src/plots/ternary/layout_defaults.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ function handleAxisDefaults(containerIn, containerOut, options, ternaryLayoutOut
9292

9393
handleTickValueDefaults(containerIn, containerOut, coerce, 'linear');
9494
handlePrefixSuffixDefaults(containerIn, containerOut, coerce, 'linear');
95-
handleTickLabelDefaults(containerIn, containerOut, coerce, 'linear');
95+
handleTickLabelDefaults(containerIn, containerOut, coerce, 'linear', { noAutotickangles: true });
9696
handleTickMarkDefaults(containerIn, containerOut, coerce,
9797
{ outerTicks: true });
9898

src/traces/carpet/ab_defaults.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ function mimickAxisDefaults(traceIn, traceOut, fullLayout, dfltColor) {
3030
var axOut = Template.newContainer(traceOut, axName);
3131

3232
var defaultOptions = {
33+
noAutotickangles: true,
3334
noTicklabelstep: true,
3435
tickfont: 'x',
3536
id: axLetter + 'axis',

src/traces/indicator/defaults.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,10 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
129129
coerceGaugeAxis('visible');
130130
traceOut._range = coerceGaugeAxis('range', traceOut._range);
131131

132-
var opts = {outerTicks: true};
132+
var opts = {
133+
noAutotickangles: true,
134+
outerTicks: true
135+
};
133136
handleTickValueDefaults(axisIn, axisOut, coerceGaugeAxis, 'linear');
134137
handlePrefixSuffixDefaults(axisIn, axisOut, coerceGaugeAxis, 'linear', opts);
135138
handleTickLabelDefaults(axisIn, axisOut, coerceGaugeAxis, 'linear', opts);

src/traces/indicator/plot.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,7 @@ function mockAxis(gd, opts, zrange) {
823823
var axisOptions = {
824824
letter: 'x',
825825
font: fullLayout.font,
826+
noAutotickangles: true,
826827
noHover: true,
827828
noTickson: true
828829
};

test/image/baselines/10.png

8.09 KB
Loading
6.05 KB
Loading
-718 Bytes
Loading
Loading
390 Bytes
Loading
-5.31 KB
Loading
-59 Bytes
Loading
859 Bytes
Loading
-3.68 KB
Loading
6.41 KB
Loading

test/image/mocks/10.json

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
-0.255461150807681,
1414
-0.25597595662203515
1515
],
16-
"name": "Trial 1",
16+
"name": "Trial 1 Very Long<br>multiline label",
1717
"boxpoints": "all",
1818
"pointpos": -1.5,
1919
"jitter": 0,
@@ -45,7 +45,7 @@
4545
2.2237747316232417,
4646
2.0456528234898133
4747
],
48-
"name": "Trial 2",
48+
"name": "Trial 2 Very Long<br>multiline label",
4949
"boxpoints": "all",
5050
"pointpos": -1.5,
5151
"jitter": 0,
@@ -77,7 +77,7 @@
7777
1.114484992405051,
7878
0.577777449231605
7979
],
80-
"name": "Trial 3",
80+
"name": "Trial 3 Very Long<br>multiline label",
8181
"boxpoints": "all",
8282
"pointpos": -1.5,
8383
"jitter": 0,
@@ -109,7 +109,7 @@
109109
3.01289053296517,
110110
2.8335761244537614
111111
],
112-
"name": "Trial 4",
112+
"name": "Trial 4 Very Long<br>multiline label",
113113
"boxpoints": "all",
114114
"pointpos": -1.5,
115115
"jitter": 0,
@@ -141,7 +141,7 @@
141141
5.687207835036456,
142142
5.718713550485276
143143
],
144-
"name": "Trial 5",
144+
"name": "Trial 5 Very Long<br>multiline label",
145145
"boxpoints": "all",
146146
"pointpos": -1.5,
147147
"jitter": 0,
@@ -173,7 +173,7 @@
173173
6.146408206163261,
174174
6.726224574612897
175175
],
176-
"name": "Trial 6",
176+
"name": "Trial 6 Very Long<br>multiline label",
177177
"boxpoints": "all",
178178
"pointpos": -1.5,
179179
"jitter": 0,
@@ -194,12 +194,12 @@
194194
},
195195
{
196196
"x": [
197-
"Trial 1",
198-
"Trial 2",
199-
"Trial 3",
200-
"Trial 4",
201-
"Trial 5",
202-
"Trial 6"
197+
"Trial 1 Very Long<br>multiline label",
198+
"Trial 2 Very Long<br>multiline label",
199+
"Trial 3 Very Long<br>multiline label",
200+
"Trial 4 Very Long<br>multiline label",
201+
"Trial 5 Very Long<br>multiline label",
202+
"Trial 6 Very Long<br>multiline label"
203203
],
204204
"y": [
205205
-0.16783142774745008,
@@ -262,7 +262,8 @@
262262
"showticklabels": true,
263263
"tick0": 0,
264264
"dtick": 1,
265-
"tickangle": 0,
265+
"tickangle": "auto",
266+
"autotickangles": [5, -25],
266267
"anchor": "y",
267268
"autorange": true
268269
},

test/jasmine/tests/axes_test.js

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4267,16 +4267,22 @@ describe('Test axes', function() {
42674267
var op = parts[0];
42684268

42694269
var method = {
4270-
'=': 'toBe',
4270+
'=': 'toBeCloseTo',
42714271
'~=': 'toBeWithin',
42724272
grew: 'toBeGreaterThan',
42734273
shrunk: 'toBeLessThan',
4274-
initial: 'toBe'
4274+
initial: 'toBeCloseTo'
42754275
}[op];
42764276

42774277
var val = op === 'initial' ? initialSize[k] : previousSize[k];
42784278
var msgk = msg + ' ' + k + (parts[1] ? ' |' + parts[1] : '');
4279-
var args = op === '~=' ? [val, 1.1, msgk] : [val, msgk, ''];
4279+
var args = [val];
4280+
if(op === '~=') {
4281+
args.push(1.1);
4282+
} else if(method === 'toBeCloseTo') {
4283+
args.push(3);
4284+
}
4285+
args.push(msgk);
42804286

42814287
expect(actual[k])[method](args[0], args[1], args[2]);
42824288
}
@@ -4312,7 +4318,7 @@ describe('Test axes', function() {
43124318
width: 600, height: 600
43134319
})
43144320
.then(function() {
4315-
expect(gd._fullLayout.xaxis._tickAngles.xtick).toBe(30);
4321+
expect(gd._fullLayout.xaxis._tickAngles.xtick).toBeCloseTo(30, 3);
43164322

43174323
var gs = gd._fullLayout._size;
43184324
initialSize = Lib.extendDeep({}, gs);
@@ -4484,13 +4490,22 @@ describe('Test axes', function() {
44844490
var op = parts[0];
44854491

44864492
var method = {
4487-
'=': 'toBe',
4493+
'=': 'toBeCloseTo',
4494+
'~=': 'toBeWithin',
44884495
grew: 'toBeGreaterThan',
4496+
shrunk: 'toBeLessThan',
4497+
initial: 'toBeCloseTo'
44894498
}[op];
44904499

44914500
var val = initialSize[k];
44924501
var msgk = msg + ' ' + k + (parts[1] ? ' |' + parts[1] : '');
4493-
var args = op === '~=' ? [val, 1.1, msgk] : [val, msgk, ''];
4502+
var args = [val];
4503+
if(op === '~=') {
4504+
args.push(1.1);
4505+
} else if(method === 'toBeCloseTo') {
4506+
args.push(3);
4507+
}
4508+
args.push(msgk);
44944509

44954510
expect(actual[k])[method](args[0], args[1], args[2]);
44964511
}
@@ -4525,7 +4540,7 @@ describe('Test axes', function() {
45254540
width: 600, height: 600
45264541
})
45274542
.then(function() {
4528-
expect(gd._fullLayout.xaxis._tickAngles.xtick).toBe(30);
4543+
expect(gd._fullLayout.xaxis._tickAngles.xtick).toBeCloseTo(30, 3);
45294544

45304545
var gs = gd._fullLayout._size;
45314546
initialSize = Lib.extendDeep({}, gs);

test/jasmine/tests/plots_test.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1360,7 +1360,10 @@ describe('Test Plots with automargin and minreducedwidth/height', function() {
13601360
assert('height', '100');
13611361
})
13621362
.then(function() {
1363-
return Plotly.relayout(gd, 'minreducedwidth', 100);
1363+
// force tickangle to 90 so when we increase the width the x axis labels
1364+
// don't revert to 30 degrees, giving us a larger height
1365+
// this is a cool effect, but not what we're testing here!
1366+
return Plotly.relayout(gd, {minreducedwidth: 100, 'xaxis.tickangle': 90});
13641367
})
13651368
.then(function() {
13661369
assert('width', '100');

0 commit comments

Comments
 (0)