Skip to content

Commit 1db4448

Browse files
committedOct 24, 2016
refactor dates handling to use date strings as much as possible
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.
1 parent 9784b3b commit 1db4448

30 files changed

+852
-399
lines changed
 

‎src/components/annotations/annotation_defaults.js

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

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

5642
// positioning
57-
var axLetters = ['x', 'y'];
43+
var axLetters = ['x', 'y'],
44+
arrowPosDflt = [-10, -30],
45+
tdMock = {_fullLayout: fullLayout};
5846
for(var i = 0; i < 2; i++) {
59-
var axLetter = axLetters[i],
60-
tdMock = {_fullLayout: fullLayout};
47+
var axLetter = axLetters[i];
6148

6249
// xref, yref
63-
var axRef = Axes.coerceRef(annIn, annOut, tdMock, axLetter);
64-
65-
// TODO: should be refactored in conjunction with Axes axref, ayref
66-
var aaxRef = Axes.coerceARef(annIn, annOut, tdMock, axLetter);
50+
var axRef = Axes.coerceRef(annIn, annOut, tdMock, axLetter, '', 'paper');
6751

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

9572
// xanchor, yanchor
96-
if(!showArrow) coerce(axLetter + 'anchor');
73+
else coerce(axLetter + 'anchor');
9774
}
9875

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

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

‎src/components/annotations/attributes.js

+24-10
Original file line numberDiff line numberDiff line change
@@ -132,27 +132,27 @@ module.exports = {
132132
description: 'Sets the width (in px) of annotation arrow.'
133133
},
134134
ax: {
135-
valType: 'number',
136-
dflt: -10,
135+
valType: 'any',
137136
role: 'info',
138137
description: [
139138
'Sets the x component of the arrow tail about the arrow head.',
140139
'If `axref` is `pixel`, a positive (negative) ',
141140
'component corresponds to an arrow pointing',
142141
'from right to left (left to right).',
143-
'If `axref` is an axis, this is a value on that axis.'
142+
'If `axref` is an axis, this is an absolute value on that axis,',
143+
'like `x`, NOT a relative value.'
144144
].join(' ')
145145
},
146146
ay: {
147-
valType: 'number',
148-
dflt: -30,
147+
valType: 'any',
149148
role: 'info',
150149
description: [
151150
'Sets the y component of the arrow tail about the arrow head.',
152151
'If `ayref` is `pixel`, a positive (negative) ',
153152
'component corresponds to an arrow pointing',
154153
'from bottom to top (top to bottom).',
155-
'If `ayref` is an axis, this is a value on that axis.'
154+
'If `ayref` is an axis, this is an absolute value on that axis,',
155+
'like `y`, NOT a relative value.'
156156
].join(' ')
157157
},
158158
axref: {
@@ -207,11 +207,18 @@ module.exports = {
207207
].join(' ')
208208
},
209209
x: {
210-
valType: 'number',
210+
valType: 'any',
211211
role: 'info',
212212
description: [
213213
'Sets the annotation\'s x position.',
214-
'Note that dates and categories are converted to numbers.'
214+
'If the axis `type` is *log*, then you must take the',
215+
'log of your desired range.',
216+
'If the axis `type` is *date*, it should be date strings,',
217+
'like date data, though Date objects and unix milliseconds',
218+
'will be accepted and converted to strings.',
219+
'If the axis `type` is *category*, it should be numbers,',
220+
'using the scale where each category is assigned a serial',
221+
'number from zero in the order it appears.'
215222
].join(' ')
216223
},
217224
xanchor: {
@@ -250,11 +257,18 @@ module.exports = {
250257
].join(' ')
251258
},
252259
y: {
253-
valType: 'number',
260+
valType: 'any',
254261
role: 'info',
255262
description: [
256263
'Sets the annotation\'s y position.',
257-
'Note that dates and categories are converted to numbers.'
264+
'If the axis `type` is *log*, then you must take the',
265+
'log of your desired range.',
266+
'If the axis `type` is *date*, it should be date strings,',
267+
'like date data, though Date objects and unix milliseconds',
268+
'will be accepted and converted to strings.',
269+
'If the axis `type` is *category*, it should be numbers,',
270+
'using the scale where each category is assigned a serial',
271+
'number from zero in the order it appears.'
258272
].join(' ')
259273
},
260274
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

+20-20
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,19 @@ function drawOne(gd, index, opt, value) {
168168
continue;
169169
}
170170

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

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

185-
// first convert to fraction of the axis
186-
position = (position - axOld.range[0]) /
187-
(axOld.range[1] - axOld.range[0]);
188-
189-
// then convert to new data coordinates at the same fraction
190-
position = axNew.range[0] +
191-
position * (axNew.range[1] - axNew.range[0]);
192+
position = axNew.fraction2r(axOld.r2fraction(position));
192193
}
193194
else if(axOld) { // data -> paper
194195
// first convert to fraction of the axis
195-
position = (position - axOld.range[0]) /
196-
(axOld.range[1] - axOld.range[0]);
196+
position = axOld.r2fraction(position);
197197

198198
// next scale the axis to the whole plot
199199
position = axOld.domain[0] +
@@ -221,8 +221,7 @@ function drawOne(gd, index, opt, value) {
221221
(axNew.domain[1] - axNew.domain[0]);
222222

223223
// finally convert to data coordinates
224-
position = axNew.range[0] +
225-
position * (axNew.range[1] - axNew.range[0]);
224+
position = axNew.fraction2r(position);
226225
}
227226
}
228227

