Skip to content
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

Merged
merged 29 commits into from
Nov 10, 2016
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d4bb89d
improve dateTime2ms edge cases and standardize BADNUM and FP_SAFE con…
alexcjohnson Oct 14, 2016
9e27955
switch date axes to use date strings for range and tick0
alexcjohnson Oct 14, 2016
00138aa
Merge branch 'master' into dates-as-dates
alexcjohnson Oct 14, 2016
9784b3b
axe parseDate - moved to streambed
alexcjohnson Oct 15, 2016
1db4448
refactor dates handling to use date strings as much as possible
alexcjohnson Oct 24, 2016
5ff9722
Merge branch 'master' into dates-as-dates
alexcjohnson Oct 24, 2016
c045bc5
wider acceptance range for mapbox plot size test so it works locally
alexcjohnson Oct 25, 2016
3dd294f
fix for range slider tests with new axis range machinery
alexcjohnson Oct 25, 2016
451ee24
fix date axis ranges and date interactions in gl2d
alexcjohnson Oct 25, 2016
9ccde7e
Merge branch 'master' into dates-as-dates
alexcjohnson Oct 26, 2016
51a0563
lib/constants -> constants/numerical
alexcjohnson Oct 26, 2016
5ccd083
td -> gd once and for all
alexcjohnson Oct 26, 2016
e63ea3b
test and fix shape default positioning
alexcjohnson Oct 26, 2016
2e9dbad
fix date and log axis manual tick0/dtick handling
alexcjohnson Oct 28, 2016
37d52fe
fix handling of default tick0 with dtick for date axes
alexcjohnson Oct 28, 2016
55b313c
get rid of '2012-01-22 12h' tick format and smarter date suffix handling
alexcjohnson Oct 29, 2016
b7121c3
fix #1058 - make sure no date ever returns sub-100microsec precision
alexcjohnson Oct 29, 2016
03b4a0c
Merge branch 'master' into dates-as-dates
alexcjohnson Oct 29, 2016
dd0940a
fix _forceTick0 error
alexcjohnson Oct 29, 2016
f629f71
update baseline with new date tick format
alexcjohnson Oct 29, 2016
a29ce33
fix images on date axes, and standardize axis object names
alexcjohnson Nov 9, 2016
065ac8a
fix annotation drag on date axes, and test date/log annotations
alexcjohnson Nov 9, 2016
4a0a866
Merge branch 'master' into dates-as-dates
alexcjohnson Nov 9, 2016
7679483
update jasmine test of annotation visibility & autorange
alexcjohnson Nov 9, 2016
0d61fcc
Merge branch 'master' into dates-as-dates
alexcjohnson Nov 9, 2016
5e11571
merge lint
alexcjohnson Nov 9, 2016
d807259
add ax.r2p and ax.p2r
alexcjohnson Nov 10, 2016
05c762d
date interval milliseconds constants
alexcjohnson Nov 10, 2016
1b0e133
documentation updates
alexcjohnson Nov 10, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 32 additions & 45 deletions src/components/annotations/annotation_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,69 +39,56 @@ module.exports = function handleAnnotationDefaults(annIn, fullLayout) {
var borderWidth = coerce('borderwidth');
var showArrow = coerce('showarrow');

if(showArrow) {
coerce('arrowcolor', borderOpacity ? annOut.bordercolor : Color.defaultLine);
coerce('arrowhead');
coerce('arrowsize');
coerce('arrowwidth', ((borderOpacity && borderWidth) || 1) * 2);
coerce('ax');
coerce('ay');
coerce('axref');
coerce('ayref');

// if you have one part of arrow length you should have both
Lib.noneOrAll(annIn, annOut, ['ax', 'ay']);
}

coerce('text', showArrow ? ' ' : 'new text');
coerce('textangle');
Lib.coerceFont(coerce, 'font', fullLayout.font);

// positioning
var axLetters = ['x', 'y'];
var axLetters = ['x', 'y'],
arrowPosDflt = [-10, -30],
gdMock = {_fullLayout: fullLayout};
for(var i = 0; i < 2; i++) {
var axLetter = axLetters[i],
tdMock = {_fullLayout: fullLayout};
var axLetter = axLetters[i];

// xref, yref
var axRef = Axes.coerceRef(annIn, annOut, tdMock, axLetter);

// TODO: should be refactored in conjunction with Axes axref, ayref
var aaxRef = Axes.coerceARef(annIn, annOut, tdMock, axLetter);
var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', 'paper');

// x, y
var defaultPosition = 0.5;
if(axRef !== 'paper') {
var ax = Axes.getFromId(tdMock, axRef);
defaultPosition = ax.range[0] + defaultPosition * (ax.range[1] - ax.range[0]);

// convert date or category strings to numbers
if(['date', 'category'].indexOf(ax.type) !== -1 &&
typeof annIn[axLetter] === 'string') {
var newval;
if(ax.type === 'date') {
newval = Lib.dateTime2ms(annIn[axLetter]);
if(newval !== false) annIn[axLetter] = newval;

if(aaxRef === axRef) {
var newvalB = Lib.dateTime2ms(annIn['a' + axLetter]);
if(newvalB !== false) annIn['a' + axLetter] = newvalB;
}
}
else if((ax._categories || []).length) {
newval = ax._categories.indexOf(annIn[axLetter]);
if(newval !== -1) annIn[axLetter] = newval;
}
Axes.coercePosition(annOut, gdMock, coerce, axRef, axLetter, 0.5);

if(showArrow) {
var arrowPosAttr = 'a' + axLetter,
// axref, ayref
aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, 'pixel');

// for now the arrow can only be on the same axis or specified as pixels
// TODO: sometime it might be interesting to allow it to be on *any* axis
// but that would require updates to drawing & autorange code and maybe more
if(aaxRef !== 'pixel' && aaxRef !== axRef) {
aaxRef = annOut[arrowPosAttr] = 'pixel';
}

// ax, ay
var aDflt = (aaxRef === 'pixel') ? arrowPosDflt[i] : 0.4;
Axes.coercePosition(annOut, gdMock, coerce, aaxRef, arrowPosAttr, aDflt);
}
coerce(axLetter, defaultPosition);

// xanchor, yanchor
if(!showArrow) coerce(axLetter + 'anchor');
else coerce(axLetter + 'anchor');
}

// if you have one coordinate you should have both
Lib.noneOrAll(annIn, annOut, ['x', 'y']);

if(showArrow) {
coerce('arrowcolor', borderOpacity ? annOut.bordercolor : Color.defaultLine);
coerce('arrowhead');
coerce('arrowsize');
coerce('arrowwidth', ((borderOpacity && borderWidth) || 1) * 2);

// if you have one part of arrow length you should have both
Lib.noneOrAll(annIn, annOut, ['ax', 'ay']);
}

return annOut;
};
34 changes: 24 additions & 10 deletions src/components/annotations/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,27 +141,27 @@ module.exports = {
description: 'Sets the width (in px) of annotation arrow.'
},
ax: {
valType: 'number',
dflt: -10,
valType: 'any',
role: 'info',
description: [
'Sets the x component of the arrow tail about the arrow head.',
'If `axref` is `pixel`, a positive (negative) ',
'component corresponds to an arrow pointing',
'from right to left (left to right).',
'If `axref` is an axis, this is a value on that axis.'
'If `axref` is an axis, this is an absolute value on that axis,',
'like `x`, NOT a relative value.'
].join(' ')
},
ay: {
valType: 'number',
dflt: -30,
valType: 'any',
role: 'info',
description: [
'Sets the y component of the arrow tail about the arrow head.',
'If `ayref` is `pixel`, a positive (negative) ',
'component corresponds to an arrow pointing',
'from bottom to top (top to bottom).',
'If `ayref` is an axis, this is a value on that axis.'
'If `ayref` is an axis, this is an absolute value on that axis,',
'like `y`, NOT a relative value.'
].join(' ')
},
axref: {
Expand Down Expand Up @@ -216,11 +216,18 @@ module.exports = {
].join(' ')
},
x: {
valType: 'number',
valType: 'any',
role: 'info',
description: [
'Sets the annotation\'s x position.',
'Note that dates and categories are converted to numbers.'
'If the axis `type` is *log*, then you must take the',
'log of your desired range.',
'If the axis `type` is *date*, it should be date strings,',
'like date data, though Date objects and unix milliseconds',
'will be accepted and converted to strings.',
'If the axis `type` is *category*, it should be numbers,',
'using the scale where each category is assigned a serial',
'number from zero in the order it appears.'
].join(' ')
},
xanchor: {
Expand Down Expand Up @@ -259,11 +266,18 @@ module.exports = {
].join(' ')
},
y: {
valType: 'number',
valType: 'any',
role: 'info',
description: [
'Sets the annotation\'s y position.',
'Note that dates and categories are converted to numbers.'
'If the axis `type` is *log*, then you must take the',
'log of your desired range.',
'If the axis `type` is *date*, it should be date strings,',
'like date data, though Date objects and unix milliseconds',
'will be accepted and converted to strings.',
'If the axis `type` is *category*, it should be numbers,',
'using the scale where each category is assigned a serial',
'number from zero in the order it appears.'
].join(' ')
},
yanchor: {
Expand Down
4 changes: 2 additions & 2 deletions src/components/annotations/calc_autorange.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,14 @@ function annAutorange(gd) {
}

if(xa && xa.autorange) {
Axes.expand(xa, [xa.l2c(ann.x)], {
Axes.expand(xa, [xa.l2c(xa.r2l(ann.x))], {
ppadplus: rightSize,
ppadminus: leftSize
});
}

if(ya && ya.autorange) {
Axes.expand(ya, [ya.l2c(ann.y)], {
Axes.expand(ya, [ya.l2c(ya.r2l(ann.y))], {
ppadplus: bottomSize,
ppadminus: topSize
});
Expand Down
52 changes: 26 additions & 26 deletions src/components/annotations/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,19 @@ function drawOne(gd, index, opt, value) {
continue;
}

var axOld = Axes.getFromId(gd, Axes.coerceRef(oldRef, {}, gd, axLetter)),
axNew = Axes.getFromId(gd, Axes.coerceRef(optionsIn, {}, gd, axLetter)),
var axOld = Axes.getFromId(gd, Axes.coerceRef(oldRef, {}, gd, axLetter, '', 'paper')),
axNew = Axes.getFromId(gd, Axes.coerceRef(optionsIn, {}, gd, axLetter, '', 'paper')),
position = optionsIn[axLetter],
axTypeOld = oldPrivate['_' + axLetter + 'type'];

if(optionsEdit[axLetter + 'ref'] !== undefined) {

// TODO: include ax / ay / axref / ayref here if not 'pixel'
// or even better, move all of this machinery out of here and into
// streambed as extra attributes to a regular relayout call
// we should do this after v2.0 when it can work equivalently for
// annotations, shapes, and images.

var autoAnchor = optionsIn[axLetter + 'anchor'] === 'auto',
plotSize = (axLetter === 'x' ? gs.w : gs.h),
halfSizeFrac = (oldPrivate['_' + axLetter + 'size'] || 0) /
Expand All @@ -186,18 +193,11 @@ function drawOne(gd, index, opt, value) {
// go to the same fraction of the axis length
// whether or not these axes share a domain

// first convert to fraction of the axis
position = (position - axOld.range[0]) /
(axOld.range[1] - axOld.range[0]);

// then convert to new data coordinates at the same fraction
position = axNew.range[0] +
position * (axNew.range[1] - axNew.range[0]);
position = axNew.fraction2r(axOld.r2fraction(position));
}
else if(axOld) { // data -> paper
// first convert to fraction of the axis
position = (position - axOld.range[0]) /
(axOld.range[1] - axOld.range[0]);
position = axOld.r2fraction(position);

// next scale the axis to the whole plot
position = axOld.domain[0] +
Expand Down Expand Up @@ -225,8 +225,7 @@ function drawOne(gd, index, opt, value) {
(axNew.domain[1] - axNew.domain[0]);

// finally convert to data coordinates
position = axNew.range[0] +
position * (axNew.range[1] - axNew.range[0]);
position = axNew.fraction2r(position);
}
}

Expand Down Expand Up @@ -357,20 +356,21 @@ function drawOne(gd, index, opt, value) {
// outside the visible plot (as long as the axis
// isn't autoranged - then we need to draw it
// anyway to get its bounding box)
if(!ax.autorange && ((options[axLetter] - ax.range[0]) *
(options[axLetter] - ax.range[1]) > 0)) {
var posFraction = ax.r2fraction(options[axLetter]);
if(!ax.autorange && (posFraction < 0 || posFraction > 1)) {
if(options['a' + axLetter + 'ref'] === axRef) {
if((options['a' + axLetter] - ax.range[0]) *
(options['a' + axLetter] - ax.range[1]) > 0) {
posFraction = ax.r2fraction(options['a' + axLetter]);
if(posFraction < 0 || posFraction > 1) {
annotationIsOffscreen = true;
}
} else {
}
else {
annotationIsOffscreen = true;
}

if(annotationIsOffscreen) return;
}
annPosPx[axLetter] = ax._offset + ax.l2p(options[axLetter]);
annPosPx[axLetter] = ax._offset + ax.l2p(ax.r2l(options[axLetter]));
alignPosition = 0.5;
}
else {
Expand All @@ -383,7 +383,7 @@ function drawOne(gd, index, opt, value) {

var alignShift = 0;
if(options['a' + axLetter + 'ref'] === axRef) {
annPosPx['aa' + axLetter] = ax._offset + ax.l2p(options['a' + axLetter]);
annPosPx['aa' + axLetter] = ax._offset + ax.l2p(ax.r2l(options['a' + axLetter]));
} else {
if(options.showarrow) {
alignShift = options['a' + axLetter];
Expand Down Expand Up @@ -583,21 +583,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)) :
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wild

Copy link
Contributor

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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blocking change

Copy link
Collaborator Author

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.

Copy link
Contributor

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!

Copy link
Collaborator Author

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)) :
Copy link
Contributor

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.

Copy link
Contributor

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 ✅

Copy link
Collaborator Author

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 👍

(1 - ((arrowY + dy - gs.t) / gs.h));

if(options.axref === options.xref) {
update[annbase + '.ax'] = xa ?
(options.ax + dx / xa._m) :
xa.l2r(xa.p2l(xa.l2p(xa.r2l(options.ax)) + dx)) :
((arrowX + dx - gs.l) / gs.w);
}

if(options.ayref === options.yref) {
update[annbase + '.ay'] = ya ?
(options.ay + dy / ya._m) :
ya.l2r(ya.p2l(ya.l2p(ya.r2l(options.ay)) + dy)) :
(1 - ((arrowY + dy - gs.t) / gs.h));
}

Expand Down Expand Up @@ -644,13 +644,13 @@ function drawOne(gd, index, opt, value) {
var csr = 'pointer';
if(options.showarrow) {
if(options.axref === options.xref) {
update[annbase + '.ax'] = xa.p2l(xa.l2p(options.ax) + dx);
update[annbase + '.ax'] = xa.l2r(xa.p2l(xa.l2p(xa.r2l(options.ax)) + dx));
} else {
update[annbase + '.ax'] = options.ax + dx;
}

if(options.ayref === options.yref) {
update[annbase + '.ay'] = ya.p2l(ya.l2p(options.ay) + dy);
update[annbase + '.ay'] = ya.l2r(ya.p2l(ya.l2p(ya.r2l(options.ay)) + dy));
} else {
update[annbase + '.ay'] = options.ay + dy;
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/drawing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,14 @@ drawing.getPx = function(s, styleAttr) {
return Number(s.style(styleAttr).replace(/px$/, ''));
};

drawing.crispRound = function(td, lineWidth, dflt) {
drawing.crispRound = function(gd, lineWidth, dflt) {
// for lines that disable antialiasing we want to
// make sure the width is an integer, and at least 1 if it's nonzero

if(!lineWidth || !isNumeric(lineWidth)) return dflt || 0;

// but not for static plots - these don't get antialiased anyway.
if(td._context.staticPlot) return lineWidth;
if(gd._context.staticPlot) return lineWidth;

if(lineWidth < 1) return 1;
return Math.round(lineWidth);
Expand Down
4 changes: 2 additions & 2 deletions src/components/images/attributes.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ module.exports = {
},

x: {
valType: 'number',
valType: 'any',
role: 'info',
dflt: 0,
description: [
Expand All @@ -102,7 +102,7 @@ module.exports = {
},

y: {
valType: 'number',
valType: 'any',
role: 'info',
dflt: 0,
description: [
Expand Down
Loading