Skip to content

Commit 119b6d5

Browse files
authored
Merge pull request #5074 from plotly/period-positioning
Period positioning for Cartesian traces
2 parents fa6c5bf + 72f6de8 commit 119b6d5

Some content is hidden

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

70 files changed

+3374
-64
lines changed

src/plots/cartesian/align_period.js

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* Copyright 2012-2020, Plotly, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the MIT license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
'use strict';
10+
11+
var isNumeric = require('fast-isnumeric');
12+
var Lib = require('../../lib');
13+
var dateTime2ms = Lib.dateTime2ms;
14+
var incrementMonth = Lib.incrementMonth;
15+
var constants = require('../../constants/numerical');
16+
var ONEAVGMONTH = constants.ONEAVGMONTH;
17+
18+
module.exports = function alignPeriod(trace, ax, axLetter, vals) {
19+
if(ax.type !== 'date') return vals;
20+
21+
var alignment = trace[axLetter + 'periodalignment'];
22+
if(!alignment) return vals;
23+
24+
var period = trace[axLetter + 'period'];
25+
var mPeriod;
26+
if(isNumeric(period)) {
27+
period = +period;
28+
if(period <= 0) return vals;
29+
} else if(typeof period === 'string' && period.charAt(0) === 'M') {
30+
var n = +(period.substring(1));
31+
if(n > 0 && Math.round(n) === n) {
32+
mPeriod = n;
33+
} else return vals;
34+
}
35+
36+
var calendar = ax.calendar;
37+
38+
var isStart = 'start' === alignment;
39+
// var isMiddle = 'middle' === alignment;
40+
var isEnd = 'end' === alignment;
41+
42+
var period0 = trace[axLetter + 'period0'];
43+
var base = dateTime2ms(period0, calendar) || 0;
44+
45+
var newVals = [];
46+
var len = vals.length;
47+
for(var i = 0; i < len; i++) {
48+
var v = vals[i];
49+
50+
var nEstimated, startTime, endTime;
51+
if(mPeriod) {
52+
// guess at how many periods away from base we are
53+
nEstimated = Math.round((v - base) / (mPeriod * ONEAVGMONTH));
54+
endTime = incrementMonth(base, mPeriod * nEstimated, calendar);
55+
56+
// iterate to get the exact bounds before and after v
57+
// there may be ways to make this faster, but most of the time
58+
// we'll only execute each loop zero or one time.
59+
while(endTime > v) {
60+
endTime = incrementMonth(endTime, -mPeriod, calendar);
61+
}
62+
while(endTime <= v) {
63+
endTime = incrementMonth(endTime, mPeriod, calendar);
64+
}
65+
66+
// now we know endTime is the boundary immediately after v
67+
// so startTime is obtained by incrementing backward one period.
68+
startTime = incrementMonth(endTime, -mPeriod, calendar);
69+
} else { // case of ms
70+
nEstimated = Math.round((v - base) / period);
71+
endTime = base + nEstimated * period;
72+
73+
while(endTime > v) {
74+
endTime -= period;
75+
}
76+
while(endTime <= v) {
77+
endTime += period;
78+
}
79+
80+
startTime = endTime - period;
81+
}
82+
83+
newVals[i] = (
84+
isStart ? startTime :
85+
isEnd ? endTime :
86+
(startTime + endTime) / 2
87+
);
88+
}
89+
return newVals;
90+
};

src/plots/cartesian/set_convert.js

-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ var ONEMIN = numConstants.ONEMIN;
3030
var ONESEC = numConstants.ONESEC;
3131

3232
var axisIds = require('./axis_ids');
33-
3433
var constants = require('./constants');
3534
var HOUR_PATTERN = constants.HOUR_PATTERN;
3635
var WEEKDAY_PATTERN = constants.WEEKDAY_PATTERN;

src/traces/bar/attributes.js

