Skip to content

Commit 304e4d3

Browse files
committed
implement title.standoff for cartesian axes
- must now compute `ax._depth` when `standoff` is set to get compute distance - add `approxTitleDepth` helper with backward-compatible "looser" computation when `standoff` isn't set - add two mock: one with axis `automargin:true` and one w/o
1 parent 48cb1b2 commit 304e4d3

10 files changed

+216
-23
lines changed

src/plots/cartesian/axes.js

+72-23
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ var MINUS_SIGN = constants.MINUS_SIGN;
3333
var BADNUM = constants.BADNUM;
3434

3535
var MID_SHIFT = require('../../constants/alignment').MID_SHIFT;
36+
var CAP_SHIFT = require('../../constants/alignment').CAP_SHIFT;
3637
var LINE_SPACING = require('../../constants/alignment').LINE_SPACING;
3738
var OPPOSITE_SIDE = require('../../constants/alignment').OPPOSITE_SIDE;
3839

@@ -1859,6 +1860,11 @@ axes.drawOne = function(gd, ax, opts) {
18591860
transFn: transFn
18601861
});
18611862
});
1863+
} else if(ax.title.hasOwnProperty('standoff')) {
1864+
seq.push(function() {
1865+
var sgn = {l: -1, t: -1, r: 1, b: 1}[ax.side.charAt(0)];
1866+
ax._depth = sgn * (getLabelLevelBbox()[ax.side] - mainLinePosition);
1867+
});
18621868
}
18631869

