Skip to content

Commit 22c144a

Browse files
authored
Merge pull request #1220 from plotly/world-cals
World calendars
2 parents 6b197a0 + 7bd501f commit 22c144a

File tree

103 files changed

+4618
-634
lines changed

Some content is hidden

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

103 files changed

+4618
-634
lines changed

lib/calendars.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
/**
2+
* Copyright 2012-2016, 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+
module.exports = require('../src/components/calendars');

lib/index.js

+5
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,9 @@ Plotly.register([
5454
require('./groupby')
5555
]);
5656

57+
// components
58+
Plotly.register([
59+
require('./calendars')
60+
]);
61+
5762
module.exports = Plotly;

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@
8989
"superscript-text": "^1.0.0",
9090
"tinycolor2": "^1.3.0",
9191
"topojson-client": "^2.1.0",
92-
"webgl-context": "^2.2.0"
92+
"webgl-context": "^2.2.0",
93+
"world-calendars": "^1.0.0"
9394
},
9495
"devDependencies": {
9596
"brfs": "^1.4.3",

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(xa.r2l(ann.x))], {
72+
Axes.expand(xa, [xa.r2c(ann.x)], {
7373
ppadplus: rightSize,
7474
ppadminus: leftSize
7575
});
7676
}
7777

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

src/components/calendars/calendars.js

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* Copyright 2012-2016, 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+
// a trimmed down version of:
12+
// https://github.com/alexcjohnson/world-calendars/blob/master/dist/index.js
13+
14+
module.exports = require('world-calendars/dist/main');
15+
16+
require('world-calendars/dist/plus');
17+
18+
require('world-calendars/dist/calendars/chinese');
19+
require('world-calendars/dist/calendars/coptic');
20+
require('world-calendars/dist/calendars/discworld');
21+
require('world-calendars/dist/calendars/ethiopian');
22+
require('world-calendars/dist/calendars/hebrew');
23+
require('world-calendars/dist/calendars/islamic');
24+
require('world-calendars/dist/calendars/julian');
25+
require('world-calendars/dist/calendars/mayan');
26+
require('world-calendars/dist/calendars/nanakshahi');
27+
require('world-calendars/dist/calendars/nepali');
28+
require('world-calendars/dist/calendars/persian');
29+
require('world-calendars/dist/calendars/taiwan');
30+
require('world-calendars/dist/calendars/thai');
31+
require('world-calendars/dist/calendars/ummalqura');

src/components/calendars/index.js

+253
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/**
2+
* Copyright 2012-2016, 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 calendars = require('./calendars');
12+
13+
var Lib = require('../../lib');
14+
var constants = require('../../constants/numerical');
15+
16+
var EPOCHJD = constants.EPOCHJD;
17+
var ONEDAY = constants.ONEDAY;
18+
19+
var attributes = {
20+
valType: 'enumerated',
21+
values: Object.keys(calendars.calendars),
22+
role: 'info',
23+
dflt: 'gregorian'
24+
};
25+
26+
var handleDefaults = function(contIn, contOut, attr, dflt) {
27+
var attrs = {};
28+
attrs[attr] = attributes;
29+
30+
return Lib.coerce(contIn, contOut, attrs, attr, dflt);
31+
};
32+
33+
var handleTraceDefaults = function(traceIn, traceOut, coords, layout) {
34+
for(var i = 0; i < coords.length; i++) {
35+
handleDefaults(traceIn, traceOut, coords[i] + 'calendar', layout.calendar);
36+
}
37+
};
38+
39+
// each calendar needs its own default canonical tick. I would love to use
40+
// 2000-01-01 (or even 0000-01-01) for them all but they don't necessarily
41+
// all support either of those dates. Instead I'll use the most significant
42+
// number they *do* support, biased toward the present day.
43+
var CANONICAL_TICK = {
44+
chinese: '2000-01-01',
45+
coptic: '2000-01-01',
46+
discworld: '2000-01-01',
47+
ethiopian: '2000-01-01',
48+
hebrew: '5000-01-01',
49+
islamic: '1000-01-01',
50+
julian: '2000-01-01',
51+
mayan: '5000-01-01',
52+
nanakshahi: '1000-01-01',
53+
nepali: '2000-01-01',
54+
persian: '1000-01-01',
55+
jalali: '1000-01-01',
56+
taiwan: '1000-01-01',
57+
thai: '2000-01-01',
58+
ummalqura: '1400-01-01'
59+
};
60+
61+
// Start on a Sunday - for week ticks
62+
// Discworld and Mayan calendars don't have 7-day weeks but we're going to give them
63+
// 7-day week ticks so start on our Sundays.
64+
// If anyone really cares we can customize the auto tick spacings for these calendars.
65+
var CANONICAL_SUNDAY = {
66+
chinese: '2000-01-02',
67+
coptic: '2000-01-03',
68+
discworld: '2000-01-03',
69+
ethiopian: '2000-01-05',
70+
hebrew: '5000-01-01',
71+
islamic: '1000-01-02',
72+
julian: '2000-01-03',
73+
mayan: '5000-01-01',
74+
nanakshahi: '1000-01-05',
75+
nepali: '2000-01-05',
76+
persian: '1000-01-01',
77+
jalali: '1000-01-01',
78+
taiwan: '1000-01-04',
79+
thai: '2000-01-04',
80+
ummalqura: '1400-01-06'
81+
};
82+
83+
var DFLTRANGE = {
84+
chinese: ['2000-01-01', '2001-01-01'],
85+
coptic: ['1700-01-01', '1701-01-01'],
86+
discworld: ['1800-01-01', '1801-01-01'],
87+
ethiopian: ['2000-01-01', '2001-01-01'],
88+
hebrew: ['5700-01-01', '5701-01-01'],
89+
islamic: ['1400-01-01', '1401-01-01'],
90+
julian: ['2000-01-01', '2001-01-01'],
91+
mayan: ['5200-01-01', '5201-01-01'],
92+
nanakshahi: ['0500-01-01', '0501-01-01'],
93+
nepali: ['2000-01-01', '2001-01-01'],
94+
persian: ['1400-01-01', '1401-01-01'],
95+
jalali: ['1400-01-01', '1401-01-01'],
96+
taiwan: ['0100-01-01', '0101-01-01'],
97+
thai: ['2500-01-01', '2501-01-01'],
98+
ummalqura: ['1400-01-01', '1401-01-01']
99+
};
100+
101+
/*
102+
* convert d3 templates to world-calendars templates, so our users only need
103+
* to know d3's specifiers. Map space padding to no padding, and unknown fields
104+
* to an ugly placeholder
105+
*/
106+
var UNKNOWN = '##';
107+
var d3ToWorldCalendars = {
108+
'd': {'0': 'dd', '-': 'd'}, // 2-digit or unpadded day of month
109+
'a': {'0': 'D', '-': 'D'}, // short weekday name
110+
'A': {'0': 'DD', '-': 'DD'}, // full weekday name
111+
'j': {'0': 'oo', '-': 'o'}, // 3-digit or unpadded day of the year
112+
'W': {'0': 'ww', '-': 'w'}, // 2-digit or unpadded week of the year (Monday first)
113+
'm': {'0': 'mm', '-': 'm'}, // 2-digit or unpadded month number
114+
'b': {'0': 'M', '-': 'M'}, // short month name
115+
'B': {'0': 'MM', '-': 'MM'}, // full month name
116+
'y': {'0': 'yy', '-': 'yy'}, // 2-digit year (map unpadded to zero-padded)
117+
'Y': {'0': 'yyyy', '-': 'yyyy'}, // 4-digit year (map unpadded to zero-padded)
118+
'U': UNKNOWN, // Sunday-first week of the year
119+
'w': UNKNOWN, // day of the week [0(sunday),6]
120+
// combined format, we replace the date part with the world-calendar version
121+
// and the %X stays there for d3 to handle with time parts
122+
'%c': {'0': 'D M m %X yyyy', '-': 'D M m %X yyyy'},
123+
'%x': {'0': 'mm/dd/yyyy', '-': 'mm/dd/yyyy'}
124+
};
125+
126+
function worldCalFmt(fmt, x, calendar) {
127+
var dateJD = Math.floor(x + 0.05 / ONEDAY) + EPOCHJD,
128+
cDate = getCal(calendar).fromJD(dateJD),
129+
i = 0,
130+
modifier, directive, directiveLen, directiveObj, replacementPart;
131+
while((i = fmt.indexOf('%', i)) !== -1) {
132+
modifier = fmt.charAt(i + 1);
133+
if(modifier === '0' || modifier === '-' || modifier === '_') {
134+
directiveLen = 3;
135+
directive = fmt.charAt(i + 1);
136+
if(modifier === '_') modifier = '-';
137+
}
138+
else {
139+
directive = modifier;
140+
modifier = '0';
141+
directiveLen = 2;
142+
}
143+
directiveObj = d3ToWorldCalendars[directive];
144+
if(!directiveObj) {
145+
i += directiveLen;
146+
}
147+
else {
148+
// code is recognized as a date part but world-calendars doesn't support it
149+
if(directiveObj === UNKNOWN) replacementPart = UNKNOWN;
150+
151+
// format the cDate according to the translated directive
152+
else replacementPart = cDate.formatDate(directiveObj[modifier]);
153+
154+
fmt = fmt.substr(0, i) + replacementPart + fmt.substr(i + directiveLen);
155+
i += replacementPart.length;
156+
}
157+
}
158+
return fmt;
159+
}
160+
161+
// cache world calendars, so we don't have to reinstantiate
162+
// during each date-time conversion
163+
var allCals = {};
164+
function getCal(calendar) {
165+
var calendarObj = allCals[calendar];
166+
if(calendarObj) return calendarObj;
167+
168+
calendarObj = allCals[calendar] = calendars.instance(calendar);
169+
return calendarObj;
170+
}
171+
172+
function makeAttrs(description) {
173+
return Lib.extendFlat({}, attributes, { description: description });
174+
}
175+
176+
function makeTraceAttrsDescription(coord) {
177+
return 'Sets the calendar system to use with `' + coord + '` date data.';
178+
}
179+
180+
var xAttrs = {
181+
xcalendar: makeAttrs(makeTraceAttrsDescription('x'))
182+
};
183+
184+
var xyAttrs = Lib.extendFlat({}, xAttrs, {
185+
ycalendar: makeAttrs(makeTraceAttrsDescription('y'))
186+
});
187+
188+
var xyzAttrs = Lib.extendFlat({}, xyAttrs, {
189+
zcalendar: makeAttrs(makeTraceAttrsDescription('z'))
190+
});
191+
192+
var axisAttrs = makeAttrs([
193+
'Sets the calendar system to use for `range` and `tick0`',
194+
'if this is a date axis. This does not set the calendar for',
195+
'interpreting data on this axis, that\'s specified in the trace',
196+
'or via the global `layout.calendar`'
197+
].join(' '));
198+
199+
module.exports = {
200+
moduleType: 'component',
201+
name: 'calendars',
202+
203+
schema: {
204+
traces: {
205+
scatter: xyAttrs,
206+
bar: xyAttrs,
207+
heatmap: xyAttrs,
208+
contour: xyAttrs,
209+
histogram: xyAttrs,
210+
histogram2d: xyAttrs,
211+
histogram2dcontour: xyAttrs,
212+
scatter3d: xyzAttrs,
213+
surface: xyzAttrs,
214+
mesh3d: xyzAttrs,
215+
scattergl: xyAttrs,
216+
ohlc: xAttrs,
217+
candlestick: xAttrs
218+
},
219+
layout: {
220+
calendar: makeAttrs([
221+
'Sets the default calendar system to use for interpreting and',
222+
'displaying dates throughout the plot.'
223+
].join(' ')),
224+
'xaxis.calendar': axisAttrs,
225+
'yaxis.calendar': axisAttrs,
226+
'scene.xaxis.calendar': axisAttrs,
227+
'scene.yaxis.calendar': axisAttrs,
228+
'scene.zaxis.calendar': axisAttrs
229+
},
230+
transforms: {
231+
filter: {
232+
calendar: makeAttrs([
233+
'Sets the calendar system to use for `value`, if it is a date.',
234+
'Note that this is not necessarily the same calendar as is used',
235+
'for the target data; that is set by its own calendar attribute,',
236+
'ie `trace.x` uses `trace.xcalendar` etc.'
237+
].join(' '))
238+
}
239+
}
240+
},
241+
242+
layoutAttributes: attributes,
243+
244+
handleDefaults: handleDefaults,
245+
handleTraceDefaults: handleTraceDefaults,
246+
247+
CANONICAL_SUNDAY: CANONICAL_SUNDAY,
248+
CANONICAL_TICK: CANONICAL_TICK,
249+
DFLTRANGE: DFLTRANGE,
250+
251+
getCal: getCal,
252+
worldCalFmt: worldCalFmt
253+
};

