Skip to content

Commit 4964952

Browse files
authored
Merge pull request #6334 from plotly/mult-axis-formatting
Mult axis formatting
2 parents 369f013 + 948809b commit 4964952

20 files changed

+796
-29
lines changed

Diff for: src/components/titles/index.js

+2
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,8 @@ function draw(gd, titleClass, options) {
202202
}
203203
});
204204
shift = Math.min(maxshift, shift);
205+
// Keeping track of this for calculation of full axis size if needed
206+
cont._titleScoot = Math.abs(shift);
205207
}
206208

207209
if(shift > 0 || maxshift < 0) {

Diff for: src/plot_api/plot_schema.js

+7
Original file line numberDiff line numberDiff line change
@@ -548,6 +548,13 @@ function getLayoutAttributes() {
548548
delete layoutAttributes.yaxis[xkey];
549549
}
550550
}
551+
552+
/*
553+
* Also some attributes e.g. shift & autoshift only implemented on the yaxis
554+
* at the moment. Remove them from the xaxis.
555+
*/
556+
delete layoutAttributes.xaxis.shift;
557+
delete layoutAttributes.xaxis.autoshift;
551558
} else if(_module.name === 'colorscale') {
552559
extendDeepAll(layoutAttributes, _module.layoutAttributes);
553560
} else if(_module.layoutAttributes) {

Diff for: src/plot_api/subroutines.js

+3
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,9 @@ function lsInner(gd) {
240240
}
241241

242242
function yLinePathFree(x) {
243+
if(ya._shift !== undefined) {
244+
x += ya._shift;
245+
}
243246
return 'M' + x + ',' + ya._offset + 'v' + ya._length;
244247
}
245248

Diff for: src/plots/cartesian/axes.js

+106-20
Original file line numberDiff line numberDiff line change
@@ -2248,13 +2248,32 @@ axes.draw = function(gd, arg, opts) {
22482248

22492249
var axList = (!arg || arg === 'redraw') ? axes.listIds(gd) : arg;
22502250

2251+
var fullAxList = axes.list(gd);
2252+
// Get the list of the overlaying axis for all 'shift' axes
2253+
var overlayingShiftedAx = fullAxList.filter(function(ax) {
2254+
return ax.autoshift;
2255+
}).map(function(ax) {
2256+
return ax.overlaying;
2257+
});
2258+
2259+
2260+
var axShifts = {'false': {'left': 0, 'right': 0}};
2261+
22512262
return Lib.syncOrAsync(axList.map(function(axId) {
22522263
return function() {
22532264
if(!axId) return;
22542265

22552266
var ax = axes.getFromId(gd, axId);
2267+
2268+
if(!opts) opts = {};
2269+
opts.axShifts = axShifts;
2270+
opts.overlayingShiftedAx = overlayingShiftedAx;
2271+
22562272
var axDone = axes.drawOne(gd, ax, opts);
22572273

2274+
if(ax._shiftPusher) {
2275+
incrementShift(ax, ax._fullDepth || 0, axShifts, true);
2276+
}
22582277
ax._r = ax.range.slice();
22592278
ax._rl = Lib.simpleMap(ax._r, ax.r2l);
22602279

@@ -2293,6 +2312,9 @@ axes.draw = function(gd, arg, opts) {
22932312
axes.drawOne = function(gd, ax, opts) {
22942313
opts = opts || {};
22952314

2315+
var axShifts = opts.axShifts || {};
2316+
var overlayingShiftedAx = opts.overlayingShiftedAx || [];
2317+
22962318
var i, sp, plotinfo;
22972319

22982320
ax.setScale();
@@ -2306,15 +2328,35 @@ axes.drawOne = function(gd, ax, opts) {
23062328
// this happens when updating matched group with 'missing' axes
23072329
if(!mainPlotinfo) return;
23082330

2331+
ax._shiftPusher = ax.autoshift ||
2332+
overlayingShiftedAx.indexOf(ax._id) !== -1 ||
2333+
overlayingShiftedAx.indexOf(ax.overlaying) !== -1;
2334+
// An axis is also shifted by 1/2 of its own linewidth and inside tick length if applicable
2335+
// as well as its manually specified `shift` val if we're in the context of `autoshift`
2336+
if(ax._shiftPusher & ax.anchor === 'free') {
2337+
var selfPush = (ax.linewidth / 2 || 0);
2338+
if(ax.ticks === 'inside') {
2339+
selfPush += ax.ticklen;
2340+
}
2341+
incrementShift(ax, selfPush, axShifts, true);
2342+
incrementShift(ax, (ax.shift || 0), axShifts, false);
2343+
}
2344+
2345+
// Somewhat inelegant way of making sure that the shift value is only updated when the
2346+
// Axes.DrawOne() function is called from the right context. An issue when redrawing the
2347+
// axis as result of using the dragbox, for example.
2348+
if(opts.skipTitle !== true || ax._shift === undefined) ax._shift = setShiftVal(ax, axShifts);
2349+
23092350
var mainAxLayer = mainPlotinfo[axLetter + 'axislayer'];
23102351
var mainLinePosition = ax._mainLinePosition;
2352+
var mainLinePositionShift = mainLinePosition += ax._shift;
23112353
var mainMirrorPosition = ax._mainMirrorPosition;
23122354

23132355
var vals = ax._vals = axes.calcTicks(ax);
23142356

23152357
// Add a couple of axis properties that should cause us to recreate
23162358
// elements. Used in d3 data function.
2317-
var axInfo = [ax.mirror, mainLinePosition, mainMirrorPosition].join('_');
2359+
var axInfo = [ax.mirror, mainLinePositionShift, mainMirrorPosition].join('_');
23182360
for(i = 0; i < vals.length; i++) {
23192361
vals[i].axInfo = axInfo;
23202362
}
@@ -2409,8 +2451,8 @@ axes.drawOne = function(gd, ax, opts) {
24092451
var minorTickSigns = axes.getTickSigns(ax, 'minor');
24102452

24112453
if(ax.ticks || (ax.minor && ax.minor.ticks)) {
2412-
var majorTickPath = axes.makeTickPath(ax, mainLinePosition, majorTickSigns[2]);
2413-
var minorTickPath = axes.makeTickPath(ax, mainLinePosition, minorTickSigns[2], { minor: true });
2454+
var majorTickPath = axes.makeTickPath(ax, mainLinePositionShift, majorTickSigns[2]);
2455+
var minorTickPath = axes.makeTickPath(ax, mainLinePositionShift, minorTickSigns[2], { minor: true });
24142456

24152457
var mirrorMajorTickPath;
24162458
var mirrorMinorTickPath;
@@ -2496,7 +2538,7 @@ axes.drawOne = function(gd, ax, opts) {
24962538
layer: mainAxLayer,
24972539
plotinfo: plotinfo,
24982540
transFn: transTickLabelFn,
2499-
labelFns: axes.makeLabelFns(ax, mainLinePosition)
2541+
labelFns: axes.makeLabelFns(ax, mainLinePositionShift)
25002542
});
25012543
});
25022544

@@ -2515,28 +2557,34 @@ axes.drawOne = function(gd, ax, opts) {
25152557
repositionOnUpdate: true,
25162558
secondary: true,
25172559
transFn: transTickFn,
2518-
labelFns: axes.makeLabelFns(ax, mainLinePosition + standoff * majorTickSigns[4])
2560+
labelFns: axes.makeLabelFns(ax, mainLinePositionShift + standoff * majorTickSigns[4])
25192561
});
25202562
});
25212563

25222564
seq.push(function() {
2523-
ax._depth = majorTickSigns[4] * (getLabelLevelBbox('tick2')[ax.side] - mainLinePosition);
2565+
ax._depth = majorTickSigns[4] * (getLabelLevelBbox('tick2')[ax.side] - mainLinePositionShift);
25242566

25252567
return drawDividers(gd, ax, {
25262568
vals: dividerVals,
25272569
layer: mainAxLayer,
2528-
path: axes.makeTickPath(ax, mainLinePosition, majorTickSigns[4], { len: ax._depth }),
2570+
path: axes.makeTickPath(ax, mainLinePositionShift, majorTickSigns[4], { len: ax._depth }),
25292571
transFn: transTickFn
25302572
});
25312573
});
25322574
} else if(ax.title.hasOwnProperty('standoff')) {
25332575
seq.push(function() {
2534-
ax._depth = majorTickSigns[4] * (getLabelLevelBbox()[ax.side] - mainLinePosition);
2576+
ax._depth = majorTickSigns[4] * (getLabelLevelBbox()[ax.side] - mainLinePositionShift);
25352577
});
25362578
}
25372579

25382580
var hasRangeSlider = Registry.getComponentMethod('rangeslider', 'isVisible')(ax);
25392581

2582+
if(!opts.skipTitle &&
2583+
!(hasRangeSlider && ax.side === 'bottom')
2584+
) {
2585+
seq.push(function() { return drawTitle(gd, ax); });
2586+
}
2587+
25402588
seq.push(function() {
25412589
var s = ax.side.charAt(0);
25422590
var sMirror = OPPOSITE_SIDE[ax.side].charAt(0);
@@ -2548,7 +2596,7 @@ axes.drawOne = function(gd, ax, opts) {
25482596
var mirrorPush;
25492597
var rangeSliderPush;
25502598

2551-
if(ax.automargin || hasRangeSlider) {
2599+
if(ax.automargin || hasRangeSlider || ax._shiftPusher) {
25522600
if(ax.type === 'multicategory') {
25532601
llbbox = getLabelLevelBbox('tick2');
25542602
} else {
@@ -2559,10 +2607,27 @@ axes.drawOne = function(gd, ax, opts) {
25592607
}
25602608
}
25612609

2610+
var axDepth = 0;
2611+
var titleDepth = 0;
2612+
if(ax._shiftPusher) {
2613+
axDepth = Math.max(
2614+
outsideTickLen,
2615+
llbbox.height > 0 ? (s === 'l' ? pos - llbbox.left : llbbox.right - pos) : 0
2616+
);
2617+
if(ax.title.text !== fullLayout._dfltTitle[axLetter]) {
2618+
titleDepth = (ax._titleStandoff || 0) + (ax._titleScoot || 0);
2619+
if(s === 'l') {
2620+
titleDepth += approxTitleDepth(ax);
2621+
}
2622+
}
2623+
2624+
ax._fullDepth = Math.max(axDepth, titleDepth);
2625+
}
2626+
25622627
if(ax.automargin) {
25632628
push = {x: 0, y: 0, r: 0, l: 0, t: 0, b: 0};
25642629
var domainIndices = [0, 1];
2565-
2630+
var shift = typeof ax._shift === 'number' ? ax._shift : 0;
25662631
if(axLetter === 'x') {
25672632
if(s === 'b') {
25682633
push[s] = ax._depth;
@@ -2585,9 +2650,11 @@ axes.drawOne = function(gd, ax, opts) {
25852650
}
25862651
} else {
25872652
if(s === 'l') {
2588-
push[s] = ax._depth = Math.max(llbbox.height > 0 ? pos - llbbox.left : 0, outsideTickLen);
2653+
ax._depth = Math.max(llbbox.height > 0 ? pos - llbbox.left : 0, outsideTickLen);
2654+
push[s] = ax._depth - shift;
25892655
} else {
2590-
push[s] = ax._depth = Math.max(llbbox.height > 0 ? llbbox.right - pos : 0, outsideTickLen);
2656+
ax._depth = Math.max(llbbox.height > 0 ? llbbox.right - pos : 0, outsideTickLen);
2657+
push[s] = ax._depth + shift;
25912658
domainIndices.reverse();
25922659
}
25932660

@@ -2626,7 +2693,6 @@ axes.drawOne = function(gd, ax, opts) {
26262693
}
26272694
}
26282695
}
2629-
26302696
if(hasRangeSlider) {
26312697
rangeSliderPush = Registry.getComponentMethod('rangeslider', 'autoMarginOpts')(gd, ax);
26322698
}
@@ -2641,12 +2707,6 @@ axes.drawOne = function(gd, ax, opts) {
26412707
Plots.autoMargin(gd, rangeSliderAutoMarginID(ax), rangeSliderPush);
26422708
});
26432709

2644-
if(!opts.skipTitle &&
2645-
!(hasRangeSlider && ax.side === 'bottom')
2646-
) {
2647-
seq.push(function() { return drawTitle(gd, ax); });
2648-
}
2649-
26502710
return Lib.syncOrAsync(seq);
26512711
};
26522712

@@ -3779,7 +3839,7 @@ axes.getPxPosition = function(gd, ax) {
37793839
};
37803840
} else if(axLetter === 'y') {
37813841
anchorAxis = {
3782-
_offset: gs.l + (ax.position || 0) * gs.w,
3842+
_offset: gs.l + (ax.position || 0) * gs.w + ax._shift,
37833843
_length: 0
37843844
};
37853845
}
@@ -3902,6 +3962,8 @@ function drawTitle(gd, ax) {
39023962
}
39033963
}
39043964

3965+
ax._titleStandoff = titleStandoff;
3966+
39053967
return Titles.draw(gd, axId + 'title', {
39063968
propContainer: ax,
39073969
propName: ax._name + '.title.text',
@@ -4211,3 +4273,27 @@ function hideCounterAxisInsideTickLabels(ax, opts) {
42114273
}
42124274
}
42134275
}
4276+
4277+
function incrementShift(ax, shiftVal, axShifts, normalize) {
4278+
// Need to set 'overlay' for anchored axis
4279+
var overlay = ((ax.anchor !== 'free') && ((ax.overlaying === undefined) || (ax.overlaying === false))) ? ax._id : ax.overlaying;
4280+
var shiftValAdj;
4281+
if(normalize) {
4282+
shiftValAdj = ax.side === 'right' ? shiftVal : -shiftVal;
4283+
} else {
4284+
shiftValAdj = shiftVal;
4285+
}
4286+
if(!(overlay in axShifts)) {
4287+
axShifts[overlay] = {};
4288+
}
4289+
if(!(ax.side in axShifts[overlay])) {
4290+
axShifts[overlay][ax.side] = 0;
4291+
}
4292+
axShifts[overlay][ax.side] += shiftValAdj;
4293+
}
4294+
4295+
function setShiftVal(ax, axShifts) {
4296+
return ax.autoshift ?
4297+
axShifts[ax.overlaying][ax.side] :
4298+
(ax.shift || 0);
4299+
}

Diff for: src/plots/cartesian/axis_defaults.js

-2
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,6 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce,
163163
// mirror
164164
if(containerOut.showline || containerOut.ticks) coerce('mirror');
165165

166-
if(options.automargin) coerce('automargin');
167-
168166
var isMultiCategory = axType === 'multicategory';
169167

170168
if(!options.noTickson &&

Diff for: src/plots/cartesian/dragbox.js

+3
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ function makeDragBox(gd, plotinfo, x, y, w, h, ns, ew) {
8888
var scaleX;
8989
var scaleY;
9090

91+
// offset the x location of the box if needed
92+
x += plotinfo.yaxis._shift;
93+
9194
function recomputeAxisLists() {
9295
xa0 = plotinfo.xaxis;
9396
ya0 = plotinfo.yaxis;

Diff for: src/plots/cartesian/layout_attributes.js

+24
Original file line numberDiff line numberDiff line change
@@ -1003,6 +1003,30 @@ module.exports = {
10031003
'Only has an effect if `anchor` is set to *free*.'
10041004
].join(' ')
10051005
},
1006+
autoshift: {
1007+
valType: 'boolean',
1008+
dflt: false,
1009+
editType: 'plot',
1010+
description: [
1011+
'Automatically reposition the axis to avoid',
1012+
'overlap with other axes with the same `overlaying` value.',
1013+
'This repositioning will account for any `shift` amount applied to other',
1014+
'axes on the same side with `autoshift` is set to true.',
1015+
'Only has an effect if `anchor` is set to *free*.',
1016+
].join(' ')
1017+
},
1018+
shift: {
1019+
valType: 'number',
1020+
editType: 'plot',
1021+
description: [
1022+
'Moves the axis a given number of pixels from where it would have been otherwise.',
1023+
'Accepts both positive and negative values, which will shift the axis either right',
1024+
'or left, respectively.',
1025+
'If `autoshift` is set to true, then this defaults to a padding of -3 if `side` is set to *left*.',
1026+
'and defaults to +3 if `side` is set to *right*. Defaults to 0 if `autoshift` is set to false.',
1027+
'Only has an effect if `anchor` is set to *free*.'
1028+
].join(' ')
1029+
},
10061030
categoryorder: {
10071031
valType: 'enumerated',
10081032
values: [

Diff for: src/plots/cartesian/layout_defaults.js

+14-1
Original file line numberDiff line numberDiff line change
@@ -266,11 +266,24 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
266266
delete axLayoutOut.spikesnap;
267267
}
268268

269+
// If it exists, the the domain of the axis for the anchor of the overlaying axis
270+
var overlayingAxis = id2name(axLayoutIn.overlaying);
271+
var overlayingAnchorDomain = [0, 1];
272+
273+
if(layoutOut[overlayingAxis] !== undefined) {
274+
var overlayingAnchor = id2name(layoutOut[overlayingAxis].anchor);
275+
if(layoutOut[overlayingAnchor] !== undefined) {
276+
overlayingAnchorDomain = layoutOut[overlayingAnchor].domain;
277+
}
278+
}
279+
269280
handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, {
270281
letter: axLetter,
271282
counterAxes: counterAxes[axLetter],
272283
overlayableAxes: getOverlayableAxes(axLetter, axName),
273-
grid: layoutOut.grid
284+
grid: layoutOut.grid,
285+
overlayingDomain: overlayingAnchorDomain
286+
274287
});
275288

276289
coerce('title.standoff');

0 commit comments

Comments
 (0)