Skip to content

autotickangles for axes with tickson="boundaries" or showdividers=true #6967

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

Merged
merged 8 commits into from
Apr 16, 2024
2 changes: 2 additions & 0 deletions draftlogs/6967_fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Fix applying `autotickangles` on axes with `showdividers` as well as cases where `tickson` is set to "boundaries" [[#6967](https://github.com/plotly/plotly.js/pull/6967)],
with thanks to @my-tien for the contribution!
65 changes: 35 additions & 30 deletions src/plots/cartesian/axes.js
Original file line number Diff line number Diff line change
Expand Up @@ -3748,27 +3748,54 @@ axes.drawLabels = function(gd, ax, opts) {
});
});

if((ax.tickson === 'boundaries' || ax.showdividers) && !opts.secondary) {
// autotickangles
// if there are dividers or ticks on boundaries, the labels will be in between and
// we need to prevent overlap with the next divider/tick. Else the labels will be on
// the ticks and we need to prevent overlap with the next label.

// TODO should secondary labels also fall into this fix-overlap regime?
var preventOverlapWithTick = (ax.tickson === 'boundaries' || ax.showdividers) && !opts.secondary;

var vLen = vals.length;
var tickSpacing = Math.abs((vals[vLen - 1].x - vals[0].x) * ax._m) / (vLen - 1);

var adjacent = preventOverlapWithTick ? tickSpacing / 2 : tickSpacing;
var opposite = preventOverlapWithTick ? ax.ticklen : maxFontSize * 1.25 * maxLines;
var hypotenuse = Math.sqrt(Math.pow(adjacent, 2) + Math.pow(opposite, 2));
var maxCos = adjacent / hypotenuse;
var autoTickAnglesRadians = ax.autotickangles.map(
function(degrees) { return degrees * Math.PI / 180; }
);
var angleRadians = autoTickAnglesRadians.find(
function(angle) { return Math.abs(Math.cos(angle)) <= maxCos; }
);
if(angleRadians === undefined) {
// no angle with smaller cosine than maxCos, just pick the angle with smallest cosine
angleRadians = autoTickAnglesRadians.reduce(
function(currentMax, nextAngle) {
return Math.abs(Math.cos(currentMax)) < Math.abs(Math.cos(nextAngle)) ? currentMax : nextAngle;
}
, autoTickAnglesRadians[0]
);
}
var newAngle = angleRadians * (180 / Math.PI /* to degrees */);

if(preventOverlapWithTick) {
var gap = 2;
if(ax.ticks) gap += ax.tickwidth / 2;

// TODO should secondary labels also fall into this fix-overlap regime?

for(i = 0; i < lbbArray.length; i++) {
var xbnd = vals[i].xbnd;
var lbb = lbbArray[i];
if(
(xbnd[0] !== null && (lbb.left - ax.l2p(xbnd[0])) < gap) ||
(xbnd[1] !== null && (ax.l2p(xbnd[1]) - lbb.right) < gap)
) {
autoangle = 90;
autoangle = newAngle;
break;
}
}
} else {
var vLen = vals.length;
var tickSpacing = Math.abs((vals[vLen - 1].x - vals[0].x) * ax._m) / (vLen - 1);

var ticklabelposition = ax.ticklabelposition || '';
var has = function(str) {
return ticklabelposition.indexOf(str) !== -1;
Expand All @@ -3779,29 +3806,7 @@ axes.drawLabels = function(gd, ax, opts) {
var isBottom = has('bottom');
var isAligned = isBottom || isLeft || isTop || isRight;
var pad = !isAligned ? 0 :
(ax.tickwidth || 0) + 2 * TEXTPAD;

// autotickangles
var adjacent = tickSpacing;
var opposite = maxFontSize * 1.25 * maxLines;
var hypotenuse = Math.sqrt(Math.pow(adjacent, 2) + Math.pow(opposite, 2));
var maxCos = adjacent / hypotenuse;
var autoTickAnglesRadians = ax.autotickangles.map(
function(degrees) { return degrees * Math.PI / 180; }
);
var angleRadians = autoTickAnglesRadians.find(
function(angle) { return Math.abs(Math.cos(angle)) <= maxCos; }
);
if(angleRadians === undefined) {
// no angle with smaller cosine than maxCos, just pick the angle with smallest cosine
angleRadians = autoTickAnglesRadians.reduce(
function(currentMax, nextAngle) {
return Math.abs(Math.cos(currentMax)) < Math.abs(Math.cos(nextAngle)) ? currentMax : nextAngle;
}
, autoTickAnglesRadians[0]
);
}
var newAngle = angleRadians * (180 / Math.PI /* to degrees */);
(ax.tickwidth || 0) + 2 * TEXTPAD;

for(i = 0; i < lbbArray.length - 1; i++) {
if(Lib.bBoxIntersect(lbbArray[i], lbbArray[i + 1], pad)) {
Expand Down
Binary file modified test/image/baselines/tickson_boundaries.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions test/jasmine/tests/axes_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4896,13 +4896,13 @@ describe('Test axes', function() {
function _assert(msg, exp) {
var tickLabels = d3SelectAll('.xtick > text');

expect(tickLabels.size()).toBe(exp.angle.length, msg + ' - # of tick labels');
expect(tickLabels.size()).withContext(msg + ' - # of tick labels').toBe(exp.angle.length);

tickLabels.each(function(_, i) {
var t = d3Select(this).attr('transform');
var rotate = (t.split('rotate(')[1] || '').split(')')[0];
var angle = rotate.split(',')[0];
expect(Number(angle)).toBe(exp.angle[i], msg + ' - node ' + i);
expect(Number(angle)).withContext(msg + ' - node ' + i).toBeCloseTo(exp.angle[i], 2);
});
}

Expand All @@ -4920,7 +4920,7 @@ describe('Test axes', function() {
})
.then(function() {
_assert('base - rotated', {
angle: [90, 90, 90]
angle: [30, 30, 30]
});

return Plotly.relayout(gd, 'xaxis.range', [-0.4, 1.4]);
Expand All @@ -4934,7 +4934,7 @@ describe('Test axes', function() {
})
.then(function() {
_assert('narrow range / wide ticks - rotated', {
angle: [90, 90]
angle: [30, 30]
});
})
.then(done, done.fail);
Expand Down
6 changes: 3 additions & 3 deletions test/jasmine/tests/cartesian_interact_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -779,13 +779,13 @@ describe('axis zoom/pan and main plot zoom', function() {

function _assertLabels(msg, exp) {
var tickLabels = d3Select(gd).selectAll('.xtick > text');
expect(tickLabels.size()).toBe(exp.angle.length, msg + ' - # of tick labels');
expect(tickLabels.size()).withContext(msg + ' - # of tick labels').toBe(exp.angle.length);

tickLabels.each(function(_, i) {
var t = d3Select(this).attr('transform');
var rotate = (t.split('rotate(')[1] || '').split(')')[0];
var angle = rotate.split(',')[0];
expect(Number(angle)).toBe(exp.angle[i], msg + ' - node ' + i);
expect(Number(angle)).withContext(msg + ' - node ' + i).toBeCloseTo(exp.angle[i], 2);
});

var tickLabels2 = d3Select(gd).selectAll('.xtick2 > text');
Expand Down Expand Up @@ -813,7 +813,7 @@ describe('axis zoom/pan and main plot zoom', function() {
})
.then(function() {
return _run('drag to wide-range -> rotates labels', [-340, 0], {
angle: [90, 90, 90, 90, 90, 90, 90],
angle: [30, 30, 30, 30, 30, 30, 30],
y: [430, 430]
});
})
Expand Down