Skip to content

Commit 8ede0f7

Browse files
committed
fix bug where showexponent 'first'/'last' showed no exponents on overlaid axes
1 parent 99cfed5 commit 8ede0f7

4 files changed

Lines changed: 99 additions & 22 deletions

File tree

src/plots/cartesian/axes.js

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,9 @@ axes.calcTicks = function calcTicks(ax, opts) {
11131113
var obj = { value: x };
11141114

11151115
if(major) {
1116+
// mark first major tick, for showexponent/showtickprefix/showticksuffix 'first'
1117+
if(x === x0) obj.first = true;
1118+
11161119
if(isDLog && (x !== (x | 0))) {
11171120
obj.simpleLabel = true;
11181121
}
@@ -1255,9 +1258,10 @@ axes.calcTicks = function calcTicks(ax, opts) {
12551258
tickVals.pop();
12561259
}
12571260

1258-
// save the last tick as well as first, so we can
1259-
// show the exponent only on the last one
1261+
// save the last tick as well as first
12601262
ax._tmax = (tickVals[tickVals.length - 1] || {}).value;
1263+
// mark the last major tick, for showexponent/showtickprefix/showticksuffix 'last'
1264+
if(tickVals.length) tickVals[tickVals.length - 1].last = true;
12611265

12621266
// for showing the rest of a date when the main tick label is only the
12631267
// latter part: ax._prevDateHead holds what we showed most recently.
@@ -1279,7 +1283,8 @@ axes.calcTicks = function calcTicks(ax, opts) {
12791283
ax,
12801284
tickVal.value,
12811285
false, // hover
1282-
tickVal.simpleLabel // noSuffixPrefix
1286+
tickVal.simpleLabel, // noSuffixPrefix
1287+
{first: tickVal.first, last: tickVal.last} // positionFlags
12831288
);
12841289
var p = tickVal.periodX;
12851290
if(p !== undefined) {
@@ -1351,6 +1356,16 @@ function syncTicks(ax) {
13511356

13521357
var ticksOut = [];
13531358
if(baseAxis._vals) {
1359+
// find the indices of the first and last labelled (major, non-noTick) base ticks,
1360+
// for showexponent/showtickprefix/showticksuffix 'first'/'last'
1361+
var firstMajorIdx = -1;
1362+
var lastMajorIdx = -1;
1363+
for(var j = 0; j < baseAxis._vals.length; j++) {
1364+
if(baseAxis._vals[j].noTick || baseAxis._vals[j].minor) continue;
1365+
if(firstMajorIdx === -1) firstMajorIdx = j;
1366+
lastMajorIdx = j;
1367+
}
1368+
13541369
for(var i = 0; i < baseAxis._vals.length; i++) {
13551370
// filter vals with noTick flag
13561371
if(baseAxis._vals[i].noTick) {
@@ -1362,7 +1377,10 @@ function syncTicks(ax) {
13621377

13631378
// get the tick for the current axis based on position
13641379
var vali = ax.p2l(pos);
1365-
var obj = axes.tickText(ax, vali);
1380+
var obj = axes.tickText(ax, vali, false, undefined, {
1381+
first: i === firstMajorIdx,
1382+
last: i === lastMajorIdx,
1383+
});
13661384

13671385
// assign minor ticks
13681386
if(baseAxis._vals[i].minor) {
@@ -1408,10 +1426,27 @@ function arrayTicks(ax, majorOnly) {
14081426
// except with more precision to the numbers
14091427
if(!Lib.isArrayOrTypedArray(text)) text = [];
14101428

1429+
// indices of the first and last in-range major ticks,
1430+
// showexponent/showtickprefix/showticksuffix 'first'/'last'
1431+
var firstIdx = -1;
1432+
var lastIdx = -1;
1433+
if(!isMinor) {
1434+
for(var k = 0; k < vals.length; k++) {
1435+
var valk = tickVal2l(vals[k]);
1436+
if(valk > tickMin && valk < tickMax) {
1437+
if(firstIdx === -1) firstIdx = k;
1438+
lastIdx = k;
1439+
}
1440+
}
1441+
}
1442+
14111443
for(var i = 0; i < vals.length; i++) {
14121444
var vali = tickVal2l(vals[i]);
14131445
if(vali > tickMin && vali < tickMax) {
1414-
var obj = axes.tickText(ax, vali, false, String(text[i]));
1446+
var obj = axes.tickText(ax, vali, false, String(text[i]), {
1447+
first: i === firstIdx,
1448+
last: i === lastIdx,
1449+
});
14151450
if(isMinor) {
14161451
obj.minor = true;
14171452
obj.text = '';
@@ -1725,13 +1760,23 @@ axes.tickFirst = function(ax, opts) {
17251760
} else throw 'unrecognized dtick ' + String(dtick);
17261761
};
17271762

1728-
// draw the text for one tick.
1729-
// px,py are the location on gd.paper
1730-
// prefix is there so the x axis ticks can be dropped a line
1731-
// ax is the axis layout, x is the tick value
1732-
// hover is a (truthy) flag for whether to show numbers with a bit
1733-
// more precision for hovertext
1734-
axes.tickText = function(ax, x, hover, noSuffixPrefix) {
1763+
/**
1764+
* Compute the text and metadata for one tick.
1765+
*
1766+
* @param {object} ax: the axis layout object
1767+
* @param {number} x: the tick value
1768+
* @param {boolean} hover: whether tick being computed for hovertext (as opposed to axis)
1769+
* @param {boolean} noSuffixPrefix: whether to skip adding tickprefix and ticksuffix
1770+
* @param {object} positionFlags: optional flags describing where this tick sits on the
1771+
* axis, used by the showexponent/showtickprefix/showticksuffix 'first'/'last' options:
1772+
* - first (boolean): whether this is the first (labelled, major) tick on the axis
1773+
* - last (boolean): whether this is the last (labelled, major) tick on the axis
1774+
* @return {object} the tick object, including its formatted `text`
1775+
*/
1776+
axes.tickText = function(ax, x, hover, noSuffixPrefix, positionFlags) {
1777+
var first = !!positionFlags?.first;
1778+
var last = !!positionFlags?.last;
1779+
17351780
var out = tickTextObj(ax, x);
17361781
var arrayMode = ax.tickmode === 'array';
17371782
var extraPrecision = hover || arrayMode;
@@ -1765,13 +1810,10 @@ axes.tickText = function(ax, x, hover, noSuffixPrefix) {
17651810
function isHidden(showAttr) {
17661811
if(showAttr === undefined) return true;
17671812
if(hover) return showAttr === 'none';
1768-
1769-
var firstOrLast = {
1770-
first: ax._tmin,
1771-
last: ax._tmax
1772-
}[showAttr];
1773-
1774-
return showAttr !== 'all' && x !== firstOrLast;
1813+
if(showAttr === 'all') return false;
1814+
if(showAttr === 'first') return !first;
1815+
if(showAttr === 'last') return !last;
1816+
return true; // fallback for the hover is false and showAttr==='none' or another value
17751817
}
17761818

17771819
var hideexp = hover ?

src/traces/carpet/calc_labels.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ module.exports = function calcLabels(trace, axis) {
1313
gridline = gridlines[i];
1414

1515
if(['start', 'both'].indexOf(axis.showticklabels) !== -1) {
16-
tobj = Axes.tickText(axis, gridline.value);
16+
tobj = Axes.tickText(axis, gridline.value, false, false, {
17+
first: i === 0,
18+
last: i === gridlines.length - 1,
19+
});
1720

1821
extendFlat(tobj, {
1922
prefix: prefix,
@@ -32,7 +35,10 @@ module.exports = function calcLabels(trace, axis) {
3235
}
3336

3437
if(['end', 'both'].indexOf(axis.showticklabels) !== -1) {
35-
tobj = Axes.tickText(axis, gridline.value);
38+
tobj = Axes.tickText(axis, gridline.value, false, false, {
39+
first: i === 0,
40+
last: i === gridlines.length - 1,
41+
});
3642

3743
extendFlat(tobj, {
3844
endAnchor: false,

src/types/generated/schema.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12876,7 +12876,7 @@ export interface LayoutAxis {
1287612876
* Minimum: 0
1287712877
*/
1287812878
ticklen?: number;
12879-
/** Sets the tick mode for this axis. If *auto*, the number of ticks is set via `nticks`. If *linear*, the placement of the ticks is determined by a starting position `tick0` and a tick step `dtick` (*linear* is the default value if `tick0` and `dtick` are provided). If *array*, the placement of the ticks is set via `tickvals` and the tick text is `ticktext`. (*array* is the default value if `tickvals` is provided). If *sync*, the number of ticks will sync with the overlayed axis set by `overlaying` property. */
12879+
/** Sets the tick mode for this axis. If *auto*, the number of ticks is set via `nticks`. If *linear*, the placement of the ticks is determined by a starting position `tick0` and a tick step `dtick` (*linear* is the default value if `tick0` and `dtick` are provided). If *array*, the placement of the ticks is set via `tickvals` and the tick text is `ticktext`. (*array* is the default value if `tickvals` is provided). If *sync*, the number of ticks will sync with the overlayed axis set by `overlaying` property. When no other tick info is provided, overlaying (non-categorical) axes default to *sync*, while other axes default to *auto*. */
1288012880
tickmode?: 'auto' | 'linear' | 'array' | 'sync';
1288112881
/** Sets a tick label prefix. */
1288212882
tickprefix?: string;

test/jasmine/tests/axes_test.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8416,4 +8416,33 @@ describe('test tickmode calculator', function() {
84168416
}).then(done, done.fail);
84178417
});
84188418
});
8419+
8420+
describe('sync', function() {
8421+
it('shows the exponent on a synced overlaying axis with showexponent *first*/*last*', function(done) {
8422+
Plotly.newPlot(gd, {
8423+
data: [
8424+
{y: [0, 6]},
8425+
{y: [0, 60000], yaxis: 'y2'}
8426+
],
8427+
layout: {
8428+
yaxis2: {
8429+
overlaying: 'y',
8430+
exponentformat: 'SI',
8431+
showexponent: 'last'
8432+
}
8433+
}
8434+
}).then(function() {
8435+
var ax = gd._fullLayout.yaxis2;
8436+
expect(ax.tickmode).toBe('sync');
8437+
8438+
var labels = ax._vals
8439+
.filter(function(d) { return !d.minor; })
8440+
.map(function(d) { return d.text; });
8441+
8442+
// the multiplier (e.g. 'k') must still appear on the labelled tick
8443+
expect(labels.some(function(t) { return /k$/.test(t); }))
8444+
.toBe(true, 'expected an SI prefix in: ' + JSON.stringify(labels));
8445+
}).then(done, done.fail);
8446+
});
8447+
});
84198448
});

0 commit comments

Comments
 (0)