Skip to content

Commit 89c68e2

Browse files
authored
Merge pull request #1078 from plotly/dates-as-dates
Dates as dates
2 parents ab193b2 + 1b0e133 commit 89c68e2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1803
-978
lines changed

src/components/annotations/annotation_defaults.js

+32-45
Original file line numberDiff line numberDiff line change
@@ -39,69 +39,56 @@ module.exports = function handleAnnotationDefaults(annIn, fullLayout) {
3939
var borderWidth = coerce('borderwidth');
4040
var showArrow = coerce('showarrow');
4141

42-
if(showArrow) {
43-
coerce('arrowcolor', borderOpacity ? annOut.bordercolor : Color.defaultLine);
44-
coerce('arrowhead');
45-
coerce('arrowsize');
46-
coerce('arrowwidth', ((borderOpacity && borderWidth) || 1) * 2);
47-
coerce('ax');
48-
coerce('ay');
49-
coerce('axref');
50-
coerce('ayref');
51-
52-
// if you have one part of arrow length you should have both
53-
Lib.noneOrAll(annIn, annOut, ['ax', 'ay']);
54-
}
55-
5642
coerce('text', showArrow ? ' ' : 'new text');
5743
coerce('textangle');
5844
Lib.coerceFont(coerce, 'font', fullLayout.font);
5945

6046
// positioning
61-
var axLetters = ['x', 'y'];
47+
var axLetters = ['x', 'y'],
48+
arrowPosDflt = [-10, -30],
49+
gdMock = {_fullLayout: fullLayout};
6250
for(var i = 0; i < 2; i++) {
63-
var axLetter = axLetters[i],
64-
tdMock = {_fullLayout: fullLayout};
51+
var axLetter = axLetters[i];
6552

6653
// xref, yref
67-
var axRef = Axes.coerceRef(annIn, annOut, tdMock, axLetter);
68-
69-
// TODO: should be refactored in conjunction with Axes axref, ayref
70-
var aaxRef = Axes.coerceARef(annIn, annOut, tdMock, axLetter);
54+
var axRef = Axes.coerceRef(annIn, annOut, gdMock, axLetter, '', 'paper');
7155

7256
// x, y
73-
var defaultPosition = 0.5;
74-
if(axRef !== 'paper') {
75-
var ax = Axes.getFromId(tdMock, axRef);
76-
defaultPosition = ax.range[0] + defaultPosition * (ax.range[1] - ax.range[0]);
77-
78-
// convert date or category strings to numbers
79-
if(['date', 'category'].indexOf(ax.type) !== -1 &&
80-
typeof annIn[axLetter] === 'string') {
81-
var newval;
82-
if(ax.type === 'date') {
83-
newval = Lib.dateTime2ms(annIn[axLetter]);
84-
if(newval !== false) annIn[axLetter] = newval;
85-
86-
if(aaxRef === axRef) {
87-
var newvalB = Lib.dateTime2ms(annIn['a' + axLetter]);
88-
if(newvalB !== false) annIn['a' + axLetter] = newvalB;
89-
}
90-
}
91-
else if((ax._categories || []).length) {
92-
newval = ax._categories.indexOf(annIn[axLetter]);
93-
if(newval !== -1) annIn[axLetter] = newval;
94-
}
57+
Axes.coercePosition(annOut, gdMock, coerce, axRef, axLetter, 0.5);
58+
59+
if(showArrow) {
60+
var arrowPosAttr = 'a' + axLetter,
61+
// axref, ayref
62+
aaxRef = Axes.coerceRef(annIn, annOut, gdMock, arrowPosAttr, 'pixel');
63+
64+
// for now the arrow can only be on the same axis or specified as pixels
65+
// TODO: sometime it might be interesting to allow it to be on *any* axis
66+
// but that would require updates to drawing & autorange code and maybe more
67+
if(aaxRef !== 'pixel' && aaxRef !== axRef) {
68+
aaxRef = annOut[arrowPosAttr] = 'pixel';
9569
}
70+
71+
// ax, ay
72+
var aDflt = (aaxRef === 'pixel') ? arrowPosDflt[i] : 0.4;
73+
Axes.coercePosition(annOut, gdMock, coerce, aaxRef, arrowPosAttr, aDflt);
9674
}
97-
coerce(axLetter, defaultPosition);
9875

9976
// xanchor, yanchor
100-
if(!showArrow) coerce(axLetter + 'anchor');
77+
else coerce(axLetter + 'anchor');
10178
}
10279

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

83+
if(showArrow) {
84+
coerce('arrowcolor', borderOpacity ? annOut.bordercolor : Color.defaultLine);
85+
coerce('arrowhead');
86+
coerce('arrowsize');
87+
coerce('arrowwidth', ((borderOpacity && borderWidth) || 1) * 2);
88+
89+
// if you have one part of arrow length you should have both
90+
Lib.noneOrAll(annIn, annOut, ['ax', 'ay']);
91+
}
92+
10693
return annOut;
10794
};

src/components/annotations/attributes.js

+24-10
Original file line numberDiff line numberDiff line change
@@ -141,27 +141,27 @@ module.exports = {
141141
description: 'Sets the width (in px) of annotation arrow.'
142142
},
143143
ax: {
144-
valType: 'number',
145-
dflt: -10,
144+
valType: 'any',
146145
role: 'info',
147146
description: [
148147
'Sets the x component of the arrow tail about the arrow head.',
149148
'If `axref` is `pixel`, a positive (negative) ',
150149
'component corresponds to an arrow pointing',
151150
'from right to left (left to right).',
152-
'If `axref` is an axis, this is a value on that axis.'
151+
'If `axref` is an axis, this is an absolute value on that axis,',
152+
'like `x`, NOT a relative value.'
153153
].join(' ')
154154
},
155155
ay: {
156-
valType: 'number',
157-
dflt: -30,
156+
valType: 'any',
158157
role: 'info',
159158
description: [
160159
'Sets the y component of the arrow tail about the arrow head.',
161160
'If `ayref` is `pixel`, a positive (negative) ',
162161
'component corresponds to an arrow pointing',
163162
'from bottom to top (top to bottom).',
164-
'If `ayref` is an axis, this is a value on that axis.'
163+
'If `ayref` is an axis, this is an absolute value on that axis,',
164+
'like `y`, NOT a relative value.'
165165
].join(' ')
166166
},
167167
axref: {
@@ -216,11 +216,18 @@ module.exports = {
216216
].join(' ')
217217
},
218218
x: {
219-
valType: 'number',
219+
valType: 'any',
220220
role: 'info',
221221
description: [
222222
'Sets the annotation\'s x position.',
223-
'Note that dates and categories are converted to numbers.'
223+
'If the axis `type` is *log*, then you must take the',
224+
'log of your desired range.',
225+
'If the axis `type` is *date*, it should be date strings,',
226+
'like date data, though Date objects and unix milliseconds',
227+
'will be accepted and converted to strings.',
228+
'If the axis `type` is *category*, it should be numbers,',
229+
'using the scale where each category is assigned a serial',
230+
'number from zero in the order it appears.'
224231
].join(' ')
225232
},
226233
xanchor: {
@@ -259,11 +266,18 @@ module.exports = {
259266
].join(' ')
260267
},
261268
y: {
262-
valType: 'number',
269+
valType: 'any',
263270
role: 'info',
264271
description: [
265272
'Sets the annotation\'s y position.',
266-
'Note that dates and categories are converted to numbers.'
273+
'If the axis `type` is *log*, then you must take the',
274+
'log of your desired range.',
275+
'If the axis `type` is *date*, it should be date strings,',
276+
'like date data, though Date objects and unix milliseconds',
277+
'will be accepted and converted to strings.',
278+
'If the axis `type` is *category*, it should be numbers,',
279+
'using the scale where each category is assigned a serial',
280+
'number from zero in the order it appears.'
267281
].join(' ')
268282
},
269283
yanchor: {

src/components/annotations/calc_autorange.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,14 +69,14 @@ function annAutorange(gd) {
6969
}
7070

7171
if(xa && xa.autorange) {
72-
Axes.expand(xa, [xa.l2c(ann.x)], {
72+
Axes.expand(xa, [xa.l2c(xa.r2l(ann.x))], {
7373
ppadplus: rightSize,
7474
ppadminus: leftSize
7575
});
7676
}
7777

7878
if(ya && ya.autorange) {
79-
Axes.expand(ya, [ya.l2c(ann.y)], {
79+
Axes.expand(ya, [ya.l2c(ya.r2l(ann.y))], {
8080
ppadplus: bottomSize,
8181
ppadminus: topSize
8282
});

src/components/annotations/draw.js

+28-28
Original file line numberDiff line numberDiff line change
@@ -172,12 +172,19 @@ function drawOne(gd, index, opt, value) {
172172
continue;
173173
}
174174

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

180180
if(optionsEdit[axLetter + 'ref'] !== undefined) {
181+
182+
// TODO: include ax / ay / axref / ayref here if not 'pixel'
183+
// or even better, move all of this machinery out of here and into
184+
// streambed as extra attributes to a regular relayout call
185+
// we should do this after v2.0 when it can work equivalently for
186+
// annotations, shapes, and images.
187+
181188
var autoAnchor = optionsIn[axLetter + 'anchor'] === 'auto',
182189
plotSize = (axLetter === 'x' ? gs.w : gs.h),
183190
halfSizeFrac = (oldPrivate['_' + axLetter + 'size'] || 0) /
@@ -186,18 +193,11 @@ function drawOne(gd, index, opt, value) {
186193
// go to the same fraction of the axis length
187194
// whether or not these axes share a domain
188195

189-
// first convert to fraction of the axis
190-
position = (position - axOld.range[0]) /
191-
(axOld.range[1] - axOld.range[0]);
192-
193-
// then convert to new data coordinates at the same fraction
194-
position = axNew.range[0] +
195-
position * (axNew.range[1] - axNew.range[0]);
196+
position = axNew.fraction2r(axOld.r2fraction(position));
196197
}
197198
else if(axOld) { // data -> paper
198199
// first convert to fraction of the axis
199-
position = (position - axOld.range[0]) /
200-
(axOld.range[1] - axOld.range[0]);
200+
position = axOld.r2fraction(position);
201201

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

227227
// finally convert to data coordinates
228-
position = axNew.range[0] +
229-
position * (axNew.range[1] - axNew.range[0]);
228+
position = axNew.fraction2r(position);
230229
}
231230
}
232231

@@ -357,20 +356,21 @@ function drawOne(gd, index, opt, value) {
357356
// outside the visible plot (as long as the axis
358357
// isn't autoranged - then we need to draw it
359358
// anyway to get its bounding box)
360-
if(!ax.autorange && ((options[axLetter] - ax.range[0]) *
361-
(options[axLetter] - ax.range[1]) > 0)) {
359+
var posFraction = ax.r2fraction(options[axLetter]);
360+
if(!ax.autorange && (posFraction < 0 || posFraction > 1)) {
362361
if(options['a' + axLetter + 'ref'] === axRef) {
363-
if((options['a' + axLetter] - ax.range[0]) *
364-
(options['a' + axLetter] - ax.range[1]) > 0) {
362+
posFraction = ax.r2fraction(options['a' + axLetter]);
363+
if(posFraction < 0 || posFraction > 1) {
365364
annotationIsOffscreen = true;
366365
}
367-
} else {
366+
}
367+
else {
368368
annotationIsOffscreen = true;
369369
}
370370

371371
if(annotationIsOffscreen) return;
372372
}
373-
annPosPx[axLetter] = ax._offset + ax.l2p(options[axLetter]);
373+
annPosPx[axLetter] = ax._offset + ax.r2p(options[axLetter]);
374374
alignPosition = 0.5;
375375
}
376376
else {
@@ -383,7 +383,7 @@ function drawOne(gd, index, opt, value) {
383383

384384
var alignShift = 0;
385385
if(options['a' + axLetter + 'ref'] === axRef) {
386-
annPosPx['aa' + axLetter] = ax._offset + ax.l2p(options['a' + axLetter]);
386+
annPosPx['aa' + axLetter] = ax._offset + ax.r2p(options['a' + axLetter]);
387387
} else {
388388
if(options.showarrow) {
389389
alignShift = options['a' + axLetter];
@@ -583,22 +583,22 @@ function drawOne(gd, index, opt, value) {
583583
ann.call(Lib.setTranslate, xcenter, ycenter);
584584

585585
update[annbase + '.x'] = xa ?
586-
(options.x + dx / xa._m) :
586+
xa.p2r(xa.r2p(options.x) + dx) :
587587
((arrowX + dx - gs.l) / gs.w);
588588
update[annbase + '.y'] = ya ?
589-
(options.y + dy / ya._m) :
589+
ya.p2r(ya.r2p(options.y) + dy) :
590590
(1 - ((arrowY + dy - gs.t) / gs.h));
591591

592592
if(options.axref === options.xref) {
593593
update[annbase + '.ax'] = xa ?
594-
(options.ax + dx / xa._m) :
595-
((arrowX + dx - gs.l) / gs.w);
594+
xa.p2r(xa.r2p(options.ax) + dx) :
595+
((arrowX + dx - gs.l) / gs.w);
596596
}
597597

598598
if(options.ayref === options.yref) {
599599
update[annbase + '.ay'] = ya ?
600-
(options.ay + dy / ya._m) :
601-
(1 - ((arrowY + dy - gs.t) / gs.h));
600+
ya.p2r(ya.r2p(options.ay) + dy) :
601+
(1 - ((arrowY + dy - gs.t) / gs.h));
602602
}
603603

604604
anng.attr({
@@ -644,13 +644,13 @@ function drawOne(gd, index, opt, value) {
644644
var csr = 'pointer';
645645
if(options.showarrow) {
646646
if(options.axref === options.xref) {
647-
update[annbase + '.ax'] = xa.p2l(xa.l2p(options.ax) + dx);
647+
update[annbase + '.ax'] = xa.p2r(xa.r2p(options.ax) + dx);
648648
} else {
649649
update[annbase + '.ax'] = options.ax + dx;
650650
}
651651

652652
if(options.ayref === options.yref) {
653-
update[annbase + '.ay'] = ya.p2l(ya.l2p(options.ay) + dy);
653+
update[annbase + '.ay'] = ya.p2r(ya.r2p(options.ay) + dy);
654654
} else {
655655
update[annbase + '.ay'] = options.ay + dy;
656656
}

src/components/drawing/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,14 @@ drawing.getPx = function(s, styleAttr) {
7575
return Number(s.style(styleAttr).replace(/px$/, ''));
7676
};
7777

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

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

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

8787
if(lineWidth < 1) return 1;
8888
return Math.round(lineWidth);

src/components/images/attributes.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ module.exports = {
9090
},
9191

9292
x: {
93-
valType: 'number',
93+
valType: 'any',
9494
role: 'info',
9595
dflt: 0,
9696
description: [
@@ -102,7 +102,7 @@ module.exports = {
102102
},
103103

104104
y: {
105-
valType: 'number',
105+
valType: 'any',
106106
role: 'info',
107107
dflt: 0,
108108
description: [

0 commit comments

Comments
 (0)