src/components/colorbar/draw.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,8 @@ module.exports = function draw(gd, id) {
174174
axisOptions = {
175175
letter: 'y',
176176
font: fullLayout.font,
177-
noHover: true
177+
noHover: true,
178+
calendar: fullLayout.calendar // not really necessary (yet?)
178179
};
179180

180181
// Coerce w.r.t. Axes layoutAttributes:

src/components/rangeselector/button_attributes.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ module.exports = {
3333
'*step* milliseconds back.',
3434
'For example, with `step` set to *year* and `count` set to *1*',
3535
'the range update shifts the start of the range back to',
36-
'January 01 of the current year.'
36+
'January 01 of the current year.',
37+
'Month and year *todate* are currently available only',
38+
'for the built-in (Gregorian) calendar.'
3739
].join(' ')
3840
},
3941
count: {

src/components/rangeselector/defaults.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ var buttonAttrs = require('./button_attributes');
1616
var constants = require('./constants');
1717

1818

19-
module.exports = function handleDefaults(containerIn, containerOut, layout, counterAxes) {
19+
module.exports = function handleDefaults(containerIn, containerOut, layout, counterAxes, calendar) {
2020
var selectorIn = containerIn.rangeselector || {},
2121
selectorOut = containerOut.rangeselector = {};
2222

2323
function coerce(attr, dflt) {
2424
return Lib.coerce(selectorIn, selectorOut, attributes, attr, dflt);
2525
}
2626

27-
var buttons = buttonsDefaults(selectorIn, selectorOut);
27+
var buttons = buttonsDefaults(selectorIn, selectorOut, calendar);
2828

2929
var visible = coerce('visible', buttons.length > 0);
3030
if(!visible) return;
@@ -45,7 +45,7 @@ module.exports = function handleDefaults(containerIn, containerOut, layout, coun
4545
coerce('borderwidth');
4646
};
4747

48-
function buttonsDefaults(containerIn, containerOut) {
48+
function buttonsDefaults(containerIn, containerOut, calendar) {
4949
var buttonsIn = containerIn.buttons || [],
5050
buttonsOut = containerOut.buttons = [];
5151

@@ -63,7 +63,13 @@ function buttonsDefaults(containerIn, containerOut) {
6363

6464
var step = coerce('step');
6565
if(step !== 'all') {
66-
coerce('stepmode');
66+
if(calendar && calendar !== 'gregorian' && (step === 'month' || step === 'year')) {
67+
buttonOut.stepmode = 'backward';
68+
}
69+
else {
70+
coerce('stepmode');
71+
}
72+
6773
coerce('count');
6874
}
6975

0 commit comments

Comments
 (0)