Skip to content

Commit

Permalink
Merge pull request #5275 from plotly/ticklabelposition
Browse files Browse the repository at this point in the history
Implement ticklabelposition for cartesian subplots and colorbars
  • Loading branch information
archmoj authored Dec 2, 2020
2 parents 5700db2 + 40c09b7 commit 08f3f04
Show file tree
Hide file tree
Showing 46 changed files with 1,764 additions and 113 deletions.
13 changes: 13 additions & 0 deletions src/components/colorbar/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,19 @@ module.exports = overrideAll({
tickvals: axesAttrs.tickvals,
ticktext: axesAttrs.ticktext,
ticks: extendFlat({}, axesAttrs.ticks, {dflt: ''}),
ticklabelposition: {
valType: 'enumerated',
values: [
'outside', 'inside',
'outside top', 'inside top',
'outside bottom', 'inside bottom'
],
dflt: 'outside',
role: 'info',
description: [
'Determines where tick labels are drawn.'
].join(' ')
},
ticklen: axesAttrs.ticklen,
tickwidth: axesAttrs.tickwidth,
tickcolor: axesAttrs.tickcolor,
Expand Down
4 changes: 4 additions & 0 deletions src/components/colorbar/defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,14 @@ module.exports = function colorbarDefaults(containerIn, containerOut, layout) {
coerce('bordercolor');
coerce('borderwidth');
coerce('bgcolor');
var ticklabelposition = coerce('ticklabelposition');

handleTickValueDefaults(colorbarIn, colorbarOut, coerce, 'linear');

var opts = {outerTicks: false, font: layout.font};
if(ticklabelposition.indexOf('inside') !== -1) {
opts.bgColor = 'black'; // could we instead use the average of colors in the scale?
}
handleTickLabelDefaults(colorbarIn, colorbarOut, coerce, 'linear', opts);
handleTickMarkDefaults(colorbarIn, colorbarOut, coerce, 'linear', opts);

Expand Down
12 changes: 8 additions & 4 deletions src/components/colorbar/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -462,20 +462,19 @@ function drawColorBar(g, opts, gd) {
(opts.outlinewidth || 0) / 2 - (opts.ticks === 'outside' ? 1 : 0);

var vals = Axes.calcTicks(ax);
var transFn = Axes.makeTransFn(ax);
var tickSign = Axes.getTickSigns(ax)[2];

Axes.drawTicks(gd, ax, {
vals: ax.ticks === 'inside' ? Axes.clipEnds(ax, vals) : vals,
layer: axLayer,
path: Axes.makeTickPath(ax, shift, tickSign),
transFn: transFn
transFn: Axes.makeTransTickFn(ax)
});

return Axes.drawLabels(gd, ax, {
vals: vals,
layer: axLayer,
transFn: transFn,
transFn: Axes.makeTransTickLabelFn(ax),
labelFns: Axes.makeLabelFns(ax, shift)
});
}
Expand All @@ -485,7 +484,11 @@ function drawColorBar(g, opts, gd) {
// TODO: why are we redrawing multiple times now with this?
// I guess autoMargin doesn't like being post-promise?
function positionCB() {
var innerWidth = thickPx + opts.outlinewidth / 2 + Drawing.bBox(axLayer.node()).width;
var innerWidth = thickPx + opts.outlinewidth / 2;
if(ax.ticklabelposition.indexOf('inside') === -1) {
innerWidth += Drawing.bBox(axLayer.node()).width;
}

titleEl = titleCont.select('text');

if(titleEl.node() && !titleEl.classed(cn.jsPlaceholder)) {
Expand Down Expand Up @@ -681,6 +684,7 @@ function mockColorBarAxis(gd, opts, zrange) {
tickwidth: opts.tickwidth,
tickcolor: opts.tickcolor,
showticklabels: opts.showticklabels,
ticklabelposition: opts.ticklabelposition,
tickfont: opts.tickfont,
tickangle: opts.tickangle,
tickformat: opts.tickformat,
Expand Down
30 changes: 27 additions & 3 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,18 @@ function plot(gd, data, layout, config) {
if(hasCartesian) seq.push(positionAndAutorange);

seq.push(subroutines.layoutStyles);
if(hasCartesian) seq.push(drawAxes);
if(hasCartesian) {
seq.push(
drawAxes,
function insideTickLabelsAutorange(gd) {
if(gd._fullLayout._insideTickLabelsAutorange) {
relayout(gd, gd._fullLayout._insideTickLabelsAutorange).then(function() {
gd._fullLayout._insideTickLabelsAutorange = undefined;
});
}
}
);
}

seq.push(
subroutines.drawData,
Expand All @@ -381,9 +392,16 @@ function plot(gd, data, layout, config) {
// calculated. Would be much better to separate margin calculations from
// component drawing - see https://github.com/plotly/plotly.js/issues/2704
Plots.doAutoMargin,
saveRangeInitialForInsideTickLabels,
Plots.previousPromises
);

function saveRangeInitialForInsideTickLabels(gd) {
if(gd._fullLayout._insideTickLabelsAutorange) {
if(graphWasEmpty) Axes.saveRangeInitial(gd, true);
}
}

// even if everything we did was synchronous, return a promise
// so that the caller doesn't care which route we took
var plotDone = Lib.syncOrAsync(seq, gd);
Expand Down Expand Up @@ -1961,6 +1979,12 @@ function addAxRangeSequence(seq, rangesAltered) {
var ax = Axes.getFromId(gd, id);
axIds.push(id);

if((ax.ticklabelposition || '').indexOf('inside') !== -1) {
if(ax._anchorAxis) {
axIds.push(ax._anchorAxis._id);
}
}

if(ax._matchGroup) {
for(var id2 in ax._matchGroup) {
if(!rangesAltered[id2]) {
Expand Down Expand Up @@ -2055,7 +2079,7 @@ function _relayout(gd, aobj) {
// we're editing the (auto)range of, so we can tell the others constrained
// to scale with them that it's OK for them to shrink
var rangesAltered = {};
var axId, ax;
var ax;

function recordAlteredAxis(pleafPlus) {
var axId = Axes.name2id(pleafPlus.split('.')[0]);
Expand Down Expand Up @@ -2283,7 +2307,7 @@ function _relayout(gd, aobj) {
}

// figure out if we need to recalculate axis constraints
for(axId in rangesAltered) {
for(var axId in rangesAltered) {
ax = Axes.getFromId(gd, axId);
var group = ax && ax._constraintGroup;
if(group) {
Expand Down
1 change: 1 addition & 0 deletions src/plot_api/subroutines.js
Original file line number Diff line number Diff line change
Expand Up @@ -674,6 +674,7 @@ exports.doAutoRangeAndConstraints = function(gd) {

for(var i = 0; i < axList.length; i++) {
ax = axList[i];

if(!autoRangeDone[ax._id]) {
autoRangeDone[ax._id] = 1;
cleanAxisConstraints(gd, ax);
Expand Down
158 changes: 132 additions & 26 deletions src/plots/cartesian/autorange.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ function getAutoRange(gd, ax) {
var i, j;
var newRange = [];

var getPad = makePadFn(ax);
var getPadMin = makePadFn(ax, 0);
var getPadMax = makePadFn(ax, 1);
var extremes = concatExtremes(gd, ax);
var minArray = extremes.min;
var maxArray = extremes.max;
Expand Down Expand Up @@ -97,29 +98,16 @@ function getAutoRange(gd, ax) {
// don't allow padding to reduce the data to < 10% of the length
var minSpan = axLen / 10;

// find axis rangebreaks in [v0,v1] and compute its length in value space
var calcBreaksLength = function(v0, v1) {
var lBreaks = 0;
if(ax.rangebreaks) {
var rangebreaksOut = ax.locateBreaks(v0, v1);
for(var i = 0; i < rangebreaksOut.length; i++) {
var brk = rangebreaksOut[i];
lBreaks += brk.max - brk.min;
}
}
return lBreaks;
};

var mbest = 0;
var minpt, maxpt, minbest, maxbest, dp, dv;

for(i = 0; i < minArray.length; i++) {
minpt = minArray[i];
for(j = 0; j < maxArray.length; j++) {
maxpt = maxArray[j];
dv = maxpt.val - minpt.val - calcBreaksLength(minpt.val, maxpt.val);
dv = maxpt.val - minpt.val - calcBreaksLength(ax, minpt.val, maxpt.val);
if(dv > 0) {
dp = axLen - getPad(minpt) - getPad(maxpt);
dp = axLen - getPadMin(minpt) - getPadMax(maxpt);
if(dp > minSpan) {
if(dv / dp > mbest) {
minbest = minpt;
Expand All @@ -137,8 +125,8 @@ function getAutoRange(gd, ax) {
}
}

function getMaxPad(prev, pt) {
return Math.max(prev, getPad(pt));
function maximumPad(prev, pt) {
return Math.max(prev, getPadMax(pt));
}

if(minmin === maxmax) {
Expand All @@ -152,7 +140,7 @@ function getAutoRange(gd, ax) {
// 'tozero' pins 0 to the low end, so follow that.
newRange = [0, 1];
} else {
var maxPad = (minmin > 0 ? maxArray : minArray).reduce(getMaxPad, 0);
var maxPad = (minmin > 0 ? maxArray : minArray).reduce(maximumPad, 0);
// we're pushing a single value away from the edge due to its
// padding, with the other end clamped at zero
// 0.5 means don't push it farther than the center.
Expand All @@ -173,7 +161,7 @@ function getAutoRange(gd, ax) {
maxbest = {val: 0, pad: 0};
}
} else if(nonNegative) {
if(minbest.val - mbest * getPad(minbest) < 0) {
if(minbest.val - mbest * getPadMin(minbest) < 0) {
minbest = {val: 0, pad: 0};
}
if(maxbest.val <= 0) {
Expand All @@ -182,12 +170,12 @@ function getAutoRange(gd, ax) {
}

// in case it changed again...
mbest = (maxbest.val - minbest.val - calcBreaksLength(minpt.val, maxpt.val)) /
(axLen - getPad(minbest) - getPad(maxbest));
mbest = (maxbest.val - minbest.val - calcBreaksLength(ax, minpt.val, maxpt.val)) /
(axLen - getPadMin(minbest) - getPadMax(maxbest));

newRange = [
minbest.val - mbest * getPad(minbest),
maxbest.val + mbest * getPad(maxbest)
minbest.val - mbest * getPadMin(minbest),
maxbest.val + mbest * getPadMax(maxbest)
];
}

Expand All @@ -197,13 +185,41 @@ function getAutoRange(gd, ax) {
return Lib.simpleMap(newRange, ax.l2r || Number);
}

// find axis rangebreaks in [v0,v1] and compute its length in value space
function calcBreaksLength(ax, v0, v1) {
var lBreaks = 0;
if(ax.rangebreaks) {
var rangebreaksOut = ax.locateBreaks(v0, v1);
for(var i = 0; i < rangebreaksOut.length; i++) {
var brk = rangebreaksOut[i];
lBreaks += brk.max - brk.min;
}
}
return lBreaks;
}

/*
* calculate the pixel padding for ax._min and ax._max entries with
* optional extrapad as 5% of the total axis length
*/
function makePadFn(ax) {
function makePadFn(ax, max) {
// 5% padding for points that specify extrapad: true
var extrappad = ax._length / 20;
var extrappad = 0.05 * ax._length;

if(
(ax.ticklabelposition || '').indexOf('inside') !== -1 ||
((ax._anchorAxis || {}).ticklabelposition || '').indexOf('inside') !== -1
) {
var axReverse = ax.autorange === 'reversed';
if(!axReverse) {
var rng = Lib.simpleMap(ax.range, ax.r2l);
axReverse = rng[1] < rng[0];
}
if(axReverse) max = !max;
}

extrappad = adjustPadForInsideLabelsOnAnchorAxis(extrappad, ax, max);
extrappad = adjustPadForInsideLabelsOnThisAxis(extrappad, ax, max);

// domain-constrained axes: base extrappad on the unconstrained
// domain so it's consistent as the domain changes
Expand All @@ -215,6 +231,96 @@ function makePadFn(ax) {
return function getPad(pt) { return pt.pad + (pt.extrapad ? extrappad : 0); };
}

var TEXTPAD = 3;

function adjustPadForInsideLabelsOnThisAxis(extrappad, ax, max) {
var ticklabelposition = ax.ticklabelposition || '';
var has = function(str) {
return ticklabelposition.indexOf(str) !== -1;
};

if(!has('inside')) return extrappad;
var isTop = has('top');
var isLeft = has('left');
var isRight = has('right');
var isBottom = has('bottom');
var isAligned = isBottom || isLeft || isTop || isRight;

if(
(max && (isLeft || isBottom)) ||
(!max && (isRight || isTop))
) {
return extrappad;
}

// increase padding to make more room for inside tick labels of the axis
var fontSize = ax.tickfont ? ax.tickfont.size : 12;
var isX = ax._id.charAt(0) === 'x';
var morePad = (isX ? 1.2 : 0.6) * fontSize;

if(isAligned) {
morePad *= 2;
morePad += (ax.tickwidth || 0) / 2;
}

morePad += TEXTPAD;

extrappad = Math.max(extrappad, morePad);

return extrappad;
}

function adjustPadForInsideLabelsOnAnchorAxis(extrappad, ax, max) {
var anchorAxis = (ax._anchorAxis || {});
if((anchorAxis.ticklabelposition || '').indexOf('inside') !== -1) {
// increase padding to make more room for inside tick labels of the counter axis
if((
!max && (
anchorAxis.side === 'left' ||
anchorAxis.side === 'bottom'
)
) || (
max && (
anchorAxis.side === 'top' ||
anchorAxis.side === 'right'
)
)) {
var isX = ax._id.charAt(0) === 'x';

var morePad = 0;
if(anchorAxis._vals) {
var rad = Lib.deg2rad(anchorAxis._tickAngles[anchorAxis._id + 'tick'] || 0);
var cosA = Math.abs(Math.cos(rad));
var sinA = Math.abs(Math.sin(rad));

// use bounding boxes
anchorAxis._vals.forEach(function(t) {
if(t.bb) {
var w = t.bb.width;
var h = t.bb.height;

morePad = Math.max(morePad, isX ?
Math.max(w * cosA, h * sinA) :
Math.max(h * cosA, w * sinA)
);

// add extra pad around label
morePad += 3;
}
});
}

if(anchorAxis.ticks === 'inside' && anchorAxis.ticklabelposition === 'inside') {
morePad += anchorAxis.ticklen || 0;
}

extrappad = Math.max(extrappad, morePad);
}
}

return extrappad;
}

function concatExtremes(gd, ax, noMatch) {
var axId = ax._id;
var fullData = gd._fullData;
Expand Down
Loading

0 comments on commit 08f3f04

Please sign in to comment.