diff --git a/lib/candlestick.js b/lib/candlestick.js
new file mode 100644
index 00000000000..b4b0f06e6de
--- /dev/null
+++ b/lib/candlestick.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/traces/candlestick');
diff --git a/lib/index-finance.js b/lib/index-finance.js
new file mode 100644
index 00000000000..c612bd02df8
--- /dev/null
+++ b/lib/index-finance.js
@@ -0,0 +1,21 @@
+/**
+* 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 Plotly = require('./core');
+
+Plotly.register([
+ require('./bar'),
+ require('./histogram'),
+ require('./pie'),
+ require('./ohlc'),
+ require('./candlestick')
+]);
+
+module.exports = Plotly;
diff --git a/lib/index.js b/lib/index.js
index 359de92a48e..c578165aadd 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -20,15 +20,22 @@ Plotly.register([
require('./histogram2dcontour'),
require('./pie'),
require('./contour'),
+ require('./scatterternary'),
+
require('./scatter3d'),
require('./surface'),
require('./mesh3d'),
+
require('./scattergeo'),
require('./choropleth'),
+
require('./scattergl'),
require('./pointcloud'),
- require('./scatterternary'),
- require('./scattermapbox')
+
+ require('./scattermapbox'),
+
+ require('./ohlc'),
+ require('./candlestick')
]);
// transforms
diff --git a/lib/ohlc.js b/lib/ohlc.js
new file mode 100644
index 00000000000..989e77e521c
--- /dev/null
+++ b/lib/ohlc.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/traces/ohlc');
diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js
index fd08a20baa5..17258ffbd21 100644
--- a/src/components/legend/draw.js
+++ b/src/components/legend/draw.js
@@ -367,10 +367,28 @@ function drawTexts(g, gd) {
.call(textLayout)
.on('edit', function(text) {
this.attr({'data-unformatted': text});
+
this.text(text)
.call(textLayout);
+
if(!this.text()) text = ' \u0020\u0020 ';
- Plotly.restyle(gd, 'name', text, traceIndex);
+
+ var fullInput = legendItem.trace._fullInput || {},
+ astr;
+
+ // N.B. this block isn't super clean,
+ // is unfortunately untested at the moment,
+ // and only works for for 'ohlc' and 'candlestick',
+ // but should be generalized for other one-to-many transforms
+ if(['ohlc', 'candlestick'].indexOf(fullInput.type) !== -1) {
+ var transforms = legendItem.trace.transforms,
+ direction = transforms[transforms.length - 1].direction;
+
+ astr = direction + '.legenditem.name';
+ }
+ else astr = 'name';
+
+ Plotly.restyle(gd, astr, text, traceIndex);
});
}
else text.call(textLayout);
diff --git a/src/lib/coerce.js b/src/lib/coerce.js
index d0f8565847f..11f9153cac1 100644
--- a/src/lib/coerce.js
+++ b/src/lib/coerce.js
@@ -13,6 +13,7 @@ var isNumeric = require('fast-isnumeric');
var tinycolor = require('tinycolor2');
var nestedProperty = require('./nested_property');
var isPlainObject = require('./is_plain_object');
+var filterUnique = require('./filter_unique');
var getColorscale = require('../components/colorscale/get_scale');
var colorscaleNames = Object.keys(require('../components/colorscale/scales'));
@@ -457,5 +458,17 @@ exports.findArrayAttributes = function(trace) {
exports.crawl(trace._module.attributes, callback);
+ // Look into the fullInput module attributes for array attributes
+ // to make sure that 'custom' array attributes are detected.
+ //
+ // At the moment, we need this block to make sure that
+ // ohlc and candlestick 'open', 'high', 'low', 'close' can be
+ // used with filter ang groupby transforms.
+ if(trace._fullInput) {
+ exports.crawl(trace._fullInput._module.attributes, callback);
+
+ arrayAttributes = filterUnique(arrayAttributes);
+ }
+
return arrayAttributes;
};
diff --git a/src/lib/filter_unique.js b/src/lib/filter_unique.js
new file mode 100644
index 00000000000..d31028d7bac
--- /dev/null
+++ b/src/lib/filter_unique.js
@@ -0,0 +1,49 @@
+/**
+* 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';
+
+
+/**
+ * Return news array containing only the unique items
+ * found in input array.
+ *
+ * IMPORTANT: Note that items are considered unique
+ * if `String({})` is unique. For example;
+ *
+ * Lib.filterUnique([ { a: 1 }, { b: 2 } ])
+ *
+ * returns [{ a: 1 }]
+ *
+ * and
+ *
+ * Lib.filterUnique([ '1', 1 ])
+ *
+ * returns ['1']
+ *
+ *
+ * @param {array} array base array
+ * @return {array} new filtered array
+ */
+module.exports = function filterUnique(array) {
+ var seen = {},
+ out = [],
+ j = 0;
+
+ for(var i = 0; i < array.length; i++) {
+ var item = array[i];
+
+ if(seen[item] !== 1) {
+ seen[item] = 1;
+ out[j++] = item;
+ }
+ }
+
+ return out;
+};
diff --git a/src/lib/index.js b/src/lib/index.js
index 3518fca5d31..37d492dbf23 100644
--- a/src/lib/index.js
+++ b/src/lib/index.js
@@ -75,6 +75,8 @@ lib.error = loggersModule.error;
lib.notifier = require('./notifier');
+lib.filterUnique = require('./filter_unique');
+
/**
* swap x and y of the same attribute in container cont
* specify attr with a ? in place of x/y
diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js
index 9b1976b4f7f..640912f281b 100644
--- a/src/plot_api/plot_api.js
+++ b/src/plot_api/plot_api.js
@@ -140,7 +140,7 @@ Plotly.plot = function(gd, data, layout, config) {
// generate calcdata, if we need to
// to force redoing calcdata, just delete it before calling Plotly.plot
- var recalc = !gd.calcdata || gd.calcdata.length !== (gd.data || []).length;
+ var recalc = !gd.calcdata || gd.calcdata.length !== (gd._fullData || []).length;
if(recalc) Plots.doCalcdata(gd);
// in case it has changed, attach fullData traces to calcdata
@@ -1245,6 +1245,7 @@ function _restyle(gd, aobj, _traces) {
'histfunc', 'histnorm', 'text',
'x', 'y', 'z',
'a', 'b', 'c',
+ 'open', 'high', 'low', 'close',
'xtype', 'x0', 'dx', 'ytype', 'y0', 'dy', 'xaxis', 'yaxis',
'line.width',
'connectgaps', 'transpose', 'zsmooth',
@@ -1287,7 +1288,8 @@ function _restyle(gd, aobj, _traces) {
// TODO: could we break this out as well?
var autorangeAttrs = [
'marker', 'marker.size', 'textfont',
- 'boxpoints', 'jitter', 'pointpos', 'whiskerwidth', 'boxmean'
+ 'boxpoints', 'jitter', 'pointpos', 'whiskerwidth', 'boxmean',
+ 'tickwidth'
];
// replotAttrs attributes need a replot (because different
diff --git a/src/plots/plots.js b/src/plots/plots.js
index 56aa8a9b696..c504faa0c37 100644
--- a/src/plots/plots.js
+++ b/src/plots/plots.js
@@ -369,7 +369,7 @@ plots.supplyDefaults = function(gd) {
// then do the data
newFullLayout._globalTransforms = (gd._context || {}).globalTransforms;
- plots.supplyDataDefaults(newData, newFullData, newFullLayout);
+ plots.supplyDataDefaults(newData, newFullData, newLayout, newFullLayout);
// attach helper method to check whether a plot type is present on graph
newFullLayout._has = plots._hasPlotType.bind(newFullLayout);
@@ -530,7 +530,7 @@ function relinkPrivateKeys(toContainer, fromContainer) {
var isPlainObject = Lib.isPlainObject,
isArray = Array.isArray;
- var keys = Object.keys(fromContainer);
+ var keys = Object.keys(fromContainer || {});
for(var i = 0; i < keys.length; i++) {
var k = keys[i],
@@ -593,9 +593,9 @@ plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLa
}
};
-plots.supplyDataDefaults = function(dataIn, dataOut, layout) {
- var modules = layout._modules = [],
- basePlotModules = layout._basePlotModules = [],
+plots.supplyDataDefaults = function(dataIn, dataOut, layout, fullLayout) {
+ var modules = fullLayout._modules = [],
+ basePlotModules = fullLayout._basePlotModules = [],
cnt = 0;
function pushModule(fullTrace) {
@@ -612,18 +612,18 @@ plots.supplyDataDefaults = function(dataIn, dataOut, layout) {
for(var i = 0; i < dataIn.length; i++) {
var trace = dataIn[i],
- fullTrace = plots.supplyTraceDefaults(trace, cnt, layout);
+ fullTrace = plots.supplyTraceDefaults(trace, cnt, fullLayout);
fullTrace.index = i;
fullTrace._input = trace;
fullTrace._expandedIndex = cnt;
if(fullTrace.transforms && fullTrace.transforms.length) {
- var expandedTraces = applyTransforms(fullTrace, dataOut, layout);
+ var expandedTraces = applyTransforms(fullTrace, dataOut, layout, fullLayout);
for(var j = 0; j < expandedTraces.length; j++) {
var expandedTrace = expandedTraces[j],
- fullExpandedTrace = plots.supplyTraceDefaults(expandedTrace, cnt, layout);
+ fullExpandedTrace = plots.supplyTraceDefaults(expandedTrace, cnt, fullLayout);
// mutate uid here using parent uid and expanded index
// to promote consistency between update calls
@@ -805,7 +805,7 @@ function supplyTransformDefaults(traceIn, traceOut, layout) {
if(!_module) Lib.warn('Unrecognized transform type ' + type + '.');
if(_module && _module.supplyDefaults) {
- transformOut = _module.supplyDefaults(transformIn, traceOut, layout);
+ transformOut = _module.supplyDefaults(transformIn, traceOut, layout, traceIn);
transformOut.type = type;
}
else {
@@ -816,7 +816,7 @@ function supplyTransformDefaults(traceIn, traceOut, layout) {
}
}
-function applyTransforms(fullTrace, fullData, layout) {
+function applyTransforms(fullTrace, fullData, layout, fullLayout) {
var container = fullTrace.transforms,
dataOut = [fullTrace];
@@ -830,6 +830,7 @@ function applyTransforms(fullTrace, fullData, layout) {
fullTrace: fullTrace,
fullData: fullData,
layout: layout,
+ fullLayout: fullLayout,
transformIndex: i
});
}
diff --git a/src/traces/candlestick/attributes.js b/src/traces/candlestick/attributes.js
new file mode 100644
index 00000000000..64b7be83203
--- /dev/null
+++ b/src/traces/candlestick/attributes.js
@@ -0,0 +1,56 @@
+/**
+* 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 Lib = require('../../lib');
+var OHLCattrs = require('../ohlc/attributes');
+var boxAttrs = require('../box/attributes');
+
+var directionAttrs = {
+ name: OHLCattrs.increasing.name,
+ showlegend: OHLCattrs.increasing.showlegend,
+
+ line: {
+ color: Lib.extendFlat({}, boxAttrs.line.color),
+ width: Lib.extendFlat({}, boxAttrs.line.width)
+ },
+
+ fillcolor: Lib.extendFlat({}, boxAttrs.fillcolor),
+};
+
+module.exports = {
+ x: OHLCattrs.x,
+ open: OHLCattrs.open,
+ high: OHLCattrs.high,
+ low: OHLCattrs.low,
+ close: OHLCattrs.close,
+
+ line: {
+ width: Lib.extendFlat({}, boxAttrs.line.width, {
+ description: [
+ boxAttrs.line.width.description,
+ 'Note that this style setting can also be set per',
+ 'direction via `increasing.line.width` and',
+ '`decreasing.line.width`.'
+ ].join(' ')
+ })
+ },
+
+ increasing: Lib.extendDeep({}, directionAttrs, {
+ line: { color: { dflt: OHLCattrs.increasing.line.color.dflt } }
+ }),
+
+ decreasing: Lib.extendDeep({}, directionAttrs, {
+ line: { color: { dflt: OHLCattrs.decreasing.line.color.dflt } }
+ }),
+
+ text: OHLCattrs.text,
+ whiskerwidth: Lib.extendFlat({}, boxAttrs.whiskerwidth, { dflt: 0 })
+};
diff --git a/src/traces/candlestick/defaults.js b/src/traces/candlestick/defaults.js
new file mode 100644
index 00000000000..c6b4538ea53
--- /dev/null
+++ b/src/traces/candlestick/defaults.js
@@ -0,0 +1,46 @@
+/**
+* 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 Lib = require('../../lib');
+var handleOHLC = require('../ohlc/ohlc_defaults');
+var handleDirectionDefaults = require('../ohlc/direction_defaults');
+var helpers = require('../ohlc/helpers');
+var attributes = require('./attributes');
+
+module.exports = function supplyDefaults(traceIn, traceOut) {
+ helpers.pushDummyTransformOpts(traceIn, traceOut);
+
+ function coerce(attr, dflt) {
+ return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
+ }
+
+ var len = handleOHLC(traceIn, traceOut, coerce);
+ if(len === 0) {
+ traceOut.visible = false;
+ return;
+ }
+
+ coerce('line.width');
+
+ handleDirection(traceIn, traceOut, coerce, 'increasing');
+ handleDirection(traceIn, traceOut, coerce, 'decreasing');
+
+ coerce('text');
+ coerce('whiskerwidth');
+};
+
+function handleDirection(traceIn, traceOut, coerce, direction) {
+ handleDirectionDefaults(traceIn, traceOut, coerce, direction);
+
+ coerce(direction + '.line.color');
+ coerce(direction + '.line.width', traceOut.line.width);
+ coerce(direction + '.fillcolor');
+}
diff --git a/src/traces/candlestick/index.js b/src/traces/candlestick/index.js
new file mode 100644
index 00000000000..ccd1d5427aa
--- /dev/null
+++ b/src/traces/candlestick/index.js
@@ -0,0 +1,40 @@
+/**
+* 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 register = require('../../plot_api/register');
+
+module.exports = {
+ moduleType: 'trace',
+ name: 'candlestick',
+ basePlotModule: require('../../plots/cartesian'),
+ categories: ['cartesian', 'showLegend'],
+ meta: {
+ description: [
+ 'The candlestick is a style of financial chart describing',
+ 'open, high, low and close for a given `x` coordinate (most likely time).',
+
+ 'The boxes represent the spread between the `open` and `close` values and',
+ 'the lines represent the spread between the `low` and `high` values',
+
+ 'Sample points where the close value is higher (lower) then the open',
+ 'value are called increasing (decreasing).',
+
+ 'By default, increasing candles are drawn in green whereas',
+ 'decreasing are drawn in red.'
+ ].join(' ')
+ },
+
+ attributes: require('./attributes'),
+ supplyDefaults: require('./defaults'),
+};
+
+register(require('../box'));
+register(require('./transform'));
diff --git a/src/traces/candlestick/transform.js b/src/traces/candlestick/transform.js
new file mode 100644
index 00000000000..6d32870e3ce
--- /dev/null
+++ b/src/traces/candlestick/transform.js
@@ -0,0 +1,125 @@
+/**
+* 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 Lib = require('../../lib');
+var helpers = require('../ohlc/helpers');
+
+exports.moduleType = 'transform';
+
+exports.name = 'candlestick';
+
+exports.attributes = {};
+
+exports.supplyDefaults = function(transformIn, traceOut, layout, traceIn) {
+ helpers.clearEphemeralTransformOpts(traceIn);
+ helpers.copyOHLC(transformIn, traceOut);
+
+ return transformIn;
+};
+
+exports.transform = function transform(dataIn, state) {
+ var dataOut = [];
+
+ for(var i = 0; i < dataIn.length; i++) {
+ var traceIn = dataIn[i];
+
+ if(traceIn.type !== 'candlestick') {
+ dataOut.push(traceIn);
+ continue;
+ }
+
+ dataOut.push(
+ makeTrace(traceIn, state, 'increasing'),
+ makeTrace(traceIn, state, 'decreasing')
+ );
+ }
+
+ helpers.addRangeSlider(state.layout);
+
+ return dataOut;
+};
+
+function makeTrace(traceIn, state, direction) {
+ var traceOut = {
+ type: 'box',
+ boxpoints: false,
+
+ visible: traceIn.visible,
+ hoverinfo: traceIn.hoverinfo,
+ opacity: traceIn.opacity,
+ xaxis: traceIn.xaxis,
+ yaxis: traceIn.yaxis,
+
+ transforms: helpers.makeTransform(traceIn, state, direction)
+ };
+
+ // the rest of below may not have been coerced
+
+ var directionOpts = traceIn[direction];
+
+ if(directionOpts) {
+ Lib.extendFlat(traceOut, {
+
+ // to make autotype catch date axes soon!!
+ x: traceIn.x || [0],
+
+ // concat low and high to get correct autorange
+ y: [].concat(traceIn.low).concat(traceIn.high),
+
+ whiskerwidth: traceIn.whiskerwidth,
+ text: traceIn.text,
+
+ name: directionOpts.name,
+ showlegend: directionOpts.showlegend,
+ line: directionOpts.line,
+ fillcolor: directionOpts.fillcolor
+ });
+ }
+
+ return traceOut;
+}
+
+exports.calcTransform = function calcTransform(gd, trace, opts) {
+ var direction = opts.direction,
+ filterFn = helpers.getFilterFn(direction);
+
+ var open = trace.open,
+ high = trace.high,
+ low = trace.low,
+ close = trace.close;
+
+ var len = open.length,
+ x = [],
+ y = [];
+
+ var appendX = trace._fullInput.x ?
+ function(i) {
+ var v = trace.x[i];
+ x.push(v, v, v, v, v, v);
+ } :
+ function(i) {
+ x.push(i, i, i, i, i, i);
+ };
+
+ var appendY = function(o, h, l, c) {
+ y.push(l, o, c, c, c, h);
+ };
+
+ for(var i = 0; i < len; i++) {
+ if(filterFn(open[i], close[i])) {
+ appendX(i);
+ appendY(open[i], high[i], low[i], close[i]);
+ }
+ }
+
+ trace.x = x;
+ trace.y = y;
+};
diff --git a/src/traces/ohlc/attributes.js b/src/traces/ohlc/attributes.js
new file mode 100644
index 00000000000..b4168829c97
--- /dev/null
+++ b/src/traces/ohlc/attributes.js
@@ -0,0 +1,133 @@
+/**
+* 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 Lib = require('../../lib');
+var scatterAttrs = require('../scatter/attributes');
+
+var INCREASING_COLOR = '#3D9970';
+var DECREASING_COLOR = '#FF4136';
+
+var lineAttrs = scatterAttrs.line;
+
+var directionAttrs = {
+ name: {
+ valType: 'string',
+ role: 'info',
+ description: [
+ 'Sets the segment name.',
+ 'The segment name appear as the legend item and on hover.'
+ ].join(' ')
+ },
+
+ showlegend: {
+ valType: 'boolean',
+ role: 'info',
+ dflt: true,
+ description: [
+ 'Determines whether or not an item corresponding to this',
+ 'segment is shown in the legend.'
+ ].join(' ')
+ },
+
+ line: {
+ color: Lib.extendFlat({}, lineAttrs.color),
+ width: Lib.extendFlat({}, lineAttrs.width),
+ dash: Lib.extendFlat({}, lineAttrs.dash),
+ }
+};
+
+module.exports = {
+
+ x: {
+ valType: 'data_array',
+ description: [
+ 'Sets the x coordinates.',
+ 'If absent, linear coordinate will be generated.'
+ ].join(' ')
+ },
+
+ open: {
+ valType: 'data_array',
+ dflt: [],
+ description: 'Sets the open values.'
+ },
+
+ high: {
+ valType: 'data_array',
+ dflt: [],
+ description: 'Sets the high values.'
+ },
+
+ low: {
+ valType: 'data_array',
+ dflt: [],
+ description: 'Sets the low values.'
+ },
+
+ close: {
+ valType: 'data_array',
+ dflt: [],
+ description: 'Sets the close values.'
+ },
+
+ line: {
+ width: Lib.extendFlat({}, lineAttrs.width, {
+ description: [
+ lineAttrs.width,
+ 'Note that this style setting can also be set per',
+ 'direction via `increasing.line.width` and',
+ '`decreasing.line.width`.'
+ ].join(' ')
+ }),
+ dash: Lib.extendFlat({}, lineAttrs.dash, {
+ description: [
+ lineAttrs.dash,
+ 'Note that this style setting can also be set per',
+ 'direction via `increasing.line.dash` and',
+ '`decreasing.line.dash`.'
+ ].join(' ')
+ }),
+ },
+
+ increasing: Lib.extendDeep({}, directionAttrs, {
+ line: { color: { dflt: INCREASING_COLOR } }
+ }),
+
+ decreasing: Lib.extendDeep({}, directionAttrs, {
+ line: { color: { dflt: DECREASING_COLOR } }
+ }),
+
+ text: {
+ valType: 'string',
+ role: 'info',
+ dflt: '',
+ arrayOk: true,
+ description: [
+ 'Sets hover text elements associated with each sample point.',
+ 'If a single string, the same string appears over',
+ 'all the data points.',
+ 'If an array of string, the items are mapped in order to',
+ 'this trace\'s sample points.'
+ ].join(' ')
+ },
+
+ tickwidth: {
+ valType: 'number',
+ min: 0,
+ max: 0.5,
+ dflt: 0.3,
+ role: 'style',
+ description: [
+ 'Sets the width of the open/close tick marks',
+ 'relative to the *x* minimal interval.'
+ ].join(' ')
+ }
+};
diff --git a/src/traces/ohlc/defaults.js b/src/traces/ohlc/defaults.js
new file mode 100644
index 00000000000..791535de251
--- /dev/null
+++ b/src/traces/ohlc/defaults.js
@@ -0,0 +1,47 @@
+/**
+* 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 Lib = require('../../lib');
+var handleOHLC = require('./ohlc_defaults');
+var handleDirectionDefaults = require('./direction_defaults');
+var attributes = require('./attributes');
+var helpers = require('./helpers');
+
+module.exports = function supplyDefaults(traceIn, traceOut) {
+ helpers.pushDummyTransformOpts(traceIn, traceOut);
+
+ function coerce(attr, dflt) {
+ return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
+ }
+
+ var len = handleOHLC(traceIn, traceOut, coerce);
+ if(len === 0) {
+ traceOut.visible = false;
+ return;
+ }
+
+ coerce('line.width');
+ coerce('line.dash');
+
+ handleDirection(traceIn, traceOut, coerce, 'increasing');
+ handleDirection(traceIn, traceOut, coerce, 'decreasing');
+
+ coerce('text');
+ coerce('tickwidth');
+};
+
+function handleDirection(traceIn, traceOut, coerce, direction) {
+ handleDirectionDefaults(traceIn, traceOut, coerce, direction);
+
+ coerce(direction + '.line.color');
+ coerce(direction + '.line.width', traceOut.line.width);
+ coerce(direction + '.line.dash', traceOut.line.dash);
+}
diff --git a/src/traces/ohlc/direction_defaults.js b/src/traces/ohlc/direction_defaults.js
new file mode 100644
index 00000000000..3abda8a2e68
--- /dev/null
+++ b/src/traces/ohlc/direction_defaults.js
@@ -0,0 +1,24 @@
+/**
+* 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 = function handleDirectionDefaults(traceIn, traceOut, coerce, direction) {
+ coerce(direction + '.showlegend');
+
+ // trace-wide *showlegend* overrides direction *showlegend*
+ if(traceIn.showlegend === false) {
+ traceOut[direction].showlegend = false;
+ }
+
+ var nameDflt = traceOut.name + ' - ' + direction;
+
+ coerce(direction + '.name', nameDflt);
+};
diff --git a/src/traces/ohlc/helpers.js b/src/traces/ohlc/helpers.js
new file mode 100644
index 00000000000..8167f732dbd
--- /dev/null
+++ b/src/traces/ohlc/helpers.js
@@ -0,0 +1,110 @@
+/**
+* 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 Lib = require('../../lib');
+
+// This routine gets called during the trace supply-defaults step.
+//
+// This is a hacky way to make 'ohlc' and 'candlestick' trace types
+// go through the transform machinery.
+//
+// Note that, we must mutate user data (here traceIn) as opposed
+// to full data (here traceOut) as - at the moment - transform
+// defaults (which are called after trace defaults) start
+// from a clear transforms container. The mutations inflicted are
+// cleared in exports.clearEphemeralTransformOpts.
+exports.pushDummyTransformOpts = function(traceIn, traceOut) {
+ var transformOpts = {
+
+ // give dummy transform the same type as trace
+ type: traceOut.type,
+
+ // track ephemeral transforms in user data
+ _ephemeral: true
+ };
+
+ if(Array.isArray(traceIn.transforms)) {
+ traceIn.transforms.push(transformOpts);
+ }
+ else {
+ traceIn.transforms = [transformOpts];
+ }
+};
+
+// This routine gets called during the transform supply-defaults step
+// where it clears ephemeral transform opts in user data
+// and effectively put back user date in its pre-supplyDefaults state.
+exports.clearEphemeralTransformOpts = function(traceIn) {
+ var transformsIn = traceIn.transforms;
+
+ if(!Array.isArray(transformsIn)) return;
+
+ for(var i = 0; i < transformsIn.length; i++) {
+ if(transformsIn[i]._ephemeral) transformsIn.splice(i, 1);
+ }
+
+ if(transformsIn.length === 0) delete traceIn.transforms;
+};
+
+// This routine gets called during the transform supply-defaults step
+// where it passes 'ohlc' and 'candlestick' attributes
+// (found the transform container via exports.makeTransform)
+// to the traceOut container such that they can
+// be compatible with filter and groupby transforms.
+//
+// Note that this routine only has an effect during the
+// second round of transform defaults done on generated traces
+exports.copyOHLC = function(container, traceOut) {
+ if(container.open) traceOut.open = container.open;
+ if(container.high) traceOut.high = container.high;
+ if(container.low) traceOut.low = container.low;
+ if(container.close) traceOut.close = container.close;
+};
+
+// This routine gets called during the applyTransform step.
+//
+// We need to track trace attributes and which direction
+// ('increasing' or 'decreasing')
+// the generated correspond to for the calcTransform step.
+//
+// To make sure that the attributes reach the calcTransform,
+// store it in the transform opts object.
+exports.makeTransform = function(traceIn, state, direction) {
+ var out = Lib.extendFlat([], traceIn.transforms);
+
+ out[state.transformIndex] = {
+ type: traceIn.type,
+ direction: direction,
+
+ // these are copied to traceOut during exports.copyOHLC
+ open: traceIn.open,
+ high: traceIn.high,
+ low: traceIn.low,
+ close: traceIn.close
+ };
+
+ return out;
+};
+
+exports.getFilterFn = function(direction) {
+ switch(direction) {
+ case 'increasing':
+ return function(o, c) { return o <= c; };
+
+ case 'decreasing':
+ return function(o, c) { return o > c; };
+ }
+};
+
+exports.addRangeSlider = function(layout) {
+ if(!layout.xaxis) layout.xaxis = {};
+ if(!layout.xaxis.rangeslider) layout.xaxis.rangeslider = {};
+};
diff --git a/src/traces/ohlc/index.js b/src/traces/ohlc/index.js
new file mode 100644
index 00000000000..edaa23a1d32
--- /dev/null
+++ b/src/traces/ohlc/index.js
@@ -0,0 +1,40 @@
+/**
+* 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 register = require('../../plot_api/register');
+
+module.exports = {
+ moduleType: 'trace',
+ name: 'ohlc',
+ basePlotModule: require('../../plots/cartesian'),
+ categories: ['cartesian', 'showLegend'],
+ meta: {
+ description: [
+ 'The ohlc (short for Open-High-Low-Close) is a style of financial chart describing',
+ 'open, high, low and close for a given `x` coordinate (most likely time).',
+
+ 'The tip of the lines represent the `low` and `high` values and',
+ 'the horizontal segments represent the `open` and `close` values.',
+
+ 'Sample points where the close value is higher (lower) then the open',
+ 'value are called increasing (decreasing).',
+
+ 'By default, increasing candles are drawn in green whereas',
+ 'decreasing are drawn in red.'
+ ].join(' ')
+ },
+
+ attributes: require('./attributes'),
+ supplyDefaults: require('./defaults'),
+};
+
+register(require('../scatter'));
+register(require('./transform'));
diff --git a/src/traces/ohlc/ohlc_defaults.js b/src/traces/ohlc/ohlc_defaults.js
new file mode 100644
index 00000000000..33e335f4188
--- /dev/null
+++ b/src/traces/ohlc/ohlc_defaults.js
@@ -0,0 +1,34 @@
+/**
+* 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 = function handleOHLC(traceIn, traceOut, coerce) {
+ var len;
+
+ var x = coerce('x'),
+ open = coerce('open'),
+ high = coerce('high'),
+ low = coerce('low'),
+ close = coerce('close');
+
+ len = Math.min(open.length, high.length, low.length, close.length);
+
+ if(x) {
+ len = Math.min(len, x.length);
+ if(len < x.length) traceOut.x = x.slice(0, len);
+ }
+
+ if(len < open.length) traceOut.open = open.slice(0, len);
+ if(len < high.length) traceOut.high = high.slice(0, len);
+ if(len < low.length) traceOut.low = low.slice(0, len);
+ if(len < close.length) traceOut.close = close.slice(0, len);
+
+ return len;
+};
diff --git a/src/traces/ohlc/transform.js b/src/traces/ohlc/transform.js
new file mode 100644
index 00000000000..f7e0b959818
--- /dev/null
+++ b/src/traces/ohlc/transform.js
@@ -0,0 +1,205 @@
+/**
+* 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 Lib = require('../../lib');
+var helpers = require('./helpers');
+var Axes = require('../../plots/cartesian/axes');
+var axisIds = require('../../plots/cartesian/axis_ids');
+
+exports.moduleType = 'transform';
+
+exports.name = 'ohlc';
+
+exports.attributes = {};
+
+exports.supplyDefaults = function(transformIn, traceOut, layout, traceIn) {
+ helpers.clearEphemeralTransformOpts(traceIn);
+ helpers.copyOHLC(transformIn, traceOut);
+
+ return transformIn;
+};
+
+exports.transform = function transform(dataIn, state) {
+ var dataOut = [];
+
+ for(var i = 0; i < dataIn.length; i++) {
+ var traceIn = dataIn[i];
+
+ if(traceIn.type !== 'ohlc') {
+ dataOut.push(traceIn);
+ continue;
+ }
+
+ dataOut.push(
+ makeTrace(traceIn, state, 'increasing'),
+ makeTrace(traceIn, state, 'decreasing')
+ );
+ }
+
+ helpers.addRangeSlider(state.layout);
+
+ return dataOut;
+};
+
+function makeTrace(traceIn, state, direction) {
+ var traceOut = {
+ type: 'scatter',
+ mode: 'lines',
+ connectgaps: false,
+
+ visible: traceIn.visible,
+ opacity: traceIn.opacity,
+ xaxis: traceIn.xaxis,
+ yaxis: traceIn.yaxis,
+
+ hoverinfo: makeHoverInfo(traceIn),
+ transforms: helpers.makeTransform(traceIn, state, direction)
+ };
+
+ // the rest of below may not have been coerced
+
+ var directionOpts = traceIn[direction];
+
+ if(directionOpts) {
+ Lib.extendFlat(traceOut, {
+
+ // to make autotype catch date axes soon!!
+ x: traceIn.x || [0],
+
+ // concat low and high to get correct autorange
+ y: [].concat(traceIn.low).concat(traceIn.high),
+
+ text: traceIn.text,
+
+ name: directionOpts.name,
+ showlegend: directionOpts.showlegend,
+ line: directionOpts.line
+ });
+ }
+
+ return traceOut;
+}
+
+// Let scatter hoverPoint format 'x' coordinates, if desired.
+//
+// Note that, this solution isn't perfect: it shows open and close
+// values at slightly different 'x' coordinates then the rest of the
+// segments, but is for more robust than calling `Axes.tickText` during
+// calcTransform.
+//
+// A future iteration should perhaps try to add a hook for transforms in
+// the hoverPoints handlers.
+function makeHoverInfo(traceIn) {
+ var hoverinfo = traceIn.hoverinfo;
+
+ if(hoverinfo === 'all') return 'x+text';
+
+ var parts = hoverinfo.split('+'),
+ hasX = parts.indexOf('x') !== -1,
+ hasText = parts.indexOf('text') !== -1;
+
+ if(hasX) {
+ if(hasText) return 'x+text';
+ else return 'x';
+
+ }
+ else return 'text';
+}
+
+exports.calcTransform = function calcTransform(gd, trace, opts) {
+ var direction = opts.direction,
+ filterFn = helpers.getFilterFn(direction);
+
+ var xa = axisIds.getFromTrace(gd, trace, 'x'),
+ ya = axisIds.getFromTrace(gd, trace, 'y'),
+ tickWidth = convertTickWidth(trace.x, xa, trace._fullInput.tickwidth);
+
+ var open = trace.open,
+ high = trace.high,
+ low = trace.low,
+ close = trace.close,
+ textIn = trace.text;
+
+ var len = open.length,
+ x = [],
+ y = [],
+ textOut = [];
+
+ var getXItem = trace._fullInput.x ?
+ function(i) { return xa.d2c(trace.x[i]); } :
+ function(i) { return i; };
+
+ var getTextItem = Array.isArray(textIn) ?
+ function(i) { return textIn[i] || ''; } :
+ function() { return textIn; };
+
+ var appendX = function(i) {
+ var v = getXItem(i);
+ x.push(v - tickWidth, v, v, v, v, v + tickWidth, null);
+ };
+
+ var appendY = function(o, h, l, c) {
+ y.push(o, o, h, l, c, c, null);
+ };
+
+ var format = function(ax, val) {
+ return Axes.tickText(ax, ax.c2l(val), 'hover').text;
+ };
+
+ var hoverinfo = trace._fullInput.hoverinfo,
+ hoverParts = hoverinfo.split('+'),
+ hasAll = hoverinfo === 'all',
+ hasY = hasAll || hoverParts.indexOf('y') !== -1,
+ hasText = hasAll || hoverParts.indexOf('text') !== -1;
+
+ var appendText = function(i, o, h, l, c) {
+ var t = [];
+
+ if(hasY) {
+ t.push('Open: ' + format(ya, o));
+ t.push('High: ' + format(ya, h));
+ t.push('Low: ' + format(ya, l));
+ t.push('Close: ' + format(ya, c));
+ }
+
+ if(hasText) t.push(getTextItem(i));
+
+ var _t = t.join('
');
+
+ textOut.push(_t, _t, _t, _t, _t, _t, null);
+ };
+
+ for(var i = 0; i < len; i++) {
+ if(filterFn(open[i], close[i])) {
+ appendX(i);
+ appendY(open[i], high[i], low[i], close[i]);
+ appendText(i, open[i], high[i], low[i], close[i]);
+ }
+ }
+
+ trace.x = x;
+ trace.y = y;
+ trace.text = textOut;
+};
+
+function convertTickWidth(coords, ax, tickWidth) {
+ if(coords.length < 2) return tickWidth;
+
+ var _coords = coords.map(ax.d2c),
+ minDTick = Math.abs(_coords[1] - _coords[0]);
+
+ for(var i = 1; i < _coords.length - 1; i++) {
+ var dist = Math.abs(_coords[i + 1] - _coords[i]);
+ minDTick = Math.min(dist, minDTick);
+ }
+
+ return minDTick * tickWidth;
+}
diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js
index f736c0e1a75..0cce190eab3 100644
--- a/src/transforms/groupby.js
+++ b/src/transforms/groupby.js
@@ -117,16 +117,13 @@ function transformOne(trace, state) {
var opts = state.transform;
var groups = trace.transforms[state.transformIndex].groups;
- var groupNames = groups.filter(function(g, i, self) {
- return self.indexOf(g) === i;
- });
-
if(!(Array.isArray(groups)) || groups.length === 0) {
return trace;
}
- var newData = new Array(groupNames.length);
- var len = groups.length;
+ var groupNames = Lib.filterUnique(groups),
+ newData = new Array(groupNames.length),
+ len = groups.length;
var arrayAttrs = Lib.findArrayAttributes(trace);
diff --git a/tasks/util/constants.js b/tasks/util/constants.js
index f5986e5a92b..c867dd2cf26 100644
--- a/tasks/util/constants.js
+++ b/tasks/util/constants.js
@@ -13,7 +13,7 @@ var pathToTopojsonSrc = path.join(
);
var partialBundleNames = [
- 'basic', 'cartesian', 'geo', 'gl3d', 'gl2d', 'mapbox'
+ 'basic', 'cartesian', 'geo', 'gl3d', 'gl2d', 'mapbox', 'finance'
];
var partialBundlePaths = partialBundleNames.map(function(name) {
diff --git a/test/image/baselines/candlestick_double-y-axis.png b/test/image/baselines/candlestick_double-y-axis.png
new file mode 100644
index 00000000000..8c6c223bc13
Binary files /dev/null and b/test/image/baselines/candlestick_double-y-axis.png differ
diff --git a/test/image/baselines/finance_style.png b/test/image/baselines/finance_style.png
new file mode 100644
index 00000000000..6bf7d6fa708
Binary files /dev/null and b/test/image/baselines/finance_style.png differ
diff --git a/test/image/baselines/ohlc_first.png b/test/image/baselines/ohlc_first.png
new file mode 100644
index 00000000000..4ce83c5aa83
Binary files /dev/null and b/test/image/baselines/ohlc_first.png differ
diff --git a/test/image/mocks/candlestick_double-y-axis.json b/test/image/mocks/candlestick_double-y-axis.json
new file mode 100644
index 00000000000..3d94eaa9e0f
--- /dev/null
+++ b/test/image/mocks/candlestick_double-y-axis.json
@@ -0,0 +1,1973 @@
+{
+ "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": [
+ 1452453861917,
+ 1471240462978
+ ]
+ },
+ "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
new file mode 100644
index 00000000000..c7fb15e7def
--- /dev/null
+++ b/test/image/mocks/finance_style.json
@@ -0,0 +1,228 @@
+{
+ "data": [
+ {
+ "type": "ohlc",
+ "open": [
+ 20.209909078315253,
+ 20.971458192536797,
+ 20.351637601564537,
+ 20.638787230238137,
+ 20.801207851356043,
+ 20.259400168418665,
+ 20.142546114709678,
+ 20.350481052458303,
+ 20.41237904058586,
+ 20.783078255505295,
+ 20.490837840387382,
+ 20.244009924485404,
+ 20.171651413925517,
+ 20.27146182932616,
+ 20.33826775209861,
+ 20.291279762863333,
+ 20.886620393408265,
+ 20.422687422471526,
+ 20.579767391892602,
+ 20.577853105114748
+ ],
+ "high": [
+ 21.282352659705946,
+ 21.80039935401815,
+ 21.089537739749208,
+ 21.458006854607813,
+ 21.45005601769593,
+ 21.267041591419993,
+ 21.301758579927615,
+ 21.198538628011864,
+ 21.292791856779516,
+ 21.57661801130053,
+ 21.421548824830023,
+ 21.041424941601797,
+ 21.236699171014312,
+ 21.27159841736843,
+ 21.918646868299803,
+ 21.191142086578246,
+ 21.58460796322318,
+ 21.580154750208806,
+ 21.96911073992379,
+ 21.20676528483314
+ ],
+ "low": [
+ 19.166795412593174,
+ 19.279658608486525,
+ 19.37402923741759,
+ 19.065260737441236,
+ 19.1800075208827,
+ 19.117474838928064,
+ 19.954902315389273,
+ 19.237903553876222,
+ 19.753163769806367,
+ 19.9469940900036,
+ 19.962987438067096,
+ 19.456007410577016,
+ 19.60888046562783,
+ 19.286884095632068,
+ 19.19108946202958,
+ 19.384679515021254,
+ 19.989347894799064,
+ 19.3781138384352,
+ 19.309287076745818,
+ 19.755579576495684
+ ],
+ "close": [
+ 20.39162164923735,
+ 20.861695663049506,
+ 20.29278072395081,
+ 20.96669764859506,
+ 20.219098866544158,
+ 20.928776644426442,
+ 20.99407238754678,
+ 20.046341836023863,
+ 20.21039924078742,
+ 20.946522272678727,
+ 20.120801482863463,
+ 20.31792491216739,
+ 20.30504503296301,
+ 20.565476958831894,
+ 20.85790378618509,
+ 20.32158387968238,
+ 20.7040288083194,
+ 20.374060562211536,
+ 20.609242660033345,
+ 20.9832297017119
+ ],
+ "increasing": {
+ "line": {
+ "color": "rgb(150, 200, 250)"
+ }
+ },
+ "decreasing": {
+ "line": {
+ "color": "rgb(128, 128, 128)",
+ "dash": "dash"
+ }
+ },
+ "line": {
+ "width": 3
+ },
+ "tickwidth": 0.2
+ },
+ {
+ "type": "candlestick",
+ "open": [
+ 25.853458179392945,
+ 25.07616990956719,
+ 25.65124333859165,
+ 25.48414021344863,
+ 25.056496862387217,
+ 25.809829475241305,
+ 25.827540062997834,
+ 25.324598504321568,
+ 25.968582089525757,
+ 25.963981380629363,
+ 25.50477114012293,
+ 25.864579556482603,
+ 25.291556901078152,
+ 25.506302688462558,
+ 25.973269631805568,
+ 25.07482258950527,
+ 25.778746613691958,
+ 25.584465908282777,
+ 25.70963747743168,
+ 25.689722592070876
+ ],
+ "high": [
+ 26.485005723474284,
+ 26.636147719832294,
+ 26.197422776766363,
+ 26.00895494268842,
+ 26.96755702751034,
+ 26.391642879657034,
+ 26.01747844412544,
+ 26.48949192184998,
+ 26.358868761393314,
+ 26.737067009462333,
+ 26.939764079398152,
+ 26.251475519829782,
+ 26.171922022898766,
+ 26.961884613679263,
+ 26.825362946253517,
+ 26.99227451066661,
+ 26.242392931731345,
+ 26.88687290949724,
+ 26.415791597955774,
+ 26.55335528041219
+ ],
+ "low": [
+ 24.246492786607476,
+ 24.913586962777618,
+ 24.03362745129435,
+ 24.77998098602382,
+ 24.225496241002414,
+ 24.267434499813042,
+ 24.54509560839949,
+ 24.16946017408691,
+ 24.37465918734527,
+ 24.007039222535273,
+ 24.0273825692926,
+ 24.027390639108862,
+ 24.461601813698984,
+ 24.182820955230333,
+ 24.970999371172905,
+ 24.506663751142867,
+ 24.880080413036524,
+ 24.031332563917474,
+ 24.98987648841576,
+ 24.37745016804795
+ ],
+ "close": [
+ 25.807359510055747,
+ 25.271040318548362,
+ 25.270764777180606,
+ 25.949049688936388,
+ 25.28272319483142,
+ 25.130455345098206,
+ 25.379055011804887,
+ 25.224254555209544,
+ 25.681480742436882,
+ 25.24894261431543,
+ 25.601206842520657,
+ 25.62137592881503,
+ 25.85919540251596,
+ 25.948933037384897,
+ 25.016925230484166,
+ 25.337655806066646,
+ 25.136689245624723,
+ 25.748766825352625,
+ 25.398644940922246,
+ 25.093220170315078
+ ],
+ "increasing": {
+ "line": {
+ "color": "#d3d3d3"
+ },
+ "fillcolor": "rgb(150, 200, 250)"
+ },
+ "decreasing": {
+ "line": {
+ "color": "#d3d3d3"
+ },
+ "fillcolor": "rgb(128, 128, 128)"
+ },
+ "line": {
+ "width": 2
+ },
+ "whiskerwidth": 0.5
+ }
+ ],
+ "layout": {
+ "xaxis": {
+ "rangeslider": {
+ "visible": false
+ }
+ },
+ "showlegend": false,
+ "height": 450,
+ "width": 1100,
+ "autosize": true
+ }
+}
diff --git a/test/image/mocks/ohlc_first.json b/test/image/mocks/ohlc_first.json
new file mode 100644
index 00000000000..b0625071788
--- /dev/null
+++ b/test/image/mocks/ohlc_first.json
@@ -0,0 +1,983 @@
+{
+ "data": [
+ {
+ "type": "ohlc",
+ "name": "Company A",
+ "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"
+ ]
+ }
+ ],
+ "layout": {
+ "xaxis": {
+ "range": [
+ 1472577187875,
+ 1475225280000
+ ]
+ },
+ "yaxis": {
+ "tickprefix": "$",
+ "showtickprefix": "last"
+ },
+ "legend": {
+ "x": 0,
+ "xanchor": "left",
+ "y": 1,
+ "yanchor": "bottom"
+ },
+ "height": 600,
+ "width": 1100
+ }
+}
diff --git a/test/jasmine/bundle_tests/finance_test.js b/test/jasmine/bundle_tests/finance_test.js
new file mode 100644
index 00000000000..b56e10e14b6
--- /dev/null
+++ b/test/jasmine/bundle_tests/finance_test.js
@@ -0,0 +1,41 @@
+var Plotly = require('@lib/core');
+var ohlc = require('@lib/ohlc');
+var candlestick = require('@lib/candlestick');
+
+var d3 = require('d3');
+var createGraphDiv = require('../assets/create_graph_div');
+var destroyGraphDiv = require('../assets/destroy_graph_div');
+
+describe('Bundle with finance trace type', function() {
+ 'use strict';
+
+ Plotly.register([ohlc, candlestick]);
+
+ var mock = require('@mocks/finance_style.json');
+
+ it('should register the correct trace modules for the generated traces', function() {
+ var transformModules = Object.keys(Plotly.Plots.transformsRegistry);
+
+ expect(transformModules).toEqual(['ohlc', 'candlestick']);
+ });
+
+ it('should register the correct trace modules for the generated traces', function() {
+ var traceModules = Object.keys(Plotly.Plots.modules);
+
+ expect(traceModules).toEqual(['scatter', 'box', 'ohlc', 'candlestick']);
+ });
+
+ it('should graph ohlc and candlestick traces', function(done) {
+
+ Plotly.plot(createGraphDiv(), mock.data, mock.layout).then(function() {
+ var gSubplot = d3.select('g.cartesianlayer');
+
+ expect(gSubplot.selectAll('g.trace.scatter').size()).toEqual(2);
+ expect(gSubplot.selectAll('g.trace.boxes').size()).toEqual(2);
+
+ destroyGraphDiv();
+ done();
+ });
+
+ });
+});
diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js
new file mode 100644
index 00000000000..d0aa516eade
--- /dev/null
+++ b/test/jasmine/tests/finance_test.js
@@ -0,0 +1,783 @@
+var Plotly = require('@lib/index');
+var Plots = require('@src/plots/plots');
+var Lib = require('@src/lib');
+
+var d3 = require('d3');
+var createGraphDiv = require('../assets/create_graph_div');
+var destroyGraphDiv = require('../assets/destroy_graph_div');
+
+var mock0 = {
+ open: [33.01, 33.31, 33.50, 32.06, 34.12, 33.05, 33.31, 33.50],
+ high: [34.20, 34.37, 33.62, 34.25, 35.18, 33.25, 35.37, 34.62],
+ low: [31.70, 30.75, 32.87, 31.62, 30.81, 32.75, 32.75, 32.87],
+ close: [34.10, 31.93, 33.37, 33.18, 31.18, 33.10, 32.93, 33.70]
+};
+
+var mock1 = Lib.extendDeep({}, mock0, {
+ x: [
+ '2016-09-01', '2016-09-02', '2016-09-03', '2016-09-04',
+ '2016-09-05', '2016-09-06', '2016-09-07', '2016-09-10'
+ ]
+});
+
+describe('finance charts defaults:', function() {
+ 'use strict';
+
+ function _supply(data, layout) {
+ var gd = {
+ data: data,
+ layout: layout
+ };
+
+ Plots.supplyDefaults(gd);
+
+ return gd;
+ }
+
+ it('should generated the correct number of full traces', function() {
+ var trace0 = Lib.extendDeep({}, mock0, {
+ type: 'ohlc'
+ });
+
+ var trace1 = Lib.extendDeep({}, mock1, {
+ type: 'candlestick'
+ });
+
+ var out = _supply([trace0, trace1]);
+
+ expect(out.data.length).toEqual(2);
+ expect(out._fullData.length).toEqual(4);
+
+ var directions = out._fullData.map(function(fullTrace) {
+ return fullTrace.transforms[0].direction;
+ });
+
+ expect(directions).toEqual(['increasing', 'decreasing', 'increasing', 'decreasing']);
+ });
+
+ it('should not mutate user data', function() {
+ var trace0 = Lib.extendDeep({}, mock0, {
+ type: 'ohlc'
+ });
+
+ var trace1 = Lib.extendDeep({}, mock1, {
+ type: 'candlestick'
+ });
+
+ var out = _supply([trace0, trace1]);
+ expect(out.data[0]).toBe(trace0);
+ expect(out.data[0].transforms).toBeUndefined();
+ expect(out.data[1]).toBe(trace1);
+ expect(out.data[1].transforms).toBeUndefined();
+
+ // ... and in an idempotent way
+
+ var out2 = _supply(out.data);
+ expect(out2.data[0]).toBe(trace0);
+ expect(out2.data[0].transforms).toBeUndefined();
+ expect(out2.data[1]).toBe(trace1);
+ expect(out2.data[1].transforms).toBeUndefined();
+ });
+
+ it('should work with transforms', function() {
+ var trace0 = Lib.extendDeep({}, mock1, {
+ type: 'ohlc',
+ transforms: [{
+ type: 'filter'
+ }]
+ });
+
+ var trace1 = Lib.extendDeep({}, mock0, {
+ type: 'candlestick',
+ transforms: [{
+ type: 'filter'
+ }]
+ });
+
+ var out = _supply([trace0, trace1]);
+
+ expect(out.data.length).toEqual(2);
+ expect(out._fullData.length).toEqual(4);
+
+ var transformTypesIn = out.data.map(function(trace) {
+ return trace.transforms.map(function(opts) {
+ return opts.type;
+ });
+ });
+
+ expect(transformTypesIn).toEqual([ ['filter'], ['filter'] ]);
+
+ var transformTypesOut = out._fullData.map(function(fullTrace) {
+ return fullTrace.transforms.map(function(opts) {
+ return opts.type;
+ });
+ });
+
+ // dummy 'ohlc' and 'candlestick' transforms are pushed at the end
+ // of the 'transforms' array container
+
+ expect(transformTypesOut).toEqual([
+ ['filter', 'ohlc'], ['filter', 'ohlc'],
+ ['filter', 'candlestick'], ['filter', 'candlestick']
+ ]);
+ });
+
+ it('should slice data array according to minimum supplied length', function() {
+
+ function assertDataLength(fullTrace, len) {
+ expect(fullTrace.visible).toBe(true);
+
+ expect(fullTrace.open.length).toEqual(len);
+ expect(fullTrace.high.length).toEqual(len);
+ expect(fullTrace.low.length).toEqual(len);
+ expect(fullTrace.close.length).toEqual(len);
+ }
+
+ var trace0 = Lib.extendDeep({}, mock0, { type: 'ohlc' });
+ trace0.open = [33.01, 33.31, 33.50, 32.06, 34.12];
+
+ var trace1 = Lib.extendDeep({}, mock1, { type: 'candlestick' });
+ trace1.x = ['2016-09-01', '2016-09-02', '2016-09-03', '2016-09-04'];
+
+ var out = _supply([trace0, trace1]);
+
+ assertDataLength(out._fullData[0], 5);
+ assertDataLength(out._fullData[1], 5);
+ assertDataLength(out._fullData[2], 4);
+ assertDataLength(out._fullData[3], 4);
+
+ expect(out._fullData[0]._fullInput.x).toBeUndefined();
+ expect(out._fullData[1]._fullInput.x).toBeUndefined();
+ expect(out._fullData[2]._fullInput.x.length).toEqual(4);
+ expect(out._fullData[3]._fullInput.x.length).toEqual(4);
+ });
+
+ it('should set visible to *false* when minimum supplied length is 0', function() {
+ var trace0 = Lib.extendDeep({}, mock0, { type: 'ohlc' });
+ trace0.close = undefined;
+
+ var trace1 = Lib.extendDeep({}, mock1, { type: 'candlestick' });
+ trace1.high = null;
+
+ var out = _supply([trace0, trace1]);
+
+ expect(out.data.length).toEqual(2);
+ expect(out._fullData.length).toEqual(4);
+
+ var visibilities = out._fullData.map(function(fullTrace) {
+ return fullTrace.visible;
+ });
+
+ expect(visibilities).toEqual([false, false, false, false]);
+ });
+
+ it('direction *showlegend* should be inherited from trace-wide *showlegend*', function() {
+ var trace0 = Lib.extendDeep({}, mock0, {
+ type: 'ohlc',
+ showlegend: false,
+ });
+
+ var trace1 = Lib.extendDeep({}, mock1, {
+ type: 'candlestick',
+ showlegend: false,
+ increasing: { showlegend: true },
+ decreasing: { showlegend: true }
+ });
+
+ var out = _supply([trace0, trace1]);
+
+ var visibilities = out._fullData.map(function(fullTrace) {
+ return fullTrace.showlegend;
+ });
+
+ expect(visibilities).toEqual([false, false, false, false]);
+ });
+
+ it('direction *name* should be inherited from trace-wide *name*', function() {
+ var trace0 = Lib.extendDeep({}, mock0, {
+ type: 'ohlc',
+ name: 'Company A'
+ });
+
+ var trace1 = Lib.extendDeep({}, mock1, {
+ type: 'candlestick',
+ name: 'Company B',
+ increasing: { name: 'B - UP' },
+ decreasing: { name: 'B - DOWN' }
+ });
+
+ var out = _supply([trace0, trace1]);
+
+ var names = out._fullData.map(function(fullTrace) {
+ return fullTrace.name;
+ });
+
+ expect(names).toEqual([
+ 'Company A - increasing',
+ 'Company A - decreasing',
+ 'B - UP',
+ 'B - DOWN'
+ ]);
+ });
+
+ it('trace-wide styling should set default for corresponding per-direction styling', function() {
+ function assertLine(cont, width, dash) {
+ expect(cont.line.width).toEqual(width);
+ if(dash) expect(cont.line.dash).toEqual(dash);
+ }
+
+ var trace0 = Lib.extendDeep({}, mock0, {
+ type: 'ohlc',
+ line: { width: 1, dash: 'dash' },
+ decreasing: { line: { dash: 'dot' } }
+ });
+
+ var trace1 = Lib.extendDeep({}, mock1, {
+ type: 'candlestick',
+ line: { width: 3 },
+ increasing: { line: { width: 0 } }
+ });
+
+ var out = _supply([trace0, trace1]);
+
+
+ var fullData = out._fullData;
+ var fullInput = fullData.map(function(fullTrace) { return fullTrace._fullInput; });
+
+ assertLine(fullInput[0].increasing, 1, 'dash');
+ assertLine(fullInput[0].decreasing, 1, 'dot');
+ assertLine(fullInput[2].increasing, 0);
+ assertLine(fullInput[2].decreasing, 3);
+
+ assertLine(fullData[0], 1, 'dash');
+ assertLine(fullData[1], 1, 'dot');
+ assertLine(fullData[2], 0);
+ assertLine(fullData[3], 3);
+ });
+
+ it('trace-wide *visible* should be passed to generated traces', function() {
+ var trace0 = Lib.extendDeep({}, mock0, {
+ type: 'ohlc',
+ visible: 'legendonly'
+ });
+
+ var trace1 = Lib.extendDeep({}, mock1, {
+ type: 'candlestick',
+ visible: false
+ });
+
+ var out = _supply([trace0, trace1]);
+
+ var visibilities = out._fullData.map(function(fullTrace) {
+ return fullTrace.visible;
+ });
+
+ // only three items here as visible: false traces are not transformed
+
+ expect(visibilities).toEqual(['legendonly', 'legendonly', false]);
+ });
+
+ it('should add a few layout settings by default', function() {
+ var trace0 = Lib.extendDeep({}, mock0, {
+ type: 'ohlc'
+ });
+
+ var layout0 = {};
+
+ var out0 = _supply([trace0], layout0);
+
+ expect(out0.layout.xaxis.rangeslider).toBeDefined();
+ expect(out0._fullLayout.xaxis.rangeslider.visible).toBe(true);
+
+ var trace1 = Lib.extendDeep({}, mock0, {
+ type: 'candlestick'
+ });
+
+ var layout1 = {
+ xaxis: { rangeslider: { visible: false }}
+ };
+
+ var out1 = _supply([trace1], layout1);
+
+ expect(out1.layout.xaxis.rangeslider).toBeDefined();
+ expect(out1._fullLayout.xaxis.rangeslider.visible).toBe(false);
+ });
+});
+
+describe('finance charts calc transforms:', function() {
+ 'use strict';
+
+ function calcDatatoTrace(calcTrace) {
+ return calcTrace[0].trace;
+ }
+
+ function _calc(data, layout) {
+ var gd = {
+ data: data,
+ layout: layout || {}
+ };
+
+ Plots.supplyDefaults(gd);
+ Plots.doCalcdata(gd);
+
+ return gd.calcdata.map(calcDatatoTrace);
+ }
+
+ function ms2DateTime(v) {
+ return typeof v === 'number' ? Lib.ms2DateTime(v) : null;
+ }
+
+ it('should fill when *x* is not present', function() {
+ var trace0 = Lib.extendDeep({}, mock0, {
+ type: 'ohlc',
+ });
+
+ var trace1 = Lib.extendDeep({}, mock0, {
+ type: 'candlestick',
+ });
+
+ var out = _calc([trace0, trace1]);
+
+ expect(out[0].x).toEqual([
+ -0.3, 0, 0, 0, 0, 0.3, null,
+ 2.7, 3, 3, 3, 3, 3.3, null,
+ 4.7, 5, 5, 5, 5, 5.3, null,
+ 6.7, 7, 7, 7, 7, 7.3, null
+ ]);
+ expect(out[1].x).toEqual([
+ 0.7, 1, 1, 1, 1, 1.3, null,
+ 1.7, 2, 2, 2, 2, 2.3, null,
+ 3.7, 4, 4, 4, 4, 4.3, null,
+ 5.7, 6, 6, 6, 6, 6.3, null
+ ]);
+ expect(out[2].x).toEqual([
+ 0, 0, 0, 0, 0, 0,
+ 3, 3, 3, 3, 3, 3,
+ 5, 5, 5, 5, 5, 5,
+ 7, 7, 7, 7, 7, 7
+ ]);
+ expect(out[3].x).toEqual([
+ 1, 1, 1, 1, 1, 1,
+ 2, 2, 2, 2, 2, 2,
+ 4, 4, 4, 4, 4, 4,
+ 6, 6, 6, 6, 6, 6
+ ]);
+ });
+
+ it('should fill *text* for OHLC hover labels', function() {
+ var trace0 = Lib.extendDeep({}, mock0, {
+ type: 'ohlc',
+ text: ['A', 'B', 'C', 'D']
+ });
+
+ var trace1 = Lib.extendDeep({}, mock1, {
+ type: 'ohlc',
+ text: 'IMPORTANT',
+ hoverinfo: 'x+text',
+ xaxis: 'x2'
+ });
+
+ var trace2 = Lib.extendDeep({}, mock1, {
+ type: 'ohlc',
+ hoverinfo: 'y',
+ xaxis: 'x2'
+ });
+
+ var trace3 = Lib.extendDeep({}, mock0, {
+ type: 'ohlc',
+ hoverinfo: 'x',
+ });
+
+ var out = _calc([trace0, trace1, trace2, trace3]);
+
+ expect(out[0].hoverinfo).toEqual('x+text');
+ expect(out[0].text[0])
+ .toEqual('Open: 33.01
High: 34.2
Low: 31.7
Close: 34.1
A');
+ expect(out[0].hoverinfo).toEqual('x+text');
+ expect(out[1].text[0])
+ .toEqual('Open: 33.31
High: 34.37
Low: 30.75
Close: 31.93
B');
+
+ expect(out[2].hoverinfo).toEqual('x+text');
+ expect(out[2].text[0]).toEqual('IMPORTANT');
+
+ expect(out[3].hoverinfo).toEqual('x+text');
+ expect(out[3].text[0]).toEqual('IMPORTANT');
+
+ expect(out[4].hoverinfo).toEqual('text');
+ expect(out[4].text[0])
+ .toEqual('Open: 33.01
High: 34.2
Low: 31.7
Close: 34.1');
+ expect(out[5].hoverinfo).toEqual('text');
+ expect(out[5].text[0])
+ .toEqual('Open: 33.31
High: 34.37
Low: 30.75
Close: 31.93');
+
+ expect(out[6].hoverinfo).toEqual('x');
+ expect(out[6].text[0]).toEqual('');
+ expect(out[7].hoverinfo).toEqual('x');
+ expect(out[7].text[0]).toEqual('');
+ });
+
+ it('should work with *filter* transforms', function() {
+ var trace0 = Lib.extendDeep({}, mock1, {
+ type: 'ohlc',
+ tickwidth: 0.05,
+ transforms: [{
+ type: 'filter',
+ operation: '>',
+ filtersrc: 'open',
+ value: 33
+ }]
+ });
+
+ var trace1 = Lib.extendDeep({}, mock1, {
+ type: 'candlestick',
+ transforms: [{
+ type: 'filter',
+ operation: '{}',
+ filtersrc: 'x',
+ value: ['2016-09-01', '2016-09-10']
+ }]
+ });
+
+ var out = _calc([trace0, trace1]);
+
+ expect(out.length).toEqual(4);
+
+ expect(out[0].x.map(ms2DateTime)).toEqual([
+ '2016-08-31 22:48', '2016-09-01', '2016-09-01', '2016-09-01', '2016-09-01', '2016-09-01 01:12', null,
+ '2016-09-05 22:48', '2016-09-06', '2016-09-06', '2016-09-06', '2016-09-06', '2016-09-06 01:12', null,
+ '2016-09-09 22:48', '2016-09-10', '2016-09-10', '2016-09-10', '2016-09-10', '2016-09-10 01:12', null
+ ]);
+ expect(out[0].y).toEqual([
+ 33.01, 33.01, 34.2, 31.7, 34.1, 34.1, null,
+ 33.05, 33.05, 33.25, 32.75, 33.1, 33.1, null,
+ 33.5, 33.5, 34.62, 32.87, 33.7, 33.7, null
+ ]);
+ expect(out[1].x.map(ms2DateTime)).toEqual([
+ '2016-09-01 22:48', '2016-09-02', '2016-09-02', '2016-09-02', '2016-09-02', '2016-09-02 01:12', null,
+ '2016-09-02 22:48', '2016-09-03', '2016-09-03', '2016-09-03', '2016-09-03', '2016-09-03 01:12', null,
+ '2016-09-04 22:48', '2016-09-05', '2016-09-05', '2016-09-05', '2016-09-05', '2016-09-05 01:12', null,
+ '2016-09-06 22:48', '2016-09-07', '2016-09-07', '2016-09-07', '2016-09-07', '2016-09-07 01:12', null
+ ]);
+ expect(out[1].y).toEqual([
+ 33.31, 33.31, 34.37, 30.75, 31.93, 31.93, null,
+ 33.5, 33.5, 33.62, 32.87, 33.37, 33.37, null,
+ 34.12, 34.12, 35.18, 30.81, 31.18, 31.18, null,
+ 33.31, 33.31, 35.37, 32.75, 32.93, 32.93, null
+ ]);
+
+ expect(out[2].x).toEqual([
+ '2016-09-01', '2016-09-01', '2016-09-01', '2016-09-01', '2016-09-01', '2016-09-01',
+ '2016-09-10', '2016-09-10', '2016-09-10', '2016-09-10', '2016-09-10', '2016-09-10'
+ ]);
+ expect(out[2].y).toEqual([
+ 31.7, 33.01, 34.1, 34.1, 34.1, 34.2,
+ 32.87, 33.5, 33.7, 33.7, 33.7, 34.62
+ ]);
+
+ expect(out[3].x).toEqual([]);
+ expect(out[3].y).toEqual([]);
+ });
+
+ it('should work with *groupby* transforms (ohlc)', function() {
+ var opts = {
+ type: 'groupby',
+ groups: ['b', 'b', 'b', 'a'],
+ };
+
+ var trace0 = Lib.extendDeep({}, mock1, {
+ type: 'ohlc',
+ tickwidth: 0.05,
+ transforms: [opts]
+ });
+
+ var out = _calc([trace0]);
+
+ expect(out[0].name).toEqual('trace 0 - increasing');
+ expect(out[0].x.map(ms2DateTime)).toEqual([
+ '2016-08-31 22:48', '2016-09-01', '2016-09-01', '2016-09-01', '2016-09-01', '2016-09-01 01:12', null,
+ ]);
+ expect(out[0].y).toEqual([
+ 33.01, 33.01, 34.2, 31.7, 34.1, 34.1, null,
+ ]);
+
+ expect(out[1].name).toEqual('trace 0 - decreasing');
+ expect(out[1].x.map(ms2DateTime)).toEqual([
+ '2016-09-01 22:48', '2016-09-02', '2016-09-02', '2016-09-02', '2016-09-02', '2016-09-02 01:12', null,
+ '2016-09-02 22:48', '2016-09-03', '2016-09-03', '2016-09-03', '2016-09-03', '2016-09-03 01:12', null
+ ]);
+ expect(out[1].y).toEqual([
+ 33.31, 33.31, 34.37, 30.75, 31.93, 31.93, null,
+ 33.5, 33.5, 33.62, 32.87, 33.37, 33.37, null
+ ]);
+
+ expect(out[2].name).toEqual('trace 0 - increasing');
+ expect(out[2].x.map(ms2DateTime)).toEqual([
+ '2016-09-03 23:59:59.999', '2016-09-04', '2016-09-04', '2016-09-04', '2016-09-04', '2016-09-04', null
+ ]);
+ expect(out[2].y).toEqual([
+ 32.06, 32.06, 34.25, 31.62, 33.18, 33.18, null
+ ]);
+
+ expect(out[3].name).toEqual('trace 0 - decreasing');
+ expect(out[3].x.map(ms2DateTime)).toEqual([]);
+ expect(out[3].y).toEqual([]);
+ });
+
+ it('should work with *groupby* transforms (candlestick)', function() {
+ var opts = {
+ type: 'groupby',
+ groups: ['a', 'b', 'b', 'a'],
+ };
+
+ var trace0 = Lib.extendDeep({}, mock1, {
+ type: 'candlestick',
+ transforms: [opts]
+ });
+
+ var out = _calc([trace0]);
+
+ expect(out[0].name).toEqual('trace 0 - increasing');
+ expect(out[0].x).toEqual([
+ '2016-09-01', '2016-09-01', '2016-09-01', '2016-09-01', '2016-09-01', '2016-09-01',
+ '2016-09-04', '2016-09-04', '2016-09-04', '2016-09-04', '2016-09-04', '2016-09-04'
+ ]);
+ expect(out[0].y).toEqual([
+ 31.7, 33.01, 34.1, 34.1, 34.1, 34.2,
+ 31.62, 32.06, 33.18, 33.18, 33.18, 34.25
+ ]);
+
+ expect(out[1].name).toEqual('trace 0 - decreasing');
+ expect(out[1].x).toEqual([]);
+ expect(out[1].y).toEqual([]);
+
+ expect(out[2].name).toEqual('trace 0 - increasing');
+ expect(out[2].x).toEqual([]);
+ expect(out[2].y).toEqual([]);
+
+ expect(out[3].name).toEqual('trace 0 - decreasing');
+ expect(out[3].x).toEqual([
+ '2016-09-02', '2016-09-02', '2016-09-02', '2016-09-02', '2016-09-02', '2016-09-02',
+ '2016-09-03', '2016-09-03', '2016-09-03', '2016-09-03', '2016-09-03', '2016-09-03'
+ ]);
+ expect(out[3].y).toEqual([
+ 30.75, 33.31, 31.93, 31.93, 31.93, 34.37,
+ 32.87, 33.5, 33.37, 33.37, 33.37, 33.62
+ ]);
+ });
+});
+
+describe('finance charts updates:', function() {
+ 'use strict';
+
+ var gd;
+
+ beforeEach(function() {
+ gd = createGraphDiv();
+ });
+
+ afterEach(function() {
+ Plotly.purge(gd);
+ destroyGraphDiv();
+ });
+
+ function countScatterTraces() {
+ return d3.select('g.cartesianlayer').selectAll('g.trace.scatter').size();
+ }
+
+ function countBoxTraces() {
+ return d3.select('g.cartesianlayer').selectAll('g.trace.boxes').size();
+ }
+
+ function countRangeSliders() {
+ return d3.select('g.rangeslider-rangeplot').size();
+ }
+
+ it('Plotly.restyle should work', function(done) {
+ var trace0 = Lib.extendDeep({}, mock0, { type: 'ohlc' });
+
+ var path0;
+
+ Plotly.plot(gd, [trace0]).then(function() {
+ expect(gd.calcdata[0][0].x).toEqual(-0.3);
+ expect(gd.calcdata[0][0].y).toEqual(33.01);
+
+ return Plotly.restyle(gd, 'tickwidth', 0.5);
+ })
+ .then(function() {
+ expect(gd.calcdata[0][0].x).toEqual(-0.5);
+
+ return Plotly.restyle(gd, 'open', [[0, 30.75, 32.87, 31.62, 30.81, 32.75, 32.75, 32.87]]);
+ })
+ .then(function() {
+ expect(gd.calcdata[0][0].y).toEqual(0);
+
+ return Plotly.restyle(gd, {
+ type: 'candlestick',
+ open: [[33.01, 33.31, 33.50, 32.06, 34.12, 33.05, 33.31, 33.50]]
+ });
+ })
+ .then(function() {
+ path0 = d3.select('path.box').attr('d');
+
+ return Plotly.restyle(gd, 'whiskerwidth', 0.2);
+ })
+ .then(function() {
+ expect(d3.select('path.box').attr('d')).not.toEqual(path0);
+
+ done();
+ });
+
+ });
+
+ it('should be able to toggle visibility', function(done) {
+ var data = [
+ Lib.extendDeep({}, mock0, { type: 'ohlc' }),
+ Lib.extendDeep({}, mock0, { type: 'candlestick' }),
+ ];
+
+ Plotly.plot(gd, data).then(function() {
+ expect(countScatterTraces()).toEqual(2);
+ expect(countBoxTraces()).toEqual(2);
+
+ return Plotly.restyle(gd, 'visible', false);
+ })
+ .then(function() {
+ expect(countScatterTraces()).toEqual(0);
+ expect(countBoxTraces()).toEqual(0);
+
+ return Plotly.restyle(gd, 'visible', 'legendonly', [1]);
+ })
+ .then(function() {
+ expect(countScatterTraces()).toEqual(0);
+ expect(countBoxTraces()).toEqual(0);
+
+ return Plotly.restyle(gd, 'visible', true, [1]);
+ })
+ .then(function() {
+ expect(countScatterTraces()).toEqual(0);
+ expect(countBoxTraces()).toEqual(2);
+
+ return Plotly.restyle(gd, 'visible', true, [0]);
+ })
+ .then(function() {
+ expect(countScatterTraces()).toEqual(2);
+ expect(countBoxTraces()).toEqual(2);
+
+ return Plotly.restyle(gd, 'visible', 'legendonly', [0]);
+ })
+ .then(function() {
+ expect(countScatterTraces()).toEqual(0);
+ expect(countBoxTraces()).toEqual(2);
+
+ return Plotly.restyle(gd, 'visible', true);
+ })
+ .then(function() {
+ expect(countScatterTraces()).toEqual(2);
+ expect(countBoxTraces()).toEqual(2);
+
+ done();
+ });
+ });
+
+ it('Plotly.relayout should work', function(done) {
+ var trace0 = Lib.extendDeep({}, mock0, { type: 'ohlc' });
+
+ Plotly.plot(gd, [trace0]).then(function() {
+ expect(countRangeSliders()).toEqual(1);
+
+ return Plotly.relayout(gd, 'xaxis.rangeslider.visible', false);
+ })
+ .then(function() {
+ expect(countRangeSliders()).toEqual(0);
+
+ done();
+ });
+
+ });
+
+ it('Plotly.extendTraces should work', function(done) {
+ var data = [
+ Lib.extendDeep({}, mock0, { type: 'ohlc' }),
+ Lib.extendDeep({}, mock0, { type: 'candlestick' }),
+ ];
+
+ // ohlc have 7 calc pts per 'x' coords
+
+ Plotly.plot(gd, data).then(function() {
+ expect(gd.calcdata[0].length).toEqual(28);
+ expect(gd.calcdata[1].length).toEqual(28);
+ expect(gd.calcdata[2].length).toEqual(4);
+ expect(gd.calcdata[3].length).toEqual(4);
+
+ return Plotly.extendTraces(gd, {
+ open: [[ 34, 35 ]],
+ high: [[ 40, 41 ]],
+ low: [[ 32, 33 ]],
+ close: [[ 38, 39 ]]
+ }, [1]);
+ })
+ .then(function() {
+ expect(gd.calcdata[0].length).toEqual(28);
+ expect(gd.calcdata[1].length).toEqual(28);
+ expect(gd.calcdata[2].length).toEqual(6);
+ expect(gd.calcdata[3].length).toEqual(4);
+
+ return Plotly.extendTraces(gd, {
+ open: [[ 34, 35 ]],
+ high: [[ 40, 41 ]],
+ low: [[ 32, 33 ]],
+ close: [[ 38, 39 ]]
+ }, [0]);
+ })
+ .then(function() {
+ expect(gd.calcdata[0].length).toEqual(42);
+ expect(gd.calcdata[1].length).toEqual(28);
+ expect(gd.calcdata[2].length).toEqual(6);
+ expect(gd.calcdata[3].length).toEqual(4);
+
+ done();
+ });
+ });
+
+ it('Plotly.deleteTraces / addTraces should work', function(done) {
+ var data = [
+ Lib.extendDeep({}, mock0, { type: 'ohlc' }),
+ Lib.extendDeep({}, mock0, { type: 'candlestick' }),
+ ];
+
+ Plotly.plot(gd, data).then(function() {
+ expect(countScatterTraces()).toEqual(2);
+ expect(countBoxTraces()).toEqual(2);
+
+ return Plotly.deleteTraces(gd, [1]);
+ })
+ .then(function() {
+ expect(countScatterTraces()).toEqual(2);
+ expect(countBoxTraces()).toEqual(0);
+
+ return Plotly.deleteTraces(gd, [0]);
+ })
+ .then(function() {
+ expect(countScatterTraces()).toEqual(0);
+ expect(countBoxTraces()).toEqual(0);
+
+ var trace = Lib.extendDeep({}, mock0, { type: 'candlestick' });
+
+ return Plotly.addTraces(gd, [trace]);
+ })
+ .then(function() {
+ expect(countScatterTraces()).toEqual(0);
+ expect(countBoxTraces()).toEqual(2);
+
+ var trace = Lib.extendDeep({}, mock0, { type: 'ohlc' });
+
+ return Plotly.addTraces(gd, [trace]);
+ })
+ .then(function() {
+ expect(countScatterTraces()).toEqual(2);
+ expect(countBoxTraces()).toEqual(2);
+
+ done();
+ });
+ });
+});
diff --git a/test/jasmine/tests/lib_test.js b/test/jasmine/tests/lib_test.js
index 68dbf8e544b..2ed3fccf402 100644
--- a/test/jasmine/tests/lib_test.js
+++ b/test/jasmine/tests/lib_test.js
@@ -1367,6 +1367,37 @@ describe('Test lib.js:', function() {
});
});
+ describe('filterUnique', function() {
+
+ it('should return array containing unique values', function() {
+ expect(
+ Lib.filterUnique(['a', 'a', 'b', 'b'])
+ )
+ .toEqual(['a', 'b']);
+
+ expect(
+ Lib.filterUnique(['1', ['1'], 1])
+ )
+ .toEqual(['1']);
+
+ expect(
+ Lib.filterUnique([1, '1', [1]])
+ )
+ .toEqual([1]);
+
+ expect(
+ Lib.filterUnique([ { a: 1 }, { b: 2 }])
+ )
+ .toEqual([{ a: 1 }]);
+
+ expect(
+ Lib.filterUnique([null, undefined, null, null, undefined])
+ )
+ .toEqual([null, undefined]);
+ });
+
+ });
+
describe('numSeparate', function() {
it('should work on numbers and strings', function() {
diff --git a/test/jasmine/tests/transform_multi_test.js b/test/jasmine/tests/transform_multi_test.js
index 70ac4f4ed7b..43499b73079 100644
--- a/test/jasmine/tests/transform_multi_test.js
+++ b/test/jasmine/tests/transform_multi_test.js
@@ -157,11 +157,12 @@ describe('user-defined transforms:', function() {
transforms: [transformIn]
}];
- var layout = {};
+ var layout = {},
+ fullLayout = {};
function assertSupplyDefaultsArgs(_transformIn, traceOut, _layout) {
expect(_transformIn).toBe(transformIn);
- expect(_layout).toBe(layout);
+ expect(_layout).toBe(fullLayout);
return transformOut;
}
@@ -171,6 +172,7 @@ describe('user-defined transforms:', function() {
expect(opts.transform).toBe(transformOut);
expect(opts.fullTrace._input).toBe(dataIn[0]);
expect(opts.layout).toBe(layout);
+ expect(opts.fullLayout).toBe(fullLayout);
return dataOut;
}
@@ -184,7 +186,7 @@ describe('user-defined transforms:', function() {
};
Plotly.register(fakeTransformModule);
- Plots.supplyDataDefaults(dataIn, [], layout);
+ Plots.supplyDataDefaults(dataIn, [], layout, fullLayout);
delete Plots.transformsRegistry.fake;
});