From e915a297626f094d9a1297cf962a9d1039847a56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 6 Oct 2016 23:16:31 -0400 Subject: [PATCH 01/32] relax requirement for re-calc - recalc when gd.calcdata doesn't match with fullData (not gd.data) --- src/plot_api/plot_api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 9b1976b4f7f..ef0831cbfb3 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 From cf5da9bf06d0a232134069abc26695af2a14533c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 6 Oct 2016 23:17:16 -0400 Subject: [PATCH 02/32] plots: pass user layout to transforms - so that transform writing can set layout option during transform step --- src/plots/plots.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index 56aa8a9b696..5a6889407ba 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); @@ -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 @@ -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 }); } From 7864fa8c816ba40024b4d8e9bf2971ef4ec0f10b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 9 Sep 2016 17:43:56 -0400 Subject: [PATCH 03/32] first pass ohlc trace type --- lib/ohlc.js | 9 ++ src/traces/ohlc/attributes.js | 105 ++++++++++++++++++++++ src/traces/ohlc/defaults.js | 50 +++++++++++ src/traces/ohlc/helpers.js | 26 ++++++ src/traces/ohlc/index.js | 30 +++++++ src/traces/ohlc/ohlc_defaults.js | 34 +++++++ src/traces/ohlc/transform.js | 149 +++++++++++++++++++++++++++++++ 7 files changed, 403 insertions(+) create mode 100644 lib/ohlc.js create mode 100644 src/traces/ohlc/attributes.js create mode 100644 src/traces/ohlc/defaults.js create mode 100644 src/traces/ohlc/helpers.js create mode 100644 src/traces/ohlc/index.js create mode 100644 src/traces/ohlc/ohlc_defaults.js create mode 100644 src/traces/ohlc/transform.js diff --git a/lib/ohlc.js b/lib/ohlc.js new file mode 100644 index 00000000000..d8d96843590 --- /dev/null +++ b/lib/ohlc.js @@ -0,0 +1,9 @@ +/** +* 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. +*/ + +module.exports = require('../src/traces/ohlc'); diff --git a/src/traces/ohlc/attributes.js b/src/traces/ohlc/attributes.js new file mode 100644 index 00000000000..a6c870bbe83 --- /dev/null +++ b/src/traces/ohlc/attributes.js @@ -0,0 +1,105 @@ +/** +* 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 lineAttrs = scatterAttrs.line; + +var directionAttrs = { + visible: { + valType: 'enumerated', + values: [true, false, 'legendonly'], + role: 'info', + dflt: true, + description: [ + + ].join(' ') + }, + + color: Lib.extendFlat({}, lineAttrs.color), + width: Lib.extendFlat({}, lineAttrs.width), + dash: Lib.extendFlat({}, lineAttrs.dash), + + tickwidth: { + valType: 'number', + min: 0, + max: 1, + dflt: 0.1, + role: 'style', + description: [ + 'Sets the width of the open/close tick marks', + 'relative to the *t* minimal interval.' + ].join(' ') + } +}; + +module.exports = { + + // or should this be 'x' + // + // + // should we add the option for ohlc along y-axis ?? + t: { + valType: 'data_array', + description: [ + 'Sets the time coordinate.', + '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.' + }, + + // TODO find better colors + increasing: Lib.extendDeep({}, directionAttrs, { + color: { dflt: 'green' } + }), + + decreasing: Lib.extendDeep({}, directionAttrs, { + color: { dflt: 'red' } + }), + + 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 the', + 'this trace\'s sample points.' + ].join(' ') + } +}; diff --git a/src/traces/ohlc/defaults.js b/src/traces/ohlc/defaults.js new file mode 100644 index 00000000000..8a666fb83c9 --- /dev/null +++ b/src/traces/ohlc/defaults.js @@ -0,0 +1,50 @@ +/** +* 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 attributes = require('./attributes'); + +module.exports = function supplyDefaults(traceIn, traceOut) { + + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + // TODO .. HAVE TO TEST FILTER with 't', 'high', 'open', 'low' ... + + var transformOpts = { type: 'ohlc' }; + if(Array.isArray(traceOut.transforms)) traceOut.transforms.push(transformOpts); + else traceOut.transforms = [transformOpts]; + + var len = handleOHLC(traceIn, traceOut, coerce); + + if(len === 0) { + traceOut.visible = false; + return; + } + + coerce('text'); + + handleDirection(traceOut, coerce, 'increasing'); + handleDirection(traceOut, coerce, 'decreasing'); +}; + +function handleDirection(traceOut, coerce, direction) { + var dirVisible = coerce(direction + '.visible', traceOut.visible); + + if(dirVisible) { + coerce(direction + '.color'); + coerce(direction + '.width'); + coerce(direction + '.dash'); + coerce(direction + '.tickwidth'); + } +} diff --git a/src/traces/ohlc/helpers.js b/src/traces/ohlc/helpers.js new file mode 100644 index 00000000000..01953e00cda --- /dev/null +++ b/src/traces/ohlc/helpers.js @@ -0,0 +1,26 @@ +/** +* 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'; + + +exports.getFilterFn = function getFilterFn(direction) { + switch(direction) { + case 'increasing': + return function(o, c) { return o <= c; }; + + case 'decreasing': + return function(o, c) { return o > c; }; + } +}; + +exports.addRangeSlider = function addRangeSlider(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..89b42b9a47d --- /dev/null +++ b/src/traces/ohlc/index.js @@ -0,0 +1,30 @@ +/** +* 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: [ + // ... + ].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..2e44a05fe9d --- /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 t = coerce('t'), + open = coerce('open'), + high = coerce('high'), + low = coerce('low'), + close = coerce('close'); + + len = Math.min(open.length, high.length, low.length, close.length); + + if(t) { + len = Math.min(len, t.length); + t.slice(0, len); + } + + open = open.slice(0, len); + high = high.slice(0, len); + low = low.slice(0, len); + 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..a94f1cea93f --- /dev/null +++ b/src/traces/ohlc/transform.js @@ -0,0 +1,149 @@ +/** +* 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 axisIds = require('../../plots/cartesian/axis_ids'); + +exports.moduleType = 'transform'; + +exports.name = 'ohlc'; + +exports.attributes = {}; + +exports.supplyDefaults = null; + +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') + ); + } + + // add a few layout features + var layout = state.layout; + if(!layout.hovermode) layout.hovermode = 'closest'; + helpers.addRangeSlider(layout); + + return dataOut; +}; + +function makeTrace(traceIn, state, direction) { + var directionOpts = traceIn[direction]; + + // We need to track which direction ('increasing' or 'decreasing') + // the generated correspond to for the calcTransform step. + // + // To make sure that direction reaches the calcTransform, + // store it in the transform opts object. + var _transforms = Lib.extendFlat([], traceIn.transforms); + _transforms[state.transformIndex] = { + type: 'ohlc', + direction: direction + }; + + return { + type: 'scatter', + mode: 'lines', + connectgaps: false, + + // TODO could do better + name: direction, + + // to make autotype catch date axes soon!! + x: traceIn.t || [0], + + // concat low and high to get correct autorange + y: [].concat(traceIn.low).concat(traceIn.high), + + visible: directionOpts.visible, + + line: { + color: directionOpts.color, + width: directionOpts.width, + dash: directionOpts.dash + }, + + text: traceIn.text, + hoverinfo: traceIn.hoverinfo, + + opacity: traceIn.opacity, + showlegend: traceIn.showlegend, + + transforms: _transforms + }; +} + +exports.calcTransform = function calcTransform(gd, trace, opts) { + var fullInput = trace._fullInput, + direction = opts.direction; + + var filterFn = helpers.getFilterFn(direction), + ax = axisIds.getFromTrace(gd, trace, 'x'), + tickWidth = convertTickWidth(fullInput.t, ax, fullInput[direction].tickwidth); + + // TODO tickwidth does not restyle + + var open = fullInput.open, + high = fullInput.high, + low = fullInput.low, + close = fullInput.close; + + // sliced accordingly in supply-defaults + var len = open.length; + + // clear generated trace x / y + trace.x = []; + trace.y = []; + + var appendX = fullInput.t ? + function(i) { + var t = ax.d2c(fullInput.t[i]); + trace.x.push(t - tickWidth, t, t, t, t, t + tickWidth, null); + } : + function(i) { + trace.x.push(i - tickWidth, i, i, i, i, i + tickWidth, null); + }; + + var appendY = function(o, h, l, c) { + trace.y.push(o, o, h, l, c, c, 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]); + } + } +}; + +function convertTickWidth(t, ax, tickWidth) { + if(!t) return tickWidth; + + var tInData = t.map(ax.d2c), + minDTick = Infinity; + + for(var i = 0; i < t.length - 1; i++) { + minDTick = Math.min(tInData[i + 1] - tInData[i], minDTick); + } + + return minDTick * tickWidth; +} From 44f2becabafd5e42175d9666f8c4e4fc86fd7375 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 9 Sep 2016 17:43:43 -0400 Subject: [PATCH 04/32] first pass candlestick trace type --- lib/candlestick.js | 9 ++ src/traces/candlestick/attributes.js | 49 ++++++++++ src/traces/candlestick/defaults.js | 49 ++++++++++ src/traces/candlestick/index.js | 30 ++++++ src/traces/candlestick/transform.js | 132 +++++++++++++++++++++++++++ 5 files changed, 269 insertions(+) create mode 100644 lib/candlestick.js create mode 100644 src/traces/candlestick/attributes.js create mode 100644 src/traces/candlestick/defaults.js create mode 100644 src/traces/candlestick/index.js create mode 100644 src/traces/candlestick/transform.js diff --git a/lib/candlestick.js b/lib/candlestick.js new file mode 100644 index 00000000000..9513d367823 --- /dev/null +++ b/lib/candlestick.js @@ -0,0 +1,9 @@ +/** +* 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. +*/ + +module.exports = require('../src/traces/candlestick'); diff --git a/src/traces/candlestick/attributes.js b/src/traces/candlestick/attributes.js new file mode 100644 index 00000000000..653e460e27e --- /dev/null +++ b/src/traces/candlestick/attributes.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'; + +var Lib = require('../../lib'); +var OHLCattrs = require('../ohlc/attributes'); +var boxAttrs = require('../box/attributes'); + +var directionAttrs = { + visible: { + valType: 'enumerated', + values: [true, false, 'legendonly'], + role: 'info', + dflt: true, + description: [ + + ].join(' ') + }, + + color: Lib.extendFlat({}, boxAttrs.line.color), + width: Lib.extendFlat({}, boxAttrs.line.width), + fillcolor: Lib.extendFlat({}, boxAttrs.fillcolor), + tickwidth: Lib.extendFlat({}, boxAttrs.whiskerwidth, { dflt: 0 }), +}; + +module.exports = { + t: OHLCattrs.t, + open: OHLCattrs.open, + high: OHLCattrs.high, + low: OHLCattrs.low, + close: OHLCattrs.close, + + increasing: Lib.extendDeep({}, directionAttrs, { + color: { dflt: 'green' } + }), + + decreasing: Lib.extendDeep({}, directionAttrs, { + color: { dflt: 'red' } + }), + + text: OHLCattrs.text +}; diff --git a/src/traces/candlestick/defaults.js b/src/traces/candlestick/defaults.js new file mode 100644 index 00000000000..a7b2e74c2ba --- /dev/null +++ b/src/traces/candlestick/defaults.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'; + +var Lib = require('../../lib'); +var handleOHLC = require('../ohlc/ohlc_defaults'); +var attributes = require('./attributes'); + +module.exports = function supplyDefaults(traceIn, traceOut) { + + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + // add comment about this hack block + var transformOpts = { type: 'candlestick' }; + if(Array.isArray(traceOut.transforms)) traceOut.transforms.push(transformOpts); + else traceOut.transforms = [transformOpts]; + + var len = handleOHLC(traceIn, traceOut, coerce); + + if(len === 0) { + traceOut.visible = false; + return; + } + + coerce('text'); + + handleDirection(traceOut, coerce, 'increasing'); + handleDirection(traceOut, coerce, 'decreasing'); +}; + +function handleDirection(traceOut, coerce, direction) { + var dirVisible = coerce(direction + '.visible', traceOut.visible); + + if(dirVisible) { + coerce(direction + '.color'); + coerce(direction + '.width'); + coerce(direction + '.fillcolor'); + coerce(direction + '.tickwidth'); + } +} diff --git a/src/traces/candlestick/index.js b/src/traces/candlestick/index.js new file mode 100644 index 00000000000..d3594bda298 --- /dev/null +++ b/src/traces/candlestick/index.js @@ -0,0 +1,30 @@ +/** +* 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: [ + // ... + ].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..0531ac12da7 --- /dev/null +++ b/src/traces/candlestick/transform.js @@ -0,0 +1,132 @@ +/** +* 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 = null; + +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') + ); + } + + // add a few layout features + var layout = state.layout; + helpers.addRangeSlider(layout); + + return dataOut; +}; + +function makeTrace(traceIn, state, direction) { + var directionOpts = traceIn[direction]; + + // We need to track which direction ('increasing' or 'decreasing') + // the generated correspond to for the calcTransform step. + // + // To make sure that direction reaches the calcTransform, + // store it in the transform opts object. + var _transforms = Lib.extendFlat([], traceIn.transforms); + _transforms[state.transformIndex] = { + type: 'candlestick', + direction: direction + }; + + return { + type: 'box', + boxpoints: false, + + // TODO could do better + name: direction, + + // to make autotype catch date axes soon!! + x: traceIn.t || [0], + + // concat low and high to get correct autorange + y: [].concat(traceIn.low).concat(traceIn.high), + + visible: directionOpts.visible, + + line: { + color: directionOpts.color, + width: directionOpts.width + }, + fillcolor: directionOpts.fillcolor, + + // TODO this doesn't restyle currently + whiskerwidth: directionOpts.tickwidth, + + text: traceIn.text, + hoverinfo: traceIn.hoverinfo, + + opacity: traceIn.opacity, + showlegend: traceIn.showlegend, + + transforms: _transforms + }; +} + +exports.calcTransform = function calcTransform(gd, trace, opts) { + var fullInput = trace._fullInput, + direction = opts.direction; + + var filterFn = helpers.getFilterFn(direction); + + var open = fullInput.open, + high = fullInput.high, + low = fullInput.low, + close = fullInput.close; + + // sliced accordingly in supply-defaults + var len = open.length; + + // clear generated trace x / y + trace.x = []; + trace.y = []; + + var appendX = fullInput.t ? + function(i) { + var t = fullInput.t[i]; + trace.x.push(t, t, t, t, t, t); + } : + function(i) { + trace.x.push(i, i, i, i, i, i); + }; + + var appendY = function(o, h, l, c) { + trace.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]); + } + } +}; From df24552b7fb58c407945a699d8a3dc6f669f1f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 9 Sep 2016 17:44:24 -0400 Subject: [PATCH 05/32] add ohlc and candlestick to main plotly.js lib index --- lib/candlestick.js | 2 ++ lib/index.js | 11 +++++++++-- lib/ohlc.js | 2 ++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/candlestick.js b/lib/candlestick.js index 9513d367823..b4b0f06e6de 100644 --- a/lib/candlestick.js +++ b/lib/candlestick.js @@ -6,4 +6,6 @@ * LICENSE file in the root directory of this source tree. */ +'use strict'; + module.exports = require('../src/traces/candlestick'); 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 index d8d96843590..989e77e521c 100644 --- a/lib/ohlc.js +++ b/lib/ohlc.js @@ -6,4 +6,6 @@ * LICENSE file in the root directory of this source tree. */ +'use strict'; + module.exports = require('../src/traces/ohlc'); From 6cac3fe4e2f0cafb421289806361b0c6639af2e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 9 Sep 2016 17:44:41 -0400 Subject: [PATCH 06/32] [maybe?] add 'finance' partial bundle --- lib/index-finance.js | 21 +++++++++++++++++++++ tasks/util/constants.js | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 lib/index-finance.js 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/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) { From 0f17423e08e65e68c5b94f107a9d8ba33905191e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 7 Oct 2016 09:00:04 -0400 Subject: [PATCH 07/32] fixup user-defined transform test - after 5b5a5a067b47623b977faf9a8590c98166795fb5 --- test/jasmine/tests/transform_multi_test.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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; }); From 61c72cc442dffd718ce56524a52d38fe7fd4e093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 7 Oct 2016 18:25:55 -0400 Subject: [PATCH 08/32] finance: replace attribute 't' -> 'x' --- src/traces/candlestick/attributes.js | 2 +- src/traces/ohlc/attributes.js | 8 ++------ src/traces/ohlc/ohlc_defaults.js | 16 ++++++++-------- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/traces/candlestick/attributes.js b/src/traces/candlestick/attributes.js index 653e460e27e..2044a80fd56 100644 --- a/src/traces/candlestick/attributes.js +++ b/src/traces/candlestick/attributes.js @@ -31,7 +31,7 @@ var directionAttrs = { }; module.exports = { - t: OHLCattrs.t, + x: OHLCattrs.x, open: OHLCattrs.open, high: OHLCattrs.high, low: OHLCattrs.low, diff --git a/src/traces/ohlc/attributes.js b/src/traces/ohlc/attributes.js index a6c870bbe83..eb49e38d888 100644 --- a/src/traces/ohlc/attributes.js +++ b/src/traces/ohlc/attributes.js @@ -44,14 +44,10 @@ var directionAttrs = { module.exports = { - // or should this be 'x' - // - // - // should we add the option for ohlc along y-axis ?? - t: { + x: { valType: 'data_array', description: [ - 'Sets the time coordinate.', + 'Sets the x coordinates.', 'If absent, linear coordinate will be generated.' ].join(' ') }, diff --git a/src/traces/ohlc/ohlc_defaults.js b/src/traces/ohlc/ohlc_defaults.js index 2e44a05fe9d..33e335f4188 100644 --- a/src/traces/ohlc/ohlc_defaults.js +++ b/src/traces/ohlc/ohlc_defaults.js @@ -12,7 +12,7 @@ module.exports = function handleOHLC(traceIn, traceOut, coerce) { var len; - var t = coerce('t'), + var x = coerce('x'), open = coerce('open'), high = coerce('high'), low = coerce('low'), @@ -20,15 +20,15 @@ module.exports = function handleOHLC(traceIn, traceOut, coerce) { len = Math.min(open.length, high.length, low.length, close.length); - if(t) { - len = Math.min(len, t.length); - t.slice(0, len); + if(x) { + len = Math.min(len, x.length); + if(len < x.length) traceOut.x = x.slice(0, len); } - open = open.slice(0, len); - high = high.slice(0, len); - low = low.slice(0, len); - close = close.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; }; From a9eb7c596f2c5233f8a2f02a002b90c74a3e70a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 7 Oct 2016 18:27:11 -0400 Subject: [PATCH 09/32] candlestick: rename 'tickwidth' -> 'whiskerwidth' + make it 1 per trace --- src/traces/candlestick/attributes.js | 4 ++-- src/traces/candlestick/defaults.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/traces/candlestick/attributes.js b/src/traces/candlestick/attributes.js index 2044a80fd56..17f7fabc3e2 100644 --- a/src/traces/candlestick/attributes.js +++ b/src/traces/candlestick/attributes.js @@ -27,7 +27,6 @@ var directionAttrs = { color: Lib.extendFlat({}, boxAttrs.line.color), width: Lib.extendFlat({}, boxAttrs.line.width), fillcolor: Lib.extendFlat({}, boxAttrs.fillcolor), - tickwidth: Lib.extendFlat({}, boxAttrs.whiskerwidth, { dflt: 0 }), }; module.exports = { @@ -45,5 +44,6 @@ module.exports = { color: { dflt: 'red' } }), - text: OHLCattrs.text + text: OHLCattrs.text, + whiskerwidth: Lib.extendFlat({}, boxAttrs.whiskerwidth, { dflt: 0 }), }; diff --git a/src/traces/candlestick/defaults.js b/src/traces/candlestick/defaults.js index a7b2e74c2ba..ae292113cb4 100644 --- a/src/traces/candlestick/defaults.js +++ b/src/traces/candlestick/defaults.js @@ -32,6 +32,7 @@ module.exports = function supplyDefaults(traceIn, traceOut) { } coerce('text'); + coerce('whiskerwidth'); handleDirection(traceOut, coerce, 'increasing'); handleDirection(traceOut, coerce, 'decreasing'); @@ -44,6 +45,5 @@ function handleDirection(traceOut, coerce, direction) { coerce(direction + '.color'); coerce(direction + '.width'); coerce(direction + '.fillcolor'); - coerce(direction + '.tickwidth'); } } From ede8ee2655c4467234c27fdefae4f0da2d8f8fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 7 Oct 2016 18:27:44 -0400 Subject: [PATCH 10/32] ohlc: make 'tickwidth' 1 per trace --- src/traces/ohlc/attributes.js | 24 ++++++++++++------------ src/traces/ohlc/defaults.js | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/traces/ohlc/attributes.js b/src/traces/ohlc/attributes.js index eb49e38d888..c910f42e863 100644 --- a/src/traces/ohlc/attributes.js +++ b/src/traces/ohlc/attributes.js @@ -28,18 +28,6 @@ var directionAttrs = { color: Lib.extendFlat({}, lineAttrs.color), width: Lib.extendFlat({}, lineAttrs.width), dash: Lib.extendFlat({}, lineAttrs.dash), - - tickwidth: { - valType: 'number', - min: 0, - max: 1, - dflt: 0.1, - role: 'style', - description: [ - 'Sets the width of the open/close tick marks', - 'relative to the *t* minimal interval.' - ].join(' ') - } }; module.exports = { @@ -97,5 +85,17 @@ module.exports = { 'If an array of string, the items are mapped in order to the', 'this trace\'s sample points.' ].join(' ') + }, + + tickwidth: { + valType: 'number', + min: 0, + max: 0.5, + dflt: 0.05, + 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 index 8a666fb83c9..54db210f3b9 100644 --- a/src/traces/ohlc/defaults.js +++ b/src/traces/ohlc/defaults.js @@ -33,6 +33,7 @@ module.exports = function supplyDefaults(traceIn, traceOut) { } coerce('text'); + coerce('tickwidth'); handleDirection(traceOut, coerce, 'increasing'); handleDirection(traceOut, coerce, 'decreasing'); @@ -45,6 +46,5 @@ function handleDirection(traceOut, coerce, direction) { coerce(direction + '.color'); coerce(direction + '.width'); coerce(direction + '.dash'); - coerce(direction + '.tickwidth'); } } From f6c33f2b49be1bd65ab681754519516d3817a2f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 7 Oct 2016 18:31:07 -0400 Subject: [PATCH 11/32] finance: 2nd iteration - factor out hacky moves in ohlc/helpers.js - pass open, high, low, close array in transform opts, then pass then to traceOut in transform supplyDefaults - use trace (not trace._fullInput) to filter -> this makes ohlc and candlestick compatible with filters & groupby --- src/traces/candlestick/defaults.js | 6 +- src/traces/candlestick/transform.js | 100 +++++++++++++------------ src/traces/ohlc/defaults.js | 7 +- src/traces/ohlc/helpers.js | 45 +++++++++++- src/traces/ohlc/transform.js | 109 +++++++++++++--------------- 5 files changed, 148 insertions(+), 119 deletions(-) diff --git a/src/traces/candlestick/defaults.js b/src/traces/candlestick/defaults.js index ae292113cb4..f085e2565e1 100644 --- a/src/traces/candlestick/defaults.js +++ b/src/traces/candlestick/defaults.js @@ -11,6 +11,7 @@ var Lib = require('../../lib'); var handleOHLC = require('../ohlc/ohlc_defaults'); +var helpers = require('../ohlc/helpers'); var attributes = require('./attributes'); module.exports = function supplyDefaults(traceIn, traceOut) { @@ -19,13 +20,10 @@ module.exports = function supplyDefaults(traceIn, traceOut) { return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); } - // add comment about this hack block var transformOpts = { type: 'candlestick' }; - if(Array.isArray(traceOut.transforms)) traceOut.transforms.push(transformOpts); - else traceOut.transforms = [transformOpts]; + helpers.prependTransformOpts(traceIn, traceOut, transformOpts); var len = handleOHLC(traceIn, traceOut, coerce); - if(len === 0) { traceOut.visible = false; return; diff --git a/src/traces/candlestick/transform.js b/src/traces/candlestick/transform.js index 0531ac12da7..d31606ba55d 100644 --- a/src/traces/candlestick/transform.js +++ b/src/traces/candlestick/transform.js @@ -9,7 +9,6 @@ 'use strict'; -var Lib = require('../../lib'); var helpers = require('../ohlc/helpers'); exports.moduleType = 'transform'; @@ -18,7 +17,11 @@ exports.name = 'candlestick'; exports.attributes = {}; -exports.supplyDefaults = null; +exports.supplyDefaults = function(transformIn, traceOut) { + helpers.copyOHLC(transformIn, traceOut); + + return transformIn; +}; exports.transform = function transform(dataIn, state) { var dataOut = []; @@ -45,42 +48,15 @@ exports.transform = function transform(dataIn, state) { }; function makeTrace(traceIn, state, direction) { - var directionOpts = traceIn[direction]; - - // We need to track which direction ('increasing' or 'decreasing') - // the generated correspond to for the calcTransform step. - // - // To make sure that direction reaches the calcTransform, - // store it in the transform opts object. - var _transforms = Lib.extendFlat([], traceIn.transforms); - _transforms[state.transformIndex] = { - type: 'candlestick', - direction: direction - }; - - return { + var traceOut = { type: 'box', boxpoints: false, // TODO could do better name: direction, - // to make autotype catch date axes soon!! - x: traceIn.t || [0], - - // concat low and high to get correct autorange - y: [].concat(traceIn.low).concat(traceIn.high), - - visible: directionOpts.visible, - - line: { - color: directionOpts.color, - width: directionOpts.width - }, - fillcolor: directionOpts.fillcolor, - // TODO this doesn't restyle currently - whiskerwidth: directionOpts.tickwidth, + whiskerwidth: traceIn.whiskerwidth, text: traceIn.text, hoverinfo: traceIn.hoverinfo, @@ -88,39 +64,58 @@ function makeTrace(traceIn, state, direction) { opacity: traceIn.opacity, showlegend: traceIn.showlegend, - transforms: _transforms + transforms: helpers.makeTransform(traceIn, state, direction) }; -} -exports.calcTransform = function calcTransform(gd, trace, opts) { - var fullInput = trace._fullInput, - direction = opts.direction; + // the rest of below may not have been coerced - var filterFn = helpers.getFilterFn(direction); + var directionOpts = traceIn[direction]; - var open = fullInput.open, - high = fullInput.high, - low = fullInput.low, - close = fullInput.close; + if(directionOpts) { - // sliced accordingly in supply-defaults - var len = open.length; + // to make autotype catch date axes soon!! + traceOut.x = traceIn.x || [0]; + + // concat low and high to get correct autorange + traceOut.y = [].concat(traceIn.low).concat(traceIn.high); + + traceOut.visible = directionOpts.visible; + + traceOut.line = { + color: directionOpts.color, + width: directionOpts.width + }; - // clear generated trace x / y - trace.x = []; - trace.y = []; + traceOut.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 appendX = fullInput.t ? + var len = open.length, + x = [], + y = []; + + var appendX = trace._fullInput.x ? function(i) { - var t = fullInput.t[i]; - trace.x.push(t, t, t, t, t, t); + var v = trace.x[i]; + x.push(v, v, v, v, v, v); } : function(i) { - trace.x.push(i, i, i, i, i, i); + x.push(i, i, i, i, i, i); }; var appendY = function(o, h, l, c) { - trace.y.push(l, o, c, c, c, h); + y.push(l, o, c, c, c, h); }; for(var i = 0; i < len; i++) { @@ -129,4 +124,7 @@ exports.calcTransform = function calcTransform(gd, trace, opts) { appendY(open[i], high[i], low[i], close[i]); } } + + trace.x = x; + trace.y = y; }; diff --git a/src/traces/ohlc/defaults.js b/src/traces/ohlc/defaults.js index 54db210f3b9..b8b156bcc5e 100644 --- a/src/traces/ohlc/defaults.js +++ b/src/traces/ohlc/defaults.js @@ -12,6 +12,7 @@ var Lib = require('../../lib'); var handleOHLC = require('./ohlc_defaults'); var attributes = require('./attributes'); +var helpers = require('./helpers'); module.exports = function supplyDefaults(traceIn, traceOut) { @@ -19,14 +20,10 @@ module.exports = function supplyDefaults(traceIn, traceOut) { return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); } - // TODO .. HAVE TO TEST FILTER with 't', 'high', 'open', 'low' ... - var transformOpts = { type: 'ohlc' }; - if(Array.isArray(traceOut.transforms)) traceOut.transforms.push(transformOpts); - else traceOut.transforms = [transformOpts]; + helpers.prependTransformOpts(traceIn, traceOut, transformOpts); var len = handleOHLC(traceIn, traceOut, coerce); - if(len === 0) { traceOut.visible = false; return; diff --git a/src/traces/ohlc/helpers.js b/src/traces/ohlc/helpers.js index 01953e00cda..35aa94ed824 100644 --- a/src/traces/ohlc/helpers.js +++ b/src/traces/ohlc/helpers.js @@ -9,8 +9,49 @@ 'use strict'; +var Lib = require('../../lib'); -exports.getFilterFn = function getFilterFn(direction) { +// TODO add comment +exports.prependTransformOpts = function(traceIn, traceOut, transformOpts) { + if(Array.isArray(traceIn.transforms)) { + traceIn.transforms.push(transformOpts); + } + else { + traceIn.transforms = [transformOpts]; + } +}; + +// TODO add comment +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; +}; + +// We need to track which direction ('increasing' or 'decreasing') +// the generated correspond to for the calcTransform step. +// +// To make sure that direction reaches 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, + + // ... + 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; }; @@ -20,7 +61,7 @@ exports.getFilterFn = function getFilterFn(direction) { } }; -exports.addRangeSlider = function addRangeSlider(layout) { +exports.addRangeSlider = function(layout) { if(!layout.xaxis) layout.xaxis = {}; if(!layout.xaxis.rangeslider) layout.xaxis.rangeslider = {}; }; diff --git a/src/traces/ohlc/transform.js b/src/traces/ohlc/transform.js index a94f1cea93f..7ddd6b1bbb3 100644 --- a/src/traces/ohlc/transform.js +++ b/src/traces/ohlc/transform.js @@ -9,7 +9,6 @@ 'use strict'; -var Lib = require('../../lib'); var helpers = require('./helpers'); var axisIds = require('../../plots/cartesian/axis_ids'); @@ -19,7 +18,11 @@ exports.name = 'ohlc'; exports.attributes = {}; -exports.supplyDefaults = null; +exports.supplyDefaults = function(transformIn, traceOut) { + helpers.copyOHLC(transformIn, traceOut); + + return transformIn; +}; exports.transform = function transform(dataIn, state) { var dataOut = []; @@ -47,20 +50,7 @@ exports.transform = function transform(dataIn, state) { }; function makeTrace(traceIn, state, direction) { - var directionOpts = traceIn[direction]; - - // We need to track which direction ('increasing' or 'decreasing') - // the generated correspond to for the calcTransform step. - // - // To make sure that direction reaches the calcTransform, - // store it in the transform opts object. - var _transforms = Lib.extendFlat([], traceIn.transforms); - _transforms[state.transformIndex] = { - type: 'ohlc', - direction: direction - }; - - return { + var traceOut = { type: 'scatter', mode: 'lines', connectgaps: false, @@ -68,63 +58,65 @@ function makeTrace(traceIn, state, direction) { // TODO could do better name: direction, + text: traceIn.text, + hoverinfo: traceIn.hoverinfo, + + opacity: traceIn.opacity, + showlegend: traceIn.showlegend, + + transforms: helpers.makeTransform(traceIn, state, direction) + }; + + // the rest of below may not have been coerced + + var directionOpts = traceIn[direction]; + + if(directionOpts) { + // to make autotype catch date axes soon!! - x: traceIn.t || [0], + traceOut.x = traceIn.x || [0]; // concat low and high to get correct autorange - y: [].concat(traceIn.low).concat(traceIn.high), + traceOut.y = [].concat(traceIn.low).concat(traceIn.high); - visible: directionOpts.visible, + traceOut.visible = directionOpts.visible; - line: { + traceOut.line = { color: directionOpts.color, width: directionOpts.width, dash: directionOpts.dash - }, - - text: traceIn.text, - hoverinfo: traceIn.hoverinfo, - - opacity: traceIn.opacity, - showlegend: traceIn.showlegend, + }; + } - transforms: _transforms - }; + return traceOut; } exports.calcTransform = function calcTransform(gd, trace, opts) { - var fullInput = trace._fullInput, - direction = opts.direction; - - var filterFn = helpers.getFilterFn(direction), + var direction = opts.direction, + filterFn = helpers.getFilterFn(direction), ax = axisIds.getFromTrace(gd, trace, 'x'), - tickWidth = convertTickWidth(fullInput.t, ax, fullInput[direction].tickwidth); + tickWidth = convertTickWidth(trace.x, ax, trace._fullInput.tickwidth); - // TODO tickwidth does not restyle + var open = trace.open, + high = trace.high, + low = trace.low, + close = trace.close; - var open = fullInput.open, - high = fullInput.high, - low = fullInput.low, - close = fullInput.close; + var len = open.length, + x = [], + y = []; - // sliced accordingly in supply-defaults - var len = open.length; - - // clear generated trace x / y - trace.x = []; - trace.y = []; - - var appendX = fullInput.t ? + var appendX = trace._fullInput.x ? function(i) { - var t = ax.d2c(fullInput.t[i]); - trace.x.push(t - tickWidth, t, t, t, t, t + tickWidth, null); + var v = ax.d2c(trace.x[i]); + x.push(v - tickWidth, v, v, v, v, v + tickWidth, null); } : function(i) { - trace.x.push(i - tickWidth, i, i, i, i, i + tickWidth, null); + x.push(i - tickWidth, i, i, i, i, i + tickWidth, null); }; var appendY = function(o, h, l, c) { - trace.y.push(o, o, h, l, c, c, null); + y.push(o, o, h, l, c, c, null); }; for(var i = 0; i < len; i++) { @@ -133,16 +125,19 @@ exports.calcTransform = function calcTransform(gd, trace, opts) { appendY(open[i], high[i], low[i], close[i]); } } + + trace.x = x; + trace.y = y; }; -function convertTickWidth(t, ax, tickWidth) { - if(!t) return tickWidth; +function convertTickWidth(coords, ax, tickWidth) { + if(coords.length < 2) return tickWidth; - var tInData = t.map(ax.d2c), - minDTick = Infinity; + var _coords = coords.map(ax.d2c), + minDTick = _coords[1] - _coords[0]; - for(var i = 0; i < t.length - 1; i++) { - minDTick = Math.min(tInData[i + 1] - tInData[i], minDTick); + for(var i = 1; i < _coords.length - 1; i++) { + minDTick = Math.min(_coords[i + 1] - _coords[i], minDTick); } return minDTick * tickWidth; From 96e129ad4b8865106a779c073daa137a60e60c5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 7 Oct 2016 18:31:37 -0400 Subject: [PATCH 12/32] fix groupby when `enabled: false --- src/transforms/groupby.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/transforms/groupby.js b/src/transforms/groupby.js index f736c0e1a75..2de757f6681 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -117,14 +117,14 @@ 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 groupNames = groups.filter(function(g, i, self) { + return self.indexOf(g) === i; + }); + var newData = new Array(groupNames.length); var len = groups.length; From e32b60f266348fbf4fa556f0ac41b2a91bb32e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 7 Oct 2016 18:33:48 -0400 Subject: [PATCH 13/32] findArrayAttributes: include array attribute in fullInput module - this may have some side effects, but this is necessary to make ohlc and candlestick work with filter and groupby transforms - in brief, we need to make sure 'data_array' of input module are used correctly during filter and groupby operations --- src/lib/coerce.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/lib/coerce.js b/src/lib/coerce.js index d0f8565847f..c9f18944336 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -457,5 +457,14 @@ exports.findArrayAttributes = function(trace) { exports.crawl(trace._module.attributes, callback); + // TODO add comment + if(trace._fullInput) { + exports.crawl(trace._fullInput._module.attributes, callback); + + arrayAttributes = arrayAttributes.filter(function(g, i, self) { + return self.indexOf(g) === i; + }); + } + return arrayAttributes; }; From d61afb92f7a56732b6ee759e5a7bb4974f98bf8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Fri, 7 Oct 2016 18:34:03 -0400 Subject: [PATCH 14/32] test: first pass finance suite --- test/jasmine/tests/finance_test.js | 432 +++++++++++++++++++++++++++++ 1 file changed, 432 insertions(+) create mode 100644 test/jasmine/tests/finance_test.js diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js new file mode 100644 index 00000000000..c619d570a0d --- /dev/null +++ b/test/jasmine/tests/finance_test.js @@ -0,0 +1,432 @@ +var Plotly = require('@lib/index'); +var Plots = require('@src/plots/plots'); +var Lib = require('@src/lib'); + +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 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 transformTypes = out._fullData.map(function(fullTrace) { + return fullTrace.transforms.map(function(opts) { + return opts.type; + }); + }); + + expect(transformTypes).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 visibility should be inherited visibility from trace-wide visibility', function() { + + }); + + it('direction visibility should be inherited visibility from trace-wide visibility', function() { + + }); + + 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.hovermode).toEqual('closest'); + expect(out0._fullLayout.hovermode).toEqual('closest'); + 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.05, 0, 0, 0, 0, 0.05, null, + 2.95, 3, 3, 3, 3, 3.05, null, + 4.95, 5, 5, 5, 5, 5.05, null, + 6.95, 7, 7, 7, 7, 7.05, null + ]); + expect(out[1].x).toEqual([ + 0.95, 1, 1, 1, 1, 1.05, null, + 1.95, 2, 2, 2, 2, 2.05, null, + 3.95, 4, 4, 4, 4, 4.05, null, + 5.95, 6, 6, 6, 6, 6.05, 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 work with *filter* transforms', function() { + var trace0 = Lib.extendDeep({}, mock1, { + type: 'ohlc', + 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', + transforms: [opts] + }); + + var out = _calc([trace0]); + + expect(out[0].name).toEqual('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('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('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('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('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('decreasing'); + expect(out[1].x).toEqual([]); + expect(out[1].y).toEqual([]); + + expect(out[2].name).toEqual('increasing'); + expect(out[2].x).toEqual([]); + expect(out[2].y).toEqual([]); + + expect(out[3].name).toEqual('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(); + }); + + it('Plotly.restyle should work', function(done) { + var trace0 = Lib.extendDeep({}, mock0, { type: 'ohlc' }); + + Plotly.plot(gd, [trace0]).then(function() { + + // gotta test 'tickwidth' and 'whiskerwitdth' + + done(); + }); + + }); + + it('Plotly.relayout should work', function(done) { + var trace0 = Lib.extendDeep({}, mock0, { type: 'ohlc' }); + + Plotly.plot(gd, [trace0]).then(function() { + + done(); + }); + + }); + + it('Plotly.extendTraces should work', function(done) { + + done(); + }); + + it('Plotly.deleteTraces should work', function(done) { + done(); + }); + + it('legend *editable: true* interactions should work', function(done) { + + done(); + }); +}); From dc8aaea081327dc5783afa50017a3c0e2794d11f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 11 Oct 2016 10:53:48 -0400 Subject: [PATCH 15/32] finace: ensure supplyDefaults is idempotent --- src/traces/candlestick/defaults.js | 4 +--- src/traces/ohlc/defaults.js | 4 +--- src/traces/ohlc/helpers.js | 17 ++++++++++++++++- test/jasmine/tests/finance_test.js | 20 ++++++++++++++++++++ 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/traces/candlestick/defaults.js b/src/traces/candlestick/defaults.js index f085e2565e1..0c1f0bd7109 100644 --- a/src/traces/candlestick/defaults.js +++ b/src/traces/candlestick/defaults.js @@ -15,14 +15,12 @@ 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 transformOpts = { type: 'candlestick' }; - helpers.prependTransformOpts(traceIn, traceOut, transformOpts); - var len = handleOHLC(traceIn, traceOut, coerce); if(len === 0) { traceOut.visible = false; diff --git a/src/traces/ohlc/defaults.js b/src/traces/ohlc/defaults.js index b8b156bcc5e..b41a5338564 100644 --- a/src/traces/ohlc/defaults.js +++ b/src/traces/ohlc/defaults.js @@ -15,14 +15,12 @@ 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 transformOpts = { type: 'ohlc' }; - helpers.prependTransformOpts(traceIn, traceOut, transformOpts); - var len = handleOHLC(traceIn, traceOut, coerce); if(len === 0) { traceOut.visible = false; diff --git a/src/traces/ohlc/helpers.js b/src/traces/ohlc/helpers.js index 35aa94ed824..862a9a81463 100644 --- a/src/traces/ohlc/helpers.js +++ b/src/traces/ohlc/helpers.js @@ -12,8 +12,23 @@ var Lib = require('../../lib'); // TODO add comment -exports.prependTransformOpts = function(traceIn, traceOut, transformOpts) { +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)) { + var transformsIn = traceIn.transforms; + + for(var i = 0; i < transformsIn.length; i++) { + if(transformsIn[i]._ephemeral) transformsIn.splice(i, 1); + } + traceIn.transforms.push(transformOpts); } else { diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js index c619d570a0d..3703967a7a9 100644 --- a/test/jasmine/tests/finance_test.js +++ b/test/jasmine/tests/finance_test.js @@ -54,6 +54,26 @@ describe('finance charts defaults:', function() { expect(directions).toEqual(['increasing', 'decreasing', 'increasing', 'decreasing']); }); + it('unfortunately mutates user trace', function() { + var trace0 = Lib.extendDeep({}, mock0, { + type: 'ohlc' + }); + + var trace1 = Lib.extendDeep({}, mock1, { + type: 'candlestick' + }); + + var out = _supply([trace0, trace1]); + expect(out.data[0].transforms).toEqual([{ type: 'ohlc', _ephemeral: true }]); + expect(out.data[1].transforms).toEqual([{ type: 'candlestick', _ephemeral: true }]); + + // but at least in an idempotent way + + var out2 = _supply(out.data); + expect(out2.data[0].transforms).toEqual([{ type: 'ohlc', _ephemeral: true }]); + expect(out2.data[1].transforms).toEqual([{ type: 'candlestick', _ephemeral: true }]); + }); + it('should work with transforms', function() { var trace0 = Lib.extendDeep({}, mock1, { type: 'ohlc', From 5c94c0506bd0cc6b1cacd5bec6912866dea79121 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 11 Oct 2016 11:08:35 -0400 Subject: [PATCH 16/32] finance: add re-calc attributes to restyle lists --- src/plot_api/plot_api.js | 4 +++- test/jasmine/tests/finance_test.js | 30 ++++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index ef0831cbfb3..640912f281b 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -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/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js index 3703967a7a9..25e2ac002fc 100644 --- a/test/jasmine/tests/finance_test.js +++ b/test/jasmine/tests/finance_test.js @@ -2,6 +2,7 @@ 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'); @@ -417,9 +418,34 @@ describe('finance charts updates:', function() { it('Plotly.restyle should work', function(done) { var trace0 = Lib.extendDeep({}, mock0, { type: 'ohlc' }); - Plotly.plot(gd, [trace0]).then(function() { + var path0; - // gotta test 'tickwidth' and 'whiskerwitdth' + Plotly.plot(gd, [trace0]).then(function() { + expect(gd.calcdata[0][0].x).toEqual(-0.05); + 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(); }); From 1f574f2c0a91278ab99c204a4138d648a81aedba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 11 Oct 2016 16:16:51 -0400 Subject: [PATCH 17/32] finance: ensure that restyling visible works --- src/plots/plots.js | 2 +- test/jasmine/tests/finance_test.js | 151 ++++++++++++++++++++++++++++- 2 files changed, 149 insertions(+), 4 deletions(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index 5a6889407ba..b950109030f 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -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], diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js index 25e2ac002fc..86ba6ba8feb 100644 --- a/test/jasmine/tests/finance_test.js +++ b/test/jasmine/tests/finance_test.js @@ -415,6 +415,18 @@ describe('finance charts updates:', function() { 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' }); @@ -452,10 +464,66 @@ describe('finance charts updates:', function() { }); + 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(); }); @@ -463,12 +531,89 @@ describe('finance charts updates:', function() { }); 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(); + done(); + }); }); - it('Plotly.deleteTraces should work', function(done) { - 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(); + }); }); it('legend *editable: true* interactions should work', function(done) { From 7b19b742767050a28561f131d033a4a3ab77e90f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 11 Oct 2016 16:17:26 -0400 Subject: [PATCH 18/32] finance: improve dflt colors --- src/traces/candlestick/attributes.js | 4 ++-- src/traces/ohlc/attributes.js | 8 +++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/traces/candlestick/attributes.js b/src/traces/candlestick/attributes.js index 17f7fabc3e2..3d8dc9a0fa0 100644 --- a/src/traces/candlestick/attributes.js +++ b/src/traces/candlestick/attributes.js @@ -37,11 +37,11 @@ module.exports = { close: OHLCattrs.close, increasing: Lib.extendDeep({}, directionAttrs, { - color: { dflt: 'green' } + color: { dflt: OHLCattrs.increasing.color.dflt } }), decreasing: Lib.extendDeep({}, directionAttrs, { - color: { dflt: 'red' } + color: { dflt: OHLCattrs.decreasing.color.dflt } }), text: OHLCattrs.text, diff --git a/src/traces/ohlc/attributes.js b/src/traces/ohlc/attributes.js index c910f42e863..8aef53f98eb 100644 --- a/src/traces/ohlc/attributes.js +++ b/src/traces/ohlc/attributes.js @@ -12,6 +12,9 @@ var Lib = require('../../lib'); var scatterAttrs = require('../scatter/attributes'); +var INCREASING_COLOR = '#3D9970'; +var DECREASING_COLOR = '#FF4136'; + var lineAttrs = scatterAttrs.line; var directionAttrs = { @@ -64,13 +67,12 @@ module.exports = { description: 'Sets the close values.' }, - // TODO find better colors increasing: Lib.extendDeep({}, directionAttrs, { - color: { dflt: 'green' } + color: { dflt: INCREASING_COLOR } }), decreasing: Lib.extendDeep({}, directionAttrs, { - color: { dflt: 'red' } + color: { dflt: DECREASING_COLOR } }), text: { From 2b1918cfe13b01106ff58a2d466379c4b1ee776a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 11 Oct 2016 16:19:34 -0400 Subject: [PATCH 19/32] finance: improve inc / dec 'name' / 'showlegend' logic - remove `increasing/decreasing.visible` attribute - add increasing/decreasing 'name' and 'showlegend' --- src/traces/candlestick/attributes.js | 11 +--- src/traces/candlestick/defaults.js | 17 +++-- src/traces/candlestick/transform.js | 37 ++++++----- src/traces/ohlc/attributes.js | 17 +++-- src/traces/ohlc/defaults.js | 17 +++-- src/traces/ohlc/direction_defaults.js | 24 +++++++ src/traces/ohlc/transform.js | 33 +++++----- test/jasmine/tests/finance_test.js | 90 ++++++++++++++++++++++++--- 8 files changed, 170 insertions(+), 76 deletions(-) create mode 100644 src/traces/ohlc/direction_defaults.js diff --git a/src/traces/candlestick/attributes.js b/src/traces/candlestick/attributes.js index 3d8dc9a0fa0..c2fefb33782 100644 --- a/src/traces/candlestick/attributes.js +++ b/src/traces/candlestick/attributes.js @@ -14,15 +14,8 @@ var OHLCattrs = require('../ohlc/attributes'); var boxAttrs = require('../box/attributes'); var directionAttrs = { - visible: { - valType: 'enumerated', - values: [true, false, 'legendonly'], - role: 'info', - dflt: true, - description: [ - - ].join(' ') - }, + name: OHLCattrs.increasing.name, + showlegend: OHLCattrs.increasing.showlegend, color: Lib.extendFlat({}, boxAttrs.line.color), width: Lib.extendFlat({}, boxAttrs.line.width), diff --git a/src/traces/candlestick/defaults.js b/src/traces/candlestick/defaults.js index 0c1f0bd7109..f00ce9e3a74 100644 --- a/src/traces/candlestick/defaults.js +++ b/src/traces/candlestick/defaults.js @@ -11,6 +11,7 @@ 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'); @@ -30,16 +31,14 @@ module.exports = function supplyDefaults(traceIn, traceOut) { coerce('text'); coerce('whiskerwidth'); - handleDirection(traceOut, coerce, 'increasing'); - handleDirection(traceOut, coerce, 'decreasing'); + handleDirection(traceIn, traceOut, coerce, 'increasing'); + handleDirection(traceIn, traceOut, coerce, 'decreasing'); }; -function handleDirection(traceOut, coerce, direction) { - var dirVisible = coerce(direction + '.visible', traceOut.visible); +function handleDirection(traceIn, traceOut, coerce, direction) { + handleDirectionDefaults(traceIn, traceOut, coerce, direction); - if(dirVisible) { - coerce(direction + '.color'); - coerce(direction + '.width'); - coerce(direction + '.fillcolor'); - } + coerce(direction + '.color'); + coerce(direction + '.width'); + coerce(direction + '.fillcolor'); } diff --git a/src/traces/candlestick/transform.js b/src/traces/candlestick/transform.js index d31606ba55d..6e9ecf16569 100644 --- a/src/traces/candlestick/transform.js +++ b/src/traces/candlestick/transform.js @@ -9,6 +9,7 @@ 'use strict'; +var Lib = require('../../lib'); var helpers = require('../ohlc/helpers'); exports.moduleType = 'transform'; @@ -52,17 +53,9 @@ function makeTrace(traceIn, state, direction) { type: 'box', boxpoints: false, - // TODO could do better - name: direction, - - // TODO this doesn't restyle currently - whiskerwidth: traceIn.whiskerwidth, - - text: traceIn.text, + visible: traceIn.visible, hoverinfo: traceIn.hoverinfo, - opacity: traceIn.opacity, - showlegend: traceIn.showlegend, transforms: helpers.makeTransform(traceIn, state, direction) }; @@ -72,21 +65,27 @@ function makeTrace(traceIn, state, direction) { var directionOpts = traceIn[direction]; if(directionOpts) { + Lib.extendFlat(traceOut, { - // to make autotype catch date axes soon!! - traceOut.x = traceIn.x || [0]; + // to make autotype catch date axes soon!! + x: traceIn.x || [0], - // concat low and high to get correct autorange - traceOut.y = [].concat(traceIn.low).concat(traceIn.high); + // concat low and high to get correct autorange + y: [].concat(traceIn.low).concat(traceIn.high), - traceOut.visible = directionOpts.visible; + whiskerwidth: traceIn.whiskerwidth, + text: traceIn.text, - traceOut.line = { - color: directionOpts.color, - width: directionOpts.width - }; + name: directionOpts.name, + showlegend: directionOpts.showlegend, + + line: { + color: directionOpts.color, + width: directionOpts.width + }, - traceOut.fillcolor = directionOpts.fillcolor; + fillcolor: directionOpts.fillcolor + }); } return traceOut; diff --git a/src/traces/ohlc/attributes.js b/src/traces/ohlc/attributes.js index 8aef53f98eb..6001e110446 100644 --- a/src/traces/ohlc/attributes.js +++ b/src/traces/ohlc/attributes.js @@ -18,13 +18,22 @@ var DECREASING_COLOR = '#FF4136'; var lineAttrs = scatterAttrs.line; var directionAttrs = { - visible: { - valType: 'enumerated', - values: [true, false, 'legendonly'], + name: { + valType: 'string', role: 'info', - dflt: true, 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(' ') }, diff --git a/src/traces/ohlc/defaults.js b/src/traces/ohlc/defaults.js index b41a5338564..d37f8c4ca6a 100644 --- a/src/traces/ohlc/defaults.js +++ b/src/traces/ohlc/defaults.js @@ -11,6 +11,7 @@ var Lib = require('../../lib'); var handleOHLC = require('./ohlc_defaults'); +var handleDirectionDefaults = require('./direction_defaults'); var attributes = require('./attributes'); var helpers = require('./helpers'); @@ -30,16 +31,14 @@ module.exports = function supplyDefaults(traceIn, traceOut) { coerce('text'); coerce('tickwidth'); - handleDirection(traceOut, coerce, 'increasing'); - handleDirection(traceOut, coerce, 'decreasing'); + handleDirection(traceIn, traceOut, coerce, 'increasing'); + handleDirection(traceIn, traceOut, coerce, 'decreasing'); }; -function handleDirection(traceOut, coerce, direction) { - var dirVisible = coerce(direction + '.visible', traceOut.visible); +function handleDirection(traceIn, traceOut, coerce, direction) { + handleDirectionDefaults(traceIn, traceOut, coerce, direction); - if(dirVisible) { - coerce(direction + '.color'); - coerce(direction + '.width'); - coerce(direction + '.dash'); - } + coerce(direction + '.color'); + coerce(direction + '.width'); + coerce(direction + '.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/transform.js b/src/traces/ohlc/transform.js index 7ddd6b1bbb3..971ee9561ff 100644 --- a/src/traces/ohlc/transform.js +++ b/src/traces/ohlc/transform.js @@ -9,6 +9,7 @@ 'use strict'; +var Lib = require('../../lib'); var helpers = require('./helpers'); var axisIds = require('../../plots/cartesian/axis_ids'); @@ -55,14 +56,9 @@ function makeTrace(traceIn, state, direction) { mode: 'lines', connectgaps: false, - // TODO could do better - name: direction, - - text: traceIn.text, + visible: traceIn.visible, hoverinfo: traceIn.hoverinfo, - opacity: traceIn.opacity, - showlegend: traceIn.showlegend, transforms: helpers.makeTransform(traceIn, state, direction) }; @@ -72,20 +68,25 @@ function makeTrace(traceIn, state, direction) { var directionOpts = traceIn[direction]; if(directionOpts) { + Lib.extendFlat(traceOut, { - // to make autotype catch date axes soon!! - traceOut.x = traceIn.x || [0]; + // to make autotype catch date axes soon!! + x: traceIn.x || [0], - // concat low and high to get correct autorange - traceOut.y = [].concat(traceIn.low).concat(traceIn.high); + // concat low and high to get correct autorange + y: [].concat(traceIn.low).concat(traceIn.high), - traceOut.visible = directionOpts.visible; + text: traceIn.text, - traceOut.line = { - color: directionOpts.color, - width: directionOpts.width, - dash: directionOpts.dash - }; + name: directionOpts.name, + showlegend: directionOpts.showlegend, + + line: { + color: directionOpts.color, + width: directionOpts.width, + dash: directionOpts.dash + } + }); } return traceOut; diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js index 86ba6ba8feb..faac1b42c26 100644 --- a/test/jasmine/tests/finance_test.js +++ b/test/jasmine/tests/finance_test.js @@ -65,13 +65,17 @@ describe('finance charts defaults:', function() { }); var out = _supply([trace0, trace1]); + expect(out.data[0].transforms.length).toEqual(1); expect(out.data[0].transforms).toEqual([{ type: 'ohlc', _ephemeral: true }]); + expect(out.data[1].transforms.length).toEqual(1); expect(out.data[1].transforms).toEqual([{ type: 'candlestick', _ephemeral: true }]); // but at least in an idempotent way var out2 = _supply(out.data); + expect(out2.data[0].transforms.length).toEqual(1); expect(out2.data[0].transforms).toEqual([{ type: 'ohlc', _ephemeral: true }]); + expect(out2.data[0].transforms.length).toEqual(1); expect(out2.data[1].transforms).toEqual([{ type: 'candlestick', _ephemeral: true }]); }); @@ -101,6 +105,9 @@ describe('finance charts defaults:', function() { }); }); + // dummy 'ohlc' and 'candlestick' transforms are pushed at the end + // of the 'transforms' array container + expect(transformTypes).toEqual([ ['filter', 'ohlc'], ['filter', 'ohlc'], ['filter', 'candlestick'], ['filter', 'candlestick'] @@ -156,12 +163,75 @@ describe('finance charts defaults:', function() { expect(visibilities).toEqual([false, false, false, false]); }); - it('direction visibility should be inherited visibility from trace-wide visibility', function() { + 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 visibility should be inherited visibility from trace-wide visibility', function() { + 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 *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() { @@ -327,7 +397,7 @@ describe('finance charts calc transforms:', function() { var out = _calc([trace0]); - expect(out[0].name).toEqual('increasing'); + 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, ]); @@ -335,7 +405,7 @@ describe('finance charts calc transforms:', function() { 33.01, 33.01, 34.2, 31.7, 34.1, 34.1, null, ]); - expect(out[1].name).toEqual('decreasing'); + 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 @@ -345,7 +415,7 @@ describe('finance charts calc transforms:', function() { 33.5, 33.5, 33.62, 32.87, 33.37, 33.37, null ]); - expect(out[2].name).toEqual('increasing'); + 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 ]); @@ -353,7 +423,7 @@ describe('finance charts calc transforms:', function() { 32.06, 32.06, 34.25, 31.62, 33.18, 33.18, null ]); - expect(out[3].name).toEqual('decreasing'); + expect(out[3].name).toEqual('trace 0 - decreasing'); expect(out[3].x.map(ms2DateTime)).toEqual([]); expect(out[3].y).toEqual([]); }); @@ -371,7 +441,7 @@ describe('finance charts calc transforms:', function() { var out = _calc([trace0]); - expect(out[0].name).toEqual('increasing'); + 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' @@ -381,15 +451,15 @@ describe('finance charts calc transforms:', function() { 31.62, 32.06, 33.18, 33.18, 33.18, 34.25 ]); - expect(out[1].name).toEqual('decreasing'); + expect(out[1].name).toEqual('trace 0 - decreasing'); expect(out[1].x).toEqual([]); expect(out[1].y).toEqual([]); - expect(out[2].name).toEqual('increasing'); + expect(out[2].name).toEqual('trace 0 - increasing'); expect(out[2].x).toEqual([]); expect(out[2].y).toEqual([]); - expect(out[3].name).toEqual('decreasing'); + 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' From fbb299b145d0ed1c16e4802a4a25120938ff4e16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 11 Oct 2016 16:20:34 -0400 Subject: [PATCH 20/32] legend: add logic for 'ohlc' and 'candlestick' in legend name edits - so that editing legend item text restyles increasing / decreasing 'name' instead of trace-wide name --- src/components/legend/draw.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index fd08a20baa5..11183fcdc3f 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -367,10 +367,27 @@ 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, + // 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); From caf390bd6e5f675beb4c9c2f88998fc9265781da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 11 Oct 2016 18:45:33 -0400 Subject: [PATCH 21/32] doc: add comments about non-trivial logic in ohlc / candlestick --- src/components/legend/draw.js | 1 + src/lib/coerce.js | 7 ++++++- src/traces/candlestick/index.js | 12 +++++++++++- src/traces/ohlc/attributes.js | 2 +- src/traces/ohlc/helpers.js | 30 +++++++++++++++++++++++++----- src/traces/ohlc/index.js | 12 +++++++++++- test/jasmine/tests/finance_test.js | 5 ----- 7 files changed, 55 insertions(+), 14 deletions(-) diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js index 11183fcdc3f..17258ffbd21 100644 --- a/src/components/legend/draw.js +++ b/src/components/legend/draw.js @@ -377,6 +377,7 @@ function drawTexts(g, gd) { 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) { diff --git a/src/lib/coerce.js b/src/lib/coerce.js index c9f18944336..db43584e0e8 100644 --- a/src/lib/coerce.js +++ b/src/lib/coerce.js @@ -457,7 +457,12 @@ exports.findArrayAttributes = function(trace) { exports.crawl(trace._module.attributes, callback); - // TODO add comment + // 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); diff --git a/src/traces/candlestick/index.js b/src/traces/candlestick/index.js index d3594bda298..ccd1d5427aa 100644 --- a/src/traces/candlestick/index.js +++ b/src/traces/candlestick/index.js @@ -18,7 +18,17 @@ module.exports = { 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(' ') }, diff --git a/src/traces/ohlc/attributes.js b/src/traces/ohlc/attributes.js index 6001e110446..81067f74e0c 100644 --- a/src/traces/ohlc/attributes.js +++ b/src/traces/ohlc/attributes.js @@ -93,7 +93,7 @@ module.exports = { '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 the', + 'If an array of string, the items are mapped in order to', 'this trace\'s sample points.' ].join(' ') }, diff --git a/src/traces/ohlc/helpers.js b/src/traces/ohlc/helpers.js index 862a9a81463..cd5330af577 100644 --- a/src/traces/ohlc/helpers.js +++ b/src/traces/ohlc/helpers.js @@ -11,7 +11,15 @@ var Lib = require('../../lib'); -// TODO add comment +// 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. exports.pushDummyTransformOpts = function(traceIn, traceOut) { var transformOpts = { @@ -25,6 +33,8 @@ exports.pushDummyTransformOpts = function(traceIn, traceOut) { if(Array.isArray(traceIn.transforms)) { var transformsIn = traceIn.transforms; + // remove all ephemeral transforms, + // before adding dummy transform for(var i = 0; i < transformsIn.length; i++) { if(transformsIn[i]._ephemeral) transformsIn.splice(i, 1); } @@ -36,7 +46,14 @@ exports.pushDummyTransformOpts = function(traceIn, traceOut) { } }; -// TODO add comment +// This routine gets called during the transform supply-defaults step, +// but only has an effect (because of the if-statements) during the +// second round of transform defaults done on generated traces. +// +// This is a hacky way to pass '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. exports.copyOHLC = function(container, traceOut) { if(container.open) traceOut.open = container.open; if(container.high) traceOut.high = container.high; @@ -44,10 +61,13 @@ exports.copyOHLC = function(container, traceOut) { if(container.close) traceOut.close = container.close; }; -// We need to track which direction ('increasing' or 'decreasing') +// 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 direction reaches the calcTransform, +// 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); @@ -56,7 +76,7 @@ exports.makeTransform = function(traceIn, state, direction) { type: traceIn.type, direction: direction, - // ... + // these are copied to traceOut during exports.copyOHLC open: traceIn.open, high: traceIn.high, low: traceIn.low, diff --git a/src/traces/ohlc/index.js b/src/traces/ohlc/index.js index 89b42b9a47d..edaa23a1d32 100644 --- a/src/traces/ohlc/index.js +++ b/src/traces/ohlc/index.js @@ -18,7 +18,17 @@ module.exports = { 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(' ') }, diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js index faac1b42c26..e2864b10506 100644 --- a/test/jasmine/tests/finance_test.js +++ b/test/jasmine/tests/finance_test.js @@ -685,9 +685,4 @@ describe('finance charts updates:', function() { done(); }); }); - - it('legend *editable: true* interactions should work', function(done) { - - done(); - }); }); From 672d51742a764bd6203528bfe8721786f414ab49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 11 Oct 2016 23:37:19 -0400 Subject: [PATCH 22/32] finance: pass trace 'xaxis' & 'yaxis' to generated traces --- src/traces/candlestick/transform.js | 2 ++ src/traces/ohlc/transform.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/traces/candlestick/transform.js b/src/traces/candlestick/transform.js index 6e9ecf16569..05f5d2b6311 100644 --- a/src/traces/candlestick/transform.js +++ b/src/traces/candlestick/transform.js @@ -56,6 +56,8 @@ function makeTrace(traceIn, state, direction) { visible: traceIn.visible, hoverinfo: traceIn.hoverinfo, opacity: traceIn.opacity, + xaxis: traceIn.xaxis, + yaxis: traceIn.yaxis, transforms: helpers.makeTransform(traceIn, state, direction) }; diff --git a/src/traces/ohlc/transform.js b/src/traces/ohlc/transform.js index 971ee9561ff..0ceda9fd2fd 100644 --- a/src/traces/ohlc/transform.js +++ b/src/traces/ohlc/transform.js @@ -59,6 +59,8 @@ function makeTrace(traceIn, state, direction) { visible: traceIn.visible, hoverinfo: traceIn.hoverinfo, opacity: traceIn.opacity, + xaxis: traceIn.xaxis, + yaxis: traceIn.yaxis, transforms: helpers.makeTransform(traceIn, state, direction) }; From ea3c5a1051ae1c925a59d7b8332c8446ce83f40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 11 Oct 2016 23:38:45 -0400 Subject: [PATCH 23/32] finance: make sure computed tickWidth is always positive --- src/traces/ohlc/transform.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/traces/ohlc/transform.js b/src/traces/ohlc/transform.js index 0ceda9fd2fd..f11c8f09978 100644 --- a/src/traces/ohlc/transform.js +++ b/src/traces/ohlc/transform.js @@ -137,10 +137,11 @@ function convertTickWidth(coords, ax, tickWidth) { if(coords.length < 2) return tickWidth; var _coords = coords.map(ax.d2c), - minDTick = _coords[1] - _coords[0]; + minDTick = Math.abs(_coords[1] - _coords[0]); for(var i = 1; i < _coords.length - 1; i++) { - minDTick = Math.min(_coords[i + 1] - _coords[i], minDTick); + var dist = Math.abs(_coords[i + 1] - _coords[i]); + minDTick = Math.min(dist, minDTick); } return minDTick * tickWidth; From a892b262ee0302d33684d9a72907c3dc0eb855a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Tue, 11 Oct 2016 23:39:24 -0400 Subject: [PATCH 24/32] olhc: add custom hover text - by filling in a 'text' array in the generated traces --- src/traces/ohlc/transform.js | 84 +++++++++++++++++++++++++----- test/jasmine/tests/finance_test.js | 52 ++++++++++++++++++ 2 files changed, 122 insertions(+), 14 deletions(-) diff --git a/src/traces/ohlc/transform.js b/src/traces/ohlc/transform.js index f11c8f09978..4afa2d02010 100644 --- a/src/traces/ohlc/transform.js +++ b/src/traces/ohlc/transform.js @@ -11,6 +11,7 @@ var Lib = require('../../lib'); var helpers = require('./helpers'); +var Axes = require('../../plots/cartesian/axes'); var axisIds = require('../../plots/cartesian/axis_ids'); exports.moduleType = 'transform'; @@ -57,11 +58,11 @@ function makeTrace(traceIn, state, direction) { connectgaps: false, visible: traceIn.visible, - hoverinfo: traceIn.hoverinfo, opacity: traceIn.opacity, xaxis: traceIn.xaxis, yaxis: traceIn.yaxis, + hoverinfo: makeHoverInfo(traceIn), transforms: helpers.makeTransform(traceIn, state, direction) }; @@ -94,43 +95,98 @@ function makeTrace(traceIn, state, direction) { return traceOut; } +// let scatter hoverPoint format 'x' coordinates, if desired +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), - ax = axisIds.getFromTrace(gd, trace, 'x'), - tickWidth = convertTickWidth(trace.x, ax, trace._fullInput.tickwidth); + 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; + close = trace.close, + textIn = trace.text; var len = open.length, x = [], - y = []; + y = [], + textOut = []; + + var getXItem = trace._fullInput.x ? + function(i) { return xa.d2c(trace.x[i]); } : + function(i) { return i; }; - var appendX = trace._fullInput.x ? - function(i) { - var v = ax.d2c(trace.x[i]); - x.push(v - tickWidth, v, v, v, v, v + tickWidth, null); - } : - function(i) { - x.push(i - tickWidth, i, i, i, i, i + tickWidth, null); - }; + 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) { diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js index e2864b10506..62fc0266570 100644 --- a/test/jasmine/tests/finance_test.js +++ b/test/jasmine/tests/finance_test.js @@ -323,6 +323,58 @@ describe('finance charts calc transforms:', function() { ]); }); + 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', From 7e0a382516b0868a8bbf8f5dd5401bc5f4292568 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 12 Oct 2016 12:09:18 -0400 Subject: [PATCH 25/32] finance: ensure that user data isn't mutated in Plots.supplyDefaults for ohlc and candlestick traces --- src/plots/plots.js | 2 +- src/traces/candlestick/transform.js | 3 ++- src/traces/ohlc/helpers.js | 40 +++++++++++++++++------------ src/traces/ohlc/transform.js | 3 ++- test/jasmine/tests/finance_test.js | 32 ++++++++++++++--------- 5 files changed, 49 insertions(+), 31 deletions(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index b950109030f..c504faa0c37 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -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 { diff --git a/src/traces/candlestick/transform.js b/src/traces/candlestick/transform.js index 05f5d2b6311..31bbd75193c 100644 --- a/src/traces/candlestick/transform.js +++ b/src/traces/candlestick/transform.js @@ -18,7 +18,8 @@ exports.name = 'candlestick'; exports.attributes = {}; -exports.supplyDefaults = function(transformIn, traceOut) { +exports.supplyDefaults = function(transformIn, traceOut, layout, traceIn) { + helpers.clearEphemeralTransformOpts(traceIn); helpers.copyOHLC(transformIn, traceOut); return transformIn; diff --git a/src/traces/ohlc/helpers.js b/src/traces/ohlc/helpers.js index cd5330af577..8167f732dbd 100644 --- a/src/traces/ohlc/helpers.js +++ b/src/traces/ohlc/helpers.js @@ -19,7 +19,8 @@ var Lib = require('../../lib'); // 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. +// from a clear transforms container. The mutations inflicted are +// cleared in exports.clearEphemeralTransformOpts. exports.pushDummyTransformOpts = function(traceIn, traceOut) { var transformOpts = { @@ -31,14 +32,6 @@ exports.pushDummyTransformOpts = function(traceIn, traceOut) { }; if(Array.isArray(traceIn.transforms)) { - var transformsIn = traceIn.transforms; - - // remove all ephemeral transforms, - // before adding dummy transform - for(var i = 0; i < transformsIn.length; i++) { - if(transformsIn[i]._ephemeral) transformsIn.splice(i, 1); - } - traceIn.transforms.push(transformOpts); } else { @@ -46,14 +39,29 @@ exports.pushDummyTransformOpts = function(traceIn, traceOut) { } }; -// This routine gets called during the transform supply-defaults step, -// but only has an effect (because of the if-statements) during the -// second round of transform defaults done on generated traces. -// -// This is a hacky way to pass 'ohlc' and 'candlestick' attributes +// 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. +// 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; diff --git a/src/traces/ohlc/transform.js b/src/traces/ohlc/transform.js index 4afa2d02010..48535e3c89a 100644 --- a/src/traces/ohlc/transform.js +++ b/src/traces/ohlc/transform.js @@ -20,7 +20,8 @@ exports.name = 'ohlc'; exports.attributes = {}; -exports.supplyDefaults = function(transformIn, traceOut) { +exports.supplyDefaults = function(transformIn, traceOut, layout, traceIn) { + helpers.clearEphemeralTransformOpts(traceIn); helpers.copyOHLC(transformIn, traceOut); return transformIn; diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js index 62fc0266570..1ad087552ca 100644 --- a/test/jasmine/tests/finance_test.js +++ b/test/jasmine/tests/finance_test.js @@ -55,7 +55,7 @@ describe('finance charts defaults:', function() { expect(directions).toEqual(['increasing', 'decreasing', 'increasing', 'decreasing']); }); - it('unfortunately mutates user trace', function() { + it('should not mutate user data', function() { var trace0 = Lib.extendDeep({}, mock0, { type: 'ohlc' }); @@ -65,18 +65,18 @@ describe('finance charts defaults:', function() { }); var out = _supply([trace0, trace1]); - expect(out.data[0].transforms.length).toEqual(1); - expect(out.data[0].transforms).toEqual([{ type: 'ohlc', _ephemeral: true }]); - expect(out.data[1].transforms.length).toEqual(1); - expect(out.data[1].transforms).toEqual([{ type: 'candlestick', _ephemeral: true }]); + expect(out.data[0]).toBe(trace0); + expect(out.data[0].transforms).toBeUndefined(); + expect(out.data[1]).toBe(trace1); + expect(out.data[1].transforms).toBeUndefined(); - // but at least in an idempotent way + // ... and in an idempotent way var out2 = _supply(out.data); - expect(out2.data[0].transforms.length).toEqual(1); - expect(out2.data[0].transforms).toEqual([{ type: 'ohlc', _ephemeral: true }]); - expect(out2.data[0].transforms.length).toEqual(1); - expect(out2.data[1].transforms).toEqual([{ type: 'candlestick', _ephemeral: true }]); + 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() { @@ -99,7 +99,15 @@ describe('finance charts defaults:', function() { expect(out.data.length).toEqual(2); expect(out._fullData.length).toEqual(4); - var transformTypes = out._fullData.map(function(fullTrace) { + 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; }); @@ -108,7 +116,7 @@ describe('finance charts defaults:', function() { // dummy 'ohlc' and 'candlestick' transforms are pushed at the end // of the 'transforms' array container - expect(transformTypes).toEqual([ + expect(transformTypesOut).toEqual([ ['filter', 'ohlc'], ['filter', 'ohlc'], ['filter', 'candlestick'], ['filter', 'candlestick'] ]); From f6f072f09a769a5c3e050be11cde584c7ec475b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 12 Oct 2016 12:48:37 -0400 Subject: [PATCH 26/32] utils: add filterUnique helper - use it in groupby transform and findArrayAttributes --- src/lib/coerce.js | 5 ++-- src/lib/filter_unique.js | 49 ++++++++++++++++++++++++++++++++++ src/lib/index.js | 2 ++ src/transforms/groupby.js | 9 +++---- test/jasmine/tests/lib_test.js | 31 +++++++++++++++++++++ 5 files changed, 87 insertions(+), 9 deletions(-) create mode 100644 src/lib/filter_unique.js diff --git a/src/lib/coerce.js b/src/lib/coerce.js index db43584e0e8..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')); @@ -466,9 +467,7 @@ exports.findArrayAttributes = function(trace) { if(trace._fullInput) { exports.crawl(trace._fullInput._module.attributes, callback); - arrayAttributes = arrayAttributes.filter(function(g, i, self) { - return self.indexOf(g) === i; - }); + 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/transforms/groupby.js b/src/transforms/groupby.js index 2de757f6681..0cce190eab3 100644 --- a/src/transforms/groupby.js +++ b/src/transforms/groupby.js @@ -121,12 +121,9 @@ function transformOne(trace, state) { return trace; } - var groupNames = groups.filter(function(g, i, self) { - return self.indexOf(g) === i; - }); - - 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/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() { From 6036045e328be41f93523a6014a1dc82ac81468b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 12 Oct 2016 13:21:52 -0400 Subject: [PATCH 27/32] ohlc: don't force hovermode: 'closest' - the default hovermode: 'x' now looks good after adding transformed hover text --- src/traces/candlestick/transform.js | 4 +--- src/traces/ohlc/transform.js | 15 ++++++++++----- test/jasmine/tests/finance_test.js | 2 -- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/traces/candlestick/transform.js b/src/traces/candlestick/transform.js index 31bbd75193c..b1c237a9f45 100644 --- a/src/traces/candlestick/transform.js +++ b/src/traces/candlestick/transform.js @@ -42,9 +42,7 @@ exports.transform = function transform(dataIn, state) { ); } - // add a few layout features - var layout = state.layout; - helpers.addRangeSlider(layout); + helpers.addRangeSlider(state.layout); return dataOut; }; diff --git a/src/traces/ohlc/transform.js b/src/traces/ohlc/transform.js index 48535e3c89a..3b6e91f2be9 100644 --- a/src/traces/ohlc/transform.js +++ b/src/traces/ohlc/transform.js @@ -44,10 +44,7 @@ exports.transform = function transform(dataIn, state) { ); } - // add a few layout features - var layout = state.layout; - if(!layout.hovermode) layout.hovermode = 'closest'; - helpers.addRangeSlider(layout); + helpers.addRangeSlider(state.layout); return dataOut; }; @@ -96,7 +93,15 @@ function makeTrace(traceIn, state, direction) { return traceOut; } -// let scatter hoverPoint format 'x' coordinates, if desired +// 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; diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js index 1ad087552ca..c6b6ee4bfb4 100644 --- a/test/jasmine/tests/finance_test.js +++ b/test/jasmine/tests/finance_test.js @@ -251,8 +251,6 @@ describe('finance charts defaults:', function() { var out0 = _supply([trace0], layout0); - expect(out0.layout.hovermode).toEqual('closest'); - expect(out0._fullLayout.hovermode).toEqual('closest'); expect(out0.layout.xaxis.rangeslider).toBeDefined(); expect(out0._fullLayout.xaxis.rangeslider.visible).toBe(true); From d26a4b428f11537ebcc0733be316111c1183dc83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 12 Oct 2016 13:27:42 -0400 Subject: [PATCH 28/32] ohlc: bump tickwidth dflt to 0.1 --- src/traces/ohlc/attributes.js | 2 +- test/jasmine/tests/finance_test.js | 20 +++++++++++--------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/traces/ohlc/attributes.js b/src/traces/ohlc/attributes.js index 81067f74e0c..d6da82d67ca 100644 --- a/src/traces/ohlc/attributes.js +++ b/src/traces/ohlc/attributes.js @@ -102,7 +102,7 @@ module.exports = { valType: 'number', min: 0, max: 0.5, - dflt: 0.05, + dflt: 0.1, role: 'style', description: [ 'Sets the width of the open/close tick marks', diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js index c6b6ee4bfb4..109c603fce1 100644 --- a/test/jasmine/tests/finance_test.js +++ b/test/jasmine/tests/finance_test.js @@ -304,16 +304,16 @@ describe('finance charts calc transforms:', function() { var out = _calc([trace0, trace1]); expect(out[0].x).toEqual([ - -0.05, 0, 0, 0, 0, 0.05, null, - 2.95, 3, 3, 3, 3, 3.05, null, - 4.95, 5, 5, 5, 5, 5.05, null, - 6.95, 7, 7, 7, 7, 7.05, null + -0.1, 0, 0, 0, 0, 0.1, null, + 2.9, 3, 3, 3, 3, 3.1, null, + 4.9, 5, 5, 5, 5, 5.1, null, + 6.9, 7, 7, 7, 7, 7.1, null ]); expect(out[1].x).toEqual([ - 0.95, 1, 1, 1, 1, 1.05, null, - 1.95, 2, 2, 2, 2, 2.05, null, - 3.95, 4, 4, 4, 4, 4.05, null, - 5.95, 6, 6, 6, 6, 6.05, null + 0.9, 1, 1, 1, 1, 1.1, null, + 1.9, 2, 2, 2, 2, 2.1, null, + 3.9, 4, 4, 4, 4, 4.1, null, + 5.9, 6, 6, 6, 6, 6.1, null ]); expect(out[2].x).toEqual([ 0, 0, 0, 0, 0, 0, @@ -384,6 +384,7 @@ describe('finance charts calc transforms:', function() { it('should work with *filter* transforms', function() { var trace0 = Lib.extendDeep({}, mock1, { type: 'ohlc', + tickwidth: 0.05, transforms: [{ type: 'filter', operation: '>', @@ -450,6 +451,7 @@ describe('finance charts calc transforms:', function() { var trace0 = Lib.extendDeep({}, mock1, { type: 'ohlc', + tickwidth: 0.05, transforms: [opts] }); @@ -561,7 +563,7 @@ describe('finance charts updates:', function() { var path0; Plotly.plot(gd, [trace0]).then(function() { - expect(gd.calcdata[0][0].x).toEqual(-0.05); + expect(gd.calcdata[0][0].x).toEqual(-0.1); expect(gd.calcdata[0][0].y).toEqual(33.01); return Plotly.restyle(gd, 'tickwidth', 0.5); From 38030055b7810df7491b781e6d7f62e21905be86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 12 Oct 2016 14:26:53 -0400 Subject: [PATCH 29/32] finance: add common 'line' style container - trace-wide 'line' style sets default for increasing / decreasing line styling --- src/traces/candlestick/attributes.js | 24 +++++++++++++++---- src/traces/candlestick/defaults.js | 10 ++++---- src/traces/candlestick/transform.js | 7 +----- src/traces/ohlc/attributes.js | 31 ++++++++++++++++++++---- src/traces/ohlc/defaults.js | 13 +++++++---- src/traces/ohlc/transform.js | 7 +----- test/jasmine/tests/finance_test.js | 35 ++++++++++++++++++++++++++++ 7 files changed, 96 insertions(+), 31 deletions(-) diff --git a/src/traces/candlestick/attributes.js b/src/traces/candlestick/attributes.js index c2fefb33782..64b7be83203 100644 --- a/src/traces/candlestick/attributes.js +++ b/src/traces/candlestick/attributes.js @@ -17,8 +17,11 @@ var directionAttrs = { name: OHLCattrs.increasing.name, showlegend: OHLCattrs.increasing.showlegend, - color: Lib.extendFlat({}, boxAttrs.line.color), - width: Lib.extendFlat({}, boxAttrs.line.width), + line: { + color: Lib.extendFlat({}, boxAttrs.line.color), + width: Lib.extendFlat({}, boxAttrs.line.width) + }, + fillcolor: Lib.extendFlat({}, boxAttrs.fillcolor), }; @@ -29,14 +32,25 @@ module.exports = { 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, { - color: { dflt: OHLCattrs.increasing.color.dflt } + line: { color: { dflt: OHLCattrs.increasing.line.color.dflt } } }), decreasing: Lib.extendDeep({}, directionAttrs, { - color: { dflt: OHLCattrs.decreasing.color.dflt } + line: { color: { dflt: OHLCattrs.decreasing.line.color.dflt } } }), text: OHLCattrs.text, - whiskerwidth: Lib.extendFlat({}, boxAttrs.whiskerwidth, { dflt: 0 }), + whiskerwidth: Lib.extendFlat({}, boxAttrs.whiskerwidth, { dflt: 0 }) }; diff --git a/src/traces/candlestick/defaults.js b/src/traces/candlestick/defaults.js index f00ce9e3a74..c6b4538ea53 100644 --- a/src/traces/candlestick/defaults.js +++ b/src/traces/candlestick/defaults.js @@ -28,17 +28,19 @@ module.exports = function supplyDefaults(traceIn, traceOut) { return; } - coerce('text'); - coerce('whiskerwidth'); + 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 + '.color'); - coerce(direction + '.width'); + coerce(direction + '.line.color'); + coerce(direction + '.line.width', traceOut.line.width); coerce(direction + '.fillcolor'); } diff --git a/src/traces/candlestick/transform.js b/src/traces/candlestick/transform.js index b1c237a9f45..6d32870e3ce 100644 --- a/src/traces/candlestick/transform.js +++ b/src/traces/candlestick/transform.js @@ -79,12 +79,7 @@ function makeTrace(traceIn, state, direction) { name: directionOpts.name, showlegend: directionOpts.showlegend, - - line: { - color: directionOpts.color, - width: directionOpts.width - }, - + line: directionOpts.line, fillcolor: directionOpts.fillcolor }); } diff --git a/src/traces/ohlc/attributes.js b/src/traces/ohlc/attributes.js index d6da82d67ca..93868cbb114 100644 --- a/src/traces/ohlc/attributes.js +++ b/src/traces/ohlc/attributes.js @@ -37,9 +37,11 @@ var directionAttrs = { ].join(' ') }, - color: Lib.extendFlat({}, lineAttrs.color), - width: Lib.extendFlat({}, lineAttrs.width), - dash: Lib.extendFlat({}, lineAttrs.dash), + line: { + color: Lib.extendFlat({}, lineAttrs.color), + width: Lib.extendFlat({}, lineAttrs.width), + dash: Lib.extendFlat({}, lineAttrs.dash), + } }; module.exports = { @@ -76,12 +78,31 @@ module.exports = { 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, { - color: { dflt: INCREASING_COLOR } + line: { color: { dflt: INCREASING_COLOR } } }), decreasing: Lib.extendDeep({}, directionAttrs, { - color: { dflt: DECREASING_COLOR } + line: { color: { dflt: DECREASING_COLOR } } }), text: { diff --git a/src/traces/ohlc/defaults.js b/src/traces/ohlc/defaults.js index d37f8c4ca6a..791535de251 100644 --- a/src/traces/ohlc/defaults.js +++ b/src/traces/ohlc/defaults.js @@ -28,17 +28,20 @@ module.exports = function supplyDefaults(traceIn, traceOut) { return; } - coerce('text'); - coerce('tickwidth'); + 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 + '.color'); - coerce(direction + '.width'); - coerce(direction + '.dash'); + coerce(direction + '.line.color'); + coerce(direction + '.line.width', traceOut.line.width); + coerce(direction + '.line.dash', traceOut.line.dash); } diff --git a/src/traces/ohlc/transform.js b/src/traces/ohlc/transform.js index 3b6e91f2be9..f7e0b959818 100644 --- a/src/traces/ohlc/transform.js +++ b/src/traces/ohlc/transform.js @@ -81,12 +81,7 @@ function makeTrace(traceIn, state, direction) { name: directionOpts.name, showlegend: directionOpts.showlegend, - - line: { - color: directionOpts.color, - width: directionOpts.width, - dash: directionOpts.dash - } + line: directionOpts.line }); } diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js index 109c603fce1..9d64f3a43a3 100644 --- a/test/jasmine/tests/finance_test.js +++ b/test/jasmine/tests/finance_test.js @@ -220,6 +220,41 @@ describe('finance charts defaults:', function() { ]); }); + 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', From facbf3d852e102a16234317743ff4135f4eabec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Wed, 12 Oct 2016 14:27:03 -0400 Subject: [PATCH 30/32] test: add finance mocks --- .../baselines/candlestick_double-y-axis.png | Bin 0 -> 79324 bytes test/image/baselines/finance_style.png | Bin 0 -> 20690 bytes test/image/baselines/ohlc_first.png | Bin 0 -> 41976 bytes .../mocks/candlestick_double-y-axis.json | 1973 +++++++++++++++++ test/image/mocks/finance_style.json | 228 ++ test/image/mocks/ohlc_first.json | 983 ++++++++ 6 files changed, 3184 insertions(+) create mode 100644 test/image/baselines/candlestick_double-y-axis.png create mode 100644 test/image/baselines/finance_style.png create mode 100644 test/image/baselines/ohlc_first.png create mode 100644 test/image/mocks/candlestick_double-y-axis.json create mode 100644 test/image/mocks/finance_style.json create mode 100644 test/image/mocks/ohlc_first.json 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 0000000000000000000000000000000000000000..8c6c223bc13d6056b1b1814f38a7771825823b40 GIT binary patch literal 79324 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

O#E~#l=T9cG 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 literal 0 HcmV?d00001 diff --git a/test/image/baselines/finance_style.png b/test/image/baselines/finance_style.png new file mode 100644 index 0000000000000000000000000000000000000000..6bf7d6fa7080290174fbefb24eab6aafeea5b818 GIT binary patch literal 20690 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 BrON;S literal 0 HcmV?d00001 diff --git a/test/image/baselines/ohlc_first.png b/test/image/baselines/ohlc_first.png new file mode 100644 index 0000000000000000000000000000000000000000..879c3b43118ea81d1193f94c037556a6632fc556 GIT binary patch literal 41976 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 A3jhEB literal 0 HcmV?d00001 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 + } +} From b98835c4e9b99f125bd4ff7b144d8680918b2cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 13 Oct 2016 09:44:18 -0400 Subject: [PATCH 31/32] ohlc: bump tickwidth dflt to 0.3 --- src/traces/ohlc/attributes.js | 2 +- test/image/baselines/ohlc_first.png | Bin 41976 -> 41002 bytes test/jasmine/tests/finance_test.js | 18 +++++++++--------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/traces/ohlc/attributes.js b/src/traces/ohlc/attributes.js index 93868cbb114..b4168829c97 100644 --- a/src/traces/ohlc/attributes.js +++ b/src/traces/ohlc/attributes.js @@ -123,7 +123,7 @@ module.exports = { valType: 'number', min: 0, max: 0.5, - dflt: 0.1, + dflt: 0.3, role: 'style', description: [ 'Sets the width of the open/close tick marks', diff --git a/test/image/baselines/ohlc_first.png b/test/image/baselines/ohlc_first.png index 879c3b43118ea81d1193f94c037556a6632fc556..4ce83c5aa83839fa9f1bcecbce6f2610beabeb08 100644 GIT binary patch literal 41002 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 IQ+X^S<8u`*ps~*LhBewx$a8PUfB4wr!(U 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 A3jhEB diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js index 9d64f3a43a3..d0aa516eade 100644 --- a/test/jasmine/tests/finance_test.js +++ b/test/jasmine/tests/finance_test.js @@ -339,16 +339,16 @@ describe('finance charts calc transforms:', function() { var out = _calc([trace0, trace1]); expect(out[0].x).toEqual([ - -0.1, 0, 0, 0, 0, 0.1, null, - 2.9, 3, 3, 3, 3, 3.1, null, - 4.9, 5, 5, 5, 5, 5.1, null, - 6.9, 7, 7, 7, 7, 7.1, null + -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.9, 1, 1, 1, 1, 1.1, null, - 1.9, 2, 2, 2, 2, 2.1, null, - 3.9, 4, 4, 4, 4, 4.1, null, - 5.9, 6, 6, 6, 6, 6.1, null + 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, @@ -598,7 +598,7 @@ describe('finance charts updates:', function() { var path0; Plotly.plot(gd, [trace0]).then(function() { - expect(gd.calcdata[0][0].x).toEqual(-0.1); + 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); From c9de6c80aca353429a8f153901b36244ca8a955a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20T=C3=A9treault-Pinard?= Date: Thu, 13 Oct 2016 09:44:30 -0400 Subject: [PATCH 32/32] test: add finance trace module bundle test --- test/jasmine/bundle_tests/finance_test.js | 41 +++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 test/jasmine/bundle_tests/finance_test.js 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(); + }); + + }); +});