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/package.json b/package.json
index 17515ba7a89..bb9325e5d71 100644
--- a/package.json
+++ b/package.json
@@ -89,7 +89,8 @@
"superscript-text": "^1.0.0",
"tinycolor2": "^1.3.0",
"topojson-client": "^2.1.0",
- "webgl-context": "^2.2.0"
+ "webgl-context": "^2.2.0",
+ "world-calendars": "^1.0.0"
},
"devDependencies": {
"brfs": "^1.4.3",
diff --git a/src/components/annotations/calc_autorange.js b/src/components/annotations/calc_autorange.js
index a5969ae181b..9046863bf81 100644
--- a/src/components/annotations/calc_autorange.js
+++ b/src/components/annotations/calc_autorange.js
@@ -69,14 +69,14 @@ function annAutorange(gd) {
}
if(xa && xa.autorange) {
- Axes.expand(xa, [xa.l2c(xa.r2l(ann.x))], {
+ Axes.expand(xa, [xa.r2c(ann.x)], {
ppadplus: rightSize,
ppadminus: leftSize
});
}
if(ya && ya.autorange) {
- Axes.expand(ya, [ya.l2c(ya.r2l(ann.y))], {
+ Axes.expand(ya, [ya.r2c(ann.y)], {
ppadplus: bottomSize,
ppadminus: topSize
});
diff --git a/src/components/calendars/calendars.js b/src/components/calendars/calendars.js
new file mode 100644
index 00000000000..02dfbfefdc1
--- /dev/null
+++ b/src/components/calendars/calendars.js
@@ -0,0 +1,31 @@
+/**
+* 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';
+
+// a trimmed down version of:
+// https://github.com/alexcjohnson/world-calendars/blob/master/dist/index.js
+
+module.exports = require('world-calendars/dist/main');
+
+require('world-calendars/dist/plus');
+
+require('world-calendars/dist/calendars/chinese');
+require('world-calendars/dist/calendars/coptic');
+require('world-calendars/dist/calendars/discworld');
+require('world-calendars/dist/calendars/ethiopian');
+require('world-calendars/dist/calendars/hebrew');
+require('world-calendars/dist/calendars/islamic');
+require('world-calendars/dist/calendars/julian');
+require('world-calendars/dist/calendars/mayan');
+require('world-calendars/dist/calendars/nanakshahi');
+require('world-calendars/dist/calendars/nepali');
+require('world-calendars/dist/calendars/persian');
+require('world-calendars/dist/calendars/taiwan');
+require('world-calendars/dist/calendars/thai');
+require('world-calendars/dist/calendars/ummalqura');
diff --git a/src/components/calendars/index.js b/src/components/calendars/index.js
new file mode 100644
index 00000000000..076411f4739
--- /dev/null
+++ b/src/components/calendars/index.js
@@ -0,0 +1,253 @@
+/**
+* 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('./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 = {
+ chinese: '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 but we're going to give them
+// 7-day week ticks so start on our Sundays.
+// If anyone really cares we can customize the auto tick spacings for these calendars.
+var CANONICAL_SUNDAY = {
+ chinese: '2000-01-02',
+ coptic: '2000-01-03',
+ discworld: '2000-01-03',
+ 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 = {
+ chinese: ['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']
+};
+
+/*
+ * 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/colorbar/draw.js b/src/components/colorbar/draw.js
index a6c28680724..e31b43873f3 100644
--- a/src/components/colorbar/draw.js
+++ b/src/components/colorbar/draw.js
@@ -174,7 +174,8 @@ module.exports = function draw(gd, id) {
axisOptions = {
letter: 'y',
font: fullLayout.font,
- noHover: true
+ noHover: true,
+ calendar: fullLayout.calendar // not really necessary (yet?)
};
// Coerce w.r.t. Axes layoutAttributes:
diff --git a/src/components/rangeselector/button_attributes.js b/src/components/rangeselector/button_attributes.js
index 5ebfd7a7faa..db0f69cb017 100644
--- a/src/components/rangeselector/button_attributes.js
+++ b/src/components/rangeselector/button_attributes.js
@@ -33,7 +33,9 @@ module.exports = {
'*step* milliseconds back.',
'For example, with `step` set to *year* and `count` set to *1*',
'the range update shifts the start of the range back to',
- 'January 01 of the current year.'
+ 'January 01 of the current year.',
+ 'Month and year *todate* are currently available only',
+ 'for the built-in (Gregorian) calendar.'
].join(' ')
},
count: {
diff --git a/src/components/rangeselector/defaults.js b/src/components/rangeselector/defaults.js
index bb568c2479c..4f9d4efd18a 100644
--- a/src/components/rangeselector/defaults.js
+++ b/src/components/rangeselector/defaults.js
@@ -16,7 +16,7 @@ var buttonAttrs = require('./button_attributes');
var constants = require('./constants');
-module.exports = function handleDefaults(containerIn, containerOut, layout, counterAxes) {
+module.exports = function handleDefaults(containerIn, containerOut, layout, counterAxes, calendar) {
var selectorIn = containerIn.rangeselector || {},
selectorOut = containerOut.rangeselector = {};
@@ -24,7 +24,7 @@ module.exports = function handleDefaults(containerIn, containerOut, layout, coun
return Lib.coerce(selectorIn, selectorOut, attributes, attr, dflt);
}
- var buttons = buttonsDefaults(selectorIn, selectorOut);
+ var buttons = buttonsDefaults(selectorIn, selectorOut, calendar);
var visible = coerce('visible', buttons.length > 0);
if(!visible) return;
@@ -45,7 +45,7 @@ module.exports = function handleDefaults(containerIn, containerOut, layout, coun
coerce('borderwidth');
};
-function buttonsDefaults(containerIn, containerOut) {
+function buttonsDefaults(containerIn, containerOut, calendar) {
var buttonsIn = containerIn.buttons || [],
buttonsOut = containerOut.buttons = [];
@@ -63,7 +63,13 @@ function buttonsDefaults(containerIn, containerOut) {
var step = coerce('step');
if(step !== 'all') {
- coerce('stepmode');
+ if(calendar && calendar !== 'gregorian' && (step === 'month' || step === 'year')) {
+ buttonOut.stepmode = 'backward';
+ }
+ else {
+ coerce('stepmode');
+ }
+
coerce('count');
}
diff --git a/src/components/rangeselector/get_update_object.js b/src/components/rangeselector/get_update_object.js
index 71978d0a310..e01624b7c61 100644
--- a/src/components/rangeselector/get_update_object.js
+++ b/src/components/rangeselector/get_update_object.js
@@ -11,9 +11,6 @@
var d3 = require('d3');
-var Lib = require('../../lib');
-
-
module.exports = function getUpdateObject(axisLayout, buttonLayout) {
var axName = axisLayout._name;
var update = {};
@@ -33,7 +30,7 @@ module.exports = function getUpdateObject(axisLayout, buttonLayout) {
function getXRange(axisLayout, buttonLayout) {
var currentRange = axisLayout.range;
- var base = new Date(Lib.dateTime2ms(currentRange[1]));
+ var base = new Date(axisLayout.r2l(currentRange[1]));
var step = buttonLayout.step,
count = buttonLayout.count;
@@ -42,13 +39,13 @@ function getXRange(axisLayout, buttonLayout) {
switch(buttonLayout.stepmode) {
case 'backward':
- range0 = Lib.ms2DateTime(+d3.time[step].utc.offset(base, -count));
+ range0 = axisLayout.l2r(+d3.time[step].utc.offset(base, -count));
break;
case 'todate':
var base2 = d3.time[step].utc.offset(base, -count);
- range0 = Lib.ms2DateTime(+d3.time[step].utc.ceil(base2));
+ range0 = axisLayout.l2r(+d3.time[step].utc.ceil(base2));
break;
}
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/defaults.js b/src/components/rangeslider/defaults.js
index 01c07091e01..ab448bb022a 100644
--- a/src/components/rangeslider/defaults.js
+++ b/src/components/rangeslider/defaults.js
@@ -37,13 +37,12 @@ module.exports = function handleDefaults(layoutIn, layoutOut, axName, counterAxe
// Expand slider range to the axis range
if(containerOut.range && !axOut.autorange) {
+ // TODO: what if the ranges are reversed?
var outRange = containerOut.range,
- axRange = axOut.range,
- l2r = axOut.l2r,
- r2l = axOut.r2l;
+ axRange = axOut.range;
- outRange[0] = l2r(Math.min(r2l(outRange[0]), r2l(axRange[0])));
- outRange[1] = l2r(Math.max(r2l(outRange[1]), r2l(axRange[1])));
+ outRange[0] = axOut.l2r(Math.min(axOut.r2l(outRange[0]), axOut.r2l(axRange[0])));
+ outRange[1] = axOut.l2r(Math.max(axOut.r2l(outRange[1]), axOut.r2l(axRange[1])));
} else {
axOut._needsExpand = true;
}
diff --git a/src/components/rangeslider/draw.js b/src/components/rangeslider/draw.js
index 2a92d8d3091..e1f361acfe0 100644
--- a/src/components/rangeslider/draw.js
+++ b/src/components/rangeslider/draw.js
@@ -342,7 +342,8 @@ function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
xaxis: {
type: axisOpts.type,
domain: [0, 1],
- range: opts.range.slice()
+ range: opts.range.slice(),
+ calendar: axisOpts.calendar
},
width: opts._width,
height: opts._height,
@@ -352,7 +353,8 @@ function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
mockFigure.layout[oppAxisName] = {
domain: [0, 1],
- range: oppAxisOpts.range.slice()
+ range: oppAxisOpts.range.slice(),
+ calendar: oppAxisOpts.calendar
};
Plots.supplyDefaults(mockFigure);
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/dates.js b/src/lib/dates.js
index a486bc797ee..503acd6ef48 100644
--- a/src/lib/dates.js
+++ b/src/lib/dates.js
@@ -10,8 +10,10 @@
'use strict';
var d3 = require('d3');
+var isNumeric = require('fast-isnumeric');
var logError = require('./loggers').error;
+var mod = require('./mod');
var constants = require('../constants/numerical');
var BADNUM = constants.BADNUM;
@@ -19,12 +21,55 @@ var ONEDAY = constants.ONEDAY;
var ONEHOUR = constants.ONEHOUR;
var ONEMIN = constants.ONEMIN;
var ONESEC = constants.ONESEC;
+var EPOCHJD = constants.EPOCHJD;
-var DATETIME_REGEXP = /^\s*(-?\d\d\d\d|\d\d)(-(0?[1-9]|1[012])(-([0-3]?\d)([ Tt]([01]?\d|2[0-3])(:([0-5]\d)(:([0-5]\d(\.\d+)?))?(Z|z|[+\-]\d\d:?\d\d)?)?)?)?)?\s*$/m;
+var Registry = require('../registry');
+
+var utcFormat = d3.time.format.utc;
+
+var DATETIME_REGEXP = /^\s*(-?\d\d\d\d|\d\d)(-(\d?\d)(-(\d?\d)([ Tt]([01]?\d|2[0-3])(:([0-5]\d)(:([0-5]\d(\.\d+)?))?(Z|z|[+\-]\d\d:?\d\d)?)?)?)?)?\s*$/m;
+// special regex for chinese calendars to support yyyy-mmi-dd etc for intercalary months
+var DATETIME_REGEXP_CN = /^\s*(-?\d\d\d\d|\d\d)(-(\d?\di?)(-(\d?\d)([ Tt]([01]?\d|2[0-3])(:([0-5]\d)(:([0-5]\d(\.\d+)?))?(Z|z|[+\-]\d\d:?\d\d)?)?)?)?)?\s*$/m;
// for 2-digit years, the first year we map them onto
var YFIRST = new Date().getFullYear() - 70;
+function isWorldCalendar(calendar) {
+ return (
+ calendar &&
+ Registry.componentsRegistry.calendars &&
+ typeof calendar === 'string' && calendar !== 'gregorian'
+ );
+}
+
+/*
+ * dateTick0: get the canonical tick for this calendar
+ *
+ * bool sunday is for week ticks, shift it to a Sunday.
+ */
+exports.dateTick0 = function(calendar, sunday) {
+ 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) {
+ if(isWorldCalendar(calendar)) {
+ return Registry.getComponentMethod('calendars', 'DFLTRANGE')[calendar];
+ }
+ else {
+ return ['2000-01-01', '2001-01-01'];
+ }
+};
+
// is an object a javascript date?
exports.isJSDate = function(v) {
return typeof v === 'object' && v !== null && typeof v.getTime === 'function';
@@ -38,6 +83,7 @@ var MIN_MS, MAX_MS;
/**
* dateTime2ms - turn a date object or string s into milliseconds
* (relative to 1970-01-01, per javascript standard)
+ * optional calendar (string) to use a non-gregorian calendar
*
* Returns BADNUM if it doesn't find a date
*
@@ -62,6 +108,10 @@ var MIN_MS, MAX_MS;
* Note that we follow ISO 8601:2004: there *is* a year 0, which
* is 1BC/BCE, and -1===2BC etc.
*
+ * World calendars: not all of these *have* agreed extensions to this full range,
+ * if you have another calendar system but want a date range outside its validity,
+ * you can use a gregorian date string prefixed with 'G' or 'g'.
+ *
* Where to cut off 2-digit years between 1900s and 2000s?
* from http://support.microsoft.com/kb/244664:
* 1930-2029 (the most retro of all...)
@@ -83,8 +133,7 @@ var MIN_MS, MAX_MS;
* currently (2016) this range is:
* 1946-2045
*/
-
-exports.dateTime2ms = function(s) {
+exports.dateTime2ms = function(s, calendar) {
// first check if s is a date object
if(exports.isJSDate(s)) {
// Convert to the UTC milliseconds that give the same
@@ -96,24 +145,70 @@ exports.dateTime2ms = function(s) {
// otherwise only accept strings and numbers
if(typeof s !== 'string' && typeof s !== 'number') return BADNUM;
- var match = String(s).match(DATETIME_REGEXP);
+ s = String(s);
+
+ var isWorld = isWorldCalendar(calendar);
+
+ // to handle out-of-range dates in international calendars, accept
+ // 'G' as a prefix to force the built-in gregorian calendar.
+ var s0 = s.charAt(0);
+ if(isWorld && (s0 === 'G' || s0 === 'g')) {
+ s = s.substr(1);
+ calendar = '';
+ }
+
+ var isChinese = isWorld && calendar.substr(0, 7) === 'chinese';
+
+ var match = s.match(isChinese ? DATETIME_REGEXP_CN : DATETIME_REGEXP);
if(!match) return BADNUM;
var y = match[1],
- m = Number(match[3] || 1),
+ m = match[3] || '1',
d = Number(match[5] || 1),
H = Number(match[7] || 0),
M = Number(match[9] || 0),
S = Number(match[11] || 0);
+
+ if(isWorld) {
+ // disallow 2-digit years for world calendars
+ if(y.length === 2) return BADNUM;
+ y = Number(y);
+
+ var cDate;
+ try {
+ var calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar);
+ if(isChinese) {
+ var isIntercalary = m.charAt(m.length - 1) === 'i';
+ m = parseInt(m, 10);
+ cDate = calInstance.newDate(y, calInstance.toMonthIndex(y, m, isIntercalary), d);
+ }
+ else {
+ cDate = calInstance.newDate(y, Number(m), d);
+ }
+ }
+ catch(e) { return BADNUM; } // Invalid ... date
+
+ if(!cDate) return BADNUM;
+
+ return ((cDate.toJD() - EPOCHJD) * ONEDAY) +
+ (H * ONEHOUR) + (M * ONEMIN) + (S * ONESEC);
+ }
+
if(y.length === 2) {
y = (Number(y) + 2000 - YFIRST) % 100 + YFIRST;
}
else y = Number(y);
+ // new Date uses months from 0; subtract 1 here just so we
+ // don't have to do it again during the validity test below
+ m -= 1;
+
// javascript takes new Date(0..99,m,d) to mean 1900-1999, so
// to support years 0-99 we need to use setFullYear explicitly
- var date = new Date(Date.UTC(2000, m - 1, d, H, M));
+ // Note that 2000 is a leap year.
+ var date = new Date(Date.UTC(2000, m, d, H, M));
date.setUTCFullYear(y);
+ if(date.getUTCMonth() !== m) return BADNUM;
if(date.getUTCDate() !== d) return BADNUM;
return date.getTime() + S * ONESEC;
@@ -123,8 +218,8 @@ MIN_MS = exports.MIN_MS = exports.dateTime2ms('-9999');
MAX_MS = exports.MAX_MS = exports.dateTime2ms('9999-12-31 23:59:59.9999');
// is string s a date? (see above)
-exports.isDateTime = function(s) {
- return (exports.dateTime2ms(s) !== BADNUM);
+exports.isDateTime = function(s, calendar) {
+ return (exports.dateTime2ms(s, calendar) !== BADNUM);
};
// pad a number with zeroes, to given # of digits before the decimal point
@@ -143,21 +238,57 @@ function lpad(val, digits) {
var NINETYDAYS = 90 * ONEDAY;
var THREEHOURS = 3 * ONEHOUR;
var FIVEMIN = 5 * ONEMIN;
-exports.ms2DateTime = function(ms, r) {
+exports.ms2DateTime = function(ms, r, calendar) {
if(typeof ms !== 'number' || !(ms >= MIN_MS && ms <= MAX_MS)) return BADNUM;
if(!r) r = 0;
- var msecTenths = Math.round(((ms % 1) + 1) * 10) % 10,
- d = new Date(Math.round(ms - msecTenths / 10)),
- dateStr = d3.time.format.utc('%Y-%m-%d')(d),
+ var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10),
+ msRounded = Math.round(ms - msecTenths / 10),
+ dateStr, h, m, s, msec10, d;
+
+ if(isWorldCalendar(calendar)) {
+ var dateJD = Math.floor(msRounded / ONEDAY) + EPOCHJD,
+ timeMs = Math.floor(mod(ms, ONEDAY));
+ try {
+ 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
+ dateStr = utcFormat('G%Y-%m-%d')(new Date(msRounded));
+ }
+
+ // yyyy does NOT guarantee 4-digit years. YYYY mostly does, but does
+ // other things for a few calendars, so we can't trust it. Just pad
+ // it manually (after the '-' if there is one)
+ if(dateStr.charAt(0) === '-') {
+ while(dateStr.length < 11) dateStr = '-0' + dateStr.substr(1);
+ }
+ else {
+ while(dateStr.length < 10) dateStr = '0' + dateStr;
+ }
+
+ // TODO: if this is faster, we could use this block for extracting
+ // the time components of regular gregorian too
+ h = (r < NINETYDAYS) ? Math.floor(timeMs / ONEHOUR) : 0;
+ m = (r < NINETYDAYS) ? Math.floor((timeMs % ONEHOUR) / ONEMIN) : 0;
+ s = (r < THREEHOURS) ? Math.floor((timeMs % ONEMIN) / ONESEC) : 0;
+ msec10 = (r < FIVEMIN) ? (timeMs % ONESEC) * 10 + msecTenths : 0;
+ }
+ else {
+ d = new Date(msRounded);
+
+ dateStr = utcFormat('%Y-%m-%d')(d);
+
// <90 days: add hours and minutes - never *only* add hours
- h = (r < NINETYDAYS) ? d.getUTCHours() : 0,
- m = (r < NINETYDAYS) ? d.getUTCMinutes() : 0,
+ h = (r < NINETYDAYS) ? d.getUTCHours() : 0;
+ m = (r < NINETYDAYS) ? d.getUTCMinutes() : 0;
// <3 hours: add seconds
- s = (r < THREEHOURS) ? d.getUTCSeconds() : 0,
+ s = (r < THREEHOURS) ? d.getUTCSeconds() : 0;
// <5 minutes: add ms (plus one extra digit, this is msec*10)
msec10 = (r < FIVEMIN) ? d.getUTCMilliseconds() * 10 + msecTenths : 0;
+ }
return includeTime(dateStr, h, m, s, msec10);
};
@@ -171,7 +302,7 @@ exports.ms2DateTime = function(ms, r) {
exports.ms2DateTimeLocal = function(ms) {
if(!(ms >= MIN_MS + ONEDAY && ms <= MAX_MS - ONEDAY)) return BADNUM;
- var msecTenths = Math.round(((ms % 1) + 1) * 10) % 10,
+ var msecTenths = Math.floor(mod(ms + 0.05, 1) * 10),
d = new Date(Math.round(ms - msecTenths / 10)),
dateStr = d3.time.format('%Y-%m-%d')(d),
h = d.getHours(),
@@ -204,17 +335,265 @@ function includeTime(dateStr, h, m, s, msec10) {
// normalize date format to date string, in case it starts as
// a Date object or milliseconds
// optional dflt is the return value if cleaning fails
-exports.cleanDate = function(v, dflt) {
+exports.cleanDate = function(v, dflt, calendar) {
if(exports.isJSDate(v) || typeof v === 'number') {
+ // do not allow milliseconds (old) or jsdate objects (inherently
+ // described as gregorian dates) with world calendars
+ if(isWorldCalendar(calendar)) {
+ logError('JS Dates and milliseconds are incompatible with world calendars', v);
+ return dflt;
+ }
+
// NOTE: if someone puts in a year as a number rather than a string,
// this will mistakenly convert it thinking it's milliseconds from 1970
// that is: '2012' -> Jan. 1, 2012, but 2012 -> 2012 epoch milliseconds
v = exports.ms2DateTimeLocal(+v);
if(!v && dflt !== undefined) return dflt;
}
- else if(!exports.isDateTime(v)) {
+ else if(!exports.isDateTime(v, calendar)) {
logError('unrecognized date', v);
return dflt;
}
return v;
};
+
+/*
+ * Date formatting for ticks and hovertext
+ */
+
+/*
+ * modDateFormat: Support world calendars, and add one item to
+ * d3's vocabulary:
+ * %{n}f where n is the max number of digits of fractional seconds
+ */
+var fracMatch = /%(\d?)f/g;
+function modDateFormat(fmt, x, calendar) {
+ var fm = fmt.match(fracMatch),
+ d = new Date(x);
+ if(fm) {
+ var digits = Math.min(+fm[1] || 6, 6),
+ fracSecs = String((x / 1000 % 1) + 2.0000005)
+ .substr(2, digits).replace(/0+$/, '') || '0';
+ fmt = fmt.replace(fracMatch, fracSecs);
+ }
+ if(isWorldCalendar(calendar)) {
+ try {
+ fmt = Registry.getComponentMethod('calendars', 'worldCalFmt')(fmt, x, calendar);
+ }
+ catch(e) {
+ return 'Invalid';
+ }
+ }
+ return utcFormat(fmt)(d);
+}
+
+/*
+ * formatTime: create a time string from:
+ * x: milliseconds
+ * tr: tickround ('M', 'S', or # digits)
+ * only supports UTC times (where every day is 24 hours and 0 is at midnight)
+ */
+function formatTime(x, tr) {
+ var timePart = mod(x, ONEDAY);
+
+ var timeStr = lpad(Math.floor(timePart / ONEHOUR), 2) + ':' +
+ lpad(mod(Math.floor(timePart / ONEMIN), 60), 2);
+
+ if(tr !== 'M') {
+ if(!isNumeric(tr)) tr = 0; // should only be 'S'
+ timeStr += ':' + String(100 + d3.round(mod(x / ONESEC, 60), tr)).substr(1);
+ }
+ return timeStr;
+}
+
+var yearFormat = utcFormat('%Y'),
+ monthFormat = utcFormat('%b %Y'),
+ dayFormat = utcFormat('%b %-d'),
+ yearMonthDayFormat = utcFormat('%b %-d, %Y');
+
+function yearFormatWorld(cDate) { return cDate.formatDate('yyyy'); }
+function monthFormatWorld(cDate) { return cDate.formatDate('M yyyy'); }
+function dayFormatWorld(cDate) { return cDate.formatDate('M d'); }
+function yearMonthDayFormatWorld(cDate) { return cDate.formatDate('M d, yyyy'); }
+
+/*
+ * formatDate: turn a date into tick or hover label text.
+ *
+ * x: milliseconds, the value to convert
+ * fmt: optional, an explicit format string (d3 format, even for world calendars)
+ * tr: tickround ('y', 'm', 'd', 'M', 'S', or # digits)
+ * used if no explicit fmt is provided
+ * calendar: optional string, the world calendar system to use
+ *
+ * returns the date/time as a string, potentially with the leading portion
+ * on a separate line (after '\n')
+ * Note that this means if you provide an explicit format which includes '\n'
+ * the axis may choose to strip things after it when they don't change from
+ * one tick to the next (as it does with automatic formatting)
+ */
+exports.formatDate = function(x, fmt, tr, calendar) {
+ var headStr,
+ dateStr;
+
+ calendar = isWorldCalendar(calendar) && calendar;
+
+ if(fmt) return modDateFormat(fmt, x, calendar);
+
+ if(calendar) {
+ try {
+ var dateJD = Math.floor((x + 0.05) / ONEDAY) + EPOCHJD,
+ cDate = Registry.getComponentMethod('calendars', 'getCal')(calendar)
+ .fromJD(dateJD);
+
+ if(tr === 'y') dateStr = yearFormatWorld(cDate);
+ else if(tr === 'm') dateStr = monthFormatWorld(cDate);
+ else if(tr === 'd') {
+ headStr = yearFormatWorld(cDate);
+ dateStr = dayFormatWorld(cDate);
+ }
+ else {
+ headStr = yearMonthDayFormatWorld(cDate);
+ dateStr = formatTime(x, tr);
+ }
+ }
+ catch(e) { return 'Invalid'; }
+ }
+ else {
+ var d = new Date(x);
+
+ if(tr === 'y') dateStr = yearFormat(d);
+ else if(tr === 'm') dateStr = monthFormat(d);
+ else if(tr === 'd') {
+ headStr = yearFormat(d);
+ dateStr = dayFormat(d);
+ }
+ else {
+ headStr = yearMonthDayFormat(d);
+ dateStr = formatTime(x, tr);
+ }
+ }
+
+ return dateStr + (headStr ? '\n' + headStr : '');
+};
+
+/*
+ * incrementMonth: make a new milliseconds value from the given one,
+ * having changed the month
+ *
+ * special case for world calendars: multiples of 12 are treated as years,
+ * even for calendar systems that don't have (always or ever) 12 months/year
+ * TODO: perhaps we need a different code for year increments to support this?
+ *
+ * ms (number): the initial millisecond value
+ * dMonth (int): the (signed) number of months to shift
+ * calendar (string): the calendar system to use
+ *
+ * changing month does not (and CANNOT) always preserve day, since
+ * months have different lengths. The worst example of this is:
+ * d = new Date(1970,0,31); d.setMonth(1) -> Feb 31 turns into Mar 3
+ *
+ * But we want to be able to iterate over the last day of each month,
+ * regardless of what its number is.
+ * So shift 3 days forward, THEN set the new month, then unshift:
+ * 1/31 -> 2/28 (or 29) -> 3/31 -> 4/30 -> ...
+ *
+ * Note that odd behavior still exists if you start from the 26th-28th:
+ * 1/28 -> 2/28 -> 3/31
+ * but at least you can't shift any dates into the wrong month,
+ * and ticks on these days incrementing by month would be very unusual
+ */
+var THREEDAYS = 3 * ONEDAY;
+exports.incrementMonth = function(ms, dMonth, calendar) {
+ calendar = isWorldCalendar(calendar) && calendar;
+
+ // pull time out and operate on pure dates, then add time back at the end
+ // this gives maximum precision - not that we *normally* care if we're
+ // incrementing by month, but better to be safe!
+ var timeMs = mod(ms, ONEDAY);
+ ms = Math.round(ms - timeMs);
+
+ if(calendar) {
+ try {
+ var dateJD = Math.round(ms / ONEDAY) + EPOCHJD,
+ calInstance = Registry.getComponentMethod('calendars', 'getCal')(calendar),
+ cDate = calInstance.fromJD(dateJD);
+
+ if(dMonth % 12) calInstance.add(cDate, dMonth, 'm');
+ else calInstance.add(cDate, dMonth / 12, 'y');
+
+ return (cDate.toJD() - EPOCHJD) * ONEDAY + timeMs;
+ }
+ catch(e) {
+ logError('invalid ms ' + ms + ' in calendar ' + calendar);
+ // then keep going in gregorian even though the result will be 'Invalid'
+ }
+ }
+
+ var y = new Date(ms + THREEDAYS);
+ return y.setUTCMonth(y.getUTCMonth() + dMonth) + timeMs - THREEDAYS;
+};
+
+/*
+ * findExactDates: what fraction of data is exact days, months, or years?
+ *
+ * data: array of millisecond values
+ * calendar (string) the calendar to test against
+ */
+exports.findExactDates = function(data, calendar) {
+ var exactYears = 0,
+ exactMonths = 0,
+ exactDays = 0,
+ blankCount = 0,
+ d,
+ di;
+
+ var calInstance = (
+ isWorldCalendar(calendar) &&
+ Registry.getComponentMethod('calendars', 'getCal')(calendar)
+ );
+
+ for(var i = 0; i < data.length; i++) {
+ di = data[i];
+
+ // not date data at all
+ if(!isNumeric(di)) {
+ blankCount ++;
+ continue;
+ }
+
+ // not an exact date
+ if(di % ONEDAY) continue;
+
+ if(calInstance) {
+ try {
+ d = calInstance.fromJD(di / ONEDAY + EPOCHJD);
+ if(d.day() === 1) {
+ if(d.month() === 1) exactYears++;
+ else exactMonths++;
+ }
+ else exactDays++;
+ }
+ catch(e) {
+ // invalid date in this calendar - ignore it here.
+ }
+ }
+ else {
+ d = new Date(di);
+ if(d.getUTCDate() === 1) {
+ if(d.getUTCMonth() === 0) exactYears++;
+ else exactMonths++;
+ }
+ else exactDays++;
+ }
+ }
+ exactMonths += exactYears;
+ exactDays += exactMonths;
+
+ var dataCount = data.length - blankCount;
+
+ return {
+ exactYears: exactYears / dataCount,
+ exactMonths: exactMonths / dataCount,
+ exactDays: exactDays / dataCount
+ };
+};
diff --git a/src/lib/index.js b/src/lib/index.js
index 023896b0e80..5a66ac4c01b 100644
--- a/src/lib/index.js
+++ b/src/lib/index.js
@@ -16,6 +16,7 @@ var lib = module.exports = {};
lib.nestedProperty = require('./nested_property');
lib.isPlainObject = require('./is_plain_object');
lib.isArray = require('./is_array');
+lib.mod = require('./mod');
var coerceModule = require('./coerce');
lib.valObjects = coerceModule.valObjects;
@@ -31,6 +32,11 @@ lib.ms2DateTime = datesModule.ms2DateTime;
lib.ms2DateTimeLocal = datesModule.ms2DateTimeLocal;
lib.cleanDate = datesModule.cleanDate;
lib.isJSDate = datesModule.isJSDate;
+lib.formatDate = datesModule.formatDate;
+lib.incrementMonth = datesModule.incrementMonth;
+lib.dateTick0 = datesModule.dateTick0;
+lib.dfltRange = datesModule.dfltRange;
+lib.findExactDates = datesModule.findExactDates;
lib.MIN_MS = datesModule.MIN_MS;
lib.MAX_MS = datesModule.MAX_MS;
@@ -135,6 +141,22 @@ lib.identity = function(d) { return d; };
// minor convenience helper
lib.noop = function() {};
+/*
+ * simpleMap: alternative to Array.map that only
+ * passes on the element and up to 2 extra args you
+ * provide (but not the array index or the whole array)
+ *
+ * array: the array to map it to
+ * func: the function to apply
+ * x1, x2: optional extra args
+ */
+lib.simpleMap = function(array, func, x1, x2) {
+ var len = array.length,
+ out = new Array(len);
+ for(var i = 0; i < len; i++) out[i] = func(array[i], x1, x2);
+ return out;
+};
+
// random string generator
lib.randstr = function randstr(existing, bits, base) {
/*
diff --git a/src/lib/mod.js b/src/lib/mod.js
new file mode 100644
index 00000000000..0ce9323b28f
--- /dev/null
+++ b/src/lib/mod.js
@@ -0,0 +1,18 @@
+/**
+* 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';
+
+/**
+ * sanitized modulus function that always returns in the range [0, d)
+ * rather than (-d, 0] if v is negative
+ */
+module.exports = function mod(v, d) {
+ var out = v % d;
+ return out < 0 ? out + d : out;
+};
diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js
index 8d21c672bdd..2026234ae8d 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);
@@ -1295,7 +1295,8 @@ function _restyle(gd, aobj, _traces) {
'domain.x[0]', 'domain.x[1]', 'domain.y[0]', 'domain.y[1]',
'tilt', 'tiltaxis', 'depth', 'direction', 'rotation', 'pull',
'line.showscale', 'line.cauto', 'line.autocolorscale', 'line.reversescale',
- 'marker.line.showscale', 'marker.line.cauto', 'marker.line.autocolorscale', 'marker.line.reversescale'
+ 'marker.line.showscale', 'marker.line.cauto', 'marker.line.autocolorscale', 'marker.line.reversescale',
+ 'xcalendar', 'ycalendar'
];
for(i = 0; i < traces.length; i++) {
@@ -2000,6 +2001,7 @@ function _relayout(gd, aobj) {
p.parts[1] === 'rangemode' ||
p.parts[1] === 'type' ||
p.parts[1] === 'domain' ||
+ ai.indexOf('calendar') !== -1 ||
ai.match(/^(bar|box|font)/)) {
flags.docalc = true;
}
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/axes.js b/src/plots/cartesian/axes.js
index 9f9b7da42d6..44556f88eb3 100644
--- a/src/plots/cartesian/axes.js
+++ b/src/plots/cartesian/axes.js
@@ -27,6 +27,7 @@ var ONEDAY = constants.ONEDAY;
var ONEHOUR = constants.ONEHOUR;
var ONEMIN = constants.ONEMIN;
var ONESEC = constants.ONESEC;
+var BADNUM = constants.BADNUM;
var axes = module.exports = {};
@@ -121,7 +122,7 @@ axes.coercePosition = function(containerOut, gd, coerce, axRef, attr, dflt) {
}
}
else if(ax.type === 'date') {
- containerOut[attr] = Lib.cleanDate(pos);
+ containerOut[attr] = Lib.cleanDate(pos, BADNUM, ax.calendar);
return;
}
}
@@ -220,7 +221,7 @@ axes.getAutoRange = function(ax) {
axReverse = false;
if(ax.range) {
- var rng = ax.range.map(ax.r2l);
+ var rng = Lib.simpleMap(ax.range, ax.r2l);
axReverse = rng[1] < rng[0];
}
@@ -313,7 +314,7 @@ axes.getAutoRange = function(ax) {
// maintain reversal
if(axReverse) newRange.reverse();
- return newRange.map(ax.l2r || Number);
+ return Lib.simpleMap(newRange, ax.l2r || Number);
};
axes.doAutoRange = function(ax) {
@@ -485,10 +486,12 @@ axes.expand = function(ax, data, options) {
};
-axes.autoBin = function(data, ax, nbins, is2d) {
+axes.autoBin = function(data, ax, nbins, is2d, calendar) {
var dataMin = Lib.aggNums(Math.min, null, data),
dataMax = Lib.aggNums(Math.max, null, data);
+ if(!calendar) calendar = ax.calendar;
+
if(ax.type === 'category') {
return {
start: dataMin - 0.5,
@@ -518,23 +521,21 @@ axes.autoBin = function(data, ax, nbins, is2d) {
if(ax.type === 'log') {
dummyAx = {
type: 'linear',
- range: [dataMin, dataMax],
- r2l: Number
+ range: [dataMin, dataMax]
};
}
else {
dummyAx = {
type: ax.type,
- // conversion below would be ax.c2r but that's only different from l2r
- // for log, and this is the only place (so far?) we would want c2r.
- range: [dataMin, dataMax].map(ax.l2r),
- r2l: ax.r2l
+ range: Lib.simpleMap([dataMin, dataMax], ax.c2r, 0, calendar),
+ calendar: calendar
};
}
+ axes.setConvert(dummyAx);
axes.autoTicks(dummyAx, size0);
var binStart = axes.tickIncrement(
- axes.tickFirst(dummyAx), dummyAx.dtick, 'reverse'),
+ axes.tickFirst(dummyAx), dummyAx.dtick, 'reverse', calendar),
binEnd;
// check for too many data points right at the edges of bins
@@ -553,20 +554,20 @@ axes.autoBin = function(data, ax, nbins, is2d) {
// we bin it on a linear axis (which one could argue against, but that's
// a separate issue)
if(dummyAx.dtick.charAt(0) === 'M') {
- binStart = autoShiftMonthBins(binStart, data, dummyAx.dtick, dataMin);
+ binStart = autoShiftMonthBins(binStart, data, dummyAx.dtick, dataMin, calendar);
}
// calculate the endpoint for nonlinear ticks - you have to
// just increment until you're done
binEnd = binStart;
while(binEnd <= dataMax) {
- binEnd = axes.tickIncrement(binEnd, dummyAx.dtick);
+ binEnd = axes.tickIncrement(binEnd, dummyAx.dtick, false, calendar);
}
}
return {
- start: ax.c2r(binStart),
- end: ax.c2r(binEnd),
+ start: ax.c2r(binStart, 0, calendar),
+ end: ax.c2r(binEnd, 0, calendar),
size: dummyAx.dtick
};
};
@@ -618,59 +619,22 @@ function autoShiftNumericBins(binStart, data, ax, dataMin, dataMax) {
}
-function autoShiftMonthBins(binStart, data, dtick, dataMin) {
- var exactYears = 0,
- exactMonths = 0,
- exactDays = 0,
- blankCount = 0,
- dataCount,
- di,
- d,
- year,
- month;
-
- for(var i = 0; i < data.length; i++) {
- di = data[i];
- if(!isNumeric(di)) {
- blankCount ++;
- continue;
- }
- d = new Date(di),
- year = d.getUTCFullYear();
- if(di === Date.UTC(year, 0, 1)) {
- exactYears ++;
- }
- else {
- month = d.getUTCMonth();
- if(di === Date.UTC(year, month, 1)) {
- exactMonths ++;
- }
- else if(di === Date.UTC(year, month, d.getUTCDate())) {
- exactDays ++;
- }
- }
- }
-
- dataCount = data.length - blankCount;
-
- // include bigger exact dates in the smaller ones
- exactMonths += exactYears;
- exactDays += exactMonths;
-
- // unmber of data points that needs to be an exact value
+function autoShiftMonthBins(binStart, data, dtick, dataMin, calendar) {
+ var stats = Lib.findExactDates(data, calendar);
+ // number of data points that needs to be an exact value
// to shift that increment to (near) the bin center
- var threshold = 0.8 * dataCount;
+ var threshold = 0.8;
- if(exactDays > threshold) {
+ if(stats.exactDays > threshold) {
var numMonths = Number(dtick.substr(1));
- if((exactYears > threshold) && (numMonths % 12 === 0)) {
+ if((stats.exactYears > threshold) && (numMonths % 12 === 0)) {
// The exact middle of a non-leap-year is 1.5 days into July
// so if we start the bins here, all but leap years will
// get hover-labeled as exact years.
binStart = axes.tickIncrement(binStart, 'M6', 'reverse') + ONEDAY * 1.5;
}
- else if(exactMonths > threshold) {
+ else if(stats.exactMonths > threshold) {
// Months are not as clean, but if we shift half the *longest*
// month (31/2 days) then 31-day months will get labeled exactly
// and shorter months will get labeled with the correct month
@@ -700,7 +664,7 @@ function autoShiftMonthBins(binStart, data, dtick, dataMin) {
// in any case, set tickround to # of digits to round tick labels to,
// or codes to this effect for log and date scales
axes.calcTicks = function calcTicks(ax) {
- var rng = ax.range.map(ax.r2l);
+ var rng = Lib.simpleMap(ax.range, ax.r2l);
// calculate max number of (auto) ticks to display based on plot size
if(ax.tickmode === 'auto' || !ax.dtick) {
@@ -757,7 +721,7 @@ axes.calcTicks = function calcTicks(ax) {
}
for(var x = ax._tmin;
(axrev) ? (x >= endtick) : (x <= endtick);
- x = axes.tickIncrement(x, ax.dtick, axrev)) {
+ x = axes.tickIncrement(x, ax.dtick, axrev, ax.calendar)) {
vals.push(x);
// prevent infinite loops
@@ -787,7 +751,7 @@ function arrayTicks(ax) {
var vals = ax.tickvals,
text = ax.ticktext,
ticksOut = new Array(vals.length),
- rng = ax.range.map(ax.r2l),
+ rng = Lib.simpleMap(ax.range, ax.r2l),
r0expanded = rng[0] * 1.0001 - rng[1] * 0.0001,
r1expanded = rng[1] * 1.0001 - rng[0] * 0.0001,
tickMin = Math.min(r0expanded, r1expanded),
@@ -857,7 +821,7 @@ axes.autoTicks = function(ax, roughDTick) {
var base;
if(ax.type === 'date') {
- ax.tick0 = '2000-01-01';
+ ax.tick0 = Lib.dateTick0(ax.calendar);
// the criteria below are all based on the rough spacing we calculate
// being > half of the final unit - so precalculate twice the rough val
var roughX2 = 2 * roughDTick;
@@ -876,7 +840,7 @@ axes.autoTicks = function(ax, roughDTick) {
// get week ticks on sunday
// this will also move the base tick off 2000-01-01 if dtick is
// 2 or 3 days... but that's a weird enough case that we'll ignore it.
- ax.tick0 = '2000-01-02';
+ ax.tick0 = Lib.dateTick0(ax.calendar, true);
}
else if(roughX2 > ONEHOUR) {
ax.dtick = roundDTick(roughDTick, ONEHOUR, roundBase24);
@@ -895,7 +859,7 @@ axes.autoTicks = function(ax, roughDTick) {
}
else if(ax.type === 'log') {
ax.tick0 = 0;
- var rng = ax.range.map(ax.r2l);
+ var rng = Lib.simpleMap(ax.range, ax.r2l);
if(roughDTick > 0.7) {
// only show powers of 10
@@ -960,9 +924,9 @@ function autoTickRound(ax) {
// If tick0 is unusual, give tickround a bit more information
// not necessarily *all* the information in tick0 though, if it's really odd
// minimal string length for tick0: 'd' is 10, 'M' is 16, 'S' is 19
- // take off a leading minus (year < 0 so length is consistent)
- var tick0ms = Lib.dateTime2ms(ax.tick0),
- tick0str = Lib.ms2DateTime(tick0ms).replace(/^-/, ''),
+ // take off a leading minus (year < 0) and i (intercalary month) so length is consistent
+ var tick0ms = ax.r2l(ax.tick0),
+ tick0str = ax.l2r(tick0ms).replace(/(^-|i)/g, ''),
tick0len = tick0str.length;
if(String(dtick).charAt(0) === 'M') {
@@ -975,9 +939,10 @@ function autoTickRound(ax) {
else if((dtick >= ONEMIN && tick0len <= 16) || (dtick >= ONEHOUR)) ax._tickround = 'M';
else if((dtick >= ONESEC && tick0len <= 19) || (dtick >= ONEMIN)) ax._tickround = 'S';
else {
+ // tickround is a number of digits of fractional seconds
// of any two adjacent ticks, at least one will have the maximum fractional digits
// of all possible ticks - so take the max. length of tick0 and the next one
- var tick1len = Lib.ms2DateTime(tick0ms + dtick).replace(/^-/, '').length;
+ var tick1len = ax.l2r(tick0ms + dtick).replace(/^-/, '').length;
ax._tickround = Math.max(tick0len, tick1len) - 20;
}
}
@@ -1008,36 +973,18 @@ function autoTickRound(ax) {
// for pure powers of 10
// numeric ticks always have constant differences, other datetime ticks
// can all be calculated as constant number of milliseconds
-var THREEDAYS = 3 * ONEDAY;
-axes.tickIncrement = function(x, dtick, axrev) {
+axes.tickIncrement = function(x, dtick, axrev, calendar) {
var axSign = axrev ? -1 : 1;
- // includes all dates smaller than month, and pure 10^n in log
+ // includes linear, all dates smaller than month, and pure 10^n in log
if(isNumeric(dtick)) return x + axSign * dtick;
+ // everything else is a string, one character plus a number
var tType = dtick.charAt(0),
dtSigned = axSign * Number(dtick.substr(1));
- // Dates: months (or years)
- if(tType === 'M') {
- /*
- * set(UTC)Month does not (and CANNOT) always preserve day, since
- * months have different lengths. The worst example of this is:
- * d = new Date(1970,0,31); d.setMonth(1) -> Feb 31 turns into Mar 3
- *
- * But we want to be able to iterate over the last day of each month,
- * regardless of what its number is.
- * So shift 3 days forward, THEN set the new month, then unshift:
- * 1/31 -> 2/28 (or 29) -> 3/31 -> 4/30 -> ...
- *
- * Note that odd behavior still exists if you start from the 26th-28th:
- * 1/28 -> 2/28 -> 3/31
- * but at least you can't shift any dates into the wrong month,
- * and ticks on these days incrementing by month would be very unusual
- */
- var y = new Date(x + THREEDAYS);
- return y.setUTCMonth(y.getUTCMonth() + dtSigned) - THREEDAYS;
- }
+ // Dates: months (or years - see Lib.incrementMonth)
+ if(tType === 'M') return Lib.incrementMonth(x, dtSigned, calendar);
// Log scales: Linear, Digits
else if(tType === 'L') return Math.log(Math.pow(10, x) + dtSigned) / Math.LN10;
@@ -1047,7 +994,7 @@ axes.tickIncrement = function(x, dtick, axrev) {
else if(tType === 'D') {
var tickset = (dtick === 'D2') ? roundLog2 : roundLog1,
x2 = x + axSign * 0.01,
- frac = Lib.roundUp(mod(x2, 1), tickset, axrev);
+ frac = Lib.roundUp(Lib.mod(x2, 1), tickset, axrev);
return Math.floor(x2) +
Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10;
@@ -1058,7 +1005,7 @@ axes.tickIncrement = function(x, dtick, axrev) {
// calculate the first tick on an axis
axes.tickFirst = function(ax) {
var r2l = ax.r2l || Number,
- rng = ax.range.map(r2l),
+ rng = Lib.simpleMap(ax.range, r2l),
axrev = rng[1] < rng[0],
sRound = axrev ? Math.floor : Math.ceil,
// add a tiny extra bit to make sure we get ticks
@@ -1078,24 +1025,32 @@ axes.tickFirst = function(ax) {
}
var tType = dtick.charAt(0),
- dtNum = Number(dtick.substr(1)),
- t0,
- mdif,
- t1;
+ dtNum = Number(dtick.substr(1));
// Dates: months (or years)
if(tType === 'M') {
- t0 = new Date(tick0);
- r0 = new Date(r0);
- mdif = (r0.getUTCFullYear() - t0.getUTCFullYear()) * 12 +
- r0.getUTCMonth() - t0.getUTCMonth();
- t1 = t0.setUTCMonth(t0.getUTCMonth() +
- (Math.round(mdif / dtNum) + (axrev ? 1 : -1)) * dtNum);
-
- while(axrev ? t1 > r0 : t1 < r0) {
- t1 = axes.tickIncrement(t1, dtick, axrev);
+ var cnt = 0,
+ t0 = tick0,
+ t1,
+ mult,
+ newDTick;
+
+ // This algorithm should work for *any* nonlinear (but close to linear!)
+ // tick spacing. Limit to 10 iterations, for gregorian months it's normally <=3.
+ while(cnt < 10) {
+ t1 = axes.tickIncrement(t0, dtick, axrev, ax.calendar);
+ if((t1 - r0) * (t0 - r0) <= 0) {
+ // t1 and t0 are on opposite sides of r0! we've succeeded!
+ if(axrev) return Math.min(t0, t1);
+ return Math.max(t0, t1);
+ }
+ mult = (r0 - ((t0 + t1) / 2)) / (t1 - t0);
+ newDTick = tType + ((Math.abs(Math.round(mult)) || 1) * dtNum);
+ t0 = axes.tickIncrement(t0, newDTick, mult < 0 ? !axrev : axrev, ax.calendar);
+ cnt++;
}
- return t1;
+ Lib.error('tickFirst did not converge', ax);
+ return t0;
}
// Log scales: Linear, Digits
@@ -1105,7 +1060,7 @@ axes.tickFirst = function(ax) {
}
else if(tType === 'D') {
var tickset = (dtick === 'D2') ? roundLog2 : roundLog1,
- frac = Lib.roundUp(mod(r0, 1), tickset, axrev);
+ frac = Lib.roundUp(Lib.mod(r0, 1), tickset, axrev);
return Math.floor(r0) +
Math.log(d3.round(Math.pow(10, frac), 1)) / Math.LN10;
@@ -1113,32 +1068,6 @@ axes.tickFirst = function(ax) {
else throw 'unrecognized dtick ' + String(dtick);
};
-var utcFormat = d3.time.format.utc,
- yearFormat = utcFormat('%Y'),
- monthFormat = utcFormat('%b %Y'),
- dayFormat = utcFormat('%b %-d'),
- yearMonthDayFormat = utcFormat('%b %-d, %Y'),
- minuteFormat = utcFormat('%H:%M'),
- secondFormat = utcFormat(':%S');
-
-// add one item to d3's vocabulary:
-// %{n}f where n is the max number of digits
-// of fractional seconds
-var fracMatch = /%(\d?)f/g;
-function modDateFormat(fmt, x) {
- var fm = fmt.match(fracMatch),
- d = new Date(x);
- if(fm) {
- var digits = Math.min(+fm[1] || 6, 6),
- fracSecs = String((x / 1000 % 1) + 2.0000005)
- .substr(2, digits).replace(/0+$/, '') || '0';
- return utcFormat(fmt.replace(fracMatch, fracSecs))(d);
- }
- else {
- return utcFormat(fmt)(d);
- }
-}
-
// draw the text for one tick.
// px,py are the location on gd.paper
// prefix is there so the x axis ticks can be dropped a line
@@ -1154,7 +1083,7 @@ axes.tickText = function(ax, x, hover) {
tickVal2l = ax.type === 'category' ? ax.d2l_noadd : ax.d2l;
if(arrayMode && Array.isArray(ax.ticktext)) {
- var rng = ax.range.map(ax.r2l),
+ var rng = Lib.simpleMap(ax.range, ax.r2l),
minDiff = Math.abs(rng[1] - rng[0]) / 10000;
for(i = 0; i < ax.ticktext.length; i++) {
if(Math.abs(x - tickVal2l(ax.tickvals[i])) < minDiff) break;
@@ -1208,76 +1137,59 @@ function tickTextObj(ax, x, text) {
}
function formatDate(ax, out, hover, extraPrecision) {
- var x = out.x,
- tr = ax._tickround,
- d = new Date(x),
- // headPart completes the full date info, to be included
- // with only the first tick or if any info before what's
- // shown has changed
- headPart,
- tt;
- if(hover && ax.hoverformat) {
- tt = modDateFormat(ax.hoverformat, x);
- }
- else if(ax.tickformat) {
- tt = modDateFormat(ax.tickformat, x);
- // TODO: potentially hunt for ways to automatically add more
- // precision to the hover text?
+ var tr = ax._tickround,
+ fmt = (hover && ax.hoverformat) || ax.tickformat;
+
+ if(extraPrecision) {
+ // second or sub-second precision: extra always shows max digits.
+ // for other fields, extra precision just adds one field.
+ if(isNumeric(tr)) tr = 4;
+ else tr = {y: 'm', m: 'd', d: 'M', M: 'S', S: 4}[tr];
}
- else {
- if(extraPrecision) {
- if(isNumeric(tr)) tr += 2;
- else tr = {y: 'm', m: 'd', d: 'M', M: 'S', S: 2}[tr];
- }
- if(tr === 'y') tt = yearFormat(d);
- else if(tr === 'm') tt = monthFormat(d);
- else {
- if(tr === 'd') {
- headPart = yearFormat(d);
- tt = dayFormat(d);
- }
- else {
- headPart = yearMonthDayFormat(d);
-
- tt = minuteFormat(d);
- if(tr !== 'M') {
- tt += secondFormat(d);
- if(tr !== 'S') {
- tt += numFormat(d3.round(mod(x / 1000, 1), 4), ax, 'none', hover)
- .substr(1);
- }
- }
- }
- }
+ var dateStr = Lib.formatDate(out.x, fmt, tr, ax.calendar),
+ headStr;
+
+ var splitIndex = dateStr.indexOf('\n');
+ if(splitIndex !== -1) {
+ headStr = dateStr.substr(splitIndex + 1);
+ dateStr = dateStr.substr(0, splitIndex);
}
- if(hover || ax.tickmode === 'array') {
- // we get extra precision in array mode or hover,
- // but it may be useless, strip it off
- if(tt === '00:00:00' || tt === '00:00') {
- tt = headPart;
- headPart = '';
+
+ if(extraPrecision) {
+ // if extraPrecision led to trailing zeros, strip them off
+ // actually, this can lead to removing even more zeros than
+ // in the original rounding, but that's fine because in these
+ // contexts uniformity is not so important (if there's even
+ // anything to be uniform with!)
+
+ // can we remove the whole time part?
+ if(dateStr === '00:00:00' || dateStr === '00:00') {
+ dateStr = headStr;
+ headStr = '';
}
- else if(tt.length === 8) {
+ else if(dateStr.length === 8) {
// strip off seconds if they're zero (zero fractional seconds
// are already omitted)
- tt = tt.replace(/:00$/, '');
+ // but we never remove minutes and leave just hours
+ dateStr = dateStr.replace(/:00$/, '');
}
}
- if(headPart) {
+ if(headStr) {
if(hover) {
// hover puts it all on one line, so headPart works best up front
// except for year headPart: turn this into "Jan 1, 2000" etc.
- if(tr === 'd') tt += ', ' + headPart;
- else tt = headPart + (tt ? ', ' + tt : '');
+ if(tr === 'd') dateStr += ', ' + headStr;
+ else dateStr = headStr + (dateStr ? ', ' + dateStr : '');
}
- else if(!ax._inCalcTicks || (headPart !== ax._prevDateHead)) {
- tt += '
' + headPart;
- ax._prevDateHead = headPart;
+ else if(!ax._inCalcTicks || (headStr !== ax._prevDateHead)) {
+ dateStr += '
' + headStr;
+ ax._prevDateHead = headStr;
}
}
- out.text = tt;
+
+ out.text = dateStr;
}
function formatLog(ax, out, hover, extraPrecision, hideexp) {
@@ -1288,7 +1200,7 @@ function formatLog(ax, out, hover, extraPrecision, hideexp) {
if(ax.tickformat || (typeof dtick === 'string' && dtick.charAt(0) === 'L')) {
out.text = numFormat(Math.pow(10, x), ax, hideexp, extraPrecision);
}
- else if(isNumeric(dtick) || ((dtick.charAt(0) === 'D') && (mod(x + 0.01, 1) < 0.1))) {
+ else if(isNumeric(dtick) || ((dtick.charAt(0) === 'D') && (Lib.mod(x + 0.01, 1) < 0.1))) {
if(['e', 'E', 'power'].indexOf(ax.exponentformat) !== -1) {
var p = Math.round(x);
if(p === 0) out.text = 1;
@@ -1306,7 +1218,7 @@ function formatLog(ax, out, hover, extraPrecision, hideexp) {
}
}
else if(dtick.charAt(0) === 'D') {
- out.text = String(Math.round(Math.pow(10, mod(x, 1))));
+ out.text = String(Math.round(Math.pow(10, Lib.mod(x, 1))));
out.fontSize *= 0.75;
}
else throw 'unrecognized dtick ' + String(dtick);
@@ -1659,7 +1571,7 @@ axes.doTicks = function(gd, axid, skipTitle) {
var axDone = axes.doTicks(gd, ax._id);
if(axid === 'redraw') {
ax._r = ax.range.slice();
- ax._rl = ax._r.map(ax.r2l);
+ ax._rl = Lib.simpleMap(ax._r, ax.r2l);
}
return axDone;
};
@@ -2066,7 +1978,7 @@ axes.doTicks = function(gd, axid, skipTitle) {
break;
}
}
- var rng = ax.range.map(ax.r2l),
+ var rng = Lib.simpleMap(ax.range, ax.r2l),
showZl = (rng[0] * rng[1] <= 0) && ax.zeroline &&
(ax.type === 'linear' || ax.type === '-') && gridvals.length &&
(hasBarsOrFill || clipEnds({x: 0}) || !ax.showline);
@@ -2286,7 +2198,3 @@ function swapAxisAttrs(layout, key, xFullAxes, yFullAxes) {
np(layout, yFullAxes[i]._name + '.' + key).set(xVal);
}
}
-
-// mod - version of modulus that always restricts to [0,divisor)
-// rather than built-in % which gives a negative value for negative v
-function mod(v, d) { return ((v % d) + d) % d; }
diff --git a/src/plots/cartesian/axis_autotype.js b/src/plots/cartesian/axis_autotype.js
index 00139887d28..7dec6b2fd52 100644
--- a/src/plots/cartesian/axis_autotype.js
+++ b/src/plots/cartesian/axis_autotype.js
@@ -14,8 +14,8 @@ var isNumeric = require('fast-isnumeric');
var Lib = require('../../lib');
var BADNUM = require('../../constants/numerical').BADNUM;
-module.exports = function autoType(array) {
- if(moreDates(array)) return 'date';
+module.exports = function autoType(array, calendar) {
+ if(moreDates(array, calendar)) return 'date';
if(category(array)) return 'category';
if(linearOK(array)) return 'linear';
else return '-';
@@ -38,7 +38,7 @@ function linearOK(array) {
// 2- or 4-digit integers can be both, so require twice as many
// dates as non-dates, to exclude cases with mostly 2 & 4 digit
// numbers and a few dates
-function moreDates(a) {
+function moreDates(a, calendar) {
var dcnt = 0,
ncnt = 0,
// test at most 1000 points, evenly spaced
@@ -47,7 +47,7 @@ function moreDates(a) {
for(var i = 0; i < a.length; i += inc) {
ai = a[Math.round(i)];
- if(Lib.isDateTime(ai)) dcnt += 1;
+ if(Lib.isDateTime(ai, calendar)) dcnt += 1;
if(isNumeric(ai)) ncnt += 1;
}
diff --git a/src/plots/cartesian/axis_defaults.js b/src/plots/cartesian/axis_defaults.js
index 0bfdbde1ff2..a0ab9aed0b8 100644
--- a/src/plots/cartesian/axis_defaults.js
+++ b/src/plots/cartesian/axis_defaults.js
@@ -74,6 +74,11 @@ module.exports = function handleAxisDefaults(containerIn, containerOut, coerce,
}
}
+ if(axType === 'date') {
+ var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults');
+ handleCalendarDefaults(containerIn, containerOut, 'calendar', options.calendar);
+ }
+
setConvert(containerOut);
var dfltColor = coerce('color');
@@ -166,6 +171,9 @@ function setAutoType(ax, data) {
return;
}
+ var calAttr = axLetter + 'calendar',
+ calendar = d0[calAttr];
+
// check all boxes on this x axis to see
// if they're dates, numbers, or categories
if(isBoxWithoutPositionCoords(d0, axLetter)) {
@@ -181,12 +189,14 @@ function setAutoType(ax, data) {
if(trace[posLetter] !== undefined) boxPositions.push(trace[posLetter][0]);
else if(trace.name !== undefined) boxPositions.push(trace.name);
else boxPositions.push('text');
+
+ if(trace[calAttr] !== calendar) calendar = undefined;
}
- ax.type = autoType(boxPositions);
+ ax.type = autoType(boxPositions, calendar);
}
else {
- ax.type = autoType(d0[axLetter] || [d0[axLetter + '0']]);
+ ax.type = autoType(d0[axLetter] || [d0[axLetter + '0']], calendar);
}
}
diff --git a/src/plots/cartesian/constants.js b/src/plots/cartesian/constants.js
index 765241182a6..dc7d444c477 100644
--- a/src/plots/cartesian/constants.js
+++ b/src/plots/cartesian/constants.js
@@ -66,8 +66,7 @@ module.exports = {
// delay before a redraw (relayout) after smooth panning and zooming
REDRAWDELAY: 50,
- // last resort axis ranges for x, y, and date axes if we have no data
+ // last resort axis ranges for x and y axes if we have no data
DFLTRANGEX: [-1, 6],
- DFLTRANGEY: [-1, 4],
- DFLTRANGEDATE: ['2000-01-01', '2001-01-01'],
+ DFLTRANGEY: [-1, 4]
};
diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js
index 2927becef5a..43c9e7a1610 100644
--- a/src/plots/cartesian/dragbox.js
+++ b/src/plots/cartesian/dragbox.js
@@ -424,7 +424,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
function zoomWheelOneAxis(ax, centerFraction, zoom) {
if(ax.fixedrange) return;
- var axRange = ax.range.map(ax.r2l),
+ var axRange = Lib.simpleMap(ax.range, ax.r2l),
v0 = axRange[0] + (axRange[1] - axRange[0]) * centerFraction;
function doZoom(v) { return ax.l2r(v0 + (v - v0) * zoom); }
ax.range = axRange.map(doZoom);
diff --git a/src/plots/cartesian/graph_interact.js b/src/plots/cartesian/graph_interact.js
index 10408f47ac7..57c65b99b13 100644
--- a/src/plots/cartesian/graph_interact.js
+++ b/src/plots/cartesian/graph_interact.js
@@ -666,6 +666,10 @@ function cleanPoint(d, hovermode) {
if(d.xLabelVal === 0) d.xLabel = '0';
else d.xLabel = '-' + xLabelObj.text;
}
+ // TODO: should we do something special if the axis calendar and
+ // the data calendar are different? Somehow display both dates with
+ // their system names? Right now it will just display in the axis calendar
+ // but users could add the other one as text.
else d.xLabel = xLabelObj.text;
d.xVal = d.xa.c2d(d.xLabelVal);
}
@@ -678,6 +682,7 @@ function cleanPoint(d, hovermode) {
if(d.yLabelVal === 0) d.yLabel = '0';
else d.yLabel = '-' + yLabelObj.text;
}
+ // TODO: see above TODO
else d.yLabel = yLabelObj.text;
d.yVal = d.ya.c2d(d.yLabelVal);
}
diff --git a/src/plots/cartesian/layout_defaults.js b/src/plots/cartesian/layout_defaults.js
index a3078d94897..625cb9e171f 100644
--- a/src/plots/cartesian/layout_defaults.js
+++ b/src/plots/cartesian/layout_defaults.js
@@ -126,7 +126,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
showGrid: !noGrids[axName],
name: axName,
data: fullData,
- bgColor: bgColor
+ bgColor: bgColor,
+ calendar: layoutOut.calendar
},
positioningOptions = {
letter: axLetter,
@@ -140,7 +141,7 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
return Lib.coerce(axLayoutIn, axLayoutOut, layoutAttributes, attr, dflt);
}
- handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions);
+ handleAxisDefaults(axLayoutIn, axLayoutOut, coerce, defaultOptions, layoutOut);
handlePositionDefaults(axLayoutIn, axLayoutOut, coerce, positioningOptions);
layoutOut[axName] = axLayoutOut;
@@ -166,7 +167,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
rangeSliderDefaults(layoutIn, layoutOut, axName, counterAxes);
if(axLetter === 'x' && axLayoutOut.type === 'date') {
- rangeSelectorDefaults(axLayoutIn, axLayoutOut, layoutOut, counterAxes);
+ rangeSelectorDefaults(axLayoutIn, axLayoutOut, layoutOut, counterAxes,
+ axLayoutOut.calendar);
}
});
};
diff --git a/src/plots/cartesian/set_convert.js b/src/plots/cartesian/set_convert.js
index 377c638ecf5..7202411e980 100644
--- a/src/plots/cartesian/set_convert.js
+++ b/src/plots/cartesian/set_convert.js
@@ -13,6 +13,10 @@ var d3 = require('d3');
var isNumeric = require('fast-isnumeric');
var Lib = require('../../lib');
+var cleanNumber = Lib.cleanNumber;
+var ms2DateTime = Lib.ms2DateTime;
+var dateTime2ms = Lib.dateTime2ms;
+
var numConstants = require('../../constants/numerical');
var FP_SAFE = numConstants.FP_SAFE;
var BADNUM = numConstants.BADNUM;
@@ -20,6 +24,16 @@ var BADNUM = numConstants.BADNUM;
var constants = require('./constants');
var axisIds = require('./axis_ids');
+function fromLog(v) {
+ return Math.pow(10, v);
+}
+
+function num(v) {
+ if(!isNumeric(v)) return BADNUM;
+ v = Number(v);
+ if(v < -FP_SAFE || v > FP_SAFE) return BADNUM;
+ return isNumeric(v) ? Number(v) : BADNUM;
+}
/**
* Define the conversion functions for an axis data is used in 5 ways:
@@ -41,8 +55,9 @@ var axisIds = require('./axis_ids');
* shapes will work the same way as ranges, tick0, and annotations
* so they can use this conversion too.
*
- * Creates/updates these conversion functions, as well as cleaner functions:
- * ax.d2d and ax.clean2r
+ * Creates/updates these conversion functions, and a few more utilities
+ * like cleanRange, and makeCalcdata
+ *
* also clears the autorange bounds ._min and ._max
* and the autotick constraints ._minDtick, ._forceTick0
*/
@@ -67,24 +82,170 @@ module.exports = function setConvert(ax) {
else return BADNUM;
}
- function fromLog(v) {
- return Math.pow(10, v);
+ /*
+ * wrapped dateTime2ms that:
+ * - accepts ms numbers for backward compatibility
+ * - inserts a dummy arg so calendar is the 3rd arg (see notes below).
+ * - defaults to ax.calendar
+ */
+ function dt2ms(v, _, calendar) {
+ // NOTE: Changed this behavior: previously we took any numeric value
+ // to be a ms, even if it was a string that could be a bare year.
+ // Now we convert it as a date if at all possible, and only try
+ // as (local) ms if that fails.
+ var ms = dateTime2ms(v, calendar || ax.calendar);
+ if(ms === BADNUM) {
+ if(isNumeric(v)) ms = dateTime2ms(new Date(+v));
+ else return BADNUM;
+ }
+ return ms;
+ }
+
+ // wrapped ms2DateTime to insert default ax.calendar
+ function ms2dt(v, r, calendar) {
+ return ms2DateTime(v, r, calendar || ax.calendar);
+ }
+
+ function getCategoryName(v) {
+ return ax._categories[Math.round(v)];
+ }
+
+ /*
+ * setCategoryIndex: return the index of category v,
+ * inserting it in the list if it's not already there
+ *
+ * this will enter the categories in the order it
+ * encounters them, ie all the categories from the
+ * first data set, then all the ones from the second
+ * that aren't in the first etc.
+ *
+ * it is assumed that this function is being invoked in the
+ * already sorted category order; otherwise there would be
+ * a disconnect between the array and the index returned
+ */
+ function setCategoryIndex(v) {
+ if(v !== null && v !== undefined) {
+ var c = ax._categories.indexOf(v);
+ if(c === -1) {
+ ax._categories.push(v);
+ return ax._categories.length - 1;
+ }
+ return c;
+ }
+ return BADNUM;
+ }
+
+ function getCategoryIndex(v) {
+ // d2l/d2c variant that that won't add categories but will also
+ // allow numbers to be mapped to the linearized axis positions
+ var index = ax._categories.indexOf(v);
+ if(index !== -1) return index;
+ if(typeof v === 'number') return v;
}
- function num(v) {
+ function l2p(v) {
if(!isNumeric(v)) return BADNUM;
- v = Number(v);
- if(v < -FP_SAFE || v > FP_SAFE) return BADNUM;
- return isNumeric(v) ? Number(v) : BADNUM;
+
+ // include 2 fractional digits on pixel, for PDF zooming etc
+ return d3.round(ax._b + ax._m * v, 2);
}
+ function p2l(px) { return (px - ax._b) / ax._m; }
+
+ // conversions among c/l/p are fairly simple - do them together for all axis types
ax.c2l = (ax.type === 'log') ? toLog : num;
ax.l2c = (ax.type === 'log') ? fromLog : num;
- ax.l2d = function(v) { return ax.c2d(ax.l2c(v)); };
- ax.p2d = function(v) { return ax.l2d(ax.p2l(v)); };
+
+ ax.l2p = l2p;
+ ax.p2l = p2l;
+
+ ax.c2p = (ax.type === 'log') ? function(v, clip) { return l2p(toLog(v, clip)); } : l2p;
+ ax.p2c = (ax.type === 'log') ? function(px) { return fromLog(p2l(px)); } : p2l;
+
+ /*
+ * now type-specific conversions for **ALL** other combinations
+ * they're all written out, instead of being combinations of each other, for
+ * both clarity and speed.
+ */
+ if(['linear', '-'].indexOf(ax.type) !== -1) {
+ // all are data vals, but d and r need cleaning
+ ax.d2r = ax.r2d = ax.d2c = ax.r2c = ax.d2l = ax.r2l = cleanNumber;
+ ax.c2d = ax.c2r = ax.l2d = ax.l2r = num;
+
+ ax.d2p = ax.r2p = function(v) { return l2p(cleanNumber(v)); };
+ ax.p2d = ax.p2r = p2l;
+ }
+ else if(ax.type === 'log') {
+ // d and c are data vals, r and l are logged (but d and r need cleaning)
+ ax.d2r = ax.d2l = function(v, clip) { return toLog(cleanNumber(v), clip); };
+ ax.r2d = ax.r2c = function(v) { return fromLog(cleanNumber(v)); };
+
+ ax.d2c = ax.r2l = cleanNumber;
+ ax.c2d = ax.l2r = num;
+
+ ax.c2r = toLog;
+ ax.l2d = fromLog;
+
+ ax.d2p = function(v, clip) { return l2p(ax.d2r(v, clip)); };
+ ax.p2d = function(px) { return fromLog(p2l(px)); };
+
+ ax.r2p = function(v) { return l2p(cleanNumber(v)); };
+ ax.p2r = p2l;
+ }
+ else if(ax.type === 'date') {
+ // r and d are date strings, l and c are ms
+
+ /*
+ * Any of these functions with r and d on either side, calendar is the
+ * **3rd** argument. log has reserved the second argument.
+ *
+ * Unless you need the special behavior of the second arg (ms2DateTime
+ * uses this to limit precision, toLog uses true to clip negatives
+ * to offscreen low rather than undefined), it's safe to pass 0.
+ */
+ ax.d2r = ax.r2d = Lib.identity;
+
+ ax.d2c = ax.r2c = ax.d2l = ax.r2l = dt2ms;
+ ax.c2d = ax.c2r = ax.l2d = ax.l2r = ms2dt;
+
+ ax.d2p = ax.r2p = function(v, _, calendar) { return l2p(dt2ms(v, 0, calendar)); };
+ ax.p2d = ax.p2r = function(px, r, calendar) { return ms2dt(p2l(px), r, calendar); };
+ }
+ else if(ax.type === 'category') {
+ // d is categories; r, c, and l are indices
+ // TODO: should r accept category names too?
+ // ie r2c and r2l would be getCategoryIndex (and r2p would change)
+
+ ax.d2r = ax.d2c = ax.d2l = setCategoryIndex;
+ ax.r2d = ax.c2d = ax.l2d = getCategoryName;
+
+ // special d2l variant that won't add categories
+ ax.d2l_noadd = getCategoryIndex;
+
+ ax.r2l = ax.l2r = ax.r2c = ax.c2r = num;
+
+ ax.d2p = function(v) { return l2p(getCategoryIndex(v)); };
+ ax.p2d = function(px) { return getCategoryName(p2l(px)); };
+ ax.r2p = l2p;
+ ax.p2r = p2l;
+ }
+
+ // find the range value at the specified (linear) fraction of the axis
+ ax.fraction2r = function(v) {
+ var rl0 = ax.r2l(ax.range[0]),
+ rl1 = ax.r2l(ax.range[1]);
+ return ax.l2r(rl0 + v * (rl1 - rl0));
+ };
+
+ // find the fraction of the range at the specified range value
+ ax.r2fraction = function(v) {
+ var rl0 = ax.r2l(ax.range[0]),
+ rl1 = ax.r2l(ax.range[1]);
+ return (ax.r2l(v) - rl0) / (rl1 - rl0);
+ };
/*
- * fn to make sure range is a couplet of valid & distinct values
+ * cleanRange: make sure range is a couplet of valid & distinct values
* keep numbers away from the limits of floating point numbers,
* and dates away from the ends of our date system (+/- 9999 years)
*
@@ -97,7 +258,7 @@ module.exports = function setConvert(ax) {
axLetter = (ax._id || 'x').charAt(0),
i, dflt;
- if(ax.type === 'date') dflt = constants.DFLTRANGEDATE;
+ if(ax.type === 'date') dflt = Lib.dfltRange(ax.calendar);
else if(axLetter === 'y') dflt = constants.DFLTRANGEY;
else dflt = constants.DFLTRANGEX;
@@ -112,20 +273,17 @@ module.exports = function setConvert(ax) {
if(ax.type === 'date') {
// check if milliseconds or js date objects are provided for range
// and convert to date strings
- range[0] = Lib.cleanDate(range[0]);
- range[1] = Lib.cleanDate(range[1]);
+ range[0] = Lib.cleanDate(range[0], BADNUM, ax.calendar);
+ range[1] = Lib.cleanDate(range[1], BADNUM, ax.calendar);
}
for(i = 0; i < 2; i++) {
if(ax.type === 'date') {
- if(!Lib.isDateTime(range[i])) {
+ if(!Lib.isDateTime(range[i], ax.calendar)) {
ax[rangeAttr] = dflt;
break;
}
- if(range[i] < Lib.MIN_MS) range[i] = Lib.MIN_MS;
- if(range[i] > Lib.MAX_MS) range[i] = Lib.MAX_MS;
-
if(ax.r2l(range[0]) === ax.r2l(range[1])) {
// split by +/- 1 second
var linCenter = Lib.constrain(ax.r2l(range[0]),
@@ -159,20 +317,6 @@ module.exports = function setConvert(ax) {
}
};
- // find the range value at the specified (linear) fraction of the axis
- ax.fraction2r = function(v) {
- var rl0 = ax.r2l(ax.range[0]),
- rl1 = ax.r2l(ax.range[1]);
- return ax.l2r(rl0 + v * (rl1 - rl0));
- };
-
- // find the fraction of the range at the specified range value
- ax.r2fraction = function(v) {
- var rl0 = ax.r2l(ax.range[0]),
- rl1 = ax.r2l(ax.range[1]);
- return (ax.r2l(v) - rl0) / (rl1 - rl0);
- };
-
// set scaling to pixels
ax.setScale = function(usePrivateRange) {
var gs = ax._gd._fullLayout._size,
@@ -192,11 +336,12 @@ module.exports = function setConvert(ax) {
// issue if we transform the drawn layer *and* use the new axis range to
// draw the data. This allows us to construct setConvert using the pre-
// interaction values of the range:
- var rangeAttr = (usePrivateRange && ax._r) ? '_r' : 'range';
+ var rangeAttr = (usePrivateRange && ax._r) ? '_r' : 'range',
+ calendar = ax.calendar;
ax.cleanRange(rangeAttr);
- var rl0 = ax.r2l(ax[rangeAttr][0]),
- rl1 = ax.r2l(ax[rangeAttr][1]);
+ var rl0 = ax.r2l(ax[rangeAttr][0], calendar),
+ rl1 = ax.r2l(ax[rangeAttr][1], calendar);
if(axLetter === 'y') {
ax._offset = gs.t + (1 - ax.domain[1]) * gs.h;
@@ -220,114 +365,6 @@ module.exports = function setConvert(ax) {
}
};
- ax.l2p = function(v) {
- if(!isNumeric(v)) return BADNUM;
-
- // include 2 fractional digits on pixel, for PDF zooming etc
- return d3.round(ax._b + ax._m * v, 2);
- };
-
- ax.p2l = function(px) { return (px - ax._b) / ax._m; };
-
- ax.c2p = function(v, clip) { return ax.l2p(ax.c2l(v, clip)); };
- ax.p2c = function(px) { return ax.l2c(ax.p2l(px)); };
-
- // clip doesn't do anything here yet, but in v2.0 when log axes get
- // refactored it will... so including it now so we don't forget.
- ax.r2p = function(v, clip) { return ax.l2p(ax.r2l(v, clip)); };
- ax.p2r = function(px) { return ax.l2r(ax.p2l(px)); };
-
- ax.r2c = function(v) { return ax.l2c(ax.r2l(v)); };
- ax.c2r = function(v) { return ax.l2r(ax.c2l(v)); };
-
- if(['linear', 'log', '-'].indexOf(ax.type) !== -1) {
- ax.c2d = num;
- ax.d2c = Lib.cleanNumber;
- if(ax.type === 'log') {
- ax.d2l = function(v, clip) {
- return ax.c2l(ax.d2c(v), clip);
- };
- ax.d2r = ax.d2l;
- ax.r2d = ax.l2d;
- }
- else {
- ax.d2l = Lib.cleanNumber;
- ax.d2r = Lib.cleanNumber;
- ax.r2d = num;
- }
- ax.r2l = num;
- ax.l2r = num;
- }
- else if(ax.type === 'date') {
- ax.c2d = Lib.ms2DateTime;
-
- ax.d2c = function(v) {
- // NOTE: Changed this behavior: previously we took any numeric value
- // to be a ms, even if it was a string that could be a bare year.
- // Now we convert it as a date if at all possible, and only try
- // as (local) ms if that fails.
- var ms = Lib.dateTime2ms(v);
- if(ms === BADNUM) {
- if(isNumeric(v)) ms = Lib.dateTime2ms(new Date(v));
- else return BADNUM;
- }
- return Lib.constrain(ms, Lib.MIN_MS, Lib.MAX_MS);
- };
-
- ax.d2l = ax.d2c;
- ax.r2l = ax.d2c;
- ax.l2r = ax.c2d;
- ax.d2r = Lib.identity;
- ax.r2d = Lib.identity;
- ax.cleanr = function(v) {
- /*
- * If v is already a date string this is a noop, but running it
- * through d2c and back validates the value:
- * normalizes Date objects, milliseconds, and out-of-bounds dates
- * so we always end up with either a clean date string or BADNUM
- */
- return ax.c2d(ax.d2c(v));
- };
- }
- else if(ax.type === 'category') {
-
- ax.c2d = function(v) {
- return ax._categories[Math.round(v)];
- };
-
- ax.d2c = function(v) {
- // create the category list
- // this will enter the categories in the order it
- // encounters them, ie all the categories from the
- // first data set, then all the ones from the second
- // that aren't in the first etc.
- // it is assumed that this function is being invoked in the
- // already sorted category order; otherwise there would be
- // a disconnect between the array and the index returned
-
- if(v !== null && v !== undefined && ax._categories.indexOf(v) === -1) {
- ax._categories.push(v);
- }
-
- var c = ax._categories.indexOf(v);
- return c === -1 ? BADNUM : c;
- };
-
- ax.d2l_noadd = function(v) {
- // d2c variant that that won't add categories but will also
- // allow numbers to be mapped to the linearized axis positions
- var index = ax._categories.indexOf(v);
- if(index !== -1) return index;
- if(typeof v === 'number') return v;
- };
-
- ax.d2l = ax.d2c;
- ax.r2l = num;
- ax.l2r = num;
- ax.d2r = ax.d2c;
- ax.r2d = ax.c2d;
- }
-
// makeCalcdata: takes an x or y array and converts it
// to a position on the axis object "ax"
// inputs:
@@ -340,15 +377,19 @@ module.exports = function setConvert(ax) {
ax.makeCalcdata = function(trace, axLetter) {
var arrayIn, arrayOut, i;
+ var cal = ax.type === 'date' && trace[axLetter + 'calendar'];
+
if(axLetter in trace) {
arrayIn = trace[axLetter];
arrayOut = new Array(arrayIn.length);
- for(i = 0; i < arrayIn.length; i++) arrayOut[i] = ax.d2c(arrayIn[i]);
+ for(i = 0; i < arrayIn.length; i++) {
+ arrayOut[i] = ax.d2c(arrayIn[i], 0, cal);
+ }
}
else {
var v0 = ((axLetter + '0') in trace) ?
- ax.d2c(trace[axLetter + '0']) : 0,
+ ax.d2c(trace[axLetter + '0'], 0, cal) : 0,
dv = (trace['d' + axLetter]) ?
Number(trace['d' + axLetter]) : 1;
diff --git a/src/plots/cartesian/tick_value_defaults.js b/src/plots/cartesian/tick_value_defaults.js
index f441cb5e055..0c8790eb346 100644
--- a/src/plots/cartesian/tick_value_defaults.js
+++ b/src/plots/cartesian/tick_value_defaults.js
@@ -61,7 +61,7 @@ module.exports = function handleTickValueDefaults(containerIn, containerOut, coe
// tick0 can have different valType for different axis types, so
// validate that now. Also for dates, change milliseconds to date strings
- var tick0Dflt = (axType === 'date') ? '2000-01-01' : 0;
+ var tick0Dflt = (axType === 'date') ? Lib.dateTick0(containerOut.calendar) : 0;
var tick0 = coerce('tick0', tick0Dflt);
if(axType === 'date') {
containerOut.tick0 = Lib.cleanDate(tick0, tick0Dflt);
diff --git a/src/plots/gl3d/layout/axis_defaults.js b/src/plots/gl3d/layout/axis_defaults.js
index df6857ec47a..981c7b042a3 100644
--- a/src/plots/gl3d/layout/axis_defaults.js
+++ b/src/plots/gl3d/layout/axis_defaults.js
@@ -46,7 +46,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, options) {
letter: axName[0],
data: options.data,
showGrid: true,
- bgColor: options.bgColor
+ bgColor: options.bgColor,
+ calendar: options.calendar
});
coerce('gridcolor', colorMix(containerOut.color, options.bgColor, gridLightness).toRgbString());
diff --git a/src/plots/gl3d/layout/defaults.js b/src/plots/gl3d/layout/defaults.js
index 291d8dcd572..a8aa30dd0de 100644
--- a/src/plots/gl3d/layout/defaults.js
+++ b/src/plots/gl3d/layout/defaults.js
@@ -41,7 +41,8 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
font: layoutOut.font,
fullData: fullData,
getDfltFromLayout: getDfltFromLayout,
- paper_bgcolor: layoutOut.paper_bgcolor
+ paper_bgcolor: layoutOut.paper_bgcolor,
+ calendar: layoutOut.calendar
});
};
@@ -97,7 +98,8 @@ function handleGl3dDefaults(sceneLayoutIn, sceneLayoutOut, coerce, opts) {
font: opts.font,
scene: opts.id,
data: opts.fullData,
- bgColor: bgColorCombined
+ bgColor: bgColorCombined,
+ calendar: opts.calendar
});
coerce('dragmode', opts.getDfltFromLayout('dragmode'));
diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js
index 28750ec3208..02188822a9c 100644
--- a/src/plots/gl3d/scene.js
+++ b/src/plots/gl3d/scene.js
@@ -292,12 +292,12 @@ proto.recoverContext = function() {
var axisProperties = [ 'xaxis', 'yaxis', 'zaxis' ];
-function coordinateBound(axis, coord, d, bounds) {
+function coordinateBound(axis, coord, d, bounds, calendar) {
var x;
for(var i = 0; i < coord.length; ++i) {
if(Array.isArray(coord[i])) {
for(var j = 0; j < coord[i].length; ++j) {
- x = axis.d2l(coord[i][j]);
+ x = axis.d2l(coord[i][j], 0, calendar);
if(!isNaN(x) && isFinite(x)) {
bounds[0][d] = Math.min(bounds[0][d], x);
bounds[1][d] = Math.max(bounds[1][d], x);
@@ -305,7 +305,7 @@ function coordinateBound(axis, coord, d, bounds) {
}
}
else {
- x = axis.d2l(coord[i]);
+ x = axis.d2l(coord[i], 0, calendar);
if(!isNaN(x) && isFinite(x)) {
bounds[0][d] = Math.min(bounds[0][d], x);
bounds[1][d] = Math.max(bounds[1][d], x);
@@ -316,9 +316,9 @@ function coordinateBound(axis, coord, d, bounds) {
function computeTraceBounds(scene, trace, bounds) {
var sceneLayout = scene.fullSceneLayout;
- coordinateBound(sceneLayout.xaxis, trace.x, 0, bounds);
- coordinateBound(sceneLayout.yaxis, trace.y, 1, bounds);
- coordinateBound(sceneLayout.zaxis, trace.z, 2, bounds);
+ coordinateBound(sceneLayout.xaxis, trace.x, 0, bounds, trace.xcalendar);
+ coordinateBound(sceneLayout.yaxis, trace.y, 1, bounds, trace.ycalendar);
+ coordinateBound(sceneLayout.zaxis, trace.z, 2, bounds, trace.zcalendar);
}
proto.plot = function(sceneData, fullLayout, layout) {
diff --git a/src/plots/layout_attributes.js b/src/plots/layout_attributes.js
index f98ffc9606f..235ff39250c 100644
--- a/src/plots/layout_attributes.js
+++ b/src/plots/layout_attributes.js
@@ -170,7 +170,6 @@ module.exports = {
role: 'info',
description: 'Determines whether or not a legend is drawn.'
},
-
dragmode: {
valType: 'enumerated',
role: 'info',
diff --git a/src/plots/plots.js b/src/plots/plots.js
index 06f2e91aff5..e670463ce37 100644
--- a/src/plots/plots.js
+++ b/src/plots/plots.js
@@ -941,6 +941,9 @@ plots.supplyLayoutGlobalDefaults = function(layoutIn, layoutOut) {
coerce('separators');
coerce('hidesources');
coerce('smith');
+
+ var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults');
+ handleCalendarDefaults(layoutIn, layoutOut, 'calendar');
};
plots.plotAutoSize = function plotAutoSize(gd, layout, fullLayout) {
diff --git a/src/traces/bar/calc.js b/src/traces/bar/calc.js
index 22c2a1a9871..52f45c4b69a 100644
--- a/src/traces/bar/calc.js
+++ b/src/traces/bar/calc.js
@@ -25,17 +25,24 @@ module.exports = function calc(gd, trace) {
var xa = Axes.getFromId(gd, trace.xaxis || 'x'),
ya = Axes.getFromId(gd, trace.yaxis || 'y'),
orientation = trace.orientation || ((trace.x && !trace.y) ? 'h' : 'v'),
- sa, pos, size, i;
+ sa, pos, size, i, scalendar;
if(orientation === 'h') {
sa = xa;
size = xa.makeCalcdata(trace, 'x');
pos = ya.makeCalcdata(trace, 'y');
+
+ // not sure if it really makes sense to have dates for bar size data...
+ // ideally if we want to make gantt charts or something we'd treat
+ // the actual size (trace.x or y) as time delta but base as absolute
+ // time. But included here for completeness.
+ scalendar = trace.xcalendar;
}
else {
sa = ya;
size = ya.makeCalcdata(trace, 'y');
pos = xa.makeCalcdata(trace, 'x');
+ scalendar = trace.ycalendar;
}
// create the "calculated data" to plot
@@ -60,7 +67,7 @@ module.exports = function calc(gd, trace) {
if(Array.isArray(base)) {
for(i = 0; i < Math.min(base.length, cd.length); i++) {
- b = sa.d2c(base[i]);
+ b = sa.d2c(base[i], 0, scalendar);
cd[i].b = (isNumeric(b)) ? b : 0;
}
for(; i < cd.length; i++) {
@@ -68,7 +75,7 @@ module.exports = function calc(gd, trace) {
}
}
else {
- b = sa.d2c(base);
+ b = sa.d2c(base, 0, scalendar);
b = (isNumeric(b)) ? b : 0;
for(i = 0; i < cd.length; i++) {
cd[i].b = b;
diff --git a/src/traces/bar/defaults.js b/src/traces/bar/defaults.js
index e907b40ad06..ada3fe7116b 100644
--- a/src/traces/bar/defaults.js
+++ b/src/traces/bar/defaults.js
@@ -25,7 +25,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
var coerceFont = Lib.coerceFont;
- var len = handleXYDefaults(traceIn, traceOut, coerce);
+ var len = handleXYDefaults(traceIn, traceOut, layout, coerce);
if(!len) {
traceOut.visible = false;
return;
diff --git a/src/traces/box/attributes.js b/src/traces/box/attributes.js
index c01f7914850..41d7170f7bf 100644
--- a/src/traces/box/attributes.js
+++ b/src/traces/box/attributes.js
@@ -47,6 +47,8 @@ module.exports = {
'See overview for more info.'
].join(' ')
},
+ xcalendar: scatterAttrs.xcalendar,
+ ycalendar: scatterAttrs.ycalendar,
whiskerwidth: {
valType: 'number',
min: 0,
diff --git a/src/traces/box/calc.js b/src/traces/box/calc.js
index cafd8eb16c7..c8bffeeff5e 100644
--- a/src/traces/box/calc.js
+++ b/src/traces/box/calc.js
@@ -62,7 +62,7 @@ module.exports = function calc(gd, trace) {
pos0 = trace.name;
}
else pos0 = gd.numboxes;
- pos0 = posAxis.d2c(pos0);
+ pos0 = posAxis.d2c(pos0, 0, trace[posLetter + 'calendar']);
pos = val.map(function() { return pos0; });
}
return pos;
diff --git a/src/traces/box/defaults.js b/src/traces/box/defaults.js
index 61d9653d8a9..9f33075d88b 100644
--- a/src/traces/box/defaults.js
+++ b/src/traces/box/defaults.js
@@ -9,11 +9,12 @@
'use strict';
var Lib = require('../../lib');
+var Registry = require('../../registry');
var Color = require('../../components/color');
var attributes = require('./attributes');
-module.exports = function supplyDefaults(traceIn, traceOut, defaultColor) {
+module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
@@ -33,6 +34,9 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor) {
return;
}
+ var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
+ handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
+
coerce('orientation', defaultOrientation);
coerce('line.color', (traceIn.marker || {}).color || defaultColor);
diff --git a/src/traces/candlestick/defaults.js b/src/traces/candlestick/defaults.js
index c6b4538ea53..6668cd15cd0 100644
--- a/src/traces/candlestick/defaults.js
+++ b/src/traces/candlestick/defaults.js
@@ -15,14 +15,14 @@ var handleDirectionDefaults = require('../ohlc/direction_defaults');
var helpers = require('../ohlc/helpers');
var attributes = require('./attributes');
-module.exports = function supplyDefaults(traceIn, traceOut) {
+module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
helpers.pushDummyTransformOpts(traceIn, traceOut);
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
- var len = handleOHLC(traceIn, traceOut, coerce);
+ var len = handleOHLC(traceIn, traceOut, coerce, layout);
if(len === 0) {
traceOut.visible = false;
return;
diff --git a/src/traces/candlestick/transform.js b/src/traces/candlestick/transform.js
index 6d32870e3ce..f89dd7a46a4 100644
--- a/src/traces/candlestick/transform.js
+++ b/src/traces/candlestick/transform.js
@@ -70,6 +70,7 @@ function makeTrace(traceIn, state, direction) {
// to make autotype catch date axes soon!!
x: traceIn.x || [0],
+ xcalendar: traceIn.xcalendar,
// concat low and high to get correct autorange
y: [].concat(traceIn.low).concat(traceIn.high),
diff --git a/src/traces/contour/defaults.js b/src/traces/contour/defaults.js
index e88b43cbe7f..0f4107e5796 100644
--- a/src/traces/contour/defaults.js
+++ b/src/traces/contour/defaults.js
@@ -22,7 +22,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
- var len = handleXYZDefaults(traceIn, traceOut, coerce);
+ var len = handleXYZDefaults(traceIn, traceOut, coerce, layout);
if(!len) {
traceOut.visible = false;
return;
diff --git a/src/traces/heatmap/attributes.js b/src/traces/heatmap/attributes.js
index da86b34d004..2ec094818f9 100644
--- a/src/traces/heatmap/attributes.js
+++ b/src/traces/heatmap/attributes.js
@@ -25,6 +25,7 @@ module.exports = extendFlat({}, {
y: scatterAttrs.y,
y0: scatterAttrs.y0,
dy: scatterAttrs.dy,
+
text: {
valType: 'data_array',
description: 'Sets the text elements associated with each z value.'
diff --git a/src/traces/heatmap/calc.js b/src/traces/heatmap/calc.js
index 1428406404b..ae307000e5b 100644
--- a/src/traces/heatmap/calc.js
+++ b/src/traces/heatmap/calc.js
@@ -127,9 +127,13 @@ module.exports = function calc(gd, trace) {
colorscaleCalc(trace, z, '', 'z');
if(isContour && trace.contours && trace.contours.coloring === 'heatmap') {
- var hmType = trace.type === 'contour' ? 'heatmap' : 'histogram2d';
- cd0.xfill = makeBoundArray(hmType, xIn, x0, dx, xlen, xa);
- cd0.yfill = makeBoundArray(hmType, yIn, y0, dy, z.length, ya);
+ var dummyTrace = {
+ type: trace.type === 'contour' ? 'heatmap' : 'histogram2d',
+ xcalendar: trace.xcalendar,
+ ycalendar: trace.ycalendar
+ };
+ cd0.xfill = makeBoundArray(dummyTrace, xIn, x0, dx, xlen, xa);
+ cd0.yfill = makeBoundArray(dummyTrace, yIn, y0, dy, z.length, ya);
}
return [cd0];
diff --git a/src/traces/heatmap/convert_column_xyz.js b/src/traces/heatmap/convert_column_xyz.js
index 1f18b6a5474..1798b4e93b8 100644
--- a/src/traces/heatmap/convert_column_xyz.js
+++ b/src/traces/heatmap/convert_column_xyz.js
@@ -18,7 +18,9 @@ module.exports = function convertColumnXYZ(trace, xa, ya) {
zCol = trace.z,
textCol = trace.text,
colLen = Math.min(xCol.length, yCol.length, zCol.length),
- hasColumnText = (textCol !== undefined && !Array.isArray(textCol[0]));
+ hasColumnText = (textCol !== undefined && !Array.isArray(textCol[0])),
+ xcalendar = trace.xcalendar,
+ ycalendar = trace.ycalendar;
var i;
@@ -26,8 +28,8 @@ module.exports = function convertColumnXYZ(trace, xa, ya) {
if(colLen < yCol.length) yCol = yCol.slice(0, colLen);
for(i = 0; i < colLen; i++) {
- xCol[i] = xa.d2c(xCol[i]);
- yCol[i] = ya.d2c(yCol[i]);
+ xCol[i] = xa.d2c(xCol[i], 0, xcalendar);
+ yCol[i] = ya.d2c(yCol[i], 0, ycalendar);
}
var xColdv = Lib.distinctVals(xCol),
diff --git a/src/traces/heatmap/defaults.js b/src/traces/heatmap/defaults.js
index f41624cf57e..cc4c33aae0e 100644
--- a/src/traces/heatmap/defaults.js
+++ b/src/traces/heatmap/defaults.js
@@ -22,7 +22,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
- var len = handleXYZDefaults(traceIn, traceOut, coerce);
+ var len = handleXYZDefaults(traceIn, traceOut, coerce, layout);
if(!len) {
traceOut.visible = false;
return;
diff --git a/src/traces/heatmap/make_bound_array.js b/src/traces/heatmap/make_bound_array.js
index 42f09489a24..6640f03a4bd 100644
--- a/src/traces/heatmap/make_bound_array.js
+++ b/src/traces/heatmap/make_bound_array.js
@@ -64,10 +64,12 @@ module.exports = function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks,
else {
dv = dvIn || 1;
- if(isHist || ax.type === 'category') v0 = ax.r2c(v0In) || 0;
+ var calendar = trace[ax._id.charAt(0) + 'calendar'];
+
+ if(isHist || ax.type === 'category') v0 = ax.r2c(v0In, 0, calendar) || 0;
else if(Array.isArray(arrayIn) && arrayIn.length === 1) v0 = arrayIn[0];
else if(v0In === undefined) v0 = 0;
- else v0 = ax.d2c(v0In);
+ else v0 = ax.d2c(v0In, 0, calendar);
for(i = (isContour || isGL2D) ? 0 : -0.5; i < numbricks; i++) {
arrayOut.push(v0 + dv * i);
diff --git a/src/traces/heatmap/xyz_defaults.js b/src/traces/heatmap/xyz_defaults.js
index abc9d03f08f..f07805e88f5 100644
--- a/src/traces/heatmap/xyz_defaults.js
+++ b/src/traces/heatmap/xyz_defaults.js
@@ -11,10 +11,11 @@
var isNumeric = require('fast-isnumeric');
+var Registry = require('../../registry');
var hasColumns = require('./has_columns');
-module.exports = function handleXYZDefaults(traceIn, traceOut, coerce) {
+module.exports = function handleXYZDefaults(traceIn, traceOut, coerce, layout) {
var z = coerce('z');
var x, y;
@@ -37,6 +38,9 @@ module.exports = function handleXYZDefaults(traceIn, traceOut, coerce) {
coerce('transpose');
}
+ var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
+ handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
+
return traceOut.z.length;
};
diff --git a/src/traces/histogram/calc.js b/src/traces/histogram/calc.js
index 5248979368a..c4333d979b3 100644
--- a/src/traces/histogram/calc.js
+++ b/src/traces/histogram/calc.js
@@ -32,7 +32,8 @@ module.exports = function calc(gd, trace) {
pa = Axes.getFromId(gd,
trace.orientation === 'h' ? (trace.yaxis || 'y') : (trace.xaxis || 'x')),
maindata = trace.orientation === 'h' ? 'y' : 'x',
- counterdata = {x: 'y', y: 'x'}[maindata];
+ counterdata = {x: 'y', y: 'x'}[maindata],
+ calendar = trace[maindata + 'calendar'];
cleanBins(trace, pa, maindata);
@@ -40,7 +41,7 @@ module.exports = function calc(gd, trace) {
var pos0 = pa.makeCalcdata(trace, maindata);
// calculate the bins
if((trace['autobin' + maindata] !== false) || !(maindata + 'bins' in trace)) {
- trace[maindata + 'bins'] = Axes.autoBin(pos0, pa, trace['nbins' + maindata]);
+ trace[maindata + 'bins'] = Axes.autoBin(pos0, pa, trace['nbins' + maindata], false, calendar);
// copy bin info back to the source data.
trace._input[maindata + 'bins'] = trace[maindata + 'bins'];
@@ -64,6 +65,7 @@ module.exports = function calc(gd, trace) {
binfunc = binFunctions.count,
normfunc = normFunctions[norm],
doavg = false,
+ pr2c = function(v) { return pa.r2c(v, 0, calendar); },
rawCounterData;
if(Array.isArray(trace[counterdata]) && func !== 'count') {
@@ -74,13 +76,13 @@ module.exports = function calc(gd, trace) {
// create the bins (and any extra arrays needed)
// assume more than 5000 bins is an error, so we don't crash the browser
- i = pa.r2c(binspec.start);
+ i = pr2c(binspec.start);
// decrease end a little in case of rounding errors
- binend = pa.r2c(binspec.end) + (i - Axes.tickIncrement(i, binspec.size)) / 1e6;
+ binend = pr2c(binspec.end) + (i - Axes.tickIncrement(i, binspec.size, false, calendar)) / 1e6;
while(i < binend && pos.length < 5000) {
- i2 = Axes.tickIncrement(i, binspec.size);
+ i2 = Axes.tickIncrement(i, binspec.size, false, calendar);
pos.push((i + i2) / 2);
size.push(sizeinit);
// nonuniform bins (like months) we need to search,
@@ -96,8 +98,8 @@ module.exports = function calc(gd, trace) {
// we already have this, but uniform with start/end/size they're still strings.
if(!nonuniformBins && pa.type === 'date') {
bins = {
- start: pa.r2c(bins.start),
- end: pa.r2c(bins.end),
+ start: pr2c(bins.start),
+ end: pr2c(bins.end),
size: bins.size
};
}
diff --git a/src/traces/histogram/clean_bins.js b/src/traces/histogram/clean_bins.js
index d6c4d00e5d7..d23e6383a3e 100644
--- a/src/traces/histogram/clean_bins.js
+++ b/src/traces/histogram/clean_bins.js
@@ -10,7 +10,9 @@
'use strict';
var isNumeric = require('fast-isnumeric');
var cleanDate = require('../../lib').cleanDate;
-var ONEDAY = require('../../constants/numerical').ONEDAY;
+var constants = require('../../constants/numerical');
+var ONEDAY = constants.ONEDAY;
+var BADNUM = constants.BADNUM;
/*
* cleanBins: validate attributes autobin[xy] and [xy]bins.(start, end, size)
@@ -29,7 +31,7 @@ module.exports = function cleanBins(trace, ax, binDirection) {
if(!bins) bins = trace[binAttr] = {};
var cleanBound = (axType === 'date') ?
- function(v) { return (v || v === 0) ? cleanDate(v) : null; } :
+ function(v) { return (v || v === 0) ? cleanDate(v, BADNUM, bins.calendar) : null; } :
function(v) { return isNumeric(v) ? Number(v) : null; };
bins.start = cleanBound(bins.start);
diff --git a/src/traces/histogram/defaults.js b/src/traces/histogram/defaults.js
index a415ff00797..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,6 +37,9 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
return;
}
+ 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/calc.js b/src/traces/histogram2d/calc.js
index 1f524e34ee0..e9550ec9bef 100644
--- a/src/traces/histogram2d/calc.js
+++ b/src/traces/histogram2d/calc.js
@@ -23,6 +23,12 @@ module.exports = function calc(gd, trace) {
x = trace.x ? xa.makeCalcdata(trace, 'x') : [],
ya = Axes.getFromId(gd, trace.yaxis || 'y'),
y = trace.y ? ya.makeCalcdata(trace, 'y') : [],
+ xcalendar = trace.xcalendar,
+ ycalendar = trace.ycalendar,
+ xr2c = function(v) { return xa.r2c(v, 0, xcalendar); },
+ yr2c = function(v) { return ya.r2c(v, 0, ycalendar); },
+ xc2r = function(v) { return xa.c2r(v, 0, xcalendar); },
+ yc2r = function(v) { return ya.c2r(v, 0, ycalendar); },
x0,
dx,
y0,
@@ -40,22 +46,26 @@ module.exports = function calc(gd, trace) {
// calculate the bins
if(trace.autobinx || !('xbins' in trace)) {
- trace.xbins = Axes.autoBin(x, xa, trace.nbinsx, '2d');
+ trace.xbins = Axes.autoBin(x, xa, trace.nbinsx, '2d', xcalendar);
if(trace.type === 'histogram2dcontour') {
// the "true" last argument reverses the tick direction (which we can't
// just do with a minus sign because of month bins)
- trace.xbins.start = xa.c2r(Axes.tickIncrement(xa.r2c(trace.xbins.start), trace.xbins.size, true));
- trace.xbins.end = xa.c2r(Axes.tickIncrement(xa.r2c(trace.xbins.end), trace.xbins.size));
+ trace.xbins.start = xc2r(Axes.tickIncrement(
+ xr2c(trace.xbins.start), trace.xbins.size, true, xcalendar));
+ trace.xbins.end = xc2r(Axes.tickIncrement(
+ xr2c(trace.xbins.end), trace.xbins.size, false, xcalendar));
}
// copy bin info back to the source data.
trace._input.xbins = trace.xbins;
}
if(trace.autobiny || !('ybins' in trace)) {
- trace.ybins = Axes.autoBin(y, ya, trace.nbinsy, '2d');
+ trace.ybins = Axes.autoBin(y, ya, trace.nbinsy, '2d', ycalendar);
if(trace.type === 'histogram2dcontour') {
- trace.ybins.start = ya.c2r(Axes.tickIncrement(ya.r2c(trace.ybins.start), trace.ybins.size, true));
- trace.ybins.end = ya.c2r(Axes.tickIncrement(ya.r2c(trace.ybins.end), trace.ybins.size));
+ trace.ybins.start = yc2r(Axes.tickIncrement(
+ yr2c(trace.ybins.start), trace.ybins.size, true, ycalendar));
+ trace.ybins.end = yc2r(Axes.tickIncrement(
+ yr2c(trace.ybins.end), trace.ybins.size, false, ycalendar));
}
trace._input.ybins = trace.ybins;
}
@@ -99,11 +109,11 @@ module.exports = function calc(gd, trace) {
// decrease end a little in case of rounding errors
var binspec = trace.xbins,
- binStart = xa.r2c(binspec.start),
- binEnd = xa.r2c(binspec.end) +
- (binStart - Axes.tickIncrement(binStart, binspec.size)) / 1e6;
+ binStart = xr2c(binspec.start),
+ binEnd = xr2c(binspec.end) +
+ (binStart - Axes.tickIncrement(binStart, binspec.size, false, xcalendar)) / 1e6;
- for(i = binStart; i < binEnd; i = Axes.tickIncrement(i, binspec.size)) {
+ for(i = binStart; i < binEnd; i = Axes.tickIncrement(i, binspec.size, false, xcalendar)) {
onecol.push(sizeinit);
if(nonuniformBinsX) xbins.push(i);
if(doavg) zerocol.push(0);
@@ -112,16 +122,16 @@ module.exports = function calc(gd, trace) {
var nx = onecol.length;
x0 = trace.xbins.start;
- var x0c = xa.r2c(x0);
+ var x0c = xr2c(x0);
dx = (i - x0c) / nx;
- x0 = xa.c2r(x0c + dx / 2);
+ x0 = xc2r(x0c + dx / 2);
binspec = trace.ybins;
- binStart = ya.r2c(binspec.start);
- binEnd = ya.r2c(binspec.end) +
- (binStart - Axes.tickIncrement(binStart, binspec.size)) / 1e6;
+ binStart = yr2c(binspec.start);
+ binEnd = yr2c(binspec.end) +
+ (binStart - Axes.tickIncrement(binStart, binspec.size, false, ycalendar)) / 1e6;
- for(i = binStart; i < binEnd; i = Axes.tickIncrement(i, binspec.size)) {
+ for(i = binStart; i < binEnd; i = Axes.tickIncrement(i, binspec.size, false, ycalendar)) {
z.push(onecol.concat());
if(nonuniformBinsY) ybins.push(i);
if(doavg) counts.push(zerocol.concat());
@@ -130,9 +140,9 @@ module.exports = function calc(gd, trace) {
var ny = z.length;
y0 = trace.ybins.start;
- var y0c = ya.r2c(y0);
+ var y0c = yr2c(y0);
dy = (i - y0c) / ny;
- y0 = ya.c2r(y0c + dy / 2);
+ y0 = yc2r(y0c + dy / 2);
if(densitynorm) {
xinc = onecol.map(function(v, i) {
@@ -149,15 +159,15 @@ module.exports = function calc(gd, trace) {
// we already have this, but uniform with start/end/size they're still strings.
if(!nonuniformBinsX && xa.type === 'date') {
xbins = {
- start: xa.r2c(xbins.start),
- end: xa.r2c(xbins.end),
+ start: xr2c(xbins.start),
+ end: xr2c(xbins.end),
size: xbins.size
};
}
if(!nonuniformBinsY && ya.type === 'date') {
ybins = {
- start: ya.r2c(ybins.start),
- end: ya.r2c(ybins.end),
+ start: yr2c(ybins.start),
+ end: yr2c(ybins.end),
size: ybins.size
};
}
diff --git a/src/traces/histogram2d/defaults.js b/src/traces/histogram2d/defaults.js
index 563c59563d1..8d9d1582fec 100644
--- a/src/traces/histogram2d/defaults.js
+++ b/src/traces/histogram2d/defaults.js
@@ -16,12 +16,12 @@ var colorscaleDefaults = require('../../components/colorscale/defaults');
var attributes = require('./attributes');
-module.exports = function supplyDefaults(traceIn, traceOut, layout) {
+module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
- handleSampleDefaults(traceIn, traceOut, coerce);
+ handleSampleDefaults(traceIn, traceOut, coerce, layout);
var zsmooth = coerce('zsmooth');
if(zsmooth === false) {
diff --git a/src/traces/histogram2d/sample_defaults.js b/src/traces/histogram2d/sample_defaults.js
index b6197d490f7..37070082c48 100644
--- a/src/traces/histogram2d/sample_defaults.js
+++ b/src/traces/histogram2d/sample_defaults.js
@@ -9,10 +9,11 @@
'use strict';
+var Registry = require('../../registry');
var handleBinDefaults = require('../histogram/bin_defaults');
-module.exports = function handleSampleDefaults(traceIn, traceOut, coerce) {
+module.exports = function handleSampleDefaults(traceIn, traceOut, coerce, layout) {
var x = coerce('x'),
y = coerce('y');
@@ -24,6 +25,9 @@ module.exports = function handleSampleDefaults(traceIn, traceOut, coerce) {
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/defaults.js b/src/traces/histogram2dcontour/defaults.js
index 18331ac99ff..be0d9f457f8 100644
--- a/src/traces/histogram2dcontour/defaults.js
+++ b/src/traces/histogram2dcontour/defaults.js
@@ -21,7 +21,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
- handleSampleDefaults(traceIn, traceOut, coerce);
+ handleSampleDefaults(traceIn, traceOut, coerce, layout);
var contourStart = Lib.coerce2(traceIn, traceOut, attributes, 'contours.start'),
contourEnd = Lib.coerce2(traceIn, traceOut, attributes, 'contours.end'),
diff --git a/src/traces/mesh3d/convert.js b/src/traces/mesh3d/convert.js
index 9652842315a..7435526fc9f 100644
--- a/src/traces/mesh3d/convert.js
+++ b/src/traces/mesh3d/convert.js
@@ -75,16 +75,16 @@ proto.update = function(data) {
this.data = data;
// Unpack position data
- function toDataCoords(axis, coord, scale) {
+ function toDataCoords(axis, coord, scale, calendar) {
return coord.map(function(x) {
- return axis.d2l(x) * scale;
+ return axis.d2l(x, 0, calendar) * scale;
});
}
var positions = zip3(
- toDataCoords(layout.xaxis, data.x, scene.dataScale[0]),
- toDataCoords(layout.yaxis, data.y, scene.dataScale[1]),
- toDataCoords(layout.zaxis, data.z, scene.dataScale[2]));
+ toDataCoords(layout.xaxis, data.x, scene.dataScale[0], data.xcalendar),
+ toDataCoords(layout.yaxis, data.y, scene.dataScale[1], data.ycalendar),
+ toDataCoords(layout.zaxis, data.z, scene.dataScale[2], data.zcalendar));
var cells;
if(data.i && data.j && data.k) {
diff --git a/src/traces/mesh3d/defaults.js b/src/traces/mesh3d/defaults.js
index 54feaaad32f..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');
@@ -48,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/defaults.js b/src/traces/ohlc/defaults.js
index 791535de251..cf1a32ed970 100644
--- a/src/traces/ohlc/defaults.js
+++ b/src/traces/ohlc/defaults.js
@@ -15,14 +15,14 @@ var handleDirectionDefaults = require('./direction_defaults');
var attributes = require('./attributes');
var helpers = require('./helpers');
-module.exports = function supplyDefaults(traceIn, traceOut) {
+module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
helpers.pushDummyTransformOpts(traceIn, traceOut);
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
- var len = handleOHLC(traceIn, traceOut, coerce);
+ var len = handleOHLC(traceIn, traceOut, coerce, layout);
if(len === 0) {
traceOut.visible = false;
return;
diff --git a/src/traces/ohlc/ohlc_defaults.js b/src/traces/ohlc/ohlc_defaults.js
index 33e335f4188..0fe5abdfda7 100644
--- a/src/traces/ohlc/ohlc_defaults.js
+++ b/src/traces/ohlc/ohlc_defaults.js
@@ -9,7 +9,10 @@
'use strict';
-module.exports = function handleOHLC(traceIn, traceOut, coerce) {
+var Registry = require('../../registry');
+
+
+module.exports = function handleOHLC(traceIn, traceOut, coerce, layout) {
var len;
var x = coerce('x'),
@@ -18,6 +21,9 @@ module.exports = function handleOHLC(traceIn, traceOut, coerce) {
low = coerce('low'),
close = coerce('close');
+ var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
+ handleCalendarDefaults(traceIn, traceOut, ['x'], layout);
+
len = Math.min(open.length, high.length, low.length, close.length);
if(x) {
diff --git a/src/traces/ohlc/transform.js b/src/traces/ohlc/transform.js
index 93ea567cea9..c1e90ddd00f 100644
--- a/src/traces/ohlc/transform.js
+++ b/src/traces/ohlc/transform.js
@@ -73,6 +73,7 @@ function makeTrace(traceIn, state, direction) {
// to make autotype catch date axes soon!!
x: traceIn.x || [0],
+ xcalendar: traceIn.xcalendar,
// concat low and high to get correct autorange
y: [].concat(traceIn.low).concat(traceIn.high),
@@ -138,12 +139,13 @@ exports.calcTransform = function calcTransform(gd, trace, opts) {
if(trace._fullInput.x) {
appendX = function(i) {
var xi = trace.x[i],
- xcalc = xa.d2c(xi);
+ xcalendar = trace.xcalendar,
+ xcalc = xa.d2c(xi, 0, xcalendar);
x.push(
- xa.c2d(xcalc - tickWidth),
+ xa.c2d(xcalc - tickWidth, 0, xcalendar),
xi, xi, xi, xi,
- xa.c2d(xcalc + tickWidth),
+ xa.c2d(xcalc + tickWidth, 0, xcalendar),
null);
};
}
@@ -236,7 +238,8 @@ function convertTickWidth(gd, xa, trace) {
// - handle trace of length 1 separately.
if(_trace.x && _trace.x.length > 1) {
- var _minDiff = Lib.distinctVals(_trace.x.map(xa.d2c)).minDiff;
+ var xcalc = Lib.simpleMap(_trace.x, xa.d2c, 0, trace.xcalendar),
+ _minDiff = Lib.distinctVals(xcalc).minDiff;
minDiff = Math.min(minDiff, _minDiff);
}
}
diff --git a/src/traces/scatter/defaults.js b/src/traces/scatter/defaults.js
index 115ef4c757e..c5fc7259dc8 100644
--- a/src/traces/scatter/defaults.js
+++ b/src/traces/scatter/defaults.js
@@ -28,7 +28,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
- var len = handleXYDefaults(traceIn, traceOut, coerce),
+ var len = handleXYDefaults(traceIn, traceOut, layout, coerce),
// TODO: default mode by orphan points...
defaultMode = len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines';
if(!len) {
diff --git a/src/traces/scatter/plot.js b/src/traces/scatter/plot.js
index 3cd4663ea40..a026a3dd2fa 100644
--- a/src/traces/scatter/plot.js
+++ b/src/traces/scatter/plot.js
@@ -496,8 +496,8 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
function selectMarkers(gd, idx, plotinfo, cdscatter, cdscatterAll) {
var xa = plotinfo.xaxis,
ya = plotinfo.yaxis,
- xr = d3.extent(xa.range.map(xa.r2l).map(xa.l2c)),
- yr = d3.extent(ya.range.map(ya.r2l).map(ya.l2c));
+ xr = d3.extent(Lib.simpleMap(xa.range, xa.r2c)),
+ yr = d3.extent(Lib.simpleMap(ya.range, ya.r2c));
var trace = cdscatter[0].trace;
if(!subTypes.hasMarkers(trace)) return;
diff --git a/src/traces/scatter/xy_defaults.js b/src/traces/scatter/xy_defaults.js
index d85a0aea2b1..7e6ff7409f7 100644
--- a/src/traces/scatter/xy_defaults.js
+++ b/src/traces/scatter/xy_defaults.js
@@ -9,12 +9,17 @@
'use strict';
+var Registry = require('../../registry');
-module.exports = function handleXYDefaults(traceIn, traceOut, coerce) {
+
+module.exports = function handleXYDefaults(traceIn, traceOut, layout, coerce) {
var len,
x = coerce('x'),
y = coerce('y');
+ var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
+ handleCalendarDefaults(traceIn, traceOut, ['x', 'y'], layout);
+
if(x) {
if(y) {
len = Math.min(x.length, y.length);
diff --git a/src/traces/scatter3d/attributes.js b/src/traces/scatter3d/attributes.js
index 5208068b5de..836c4511cb4 100644
--- a/src/traces/scatter3d/attributes.js
+++ b/src/traces/scatter3d/attributes.js
@@ -65,6 +65,7 @@ module.exports = {
valType: 'data_array',
description: 'Sets the z coordinates.'
},
+
text: extendFlat({}, scatterAttrs.text, {
description: [
'Sets text elements associated with each (x,y,z) triplet.',
diff --git a/src/traces/scatter3d/convert.js b/src/traces/scatter3d/convert.js
index c58aa8a2674..b16a08d667c 100644
--- a/src/traces/scatter3d/convert.js
+++ b/src/traces/scatter3d/convert.js
@@ -177,14 +177,17 @@ function convertPlotlyOptions(scene, data) {
yc, y = data.y || [],
zc, z = data.z || [],
len = x.length,
+ xcalendar = data.xcalendar,
+ ycalendar = data.ycalendar,
+ zcalendar = data.zcalendar,
text;
// Convert points
for(i = 0; i < len; i++) {
// sanitize numbers and apply transforms based on axes.type
- xc = xaxis.d2l(x[i]) * scaleFactor[0];
- yc = yaxis.d2l(y[i]) * scaleFactor[1];
- zc = zaxis.d2l(z[i]) * scaleFactor[2];
+ xc = xaxis.d2l(x[i], 0, xcalendar) * scaleFactor[0];
+ yc = yaxis.d2l(y[i], 0, ycalendar) * scaleFactor[1];
+ zc = zaxis.d2l(z[i], 0, zcalendar) * scaleFactor[2];
points[i] = [xc, yc, zc];
}
diff --git a/src/traces/scatter3d/defaults.js b/src/traces/scatter3d/defaults.js
index 9dcf04788f8..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');
@@ -26,7 +27,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
- var len = handleXYZDefaults(traceIn, traceOut, coerce);
+ var len = handleXYZDefaults(traceIn, traceOut, coerce, layout);
if(!len) {
traceOut.visible = false;
return;
@@ -66,12 +67,15 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'z'});
};
-function handleXYZDefaults(traceIn, traceOut, coerce) {
+function handleXYZDefaults(traceIn, traceOut, coerce, layout) {
var len = 0,
x = coerce('x'),
y = coerce('y'),
z = coerce('z');
+ 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);
if(len < x.length) traceOut.x = x.slice(0, len);
diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js
index 55c8b2681a8..c5bb9c12bc5 100644
--- a/src/traces/scattergl/attributes.js
+++ b/src/traces/scattergl/attributes.js
@@ -27,6 +27,7 @@ module.exports = {
y: scatterAttrs.y,
y0: scatterAttrs.y0,
dy: scatterAttrs.dy,
+
text: extendFlat({}, scatterAttrs.text, {
description: [
'Sets text elements associated with each (x,y) pair to appear on hover.',
diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js
index 60725bb1aa8..a5e07c095c5 100644
--- a/src/traces/scattergl/convert.js
+++ b/src/traces/scattergl/convert.js
@@ -298,8 +298,10 @@ proto.updateFast = function(options) {
var xx, yy;
+ var xcalendar = options.xcalendar;
+
var fastType = allFastTypesLikely(x);
- var isDateTime = !fastType && autoType(x) === 'date';
+ var isDateTime = !fastType && autoType(x, xcalendar) === 'date';
// TODO add 'very fast' mode that bypasses this loop
// TODO bypass this on modebar +/- zoom
@@ -312,7 +314,7 @@ proto.updateFast = function(options) {
if(isNumeric(yy)) {
if(!fastType) {
- xx = Lib.dateTime2ms(xx);
+ xx = Lib.dateTime2ms(xx, xcalendar);
}
idToIndex[pId++] = i;
@@ -397,12 +399,8 @@ proto.updateFancy = function(options) {
ptrX = 0,
ptrY = 0;
- var getX = (xaxis.type === 'log') ?
- function(x) { return xaxis.d2l(x); } :
- function(x) { return x; };
- var getY = (yaxis.type === 'log') ?
- function(y) { return yaxis.d2l(y); } :
- function(y) { return y; };
+ var getX = (xaxis.type === 'log') ? xaxis.d2l : function(x) { return x; };
+ var getY = (yaxis.type === 'log') ? yaxis.d2l : function(y) { return y; };
var i, j, xx, yy, ex0, ex1, ey0, ey1;
diff --git a/src/traces/scattergl/defaults.js b/src/traces/scattergl/defaults.js
index b1a8effb015..3d15c0d0a3e 100644
--- a/src/traces/scattergl/defaults.js
+++ b/src/traces/scattergl/defaults.js
@@ -27,7 +27,7 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
- var len = handleXYDefaults(traceIn, traceOut, coerce);
+ var len = handleXYDefaults(traceIn, traceOut, layout, coerce);
if(!len) {
traceOut.visible = false;
return;
diff --git a/src/traces/surface/attributes.js b/src/traces/surface/attributes.js
index 7bd693a1894..47a0eaf725d 100644
--- a/src/traces/surface/attributes.js
+++ b/src/traces/surface/attributes.js
@@ -109,6 +109,7 @@ module.exports = {
valType: 'data_array',
description: 'Sets the y coordinates.'
},
+
text: {
valType: 'data_array',
description: 'Sets the text elements associated with each z value.'
diff --git a/src/traces/surface/convert.js b/src/traces/surface/convert.js
index 2b3fbbda9ee..a430cd4bef1 100644
--- a/src/traces/surface/convert.js
+++ b/src/traces/surface/convert.js
@@ -61,9 +61,9 @@ proto.handlePick = function(selection) {
var sceneLayout = this.scene.fullSceneLayout;
selection.dataCoordinate = [
- sceneLayout.xaxis.d2l(traceCoordinate[0]) * this.scene.dataScale[0],
- sceneLayout.yaxis.d2l(traceCoordinate[1]) * this.scene.dataScale[1],
- sceneLayout.zaxis.d2l(traceCoordinate[2]) * this.scene.dataScale[2]
+ sceneLayout.xaxis.d2l(traceCoordinate[0], 0, this.data.xcalendar) * this.scene.dataScale[0],
+ sceneLayout.yaxis.d2l(traceCoordinate[1], 0, this.data.ycalendar) * this.scene.dataScale[1],
+ sceneLayout.zaxis.d2l(traceCoordinate[2], 0, this.data.zcalendar) * this.scene.dataScale[2]
];
var text = this.data.text;
@@ -213,31 +213,36 @@ proto.update = function(data) {
* and that the sub-array entries correspond to a x-coords,
* which is the transpose of 'gl-surface-plot'.
*/
+
+ var xcalendar = data.xcalendar,
+ ycalendar = data.ycalendar,
+ zcalendar = data.zcalendar;
+
fill(coords[2], function(row, col) {
- return zaxis.d2l(z[col][row]) * scaleFactor[2];
+ return zaxis.d2l(z[col][row], 0, zcalendar) * scaleFactor[2];
});
// coords x
if(Array.isArray(x[0])) {
fill(xc, function(row, col) {
- return xaxis.d2l(x[col][row]) * scaleFactor[0];
+ return xaxis.d2l(x[col][row], 0, xcalendar) * scaleFactor[0];
});
} else {
// ticks x
fill(xc, function(row) {
- return xaxis.d2l(x[row]) * scaleFactor[0];
+ return xaxis.d2l(x[row], 0, xcalendar) * scaleFactor[0];
});
}
// coords y
if(Array.isArray(y[0])) {
fill(yc, function(row, col) {
- return yaxis.d2l(y[col][row]) * scaleFactor[1];
+ return yaxis.d2l(y[col][row], 0, ycalendar) * scaleFactor[1];
});
} else {
// ticks y
fill(yc, function(row, col) {
- return yaxis.d2l(y[col]) * scaleFactor[1];
+ return yaxis.d2l(y[col], 0, ycalendar) * scaleFactor[1];
});
}
diff --git a/src/traces/surface/defaults.js b/src/traces/surface/defaults.js
index a03da51d3a5..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,6 +35,9 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
coerce('x');
coerce('y');
+ var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
+ handleCalendarDefaults(traceIn, traceOut, ['x', 'y', 'z'], layout);
+
if(!Array.isArray(traceOut.x)) {
// build a linearly scaled x
traceOut.x = [];
diff --git a/src/transforms/filter.js b/src/transforms/filter.js
index b1c6d704b2e..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');
@@ -116,6 +117,9 @@ exports.supplyDefaults = function(transformIn) {
coerce('operation');
coerce('value');
coerce('target');
+
+ var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleDefaults');
+ handleCalendarDefaults(transformIn, transformOut, 'calendar', null);
}
return transformOut;
@@ -130,8 +134,9 @@ exports.calcTransform = function(gd, trace, opts) {
if(!len) return;
- var dataToCoord = getDataToCoordFunc(gd, trace, target),
- filterFunc = getFilterFunc(opts, dataToCoord),
+ var targetCalendar = Lib.nestedProperty(trace, target + 'calendar').get(),
+ dataToCoord = getDataToCoordFunc(gd, trace, target),
+ filterFunc = getFilterFunc(opts, dataToCoord, targetCalendar),
arrayAttrs = PlotSchema.findArrayAttributes(trace),
originalArrays = {};
@@ -188,9 +193,11 @@ function getDataToCoordFunc(gd, trace, target) {
setConvert(ax);
- // build up ax._categories (usually done during ax.makeCalcdata()
- for(var i = 0; i < target.length; i++) {
- ax.d2c(target[i]);
+ if(ax.type === 'category') {
+ // build up ax._categories (usually done during ax.makeCalcdata()
+ for(var i = 0; i < target.length; i++) {
+ ax.d2c(target[i]);
+ }
}
}
else {
@@ -210,7 +217,7 @@ function getDataToCoordFunc(gd, trace, target) {
return function(v) { return +v; };
}
-function getFilterFunc(opts, d2c) {
+function getFilterFunc(opts, d2c, targetCalendar) {
var operation = opts.operation,
value = opts.value,
hasArrayValue = Array.isArray(value);
@@ -219,93 +226,96 @@ function getFilterFunc(opts, d2c) {
return array.indexOf(operation) !== -1;
}
+ var d2cValue = function(v) { return d2c(v, 0, opts.calendar); },
+ d2cTarget = function(v) { return d2c(v, 0, targetCalendar); };
+
var coercedValue;
if(isOperationIn(INEQUALITY_OPS)) {
- coercedValue = hasArrayValue ? d2c(value[0]) : d2c(value);
+ coercedValue = hasArrayValue ? d2cValue(value[0]) : d2cValue(value);
}
else if(isOperationIn(INTERVAL_OPS)) {
coercedValue = hasArrayValue ?
- [d2c(value[0]), d2c(value[1])] :
- [d2c(value), d2c(value)];
+ [d2cValue(value[0]), d2cValue(value[1])] :
+ [d2cValue(value), d2cValue(value)];
}
else if(isOperationIn(SET_OPS)) {
- coercedValue = hasArrayValue ? value.map(d2c) : [d2c(value)];
+ coercedValue = hasArrayValue ? value.map(d2cValue) : [d2cValue(value)];
}
switch(operation) {
case '=':
- return function(v) { return d2c(v) === coercedValue; };
+ return function(v) { return d2cTarget(v) === coercedValue; };
case '<':
- return function(v) { return d2c(v) < coercedValue; };
+ return function(v) { return d2cTarget(v) < coercedValue; };
case '<=':
- return function(v) { return d2c(v) <= coercedValue; };
+ return function(v) { return d2cTarget(v) <= coercedValue; };
case '>':
- return function(v) { return d2c(v) > coercedValue; };
+ return function(v) { return d2cTarget(v) > coercedValue; };
case '>=':
- return function(v) { return d2c(v) >= coercedValue; };
+ return function(v) { return d2cTarget(v) >= coercedValue; };
case '[]':
return function(v) {
- var cv = d2c(v);
+ var cv = d2cTarget(v);
return cv >= coercedValue[0] && cv <= coercedValue[1];
};
case '()':
return function(v) {
- var cv = d2c(v);
+ var cv = d2cTarget(v);
return cv > coercedValue[0] && cv < coercedValue[1];
};
case '[)':
return function(v) {
- var cv = d2c(v);
+ var cv = d2cTarget(v);
return cv >= coercedValue[0] && cv < coercedValue[1];
};
case '(]':
return function(v) {
- var cv = d2c(v);
+ var cv = d2cTarget(v);
return cv > coercedValue[0] && cv <= coercedValue[1];
};
case '][':
return function(v) {
- var cv = d2c(v);
+ var cv = d2cTarget(v);
return cv <= coercedValue[0] || cv >= coercedValue[1];
};
case ')(':
return function(v) {
- var cv = d2c(v);
+ var cv = d2cTarget(v);
return cv < coercedValue[0] || cv > coercedValue[1];
};
case '](':
return function(v) {
- var cv = d2c(v);
+ var cv = d2cTarget(v);
return cv <= coercedValue[0] || cv > coercedValue[1];
};
case ')[':
return function(v) {
- var cv = d2c(v);
+ var cv = d2cTarget(v);
return cv < coercedValue[0] || cv >= coercedValue[1];
};
case '{}':
return function(v) {
- return coercedValue.indexOf(d2c(v)) !== -1;
+ return coercedValue.indexOf(d2cTarget(v)) !== -1;
};
case '}{':
return function(v) {
- return coercedValue.indexOf(d2c(v)) === -1;
+ return coercedValue.indexOf(d2cTarget(v)) === -1;
};
}
}
diff --git a/test/image/baselines/candlestick_rangeslider_thai.png b/test/image/baselines/candlestick_rangeslider_thai.png
new file mode 100644
index 00000000000..60880772a07
Binary files /dev/null and b/test/image/baselines/candlestick_rangeslider_thai.png differ
diff --git a/test/image/baselines/finance_style.png b/test/image/baselines/finance_style.png
index 6bf7d6fa708..c9902e3eb3f 100644
Binary files a/test/image/baselines/finance_style.png and b/test/image/baselines/finance_style.png differ
diff --git a/test/image/baselines/gl2d_date_axes.png b/test/image/baselines/gl2d_date_axes.png
index bc2b828d88a..96fd04f20ab 100644
Binary files a/test/image/baselines/gl2d_date_axes.png and b/test/image/baselines/gl2d_date_axes.png differ
diff --git a/test/image/baselines/gl3d_scatter-date.png b/test/image/baselines/gl3d_scatter-date.png
deleted file mode 100644
index 2a60ee8b50e..00000000000
Binary files a/test/image/baselines/gl3d_scatter-date.png and /dev/null differ
diff --git a/test/image/baselines/gl3d_world-cals.png b/test/image/baselines/gl3d_world-cals.png
new file mode 100644
index 00000000000..ffbc6fc552e
Binary files /dev/null and b/test/image/baselines/gl3d_world-cals.png differ
diff --git a/test/image/baselines/world-cals.png b/test/image/baselines/world-cals.png
new file mode 100644
index 00000000000..a5263a73199
Binary files /dev/null and b/test/image/baselines/world-cals.png differ
diff --git a/test/image/mocks/candlestick_double-y-axis.json b/test/image/mocks/candlestick_double-y-axis.json
index 3d94eaa9e0f..ecb8a71b398 100644
--- a/test/image/mocks/candlestick_double-y-axis.json
+++ b/test/image/mocks/candlestick_double-y-axis.json
@@ -1947,8 +1947,8 @@
"layout": {
"xaxis": {
"range": [
- 1452453861917,
- 1471240462978
+ "2016-01-10 19:24:21.917",
+ "2016-08-15 05:54:22.978"
]
},
"yaxis": {
diff --git a/test/image/mocks/candlestick_rangeslider_thai.json b/test/image/mocks/candlestick_rangeslider_thai.json
new file mode 100644
index 00000000000..e9d13a688e9
--- /dev/null
+++ b/test/image/mocks/candlestick_rangeslider_thai.json
@@ -0,0 +1,1975 @@
+{
+ "data": [
+ {
+ "type": "candlestick",
+ "name": "ABC",
+ "x": [
+ "2016-09-30",
+ "2016-09-29",
+ "2016-09-28",
+ "2016-09-27",
+ "2016-09-26",
+ "2016-09-23",
+ "2016-09-22",
+ "2016-09-21",
+ "2016-09-20",
+ "2016-09-19",
+ "2016-09-16",
+ "2016-09-15",
+ "2016-09-14",
+ "2016-09-13",
+ "2016-09-12",
+ "2016-09-09",
+ "2016-09-08",
+ "2016-09-07",
+ "2016-09-06",
+ "2016-09-02",
+ "2016-09-01",
+ "2016-08-31",
+ "2016-08-30",
+ "2016-08-29",
+ "2016-08-26",
+ "2016-08-25",
+ "2016-08-24",
+ "2016-08-23",
+ "2016-08-22",
+ "2016-08-19",
+ "2016-08-18",
+ "2016-08-17",
+ "2016-08-16",
+ "2016-08-15",
+ "2016-08-12",
+ "2016-08-11",
+ "2016-08-10",
+ "2016-08-09",
+ "2016-08-08",
+ "2016-08-05",
+ "2016-08-04",
+ "2016-08-03",
+ "2016-08-02",
+ "2016-08-01",
+ "2016-07-29",
+ "2016-07-28",
+ "2016-07-27",
+ "2016-07-26",
+ "2016-07-25",
+ "2016-07-22",
+ "2016-07-21",
+ "2016-07-20",
+ "2016-07-19",
+ "2016-07-18",
+ "2016-07-15",
+ "2016-07-14",
+ "2016-07-13",
+ "2016-07-12",
+ "2016-07-11",
+ "2016-07-08",
+ "2016-07-07",
+ "2016-07-06",
+ "2016-07-05",
+ "2016-07-01",
+ "2016-06-30",
+ "2016-06-29",
+ "2016-06-28",
+ "2016-06-27",
+ "2016-06-24",
+ "2016-06-23",
+ "2016-06-22",
+ "2016-06-21",
+ "2016-06-20",
+ "2016-06-17",
+ "2016-06-16",
+ "2016-06-15",
+ "2016-06-14",
+ "2016-06-13",
+ "2016-06-10",
+ "2016-06-09",
+ "2016-06-08",
+ "2016-06-07",
+ "2016-06-06",
+ "2016-06-03",
+ "2016-06-02",
+ "2016-06-01",
+ "2016-05-31",
+ "2016-05-27",
+ "2016-05-26",
+ "2016-05-25",
+ "2016-05-24",
+ "2016-05-23",
+ "2016-05-20",
+ "2016-05-19",
+ "2016-05-18",
+ "2016-05-17",
+ "2016-05-16",
+ "2016-05-13",
+ "2016-05-12",
+ "2016-05-11",
+ "2016-05-10",
+ "2016-05-09",
+ "2016-05-06",
+ "2016-05-05",
+ "2016-05-04",
+ "2016-05-03",
+ "2016-05-02",
+ "2016-04-29",
+ "2016-04-28",
+ "2016-04-27",
+ "2016-04-26",
+ "2016-04-25",
+ "2016-04-22",
+ "2016-04-21",
+ "2016-04-20",
+ "2016-04-19",
+ "2016-04-18",
+ "2016-04-15",
+ "2016-04-14",
+ "2016-04-13",
+ "2016-04-12",
+ "2016-04-11",
+ "2016-04-08",
+ "2016-04-07",
+ "2016-04-06",
+ "2016-04-05",
+ "2016-04-04",
+ "2016-04-01",
+ "2016-03-31",
+ "2016-03-30",
+ "2016-03-29",
+ "2016-03-28",
+ "2016-03-24",
+ "2016-03-23",
+ "2016-03-22",
+ "2016-03-21",
+ "2016-03-18",
+ "2016-03-17",
+ "2016-03-16",
+ "2016-03-15",
+ "2016-03-14",
+ "2016-03-11",
+ "2016-03-10",
+ "2016-03-09",
+ "2016-03-08",
+ "2016-03-07",
+ "2016-03-04",
+ "2016-03-03",
+ "2016-03-02",
+ "2016-03-01",
+ "2016-02-29",
+ "2016-02-26",
+ "2016-02-25",
+ "2016-02-24",
+ "2016-02-23",
+ "2016-02-22",
+ "2016-02-19",
+ "2016-02-18",
+ "2016-02-17",
+ "2016-02-16",
+ "2016-02-12",
+ "2016-02-11",
+ "2016-02-10",
+ "2016-02-09",
+ "2016-02-08",
+ "2016-02-05",
+ "2016-02-04",
+ "2016-02-03",
+ "2016-02-02",
+ "2016-02-01",
+ "2016-01-29",
+ "2016-01-28",
+ "2016-01-27",
+ "2016-01-26",
+ "2016-01-25",
+ "2016-01-22",
+ "2016-01-21",
+ "2016-01-20",
+ "2016-01-19",
+ "2016-01-15",
+ "2016-01-14",
+ "2016-01-13",
+ "2016-01-12",
+ "2016-01-11",
+ "2016-01-08",
+ "2016-01-07",
+ "2016-01-06",
+ "2016-01-05",
+ "2016-01-04"
+ ],
+ "open": [
+ "776.330017",
+ "781.440002",
+ "777.849976",
+ "775.50",
+ "782.73999",
+ "786.590027",
+ "780.00",
+ "772.659973",
+ "769.00",
+ "772.419983",
+ "769.75",
+ "762.890015",
+ "759.609985",
+ "764.47998",
+ "755.130005",
+ "770.099976",
+ "778.590027",
+ "780.00",
+ "773.450012",
+ "773.01001",
+ "769.25",
+ "767.01001",
+ "769.330017",
+ "768.73999",
+ "769.00",
+ "767.00",
+ "770.580017",
+ "775.47998",
+ "773.27002",
+ "775.00",
+ "780.01001",
+ "777.320007",
+ "780.299988",
+ "783.75",
+ "781.50",
+ "785.00",
+ "783.75",
+ "781.099976",
+ "782.00",
+ "773.780029",
+ "772.219971",
+ "767.179993",
+ "768.690002",
+ "761.090027",
+ "772.710022",
+ "747.039978",
+ "738.280029",
+ "739.039978",
+ "740.669983",
+ "741.859985",
+ "740.359985",
+ "737.330017",
+ "729.890015",
+ "722.710022",
+ "725.72998",
+ "721.580017",
+ "723.619995",
+ "719.119995",
+ "708.049988",
+ "699.50",
+ "698.080017",
+ "689.97998",
+ "696.059998",
+ "692.200012",
+ "685.469971",
+ "683.00",
+ "678.969971",
+ "671.00",
+ "675.169983",
+ "697.450012",
+ "699.059998",
+ "698.400024",
+ "698.77002",
+ "708.650024",
+ "714.909973",
+ "719.00",
+ "716.47998",
+ "716.51001",
+ "719.469971",
+ "722.869995",
+ "723.960022",
+ "719.840027",
+ "724.909973",
+ "729.27002",
+ "732.50",
+ "734.530029",
+ "731.73999",
+ "724.01001",
+ "722.869995",
+ "720.76001",
+ "706.859985",
+ "706.530029",
+ "701.619995",
+ "702.359985",
+ "703.669983",
+ "715.98999",
+ "709.130005",
+ "711.929993",
+ "717.059998",
+ "723.409973",
+ "716.75",
+ "712.00",
+ "698.380005",
+ "697.700012",
+ "690.48999",
+ "696.869995",
+ "697.630005",
+ "690.700012",
+ "708.26001",
+ "707.289978",
+ "725.419983",
+ "716.099976",
+ "726.299988",
+ "755.380005",
+ "758.00",
+ "769.51001",
+ "760.460022",
+ "753.97998",
+ "754.01001",
+ "749.159973",
+ "738.00",
+ "743.02002",
+ "743.969971",
+ "745.369995",
+ "735.77002",
+ "738.00",
+ "750.059998",
+ "738.599976",
+ "749.25",
+ "750.099976",
+ "734.590027",
+ "736.789978",
+ "732.01001",
+ "742.359985",
+ "737.460022",
+ "736.50",
+ "741.859985",
+ "736.450012",
+ "726.369995",
+ "726.919983",
+ "726.809998",
+ "720.00",
+ "708.119995",
+ "698.469971",
+ "688.590027",
+ "706.900024",
+ "714.98999",
+ "718.679993",
+ "719.00",
+ "703.619995",
+ "700.320007",
+ "708.580017",
+ "700.01001",
+ "688.919983",
+ "701.450012",
+ "707.450012",
+ "695.030029",
+ "710.00",
+ "698.090027",
+ "692.97998",
+ "690.26001",
+ "675.00",
+ "686.859985",
+ "672.320007",
+ "667.849976",
+ "703.869995",
+ "722.809998",
+ "770.219971",
+ "784.50",
+ "750.460022",
+ "731.530029",
+ "722.219971",
+ "713.669983",
+ "713.849976",
+ "723.580017",
+ "723.599976",
+ "702.179993",
+ "688.609985",
+ "703.299988",
+ "692.289978",
+ "705.380005",
+ "730.849976",
+ "721.679993",
+ "716.609985",
+ "731.450012",
+ "730.309998",
+ "730.00",
+ "746.450012",
+ "743.00"
+ ],
+ "high": [
+ "780.940002",
+ "785.799988",
+ "781.809998",
+ "785.98999",
+ "782.73999",
+ "788.929993",
+ "789.849976",
+ "777.159973",
+ "773.330017",
+ "774.00",
+ "769.75",
+ "773.799988",
+ "767.679993",
+ "766.219971",
+ "770.289978",
+ "773.244995",
+ "780.349976",
+ "782.72998",
+ "782.00",
+ "773.919983",
+ "771.02002",
+ "769.090027",
+ "774.466003",
+ "774.98999",
+ "776.080017",
+ "771.890015",
+ "774.50",
+ "776.440002",
+ "774.539978",
+ "777.099976",
+ "782.859985",
+ "780.809998",
+ "780.97998",
+ "787.48999",
+ "783.39502",
+ "789.75",
+ "786.812012",
+ "788.940002",
+ "782.630005",
+ "783.039978",
+ "774.070007",
+ "773.210022",
+ "775.840027",
+ "780.429993",
+ "778.549988",
+ "748.650024",
+ "744.460022",
+ "741.690002",
+ "742.609985",
+ "743.23999",
+ "741.690002",
+ "742.130005",
+ "736.98999",
+ "736.130005",
+ "725.73999",
+ "722.210022",
+ "724.00",
+ "722.940002",
+ "716.51001",
+ "705.710022",
+ "698.200012",
+ "701.679993",
+ "696.940002",
+ "700.650024",
+ "692.320007",
+ "687.429016",
+ "680.330017",
+ "672.299988",
+ "689.400024",
+ "701.950012",
+ "700.859985",
+ "702.77002",
+ "702.47998",
+ "708.820007",
+ "716.650024",
+ "722.97998",
+ "722.469971",
+ "725.440002",
+ "725.890015",
+ "729.539978",
+ "728.570007",
+ "721.97998",
+ "724.909973",
+ "729.48999",
+ "733.02002",
+ "737.210022",
+ "739.72998",
+ "733.935974",
+ "728.330017",
+ "727.51001",
+ "720.969971",
+ "711.478027",
+ "714.580017",
+ "706.00",
+ "711.599976",
+ "721.52002",
+ "718.47998",
+ "716.661987",
+ "719.25",
+ "724.47998",
+ "723.50",
+ "718.710022",
+ "711.859985",
+ "702.320007",
+ "699.75",
+ "697.840027",
+ "700.640015",
+ "697.619995",
+ "714.169983",
+ "708.97998",
+ "725.765991",
+ "723.929993",
+ "736.119995",
+ "760.450012",
+ "758.132019",
+ "769.900024",
+ "768.049988",
+ "761.00",
+ "757.309998",
+ "754.380005",
+ "743.830017",
+ "745.00",
+ "745.450012",
+ "747.00",
+ "746.23999",
+ "742.799988",
+ "752.799988",
+ "750.340027",
+ "750.849976",
+ "757.880005",
+ "747.25",
+ "738.98999",
+ "737.747009",
+ "745.719971",
+ "745.00",
+ "742.50",
+ "742.00",
+ "743.070007",
+ "737.469971",
+ "732.289978",
+ "735.50",
+ "726.919983",
+ "716.440002",
+ "705.679993",
+ "703.789978",
+ "708.091003",
+ "716.48999",
+ "719.450012",
+ "720.00",
+ "718.809998",
+ "710.890015",
+ "713.429993",
+ "705.97998",
+ "700.00",
+ "708.400024",
+ "713.23999",
+ "703.080994",
+ "712.349976",
+ "709.75",
+ "698.00",
+ "693.75",
+ "689.349976",
+ "701.309998",
+ "699.900024",
+ "684.030029",
+ "703.98999",
+ "727.00",
+ "774.50",
+ "789.869995",
+ "757.859985",
+ "744.98999",
+ "733.690002",
+ "718.234985",
+ "718.280029",
+ "729.679993",
+ "728.130005",
+ "719.190002",
+ "706.849976",
+ "709.97998",
+ "706.73999",
+ "721.924988",
+ "734.73999",
+ "728.75",
+ "718.85498",
+ "733.22998",
+ "738.50",
+ "747.179993",
+ "752.00",
+ "744.059998"
+ ],
+ "low": [
+ "774.090027",
+ "774.231995",
+ "774.969971",
+ "774.307983",
+ "773.070007",
+ "784.150024",
+ "778.440002",
+ "768.301025",
+ "768.530029",
+ "764.440979",
+ "764.659973",
+ "759.960022",
+ "759.109985",
+ "755.799988",
+ "754.00",
+ "759.659973",
+ "773.580017",
+ "776.200012",
+ "771.00",
+ "768.409973",
+ "764.299988",
+ "765.380005",
+ "766.840027",
+ "766.61499",
+ "765.849976",
+ "763.184998",
+ "767.070007",
+ "771.784973",
+ "770.049988",
+ "773.130005",
+ "777.00",
+ "773.530029",
+ "773.44397",
+ "780.109985",
+ "780.400024",
+ "782.969971",
+ "782.778015",
+ "780.570007",
+ "778.091003",
+ "772.340027",
+ "768.794983",
+ "766.820007",
+ "767.849976",
+ "761.090027",
+ "766.77002",
+ "739.299988",
+ "737.00",
+ "734.27002",
+ "737.50",
+ "736.559998",
+ "735.830994",
+ "737.099976",
+ "729.00",
+ "721.190002",
+ "719.054993",
+ "718.030029",
+ "716.849976",
+ "715.909973",
+ "707.23999",
+ "696.434998",
+ "688.215027",
+ "689.090027",
+ "688.880005",
+ "692.130005",
+ "683.650024",
+ "681.409973",
+ "673.00",
+ "663.283997",
+ "673.450012",
+ "687.00",
+ "693.08197",
+ "692.01001",
+ "693.409973",
+ "688.452026",
+ "703.26001",
+ "717.309998",
+ "713.119995",
+ "716.51001",
+ "716.429993",
+ "722.335999",
+ "720.580017",
+ "716.549988",
+ "714.609985",
+ "720.559998",
+ "724.169983",
+ "730.659973",
+ "731.26001",
+ "724.00",
+ "720.280029",
+ "719.705017",
+ "706.859985",
+ "704.179993",
+ "700.52002",
+ "696.799988",
+ "700.630005",
+ "704.109985",
+ "705.650024",
+ "709.26001",
+ "709.00",
+ "712.799988",
+ "715.719971",
+ "710.00",
+ "698.106995",
+ "695.719971",
+ "689.01001",
+ "692.00",
+ "691.00",
+ "689.00",
+ "689.549988",
+ "692.36499",
+ "703.026001",
+ "715.590027",
+ "713.609985",
+ "749.549988",
+ "750.01001",
+ "749.330017",
+ "757.299988",
+ "752.69397",
+ "752.705017",
+ "744.260986",
+ "731.01001",
+ "736.049988",
+ "735.549988",
+ "736.280029",
+ "735.559998",
+ "735.369995",
+ "742.429993",
+ "737.00",
+ "740.940002",
+ "748.73999",
+ "728.76001",
+ "732.50",
+ "731.00",
+ "736.150024",
+ "737.460022",
+ "733.515991",
+ "731.830017",
+ "736.00",
+ "724.51001",
+ "724.77002",
+ "725.150024",
+ "717.125",
+ "703.359985",
+ "694.00",
+ "685.340027",
+ "686.900024",
+ "706.02002",
+ "706.02002",
+ "712.00",
+ "699.77002",
+ "697.679993",
+ "700.859985",
+ "690.585022",
+ "680.780029",
+ "693.580017",
+ "702.51001",
+ "694.049988",
+ "696.030029",
+ "691.380005",
+ "685.049988",
+ "678.599976",
+ "668.867981",
+ "682.130005",
+ "668.77002",
+ "663.059998",
+ "680.150024",
+ "701.859985",
+ "720.50",
+ "764.650024",
+ "743.27002",
+ "726.799988",
+ "712.349976",
+ "694.390015",
+ "706.47998",
+ "710.01001",
+ "720.120972",
+ "694.460022",
+ "673.26001",
+ "693.409973",
+ "685.369995",
+ "689.099976",
+ "698.609985",
+ "717.317017",
+ "703.539978",
+ "713.00",
+ "719.059998",
+ "728.919983",
+ "738.640015",
+ "731.257996"
+ ],
+ "close": [
+ "777.289978",
+ "775.01001",
+ "781.559998",
+ "783.01001",
+ "774.210022",
+ "786.900024",
+ "787.210022",
+ "776.219971",
+ "771.409973",
+ "765.700012",
+ "768.880005",
+ "771.76001",
+ "762.48999",
+ "759.690002",
+ "769.02002",
+ "759.659973",
+ "775.320007",
+ "780.349976",
+ "780.080017",
+ "771.460022",
+ "768.780029",
+ "767.049988",
+ "769.090027",
+ "772.150024",
+ "769.539978",
+ "769.409973",
+ "769.640015",
+ "772.080017",
+ "772.150024",
+ "775.419983",
+ "777.50",
+ "779.909973",
+ "777.140015",
+ "782.440002",
+ "783.219971",
+ "784.849976",
+ "784.679993",
+ "784.26001",
+ "781.76001",
+ "782.219971",
+ "771.609985",
+ "773.179993",
+ "771.070007",
+ "772.880005",
+ "768.789978",
+ "745.909973",
+ "741.77002",
+ "738.419983",
+ "739.77002",
+ "742.73999",
+ "738.630005",
+ "741.190002",
+ "736.960022",
+ "733.780029",
+ "719.849976",
+ "720.950012",
+ "716.97998",
+ "720.640015",
+ "715.090027",
+ "705.630005",
+ "695.359985",
+ "697.77002",
+ "694.950012",
+ "699.210022",
+ "692.099976",
+ "684.109985",
+ "680.039978",
+ "668.26001",
+ "675.219971",
+ "701.869995",
+ "697.460022",
+ "695.940002",
+ "693.710022",
+ "691.719971",
+ "710.359985",
+ "718.919983",
+ "718.27002",
+ "718.359985",
+ "719.409973",
+ "728.580017",
+ "728.280029",
+ "716.650024",
+ "716.549988",
+ "722.340027",
+ "730.400024",
+ "734.150024",
+ "735.719971",
+ "732.659973",
+ "724.119995",
+ "725.27002",
+ "720.090027",
+ "704.23999",
+ "709.73999",
+ "700.320007",
+ "706.630005",
+ "706.22998",
+ "716.48999",
+ "710.830017",
+ "713.309998",
+ "715.289978",
+ "723.179993",
+ "712.900024",
+ "711.119995",
+ "701.429993",
+ "695.700012",
+ "692.359985",
+ "698.210022",
+ "693.01001",
+ "691.02002",
+ "705.840027",
+ "708.140015",
+ "723.150024",
+ "718.77002",
+ "759.140015",
+ "752.669983",
+ "753.929993",
+ "766.609985",
+ "759.00",
+ "753.200012",
+ "751.719971",
+ "743.090027",
+ "736.099976",
+ "739.150024",
+ "740.280029",
+ "745.690002",
+ "737.799988",
+ "745.289978",
+ "749.909973",
+ "744.950012",
+ "750.530029",
+ "744.77002",
+ "733.530029",
+ "735.299988",
+ "738.059998",
+ "740.75",
+ "742.090027",
+ "737.599976",
+ "737.780029",
+ "736.090027",
+ "728.330017",
+ "730.48999",
+ "726.820007",
+ "712.820007",
+ "705.23999",
+ "693.969971",
+ "695.159973",
+ "710.890015",
+ "712.419983",
+ "718.849976",
+ "718.809998",
+ "697.77002",
+ "705.070007",
+ "705.75",
+ "699.559998",
+ "695.849976",
+ "706.460022",
+ "700.909973",
+ "697.349976",
+ "708.400024",
+ "691.00",
+ "682.400024",
+ "683.109985",
+ "684.119995",
+ "678.109985",
+ "682.73999",
+ "683.570007",
+ "708.01001",
+ "726.950012",
+ "764.650024",
+ "752.00",
+ "742.950012",
+ "730.960022",
+ "699.98999",
+ "713.039978",
+ "711.669983",
+ "725.25",
+ "706.590027",
+ "698.450012",
+ "701.789978",
+ "694.450012",
+ "714.719971",
+ "700.559998",
+ "726.070007",
+ "716.030029",
+ "714.469971",
+ "726.390015",
+ "743.619995",
+ "742.580017",
+ "741.840027"
+ ],
+ "increasing": {
+ "line": {
+ "color": "#4dac26"
+ },
+ "name": "ABC"
+ },
+ "decreasing": {
+ "line": {
+ "color": "#d01c8b"
+ },
+ "showlegend": false
+ }
+ },
+ {
+ "type": "candlestick",
+ "name": "XYZ",
+ "x": [
+ "2016-09-30",
+ "2016-09-29",
+ "2016-09-28",
+ "2016-09-27",
+ "2016-09-26",
+ "2016-09-23",
+ "2016-09-22",
+ "2016-09-21",
+ "2016-09-20",
+ "2016-09-19",
+ "2016-09-16",
+ "2016-09-15",
+ "2016-09-14",
+ "2016-09-13",
+ "2016-09-12",
+ "2016-09-09",
+ "2016-09-08",
+ "2016-09-07",
+ "2016-09-06",
+ "2016-09-02",
+ "2016-09-01",
+ "2016-08-31",
+ "2016-08-30",
+ "2016-08-29",
+ "2016-08-26",
+ "2016-08-25",
+ "2016-08-24",
+ "2016-08-23",
+ "2016-08-22",
+ "2016-08-19",
+ "2016-08-18",
+ "2016-08-17",
+ "2016-08-16",
+ "2016-08-15",
+ "2016-08-12",
+ "2016-08-11",
+ "2016-08-10",
+ "2016-08-09",
+ "2016-08-08",
+ "2016-08-05",
+ "2016-08-04",
+ "2016-08-03",
+ "2016-08-02",
+ "2016-08-01",
+ "2016-07-29",
+ "2016-07-28",
+ "2016-07-27",
+ "2016-07-26",
+ "2016-07-25",
+ "2016-07-22",
+ "2016-07-21",
+ "2016-07-20",
+ "2016-07-19",
+ "2016-07-18",
+ "2016-07-15",
+ "2016-07-14",
+ "2016-07-13",
+ "2016-07-12",
+ "2016-07-11",
+ "2016-07-08",
+ "2016-07-07",
+ "2016-07-06",
+ "2016-07-05",
+ "2016-07-01",
+ "2016-06-30",
+ "2016-06-29",
+ "2016-06-28",
+ "2016-06-27",
+ "2016-06-24",
+ "2016-06-23",
+ "2016-06-22",
+ "2016-06-21",
+ "2016-06-20",
+ "2016-06-17",
+ "2016-06-16",
+ "2016-06-15",
+ "2016-06-14",
+ "2016-06-13",
+ "2016-06-10",
+ "2016-06-09",
+ "2016-06-08",
+ "2016-06-07",
+ "2016-06-06",
+ "2016-06-03",
+ "2016-06-02",
+ "2016-06-01",
+ "2016-05-31",
+ "2016-05-27",
+ "2016-05-26",
+ "2016-05-25",
+ "2016-05-24",
+ "2016-05-23",
+ "2016-05-20",
+ "2016-05-19",
+ "2016-05-18",
+ "2016-05-17",
+ "2016-05-16",
+ "2016-05-13",
+ "2016-05-12",
+ "2016-05-11",
+ "2016-05-10",
+ "2016-05-09",
+ "2016-05-06",
+ "2016-05-05",
+ "2016-05-04",
+ "2016-05-03",
+ "2016-05-02",
+ "2016-04-29",
+ "2016-04-28",
+ "2016-04-27",
+ "2016-04-26",
+ "2016-04-25",
+ "2016-04-22",
+ "2016-04-21",
+ "2016-04-20",
+ "2016-04-19",
+ "2016-04-18",
+ "2016-04-15",
+ "2016-04-14",
+ "2016-04-13",
+ "2016-04-12",
+ "2016-04-11",
+ "2016-04-08",
+ "2016-04-07",
+ "2016-04-06",
+ "2016-04-05",
+ "2016-04-04",
+ "2016-04-01",
+ "2016-03-31",
+ "2016-03-30",
+ "2016-03-29",
+ "2016-03-28",
+ "2016-03-24",
+ "2016-03-23",
+ "2016-03-22",
+ "2016-03-21",
+ "2016-03-18",
+ "2016-03-17",
+ "2016-03-16",
+ "2016-03-15",
+ "2016-03-14",
+ "2016-03-11",
+ "2016-03-10",
+ "2016-03-09",
+ "2016-03-08",
+ "2016-03-07",
+ "2016-03-04",
+ "2016-03-03",
+ "2016-03-02",
+ "2016-03-01",
+ "2016-02-29",
+ "2016-02-26",
+ "2016-02-25",
+ "2016-02-24",
+ "2016-02-23",
+ "2016-02-22",
+ "2016-02-19",
+ "2016-02-18",
+ "2016-02-17",
+ "2016-02-16",
+ "2016-02-12",
+ "2016-02-11",
+ "2016-02-10",
+ "2016-02-09",
+ "2016-02-08",
+ "2016-02-05",
+ "2016-02-04",
+ "2016-02-03",
+ "2016-02-02",
+ "2016-02-01",
+ "2016-01-29",
+ "2016-01-28",
+ "2016-01-27",
+ "2016-01-26",
+ "2016-01-25",
+ "2016-01-22",
+ "2016-01-21",
+ "2016-01-20",
+ "2016-01-19",
+ "2016-01-15",
+ "2016-01-14",
+ "2016-01-13",
+ "2016-01-12",
+ "2016-01-11",
+ "2016-01-08",
+ "2016-01-07",
+ "2016-01-06",
+ "2016-01-05",
+ "2016-01-04"
+ ],
+ "open": [
+ "112.459999",
+ "113.160004",
+ "113.690002",
+ "113.00",
+ "111.639999",
+ "114.419998",
+ "114.349998",
+ "113.849998",
+ "113.050003",
+ "115.190002",
+ "115.120003",
+ "113.860001",
+ "108.730003",
+ "107.510002",
+ "102.650002",
+ "104.639999",
+ "107.25",
+ "107.830002",
+ "107.900002",
+ "107.699997",
+ "106.139999",
+ "105.660004",
+ "105.800003",
+ "106.620003",
+ "107.410004",
+ "107.389999",
+ "108.57",
+ "108.589996",
+ "108.860001",
+ "108.769997",
+ "109.230003",
+ "109.099998",
+ "109.629997",
+ "108.139999",
+ "107.779999",
+ "108.519997",
+ "108.709999",
+ "108.230003",
+ "107.519997",
+ "106.269997",
+ "105.580002",
+ "104.809998",
+ "106.050003",
+ "104.410004",
+ "104.190002",
+ "102.830002",
+ "104.269997",
+ "96.82",
+ "98.25",
+ "99.260002",
+ "99.830002",
+ "100.00",
+ "99.559998",
+ "98.699997",
+ "98.919998",
+ "97.389999",
+ "97.410004",
+ "97.169998",
+ "96.75",
+ "96.489998",
+ "95.699997",
+ "94.599998",
+ "95.389999",
+ "95.489998",
+ "94.440002",
+ "93.970001",
+ "92.900002",
+ "93.00",
+ "92.910004",
+ "95.940002",
+ "96.25",
+ "94.940002",
+ "96.00",
+ "96.620003",
+ "96.449997",
+ "97.82",
+ "97.32",
+ "98.690002",
+ "98.529999",
+ "98.50",
+ "99.019997",
+ "99.25",
+ "97.989998",
+ "97.790001",
+ "97.599998",
+ "99.019997",
+ "99.599998",
+ "99.440002",
+ "99.68",
+ "98.669998",
+ "97.220001",
+ "95.870003",
+ "94.639999",
+ "94.639999",
+ "94.160004",
+ "94.550003",
+ "92.389999",
+ "90.00",
+ "92.720001",
+ "93.480003",
+ "93.330002",
+ "93.00",
+ "93.370003",
+ "94.00",
+ "95.199997",
+ "94.199997",
+ "93.970001",
+ "93.989998",
+ "97.610001",
+ "96.00",
+ "103.910004",
+ "105.00",
+ "105.010002",
+ "106.93",
+ "106.639999",
+ "107.879997",
+ "108.889999",
+ "112.110001",
+ "111.620003",
+ "110.800003",
+ "109.339996",
+ "108.970001",
+ "108.910004",
+ "109.949997",
+ "110.230003",
+ "109.510002",
+ "110.419998",
+ "108.779999",
+ "109.720001",
+ "108.650002",
+ "104.889999",
+ "106.00",
+ "105.470001",
+ "106.480003",
+ "105.25",
+ "105.93",
+ "106.339996",
+ "105.519997",
+ "104.610001",
+ "103.959999",
+ "101.910004",
+ "102.239998",
+ "101.410004",
+ "101.309998",
+ "100.779999",
+ "102.389999",
+ "102.370003",
+ "100.580002",
+ "100.510002",
+ "97.650002",
+ "96.860001",
+ "97.199997",
+ "96.050003",
+ "93.980003",
+ "96.400002",
+ "96.309998",
+ "96.00",
+ "98.839996",
+ "96.669998",
+ "95.019997",
+ "94.190002",
+ "93.790001",
+ "95.919998",
+ "94.290001",
+ "93.129997",
+ "96.519997",
+ "95.860001",
+ "95.00",
+ "95.419998",
+ "96.470001",
+ "94.790001",
+ "93.790001",
+ "96.040001",
+ "99.93",
+ "101.519997",
+ "98.629997",
+ "97.059998",
+ "95.099998",
+ "98.410004",
+ "96.199997",
+ "97.959999",
+ "100.32",
+ "100.550003",
+ "98.970001",
+ "98.550003",
+ "98.68",
+ "100.559998",
+ "105.75",
+ "102.610001"
+ ],
+ "high": [
+ "113.370003",
+ "113.800003",
+ "114.639999",
+ "113.18",
+ "113.389999",
+ "114.790001",
+ "114.940002",
+ "113.989998",
+ "114.120003",
+ "116.18",
+ "116.129997",
+ "115.730003",
+ "113.029999",
+ "108.790001",
+ "105.720001",
+ "105.720001",
+ "107.269997",
+ "108.760002",
+ "108.300003",
+ "108.00",
+ "106.800003",
+ "106.57",
+ "106.50",
+ "107.440002",
+ "107.949997",
+ "107.879997",
+ "108.75",
+ "109.32",
+ "109.099998",
+ "109.690002",
+ "109.599998",
+ "109.370003",
+ "110.230003",
+ "109.540001",
+ "108.440002",
+ "108.93",
+ "108.900002",
+ "108.940002",
+ "108.370003",
+ "107.650002",
+ "106.00",
+ "105.839996",
+ "106.07",
+ "106.150002",
+ "104.550003",
+ "104.449997",
+ "104.349998",
+ "97.970001",
+ "98.839996",
+ "99.300003",
+ "101.00",
+ "100.459999",
+ "100.00",
+ "100.129997",
+ "99.300003",
+ "98.989998",
+ "97.669998",
+ "97.699997",
+ "97.650002",
+ "96.889999",
+ "96.50",
+ "95.660004",
+ "95.400002",
+ "96.470001",
+ "95.769997",
+ "94.550003",
+ "93.660004",
+ "93.050003",
+ "94.660004",
+ "96.290001",
+ "96.889999",
+ "96.349998",
+ "96.57",
+ "96.650002",
+ "97.75",
+ "98.410004",
+ "98.480003",
+ "99.120003",
+ "99.349998",
+ "99.989998",
+ "99.559998",
+ "99.870003",
+ "101.889999",
+ "98.269997",
+ "97.839996",
+ "99.540001",
+ "100.400002",
+ "100.470001",
+ "100.730003",
+ "99.739998",
+ "98.089996",
+ "97.190002",
+ "95.43",
+ "94.639999",
+ "95.209999",
+ "94.699997",
+ "94.389999",
+ "91.669998",
+ "92.779999",
+ "93.57",
+ "93.57",
+ "93.769997",
+ "93.449997",
+ "94.07",
+ "95.900002",
+ "95.739998",
+ "94.080002",
+ "94.720001",
+ "97.879997",
+ "98.709999",
+ "105.300003",
+ "105.650002",
+ "106.480003",
+ "106.93",
+ "108.089996",
+ "108.00",
+ "108.949997",
+ "112.300003",
+ "112.389999",
+ "112.339996",
+ "110.50",
+ "110.610001",
+ "109.769997",
+ "110.419998",
+ "110.980003",
+ "110.730003",
+ "112.190002",
+ "110.00",
+ "109.900002",
+ "110.419998",
+ "107.790001",
+ "106.190002",
+ "106.25",
+ "107.07",
+ "107.290001",
+ "107.650002",
+ "106.50",
+ "106.470001",
+ "106.309998",
+ "105.18",
+ "102.910004",
+ "102.279999",
+ "102.239998",
+ "101.580002",
+ "101.760002",
+ "102.830002",
+ "103.75",
+ "101.709999",
+ "100.889999",
+ "100.769997",
+ "98.230003",
+ "98.019997",
+ "96.760002",
+ "96.379997",
+ "96.50",
+ "96.900002",
+ "96.760002",
+ "98.889999",
+ "98.209999",
+ "96.849998",
+ "94.50",
+ "94.720001",
+ "96.349998",
+ "95.940002",
+ "95.699997",
+ "96.919998",
+ "97.330002",
+ "96.839996",
+ "96.040001",
+ "96.709999",
+ "97.339996",
+ "94.519997",
+ "96.629997",
+ "100.879997",
+ "101.529999",
+ "101.459999",
+ "97.879997",
+ "98.190002",
+ "98.650002",
+ "97.709999",
+ "100.480003",
+ "101.190002",
+ "100.690002",
+ "99.059998",
+ "99.110001",
+ "100.129997",
+ "102.370003",
+ "105.849998",
+ "105.370003"
+ ],
+ "low": [
+ "111.800003",
+ "111.800003",
+ "113.43",
+ "112.339996",
+ "111.550003",
+ "111.550003",
+ "114.00",
+ "112.440002",
+ "112.510002",
+ "113.25",
+ "114.040001",
+ "113.489998",
+ "108.599998",
+ "107.239998",
+ "102.529999",
+ "103.129997",
+ "105.239998",
+ "107.07",
+ "107.510002",
+ "106.82",
+ "105.620003",
+ "105.639999",
+ "105.50",
+ "106.290001",
+ "106.309998",
+ "106.68",
+ "107.68",
+ "108.529999",
+ "107.849998",
+ "108.360001",
+ "109.019997",
+ "108.339996",
+ "109.209999",
+ "108.080002",
+ "107.779999",
+ "107.849998",
+ "107.760002",
+ "108.010002",
+ "107.160004",
+ "106.18",
+ "105.279999",
+ "104.769997",
+ "104.00",
+ "104.410004",
+ "103.68",
+ "102.82",
+ "102.75",
+ "96.419998",
+ "96.919998",
+ "98.309998",
+ "99.129997",
+ "99.739998",
+ "99.339996",
+ "98.599998",
+ "98.50",
+ "97.32",
+ "96.839996",
+ "97.120003",
+ "96.730003",
+ "96.050003",
+ "95.620003",
+ "94.370003",
+ "94.459999",
+ "95.330002",
+ "94.300003",
+ "93.629997",
+ "92.139999",
+ "91.50",
+ "92.650002",
+ "95.25",
+ "95.349998",
+ "94.68",
+ "95.029999",
+ "95.300003",
+ "96.07",
+ "97.029999",
+ "96.75",
+ "97.099998",
+ "98.480003",
+ "98.459999",
+ "98.68",
+ "98.959999",
+ "97.550003",
+ "97.449997",
+ "96.629997",
+ "98.330002",
+ "98.82",
+ "99.25",
+ "98.639999",
+ "98.110001",
+ "96.839996",
+ "95.669998",
+ "94.519997",
+ "93.57",
+ "93.889999",
+ "93.010002",
+ "91.650002",
+ "90.00",
+ "89.470001",
+ "92.459999",
+ "92.110001",
+ "92.589996",
+ "91.849998",
+ "92.68",
+ "93.82",
+ "93.68",
+ "92.400002",
+ "92.510002",
+ "94.25",
+ "95.68",
+ "103.910004",
+ "104.510002",
+ "104.620003",
+ "105.519997",
+ "106.059998",
+ "106.230003",
+ "106.940002",
+ "109.730003",
+ "111.330002",
+ "110.800003",
+ "108.660004",
+ "108.830002",
+ "108.169998",
+ "108.120003",
+ "109.199997",
+ "109.419998",
+ "110.269997",
+ "108.199997",
+ "108.879997",
+ "108.599998",
+ "104.879997",
+ "105.059998",
+ "104.889999",
+ "105.900002",
+ "105.209999",
+ "105.139999",
+ "105.190002",
+ "104.959999",
+ "104.589996",
+ "103.849998",
+ "101.779999",
+ "101.50",
+ "100.150002",
+ "100.269997",
+ "100.400002",
+ "100.959999",
+ "101.370003",
+ "100.449997",
+ "99.639999",
+ "97.419998",
+ "96.650002",
+ "96.580002",
+ "95.25",
+ "93.32",
+ "94.550003",
+ "95.919998",
+ "95.800003",
+ "96.089996",
+ "96.150002",
+ "94.610001",
+ "93.010002",
+ "92.589996",
+ "94.099998",
+ "93.93",
+ "93.040001",
+ "93.690002",
+ "95.190002",
+ "94.080002",
+ "94.279999",
+ "95.400002",
+ "94.349998",
+ "92.389999",
+ "93.339996",
+ "98.07",
+ "99.209999",
+ "98.370003",
+ "94.940002",
+ "93.419998",
+ "95.50",
+ "95.360001",
+ "95.739998",
+ "97.300003",
+ "98.839996",
+ "97.339996",
+ "96.760002",
+ "96.43",
+ "99.870003",
+ "102.410004",
+ "102.00"
+ ],
+ "close": [
+ "113.050003",
+ "112.18",
+ "113.949997",
+ "113.089996",
+ "112.879997",
+ "112.709999",
+ "114.620003",
+ "113.550003",
+ "113.57",
+ "113.580002",
+ "114.919998",
+ "115.57",
+ "111.769997",
+ "107.949997",
+ "105.440002",
+ "103.129997",
+ "105.519997",
+ "108.360001",
+ "107.699997",
+ "107.730003",
+ "106.730003",
+ "106.099998",
+ "106.00",
+ "106.82",
+ "106.940002",
+ "107.57",
+ "108.029999",
+ "108.849998",
+ "108.510002",
+ "109.360001",
+ "109.080002",
+ "109.220001",
+ "109.379997",
+ "109.480003",
+ "108.18",
+ "107.93",
+ "108.00",
+ "108.809998",
+ "108.370003",
+ "107.480003",
+ "105.870003",
+ "105.790001",
+ "104.480003",
+ "106.050003",
+ "104.209999",
+ "104.339996",
+ "102.949997",
+ "96.669998",
+ "97.339996",
+ "98.660004",
+ "99.43",
+ "99.959999",
+ "99.870003",
+ "99.830002",
+ "98.779999",
+ "98.790001",
+ "96.870003",
+ "97.419998",
+ "96.980003",
+ "96.68",
+ "95.940002",
+ "95.529999",
+ "94.989998",
+ "95.889999",
+ "95.599998",
+ "94.400002",
+ "93.589996",
+ "92.040001",
+ "93.400002",
+ "96.099998",
+ "95.550003",
+ "95.910004",
+ "95.099998",
+ "95.330002",
+ "97.550003",
+ "97.139999",
+ "97.459999",
+ "97.339996",
+ "98.830002",
+ "99.650002",
+ "98.940002",
+ "99.029999",
+ "98.629997",
+ "97.919998",
+ "97.720001",
+ "98.459999",
+ "99.860001",
+ "100.349998",
+ "100.410004",
+ "99.620003",
+ "97.900002",
+ "96.43",
+ "95.220001",
+ "94.199997",
+ "94.559998",
+ "93.489998",
+ "93.879997",
+ "90.519997",
+ "90.339996",
+ "92.510002",
+ "93.419998",
+ "92.790001",
+ "92.720001",
+ "93.239998",
+ "94.190002",
+ "95.18",
+ "93.639999",
+ "93.739998",
+ "94.830002",
+ "97.82",
+ "104.349998",
+ "105.080002",
+ "105.68",
+ "105.970001",
+ "107.129997",
+ "106.910004",
+ "107.480003",
+ "109.849998",
+ "112.099998",
+ "112.040001",
+ "110.440002",
+ "109.019997",
+ "108.660004",
+ "108.540001",
+ "110.959999",
+ "109.809998",
+ "111.120003",
+ "109.989998",
+ "108.989998",
+ "109.559998",
+ "107.68",
+ "105.190002",
+ "105.669998",
+ "106.129997",
+ "106.720001",
+ "105.910004",
+ "105.919998",
+ "105.800003",
+ "105.970001",
+ "104.580002",
+ "102.519997",
+ "102.260002",
+ "101.169998",
+ "101.120003",
+ "101.029999",
+ "101.870003",
+ "103.010002",
+ "101.50",
+ "100.75",
+ "100.529999",
+ "96.690002",
+ "96.910004",
+ "96.760002",
+ "96.099998",
+ "94.690002",
+ "96.879997",
+ "96.040001",
+ "96.260002",
+ "98.120003",
+ "96.639999",
+ "93.989998",
+ "93.699997",
+ "94.269997",
+ "94.989998",
+ "95.010002",
+ "94.019997",
+ "96.599998",
+ "96.349998",
+ "94.480003",
+ "96.43",
+ "97.339996",
+ "94.089996",
+ "93.419998",
+ "99.989998",
+ "99.440002",
+ "101.419998",
+ "96.300003",
+ "96.790001",
+ "96.660004",
+ "97.129997",
+ "99.519997",
+ "97.389999",
+ "99.959999",
+ "98.529999",
+ "96.959999",
+ "96.449997",
+ "100.699997",
+ "102.709999",
+ "105.349998"
+ ],
+ "increasing": {
+ "line": {
+ "color": "#b8e186"
+ },
+ "name": "XYZ"
+ },
+ "decreasing": {
+ "line": {
+ "color": "#f1b6da"
+ },
+ "showlegend": false
+ },
+ "yaxis": "y2"
+ }
+ ],
+ "layout": {
+ "xaxis": {
+ "range": [
+ "2559-01-10 14:24:21.917",
+ "2559-08-15 01:54:22.978"
+ ],
+ "calendar": "thai",
+ "title": "Thai dates"
+ },
+ "yaxis": {
+ "tickprefix": "$",
+ "showtickprefix": "last"
+ },
+ "yaxis2": {
+ "overlaying": "y",
+ "side": "right",
+ "tickprefix": "$",
+ "showtickprefix": "last"
+ },
+ "legend": {
+ "x": 0,
+ "xanchor": "left",
+ "y": 1,
+ "yanchor": "bottom"
+ },
+ "height": 600,
+ "width": 1100
+ }
+}
diff --git a/test/image/mocks/finance_style.json b/test/image/mocks/finance_style.json
index c7fb15e7def..7adc4f2bdd6 100644
--- a/test/image/mocks/finance_style.json
+++ b/test/image/mocks/finance_style.json
@@ -90,6 +90,29 @@
20.609242660033345,
20.9832297017119
],
+ "x": [
+ "0550-01-01",
+ "0550-01-02",
+ "0550-01-03",
+ "0550-01-04",
+ "0550-01-05",
+ "0550-01-06",
+ "0550-01-07",
+ "0550-01-08",
+ "0550-01-09",
+ "0550-01-10",
+ "0550-01-11",
+ "0550-01-12",
+ "0550-01-13",
+ "0550-01-14",
+ "0550-01-15",
+ "0550-01-16",
+ "0550-01-17",
+ "0550-01-18",
+ "0550-01-19",
+ "0550-01-20"
+ ],
+ "xcalendar": "nanakshahi",
"increasing": {
"line": {
"color": "rgb(150, 200, 250)"
@@ -196,6 +219,29 @@
25.398644940922246,
25.093220170315078
],
+ "x": [
+ "0550-01-01",
+ "0550-01-02",
+ "0550-01-03",
+ "0550-01-04",
+ "0550-01-05",
+ "0550-01-06",
+ "0550-01-07",
+ "0550-01-08",
+ "0550-01-09",
+ "0550-01-10",
+ "0550-01-11",
+ "0550-01-12",
+ "0550-01-13",
+ "0550-01-14",
+ "0550-01-15",
+ "0550-01-16",
+ "0550-01-17",
+ "0550-01-18",
+ "0550-01-19",
+ "0550-01-20"
+ ],
+ "xcalendar": "nanakshahi",
"increasing": {
"line": {
"color": "#d3d3d3"
@@ -218,7 +264,9 @@
"xaxis": {
"rangeslider": {
"visible": false
- }
+ },
+ "calendar": "islamic",
+ "title": "Islamic dates"
},
"showlegend": false,
"height": 450,
diff --git a/test/image/mocks/gl2d_date_axes.json b/test/image/mocks/gl2d_date_axes.json
index 4f6fff945b2..d0ff732579d 100644
--- a/test/image/mocks/gl2d_date_axes.json
+++ b/test/image/mocks/gl2d_date_axes.json
@@ -14,19 +14,20 @@
"2016-10-25 22:23:00.004"
],
"y": [
- 0,
- 1,
- 2,
- 3,
- 4,
- 5,
- 6,
- 7,
- 8,
- 9
+ "0100-01-01",
+ "0100-01-02",
+ "0100-01-03",
+ "0100-01-04",
+ "0100-01-05",
+ "0100-01-06",
+ "0100-01-07",
+ "0100-01-08",
+ "0100-01-09",
+ "0100-01-10"
],
"type": "scattergl",
- "mode": "markers"
+ "mode": "markers",
+ "ycalendar": "taiwan"
},
{
"x": [
@@ -42,28 +43,35 @@
"2016-10-25 22:23:00.004"
],
"y": [
- 10,
- 11,
- 12,
- 13,
- 14,
- 15,
- 16,
- 17,
- 18,
- 19
+ "0100-01-11",
+ "0100-01-12",
+ "0100-01-13",
+ "0100-01-14",
+ "0100-01-15",
+ "0100-01-16",
+ "0100-01-17",
+ "0100-01-18",
+ "0100-01-19",
+ "0100-01-20"
],
"type": "scattergl",
- "mode": "lines+markers"
+ "mode": "lines+markers",
+ "ycalendar": "taiwan"
}
],
"layout": {
"xaxis": {
"range": [
- 1477434179998,
- 1477434180004
+ "2559-10-25 22:22:59.998",
+ "2559-10-25 22:23:00.004"
],
- "autorange": false
+ "autorange": false,
+ "calendar": "thai",
+ "title": "Thai"
+ },
+ "yaxis": {
+ "calendar": "mayan",
+ "title": "Mayan"
}
}
}
\ No newline at end of file
diff --git a/test/image/mocks/gl3d_scatter-date.json b/test/image/mocks/gl3d_scatter-date.json
deleted file mode 100644
index 65bdf83a472..00000000000
--- a/test/image/mocks/gl3d_scatter-date.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "data":[
- {
- "y":[1.1,1.16,1.16,1.2,1.18,1.31,1.3,1.34,1.38,1.39,1.35,1.37,1.39],
- "z":[1.1,1.16,1.16,1.2,1.18,1.31,1.3,1.34,1.38,1.39,1.35,1.37,1.39],
- "x":["2013-07","2013-08","2013-09","2013-10","2013-11","2013-12","2014-01","2014-02","2014-03","2014-04","2014-05","2014-06","2014-07"],
- "type":"scatter3d"
- }
- ],
- "layout": {
- "title": "Date values"
- }
-}
diff --git a/test/image/mocks/gl3d_world-cals.json b/test/image/mocks/gl3d_world-cals.json
new file mode 100644
index 00000000000..f54d1ff49cf
--- /dev/null
+++ b/test/image/mocks/gl3d_world-cals.json
@@ -0,0 +1,129 @@
+{
+ "data": [
+ {
+ "i": [
+ 0,
+ 0,
+ 0,
+ 1
+ ],
+ "j": [
+ 1,
+ 2,
+ 3,
+ 2
+ ],
+ "k": [
+ 2,
+ 3,
+ 1,
+ 3
+ ],
+ "facecolor": [
+ "rgb(0, 0, 0)",
+ "rgb(255,0,0)",
+ "rgb(0,255,0)",
+ "rgb(0,0,255)"
+ ],
+ "type": "mesh3d",
+ "x": [
+ "2001-04-01",
+ "2001-04i-01",
+ "2001-05-01",
+ "2001-04-01"
+ ],
+ "xcalendar": "chinese",
+ "y": [
+ "0100-01-01",
+ "0100-01-01",
+ "0100-02-01",
+ "0100-03-01"
+ ],
+ "ycalendar": "taiwan",
+ "z": [
+ "2550-01-01",
+ "2550-03-01",
+ "2550-01-01",
+ "2550-02-01"
+ ],
+ "zcalendar": "thai"
+ },
+ {
+ "x": [
+ "2001-04-01",
+ "2001-04-01",
+ "2001-05-01",
+ "2001-05-01"
+ ],
+ "xcalendar": "chinese",
+ "y": [
+ "0100-01-01",
+ "0100-03-01",
+ "0100-01-01",
+ "0100-03-01"
+ ],
+ "ycalendar": "taiwan",
+ "z": [
+ "2550-01-01",
+ "2550-03-01",
+ "2550-03-01",
+ "2550-01-01"
+ ],
+ "zcalendar": "thai",
+ "type": "scatter3d"
+ },
+ {
+ "x": [
+ "2001-04-01",
+ "2001-05-01"
+ ],
+ "xcalendar": "chinese",
+ "y": [
+ "0100-01-01",
+ "0100-03-01"
+ ],
+ "ycalendar": "taiwan",
+ "z": [
+ [
+ "2550-01-25",
+ "2550-02-10"
+ ],
+ [
+ "2550-02-10",
+ "2550-01-25"
+ ]
+ ],
+ "zcalendar": "thai",
+ "type": "surface",
+ "showscale": false,
+ "surfacecolor": [[0, 1],[2, 3]]
+ }
+ ],
+ "layout": {
+ "scene": {
+ "xaxis": {
+ "title": "taiwan",
+ "calendar": "taiwan",
+ "type": "date"
+ },
+ "yaxis": {
+ "title": "gregorian",
+ "type": "date"
+ },
+ "zaxis": {
+ "title": "chinese",
+ "calendar": "chinese",
+ "type": "date"
+ },
+ "camera": {
+ "eye": {
+ "x": -1.8,
+ "y": 1.38,
+ "z": 0.75
+ }
+ }
+ },
+ "width": 800,
+ "height": 700
+ }
+}
\ No newline at end of file
diff --git a/test/image/mocks/world-cals.json b/test/image/mocks/world-cals.json
new file mode 100644
index 00000000000..8d06668a3d5
--- /dev/null
+++ b/test/image/mocks/world-cals.json
@@ -0,0 +1,443 @@
+{
+ "data": [
+ {
+ "type": "bar",
+ "x": [
+ "1686-01-01",
+ "1687-01-01",
+ "1688-01-01"
+ ],
+ "y": [
+ -1,
+ -2,
+ -3
+ ],
+ "xcalendar": "coptic",
+ "name": "coptic bars",
+ "uid": "74ab24"
+ },
+ {
+ "type": "box",
+ "x": [
+ "1798-01-01",
+ "1798-04-04",
+ "1798-05-05",
+ "1798-05-05",
+ "1798-07-05",
+ "1798-07-22",
+ "1799-01-01"
+ ],
+ "orientation": "h",
+ "xcalendar": "discworld",
+ "y0": -3,
+ "name": "discworld box",
+ "uid": "8c8a7d"
+ },
+ {
+ "type": "contour",
+ "x": [
+ "1961-07-01",
+ "1961-09-01",
+ "1961-11-01"
+ ],
+ "y": [
+ 3,
+ 4,
+ 5
+ ],
+ "z": [
+ [
+ 1,
+ 2,
+ 1
+ ],
+ [
+ 3,
+ 4,
+ 3
+ ],
+ [
+ 1,
+ 2,
+ 3
+ ]
+ ],
+ "xcalendar": "ethiopian",
+ "name": "ethiopian contour",
+ "showscale": false,
+ "uid": "506ec8",
+ "zmin": 1,
+ "zmax": 4,
+ "contours": {
+ "coloring": "fill",
+ "showlines": true,
+ "start": 1.5,
+ "size": 0.5,
+ "end": 3.505
+ }
+ },
+ {
+ "type": "heatmap",
+ "x": [
+ "5730-08-01",
+ "5730-10-01",
+ "5730-12-01"
+ ],
+ "y": [
+ 3,
+ 4,
+ 5
+ ],
+ "z": [
+ [
+ 1,
+ 2
+ ],
+ [
+ 3,
+ 4
+ ]
+ ],
+ "xcalendar": "hebrew",
+ "name": "hebrew heatmap",
+ "showscale": false,
+ "uid": "2f8d3f",
+ "zmin": 1,
+ "zmax": 4
+ },
+ {
+ "type": "histogram2d",
+ "x": [
+ "1389-01-01",
+ "1389-01-01",
+ "1389-01-01",
+ "1389-03-01",
+ "1389-03-01",
+ "1389-06-01"
+ ],
+ "y": [
+ 1.5,
+ 1.5,
+ 1.5,
+ 2.5,
+ 2.5,
+ 1.5
+ ],
+ "xcalendar": "islamic",
+ "name": "islamic histogram2d",
+ "nbinsx": 3,
+ "nbinsy": 2,
+ "showscale": false,
+ "uid": "beb4e5",
+ "xbins": {
+ "start": "1388-12-14 12:00",
+ "end": "1389-06-14 12:00",
+ "size": "M2"
+ },
+ "ybins": {
+ "start": 1,
+ "end": 3,
+ "size": 1
+ },
+ "zmin": 0,
+ "zmax": 3
+ },
+ {
+ "type": "histogram2dcontour",
+ "x": [
+ "1970-01-01",
+ "1970-01-01",
+ "1970-01-01",
+ "1970-03-01",
+ "1970-03-01",
+ "1970-06-01"
+ ],
+ "y": [
+ 1.5,
+ 1.5,
+ 1.5,
+ 2.5,
+ 2.5,
+ 1.5
+ ],
+ "xcalendar": "julian",
+ "name": "julian histogram2dcontour",
+ "nbinsx": 3,
+ "nbinsy": 2,
+ "showscale": false,
+ "uid": "d01089",
+ "xbins": {
+ "start": "1969-10-16 12:00",
+ "end": "1970-08-16 12:00",
+ "size": "M2"
+ },
+ "ybins": {
+ "start": 0,
+ "end": 4,
+ "size": 1
+ },
+ "zmin": 0,
+ "zmax": 3,
+ "colorscale": [
+ [
+ 0,
+ "rgb(220,220,220)"
+ ],
+ [
+ 0.2,
+ "rgb(245,195,157)"
+ ],
+ [
+ 0.4,
+ "rgb(245,160,105)"
+ ],
+ [
+ 1,
+ "rgb(178,10,28)"
+ ]
+ ],
+ "contours": {
+ "coloring": "fill",
+ "showlines": true,
+ "start": 0.5,
+ "size": 0.5,
+ "end": 2.505
+ }
+ },
+ {
+ "type": "scatter",
+ "x": [
+ "5157-01-01",
+ "5157-02-01",
+ "5157-03-01",
+ "5157-04-01"
+ ],
+ "y": [
+ 4,
+ 2,
+ 3,
+ 1
+ ],
+ "xcalendar": "mayan",
+ "name": "mayan",
+ "uid": "af70cc"
+ },
+ {
+ "type": "scatter",
+ "x": [
+ "0502-09-01",
+ "0502-10-01",
+ "0502-11-01",
+ "0502-12-01"
+ ],
+ "y": [
+ 4,
+ 2,
+ 3,
+ 1
+ ],
+ "xcalendar": "nanakshahi",
+ "name": "nanakshahi",
+ "uid": "de4e9d"
+ },
+ {
+ "type": "scatter",
+ "x": [
+ "2027-10-01",
+ "2027-11-01",
+ "2027-12-01",
+ "2028-01-01"
+ ],
+ "y": [
+ 4,
+ 2,
+ 3,
+ 1
+ ],
+ "xcalendar": "nepali",
+ "name": "nepali",
+ "uid": "5d4950"
+ },
+ {
+ "type": "scatter",
+ "x": [
+ "1350-01-01",
+ "1350-02-01",
+ "1350-03-01",
+ "1350-04-01"
+ ],
+ "y": [
+ 4,
+ 2,
+ 3,
+ 1
+ ],
+ "xcalendar": "persian",
+ "name": "persian",
+ "uid": "d13636"
+ },
+ {
+ "type": "scatter",
+ "x": [
+ "1350-03-01",
+ "1350-04-01",
+ "1350-05-01",
+ "1350-06-01"
+ ],
+ "y": [
+ 4,
+ 2,
+ 3,
+ 1
+ ],
+ "xcalendar": "jalali",
+ "name": "jalali",
+ "uid": "612738"
+ },
+ {
+ "type": "scatter",
+ "x": [
+ "0060-08-01",
+ "0060-09-01",
+ "0060-10-01",
+ "0060-11-01"
+ ],
+ "y": [
+ 4,
+ 2,
+ 3,
+ 1
+ ],
+ "xcalendar": "taiwan",
+ "name": "taiwan",
+ "uid": "24ac23"
+ },
+ {
+ "type": "scatter",
+ "x": [
+ "2514-10-01",
+ "2514-11-01",
+ "2514-12-01",
+ "2515-01-01"
+ ],
+ "y": [
+ 4,
+ 2,
+ 3,
+ 1
+ ],
+ "xcalendar": "thai",
+ "name": "thai",
+ "uid": "830f65"
+ },
+ {
+ "type": "histogram",
+ "y": [
+ "5200-01-01",
+ "5200-01-01",
+ "5200-01-02",
+ "5200-01-03",
+ "5200-01-03",
+ "5200-01-03",
+ "5201-04-01",
+ "5199-01-01"
+ ],
+ "xaxis": "x2",
+ "yaxis": "y2",
+ "ycalendar": "hebrew",
+ "name": "hebrew histogram
jalali filtered",
+ "transforms": [
+ {
+ "type": "filter",
+ "operation": "[]",
+ "calendar": "jalali",
+ "value": [
+ "0818-08",
+ "0819-06"
+ ],
+ "target": "y"
+ }
+ ],
+ "uid": "cf8dad",
+ "ybins": {
+ "start": "5200-12-29 12:00",
+ "end": "5200-01-03 12:00",
+ "size": 86400000
+ }
+ }
+ ],
+ "layout": {
+ "xaxis": {
+ "calendar": "ummalqura",
+ "title": "ummalqura axis",
+ "domain": [
+ 0,
+ 0.8
+ ],
+ "type": "date",
+ "range": [
+ "1388-08-05 16:38:43.314",
+ "1392-01-29 11:07:58.7911"
+ ],
+ "autorange": true
+ },
+ "xaxis2": {
+ "domain": [
+ 0.9,
+ 1
+ ],
+ "range": [
+ 0,
+ 3.1578947368421053
+ ],
+ "autorange": true
+ },
+ "yaxis2": {
+ "anchor": "x2",
+ "calendar": "nepali",
+ "title": "nepali axis",
+ "type": "date",
+ "range": [
+ "1496-11-30 12:00",
+ "1496-12-03 12:00"
+ ],
+ "autorange": true
+ },
+ "width": 1200,
+ "height": 400,
+ "annotations": [
+ {
+ "x": "1389-06-05",
+ "y": 4,
+ "text": "ethiopian
contour",
+ "showarrow": false
+ },
+ {
+ "x": "1390-01-10",
+ "y": 4,
+ "text": "hebrew
heatmap",
+ "showarrow": false
+ },
+ {
+ "x": "1389-08-01",
+ "y": 2,
+ "text": "islamic
hist2d",
+ "showarrow": false
+ },
+ {
+ "x": "1390-03-15",
+ "y": 2,
+ "text": "julian
hist2dcontour",
+ "showarrow": false
+ }
+ ],
+ "yaxis": {
+ "type": "linear",
+ "range": [
+ -3.5,
+ 5
+ ],
+ "autorange": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/jasmine/tests/annotations_test.js b/test/jasmine/tests/annotations_test.js
index eeac5f13c9a..fd99e6c6881 100644
--- a/test/jasmine/tests/annotations_test.js
+++ b/test/jasmine/tests/annotations_test.js
@@ -237,7 +237,8 @@ describe('annotations autosize', function() {
expect(fullLayout.xaxis.range).toBeCloseToArray(x, PREC, '- xaxis');
expect(fullLayout.yaxis.range).toBeCloseToArray(y, PREC, '- yaxis');
- expect(dateAx.range.map(dateAx.r2l)).toBeCloseToArray(x2.map(dateAx.r2l), PRECX2, 'xaxis2 ' + dateAx.range);
+ expect(Lib.simpleMap(dateAx.range, dateAx.r2l))
+ .toBeCloseToArray(Lib.simpleMap(x2, dateAx.r2l), PRECX2, 'xaxis2 ' + dateAx.range);
expect(fullLayout.yaxis2.range).toBeCloseToArray(y2, PRECY2, 'yaxis2');
expect(fullLayout.xaxis3.range).toBeCloseToArray(x3, PREC, 'xaxis3');
expect(fullLayout.yaxis3.range).toBeCloseToArray(y3, PREC, 'yaxis3');
diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js
index 7378c50b376..d08273bfa58 100644
--- a/test/jasmine/tests/axes_test.js
+++ b/test/jasmine/tests/axes_test.js
@@ -385,6 +385,34 @@ describe('Test axes', function() {
expect(layoutOut.yaxis2.gridcolor)
.toEqual(tinycolor.mix('#444', bgColor, frac).toRgbString());
});
+
+ it('should inherit calendar from the layout', function() {
+ layoutOut.calendar = 'nepali';
+ layoutIn = {
+ calendar: 'nepali',
+ xaxis: {type: 'date'},
+ yaxis: {type: 'date'}
+ };
+
+ supplyLayoutDefaults(layoutIn, layoutOut, fullData);
+
+ expect(layoutOut.xaxis.calendar).toBe('nepali');
+ expect(layoutOut.yaxis.calendar).toBe('nepali');
+ });
+
+ it('should allow its own calendar', function() {
+ layoutOut.calendar = 'nepali';
+ layoutIn = {
+ calendar: 'nepali',
+ xaxis: {type: 'date', calendar: 'coptic'},
+ yaxis: {type: 'date', calendar: 'thai'}
+ };
+
+ supplyLayoutDefaults(layoutIn, layoutOut, fullData);
+
+ expect(layoutOut.xaxis.calendar).toBe('coptic');
+ expect(layoutOut.yaxis.calendar).toBe('thai');
+ });
});
describe('categoryorder', function() {
diff --git a/test/jasmine/tests/bar_test.js b/test/jasmine/tests/bar_test.js
index 75f8bc01926..8edf4e1d608 100644
--- a/test/jasmine/tests/bar_test.js
+++ b/test/jasmine/tests/bar_test.js
@@ -27,14 +27,14 @@ describe('Bar.supplyDefaults', function() {
it('should set visible to false when x and y are empty', function() {
traceIn = {};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.visible).toBe(false);
traceIn = {
x: [],
y: []
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.visible).toBe(false);
});
@@ -42,27 +42,27 @@ describe('Bar.supplyDefaults', function() {
traceIn = {
x: []
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.visible).toBe(false);
traceIn = {
x: [],
y: [1, 2, 3]
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.visible).toBe(false);
traceIn = {
y: []
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.visible).toBe(false);
traceIn = {
x: [1, 2, 3],
y: []
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.visible).toBe(false);
});
@@ -70,7 +70,7 @@ describe('Bar.supplyDefaults', function() {
traceIn = {
y: [1, 2, 3]
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.base).toBeUndefined();
expect(traceOut.offset).toBeUndefined();
expect(traceOut.width).toBeUndefined();
@@ -81,7 +81,7 @@ describe('Bar.supplyDefaults', function() {
width: -1,
y: [1, 2, 3]
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.width).toBeUndefined();
});
@@ -89,7 +89,7 @@ describe('Bar.supplyDefaults', function() {
traceIn = {
y: [1, 2, 3]
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.textposition).toBe('none');
expect(traceOut.texfont).toBeUndefined();
expect(traceOut.insidetexfont).toBeUndefined();
@@ -116,6 +116,32 @@ describe('Bar.supplyDefaults', function() {
expect(traceOut.insidetextfont).not.toBe(traceOut.textfont);
expect(traceOut.outsidetexfont).toBeUndefined();
});
+
+ it('should inherit layout.calendar', function() {
+ traceIn = {
+ x: [1, 2, 3],
+ y: [1, 2, 3]
+ };
+ supplyDefaults(traceIn, traceOut, defaultColor, {calendar: 'islamic'});
+
+ // we always fill calendar attributes, because it's hard to tell if
+ // we're on a date axis at this point.
+ expect(traceOut.xcalendar).toBe('islamic');
+ expect(traceOut.ycalendar).toBe('islamic');
+ });
+
+ it('should take its own calendars', function() {
+ traceIn = {
+ x: [1, 2, 3],
+ y: [1, 2, 3],
+ xcalendar: 'coptic',
+ ycalendar: 'ethiopian'
+ };
+ supplyDefaults(traceIn, traceOut, defaultColor, {calendar: 'islamic'});
+
+ expect(traceOut.xcalendar).toBe('coptic');
+ expect(traceOut.ycalendar).toBe('ethiopian');
+ });
});
describe('heatmap calc / setPositions', function() {
diff --git a/test/jasmine/tests/box_test.js b/test/jasmine/tests/box_test.js
index 5c357f26ad5..c292fc356c9 100644
--- a/test/jasmine/tests/box_test.js
+++ b/test/jasmine/tests/box_test.js
@@ -25,7 +25,7 @@ describe('Test boxes', function() {
x: [],
y: []
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.visible).toBe(false);
});
@@ -33,27 +33,27 @@ describe('Test boxes', function() {
traceIn = {
x: []
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.visible).toBe(false);
traceIn = {
x: [],
y: [1, 2, 3]
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.visible).toBe(false);
traceIn = {
y: []
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.visible).toBe(false);
traceIn = {
x: [1, 2, 3],
y: []
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.visible).toBe(false);
});
@@ -61,14 +61,14 @@ describe('Test boxes', function() {
traceIn = {
y: [1, 2, 3]
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.orientation).toBe('v');
traceIn = {
x: [1, 1, 1],
y: [1, 2, 3]
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.orientation).toBe('v');
});
@@ -76,11 +76,37 @@ describe('Test boxes', function() {
traceIn = {
x: [1, 2, 3]
};
- supplyDefaults(traceIn, traceOut, defaultColor);
+ supplyDefaults(traceIn, traceOut, defaultColor, {});
expect(traceOut.orientation).toBe('h');
});
+ it('should inherit layout.calendar', function() {
+ traceIn = {
+ y: [1, 2, 3]
+ };
+ supplyDefaults(traceIn, traceOut, defaultColor, {calendar: 'islamic'});
+
+ // we always fill calendar attributes, because it's hard to tell if
+ // we're on a date axis at this point.
+ expect(traceOut.xcalendar).toBe('islamic');
+ expect(traceOut.ycalendar).toBe('islamic');
+ });
+
+ it('should take its own calendars', function() {
+ traceIn = {
+ y: [1, 2, 3],
+ xcalendar: 'coptic',
+ ycalendar: 'ethiopian'
+ };
+ supplyDefaults(traceIn, traceOut, defaultColor, {calendar: 'islamic'});
+
+ // we always fill calendar attributes, because it's hard to tell if
+ // we're on a date axis at this point.
+ expect(traceOut.xcalendar).toBe('coptic');
+ expect(traceOut.ycalendar).toBe('ethiopian');
+ });
+
});
});
diff --git a/test/jasmine/tests/contour_test.js b/test/jasmine/tests/contour_test.js
index 0cc1fd0c1be..2f41da50af1 100644
--- a/test/jasmine/tests/contour_test.js
+++ b/test/jasmine/tests/contour_test.js
@@ -51,6 +51,36 @@ describe('contour defaults', function() {
supplyDefaults(traceIn, traceOut, defaultColor, layout);
expect(traceOut.autocontour).toBe(true);
});
+
+ it('should inherit layout.calendar', function() {
+ traceIn = {
+ x: [1, 2],
+ y: [1, 2],
+ z: [[1, 2], [3, 4]]
+ };
+ supplyDefaults(traceIn, traceOut, defaultColor, {calendar: 'islamic'});
+
+ // we always fill calendar attributes, because it's hard to tell if
+ // we're on a date axis at this point.
+ expect(traceOut.xcalendar).toBe('islamic');
+ expect(traceOut.ycalendar).toBe('islamic');
+ });
+
+ it('should take its own calendars', function() {
+ traceIn = {
+ x: [1, 2],
+ y: [1, 2],
+ z: [[1, 2], [3, 4]],
+ xcalendar: 'coptic',
+ ycalendar: 'ethiopian'
+ };
+ supplyDefaults(traceIn, traceOut, defaultColor, {calendar: 'islamic'});
+
+ // we always fill calendar attributes, because it's hard to tell if
+ // we're on a date axis at this point.
+ expect(traceOut.xcalendar).toBe('coptic');
+ expect(traceOut.ycalendar).toBe('ethiopian');
+ });
});
describe('contour makeColorMap', function() {
diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js
index 992beda676b..f6e292cf4dc 100644
--- a/test/jasmine/tests/finance_test.js
+++ b/test/jasmine/tests/finance_test.js
@@ -331,6 +331,42 @@ describe('finance charts defaults:', function() {
expect(out1.layout.xaxis.rangeslider).toBeDefined();
expect(out1._fullLayout.xaxis.rangeslider.visible).toBe(false);
});
+
+ it('pushes layout.calendar to all output traces', function() {
+ var trace0 = Lib.extendDeep({}, mock0, {
+ type: 'ohlc'
+ });
+
+ var trace1 = Lib.extendDeep({}, mock1, {
+ type: 'candlestick'
+ });
+
+ var out = _supply([trace0, trace1], {calendar: 'nanakshahi'});
+
+
+ out._fullData.forEach(function(fullTrace) {
+ expect(fullTrace.xcalendar).toBe('nanakshahi');
+ });
+ });
+
+ it('accepts a calendar per input trace', function() {
+ var trace0 = Lib.extendDeep({}, mock0, {
+ type: 'ohlc',
+ xcalendar: 'hebrew'
+ });
+
+ var trace1 = Lib.extendDeep({}, mock1, {
+ type: 'candlestick',
+ xcalendar: 'julian'
+ });
+
+ var out = _supply([trace0, trace1], {calendar: 'nanakshahi'});
+
+
+ out._fullData.forEach(function(fullTrace, i) {
+ expect(fullTrace.xcalendar).toBe(i < 2 ? 'hebrew' : 'julian');
+ });
+ });
});
describe('finance charts calc transforms:', function() {
@@ -652,10 +688,10 @@ describe('finance charts calc transforms:', function() {
var out = _calc([trace0, trace1]);
- var x0 = out[0].x.map(Lib.dateTime2ms);
+ var x0 = Lib.simpleMap(out[0].x, Lib.dateTime2ms);
expect(x0[x0.length - 2] - x0[0]).toEqual(1);
- var x2 = out[2].x.map(Lib.dateTime2ms);
+ var x2 = Lib.simpleMap(out[2].x, Lib.dateTime2ms);
expect(x2[x2.length - 2] - x2[0]).toEqual(1);
expect(out[1].x).toEqual([]);
diff --git a/test/jasmine/tests/gl3daxes_test.js b/test/jasmine/tests/gl3daxes_test.js
index 1e4b61912bd..fad1f8c222b 100644
--- a/test/jasmine/tests/gl3daxes_test.js
+++ b/test/jasmine/tests/gl3daxes_test.js
@@ -75,5 +75,35 @@ describe('Test Gl3dAxes', function() {
checkKeys(expected[axis], layoutOut[axis]);
});
});
+
+ it('should inherit layout.calendar', function() {
+ layoutIn = {
+ xaxis: {type: 'date'},
+ yaxis: {type: 'date'},
+ zaxis: {type: 'date'}
+ };
+ options.calendar = 'taiwan';
+
+ supplyLayoutDefaults(layoutIn, layoutOut, options);
+
+ expect(layoutOut.xaxis.calendar).toBe('taiwan');
+ expect(layoutOut.yaxis.calendar).toBe('taiwan');
+ expect(layoutOut.zaxis.calendar).toBe('taiwan');
+ });
+
+ it('should accept its own calendar', function() {
+ layoutIn = {
+ xaxis: {type: 'date', calendar: 'hebrew'},
+ yaxis: {type: 'date', calendar: 'ummalqura'},
+ zaxis: {type: 'date', calendar: 'discworld'}
+ };
+ options.calendar = 'taiwan';
+
+ supplyLayoutDefaults(layoutIn, layoutOut, options);
+
+ expect(layoutOut.xaxis.calendar).toBe('hebrew');
+ expect(layoutOut.yaxis.calendar).toBe('ummalqura');
+ expect(layoutOut.zaxis.calendar).toBe('discworld');
+ });
});
});
diff --git a/test/jasmine/tests/heatmap_test.js b/test/jasmine/tests/heatmap_test.js
index 61ccf74f4bf..9141ff5edf1 100644
--- a/test/jasmine/tests/heatmap_test.js
+++ b/test/jasmine/tests/heatmap_test.js
@@ -125,6 +125,35 @@ describe('heatmap supplyDefaults', function() {
expect(traceOut.ygap).toBe(undefined);
});
+ it('should inherit layout.calendar', function() {
+ traceIn = {
+ x: [1, 2],
+ y: [1, 2],
+ z: [[1, 2], [3, 4]]
+ };
+ supplyDefaults(traceIn, traceOut, defaultColor, {calendar: 'islamic'});
+
+ // we always fill calendar attributes, because it's hard to tell if
+ // we're on a date axis at this point.
+ expect(traceOut.xcalendar).toBe('islamic');
+ expect(traceOut.ycalendar).toBe('islamic');
+ });
+
+ it('should take its own calendars', function() {
+ traceIn = {
+ x: [1, 2],
+ y: [1, 2],
+ z: [[1, 2], [3, 4]],
+ xcalendar: 'coptic',
+ ycalendar: 'ethiopian'
+ };
+ supplyDefaults(traceIn, traceOut, defaultColor, {calendar: 'islamic'});
+
+ // we always fill calendar attributes, because it's hard to tell if
+ // we're on a date axis at this point.
+ expect(traceOut.xcalendar).toBe('coptic');
+ expect(traceOut.ycalendar).toBe('ethiopian');
+ });
});
describe('heatmap convertColumnXYZ', function() {
diff --git a/test/jasmine/tests/histogram2d_test.js b/test/jasmine/tests/histogram2d_test.js
index ae3922708bf..e6e421fad84 100644
--- a/test/jasmine/tests/histogram2d_test.js
+++ b/test/jasmine/tests/histogram2d_test.js
@@ -18,7 +18,7 @@ describe('Test histogram2d', function() {
it('should set zsmooth to false when zsmooth is empty', function() {
traceIn = {};
- supplyDefaults(traceIn, traceOut, {});
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.zsmooth).toBe(false);
});
@@ -26,13 +26,13 @@ describe('Test histogram2d', function() {
traceIn = {
zsmooth: 'fast'
};
- supplyDefaults(traceIn, traceOut, {});
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.zsmooth).toBe('fast');
});
it('should set xgap and ygap to 0 when xgap and ygap are empty', function() {
traceIn = {};
- supplyDefaults(traceIn, traceOut, {});
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.xgap).toBe(0);
expect(traceOut.ygap).toBe(0);
});
@@ -42,7 +42,7 @@ describe('Test histogram2d', function() {
xgap: 10,
ygap: 5
};
- supplyDefaults(traceIn, traceOut, {});
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.xgap).toBe(10);
expect(traceOut.ygap).toBe(5);
});
@@ -53,11 +53,39 @@ describe('Test histogram2d', function() {
ygap: 5,
zsmooth: 'best'
};
- supplyDefaults(traceIn, traceOut, {});
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.xgap).toBe(undefined);
expect(traceOut.ygap).toBe(undefined);
});
+
+ it('should inherit layout.calendar', function() {
+ traceIn = {
+ x: [1, 2, 3],
+ y: [1, 2, 3]
+ };
+ supplyDefaults(traceIn, traceOut, '', {calendar: 'islamic'});
+
+ // we always fill calendar attributes, because it's hard to tell if
+ // we're on a date axis at this point.
+ expect(traceOut.xcalendar).toBe('islamic');
+ expect(traceOut.ycalendar).toBe('islamic');
+ });
+
+ it('should take its own calendars', function() {
+ traceIn = {
+ x: [1, 2, 3],
+ y: [1, 2, 3],
+ xcalendar: 'coptic',
+ ycalendar: 'ethiopian'
+ };
+ supplyDefaults(traceIn, traceOut, '', {calendar: 'islamic'});
+
+ // we always fill calendar attributes, because it's hard to tell if
+ // we're on a date axis at this point.
+ expect(traceOut.xcalendar).toBe('coptic');
+ expect(traceOut.ycalendar).toBe('ethiopian');
+ });
});
diff --git a/test/jasmine/tests/histogram_test.js b/test/jasmine/tests/histogram_test.js
index 2fd1140df7e..684d2320939 100644
--- a/test/jasmine/tests/histogram_test.js
+++ b/test/jasmine/tests/histogram_test.js
@@ -20,13 +20,13 @@ describe('Test histogram', function() {
traceIn = {
x: []
};
- supplyDefaults(traceIn, traceOut);
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.visible).toBe(false);
traceIn = {
y: []
};
- supplyDefaults(traceIn, traceOut);
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.visible).toBe(false);
});
@@ -36,7 +36,7 @@ describe('Test histogram', function() {
x: [],
y: [1, 2, 2]
};
- supplyDefaults(traceIn, traceOut);
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.visible).toBe(false);
traceIn = {
@@ -44,7 +44,7 @@ describe('Test histogram', function() {
x: [1, 2, 2],
y: []
};
- supplyDefaults(traceIn, traceOut);
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.visible).toBe(false);
traceIn = {
@@ -52,7 +52,7 @@ describe('Test histogram', function() {
x: [],
y: []
};
- supplyDefaults(traceIn, traceOut);
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.visible).toBe(false);
traceIn = {
@@ -60,7 +60,7 @@ describe('Test histogram', function() {
x: [],
y: [1, 2, 2]
};
- supplyDefaults(traceIn, traceOut);
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.visible).toBe(false);
});
@@ -68,14 +68,14 @@ describe('Test histogram', function() {
traceIn = {
x: [1, 2, 2]
};
- supplyDefaults(traceIn, traceOut);
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.orientation).toBe('v');
traceIn = {
x: [1, 2, 2],
y: [1, 2, 2]
};
- supplyDefaults(traceIn, traceOut);
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.orientation).toBe('v');
});
@@ -83,7 +83,7 @@ describe('Test histogram', function() {
traceIn = {
y: [1, 2, 2]
};
- supplyDefaults(traceIn, traceOut);
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.orientation).toBe('h');
});
@@ -100,13 +100,13 @@ describe('Test histogram', function() {
size: 1
}
};
- supplyDefaults(traceIn, traceOut);
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.autobinx).toBeUndefined();
traceIn = {
x: [1, 2, 2]
};
- supplyDefaults(traceIn, traceOut);
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.autobinx).toBeUndefined();
});
@@ -119,16 +119,41 @@ describe('Test histogram', function() {
size: 1
}
};
- supplyDefaults(traceIn, traceOut);
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.autobiny).toBeUndefined();
traceIn = {
y: [1, 2, 2]
};
- supplyDefaults(traceIn, traceOut);
+ supplyDefaults(traceIn, traceOut, '', {});
expect(traceOut.autobiny).toBeUndefined();
});
+ it('should inherit layout.calendar', function() {
+ traceIn = {
+ x: [1, 2, 3]
+ };
+ supplyDefaults(traceIn, traceOut, '', {calendar: 'islamic'});
+
+ // we always fill calendar attributes, because it's hard to tell if
+ // we're on a date axis at this point.
+ // size axis calendar is weird, but *might* be able to happen if
+ // we're using histfunc=min or max (does this work?)
+ expect(traceOut.xcalendar).toBe('islamic');
+ expect(traceOut.ycalendar).toBe('islamic');
+ });
+
+ it('should take its own calendars', function() {
+ traceIn = {
+ x: [1, 2, 3],
+ xcalendar: 'coptic',
+ ycalendar: 'nepali'
+ };
+ supplyDefaults(traceIn, traceOut, '', {calendar: 'islamic'});
+
+ expect(traceOut.xcalendar).toBe('coptic');
+ expect(traceOut.ycalendar).toBe('nepali');
+ });
});
diff --git a/test/jasmine/tests/lib_date_test.js b/test/jasmine/tests/lib_date_test.js
index a73f79c7b25..9df1ecfd99a 100644
--- a/test/jasmine/tests/lib_date_test.js
+++ b/test/jasmine/tests/lib_date_test.js
@@ -1,5 +1,10 @@
var isNumeric = require('fast-isnumeric');
+
var Lib = require('@src/lib');
+var calComponent = require('@src/components/calendars');
+
+// use only the parts of world-calendars that we've imported for our tests
+var calendars = require('@src/components/calendars/calendars');
describe('dates', function() {
'use strict';
@@ -222,6 +227,118 @@ describe('dates', function() {
expect(Lib.ms2DateTime(Lib.dateTime2ms(v[0]), v[1])).toBe(v[2], v);
});
});
+
+ it('should work right with inputs beyond our precision', function() {
+ for(var i = -1; i <= 1; i += 0.001) {
+ var tenths = Math.round(i * 10),
+ base = i < -0.05 ? '1969-12-31 23:59:59.99' : '1970-01-01 00:00:00.00',
+ expected = (base + String(tenths + 200).substr(1))
+ .replace(/0+$/, '')
+ .replace(/ 00:00:00[\.]$/, '');
+ expect(Lib.ms2DateTime(i)).toBe(expected, i);
+ }
+ });
+ });
+
+ describe('world calendar inputs', function() {
+ it('should give the right values near epoch zero', function() {
+ [
+ [undefined, '1970-01-01'],
+ ['gregorian', '1970-01-01'],
+ ['chinese', '1969-11-24'],
+ ['coptic', '1686-04-23'],
+ ['discworld', '1798-12-27'],
+ ['ethiopian', '1962-04-23'],
+ ['hebrew', '5730-10-23'],
+ ['islamic', '1389-10-22'],
+ ['julian', '1969-12-19'],
+ ['mayan', '5156-07-05'],
+ ['nanakshahi', '0501-10-19'],
+ ['nepali', '2026-09-17'],
+ ['persian', '1348-10-11'],
+ ['jalali', '1348-10-11'],
+ ['taiwan', '0059-01-01'],
+ ['thai', '2513-01-01'],
+ ['ummalqura', '1389-10-23']
+ ].forEach(function(v) {
+ var calendar = v[0],
+ dateStr = v[1];
+ expect(Lib.ms2DateTime(0, 0, calendar)).toBe(dateStr, calendar);
+ expect(Lib.dateTime2ms(dateStr, calendar)).toBe(0, calendar);
+
+ var expected_p1ms = dateStr + ' 00:00:00.0001',
+ expected_1s = dateStr + ' 00:00:01',
+ expected_1m = dateStr + ' 00:01',
+ expected_1h = dateStr + ' 01:00',
+ expected_lastinstant = dateStr + ' 23:59:59.9999';
+
+ var oneSec = 1000,
+ oneMin = 60 * oneSec,
+ oneHour = 60 * oneMin,
+ lastInstant = 24 * oneHour - 0.1;
+
+ expect(Lib.ms2DateTime(0.1, 0, calendar)).toBe(expected_p1ms, calendar);
+ expect(Lib.ms2DateTime(oneSec, 0, calendar)).toBe(expected_1s, calendar);
+ expect(Lib.ms2DateTime(oneMin, 0, calendar)).toBe(expected_1m, calendar);
+ expect(Lib.ms2DateTime(oneHour, 0, calendar)).toBe(expected_1h, calendar);
+ expect(Lib.ms2DateTime(lastInstant, 0, calendar)).toBe(expected_lastinstant, calendar);
+
+ expect(Lib.dateTime2ms(expected_p1ms, calendar)).toBe(0.1, calendar);
+ expect(Lib.dateTime2ms(expected_1s, calendar)).toBe(oneSec, calendar);
+ expect(Lib.dateTime2ms(expected_1m, calendar)).toBe(oneMin, calendar);
+ expect(Lib.dateTime2ms(expected_1h, calendar)).toBe(oneHour, calendar);
+ expect(Lib.dateTime2ms(expected_lastinstant, calendar)).toBe(lastInstant, calendar);
+ });
+ });
+
+ it('should contain canonical ticks sundays, ranges for all calendars', function() {
+ var calList = Object.keys(calendars.calendars).filter(function(v) {
+ return v !== 'gregorian';
+ });
+
+ var canonicalTick = calComponent.CANONICAL_TICK,
+ canonicalSunday = calComponent.CANONICAL_SUNDAY,
+ dfltRange = calComponent.DFLTRANGE;
+ expect(Object.keys(canonicalTick).length).toBe(calList.length);
+ expect(Object.keys(canonicalSunday).length).toBe(calList.length);
+ expect(Object.keys(dfltRange).length).toBe(calList.length);
+
+ calList.forEach(function(calendar) {
+ expect(Lib.dateTime2ms(canonicalTick[calendar], calendar)).toBeDefined(calendar);
+ var sunday = Lib.dateTime2ms(canonicalSunday[calendar], calendar);
+ // convert back implicitly with gregorian calendar
+ expect(Lib.formatDate(sunday, '%A')).toBe('Sunday', calendar);
+
+ expect(Lib.dateTime2ms(dfltRange[calendar][0], calendar)).toBeDefined(calendar);
+ expect(Lib.dateTime2ms(dfltRange[calendar][1], calendar)).toBeDefined(calendar);
+ });
+ });
+
+ it('should handle Chinese intercalary months correctly', function() {
+ var intercalaryDates = [
+ '1995-08i-01',
+ '1995-08i-29',
+ '1984-10i-15',
+ '2023-02i-29'
+ ];
+ intercalaryDates.forEach(function(v) {
+ var ms = Lib.dateTime2ms(v, 'chinese');
+ expect(Lib.ms2DateTime(ms, 0, 'chinese')).toBe(v);
+
+ // should also work without leading zeros
+ var vShort = v.replace(/-0/g, '-');
+ expect(Lib.dateTime2ms(vShort, 'chinese')).toBe(ms, vShort);
+ });
+
+ var badIntercalaryDates = [
+ '1995-07i-01',
+ '1995-08i-30',
+ '1995-09i-01'
+ ];
+ badIntercalaryDates.forEach(function(v) {
+ expect(Lib.dateTime2ms(v, 'chinese')).toBeUndefined(v);
+ });
+ });
});
describe('cleanDate', function() {
@@ -279,6 +396,51 @@ describe('dates', function() {
});
});
+ describe('incrementMonth', function() {
+ it('should include Chinese intercalary months', function() {
+ var start = '1995-06-01';
+ var expected = [
+ '1995-07-01',
+ '1995-08-01',
+ '1995-08i-01',
+ '1995-09-01',
+ '1995-10-01',
+ '1995-11-01',
+ '1995-12-01',
+ '1996-01-01'
+ ];
+ var tick = Lib.dateTime2ms(start, 'chinese');
+ expected.forEach(function(v) {
+ tick = Lib.incrementMonth(tick, 1, 'chinese');
+ expect(tick).toBe(Lib.dateTime2ms(v, 'chinese'), v);
+ });
+ });
+
+ it('should increment years even over leap years', function() {
+ var start = '1995-06-01';
+ var expected = [
+ '1996-06-01',
+ '1997-06-01',
+ '1998-06-01',
+ '1999-06-01',
+ '2000-06-01',
+ '2001-06-01',
+ '2002-06-01',
+ '2003-06-01',
+ '2004-06-01',
+ '2005-06-01',
+ '2006-06-01',
+ '2007-06-01',
+ '2008-06-01'
+ ];
+ var tick = Lib.dateTime2ms(start, 'chinese');
+ expected.forEach(function(v) {
+ tick = Lib.incrementMonth(tick, 12, 'chinese');
+ expect(tick).toBe(Lib.dateTime2ms(v, 'chinese'), v);
+ });
+ });
+ });
+
describe('isJSDate', function() {
it('should return true for any Date object but not the equivalent numbers', function() {
[
diff --git a/test/jasmine/tests/plotschema_test.js b/test/jasmine/tests/plotschema_test.js
index eb0983b4857..2420632f3bf 100644
--- a/test/jasmine/tests/plotschema_test.js
+++ b/test/jasmine/tests/plotschema_test.js
@@ -39,7 +39,7 @@ describe('plot schema', function() {
assertPlotSchema(
function(attr) {
if(isValObject(attr)) {
- expect(ROLES.indexOf(attr.role) !== -1).toBe(true);
+ expect(ROLES.indexOf(attr.role) !== -1).toBe(true, attr);
}
}
);
@@ -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();
diff --git a/test/jasmine/tests/range_selector_test.js b/test/jasmine/tests/range_selector_test.js
index cae2373e79b..1a306e4b24d 100644
--- a/test/jasmine/tests/range_selector_test.js
+++ b/test/jasmine/tests/range_selector_test.js
@@ -9,6 +9,7 @@ var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');
var getRectCenter = require('../assets/get_rect_center');
var mouseEvent = require('../assets/mouse_event');
+var setConvert = require('@src/plots/cartesian/set_convert');
describe('range selector defaults:', function() {
@@ -16,7 +17,7 @@ describe('range selector defaults:', function() {
var handleDefaults = RangeSelector.handleDefaults;
- function supply(containerIn, containerOut) {
+ function supply(containerIn, containerOut, calendar) {
containerOut.domain = [0, 1];
var layout = {
@@ -25,7 +26,7 @@ describe('range selector defaults:', function() {
var counterAxes = ['yaxis'];
- handleDefaults(containerIn, containerOut, layout, counterAxes);
+ handleDefaults(containerIn, containerOut, layout, counterAxes, calendar);
}
it('should set \'visible\' to false when no buttons are present', function() {
@@ -93,7 +94,7 @@ describe('range selector defaults:', function() {
};
var containerOut = {};
- supply(containerIn, containerOut, {}, []);
+ supply(containerIn, containerOut);
expect(containerOut.rangeselector.visible).toBe(true);
expect(containerOut.rangeselector.buttons).toEqual([
@@ -113,7 +114,7 @@ describe('range selector defaults:', function() {
};
var containerOut = {};
- supply(containerIn, containerOut, {}, []);
+ supply(containerIn, containerOut);
expect(containerOut.rangeselector.buttons).toEqual([{
step: 'all',
@@ -175,6 +176,53 @@ describe('range selector defaults:', function() {
expect(containerOut.rangeselector.x).toEqual(0.5);
expect(containerOut.rangeselector.y).toBeCloseTo(0.87);
});
+
+ it('should not allow month/year todate with calendars other than Gregorian', function() {
+ var containerIn = {
+ rangeselector: {
+ buttons: [{
+ step: 'year',
+ count: 1,
+ stepmode: 'todate'
+ }, {
+ step: 'month',
+ count: 6,
+ stepmode: 'todate'
+ }, {
+ step: 'day',
+ count: 1,
+ stepmode: 'todate'
+ }, {
+ step: 'hour',
+ count: 1,
+ stepmode: 'todate'
+ }]
+ }
+ };
+ var containerOut;
+ function getStepmode(button) { return button.stepmode; }
+
+ containerOut = {};
+ supply(containerIn, containerOut);
+
+ expect(containerOut.rangeselector.buttons.map(getStepmode)).toEqual([
+ 'todate', 'todate', 'todate', 'todate'
+ ]);
+
+ containerOut = {};
+ supply(containerIn, containerOut, 'gregorian');
+
+ expect(containerOut.rangeselector.buttons.map(getStepmode)).toEqual([
+ 'todate', 'todate', 'todate', 'todate'
+ ]);
+
+ containerOut = {};
+ supply(containerIn, containerOut, 'chinese');
+
+ expect(containerOut.rangeselector.buttons.map(getStepmode)).toEqual([
+ 'backward', 'backward', 'todate', 'todate'
+ ]);
+ });
});
describe('range selector getUpdateObject:', function() {
@@ -185,14 +233,20 @@ describe('range selector getUpdateObject:', function() {
expect(update['xaxis.range[1]']).toEqual(range1);
}
+ function setupAxis(opts) {
+ var axisOut = Lib.extendFlat({type: 'date'}, opts);
+ setConvert(axisOut);
+ return axisOut;
+ }
+
// buttonLayout: {step, stepmode, count}
// range0out: expected resulting range[0] (input is always '1948-01-01')
// range1: input range[1], expected to also be the output
function assertUpdateCase(buttonLayout, range0out, range1) {
- var axisLayout = {
+ var axisLayout = setupAxis({
_name: 'xaxis',
range: ['1948-01-01', range1]
- };
+ });
var update = getUpdateObject(axisLayout, buttonLayout);
@@ -280,10 +334,10 @@ describe('range selector getUpdateObject:', function() {
});
it('should return update object (reset case)', function() {
- var axisLayout = {
+ var axisLayout = setupAxis({
_name: 'xaxis',
range: ['1948-01-01', '2015-11-30']
- };
+ });
var buttonLayout = {
step: 'all'
@@ -375,10 +429,10 @@ describe('range selector getUpdateObject:', function() {
});
it('should return update object with correct axis names', function() {
- var axisLayout = {
+ var axisLayout = setupAxis({
_name: 'xaxis5',
range: ['1948-01-01', '2015-11-30']
- };
+ });
var buttonLayout = {
step: 'month',
diff --git a/test/jasmine/tests/scatter3d_test.js b/test/jasmine/tests/scatter3d_test.js
index 1b165b7cff6..7518da16166 100644
--- a/test/jasmine/tests/scatter3d_test.js
+++ b/test/jasmine/tests/scatter3d_test.js
@@ -8,9 +8,9 @@ describe('Scatter3D defaults', function() {
var defaultColor = '#d3d3d3';
- function _supply(traceIn) {
+ function _supply(traceIn, layoutEdits) {
var traceOut = { visible: true },
- layout = { _dataLength: 1 };
+ layout = Lib.extendFlat({ _dataLength: 1 }, layoutEdits);
Scatter3D.supplyDefaults(traceIn, traceOut, defaultColor, layout);
return traceOut;
@@ -65,4 +65,27 @@ describe('Scatter3D defaults', function() {
expect(out.marker.color).toBe(color);
expect(out.marker.line.color).toBe(Color.defaultLine);
});
+
+ it('should inherit layout.calendar', function() {
+ var out = _supply(base, {calendar: 'islamic'});
+
+ // we always fill calendar attributes, because it's hard to tell if
+ // we're on a date axis at this point.
+ expect(out.xcalendar).toBe('islamic');
+ expect(out.ycalendar).toBe('islamic');
+ expect(out.zcalendar).toBe('islamic');
+ });
+
+ it('should take its own calendars', function() {
+ var traceIn = Lib.extendFlat({}, base, {
+ xcalendar: 'coptic',
+ ycalendar: 'ethiopian',
+ zcalendar: 'mayan'
+ });
+ var out = _supply(traceIn, {calendar: 'islamic'});
+
+ expect(out.xcalendar).toBe('coptic');
+ expect(out.ycalendar).toBe('ethiopian');
+ expect(out.zcalendar).toBe('mayan');
+ });
});
diff --git a/test/jasmine/tests/scatter_test.js b/test/jasmine/tests/scatter_test.js
index 8c948779459..1dcb3abbee3 100644
--- a/test/jasmine/tests/scatter_test.js
+++ b/test/jasmine/tests/scatter_test.js
@@ -92,6 +92,32 @@ describe('Test scatter', function() {
supplyDefaults(traceIn, traceOut, defaultColor, layout);
expect(traceOut.hoveron).toBe('points');
});
+
+ it('should inherit layout.calendar', function() {
+ traceIn = {
+ x: [1, 2, 3],
+ y: [1, 2, 3]
+ };
+ supplyDefaults(traceIn, traceOut, defaultColor, {calendar: 'islamic'});
+
+ // we always fill calendar attributes, because it's hard to tell if
+ // we're on a date axis at this point.
+ expect(traceOut.xcalendar).toBe('islamic');
+ expect(traceOut.ycalendar).toBe('islamic');
+ });
+
+ it('should take its own calendars', function() {
+ traceIn = {
+ x: [1, 2, 3],
+ y: [1, 2, 3],
+ xcalendar: 'coptic',
+ ycalendar: 'ethiopian'
+ };
+ supplyDefaults(traceIn, traceOut, defaultColor, {calendar: 'islamic'});
+
+ expect(traceOut.xcalendar).toBe('coptic');
+ expect(traceOut.ycalendar).toBe('ethiopian');
+ });
});
describe('isBubble', function() {
diff --git a/test/jasmine/tests/surface_test.js b/test/jasmine/tests/surface_test.js
index 73905747f22..96aa26f0193 100644
--- a/test/jasmine/tests/surface_test.js
+++ b/test/jasmine/tests/surface_test.js
@@ -150,5 +150,32 @@ describe('Test surface', function() {
expect(traceOut.cmin).toBeUndefined();
expect(traceOut.cmax).toBeUndefined();
});
+
+ it('should inherit layout.calendar', function() {
+ traceIn = {
+ z: [[1, 2, 3], [2, 1, 2]]
+ };
+ supplyDefaults(traceIn, traceOut, defaultColor, {calendar: 'islamic'});
+
+ // we always fill calendar attributes, because it's hard to tell if
+ // we're on a date axis at this point.
+ expect(traceOut.xcalendar).toBe('islamic');
+ expect(traceOut.ycalendar).toBe('islamic');
+ expect(traceOut.zcalendar).toBe('islamic');
+ });
+
+ it('should take its own calendars', function() {
+ var traceIn = {
+ z: [[1, 2, 3], [2, 1, 2]],
+ xcalendar: 'coptic',
+ ycalendar: 'ethiopian',
+ zcalendar: 'mayan'
+ };
+
+ supplyDefaults(traceIn, traceOut, defaultColor, layout);
+ expect(traceOut.xcalendar).toBe('coptic');
+ expect(traceOut.ycalendar).toBe('ethiopian');
+ expect(traceOut.zcalendar).toBe('mayan');
+ });
});
});
diff --git a/test/jasmine/tests/updatemenus_test.js b/test/jasmine/tests/updatemenus_test.js
index ef2128feaa2..37a3a415e2d 100644
--- a/test/jasmine/tests/updatemenus_test.js
+++ b/test/jasmine/tests/updatemenus_test.js
@@ -698,7 +698,7 @@ describe('update menus interactions', function() {
// must compare with a tolerance as the exact result
// is browser/font dependent (via getBBox)
- expect(Math.abs(actualWidth - width)).toBeLessThan(12);
+ expect(Math.abs(actualWidth - width)).toBeLessThan(16);
// height is determined by 'fontsize',
// so no such tolerance is needed