-
-
Notifications
You must be signed in to change notification settings - Fork 1.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
Dates as dates #1078
Dates as dates #1078
Conversation
and introduce new 'r' conversion mode for ranges: can be the same as 'd' (for linear and date), 'l' (for log, for now!) or 'c' (for category, this will have to stay different from 'd' forever so it's continuous)
for axis ranges, annotation & image positions... everything that's an absolute value, not a delta (so dtick is not changed, for example) Apologies in advance to reviewers, I couldn't see a way to break this up.
(for me alexcjohnson anyway)
*/ | ||
module.exports = function cleanNumber(v) { | ||
if(typeof v === 'string') { | ||
v = v.replace(FRONTJUNK, '').replace(ENDJUNK, ''); |
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.
That gets my vote for variable name of the year 🏆
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.
Very impressive PRs.
💯 bonus points for cleaning up the gl2d logic.
No 🔴 flags caught my 👀 on the first pass.
But, as the PR impacts a lot of internal and external (hello streambed) plotly components, we should before merging:
-
npm link
this branch in streambed and run the toolpanel and workspace tests there @alexcjohnson I can show how to do this on slack. - make sure animations behave as before @rreusser would you mind fetching this branch and trying out of few mocks?
- test drive gl3d / gl2d - I'll take care of that.
Moreover,
- we should document what breaking changes to make in v2 in wishlist for potential breaking changes since v1 #420
'use strict'; | ||
|
||
|
||
module.exports = { |
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.
Oh you added a src/lib/constants.js
file, there's also a src/constants/
directory in case you haven't noticed.
I'm exactly not sure where to draw the line between the two. Maybe putting this in src/constants/numerical.js
would be best. But this is non- ⛔ ; I'll let you decide what's best.
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.
Ah OK - no I hadn't noticed that dir, I'll move it in there. I made the file to get BADNUM
out of src/plots/cartesian/constants.js
, perhaps eventually this should move there too.
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.
try { | ||
if(s.getTime) return +s; | ||
} | ||
catch(e) { |
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.
very nice here. try-catch
blocks are sloooooooow.
@@ -528,6 +599,8 @@ axes.autoBin = function(data, ax, nbins, is2d) { | |||
axes.calcTicks = function calcTicks(ax) { | |||
if(ax.tickmode === 'array') return arrayTicks(ax); | |||
|
|||
var rng = ax.range.map(ax.r2l); |
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.
clever 👍
// last resort axis ranges for x, y, and date axes if we have no data | ||
DFLTRANGEX: [-1, 6], | ||
DFLTRANGEY: [-1, 4], | ||
DFLTRANGEDATE: ['2000-01-01', '2001-01-01'], |
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.
🎉
var axisIds = require('./axis_ids'); | ||
|
||
|
||
/** | ||
* Define the conversion functions for an axis data is used in 4 ways: | ||
* Define the conversion functions for an axis data is used in 5 ways: |
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 👁️
@@ -35,7 +35,6 @@ var datesModule = require('./dates'); | |||
lib.dateTime2ms = datesModule.dateTime2ms; | |||
lib.isDateTime = datesModule.isDateTime; | |||
lib.ms2DateTime = datesModule.ms2DateTime; | |||
lib.parseDate = datesModule.parseDate; |
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.
🎉
var axLetters = ['x', 'y']; | ||
var axLetters = ['x', 'y'], | ||
arrowPosDflt = [-10, -30], | ||
tdMock = {_fullLayout: fullLayout}; |
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.
Those td
scare the 💩 out of me. Would you mind renaming this gdMock
?
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.
Halloween is almost upon us, so maybe I'll scare you even more by swapping td
for gd
EVERYWHERE that remnant of streambed was lingering. EXORCISE THE DEMON! 5ccd083
|
||
var annotationIn = { | ||
annotations: [{ showarrow: true, axref: 'x', ayref: 'y', x: '2008-07-01', ax: '2004-07-01', y: 0, ay: 50}] | ||
it('should convert ax/ay date coordinates to date string if tail is in milliseconds and axis is a date', function() { |
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.
nice!
in0 = shapeIn[attr0], | ||
in1 = shapeIn[attr1]; | ||
shapeIn[attr0] = pos2r(shapeIn[attr0], true); | ||
shapeIn[attr1] = pos2r(shapeIn[attr1], true); |
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.
Would you mind making sure this logic is tested in shapes_test.js
?
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 call - it was actually broken 🙈
e63ea3b
@@ -449,7 +449,7 @@ describe('mapbox plots', function() { | |||
|
|||
var divStyle = mapInfo.div.style; | |||
['left', 'top', 'width', 'height'].forEach(function(p, i) { | |||
expect(parseFloat(divStyle[p])).toBeWithin(dims[i], 5); | |||
expect(parseFloat(divStyle[p])).toBeWithin(dims[i], 8); |
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.
Oh nice. Does this make the full npm run citest-jamine
run without errors for you?
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.
npm-run test-jasmine
runs without errors after this change (though that skips a bunch of the tests), but citest-jasmine
still seems to fail.
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.
other than the occasional
Uncaught TypeError: gd.emit is not a function
at /var/folders/z4/src/plots/mapbox/mapbox.js:147:0
that still causes test-jasmine
to fail intermittently
@alexcjohnson and I should add: I'm thinking about not including this PR in I would prefer making this PR a stepping-stone for non-western set for |
Fine by me. 20 can be the big date overhaul. |
except for image server compatibility, we leave td in plotTile
@etpinard I think I've addressed your comments. I'll ping you tomorrow about |
(after I fix the jasmine test I broke that is) |
FYI: your tests will probably continue to fail due to a backwards-incompatible eslint upgrade that seems to have just taken effect on circle-ci. @etpinard is fixing now. Solution will be an eslint upgrade. |
merging |
'gives ticks linearly spaced in value (but not position).', | ||
'For example `tick0` = 0.1, `dtick` = *L0.5* will put ticks at 0.1, 0.6, 1.1, 1.6 etc.', | ||
'To show powers of 10 plus small digits between, use *D1* (all digits) or *D2* (only 2 and 5).', | ||
'`tick0` is ignored for *D1* and *D2*.', |
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.
Nice. Is this a new or a current (but hidden) feature?
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.
These have always existed behind the scenes but I think there were some bugs if you tried to use them yourself as opposed to getting them automatically. Now they work manually, and are tested at the level of supplyDefaults (which is where the bugs were before, I think) though at some point it might be worth adding tests of the functionality when used manually.
@@ -22,22 +22,53 @@ module.exports = function handleTickValueDefaults(containerIn, containerOut, coe | |||
} | |||
|
|||
if(Array.isArray(containerIn.tickvals)) tickmodeDefault = 'array'; | |||
else if(containerIn.dtick && isNumeric(containerIn.dtick)) { |
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.
ha. I guess a new feature. 😌
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.
... and probably one of the most useful new features of the passed few months 🏆
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.
@alexcjohnson this is looking great. I'm particularly excited about configurable tick0
+ dtick
for log and date axes.
// dtick is usually a positive number, but there are some | ||
// special strings available for log or date axes | ||
// default is 1 day for dates, otherwise 1 | ||
var dtickDflt = (axType === 'date') ? 86400000 : 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.
that 1 day in ms 8640000
would fit well in the new src/constants/numerical
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.
blocking change
// "L<f>" gives ticks linearly spaced in data (not in position) every (float) f | ||
(axType === 'log' && prefix === 'L') || | ||
// "D1" gives powers of 10 with all small digits between, "D2" gives only 2 and 5 | ||
(axType === 'log' && prefix === 'D' && (dtickNum === 1 || dtickNum === 2)) |
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.
Nice. This is going to make a lot of users happy. You wouldn't believe how many people have ask for custom date tick labels. This is way better then tickvals
+ ticktext
!
@@ -22,22 +22,53 @@ module.exports = function handleTickValueDefaults(containerIn, containerOut, coe | |||
} | |||
|
|||
if(Array.isArray(containerIn.tickvals)) tickmodeDefault = 'array'; | |||
else if(containerIn.dtick && isNumeric(containerIn.dtick)) { |
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.
... and probably one of the most useful new features of the passed few months 🏆
var ONEDAY = 86400000, | ||
ONEHOUR = 3600000, | ||
ONEMIN = 60000, | ||
ONESEC = 1000; |
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 put these src/constants/numerical
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.
blocking change (bis)
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.
@@ -961,7 +985,7 @@ axes.tickFirst = function(ax) { | |||
var yearFormat = d3.time.format('%Y'), | |||
monthFormat = d3.time.format('%b %Y'), | |||
dayFormat = d3.time.format('%b %-d'), | |||
hourFormat = d3.time.format('%b %-d %Hh'), | |||
yearMonthDayFormat = d3.time.format('%b %-d, %Y'), |
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 can tell someone is about to add layout.xaxis.tickdateformat
...
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.
axis.tickformat
does this already... but I see the docs do not reflect this. I'll update the description accordingly. We use d3.time.format
with an addition for fractional seconds.
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.
@@ -1392,4 +1392,88 @@ describe('Test axes', function() { | |||
expect(ax._max).toEqual([{val: 6, pad: 15}]); | |||
}); | |||
}); | |||
|
|||
describe('calcTicks', function() { |
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.
Amazing tests. So clear.
@@ -4,17 +4,22 @@ | |||
"x": [1,2,3], | |||
"y": [1,2,3] | |||
}, { | |||
"x": [1,2,3], | |||
"y": [1,2,3], | |||
"x": ["2001-01-01","2002-01-01","2003-01-01"], |
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 see you're catching on to the reusing existing mocks philosophy. Thanks! Our CI test time will thank you too
@@ -579,21 +579,21 @@ function drawOne(gd, index, opt, value) { | |||
ann.call(Lib.setTranslate, xcenter, ycenter); | |||
|
|||
update[annbase + '.x'] = xa ? | |||
(options.x + dx / xa._m) : | |||
xa.l2r(xa.p2l(xa.l2p(xa.r2l(options.x)) + dx)) : |
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.
wild
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 factor that out into something like:
function addPxDistToRange(ax, v, dv) {
return ax.l2r(ax.p2l(ax.l2p(ax.r2l(v)) + dv))
}
or a better name.
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.
blocking change
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 I'll just make ax.r2p
and ax.p2r
, which I just omitted because I didn't have a use case for it for a while, but now there are a couple of places that use it.
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 I'll just make ax.r2p and ax.p2r
Sounds good!
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.
((arrowX + dx - gs.l) / gs.w); | ||
update[annbase + '.y'] = ya ? | ||
(options.y + dy / ya._m) : | ||
ya.l2r(ya.p2l(ya.l2p(ya.r2l(options.y)) + dy)) : |
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.
Have you tested shapes under { editable: true }
? @n-riesco did add some jasmine test cases for them, but you can never be too sure.
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.
Tested. Editable shapes on date axes work just fine ✅
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.
Oh interesting... didn't even know they were editable but yes, seems to work 👍
|
||
expect(fullLayout.xaxis.range).toBeCloseToArray(x, PREC, '- xaxis'); | ||
expect(fullLayout.yaxis.range).toBeCloseToArray(y, PREC, '- yaxis'); | ||
expect(fullLayout.xaxis2.range).toBeCloseToArray(x2, PREC2, 'xaxis2'); | ||
expect(fullLayout.yaxis2.range).toBeCloseToArray(y2, PREC, 'yaxis2'); | ||
expect(dateAx.range.map(dateAx.r2l)).toBeCloseToArray(x2.map(dateAx.r2l), PRECX2, 'xaxis2 ' + dateAx.range); |
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 🎉
@alexcjohnson I just submitted my review. I'll spend the next 30 minutes test 🚗 this PR. |
return Lib.coerce(containerIn, containerOut, attrDef, refAttr); | ||
if(ax.type === 'category') { | ||
// if position is given as a category name, convert it to a number | ||
if(typeof pos === 'string' && (ax._categories || []).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.
maybe add a note that ax._categories
is filled during the calc
step only (by ax.makeCaldata
to be more precise), so ax_categories
is in fact []
during Plotly.supplyDefaults
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.
So, the block below only makes sense for Axes.coercePosition
calls in the annotations and shapes draw
methods.
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.
💃 |
Does this PR by chance recognize I know that as of v1.19.0 it plotly.js recognizes |
@spencerlyon2
No, that's not in this PR, but it's on my list to add soon #1003 |
Heads up @cldougl ! When this merges we'll be able to remove all milliseconds from our documentation examples 🎉 |
Stop specifying dates as epoch milliseconds wherever possible. For backward compatibility we will still accept milliseconds where we did previously, but in
_fullLayout
these are all now turned into date strings:Introduces yet another letter to our alphabet soup of data conversions for axes:
r
for range. The goal is to getr
as much as possible to matchd
, the formats we use for incoming data, but it's not there yet (nor will it ever be):r===d
r===l
ie you still need to specify log axes (and the other quantities mentioned above) in linearized terms, ie the log10 of the data. In plotly.js V2.0 we plan to change that tor===d
as well but for now there is no clear backward-compatible way to do it.r===c
ie the serial numbers of the categories, and this will always be the case because we need a continuous variable.Note that shapes already behave (and always have) the way we intend for
r
to behave in V2.0 - that is, all positioning info is in data format except on category axes. So in V2.0 we can clean them up a bit to explicitly use ther
machinery. I made some changes in there to prepare for this, so that log looks like the odd-man-out, rather than category.Also contains some random cleanup to our date systems:
-9999-01-01 00:00:00.0000
to9999-12-31 23:59:59.9999
- note that this uses the ISO-8601 convention in which there is a year zero, in contrast to the BC/AD numbering system which has no year zero. Years should always be specified with 4 digits, though we accept 2-digit years with a sliding range now-70 to now+29 years.2016-20-24 19
) although we will still accept this as data. (fixes Invalid Date format in Hover data when full hour (0 mins) #911 )This refactor is also a prerequisite to using UTC internally (which will fix several other issues in the better dates milestone around time zones and daylight saving time) but note that for backward compatibility, when we do that we will still use local time if users specify dates as milliseconds - which we've always done even though it means these plots are not portable across time zones! But at least after we do this when users do specify dates as strings the plots will be portable. I'm planning to do this next.
@etpinard my apologies, most of this is in one big commit that I couldn't figure out how to break up...