Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ticklabelposition for cartesian subplots and colorbars #5275

Merged
merged 52 commits into from
Dec 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
4da7e58
declare calcBreaksLength once
archmoj Nov 9, 2020
3ea0bb7
refactor inside and outside ticks
archmoj Nov 9, 2020
c7a23b4
fix issue 5262
archmoj Nov 12, 2020
a9d9140
rename axes.makeTransFn to makeTransTickFn
archmoj Nov 5, 2020
ee2d648
rename transFn function in cartesian axes
archmoj Nov 5, 2020
fa07cc5
create makeTransTickLabelFn and handle period case inside it
archmoj Nov 5, 2020
9e63c04
implement ticklabelposition for cartesian axes
archmoj Nov 13, 2020
7a6fd0f
add image tests
archmoj Nov 13, 2020
c262d4b
store and use ticklabel bounding boxes to increase pad for inside labels
archmoj Nov 18, 2020
74f594d
autorange call to account extra pad for inside tick labels
archmoj Nov 18, 2020
5ca95d3
save initial ranges after insideticklabel is done
archmoj Nov 19, 2020
8a34a95
better fit based on tick angle - increase pad only when text does not…
archmoj Nov 19, 2020
22e9519
ensure plotinfo in axes redraw
archmoj Nov 19, 2020
95751cb
reset autorange flag for inside tick labels during GUI edits
archmoj Nov 19, 2020
ab37122
fixup interactions
archmoj Nov 19, 2020
432e448
Merge remote-tracking branch 'origin/master' into ticklabelposition
archmoj Nov 21, 2020
e840f8f
update attribute description
archmoj Nov 23, 2020
2361958
revise ticklabelposition supply defaults
archmoj Nov 23, 2020
91ea2f6
validate new scaleanchor matches mocks
archmoj Nov 23, 2020
47c5773
hide out of range inside text labels during redraws
archmoj Nov 23, 2020
591b321
adjust padding for inside tick labels
archmoj Nov 23, 2020
bd379f8
improve overlap detection for aligned tick labels
archmoj Nov 23, 2020
05893b3
use lib function to convert from deg to rad
archmoj Nov 24, 2020
b5f09fb
revisit ticklabelposition logic
archmoj Nov 24, 2020
6e80dc9
fix issue 5301
archmoj Nov 24, 2020
21b59e4
fixups u and v
archmoj Nov 24, 2020
e01dc32
correct linewidth effect
archmoj Nov 24, 2020
b46327c
fixup tickangle for inside tick labels
archmoj Nov 25, 2020
aa57518
handle multi-line dates on x-axis
archmoj Nov 25, 2020
70e708a
adjust pad for inside labels and make more room for end labels to appear
archmoj Nov 25, 2020
fa0f3b2
validate new mocks
archmoj Nov 25, 2020
c87cda6
add jsamine tests for ticklabelposition defaults
archmoj Nov 25, 2020
c59fcd9
clear function to hide inside tick labels and ensure subplot
archmoj Nov 25, 2020
d2c656d
simplify reading axis end positions to hide inside tick labels
archmoj Nov 25, 2020
d7423af
ensure ax._rl in hide function
archmoj Nov 25, 2020
60addca
compute linear range if not defined
archmoj Nov 26, 2020
a83d5af
add ticklabelposition option to colorbar
archmoj Nov 26, 2020
01efcf5
add inside and outside ticklabelposition options to mocks
archmoj Nov 26, 2020
5244a09
use fresh linear range in the hide function
archmoj Nov 26, 2020
a7518ec
add image test for issue 5301 - and before fixing another bug on the …
archmoj Nov 26, 2020
8efa7bd
adjust vertical ticklabels on x axes
archmoj Nov 26, 2020
2ff6cfb
revisit positioning of inside tick labels after bug fix
archmoj Nov 26, 2020
2108c8d
test another angle on the mock
archmoj Nov 26, 2020
a1d021b
adjustment for vertical tick labels inside
archmoj Nov 26, 2020
6f575a3
adjustment for vertical labels on the x axis and update baselines
archmoj Nov 26, 2020
0e52adb
remove duplicate pad functions
archmoj Dec 1, 2020
999f877
revert guiEdit and fixup autorange interactions for inside tick labels
archmoj Dec 1, 2020
7b22175
revise axId scope
archmoj Dec 2, 2020
445eee4
fixup autorange on interactions
archmoj Dec 2, 2020
5f252f5
consider some offset from top of the bounding box when hiding a horiz…
archmoj Dec 2, 2020
90c3097
adjust position on bar_multiline_labels mock
archmoj Dec 2, 2020
40c09b7
add comment about U and V
archmoj Dec 2, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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