18641870
var hasRangeSlider = Registry.getComponentMethod('rangeslider', 'isVisible')(ax);
@@ -1936,10 +1942,7 @@ axes.drawOne = function(gd, ax, opts) {
19361942
ax._anchorAxis.domain[domainIndices[0]];
19371943

19381944
if(ax.title.text !== fullLayout._dfltTitle[axLetter]) {
1939-
var extraLines = (ax.title.text.match(svgTextUtils.BR_TAG_ALL) || []).length;
1940-
push[s] += extraLines ?
1941-
ax.title.font.size * (extraLines + 1) * LINE_SPACING :
1942-
ax.title.font.size;
1945+
push[s] += approxTitleDepth(ax) + (ax.title.standoff || 0);
19431946
}
19441947

19451948
if(ax.mirror && ax.anchor !== 'free') {
@@ -2699,42 +2702,84 @@ axes.getPxPosition = function(gd, ax) {
26992702
}
27002703
};
27012704

2705+
/**
2706+
* Approximate axis title depth (w/o computing its bounding box)
2707+
*
2708+
* @param {object} ax (full) axis object
2709+
* - {string} title.text
2710+
* - {number} title.font.size
2711+
* - {number} title.standoff
2712+
* @return {number} (in px)
2713+
*/
2714+
function approxTitleDepth(ax) {
2715+
var fontSize = ax.title.font.size;
2716+
var extraLines = (ax.title.text.match(svgTextUtils.BR_TAG_ALL) || []).length;
2717+
if(ax.title.hasOwnProperty('standoff')) {
2718+
return extraLines ?
2719+
fontSize * (CAP_SHIFT + (extraLines * LINE_SPACING)) :
2720+
fontSize * CAP_SHIFT;
2721+
} else {
2722+
return extraLines ?
2723+
fontSize * (extraLines + 1) * LINE_SPACING :
2724+
fontSize;
2725+
}
2726+
}
2727+
2728+
/**
2729+
* Draw axis title, compute default standoff if necessary
2730+
*
2731+
* @param {DOM element} gd
2732+
* @param {object} ax (full) axis object
2733+
* - {string} _id
2734+
* - {string} _name
2735+
* - {string} side
2736+
* - {number} title.font.size
2737+
* - {object} _selections
2738+
*
2739+
* - {number} _depth
2740+
* - {number} title.standoff
2741+
* OR
2742+
* - {number} linewidth
2743+
* - {boolean} showticklabels
2744+
*/
27022745
function drawTitle(gd, ax) {
27032746
var fullLayout = gd._fullLayout;
27042747
var axId = ax._id;
27052748
var axLetter = axId.charAt(0);
27062749
var fontSize = ax.title.font.size;
27072750

27082751
var titleStandoff;
2709-
if(ax.type === 'multicategory') {
2710-
titleStandoff = ax._depth;
2752+
2753+
if(ax.title.hasOwnProperty('standoff')) {
2754+
titleStandoff = ax._depth + ax.title.standoff + approxTitleDepth(ax);
27112755
} else {
2712-
var offsetBase = 1.5;
2713-
titleStandoff = 10 + fontSize * offsetBase + (ax.linewidth ? ax.linewidth - 1 : 0);
2756+
if(ax.type === 'multicategory') {
2757+
titleStandoff = ax._depth;
2758+
} else {
2759+
var offsetBase = 1.5;
2760+
titleStandoff = 10 + fontSize * offsetBase + (ax.linewidth ? ax.linewidth - 1 : 0);
2761+
}
2762+
2763+
if(axLetter === 'x') {
2764+
titleStandoff += ax.side === 'top' ?
2765+
fontSize * (ax.showticklabels ? 1 : 0) :
2766+
fontSize * (ax.showticklabels ? 1.5 : 0.5);
2767+
} else {
2768+
titleStandoff += ax.side === 'right' ?
2769+
fontSize * (ax.showticklabels ? 1 : 0.5) :
2770+
fontSize * (ax.showticklabels ? 0.5 : 0);
2771+
}
27142772
}
27152773

27162774
var pos = axes.getPxPosition(gd, ax);
27172775
var transform, x, y;
27182776

27192777
if(axLetter === 'x') {
27202778
x = ax._offset + ax._length / 2;
2721-
2722-
if(ax.side === 'top') {
2723-
y = -titleStandoff - fontSize * (ax.showticklabels ? 1 : 0);
2724-
} else {
2725-
y = titleStandoff + fontSize * (ax.showticklabels ? 1.5 : 0.5);
2726-
}
2727-
y += pos;
2779+
y = (ax.side === 'top') ? pos - titleStandoff : pos + titleStandoff;
27282780
} else {
27292781
y = ax._offset + ax._length / 2;
2730-
2731-
if(ax.side === 'right') {
2732-
x = titleStandoff + fontSize * (ax.showticklabels ? 1 : 0.5);
2733-
} else {
2734-
x = -titleStandoff - fontSize * (ax.showticklabels ? 0.5 : 0);
2735-
}
2736-
x += pos;
2737-
2782+
x = (ax.side === 'right') ? pos + titleStandoff : pos - titleStandoff;
27382783
transform = {rotate: '-90', offset: 0};
27392784
}
27402785

@@ -2753,6 +2798,10 @@ function drawTitle(gd, ax) {
27532798
avoid.offsetLeft = translation.x;
27542799
avoid.offsetTop = translation.y;
27552800
}
2801+
2802+
if(ax.title.hasOwnProperty('standoff')) {
2803+
avoid.pad = 0;
2804+
}
27562805
}
27572806

27582807
return Titles.draw(gd, axId + 'title', {

src/plots/cartesian/layout_attributes.js

+15
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,21 @@ module.exports = {
6262
'by the now deprecated `titlefont` attribute.'
6363
].join(' ')
6464
}),
65+
standoff: {
66+
valType: 'number',
67+
role: 'info',
68+
min: 0,
69+
editType: 'ticks',
70+
description: [
71+
'Sets the standoff distance (in px) between the axis labels and the title text',
72+
'The default value is a function of the axis tick labels, the title `font.size`',
73+
'and the axis `linewidth`.',
74+
'Note that the axis title position is always constrained within the margins,',
75+
'so the actual standoff distance is always less than the set or default value.',
76+
'By setting `standoff` and turning on `automargin`, plotly.js will push the',
77+
'margins to fit the axis title at given standoff distance.'
78+
].join(' ')
79+
},
6580
editType: 'ticks'
6681
},
6782
type: {

src/plots/cartesian/layout_defaults.js

+2
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
235235
grid: layoutOut.grid
236236
});
237237

238+
coerce('title.standoff');
239+
238240
axLayoutOut._input = axLayoutIn;
239241
}
240242

src/plots/polar/layout_attributes.js

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ var radialAxisAttrs = {
122122

123123
// TODO
124124
// - might need a 'titleside' and even 'titledirection' down the road
125+
// - what about standoff ??
125126

126127
editType: 'plot'
127128
},

src/plots/ternary/layout_attributes.js

