-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
tickvals / ticktext edge cases #1191
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
Changes from 1 commit
426077a
27fb4b1
74da7a4
77f745e
6762130
c70f04a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -611,8 +611,6 @@ axes.autoBin = function(data, ax, nbins, is2d) { | |
// in any case, set tickround to # of digits to round tick labels to, | ||
// or codes to this effect for log and date scales | ||
axes.calcTicks = function calcTicks(ax) { | ||
if(ax.tickmode === 'array') return arrayTicks(ax); | ||
|
||
var rng = ax.range.map(ax.r2l); | ||
|
||
// calculate max number of (auto) ticks to display based on plot size | ||
|
@@ -629,6 +627,11 @@ axes.calcTicks = function calcTicks(ax) { | |
nt = Lib.constrain(ax._length / minPx, 4, 9) + 1; | ||
} | ||
} | ||
|
||
// add a couple of extra digits for filling in ticks when we | ||
// have explicit tickvals without tick text | ||
if(ax.tickmode === 'array') nt *= 100; | ||
|
||
axes.autoTicks(ax, Math.abs(rng[1] - rng[0]) / nt); | ||
// check for a forced minimum dtick | ||
if(ax._minDtick > 0 && ax.dtick < ax._minDtick * 2) { | ||
|
@@ -645,6 +648,10 @@ axes.calcTicks = function calcTicks(ax) { | |
// now figure out rounding of tick values | ||
autoTickRound(ax); | ||
|
||
// now that we've figured out the auto values for formatting | ||
// in case we're missing some ticktext, we can break out for array ticks | ||
if(ax.tickmode === 'array') return arrayTicks(ax); | ||
|
||
// find the first tick | ||
ax._tmin = axes.tickFirst(ax); | ||
|
||
|
@@ -704,8 +711,11 @@ function arrayTicks(ax) { | |
// except with more precision to the numbers | ||
if(!Array.isArray(text)) text = []; | ||
|
||
// make sure showing ticks doesn't accidentally add new categories | ||
var tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. non- 🚫 . As far as I know, we only need to add new categories during the trace modules' There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. right, like #1191 (comment) - there are a bunch of uses outside |
||
|
||
for(i = 0; i < vals.length; i++) { | ||
vali = ax.d2l(vals[i]); | ||
vali = tickVal2l(vals[i]); | ||
if(vali > tickMin && vali < tickMax) { | ||
if(text[i] === undefined) ticksOut[j] = axes.tickText(ax, vali); | ||
else ticksOut[j] = tickTextObj(ax, vali, String(text[i])); | ||
|
@@ -856,7 +866,7 @@ function autoTickRound(ax) { | |
// not necessarily *all* the information in tick0 though, if it's really odd | ||
// minimal string length for tick0: 'd' is 10, 'M' is 16, 'S' is 19 | ||
// take off a leading minus (year < 0 so length is consistent) | ||
var tick0ms = Lib.dateTime2ms(ax.tick0), | ||
var tick0ms = Lib.dateTime2ms(ax.tick0) || 0, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, that's an artifact of an older iteration... not needed anymore. |
||
tick0str = Lib.ms2DateTime(tick0ms).replace(/^-/, ''), | ||
tick0len = tick0str.length; | ||
|
||
|
@@ -1030,13 +1040,14 @@ axes.tickText = function(ax, x, hover) { | |
hideexp, | ||
arrayMode = ax.tickmode === 'array', | ||
extraPrecision = hover || arrayMode, | ||
i; | ||
i, | ||
tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l; | ||
|
||
if(arrayMode && Array.isArray(ax.ticktext)) { | ||
var rng = ax.range.map(ax.r2l), | ||
minDiff = Math.abs(rng[1] - rng[0]) / 10000; | ||
for(i = 0; i < ax.ticktext.length; i++) { | ||
if(Math.abs(x - ax.d2l(ax.tickvals[i])) < minDiff) break; | ||
if(Math.abs(x - tickVal2l(ax.tickvals[i])) < minDiff) break; | ||
} | ||
if(i < ax.ticktext.length) { | ||
out.text = String(ax.ticktext[i]); | ||
|
@@ -1113,12 +1124,12 @@ function formatDate(ax, out, hover, extraPrecision) { | |
else if(tr === 'm') tt = monthFormat(d); | ||
else { | ||
if(tr === 'd') { | ||
if(!hover) suffix = '<br>' + yearFormat(d); | ||
suffix = yearFormat(d); | ||
|
||
tt = dayFormat(d); | ||
} | ||
else { | ||
if(!hover) suffix = '<br>' + yearMonthDayFormat(d); | ||
suffix = yearMonthDayFormat(d); | ||
|
||
tt = minuteFormat(d); | ||
if(tr !== 'M') { | ||
|
@@ -1136,9 +1147,26 @@ function formatDate(ax, out, hover, extraPrecision) { | |
} | ||
} | ||
} | ||
if(suffix && (!ax._inCalcTicks || (suffix !== ax._prevSuffix))) { | ||
tt += suffix; | ||
ax._prevSuffix = suffix; | ||
if(ax.tickmode === 'array') { | ||
// we get extra precision in array mode, but it may be useless, strip it off | ||
if(tt === '00:00:00') { | ||
tt = suffix; | ||
suffix = ''; | ||
} | ||
else { | ||
tt = tt.replace(/:00$/, ''); | ||
} | ||
} | ||
|
||
if(suffix) { | ||
if(hover) { | ||
// hover puts it all on one line, so suffix works best up front | ||
tt = suffix + (tt ? ' ' + tt : ''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previously, hover text on date axes would not get the suffix at all. This changes it for all date axes, not just those with array ticks, to include the suffix (as a prefix... hmm, maybe we should change this name...) which is sometimes more information than you need to interpret it, but it's useful often enough that I think this is the right way to go about it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That would be nice. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah... yep, I see where that's coming from. Looks like I need to add some more explicit tests of |
||
} | ||
else if(!ax._inCalcTicks || (suffix !== ax._prevSuffix)) { | ||
tt += '<br>' + suffix; | ||
ax._prevSuffix = suffix; | ||
} | ||
} | ||
out.text = tt; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -313,6 +313,14 @@ module.exports = function setConvert(ax) { | |
return c === -1 ? BADNUM : c; | ||
}; | ||
|
||
ax.d2l_noadd = function(v) { | ||
// d2c variant that that won't add categories but will also | ||
// allow numbers to be mapped to the linearized axis positions | ||
var index = ax._categories.indexOf(v); | ||
if(index !== -1) return index; | ||
if(typeof v === 'number') return v; | ||
}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. before this change, sometimes tickvals could add categories to the list, but it happened after autorange calculations so they didn't show up (until you zoom out on the axis). It was weird. Now that can't happen. At some point we should probably look through all other uses of |
||
|
||
ax.d2l = ax.d2c; | ||
ax.r2l = num; | ||
ax.l2r = num; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1499,5 +1499,84 @@ describe('Test axes', function() { | |
]; | ||
expect(textOut).toEqual(expectedText); | ||
}); | ||
|
||
it('should handle edge cases with dates and tickvals', function() { | ||
var textOut = mockCalc({ | ||
type: 'date', | ||
tickmode: 'array', | ||
tickvals: [ | ||
'2012-01-01', | ||
new Date(2012, 2, 1).getTime(), | ||
'2012-08-01 00:00:00', | ||
'2012-10-01 12:00:00', | ||
new Date(2013, 0, 1, 0, 0, 1).getTime(), | ||
'2010-01-01', '2014-01-01' // off the axis | ||
], | ||
// only the first two have text | ||
ticktext: ['New year', 'February'], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks pretty useful. Nice feature to have in the 🏦 |
||
|
||
// required to get calcTicks to run | ||
range: ['2011-12-10', '2013-01-23'], | ||
nticks: 10 | ||
}); | ||
|
||
var expectedText = [ | ||
'New year', | ||
'February', | ||
'Aug 1, 2012', | ||
'12:00<br>Oct 1, 2012', | ||
'00:00:01<br>Jan 1, 2013' | ||
]; | ||
expect(textOut).toEqual(expectedText); | ||
}); | ||
|
||
it('should handle tickvals edge cases with linear and log axes', function() { | ||
['linear', 'log'].forEach(function(axType) { | ||
var textOut = mockCalc({ | ||
type: axType, | ||
tickmode: 'array', | ||
tickvals: [1, 1.5, 2.6999999, 3, 3.999, 10, 0.1], | ||
ticktext: ['One', '...and a half'], | ||
// I'll be so happy when I can finally get rid of this switch! | ||
range: axType === 'log' ? [-0.2, 0.8] : [0.5, 5], | ||
nticks: 10 | ||
}); | ||
|
||
var expectedText = [ | ||
'One', | ||
'...and a half', // the first two get explicit labels | ||
'2.7', // 2.6999999 gets rounded to 2.7 | ||
'3', | ||
'3.999' // 3.999 does not get rounded | ||
// 10 and 0.1 are off scale | ||
]; | ||
expect(textOut).toEqual(expectedText, axType); | ||
}); | ||
}); | ||
|
||
it('should handle tickvals edge cases with category axes', function() { | ||
var textOut = mockCalc({ | ||
type: 'category', | ||
_categories: ['a', 'b', 'c', 'd'], | ||
tickmode: 'array', | ||
tickvals: ['a', 1, 1.5, 'c', 2.7, 3, 'e', 4, 5, -2], | ||
ticktext: ['A!', 'B?', 'B->C'], | ||
range: [-0.5, 4.5], | ||
nticks: 10 | ||
}); | ||
|
||
var expectedText = [ | ||
'A!', // category position, explicit text | ||
'B?', // integer position, explicit text | ||
'B->C', // non-integer position, explicit text | ||
'c', // category position, no text: use category | ||
'd', // non-integer position, no text: use closest category | ||
'd', // integer position, no text: use category | ||
'' // 4: number with no close category: leave blank | ||
// but still include it so we get a tick mark & grid | ||
// 'e', 5, -2: bad category and numbers out of range: omitted | ||
]; | ||
expect(textOut).toEqual(expectedText); | ||
}); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for that comment. Very useful.