|
| 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 | +}; |
0 commit comments