Skip to content

OHLC and Candlestick trace types #1020

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 32 commits into from
Oct 13, 2016
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e915a29
relax requirement for re-calc
etpinard Oct 7, 2016
cf5da9b
plots: pass user layout to transforms
etpinard Oct 7, 2016
7864fa8
first pass ohlc trace type
etpinard Sep 9, 2016
44f2bec
first pass candlestick trace type
etpinard Sep 9, 2016
df24552
add ohlc and candlestick to main plotly.js lib index
etpinard Sep 9, 2016
6cac3fe
[maybe?] add 'finance' partial bundle
etpinard Sep 9, 2016
0f17423
fixup user-defined transform test
etpinard Oct 7, 2016
61c72cc
finance: replace attribute 't' -> 'x'
etpinard Oct 7, 2016
a9eb7c5
candlestick: rename 'tickwidth' -> 'whiskerwidth' + make it 1 per trace
etpinard Oct 7, 2016
ede8ee2
ohlc: make 'tickwidth' 1 per trace
etpinard Oct 7, 2016
f6c33f2
finance: 2nd iteration
etpinard Oct 7, 2016
96e129a
fix groupby when `enabled: false
etpinard Oct 7, 2016
e32b60f
findArrayAttributes: include array attribute in fullInput module
etpinard Oct 7, 2016
d61afb9
test: first pass finance suite
etpinard Oct 7, 2016
dc8aaea
finace: ensure supplyDefaults is idempotent
etpinard Oct 11, 2016
5c94c05
finance: add re-calc attributes to restyle lists
etpinard Oct 11, 2016
1f574f2
finance: ensure that restyling visible works
etpinard Oct 11, 2016
7b19b74
finance: improve dflt colors
etpinard Oct 11, 2016
2b1918c
finance: improve inc / dec 'name' / 'showlegend' logic
etpinard Oct 11, 2016
fbb299b
legend: add logic for 'ohlc' and 'candlestick' in legend name edits
etpinard Oct 11, 2016
caf390b
doc: add comments about non-trivial logic in ohlc / candlestick
etpinard Oct 11, 2016
672d517
finance: pass trace 'xaxis' & 'yaxis' to generated traces
etpinard Oct 12, 2016
ea3c5a1
finance: make sure computed tickWidth is always positive
etpinard Oct 12, 2016
a892b26
olhc: add custom hover text
etpinard Oct 12, 2016
7e0a382
finance: ensure that user data isn't mutated in Plots.supplyDefaults
etpinard Oct 12, 2016
f6f072f
utils: add filterUnique helper
etpinard Oct 12, 2016
6036045
ohlc: don't force hovermode: 'closest'
etpinard Oct 12, 2016
d26a4b4
ohlc: bump tickwidth dflt to 0.1
etpinard Oct 12, 2016
3803005
finance: add common 'line' style container
etpinard Oct 12, 2016
facbf3d
test: add finance mocks
etpinard Oct 12, 2016
b98835c
ohlc: bump tickwidth dflt to 0.3
etpinard Oct 13, 2016
c9de6c8
test: add finance trace module bundle test
etpinard Oct 13, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions lib/candlestick.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Copyright 2012-2016, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

module.exports = require('../src/traces/candlestick');
21 changes: 21 additions & 0 deletions lib/index-finance.js
Original file line number Diff line number Diff line change
@@ -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')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mdtusz @rreusser thoughts on this one?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

]);

module.exports = Plotly;
11 changes: 9 additions & 2 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions lib/ohlc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/**
* Copyright 2012-2016, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

'use strict';

module.exports = require('../src/traces/ohlc');
20 changes: 19 additions & 1 deletion src/components/legend/draw.js
Original file line number Diff line number Diff line change
Expand Up @@ -367,10 +367,28 @@ function drawTexts(g, gd) {
.call(textLayout)
.on('edit', function(text) {
this.attr({'data-unformatted': text});

this.text(text)
.call(textLayout);

if(!this.text()) text = ' \u0020\u0020 ';
Plotly.restyle(gd, 'name', text, traceIndex);

var fullInput = legendItem.trace._fullInput || {},
astr;

// N.B. this block isn't super clean,
// is unfortunately untested at the moment,
// and only works for for 'ohlc' and 'candlestick',
// but should be generalized for other one-to-many transforms
if(['ohlc', 'candlestick'].indexOf(fullInput.type) !== -1) {
var transforms = legendItem.trace.transforms,
direction = transforms[transforms.length - 1].direction;

astr = direction + '.legenditem.name';
}
else astr = 'name';

Plotly.restyle(gd, astr, text, traceIndex);
});
}
else text.call(textLayout);
Expand Down
14 changes: 14 additions & 0 deletions src/lib/coerce.js
Original file line number Diff line number Diff line change
Expand Up @@ -457,5 +457,19 @@ exports.findArrayAttributes = function(trace) {

exports.crawl(trace._module.attributes, callback);

// Look into the fullInput module attributes for array attributes
// to make sure that 'custom' array attributes are detected.
//
// At the moment, we need this block to make sure that
// ohlc and candlestick 'open', 'high', 'low', 'close' can be
// used with filter ang groupby transforms.
if(trace._fullInput) {
exports.crawl(trace._fullInput._module.attributes, callback);

arrayAttributes = arrayAttributes.filter(function(g, i, self) {
return self.indexOf(g) === i;
});
}

return arrayAttributes;
};
6 changes: 4 additions & 2 deletions src/plot_api/plot_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand Down
19 changes: 10 additions & 9 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand Down Expand Up @@ -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];

Expand All @@ -830,6 +830,7 @@ function applyTransforms(fullTrace, fullData, layout) {
fullTrace: fullTrace,
fullData: fullData,
layout: layout,
fullLayout: fullLayout,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sweet I was going to ask for this :) Now I can grab the axis.type

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact this might allow the whole Streambed Plotly.js build to stop requiring special Axes API.

transformIndex: i
});
}
Expand Down
42 changes: 42 additions & 0 deletions src/traces/candlestick/attributes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Copyright 2012-2016, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/


'use strict';

var Lib = require('../../lib');
var OHLCattrs = require('../ohlc/attributes');
var boxAttrs = require('../box/attributes');

var directionAttrs = {
name: OHLCattrs.increasing.name,
showlegend: OHLCattrs.increasing.showlegend,

color: Lib.extendFlat({}, boxAttrs.line.color),
width: Lib.extendFlat({}, boxAttrs.line.width),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I probably should have noticed this earlier (sorry!) but color and width seem odd for line color and width. It took me a bit to figure out that this wasn't the width of the candle, and that we don't need an attribute for that because layout.boxgap does that... This should probably be mentioned somewhere!

Also just like whiskerwidth I'd think width is a property shared by both directions. Can we call these linecolor (in directionAttrs) and linewidth (in attributes)? I guess we could argue that for consistency these should go inside a line container instead... it seems a bit funny to make a one-member container just to keep the names consistent but maybe that will actually be easier and clearer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we call these linecolor (in directionAttrs) and linewidth (in attributes)?

sounds good.

it seems a bit funny to make a one-member container

Agreed. I much prefer increasing.linecolor and decreasing.lincolor

fillcolor: Lib.extendFlat({}, boxAttrs.fillcolor),
};

module.exports = {
x: OHLCattrs.x,
open: OHLCattrs.open,
high: OHLCattrs.high,
low: OHLCattrs.low,
close: OHLCattrs.close,

increasing: Lib.extendDeep({}, directionAttrs, {
color: { dflt: OHLCattrs.increasing.color.dflt }
}),

decreasing: Lib.extendDeep({}, directionAttrs, {
color: { dflt: OHLCattrs.decreasing.color.dflt }
}),

text: OHLCattrs.text,
whiskerwidth: Lib.extendFlat({}, boxAttrs.whiskerwidth, { dflt: 0 }),
};
44 changes: 44 additions & 0 deletions src/traces/candlestick/defaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* Copyright 2012-2016, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/


'use strict';

var Lib = require('../../lib');
var handleOHLC = require('../ohlc/ohlc_defaults');
var handleDirectionDefaults = require('../ohlc/direction_defaults');
var helpers = require('../ohlc/helpers');
var attributes = require('./attributes');

module.exports = function supplyDefaults(traceIn, traceOut) {
helpers.pushDummyTransformOpts(traceIn, traceOut);

function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}

var len = handleOHLC(traceIn, traceOut, coerce);
if(len === 0) {
traceOut.visible = false;
return;
}

coerce('text');
coerce('whiskerwidth');

handleDirection(traceIn, traceOut, coerce, 'increasing');
handleDirection(traceIn, traceOut, coerce, 'decreasing');
};

function handleDirection(traceIn, traceOut, coerce, direction) {
handleDirectionDefaults(traceIn, traceOut, coerce, direction);

coerce(direction + '.color');
coerce(direction + '.width');
coerce(direction + '.fillcolor');
}
40 changes: 40 additions & 0 deletions src/traces/candlestick/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Copyright 2012-2016, Plotly, Inc.
* All rights reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/


'use strict';

var register = require('../../plot_api/register');

module.exports = {
moduleType: 'trace',
name: 'candlestick',
basePlotModule: require('../../plots/cartesian'),
categories: ['cartesian', 'showLegend'],
meta: {
description: [
'The candlestick is a style of financial chart describing',
'open, high, low and close for a given `x` coordinate (most likely time).',

'The boxes represent the spread between the `open` and `close` values and',
'the lines represent the spread between the `low` and `high` values',

'Sample points where the close value is higher (lower) then the open',
'value are called increasing (decreasing).',

'By default, increasing candles are drawn in green whereas',
'decreasing are drawn in red.'
].join(' ')
},

attributes: require('./attributes'),
supplyDefaults: require('./defaults'),
};

register(require('../box'));
register(require('./transform'));
Loading