@@ -353,20 +352,21 @@ function drawOne(gd, index, opt, value) {
353352
// outside the visible plot (as long as the axis
354353
// isn't autoranged - then we need to draw it
355354
// anyway to get its bounding box)
356-
if(!ax.autorange && ((options[axLetter] - ax.range[0]) *
357-
(options[axLetter] - ax.range[1]) > 0)) {
355+
var posFraction = ax.r2fraction(options[axLetter]);
356+
if(!ax.autorange && (posFraction < 0 || posFraction > 1)) {
358357
if(options['a' + axLetter + 'ref'] === axRef) {
359-
if((options['a' + axLetter] - ax.range[0]) *
360-
(options['a' + axLetter] - ax.range[1]) > 0) {
358+
posFraction = ax.r2fraction(options['a' + axLetter]);
359+
if(posFraction < 0 || posFraction > 1) {
361360
annotationIsOffscreen = true;
362361
}
363-
} else {
362+
}
363+
else {
364364
annotationIsOffscreen = true;
365365
}
366366

367367
if(annotationIsOffscreen) return;
368368
}
369-
annPosPx[axLetter] = ax._offset + ax.l2p(options[axLetter]);
369+
annPosPx[axLetter] = ax._offset + ax.l2p(ax.r2l(options[axLetter]));
370370
alignPosition = 0.5;
371371
}
372372
else {
@@ -379,7 +379,7 @@ function drawOne(gd, index, opt, value) {
379379

380380
var alignShift = 0;
381381
if(options['a' + axLetter + 'ref'] === axRef) {
382-
annPosPx['aa' + axLetter] = ax._offset + ax.l2p(options['a' + axLetter]);
382+
annPosPx['aa' + axLetter] = ax._offset + ax.l2p(ax.r2l(options['a' + axLetter]));
383383
} else {
384384
if(options.showarrow) {
385385
alignShift = options['a' + axLetter];

‎src/components/images/attributes.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ module.exports = {
8181
},
8282

8383
x: {
84-
valType: 'number',
84+
valType: 'any',
8585
role: 'info',
8686
dflt: 0,
8787
description: [
@@ -93,7 +93,7 @@ module.exports = {
9393
},
9494

9595
y: {
96-
valType: 'number',
96+
valType: 'any',
9797
role: 'info',
9898
dflt: 0,
9999
description: [

‎src/components/rangeselector/get_update_object.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -42,13 +42,13 @@ function getXRange(axisLayout, buttonLayout) {
4242

4343
switch(buttonLayout.stepmode) {
4444
case 'backward':
45-
range0 = Lib.ms2DateTime(d3.time[step].offset(base, -count));
45+
range0 = Lib.ms2DateTime(+d3.time[step].offset(base, -count));
4646
break;
4747

4848
case 'todate':
4949
var base2 = d3.time[step].offset(base, -count);
5050

51-
range0 = Lib.ms2DateTime(d3.time[step].ceil(base2));
51+
range0 = Lib.ms2DateTime(+d3.time[step].ceil(base2));
5252
break;
5353
}
5454

‎src/components/rangeslider/attributes.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,20 @@ module.exports = {
3434
valType: 'info_array',
3535
role: 'info',
3636
items: [
37-
{valType: 'number'},
38-
{valType: 'number'}
37+
{valType: 'any'},
38+
{valType: 'any'}
3939
],
4040
description: [
4141
'Sets the range of the range slider.',
4242
'If not set, defaults to the full xaxis range.',
4343
'If the axis `type` is *log*, then you must take the',
4444
'log of your desired range.',
45-
'If the axis `type` is *date*, then you must convert',
46-
'the date to unix time in milliseconds.'
45+
'If the axis `type` is *date*, it should be date strings,',
46+
'like date data, though Date objects and unix milliseconds',
47+
'will be accepted and converted to strings.',
48+
'If the axis `type` is *category*, it should be numbers,',
49+
'using the scale where each category is assigned a serial',
50+
'number from zero in the order it appears.'
4751
].join(' ')
4852
},
4953
thickness: {

‎src/components/rangeslider/defaults.js

+9-6
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ module.exports = function handleDefaults(layoutIn, layoutOut, axName, counterAxe
2121
}
2222

2323
var containerIn = layoutIn[axName].rangeslider,
24-
containerOut = layoutOut[axName].rangeslider = {};
24+
axOut = layoutOut[axName],
25+
containerOut = axOut.rangeslider = {};
2526

2627
function coerce(attr, dflt) {
2728
return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);
@@ -35,14 +36,16 @@ module.exports = function handleDefaults(layoutIn, layoutOut, axName, counterAxe
3536
coerce('range');
3637

3738
// Expand slider range to the axis range
38-
if(containerOut.range && !layoutOut[axName].autorange) {
39+
if(containerOut.range && !axOut.autorange) {
3940
var outRange = containerOut.range,
40-
axRange = layoutOut[axName].range;
41+
axRange = axOut.range,
42+
l2r = axOut.l2r,
43+
r2l = axOut.r2l;
4144

42-
outRange[0] = Math.min(outRange[0], axRange[0]);
43-
outRange[1] = Math.max(outRange[1], axRange[1]);
45+
outRange[0] = l2r(Math.min(r2l(outRange[0]), r2l(axRange[0])));
46+
outRange[1] = l2r(Math.max(r2l(outRange[1]), r2l(axRange[1])));
4447
} else {
45-
layoutOut[axName]._needsExpand = true;
48+
axOut._needsExpand = true;
4649
}
4750

4851
if(containerOut.visible) {

0 commit comments

Comments
 (0)
Please sign in to comment.