-
Notifications
You must be signed in to change notification settings - Fork 11.9k
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
New time scale ticks.mode
and ticks.source
options
#4507
Conversation
src/helpers/helpers.time.js
Outdated
@@ -55,7 +55,7 @@ module.exports = function(Chart) { | |||
var ticks = []; | |||
if (options.maxTicks) { | |||
var stepSize = options.stepSize; | |||
var startTick = options.min !== undefined ? options.min : niceRange.min; | |||
var startTick = options.min === undefined || options.min === null ? niceRange.min : options.min; |
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.
could use helpers.isNullOrUndef
here (once rebased against that PR)
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.
done!
src/scales/scale.time.js
Outdated
} | ||
}; | ||
|
||
function buildLookupTable(ticks, min, max, linear) { |
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.
This lookup table idea could probably be used as a better solution to handling 0 in the log axis. We would define a 0 point, and then the rest of it is a logarithmic interpolation region.
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.
I think that the whole lookup (table) logic + ticks.mode/source
options could be the base implementation for the linear
, radiallinear
, log
and time
scales (numeric scale
?). This would refactor lot of code and make more consistent API between all that scales.
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.
i agree. it would save a lot of code elsewhere to move it to the base class
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.
Let's keep that work for another refactoring PR
4dc75e1
to
a8151b5
Compare
src/scales/scale.time.js
Outdated
@@ -36,10 +40,94 @@ module.exports = function(Chart) { | |||
}, | |||
}, | |||
ticks: { | |||
autoSkip: false | |||
autoSkip: false, | |||
mode: 'series', // 'linear|series' |
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.
I think series
and auto
is perhaps not the best default because I think that most of the time using series
it'd probably be used with linear
. I think auto
will probably be the better option most of the time when linear
is used. So I'd probably change on or the other of the defaults here.
linear
is the current default on the timescale. I'm assuming linear
& auto
is supported eventhough there's no screenshot of that one. That'd be my vote for combination of options for the default
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.
auto
is the default since it the current time scale behavior, changing it will be a breaking change. auto
generates linear ticks and in this case series
or linear
modes have exactly the same result. Now, let's say you change source for labels
, as a user I would expect to have the series
alignment of ticks by default. That why I picked series
and auto
by default.
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.
auto
doesn't exist in 2.6.0. It's been added since the most recent release. In fact it's a breaking change to switch it to auto
since the only behavior that exists in 2.6.0 is linear
series
& auto
together is not doing something that makes a lot of sense to me. Perhaps we should disallow that combination of options. If auto
is forcing it to be linear
eventhough the user specified series
that's going to be really confusing
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.
auto
is the actual time
scale implementation. It generates linear ticks because that's the way how generateTicks
works but we can imagine options, for example to exclude some ranges or whatever, that would make the generated ticks not linear anymore. In this case, auto + series
would perfectly works and that's the reason I split it in 2 options since all combinations make sense.
By default having auto + series
or auto + linear
is the same as long as we don't change how generateTicks
works. The question is: if the user switch to ticks.source: 'labels'
, what result it should expect by default? I think labels + series
would be the most wanted choice (which is your timeseries
behavior).
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.
I also think that auto + series
will be the best default when we will allow non linear ticks automatic generation: for example, let's say weekend are excluded, generated ticks would be: ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Monday", "Tuesday", ...] and we will want the same distance between each ticks (ticks.mode: 'series'
). If you set ticks.mode: 'linear'
, the distance between each "Friday" and "Monday" will be 3 times the distance between other days.
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.
I think auto + linear
needs to be the default based on my understanding of series
. When the mode
is set to series, the physical spacing between the ticks in pixels is constant.
However, this may cause problems when the time.min
and time.max
settings come into play. When these settings are used, it is likely that the gap between the ticks is not constant.
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.
Seems you right, auto + series
and auto + linear
don't do the same if leading / trailing times are adjusted. Still think auto + series
would be better defaults but it's a breaking change, so should be: auto + linear
.
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.
Since unit tests pass whatever auto + series
or auto + linear
, could be great to have you guys testing use cases that could break. I didn't succeed to have different results between both combinations, even with explicit min/max.
src/scales/scale.time.js
Outdated
} | ||
}; | ||
|
||
function buildLookupTable(ticks, min, max, linear) { | ||
var ilen = ticks.length; |
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.
I think ilen
isn't very understandable as a name. why not just use ticks.length
everywhere? is it a minification thing? could we call it ticksLen
instead?
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.
No, it's for performance, caching the length of the array is faster depending on the JS engine. ilen
, jlen
, etc. is commonly use out there and since it's most of the time only used for that purpose, I prefer to not change it (I also like that it's short and also refer to the i
var).
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.
You don't need to set ilen
to ticks.length
both here and below. You could just declare ilen
with all the other variables and then initialize below in the loop as you're doing now
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.
Good catch, will keep it initialized at the top though since I'm using it at line 54.
src/scales/scale.time.js
Outdated
table.push({time: min, decimal: 0}); | ||
} | ||
|
||
for (i=0, ilen=ticks.length; i<ilen; ++i) { |
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.
can we just declare i
here instead of up above? can we declare the other variables where they're first used?
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.
It's better for minification, gather all var declarations in the same block.
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.
Also declaring var in loops can have performance impact on some JS engines.
src/scales/scale.time.js
Outdated
|
||
// only add points that breaks the scale linearity | ||
if (Math.round((next+prev)/2) !== curr) { | ||
decimal = linear? (curr - min) / (max - min) : ilen > 1? i/(ilen-1) : 0; |
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.
should probably add a space before the ?
characters. we're also inconsistent about spacing around the /
characters on this line
I think a set of parens here would help clarify order of operations. how about:
linear ? (curr - min) / (max - min) : (ilen > 1 ? i / (ilen-1) : 0);
(at least i'm assuming that's the order of operations and it isn't (linear ? (curr - min) / (max - min) : ilen > 1) ? i / (ilen-1) : 0;
)
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.
+1 for the space before ?
, however I'm not in favor of useless parenthesis.
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.
ok. works for me
src/scales/scale.time.js
Outdated
|
||
// If value is out of bounds, use ticks [0, 1] or [n-1, n] for interpolation, | ||
// note that the lookup table always contains at least 2 items (min and max) | ||
var prev = !range.lo? table[0] : !range.hi? table[table.length-2] : range.lo; |
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.
I think it's strange not to have a space before ?
throughout
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.
+1
src/scales/scale.time.js
Outdated
} | ||
}; | ||
|
||
function buildLookupTable(ticks, min, max, linear) { |
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.
it'd help to document what the key and value of this table are. i.e. what's being looked up
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.
+1
src/scales/scale.time.js
Outdated
max = parse(options.time.max, me) || max; | ||
|
||
// In case there is no valid min/max, let's use today limits | ||
min = min === MAX_INTEGER? +moment().startOf('day') : min; |
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.
what's the +
on this line mean or do? i've never seen that syntax
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.
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.
I'd prefer to be more explicit and just use .valueOf()
here for readability. I'm sure I'm not the only one who wouldn't be familiar enough with moment to know about the +
syntax
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.
I'm not familiar with moment
(might be the first time) and had to search in their docs what valueOf
actually returns. The unary operator is commonly used to convert string to number (+'3' === 3
) and I think it's not more confusing than valueOf
once you know what it does. It may be even more obvious since you would expect a number from +moment()
which is likely the timestamp. And it's also shorter, so better for minification.
@@ -10,6 +10,10 @@ module.exports = function(Chart) { | |||
|
|||
var timeHelpers = helpers.time; |
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.
I originally refactored out helpers.time
so that it could be shared between the time
and timeseries
scales. Should we move them back to this file if we're not going to have two scales?
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.
Yeah totally agree, that's my next task with some optimizations if this PR get merged
src/scales/scale.time.js
Outdated
// @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/ | ||
function lookup(table, key, value) { | ||
var lo = 0; | ||
var hi = table.length -1; |
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.
spacing with operators is inconsistent throughout this function. e.g. there's a space before the -
but not after
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.
+1
src/scales/scale.time.js
Outdated
return this.getPixelForOffset(this.ticksAsTimestamps[index]); | ||
return index >= 0 && index < this.ticks.length? | ||
this.getPixelForOffset(this.ticks[index].time) : | ||
null; |
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.
I think eslint 4 will want each continued line to have extra indent. Can we just put this null
on the previous line to avoid that error?
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.
We didn't commit on that rule and ESLint can be tweaked to disable it. I tried to get used to it but I still prefer that form since it mimics the if/else indentation. Having null
on the previous line doesn't make it more readable: should be 1 line or 3, not 2. And since having all in one line is too long, I definitely prefer this layout. That's really minor though.
@etimberg @benmccann Not sure what you both think about this implementation, but if we are going to merge this PR instead of #4364, then I can start writing unit tests, docs and a sample that showcase the different combinations of |
c503732
to
ca6170b
Compare
src/scales/scale.time.js
Outdated
prev = ticks[i - 1] || 0; | ||
curr = ticks[i]; | ||
|
||
// only add points that breaks the scale linearity |
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.
can you expand upon this either here or in the main docs for the lookup table. It's not immediately clear to me what the structure of this table is or what this logic is for
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.
You right, I'm going to document a bit more the lookup stuff in the code.
I'm very impressed you were able to combine the two classes into a single one with so few if/else checks to check the mode! So yeah, I think we should probably use this one as it seems more elegant. I haven't had time to dig into the lookup table to really understand it yet. So I haven't read through the logic to check it's meeting all the different cases, but the screenshots all look like they're generating reasonable output, so I imagine it should be pretty close. I'll definitely continue to test this out in the future especially as I try to migrate the financial chart to use this scale instead of the one in the financial repo. The only thing I wanted to ask about is whether we could have the dataset the user passes in use |
When This table is used to optimize queries from value -> pixel and pixel -> value (binary search). It contains only ticks that break the time linearity and each entry represents a time and a position. Initially, the position was in pixel but we need to build this table before the layout is fully done (no guaranty on left, top, width and height). That's why the position is between [0 1] (actually, I'm going to rename The worst case is when all ticks break the linearity, generating one item in the table for each tick. That's an extreme scenario, but the binary search makes it very fast. The best case is when all ticks are linear and so the table contains only 2 items (min and max), making lookups as fast as the current |
Just a heads up, this PR needs a rebase now |
src/scales/scale.time.js
Outdated
* meaning that in the best case, it contains only 2 objects: {min, 0} and {max, 1}. `pos` is | ||
* a decimal between 0 and 1: 0 being the start of the scale (left or top) and 1 the other | ||
* extremity (left + width or top + height). Note that it would be more optimized to directly | ||
* store pre-computed pixels, but the scale dimensions are not guaranty at the time we need |
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.
guaranty -> guaranteed ?
src/scales/scale.time.js
Outdated
* Returns an array of {time, pos} objects used to interpolate a specific `time` or position | ||
* (`pos`) on the scale, by searching entries before and after the requested value. Since the | ||
* value is linearly interpolated, only timestamps that break the time linearity are added, | ||
* meaning that in the best case, it contains only 2 objects: {min, 0} and {max, 1}. `pos` is |
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.
Could you at the beginning of this method do something like the below? (not sure this is exactly right, but should give the idea)
if (linear) {
table.push({min, 0});
table.push({max, 1});
return table;
}
Then you could simplify the section with the complicated logic a bit. Right now it's:
if (Math.round((next + prev) / 2) !== curr) {
pos = linear ? (curr - min) / (max - min) : ilen > 1 ? i / (ilen - 1) : 0;
table.push({time: curr, pos: pos});
}
I think it could just become:
table.push({time: curr, pos: ilen > 1 ? i / (ilen - 1) : 0});
I think this has a few readability advantages. First, it would make it pretty explicit from reading the code what the lookup table looks like in the linear case. Right now it's not as immediately obvious from the code. Second, I think you can make the clarifying assumption that every point will break linearity in the series case, which would also simplify things by removing the if
check. There may be some points that don't break linearity, but I'm assuming it doesn't hurt to add extra points to the lookup table.
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.
Edit: You may be right on the first part, simplify the linear table with min/max. I would keep the linearity check though since it's a great optim that applies to all sources when series
.
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.
Fixed, good catch, thanks! I was too much focused on the series
approach that I didn't realize that we don't need the intermediary ticks when linear
.
Something I didn't test is if it works with vertical time scale. |
1046bc4
to
c1458d9
Compare
Squashed and rebased (and typo fixed). |
2a82bc3
to
dbeb5ea
Compare
dbeb5ea
to
b3fd40d
Compare
`ticks.source` (`'auto'`(default)|`'labels'`): `auto` generates "optimal" ticks based on min, max and a few more options (current `time` implementation`). `labels` generates ticks from the user given `data.labels` values (two additional trailing and leading ticks can be added if min and max are provided). `ticks.mode` (`'linear'`(default)|`series`): `series` displays ticks at the same distance from each other, whatever the time value they represent, while `linear` displays them linearly in time: the distance between each tick represent the amount of time between their time values.
b3fd40d
to
7c1a823
Compare
@benmccann Can you have a look at my last commit: it fixes a few edge cases (zero or one tick) and takes in account the effective scale min and max to build the lookup table. It also makes sure that the lookup table always contains two items (at least min/max) which simplifies things. |
]; | ||
} | ||
|
||
var table = []; |
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.
I'd move this declaration down to just before the for
loop where it's first used to make it clearer that it's not used until then
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.
Better for minification to have it gathered with other declarations.
} | ||
|
||
var table = []; | ||
var items = timestamps.slice(0); |
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.
Maybe add a comment here like:
// construct an "items" array with min, timestamps, max in that order
I'm not enough of a JS expert to know what slice(0)
and unshift
do off the top of my head, so I had to Google it. The comment would just make it a little faster to read
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.
I usually don't add this kind of comment since it explains what the code do and IMO slice()
, unshift()
and push()
are kind of JavaScript basis, so reading the code should be enough to understand it. But since you ask for it, will add a note.
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.
Actually, it's not even an array with min
, timestamps
and max
, it's an array with min if min !== timestamps[0]
, timestamps
, max if max !== timestamps[length -1]
. It would be a comment that explains exactly the code, so I prefer not.
src/scales/scale.time.js
Outdated
|
||
for (i = 0, ilen = items.length; i<ilen; ++i) { | ||
next = items[i + 1] || 0; | ||
prev = items[i - 1] || 0; |
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.
this || 0
gives me pause
Let's say you have:
i = 0
prev = 0
curr = 2
next = 4
Or you have:
i = 0
prev = 0
curr = 2
next = 5
Is it really the case that one of these should trigger the if
statement and one shouldn't? I guess in actuality that's quite unlikely to happen, but it's probably slightly safer to leave the variable undefined
and then add an extra check for that in the if
statement
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.
+1
src/scales/scale.time.js
Outdated
var mid, i0, i1; | ||
|
||
while (lo >= 0 && lo <= hi) { | ||
mid = ((lo + hi) >> 1); |
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.
probably don't really need parens around (lo + hi) >> 1
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.
+1
return table; | ||
} | ||
|
||
// @see adapted from http://www.anujgakhar.com/2014/03/01/binary-search-in-javascript/ |
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.
it's probably a bit better to re-implement this as the author hasn't granted a license for its use. https://www.quora.com/Intellectual-Property-Law-Is-it-legal-to-copy-small-code-snippets-from-open-source-software
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.
I think it's fine, our version is a bit different (not a copy/paste) and it's a binary search of a range and not an exact value. If I had to re-implement, I would do it exactly the same way since it's pretty clean and efficient.
src/scales/scale.time.js
Outdated
maxTimestamp = timeHelpers.parseTime(me, timeOpts.max).valueOf(); | ||
} else { | ||
stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize); | ||
stepSize = stepSize || timeHelpers.determineStepSize(min, max, unit, capacity); |
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.
You could probably make these a single statement:
stepSize = helpers.valueOrDefault(timeOpts.stepSize, timeOpts.unitStepSize)
|| timeHelpers.determineStepSize(min, max, unit, capacity);
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.
+1
src/scales/scale.time.js
Outdated
}); | ||
|
||
// Recompute min/max, the ticks generation might have changed them (BUG?) | ||
min = ticks.length? ticks[0] : min; |
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.
space before ?
here and the next line
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.
+1
Looks good to me! I left a handful of really minor comments. Thanks for all the extra tests! |
@etimberg @benmccann what do you think to rename |
not opposed. maybe |
I think it represents very well what the option is but if you have other suggestions we can still iterate since it's not released yet. I'm totally fine with |
`ticks.source` (`'auto'`(default)|`'labels'`): `auto` generates "optimal" ticks based on min, max and a few more options (current `time` implementation`). `labels` generates ticks from the user given `data.labels` values (two additional trailing and leading ticks can be added if min and max are provided). `ticks.mode` (`'linear'`(default)|`series`): `series` displays ticks at the same distance from each other, whatever the time value they represent, while `linear` displays them linearly in time: the distance between each tick represent the amount of time between their time values.
`ticks.source` (`'auto'`(default)|`'labels'`): `auto` generates "optimal" ticks based on min, max and a few more options (current `time` implementation`). `labels` generates ticks from the user given `data.labels` values (two additional trailing and leading ticks can be added if min and max are provided). `ticks.mode` (`'linear'`(default)|`series`): `series` displays ticks at the same distance from each other, whatever the time value they represent, while `linear` displays them linearly in time: the distance between each tick represent the amount of time between their time values.
Edit: added
ticks.source: 'data'
option value in #4568Edit: renamed
scale.ticks.mode
toscale.distribution
in #4582--
This PR is a proposal to replace #4364: it seems to provides exactly the same functionalities (and more) but doesn't introduce any new scale classes (
timebase
×eries
), no code/options duplication and has a lower size impact (~1.2KB vs ~3.4KB). It's also more optimized (especially when querying pixel for a specific value) and support scattered points.Introduced two new options:
ticks.source
('auto'
|'labels'
|'data'
):auto
generates "optimal" ticks based scale size and time options (currenttime
implementation).labels
generates ticks from the user givendata.labels
values.ticks.mode
distribution
('linear'
|'series'
):series
displays ticks at the same distance from each other, whatever the time value they represent, whilelinear
displays them linearly in time: the distance between each tick represent the amount of time between their time values.Fixes #4185
@etimberg @benmccann @IlyaBeliaev