+7
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ module.exports = {
5959
y0: scatterAttrs.y0,
6060
dy: scatterAttrs.dy,
6161

62+
xperiod: scatterAttrs.xperiod,
63+
yperiod: scatterAttrs.yperiod,
64+
xperiod0: scatterAttrs.xperiod0,
65+
yperiod0: scatterAttrs.yperiod0,
66+
xperiodalignment: scatterAttrs.xperiodalignment,
67+
yperiodalignment: scatterAttrs.yperiodalignment,
68+
6269
text: scatterAttrs.text,
6370
texttemplate: texttemplateAttrs({editType: 'plot'}, {
6471
keys: constants.eventDataKeys

src/traces/bar/calc.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
'use strict';
1010

1111
var Axes = require('../../plots/cartesian/axes');
12+
var alignPeriod = require('../../plots/cartesian/align_period');
1213
var hasColorscale = require('../../components/colorscale/helpers').hasColorscale;
1314
var colorscaleCalc = require('../../components/colorscale/calc');
1415
var arraysToCalcdata = require('./arrays_to_calcdata');
@@ -17,18 +18,23 @@ var calcSelection = require('../scatter/calc_selection');
1718
module.exports = function calc(gd, trace) {
1819
var xa = Axes.getFromId(gd, trace.xaxis || 'x');
1920
var ya = Axes.getFromId(gd, trace.yaxis || 'y');
20-
var size, pos;
21+
var size, pos, origPos;
2122

2223
var sizeOpts = {
2324
msUTC: !!(trace.base || trace.base === 0)
2425
};
2526

27+
var hasPeriod;
2628
if(trace.orientation === 'h') {
2729
size = xa.makeCalcdata(trace, 'x', sizeOpts);
28-
pos = ya.makeCalcdata(trace, 'y');
30+
origPos = ya.makeCalcdata(trace, 'y');
31+
pos = alignPeriod(trace, ya, 'y', origPos);
32+
hasPeriod = !!trace.yperiodalignment;
2933
} else {
3034
size = ya.makeCalcdata(trace, 'y', sizeOpts);
31-
pos = xa.makeCalcdata(trace, 'x');
35+
origPos = xa.makeCalcdata(trace, 'x');
36+
pos = alignPeriod(trace, xa, 'x', origPos);
37+
hasPeriod = !!trace.xperiodalignment;
3238
}
3339

3440
// create the "calculated data" to plot
@@ -39,6 +45,10 @@ module.exports = function calc(gd, trace) {
3945
for(var i = 0; i < serieslen; i++) {
4046
cd[i] = { p: pos[i], s: size[i] };
4147

48+
if(hasPeriod) {
49+
cd[i].orig_p = origPos[i]; // used by hover
50+
}
51+
4252
if(trace.ids) {
4353
cd[i].id = String(trace.ids[i]);
4454
}

src/traces/bar/defaults.js

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ var Color = require('../../components/color');
1313
var Registry = require('../../registry');
1414

1515
var handleXYDefaults = require('../scatter/xy_defaults');
16+
var handlePeriodDefaults = require('../scatter/period_defaults');
1617
var handleStyleDefaults = require('./style_defaults');
1718
var getAxisGroup = require('../../plots/cartesian/axis_ids').getAxisGroup;
1819
var attributes = require('./attributes');
@@ -30,6 +31,8 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
3031
return;
3132
}
3233

34+
handlePeriodDefaults(traceIn, traceOut, layout, coerce);
35+
3336
coerce('orientation', (traceOut.x && !traceOut.y) ? 'h' : 'v');
3437
coerce('base');
3538
coerce('offset');

src/traces/bar/hover.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,9 @@ function hoverOnBars(pointData, xval, yval, hovermode) {
158158
var extent = t.extents[t.extents.round(di.p)];
159159
pointData[posLetter + '0'] = pa.c2p(isClosest ? minPos(di) : extent[0], true);
160160
pointData[posLetter + '1'] = pa.c2p(isClosest ? maxPos(di) : extent[1], true);
161-
pointData[posLetter + 'LabelVal'] = di.p;
161+
162+
var hasPeriod = di.orig_p !== undefined;
163+
pointData[posLetter + 'LabelVal'] = hasPeriod ? di.orig_p : di.p;
162164

163165
pointData.labelLabel = hoverLabelText(pa, pointData[posLetter + 'LabelVal']);
164166
pointData.valueLabel = hoverLabelText(sa, pointData[sizeLetter + 'LabelVal']);

src/traces/box/attributes.js

+7
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ module.exports = {
7676
].join(' ')
7777
},
7878

79+
xperiod: scatterAttrs.xperiod,
80+
yperiod: scatterAttrs.yperiod,
81+
xperiod0: scatterAttrs.xperiod0,
82+
yperiod0: scatterAttrs.yperiod0,
83+
xperiodalignment: scatterAttrs.xperiodalignment,
84+
yperiodalignment: scatterAttrs.yperiodalignment,
85+
7986
name: {
8087
valType: 'string',
8188
role: 'info',

src/traces/box/calc.js

+15-4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
var isNumeric = require('fast-isnumeric');
1212

1313
var Axes = require('../../plots/cartesian/axes');
14+
var alignPeriod = require('../../plots/cartesian/align_period');
1415
var Lib = require('../../lib');
1516

1617
var BADNUM = require('../../constants/numerical').BADNUM;
@@ -29,19 +30,24 @@ module.exports = function calc(gd, trace) {
2930
var valAxis, valLetter;
3031
var posAxis, posLetter;
3132

33+
var hasPeriod;
3234
if(trace.orientation === 'h') {
3335
valAxis = xa;
3436
valLetter = 'x';
3537
posAxis = ya;
3638
posLetter = 'y';
39+
hasPeriod = !!trace.yperiodalignment;
3740
} else {
3841
valAxis = ya;
3942
valLetter = 'y';
4043
posAxis = xa;
4144
posLetter = 'x';
45+
hasPeriod = !!trace.xperiodalignment;
4246
}
4347

44-
var posArray = getPos(trace, posLetter, posAxis, fullLayout[numKey]);
48+
var allPosArrays = getPosArrays(trace, posLetter, posAxis, fullLayout[numKey]);
49+
var posArray = allPosArrays[0];
50+
var origPos = allPosArrays[1];
4551
var dv = Lib.distinctVals(posArray);
4652
var posDistinct = dv.vals;
4753
var dPos = dv.minDiff / 2;
@@ -77,6 +83,9 @@ module.exports = function calc(gd, trace) {
7783

7884
cdi = {};
7985
cdi.pos = cdi[posLetter] = posi;
86+
if(hasPeriod && origPos) {
87+
cdi.orig_p = origPos[i]; // used by hover
88+
}
8089

8190
cdi.q1 = d2c('q1');
8291
cdi.med = d2c('median');
@@ -303,13 +312,15 @@ module.exports = function calc(gd, trace) {
303312
// so if you want one box
304313
// per trace, set x0 (y0) to the x (y) value or category for this trace
305314
// (or set x (y) to a constant array matching y (x))
306-
function getPos(trace, posLetter, posAxis, num) {
315+
function getPosArrays(trace, posLetter, posAxis, num) {
307316
var hasPosArray = posLetter in trace;
308317
var hasPos0 = posLetter + '0' in trace;
309318
var hasPosStep = 'd' + posLetter in trace;
310319

311320
if(hasPosArray || (hasPos0 && hasPosStep)) {
312-
return posAxis.makeCalcdata(trace, posLetter);
321+
var origPos = posAxis.makeCalcdata(trace, posLetter);
322+
var pos = alignPeriod(trace, posAxis, posLetter, origPos);
323+
return [pos, origPos];
313324
}
314325

315326
var pos0;
@@ -337,7 +348,7 @@ function getPos(trace, posLetter, posAxis, num) {
337348
var out = new Array(len);
338349
for(var i = 0; i < len; i++) out[i] = pos0c;
339350

340-
return out;
351+
return [out];
341352
}
342353

343354
function makeBins(x, dx) {

src/traces/box/defaults.js

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
var Lib = require('../../lib');
1212
var Registry = require('../../registry');
1313
var Color = require('../../components/color');
14+
var handlePeriodDefaults = require('../scatter/period_defaults');
1415
var handleGroupingDefaults = require('../bar/defaults').handleGroupingDefaults;
1516
var autoType = require('../../plots/cartesian/axis_autotype');
1617
var attributes = require('./attributes');
@@ -23,6 +24,8 @@ function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
2324
handleSampleDefaults(traceIn, traceOut, coerce, layout);
2425
if(traceOut.visible === false) return;
2526

27+
handlePeriodDefaults(traceIn, traceOut, layout, coerce);
28+
2629
var hasPreCompStats = traceOut._hasPreCompStats;
2730

2831
if(hasPreCompStats) {

src/traces/box/hover.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) {
143143
pointData[pLetter + '0'] = pAxis.c2p(di.pos + t.bPos - boxDeltaNeg, true);
144144
pointData[pLetter + '1'] = pAxis.c2p(di.pos + t.bPos + boxDeltaPos, true);
145145

146-
pointData[pLetter + 'LabelVal'] = di.pos;
146+
pointData[pLetter + 'LabelVal'] = di.orig_p !== undefined ? di.orig_p : di.pos;
147147

148148
var spikePosAttr = pLetter + 'Spike';
149149
pointData.spikeDistance = dxy(di) * spikePseudoDistance / hoverPseudoDistance;
@@ -257,14 +257,16 @@ function hoverOnPoints(pointData, xval, yval) {
257257
hovertemplate: trace.hovertemplate
258258
});
259259

260+
var origPos = di.orig_p;
261+
var pos = origPos !== undefined ? origPos : di.pos;
260262
var pa;
261263
if(trace.orientation === 'h') {
262264
pa = ya;
263265
closePtData.xLabelVal = pt.x;
264-
closePtData.yLabelVal = di.pos;
266+
closePtData.yLabelVal = pos;
265267
} else {
266268
pa = xa;
267-
closePtData.xLabelVal = di.pos;
269+
closePtData.xLabelVal = pos;
268270
closePtData.yLabelVal = pt.y;
269271
}
270272

src/traces/candlestick/attributes.js

+4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ function directionAttrs(lineColorDefault) {
2727
}
2828

2929
module.exports = {
30+
xperiod: OHLCattrs.xperiod,
31+
xperiod0: OHLCattrs.xperiod0,
32+
xperiodalignment: OHLCattrs.xperiodalignment,
33+
3034
x: OHLCattrs.x,
3135
open: OHLCattrs.open,
3236
high: OHLCattrs.high,

src/traces/candlestick/calc.js

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
var Lib = require('../../lib');
1212
var Axes = require('../../plots/cartesian/axes');
13+
var alignPeriod = require('../../plots/cartesian/align_period');
1314

1415
var calcCommon = require('../ohlc/calc').calcCommon;
1516

@@ -18,9 +19,10 @@ module.exports = function(gd, trace) {
1819
var xa = Axes.getFromId(gd, trace.xaxis);
1920
var ya = Axes.getFromId(gd, trace.yaxis);
2021

21-
var x = xa.makeCalcdata(trace, 'x');
22+
var origX = xa.makeCalcdata(trace, 'x');
23+
var x = alignPeriod(trace, xa, 'x', origX);
2224

23-
var cd = calcCommon(gd, trace, x, ya, ptFunc);
25+
var cd = calcCommon(gd, trace, origX, x, ya, ptFunc);
2426

2527
if(cd.length) {
2628
Lib.extendFlat(cd[0].t, {

src/traces/candlestick/defaults.js

+3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
var Lib = require('../../lib');
1313
var Color = require('../../components/color');
1414
var handleOHLC = require('../ohlc/ohlc_defaults');
15+
var handlePeriodDefaults = require('../scatter/period_defaults');
1516
var attributes = require('./attributes');
1617

1718
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
@@ -25,6 +26,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
2526
return;
2627
}
2728

29+
handlePeriodDefaults(traceIn, traceOut, layout, coerce, {x: true});
30+
2831
coerce('line.width');
2932

3033
handleDirection(traceIn, traceOut, coerce, 'increasing');

src/traces/contour/attributes.js

+8
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ module.exports = extendFlat({
3131
y: heatmapAttrs.y,
3232
y0: heatmapAttrs.y0,
3333
dy: heatmapAttrs.dy,
34+
35+
xperiod: heatmapAttrs.xperiod,
36+
yperiod: heatmapAttrs.yperiod,
37+
xperiod0: scatterAttrs.xperiod0,
38+
yperiod0: scatterAttrs.yperiod0,
39+
xperiodalignment: heatmapAttrs.xperiodalignment,
40+
yperiodalignment: heatmapAttrs.yperiodalignment,
41+
3442
text: heatmapAttrs.text,
3543
hovertext: heatmapAttrs.hovertext,
3644
transpose: heatmapAttrs.transpose,

src/traces/contour/defaults.js

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
var Lib = require('../../lib');
1212

1313
var handleXYZDefaults = require('../heatmap/xyz_defaults');
14+
var handlePeriodDefaults = require('../scatter/period_defaults');
1415
var handleConstraintDefaults = require('./constraint_defaults');
1516
var handleContoursDefaults = require('./contours_defaults');
1617
var handleStyleDefaults = require('./style_defaults');
@@ -32,6 +33,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
3233
return;
3334
}
3435

36+
handlePeriodDefaults(traceIn, traceOut, layout, coerce);
37+
3538
coerce('text');
3639
coerce('hovertext');
3740
coerce('hovertemplate');

0 commit comments

Comments
 (0)