+1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ var ternaryAxesAttrs = {
1919
title: {
2020
text: axesAttrs.title.text,
2121
font: axesAttrs.title.font
22+
// TODO does standoff here make sense?
2223
},
2324
color: axesAttrs.color,
2425
// ticks

src/traces/carpet/axis_attributes.js

+1
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ module.exports = {
5757
'by the now deprecated `titlefont` attribute.'
5858
].join(' ')
5959
}),
60+
// TODO how is this different than `title.standoff`
6061
offset: {
6162
valType: 'number',
6263
role: 'info',
Loading
34.8 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
{
2+
"data": [
3+
{"x": ["looooooooooong label"], "y": [1]},
4+
{"y": ["looooooooooong label"], "x": [1], "xaxis": "x2", "yaxis": "y2"}
5+
],
6+
"layout": {
7+
"grid": {"rows": 1, "columns": 2, "pattern": "independent"},
8+
"width": 600,
9+
"height": 500,
10+
"margin": {"l": 0, "r": 0, "t": 0, "b": 0},
11+
"showlegend": false,
12+
"title": {
13+
"text": "With axis automargin:true<br>with margin:0",
14+
"x": 0,
15+
"xanchor": "left",
16+
"xref": "paper"
17+
},
18+
"xaxis": {
19+
"title": {
20+
"text": "X Axis (standoff: 60)",
21+
"standoff": 60,
22+
"font": {"size": 25}
23+
},
24+
"automargin": true,
25+
"showline": true,
26+
"mirror": true,
27+
"ticks": "outside",
28+
"tickangle": 20
29+
},
30+
"yaxis": {
31+
"title": {
32+
"text": "Y<br>Axis (standoff: 100)",
33+
"standoff": 100,
34+
"font": {"size": 20}
35+
},
36+
"automargin": true,
37+
"showline": true,
38+
"mirror": true
39+
},
40+
"xaxis2": {
41+
"anchor": "y2",
42+
"side": "top",
43+
"title": {
44+
"text": "X<br>Axis 2<br>(standoff: 80)",
45+
"standoff": 80
46+
},
47+
"automargin": true,
48+
"showline": true,
49+
"mirror": true
50+
},
51+
"yaxis2": {
52+
"anchor": "x2",
53+
"side": "right",
54+
"title": {
55+
"text": "Y Axis 2 (standoff: 30)",
56+
"standoff": 30
57+
},
58+
"automargin": true,
59+
"showline": true,
60+
"mirror": true,
61+
"ticks": "outside",
62+
"tickangle": 80
63+
}
64+
}
65+
}
+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"data": [
3+
{"x": ["looooooooooong label"], "y": [1]},
4+
{"y": ["looooooooooong label"], "x": [1], "xaxis": "x2", "yaxis": "y2"}
5+
],
6+
"layout": {
7+
"grid": {"rows": 1, "columns": 2, "pattern": "independent"},
8+
"width": 600,
9+
"height": 500,
10+
"showlegend": false,
11+
"title": {
12+
"text": "No axis automargin:true<br>with default margins",
13+
"x": 0,
14+
"xanchor": "left",
15+
"xref": "paper"
16+
},
17+
"xaxis": {
18+
"title": {
19+
"text": "X Axis (standoff:10)",
20+
"standoff": 10,
21+
"font": {"size": 25}
22+
},
23+
"showline": true,
24+
"mirror": true,
25+
"ticks": "outside"
26+
},
27+
"yaxis": {
28+
"title": {
29+
"text": "Y<br>Axis (standoff:8)",
30+
"standoff": 0,
31+
"font": {"size": 8}
32+
},
33+
"showline": true,
34+
"mirror": true
35+
},
36+
"xaxis2": {
37+
"anchor": "y2",
38+
"side": "top",
39+
"title": {
40+
"text": "X<br>Axis 2<br>(standoff:0)",
41+
"standoff": 0
42+
},
43+
"showline": true,
44+
"mirror": true
45+
},
46+
"yaxis2": {
47+
"anchor": "x2",
48+
"side": "right",
49+
"title": {
50+
"text": "Y Axis 2 (standoff:0)",
51+
"standoff": 0
52+
},
53+
"showline": true,
54+
"mirror": true,
55+
"ticks": "outside",
56+
"tickangle": 80
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)