diff --git a/lib/calendars.js b/lib/calendars.js new file mode 100644 index 00000000000..93eb53e3ba6 --- /dev/null +++ b/lib/calendars.js @@ -0,0 +1,11 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +module.exports = require('../src/components/calendars'); diff --git a/lib/index.js b/lib/index.js index ea309cde21a..2f4c821fb27 100644 --- a/lib/index.js +++ b/lib/index.js @@ -54,4 +54,9 @@ Plotly.register([ require('./groupby') ]); +// components +Plotly.register([ + require('./calendars') +]); + module.exports = Plotly; diff --git a/src/components/calendars/index.js b/src/components/calendars/index.js new file mode 100644 index 00000000000..eb44e3528ea --- /dev/null +++ b/src/components/calendars/index.js @@ -0,0 +1,249 @@ +/** +* Copyright 2012-2016, Plotly, Inc. +* All rights reserved. +* +* This source code is licensed under the MIT license found in the +* LICENSE file in the root directory of this source tree. +*/ + +'use strict'; + +var calendars = require('world-calendars'); + +var Lib = require('../../lib'); +var constants = require('../../constants/numerical'); + +var EPOCHJD = constants.EPOCHJD; +var ONEDAY = constants.ONEDAY; + +var attributes = { + valType: 'enumerated', + values: Object.keys(calendars.calendars), + role: 'info', + dflt: 'gregorian' +}; + +var handleDefaults = function(contIn, contOut, attr, dflt) { + var attrs = {}; + attrs[attr] = attributes; + + return Lib.coerce(contIn, contOut, attrs, attr, dflt); +}; + +var handleTraceDefaults = function(traceIn, traceOut, coords, layout) { + for(var i = 0; i < coords.length; i++) { + handleDefaults(traceIn, traceOut, coords[i] + 'calendar', layout.calendar); + } +}; + +// each calendar needs its own default canonical tick. I would love to use +// 2000-01-01 (or even 0000-01-01) for them all but they don't necessarily +// all support either of those dates. Instead I'll use the most significant +// number they *do* support, biased toward the present day. +var CANONICAL_TICK = { + coptic: '2000-01-01', + discworld: '2000-01-01', + ethiopian: '2000-01-01', + hebrew: '5000-01-01', + islamic: '1000-01-01', + julian: '2000-01-01', + mayan: '5000-01-01', + nanakshahi: '1000-01-01', + nepali: '2000-01-01', + persian: '1000-01-01', + jalali: '1000-01-01', + taiwan: '1000-01-01', + thai: '2000-01-01', + ummalqura: '1400-01-01' +}; + +// Start on a Sunday - for week ticks +// Discworld and Mayan calendars don't have 7-day weeks anyway so don't change them. +// If anyone really cares we can customize the auto tick spacings for these calendars. +var CANONICAL_SUNDAY = { + coptic: '2000-01-03', + discworld: '2000-01-01', + ethiopian: '2000-01-05', + hebrew: '5000-01-01', + islamic: '1000-01-02', + julian: '2000-01-03', + mayan: '5000-01-01', + nanakshahi: '1000-01-05', + nepali: '2000-01-05', + persian: '1000-01-01', + jalali: '1000-01-01', + taiwan: '1000-01-04', + thai: '2000-01-04', + ummalqura: '1400-01-06' +}; + +var DFLTRANGE = { + coptic: ['1700-01-01', '1701-01-01'], + discworld: ['1800-01-01', '1801-01-01'], + ethiopian: ['2000-01-01', '2001-01-01'], + hebrew: ['5700-01-01', '5701-01-01'], + islamic: ['1400-01-01', '1401-01-01'], + julian: ['2000-01-01', '2001-01-01'], + mayan: ['5200-01-01', '5201-01-01'], + nanakshahi: ['0500-01-01', '0501-01-01'], + nepali: ['2000-01-01', '2001-01-01'], + persian: ['1400-01-01', '1401-01-01'], + jalali: ['1400-01-01', '1401-01-01'], + taiwan: ['0100-01-01', '0101-01-01'], + thai: ['2500-01-01', '2501-01-01'], + ummalqura: ['1400-01-01', '1401-01-01'] +}; + +/* + * convert d3 templates to world-calendars templates, so our users only need + * to know d3's specifiers. Map space padding to no padding, and unknown fields + * to an ugly placeholder + */ +var UNKNOWN = '##'; +var d3ToWorldCalendars = { + 'd': {'0': 'dd', '-': 'd'}, // 2-digit or unpadded day of month + 'a': {'0': 'D', '-': 'D'}, // short weekday name + 'A': {'0': 'DD', '-': 'DD'}, // full weekday name + 'j': {'0': 'oo', '-': 'o'}, // 3-digit or unpadded day of the year + 'W': {'0': 'ww', '-': 'w'}, // 2-digit or unpadded week of the year (Monday first) + 'm': {'0': 'mm', '-': 'm'}, // 2-digit or unpadded month number + 'b': {'0': 'M', '-': 'M'}, // short month name + 'B': {'0': 'MM', '-': 'MM'}, // full month name + 'y': {'0': 'yy', '-': 'yy'}, // 2-digit year (map unpadded to zero-padded) + 'Y': {'0': 'yyyy', '-': 'yyyy'}, // 4-digit year (map unpadded to zero-padded) + 'U': UNKNOWN, // Sunday-first week of the year + 'w': UNKNOWN, // day of the week [0(sunday),6] + // combined format, we replace the date part with the world-calendar version + // and the %X stays there for d3 to handle with time parts + '%c': {'0': 'D M m %X yyyy', '-': 'D M m %X yyyy'}, + '%x': {'0': 'mm/dd/yyyy', '-': 'mm/dd/yyyy'} +}; + +function worldCalFmt(fmt, x, calendar) { + var dateJD = Math.floor(x + 0.05 / ONEDAY) + EPOCHJD, + cDate = getCal(calendar).fromJD(dateJD), + i = 0, + modifier, directive, directiveLen, directiveObj, replacementPart; + while((i = fmt.indexOf('%', i)) !== -1) { + modifier = fmt.charAt(i + 1); + if(modifier === '0' || modifier === '-' || modifier === '_') { + directiveLen = 3; + directive = fmt.charAt(i + 1); + if(modifier === '_') modifier = '-'; + } + else { + directive = modifier; + modifier = '0'; + directiveLen = 2; + } + directiveObj = d3ToWorldCalendars[directive]; + if(!directiveObj) { + i += directiveLen; + } + else { + // code is recognized as a date part but world-calendars doesn't support it + if(directiveObj === UNKNOWN) replacementPart = UNKNOWN; + + // format the cDate according to the translated directive + else replacementPart = cDate.formatDate(directiveObj[modifier]); + + fmt = fmt.substr(0, i) + replacementPart + fmt.substr(i + directiveLen); + i += replacementPart.length; + } + } + return fmt; +} + +// cache world calendars, so we don't have to reinstantiate +// during each date-time conversion +var allCals = {}; +function getCal(calendar) { + var calendarObj = allCals[calendar]; + if(calendarObj) return calendarObj; + + calendarObj = allCals[calendar] = calendars.instance(calendar); + return calendarObj; +} + +function makeAttrs(description) { + return Lib.extendFlat({}, attributes, { description: description }); +} + +function makeTraceAttrsDescription(coord) { + return 'Sets the calendar system to use with `' + coord + '` date data.'; +} + +var xAttrs = { + xcalendar: makeAttrs(makeTraceAttrsDescription('x')) +}; + +var xyAttrs = Lib.extendFlat({}, xAttrs, { + ycalendar: makeAttrs(makeTraceAttrsDescription('y')) +}); + +var xyzAttrs = Lib.extendFlat({}, xyAttrs, { + zcalendar: makeAttrs(makeTraceAttrsDescription('z')) +}); + +var axisAttrs = makeAttrs([ + 'Sets the calendar system to use for `range` and `tick0`', + 'if this is a date axis. This does not set the calendar for', + 'interpreting data on this axis, that\'s specified in the trace', + 'or via the global `layout.calendar`' +].join(' ')); + +module.exports = { + moduleType: 'component', + name: 'calendars', + + schema: { + traces: { + scatter: xyAttrs, + bar: xyAttrs, + heatmap: xyAttrs, + contour: xyAttrs, + histogram: xyAttrs, + histogram2d: xyAttrs, + histogram2dcontour: xyAttrs, + scatter3d: xyzAttrs, + surface: xyzAttrs, + mesh3d: xyzAttrs, + scattergl: xyAttrs, + ohlc: xAttrs, + candlestick: xAttrs + }, + layout: { + calendar: makeAttrs([ + 'Sets the default calendar system to use for interpreting and', + 'displaying dates throughout the plot.' + ].join(' ')), + 'xaxis.calendar': axisAttrs, + 'yaxis.calendar': axisAttrs, + 'scene.xaxis.calendar': axisAttrs, + 'scene.yaxis.calendar': axisAttrs, + 'scene.zaxis.calendar': axisAttrs + }, + transforms: { + filter: { + calendar: makeAttrs([ + 'Sets the calendar system to use for `value`, if it is a date.', + 'Note that this is not necessarily the same calendar as is used', + 'for the target data; that is set by its own calendar attribute,', + 'ie `trace.x` uses `trace.xcalendar` etc.' + ].join(' ')) + } + } + }, + + layoutAttributes: attributes, + + handleDefaults: handleDefaults, + handleTraceDefaults: handleTraceDefaults, + + CANONICAL_SUNDAY: CANONICAL_SUNDAY, + CANONICAL_TICK: CANONICAL_TICK, + DFLTRANGE: DFLTRANGE, + + getCal: getCal, + worldCalFmt: worldCalFmt +}; diff --git a/src/components/rangeselector/index.js b/src/components/rangeselector/index.js index 600a7c8e608..baad450c296 100644 --- a/src/components/rangeselector/index.js +++ b/src/components/rangeselector/index.js @@ -12,7 +12,12 @@ module.exports = { moduleType: 'component', name: 'rangeselector', - layoutNodes: ['xaxis.'], + schema: { + layout: { + 'xaxis.rangeselector': require('./attributes') + } + }, + layoutAttributes: require('./attributes'), handleDefaults: require('./defaults'), diff --git a/src/components/rangeslider/index.js b/src/components/rangeslider/index.js index 7b652dc43c3..cd929887fc3 100644 --- a/src/components/rangeslider/index.js +++ b/src/components/rangeslider/index.js @@ -12,7 +12,12 @@ module.exports = { moduleType: 'component', name: 'rangeslider', - layoutNodes: ['xaxis.'], + schema: { + layout: { + 'xaxis.rangeslider': require('./attributes') + } + }, + layoutAttributes: require('./attributes'), handleDefaults: require('./defaults'), diff --git a/src/constants/numerical.js b/src/constants/numerical.js index d10ffa42a51..85155e8d16c 100644 --- a/src/constants/numerical.js +++ b/src/constants/numerical.js @@ -36,5 +36,11 @@ module.exports = { ONEDAY: 86400000, ONEHOUR: 3600000, ONEMIN: 60000, - ONESEC: 1000 + ONESEC: 1000, + + /* + * For fast conversion btwn world calendars and epoch ms, the Julian Day Number + * of the unix epoch. From calendars.instance().newDate(1970, 1, 1).toJD() + */ + EPOCHJD: 2440587.5 }; diff --git a/src/lib/coerce.js b/src/lib/coerce.js index 846869c2a20..7d69b9e64c7 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -9,7 +9,6 @@ 'use strict'; -var calendarList = Object.keys(require('world-calendars').calendars); var isNumeric = require('fast-isnumeric'); var tinycolor = require('tinycolor2'); @@ -268,20 +267,6 @@ exports.valObjects = { return true; } - }, - calendar: { - description: [ - 'A string, one of the calendar systems available', - 'in the `world-calendars` package, to be used in evaluating', - 'or displaying date data. Defaults to built-in (Gregorian) dates.', - 'available calendars:', '*' + calendarList.join('*, *') + '*' - ].join(' '), - requiredOpts: [], - otherOpts: ['dflt'], - coerceFunction: function(v, propOut, dflt) { - if(v && calendarList.indexOf(v) !== -1) propOut.set(v); - else propOut.set(dflt); - } } }; diff --git a/src/lib/dates.js b/src/lib/dates.js index c0a996e8668..2dde90b0187 100644 --- a/src/lib/dates.js +++ b/src/lib/dates.js @@ -9,7 +9,6 @@ 'use strict'; -var calendars = require('world-calendars'); var d3 = require('d3'); var isNumeric = require('fast-isnumeric'); @@ -22,6 +21,9 @@ var ONEDAY = constants.ONEDAY; var ONEHOUR = constants.ONEHOUR; var ONEMIN = constants.ONEMIN; var ONESEC = constants.ONESEC; +var EPOCHJD = constants.EPOCHJD; + +var Registry = require('../registry'); var utcFormat = d3.time.format.utc; @@ -30,102 +32,40 @@ var DATETIME_REGEXP = /^\s*(-?\d\d\d\d|\d\d)(-(\d?\d)(-(\d?\d)([ Tt]([01]?\d|2[0 // for 2-digit years, the first year we map them onto var YFIRST = new Date().getFullYear() - 70; -// cache world calendars, so we don't have to reinstantiate -// during each date-time conversion -var allCals = {}; -function getCal(calendar) { - var calendarObj = allCals[calendar]; - if(calendarObj) return calendarObj; - - calendarObj = allCals[calendar] = calendars.instance(calendar); - return calendarObj; -} - function isWorldCalendar(calendar) { - return calendar && typeof calendar === 'string' && calendar !== 'gregorian'; + return ( + calendar && + Registry.componentsRegistry.calendars && + typeof calendar === 'string' && calendar !== 'gregorian' + ); } -// For fast conversion btwn world calendars and epoch ms, the Julian Day Number -// of the unix epoch. From calendars.instance().newDate(1970, 1, 1).toJD() -var EPOCHJD = 2440587.5; - -// each calendar needs its own default canonical tick. I would love to use -// 2000-01-01 (or even 0000-01-01) for them all but they don't necessarily -// all support either of those dates. Instead I'll use the most significant -// number they *do* support, biased toward the present day. -var CANONICAL_TICK = { - gregorian: '2000-01-01', - coptic: '2000-01-01', - discworld: '2000-01-01', - ethiopian: '2000-01-01', - hebrew: '5000-01-01', - islamic: '1000-01-01', - julian: '2000-01-01', - mayan: '5000-01-01', - nanakshahi: '1000-01-01', - nepali: '2000-01-01', - persian: '1000-01-01', - jalali: '1000-01-01', - taiwan: '1000-01-01', - thai: '2000-01-01', - ummalqura: '1400-01-01' -}; -// Start on a Sunday - for week ticks -// Discworld and Mayan calendars don't have 7-day weeks anyway so don't change them. -// If anyone really cares we can customize the auto tick spacings for these calendars. -var CANONICAL_SUNDAY = { - gregorian: '2000-01-02', - coptic: '2000-01-03', - discworld: '2000-01-01', - ethiopian: '2000-01-05', - hebrew: '5000-01-01', - islamic: '1000-01-02', - julian: '2000-01-03', - mayan: '5000-01-01', - nanakshahi: '1000-01-05', - nepali: '2000-01-05', - persian: '1000-01-01', - jalali: '1000-01-01', - taiwan: '1000-01-04', - thai: '2000-01-04', - ummalqura: '1400-01-06' -}; - -var DFLTRANGE = { - gregorian: ['2000-01-01', '2001-01-01'], - coptic: ['1700-01-01', '1701-01-01'], - discworld: ['1800-01-01', '1801-01-01'], - ethiopian: ['2000-01-01', '2001-01-01'], - hebrew: ['5700-01-01', '5701-01-01'], - islamic: ['1400-01-01', '1401-01-01'], - julian: ['2000-01-01', '2001-01-01'], - mayan: ['5200-01-01', '5201-01-01'], - nanakshahi: ['0500-01-01', '0501-01-01'], - nepali: ['2000-01-01', '2001-01-01'], - persian: ['1400-01-01', '1401-01-01'], - jalali: ['1400-01-01', '1401-01-01'], - taiwan: ['0100-01-01', '0101-01-01'], - thai: ['2500-01-01', '2501-01-01'], - ummalqura: ['1400-01-01', '1401-01-01'] -}; - /* * dateTick0: get the canonical tick for this calendar * * bool sunday is for week ticks, shift it to a Sunday. */ exports.dateTick0 = function(calendar, sunday) { - calendar = (isWorldCalendar(calendar) && calendar) || 'gregorian'; - if(sunday) return CANONICAL_SUNDAY[calendar]; - return CANONICAL_TICK[calendar]; + if(isWorldCalendar(calendar)) { + return sunday ? + Registry.getComponentMethod('calendars', 'CANONICAL_SUNDAY')[calendar] : + Registry.getComponentMethod('calendars', 'CANONICAL_TICK')[calendar]; + } + else { + return sunday ? '2000-01-02' : '2000-01-01'; + } }; /* * dfltRange: for each calendar, give a valid default range */ exports.dfltRange = function(calendar) { - calendar = (isWorldCalendar(calendar) && calendar) || 'gregorian'; - return DFLTRANGE[calendar]; + if(isWorldCalendar(calendar)) { + return Registry.getComponentMethod('calendars', 'DFLTRANGE')[calendar]; + } + else { + return ['2000-01-01', '2001-01-01']; + } }; // is an object a javascript date? @@ -229,7 +169,10 @@ exports.dateTime2ms = function(s, calendar) { if(y.length === 2) return BADNUM; var cDate; - try { cDate = getCal(calendar).newDate(Number(y), m, d); } + try { + cDate = Registry.getComponentMethod('calendars', 'getCal')(calendar) + .newDate(Number(y), m, d); + } catch(e) { return BADNUM; } // Invalid ... date if(!cDate) return BADNUM; @@ -296,7 +239,8 @@ exports.ms2DateTime = function(ms, r, calendar) { var dateJD = Math.floor(msRounded / ONEDAY) + EPOCHJD, timeMs = Math.floor(mod(ms, ONEDAY)); try { - dateStr = getCal(calendar).fromJD(dateJD).formatDate('yyyy-mm-dd'); + dateStr = Registry.getComponentMethod('calendars', 'getCal')(calendar) + .fromJD(dateJD).formatDate('yyyy-mm-dd'); } catch(e) { // invalid date in this calendar - fall back to Gyyyy-mm-dd @@ -405,66 +349,6 @@ exports.cleanDate = function(v, dflt, calendar) { * Date formatting for ticks and hovertext */ -/* - * convert d3 templates to world-calendars templates, so our users only need - * to know d3's specifiers. Map space padding to no padding, and unknown fields - * to an ugly placeholder - */ -var UNKNOWN = '##'; -var d3ToWorldCalendars = { - 'd': {'0': 'dd', '-': 'd'}, // 2-digit or unpadded day of month - 'a': {'0': 'D', '-': 'D'}, // short weekday name - 'A': {'0': 'DD', '-': 'DD'}, // full weekday name - 'j': {'0': 'oo', '-': 'o'}, // 3-digit or unpadded day of the year - 'W': {'0': 'ww', '-': 'w'}, // 2-digit or unpadded week of the year (Monday first) - 'm': {'0': 'mm', '-': 'm'}, // 2-digit or unpadded month number - 'b': {'0': 'M', '-': 'M'}, // short month name - 'B': {'0': 'MM', '-': 'MM'}, // full month name - 'y': {'0': 'yy', '-': 'yy'}, // 2-digit year (map unpadded to zero-padded) - 'Y': {'0': 'yyyy', '-': 'yyyy'}, // 4-digit year (map unpadded to zero-padded) - 'U': UNKNOWN, // Sunday-first week of the year - 'w': UNKNOWN, // day of the week [0(sunday),6] - // combined format, we replace the date part with the world-calendar version - // and the %X stays there for d3 to handle with time parts - '%c': {'0': 'D M m %X yyyy', '-': 'D M m %X yyyy'}, - '%x': {'0': 'mm/dd/yyyy', '-': 'mm/dd/yyyy'} -}; - -function worldCalFmt(fmt, x, calendar) { - var dateJD = Math.floor(x + 0.05 / ONEDAY) + EPOCHJD, - cDate = getCal(calendar).fromJD(dateJD), - i = 0, - modifier, directive, directiveLen, directiveObj, replacementPart; - while((i = fmt.indexOf('%', i)) !== -1) { - modifier = fmt.charAt(i + 1); - if(modifier === '0' || modifier === '-' || modifier === '_') { - directiveLen = 3; - directive = fmt.charAt(i + 1); - if(modifier === '_') modifier = '-'; - } - else { - directive = modifier; - modifier = '0'; - directiveLen = 2; - } - directiveObj = d3ToWorldCalendars[directive]; - if(!directiveObj) { - i += directiveLen; - } - else { - // code is recognized as a date part but world-calendars doesn't support it - if(directiveObj === UNKNOWN) replacementPart = UNKNOWN; - - // format the cDate according to the translated directive - else replacementPart = cDate.formatDate(directiveObj[modifier]); - - fmt = fmt.substr(0, i) + replacementPart + fmt.substr(i + directiveLen); - i += replacementPart.length; - } - } - return fmt; -} - /* * modDateFormat: Support world calendars, and add one item to * d3's vocabulary: @@ -482,7 +366,7 @@ function modDateFormat(fmt, x, calendar) { } if(isWorldCalendar(calendar)) { try { - fmt = worldCalFmt(fmt, x, calendar); + fmt = Registry.getComponentMethod('calendars', 'worldCalFmt')(fmt, x, calendar); } catch(e) { return 'Invalid'; @@ -546,7 +430,8 @@ exports.formatDate = function(x, fmt, tr, calendar) { if(calendar) { try { var dateJD = Math.floor((x + 0.05) / ONEDAY) + EPOCHJD, - cDate = getCal(calendar).fromJD(dateJD); + cDate = Registry.getComponentMethod('calendars', 'getCal')(calendar) + .fromJD(dateJD); if(tr === 'y') dateStr = yearFormatWorld(cDate); else if(tr === 'm') dateStr = monthFormatWorld(cDate); @@ -618,7 +503,7 @@ exports.incrementMonth = function(ms, dMonth, calendar) { if(calendar) { try { var dateJD = Math.round(ms / ONEDAY) + EPOCHJD, - calInstance = getCal(calendar), + calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar), cDate = calInstance.fromJD(dateJD); if(dMonth % 12) calInstance.add(cDate, dMonth, 'm'); @@ -650,7 +535,10 @@ exports.findExactDates = function(data, calendar) { d, di; - var calInstance = isWorldCalendar(calendar) && getCal(calendar); + var calInstance = ( + isWorldCalendar(calendar) && + Registry.getComponentMethod('calendars', 'getCal')(calendar) + ); for(var i = 0; i < data.length; i++) { di = data[i]; diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index ad3bdb8bb18..8f0eb013b2c 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -312,7 +312,7 @@ Plotly.plot = function(gd, data, layout, config) { // show annotations and shapes Registry.getComponentMethod('shapes', 'draw')(gd); - Registry.getComponentMethod('annoations', 'draw')(gd); + Registry.getComponentMethod('annotations', 'draw')(gd); // source links Plots.addLinks(gd); diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js index c5a55ef34ee..1be1b01ba24 100644 --- a/src/plot_api/plot_schema.js +++ b/src/plot_api/plot_schema.js @@ -141,7 +141,7 @@ exports.findArrayAttributes = function(trace) { function callback(attr, attrName, attrs, level) { stack = stack.slice(0, level).concat([attrName]); - var splittableAttr = attr.valType === 'data_array' || attr.arrayOk === true; + var splittableAttr = attr && (attr.valType === 'data_array' || attr.arrayOk === true); if(!splittableAttr) return; var astr = toAttrString(stack); @@ -164,6 +164,7 @@ exports.findArrayAttributes = function(trace) { var transform = transforms[i]; stack = ['transforms[' + i + ']']; + exports.crawl(transform._module.attributes, callback, 1); } } @@ -211,6 +212,17 @@ function getTraceAttributes(type) { extendDeep(attributes, basePlotModule.attributes); } + // add registered components trace attributes + Object.keys(Registry.componentsRegistry).forEach(function(k) { + var _module = Registry.componentsRegistry[k]; + + if(_module.schema && _module.schema.traces && _module.schema.traces[type]) { + Object.keys(_module.schema.traces[type]).forEach(function(v) { + insertAttrs(attributes, _module.schema.traces[type][v], v); + }); + } + }); + // 'type' gets overwritten by baseAttributes; reset it here attributes.type = type; @@ -256,19 +268,19 @@ function getLayoutAttributes() { // polar layout attributes layoutAttributes = assignPolarLayoutAttrs(layoutAttributes); - // add registered components layout attribute + // add registered components layout attributes Object.keys(Registry.componentsRegistry).forEach(function(k) { var _module = Registry.componentsRegistry[k]; if(!_module.layoutAttributes) return; - if(Array.isArray(_module.layoutNodes)) { - _module.layoutNodes.forEach(function(v) { - handleRegisteredComponent(layoutAttributes, _module, v + _module.name); + if(_module.schema && _module.schema.layout) { + Object.keys(_module.schema.layout).forEach(function(v) { + insertAttrs(layoutAttributes, _module.schema.layout[v], v); }); } else { - handleRegisteredComponent(layoutAttributes, _module, _module.name); + insertAttrs(layoutAttributes, _module.layoutAttributes, _module.name); } }); @@ -279,9 +291,21 @@ function getLayoutAttributes() { function getTransformAttributes(type) { var _module = Registry.transformsRegistry[type]; + var attributes = extendDeep({}, _module.attributes); + + // add registered components transform attributes + Object.keys(Registry.componentsRegistry).forEach(function(k) { + var _module = Registry.componentsRegistry[k]; + + if(_module.schema && _module.schema.transforms && _module.schema.transforms[type]) { + Object.keys(_module.schema.transforms[type]).forEach(function(v) { + insertAttrs(attributes, _module.schema.transforms[type][v], v); + }); + } + }); return { - attributes: formatAttributes(_module.attributes) + attributes: formatAttributes(attributes) }; } @@ -365,9 +389,8 @@ function handleBasePlotModule(layoutAttributes, _module, astr) { np.set(attrs); } -function handleRegisteredComponent(layoutAttributes, _module, astr) { - var np = Lib.nestedProperty(layoutAttributes, astr), - attrs = extendDeep(np.get() || {}, _module.layoutAttributes); +function insertAttrs(baseAttrs, newAttrs, astr) { + var np = Lib.nestedProperty(baseAttrs, astr); - np.set(attrs); + np.set(extendDeep(np.get() || {}, newAttrs)); } diff --git a/src/plots/cartesian/axis_defaults.js b/src/plots/cartesian/axis_defaults.js index a69df3f66c9..a0ab9aed0b8 100644 --- a/src/plots/cartesian/axis_defaults.js +++ b/src/plots/cartesian/axis_defaults.js @@ -74,7 +74,10 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce, } } - if(axType === 'date') coerce('calendar', options.calendar); + if(axType === 'date') { + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults'); + handleCalendarDefaults(containerIn, containerOut, 'calendar', options.calendar); + } setConvert(containerOut); diff --git a/src/plots/cartesian/layout_attributes.js b/src/plots/cartesian/layout_attributes.js index bdcc2e42b31..630aa677855 100644 --- a/src/plots/cartesian/layout_attributes.js +++ b/src/plots/cartesian/layout_attributes.js @@ -52,16 +52,6 @@ module.exports = { 'the axis in question.' ].join(' ') }, - calendar: { - valType: 'calendar', - role: 'info', - description: [ - 'Sets the calendar system to use for `range` and `tick0`', - 'if this is a date axis. This does not set the calendar for', - 'interpreting data on this axis, that\'s specified in the trace', - 'or via the global `layout.calendar`' - ].join(' ') - }, autorange: { valType: 'enumerated', values: [true, false, 'reversed'], diff --git a/src/plots/gl3d/layout/axis_attributes.js b/src/plots/gl3d/layout/axis_attributes.js index de5839d830f..faad75ece28 100644 --- a/src/plots/gl3d/layout/axis_attributes.js +++ b/src/plots/gl3d/layout/axis_attributes.js @@ -73,7 +73,6 @@ module.exports = { title: axesAttrs.title, titlefont: axesAttrs.titlefont, type: axesAttrs.type, - calendar: axesAttrs.calendar, autorange: axesAttrs.autorange, rangemode: axesAttrs.rangemode, range: axesAttrs.range, diff --git a/src/plots/layout_attributes.js b/src/plots/layout_attributes.js index 21f377e1ccf..235ff39250c 100644 --- a/src/plots/layout_attributes.js +++ b/src/plots/layout_attributes.js @@ -170,15 +170,6 @@ module.exports = { role: 'info', description: 'Determines whether or not a legend is drawn.' }, - calendar: { - valType: 'calendar', - role: 'info', - dflt: 'gregorian', - description: [ - 'Sets the default calendar system to use for interpreting and', - 'displaying dates throughout the plot.' - ].join(' ') - }, dragmode: { valType: 'enumerated', role: 'info', diff --git a/src/plots/plots.js b/src/plots/plots.js index dadee1b553c..1b7190f89c7 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -942,7 +942,8 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut) { coerce('hidesources'); coerce('smith'); - coerce('calendar'); + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults'); + handleCalendarDefaults(layoutIn, layoutOut, 'calendar'); }; plots.plotAutoSize = function plotAutoSize(gd, layout, fullLayout) { diff --git a/src/traces/bar/attributes.js b/src/traces/bar/attributes.js index a5843177880..bba0d30611f 100644 --- a/src/traces/bar/attributes.js +++ b/src/traces/bar/attributes.js @@ -47,8 +47,6 @@ module.exports = { y: scatterAttrs.y, y0: scatterAttrs.y0, dy: scatterAttrs.dy, - xcalendar: scatterAttrs.xcalendar, - ycalendar: scatterAttrs.ycalendar, text: scatterAttrs.text, diff --git a/src/traces/box/defaults.js b/src/traces/box/defaults.js index 7f75a66d4eb..9f33075d88b 100644 --- a/src/traces/box/defaults.js +++ b/src/traces/box/defaults.js @@ -9,6 +9,7 @@ 'use strict'; var Lib = require('../../lib'); +var Registry = require('../../registry'); var Color = require('../../components/color'); var attributes = require('./attributes'); @@ -33,9 +34,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout return; } - var dfltCalendar = layout.calendar; - coerce('xcalendar', dfltCalendar); - coerce('ycalendar', dfltCalendar); + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); + handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); coerce('orientation', defaultOrientation); diff --git a/src/traces/candlestick/attributes.js b/src/traces/candlestick/attributes.js index f2b61402251..64b7be83203 100644 --- a/src/traces/candlestick/attributes.js +++ b/src/traces/candlestick/attributes.js @@ -27,7 +27,6 @@ var directionAttrs = { module.exports = { x: OHLCattrs.x, - xcalendar: OHLCattrs.xcalendar, open: OHLCattrs.open, high: OHLCattrs.high, low: OHLCattrs.low, diff --git a/src/traces/contour/attributes.js b/src/traces/contour/attributes.js index 9d776e00991..9849d3f06bb 100644 --- a/src/traces/contour/attributes.js +++ b/src/traces/contour/attributes.js @@ -28,8 +28,6 @@ module.exports = extendFlat({}, { transpose: heatmapAttrs.transpose, xtype: heatmapAttrs.xtype, ytype: heatmapAttrs.ytype, - xcalendar: heatmapAttrs.xcalendar, - ycalendar: heatmapAttrs.ycalendar, connectgaps: heatmapAttrs.connectgaps, diff --git a/src/traces/heatmap/attributes.js b/src/traces/heatmap/attributes.js index 19be6407e11..2ec094818f9 100644 --- a/src/traces/heatmap/attributes.js +++ b/src/traces/heatmap/attributes.js @@ -25,8 +25,6 @@ module.exports = extendFlat({}, { y: scatterAttrs.y, y0: scatterAttrs.y0, dy: scatterAttrs.dy, - xcalendar: scatterAttrs.xcalendar, - ycalendar: scatterAttrs.ycalendar, text: { valType: 'data_array', diff --git a/src/traces/heatmap/xyz_defaults.js b/src/traces/heatmap/xyz_defaults.js index 9d808a61819..f07805e88f5 100644 --- a/src/traces/heatmap/xyz_defaults.js +++ b/src/traces/heatmap/xyz_defaults.js @@ -11,6 +11,7 @@ var isNumeric = require('fast-isnumeric'); +var Registry = require('../../registry'); var hasColumns = require('./has_columns'); @@ -37,9 +38,8 @@ module.exports = function handleXYZDefaults(traceIn, traceOut, coerce, layout) { coerce('transpose'); } - var dfltCalendar = layout.calendar; - coerce('xcalendar', dfltCalendar); - coerce('ycalendar', dfltCalendar); + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); + handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); return traceOut.z.length; }; diff --git a/src/traces/histogram/attributes.js b/src/traces/histogram/attributes.js index 53ccddb1184..b455fd4b98a 100644 --- a/src/traces/histogram/attributes.js +++ b/src/traces/histogram/attributes.js @@ -24,8 +24,6 @@ module.exports = { 'Sets the sample data to be binned on the y axis.' ].join(' ') }, - xcalendar: barAttrs.xcalendar, - ycalendar: barAttrs.ycalendar, text: barAttrs.text, orientation: barAttrs.orientation, diff --git a/src/traces/histogram/defaults.js b/src/traces/histogram/defaults.js index 4c5975f2cd2..c90508f8620 100644 --- a/src/traces/histogram/defaults.js +++ b/src/traces/histogram/defaults.js @@ -9,6 +9,7 @@ 'use strict'; +var Registry = require('../../registry'); var Lib = require('../../lib'); var Color = require('../../components/color'); @@ -36,9 +37,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout return; } - var dfltCalendar = layout.calendar; - coerce('xcalendar', dfltCalendar); - coerce('ycalendar', dfltCalendar); + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); + handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); var hasAggregationData = traceOut[orientation === 'h' ? 'x' : 'y']; if(hasAggregationData) coerce('histfunc'); diff --git a/src/traces/histogram2d/attributes.js b/src/traces/histogram2d/attributes.js index 3a69e20041e..c6c6d87445a 100644 --- a/src/traces/histogram2d/attributes.js +++ b/src/traces/histogram2d/attributes.js @@ -19,8 +19,6 @@ module.exports = extendFlat({}, { x: histogramAttrs.x, y: histogramAttrs.y, - xcalendar: histogramAttrs.xcalendar, - ycalendar: histogramAttrs.ycalendar, z: { valType: 'data_array', diff --git a/src/traces/histogram2d/sample_defaults.js b/src/traces/histogram2d/sample_defaults.js index 0101ee4352b..37070082c48 100644 --- a/src/traces/histogram2d/sample_defaults.js +++ b/src/traces/histogram2d/sample_defaults.js @@ -9,6 +9,7 @@ 'use strict'; +var Registry = require('../../registry'); var handleBinDefaults = require('../histogram/bin_defaults'); @@ -16,10 +17,6 @@ module.exports = function handleSampleDefaults(traceIn, traceOut, coerce, layout var x = coerce('x'), y = coerce('y'); - var dfltCalendar = layout.calendar; - coerce('xcalendar', dfltCalendar); - coerce('ycalendar', dfltCalendar); - // we could try to accept x0 and dx, etc... // but that's a pretty weird use case. // for now require both x and y explicitly specified. @@ -28,6 +25,9 @@ module.exports = function handleSampleDefaults(traceIn, traceOut, coerce, layout return; } + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); + handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); + // if marker.color is an array, we can use it in aggregation instead of z var hasAggregationData = coerce('z') || coerce('marker.color'); diff --git a/src/traces/histogram2dcontour/attributes.js b/src/traces/histogram2dcontour/attributes.js index 995e5b9d9bf..49a1ca72596 100644 --- a/src/traces/histogram2dcontour/attributes.js +++ b/src/traces/histogram2dcontour/attributes.js @@ -18,8 +18,6 @@ var extendFlat = require('../../lib/extend').extendFlat; module.exports = extendFlat({}, { x: histogram2dAttrs.x, y: histogram2dAttrs.y, - xcalendar: histogram2dAttrs.xcalendar, - ycalendar: histogram2dAttrs.ycalendar, z: histogram2dAttrs.z, marker: histogram2dAttrs.marker, diff --git a/src/traces/mesh3d/attributes.js b/src/traces/mesh3d/attributes.js index f49f1a8ec62..c726e23c03d 100644 --- a/src/traces/mesh3d/attributes.js +++ b/src/traces/mesh3d/attributes.js @@ -37,9 +37,6 @@ module.exports = { 'jointly represent the X, Y and Z coordinates of the nth vertex.' ].join(' ') }, - xcalendar: surfaceAtts.xcalendar, - ycalendar: surfaceAtts.ycalendar, - zcalendar: surfaceAtts.zcalendar, i: { valType: 'data_array', diff --git a/src/traces/mesh3d/defaults.js b/src/traces/mesh3d/defaults.js index 47c24d484b5..f366a2dab0b 100644 --- a/src/traces/mesh3d/defaults.js +++ b/src/traces/mesh3d/defaults.js @@ -9,6 +9,7 @@ 'use strict'; +var Registry = require('../../registry'); var Lib = require('../../lib'); var colorbarDefaults = require('../../components/colorbar/defaults'); var attributes = require('./attributes'); @@ -20,12 +21,10 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout } // read in face/vertex properties - function readComponents(array, doCalendar) { + function readComponents(array) { var ret = array.map(function(attr) { var result = coerce(attr); - if(doCalendar) coerce(attr + 'calendar', layout.calendar); - if(result && Array.isArray(result)) return result; return null; }); @@ -35,7 +34,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout }) && ret; } - var coords = readComponents(['x', 'y', 'z'], true); + var coords = readComponents(['x', 'y', 'z']); var indices = readComponents(['i', 'j', 'k']); if(!coords) { @@ -50,6 +49,9 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout }); } + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); + handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout); + // Coerce remaining properties [ 'lighting.ambient', diff --git a/src/traces/ohlc/attributes.js b/src/traces/ohlc/attributes.js index 58551eed920..b4168829c97 100644 --- a/src/traces/ohlc/attributes.js +++ b/src/traces/ohlc/attributes.js @@ -53,7 +53,6 @@ module.exports = { 'If absent, linear coordinate will be generated.' ].join(' ') }, - xcalendar: scatterAttrs.xcalendar, open: { valType: 'data_array', diff --git a/src/traces/ohlc/ohlc_defaults.js b/src/traces/ohlc/ohlc_defaults.js index 51ec3a2cba1..0fe5abdfda7 100644 --- a/src/traces/ohlc/ohlc_defaults.js +++ b/src/traces/ohlc/ohlc_defaults.js @@ -9,6 +9,9 @@ 'use strict'; +var Registry = require('../../registry'); + + module.exports = function handleOHLC(traceIn, traceOut, coerce, layout) { var len; @@ -18,7 +21,8 @@ module.exports = function handleOHLC(traceIn, traceOut, coerce, layout) { low = coerce('low'), close = coerce('close'); - coerce('xcalendar', layout.calendar); + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); + handleCalendarDefaults(traceIn, traceOut, ['x'], layout); len = Math.min(open.length, high.length, low.length, close.length); diff --git a/src/traces/scatter/attributes.js b/src/traces/scatter/attributes.js index 8b804332a3f..1a728047d4d 100644 --- a/src/traces/scatter/attributes.js +++ b/src/traces/scatter/attributes.js @@ -41,11 +41,6 @@ module.exports = { 'See `x0` for more info.' ].join(' ') }, - xcalendar: { - valType: 'calendar', - role: 'info', - description: 'Sets the calendar system to use with `x` date data' - }, y: { valType: 'data_array', description: 'Sets the y coordinates.' @@ -70,11 +65,6 @@ module.exports = { 'See `y0` for more info.' ].join(' ') }, - ycalendar: { - valType: 'calendar', - role: 'info', - description: 'Sets the calendar system to use with `y` date data' - }, ids: { valType: 'data_array', description: 'A list of keys for object constancy of data points during animation' diff --git a/src/traces/scatter/xy_defaults.js b/src/traces/scatter/xy_defaults.js index 15bfa324cf3..7e6ff7409f7 100644 --- a/src/traces/scatter/xy_defaults.js +++ b/src/traces/scatter/xy_defaults.js @@ -9,15 +9,16 @@ 'use strict'; +var Registry = require('../../registry'); + module.exports = function handleXYDefaults(traceIn, traceOut, layout, coerce) { var len, x = coerce('x'), y = coerce('y'); - var dfltCalendar = layout.calendar; - coerce('xcalendar', dfltCalendar); - coerce('ycalendar', dfltCalendar); + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); + handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout); if(x) { if(y) { diff --git a/src/traces/scatter3d/attributes.js b/src/traces/scatter3d/attributes.js index ebc7fcdb040..836c4511cb4 100644 --- a/src/traces/scatter3d/attributes.js +++ b/src/traces/scatter3d/attributes.js @@ -65,11 +65,6 @@ module.exports = { valType: 'data_array', description: 'Sets the z coordinates.' }, - xcalendar: scatterAttrs.xcalendar, - ycalendar: scatterAttrs.ycalendar, - zcalendar: extendFlat({}, scatterAttrs.xcalendar, { - description: 'Sets the calendar system to use with `z` date data' - }), text: extendFlat({}, scatterAttrs.text, { description: [ diff --git a/src/traces/scatter3d/defaults.js b/src/traces/scatter3d/defaults.js index 45b420c149b..4bd5185b682 100644 --- a/src/traces/scatter3d/defaults.js +++ b/src/traces/scatter3d/defaults.js @@ -9,6 +9,7 @@ 'use strict'; +var Registry = require('../../registry'); var Lib = require('../../lib'); var subTypes = require('../scatter/subtypes'); @@ -72,9 +73,8 @@ function handleXYZDefaults(traceIn, traceOut, coerce, layout) { y = coerce('y'), z = coerce('z'); - coerce('xcalendar', layout.calendar); - coerce('ycalendar', layout.calendar); - coerce('zcalendar', layout.calendar); + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); + handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout); if(x && y && z) { len = Math.min(x.length, y.length, z.length); diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js index f6e736f6ad5..c5bb9c12bc5 100644 --- a/src/traces/scattergl/attributes.js +++ b/src/traces/scattergl/attributes.js @@ -27,8 +27,6 @@ module.exports = { y: scatterAttrs.y, y0: scatterAttrs.y0, dy: scatterAttrs.dy, - xcalendar: scatterAttrs.xcalendar, - ycalendar: scatterAttrs.ycalendar, text: extendFlat({}, scatterAttrs.text, { description: [ diff --git a/src/traces/surface/attributes.js b/src/traces/surface/attributes.js index c442abced7f..47a0eaf725d 100644 --- a/src/traces/surface/attributes.js +++ b/src/traces/surface/attributes.js @@ -11,7 +11,6 @@ var Color = require('../../components/color'); var colorscaleAttrs = require('../../components/colorscale/attributes'); var colorbarAttrs = require('../../components/colorbar/attributes'); -var scatterAttrs = require('../scatter/attributes'); var extendFlat = require('../../lib/extend').extendFlat; @@ -110,11 +109,6 @@ module.exports = { valType: 'data_array', description: 'Sets the y coordinates.' }, - xcalendar: scatterAttrs.xcalendar, - ycalendar: scatterAttrs.ycalendar, - zcalendar: extendFlat({}, scatterAttrs.xcalendar, { - description: 'Sets the calendar system to use with `z` date data' - }), text: { valType: 'data_array', diff --git a/src/traces/surface/defaults.js b/src/traces/surface/defaults.js index c104c93af6d..3e88b83674a 100644 --- a/src/traces/surface/defaults.js +++ b/src/traces/surface/defaults.js @@ -9,6 +9,7 @@ 'use strict'; +var Registry = require('../../registry'); var Lib = require('../../lib'); var colorscaleDefaults = require('../../components/colorscale/defaults'); @@ -34,9 +35,8 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout coerce('x'); coerce('y'); - coerce('xcalendar', layout.calendar); - coerce('ycalendar', layout.calendar); - coerce('zcalendar', layout.calendar); + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults'); + handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout); if(!Array.isArray(traceOut.x)) { // build a linearly scaled x diff --git a/src/transforms/filter.js b/src/transforms/filter.js index ec6454dd0e8..1467620d7cf 100644 --- a/src/transforms/filter.js +++ b/src/transforms/filter.js @@ -9,6 +9,7 @@ 'use strict'; var Lib = require('../lib'); +var Registry = require('../registry'); var PlotSchema = require('../plot_api/plot_schema'); var axisIds = require('../plots/cartesian/axis_ids'); var autoType = require('../plots/cartesian/axis_autotype'); @@ -100,16 +101,6 @@ exports.attributes = { '*value* is expected to be an array with as many items as', 'the desired set elements.' ].join(' ') - }, - calendar: { - valType: 'calendar', - role: 'info', - description: [ - 'Sets the calendar system to use for `value`, if it is a date.', - 'Note that this is not necessarily the same calendar as is used', - 'for the target data; that is set by its own calendar attribute,', - 'ie `trace.x` uses `trace.xcalendar` etc.' - ].join(' ') } }; @@ -126,7 +117,9 @@ exports.supplyDefaults = function(transformIn) { coerce('operation'); coerce('value'); coerce('target'); - coerce('calendar'); + + var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults'); + handleCalendarDefaults(transformIn, transformOut, 'calendar', null); } return transformOut; diff --git a/test/jasmine/tests/plotschema_test.js b/test/jasmine/tests/plotschema_test.js index 50503259c84..2420632f3bf 100644 --- a/test/jasmine/tests/plotschema_test.js +++ b/test/jasmine/tests/plotschema_test.js @@ -188,6 +188,17 @@ describe('plot schema', function() { }); }); + it('should work with registered components', function() { + expect(plotSchema.traces.scatter.attributes.xcalendar.valType).toEqual('enumerated'); + expect(plotSchema.traces.scatter3d.attributes.zcalendar.valType).toEqual('enumerated'); + + expect(plotSchema.layout.layoutAttributes.calendar.valType).toEqual('enumerated'); + expect(plotSchema.layout.layoutAttributes.xaxis.calendar.valType).toEqual('enumerated'); + expect(plotSchema.layout.layoutAttributes.scene.xaxis.calendar.valType).toEqual('enumerated'); + + expect(plotSchema.transforms.filter.attributes.calendar.valType).toEqual('enumerated'); + }); + it('should list correct defs', function() { expect(plotSchema.defs.valObjects).toBeDefined();