Skip to content

Commit 426077a

Browse files
committed
handle and test a bunch of tickvals / ticktext edge cases
1 parent c130932 commit 426077a

File tree

3 files changed

+126
-11
lines changed

3 files changed

+126
-11
lines changed

src/plots/cartesian/axes.js

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -611,8 +611,6 @@ axes.autoBin = function(data, ax, nbins, is2d) {
611611
// in any case, set tickround to # of digits to round tick labels to,
612612
// or codes to this effect for log and date scales
613613
axes.calcTicks = function calcTicks(ax) {
614-
if(ax.tickmode === 'array') return arrayTicks(ax);
615-
616614
var rng = ax.range.map(ax.r2l);
617615

618616
// calculate max number of (auto) ticks to display based on plot size
@@ -629,6 +627,11 @@ axes.calcTicks = function calcTicks(ax) {
629627
nt = Lib.constrain(ax._length / minPx, 4, 9) + 1;
630628
}
631629
}
630+
631+
// add a couple of extra digits for filling in ticks when we
632+
// have explicit tickvals without tick text
633+
if(ax.tickmode === 'array') nt *= 100;
634+
632635
axes.autoTicks(ax, Math.abs(rng[1] - rng[0]) / nt);
633636
// check for a forced minimum dtick
634637
if(ax._minDtick > 0 && ax.dtick < ax._minDtick * 2) {
@@ -645,6 +648,10 @@ axes.calcTicks = function calcTicks(ax) {
645648
// now figure out rounding of tick values
646649
autoTickRound(ax);
647650

651+
// now that we've figured out the auto values for formatting
652+
// in case we're missing some ticktext, we can break out for array ticks
653+
if(ax.tickmode === 'array') return arrayTicks(ax);
654+
648655
// find the first tick
649656
ax._tmin = axes.tickFirst(ax);
650657

@@ -704,8 +711,11 @@ function arrayTicks(ax) {
704711
// except with more precision to the numbers
705712
if(!Array.isArray(text)) text = [];
706713

714+
// make sure showing ticks doesn't accidentally add new categories
715+
var tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l;
716+
707717
for(i = 0; i < vals.length; i++) {
708-
vali = ax.d2l(vals[i]);
718+
vali = tickVal2l(vals[i]);
709719
if(vali > tickMin && vali < tickMax) {
710720
if(text[i] === undefined) ticksOut[j] = axes.tickText(ax, vali);
711721
else ticksOut[j] = tickTextObj(ax, vali, String(text[i]));
@@ -856,7 +866,7 @@ function autoTickRound(ax) {
856866
// not necessarily *all* the information in tick0 though, if it's really odd
857867
// minimal string length for tick0: 'd' is 10, 'M' is 16, 'S' is 19
858868
// take off a leading minus (year < 0 so length is consistent)
859-
var tick0ms = Lib.dateTime2ms(ax.tick0),
869+
var tick0ms = Lib.dateTime2ms(ax.tick0) || 0,
860870
tick0str = Lib.ms2DateTime(tick0ms).replace(/^-/, ''),
861871
tick0len = tick0str.length;
862872

@@ -1030,13 +1040,14 @@ axes.tickText = function(ax, x, hover) {
10301040
hideexp,
10311041
arrayMode = ax.tickmode === 'array',
10321042
extraPrecision = hover || arrayMode,
1033-
i;
1043+
i,
1044+
tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l;
10341045

10351046
if(arrayMode && Array.isArray(ax.ticktext)) {
10361047
var rng = ax.range.map(ax.r2l),
10371048
minDiff = Math.abs(rng[1] - rng[0]) / 10000;
10381049
for(i = 0; i < ax.ticktext.length; i++) {
1039-
if(Math.abs(x - ax.d2l(ax.tickvals[i])) < minDiff) break;
1050+
if(Math.abs(x - tickVal2l(ax.tickvals[i])) < minDiff) break;
10401051
}
10411052
if(i < ax.ticktext.length) {
10421053
out.text = String(ax.ticktext[i]);
@@ -1113,12 +1124,12 @@ function formatDate(ax, out, hover, extraPrecision) {
11131124
else if(tr === 'm') tt = monthFormat(d);
11141125
else {
11151126
if(tr === 'd') {
1116-
if(!hover) suffix = '<br>' + yearFormat(d);
1127+
suffix = yearFormat(d);
11171128

11181129
tt = dayFormat(d);
11191130
}
11201131
else {
1121-
if(!hover) suffix = '<br>' + yearMonthDayFormat(d);
1132+
suffix = yearMonthDayFormat(d);
11221133

11231134
tt = minuteFormat(d);
11241135
if(tr !== 'M') {
@@ -1136,9 +1147,26 @@ function formatDate(ax, out, hover, extraPrecision) {
11361147
}
11371148
}
11381149
}
1139-
if(suffix && (!ax._inCalcTicks || (suffix !== ax._prevSuffix))) {
1140-
tt += suffix;
1141-
ax._prevSuffix = suffix;
1150+
if(ax.tickmode === 'array') {
1151+
// we get extra precision in array mode, but it may be useless, strip it off
1152+
if(tt === '00:00:00') {
1153+
tt = suffix;
1154+
suffix = '';
1155+
}
1156+
else {
1157+
tt = tt.replace(/:00$/, '');
1158+
}
1159+
}
1160+
1161+
if(suffix) {
1162+
if(hover) {
1163+
// hover puts it all on one line, so suffix works best up front
1164+
tt = suffix + (tt ? ' ' + tt : '');
1165+
}
1166+
else if(!ax._inCalcTicks || (suffix !== ax._prevSuffix)) {
1167+
tt += '<br>' + suffix;
1168+
ax._prevSuffix = suffix;
1169+
}
11421170
}
11431171
out.text = tt;
11441172
}

src/plots/cartesian/set_convert.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,14 @@ module.exports = function setConvert(ax) {
313313
return c === -1 ? BADNUM : c;
314314
};
315315

316+
ax.d2l_noadd = function(v) {
317+
// d2c variant that that won't add categories but will also
318+
// allow numbers to be mapped to the linearized axis positions
319+
var index = ax._categories.indexOf(v);
320+
if(index !== -1) return index;
321+
if(typeof v === 'number') return v;
322+
};
323+
316324
ax.d2l = ax.d2c;
317325
ax.r2l = num;
318326
ax.l2r = num;

test/jasmine/tests/axes_test.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,5 +1499,84 @@ describe('Test axes', function() {
14991499
];
15001500
expect(textOut).toEqual(expectedText);
15011501
});
1502+
1503+
it('should handle edge cases with dates and tickvals', function() {
1504+
var textOut = mockCalc({
1505+
type: 'date',
1506+
tickmode: 'array',
1507+
tickvals: [
1508+
'2012-01-01',
1509+
new Date(2012, 2, 1).getTime(),
1510+
'2012-08-01 00:00:00',
1511+
'2012-10-01 12:00:00',
1512+
new Date(2013, 0, 1, 0, 0, 1).getTime(),
1513+
'2010-01-01', '2014-01-01' // off the axis
1514+
],
1515+
// only the first two have text
1516+
ticktext: ['New year', 'February'],
1517+
1518+
// required to get calcTicks to run
1519+
range: ['2011-12-10', '2013-01-23'],
1520+
nticks: 10
1521+
});
1522+
1523+
var expectedText = [
1524+
'New year',
1525+
'February',
1526+
'Aug 1, 2012',
1527+
'12:00<br>Oct 1, 2012',
1528+
'00:00:01<br>Jan 1, 2013'
1529+
];
1530+
expect(textOut).toEqual(expectedText);
1531+
});
1532+
1533+
it('should handle tickvals edge cases with linear and log axes', function() {
1534+
['linear', 'log'].forEach(function(axType) {
1535+
var textOut = mockCalc({
1536+
type: axType,
1537+
tickmode: 'array',
1538+
tickvals: [1, 1.5, 2.6999999, 3, 3.999, 10, 0.1],
1539+
ticktext: ['One', '...and a half'],
1540+
// I'll be so happy when I can finally get rid of this switch!
1541+
range: axType === 'log' ? [-0.2, 0.8] : [0.5, 5],
1542+
nticks: 10
1543+
});
1544+
1545+
var expectedText = [
1546+
'One',
1547+
'...and a half', // the first two get explicit labels
1548+
'2.7', // 2.6999999 gets rounded to 2.7
1549+
'3',
1550+
'3.999' // 3.999 does not get rounded
1551+
// 10 and 0.1 are off scale
1552+
];
1553+
expect(textOut).toEqual(expectedText, axType);
1554+
});
1555+
});
1556+
1557+
it('should handle tickvals edge cases with category axes', function() {
1558+
var textOut = mockCalc({
1559+
type: 'category',
1560+
_categories: ['a', 'b', 'c', 'd'],
1561+
tickmode: 'array',
1562+
tickvals: ['a', 1, 1.5, 'c', 2.7, 3, 'e', 4, 5, -2],
1563+
ticktext: ['A!', 'B?', 'B->C'],
1564+
range: [-0.5, 4.5],
1565+
nticks: 10
1566+
});
1567+
1568+
var expectedText = [
1569+
'A!', // category position, explicit text
1570+
'B?', // integer position, explicit text
1571+
'B->C', // non-integer position, explicit text
1572+
'c', // category position, no text: use category
1573+
'd', // non-integer position, no text: use closest category
1574+
'd', // integer position, no text: use category
1575+
'' // 4: number with no close category: leave blank
1576+
// but still include it so we get a tick mark & grid
1577+
// 'e', 5, -2: bad category and numbers out of range: omitted
1578+
];
1579+
expect(textOut).toEqual(expectedText);
1580+
});
15021581
});
15031582
});

0 commit comments

Comments
 (0)