Skip to content

Commit 84c36a9

Browse files
committed
refactor OHLC into a first-class trace type
1 parent e979cf0 commit 84c36a9

26 files changed

+382
-588
lines changed

src/components/legend/draw.js

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -384,20 +384,10 @@ function drawTexts(g, gd) {
384384

385385
if(!this.text()) text = ' \u0020\u0020 ';
386386

387-
var transforms, direction;
388387
var fullInput = legendItem.trace._fullInput || {};
389388
var update = {};
390389

391-
// N.B. this block isn't super clean,
392-
// is unfortunately untested at the moment,
393-
// and only works for for 'ohlc',
394-
// but should be generalized for other one-to-many transforms
395-
if(fullInput.type === 'ohlc') {
396-
transforms = legendItem.trace.transforms;
397-
direction = transforms[transforms.length - 1].direction;
398-
399-
update[direction + '.name'] = text;
400-
} else if(Registry.hasTransform(fullInput, 'groupby')) {
390+
if(Registry.hasTransform(fullInput, 'groupby')) {
401391
var groupbyIndices = Registry.getTransformIndices(fullInput, 'groupby');
402392
var index = groupbyIndices[groupbyIndices.length - 1];
403393

src/components/legend/style.js

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ module.exports = function style(s, gd) {
5353
.each(stylePies)
5454
.each(styleLines)
5555
.each(stylePoints)
56-
.each(styleCandles);
56+
.each(styleCandles)
57+
.each(styleOHLC);
5758

5859
function styleLines(d) {
5960
var trace = d[0].trace;
@@ -217,9 +218,8 @@ module.exports = function style(s, gd) {
217218
var trace = d[0].trace,
218219
pts = d3.select(this).select('g.legendpoints')
219220
.selectAll('path.legendcandle')
220-
.data(Registry.traceIs(trace, 'candlestick') && trace.visible ? [d, d] : []);
221+
.data(trace.type === 'candlestick' && trace.visible ? [d, d] : []);
221222
pts.enter().append('path').classed('legendcandle', true)
222-
// if we want the median bar, prepend M6,0H-6
223223
.attr('d', function(_, i) {
224224
if(i) return 'M-15,0H-8M-8,6V-6H8Z'; // increasing
225225
return 'M15,0H8M8,-6V6H-8Z'; // decreasing
@@ -241,6 +241,33 @@ module.exports = function style(s, gd) {
241241
});
242242
}
243243

244+
function styleOHLC(d) {
245+
var trace = d[0].trace,
246+
pts = d3.select(this).select('g.legendpoints')
247+
.selectAll('path.legendohlc')
248+
.data(trace.type === 'ohlc' && trace.visible ? [d, d] : []);
249+
pts.enter().append('path').classed('legendohlc', true)
250+
.attr('d', function(_, i) {
251+
if(i) return 'M-15,0H0M-8,-6V0'; // increasing
252+
return 'M15,0H0M8,6V0'; // decreasing
253+
})
254+
.attr('transform', 'translate(20,0)')
255+
.style('stroke-miterlimit', 1);
256+
pts.exit().remove();
257+
pts.each(function(_, i) {
258+
var container = trace[i ? 'increasing' : 'decreasing'];
259+
var w = container.line.width,
260+
p = d3.select(this);
261+
262+
p.style('fill', 'none')
263+
.call(Drawing.dashLine, container.line.dash, w);
264+
265+
if(w) {
266+
Color.stroke(p, container.line.color);
267+
}
268+
});
269+
}
270+
244271
function stylePies(d) {
245272
var trace = d[0].trace,
246273
pts = d3.select(this).select('g.legendpoints')

src/components/rangeslider/draw.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,8 @@ function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
455455
id: id,
456456
plotgroup: plotgroup,
457457
xaxis: xa,
458-
yaxis: ya
458+
yaxis: ya,
459+
isRangePlot: true
459460
};
460461

461462
if(isMainPlot) mainplotinfo = plotinfo;

src/plot_api/helpers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ exports.cleanData = function(data, existingData) {
331331
}
332332

333333
// fixes from converting finance from transforms to real trace types
334-
if(trace.type === 'candlestick') {
334+
if(trace.type === 'candlestick' || trace.type === 'ohlc') {
335335
var increasingName = cleanFinanceDir(trace.increasing);
336336
var decreasingName = cleanFinanceDir(trace.decreasing);
337337

src/plots/cartesian/constants.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ module.exports = {
6868
'carpetlayer',
6969
'violinlayer',
7070
'boxlayer',
71+
'ohlclayer',
7172
'scatterlayer'
7273
],
7374

src/plots/cartesian/index.js

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback
203203
// remaining plot traces should also be able to do this. Once implemented,
204204
// we won't need this - which should sometimes be a big speedup.
205205
if(plotinfo.plot) {
206-
plotinfo.plot.selectAll('g:not(.scatterlayer)').selectAll('g.trace').remove();
206+
plotinfo.plot.selectAll('g:not(.scatterlayer):not(.ohlclayer)').selectAll('g.trace').remove();
207207
}
208208

209209
// plot all traces for each module at once
@@ -224,43 +224,50 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
224224
var oldModules = oldFullLayout._modules || [],
225225
newModules = newFullLayout._modules || [];
226226

227-
var hadScatter, hasScatter, hadGl, hasGl, i, oldPlots, ids, subplotInfo, moduleName;
227+
var hadScatter, hasScatter, hadOHLC, hasOHLC, hadGl, hasGl, i, oldPlots, ids, subplotInfo, moduleName;
228228

229229

230230
for(i = 0; i < oldModules.length; i++) {
231231
moduleName = oldModules[i].name;
232232
if(moduleName === 'scatter') hadScatter = true;
233233
else if(moduleName === 'scattergl') hadGl = true;
234+
else if(moduleName === 'ohlc') hadOHLC = true;
234235
}
235236

236237
for(i = 0; i < newModules.length; i++) {
237238
moduleName = newModules[i].name;
238239
if(moduleName === 'scatter') hasScatter = true;
239240
else if(moduleName === 'scattergl') hasGl = true;
241+
else if(moduleName === 'ohlc') hasOHLC = true;
240242
}
241243

242-
if(hadScatter && !hasScatter) {
243-
oldPlots = oldFullLayout._plots;
244+
oldPlots = oldFullLayout._plots;
245+
var layersToEmpty = [];
246+
if(hadScatter && !hasScatter) layersToEmpty.push('g.scatterlayer');
247+
if(hadOHLC && !hasOHLC) layersToEmpty.push('g.ohlclayer');
248+
249+
if(layersToEmpty.length) {
244250
ids = Object.keys(oldPlots || {});
245251

246-
for(i = 0; i < ids.length; i++) {
247-
subplotInfo = oldPlots[ids[i]];
252+
for(var layeri = 0; layeri < layersToEmpty.length; layeri++) {
253+
for(i = 0; i < ids.length; i++) {
254+
subplotInfo = oldPlots[ids[i]];
248255

249-
if(subplotInfo.plot) {
250-
subplotInfo.plot.select('g.scatterlayer')
251-
.selectAll('g.trace')
252-
.remove();
256+
if(subplotInfo.plot) {
257+
subplotInfo.plot.select(layersToEmpty[layeri])
258+
.selectAll('g.trace')
259+
.remove();
260+
}
253261
}
254-
}
255262

256-
oldFullLayout._infolayer.selectAll('g.rangeslider-container')
257-
.select('g.scatterlayer')
258-
.selectAll('g.trace')
259-
.remove();
263+
oldFullLayout._infolayer.selectAll('g.rangeslider-container')
264+
.select(layersToEmpty[layeri])
265+
.selectAll('g.trace')
266+
.remove();
267+
}
260268
}
261269

262270
if(hadGl && !hasGl) {
263-
oldPlots = oldFullLayout._plots;
264271
ids = Object.keys(oldPlots || {});
265272

266273
for(i = 0; i < ids.length; i++) {

src/traces/bar/plot.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,11 @@ module.exports = function plot(gd, plotinfo, cdbar) {
4242
bartraces.enter().append('g')
4343
.attr('class', 'trace bars');
4444

45-
bartraces.each(function(d) {
46-
d[0].node3 = d3.select(this);
47-
});
45+
if(!plotinfo.isRangePlot) {
46+
bartraces.each(function(d) {
47+
d[0].node3 = d3.select(this);
48+
});
49+
}
4850

4951
bartraces.append('g')
5052
.attr('class', 'points')

src/traces/box/plot.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ function plot(gd, plotinfo, cdbox) {
3232
var cd0 = d[0];
3333
var t = cd0.t;
3434
var trace = cd0.trace;
35-
var sel = cd0.node3 = d3.select(this);
35+
var sel = d3.select(this);
36+
if(!plotinfo.isRangePlot) cd0.node3 = sel;
3637
var numBoxes = fullLayout._numBoxes;
3738

3839
var groupFraction = (1 - fullLayout.boxgap);
@@ -46,7 +47,7 @@ function plot(gd, plotinfo, cdbox) {
4647
var wdPos = bdPos * trace.whiskerwidth;
4748

4849
if(trace.visible !== true || t.empty) {
49-
d3.select(this).remove();
50+
sel.remove();
5051
return;
5152
}
5253

src/traces/box/style.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ module.exports = function style(gd, cd) {
3333
if(trace.type === 'candlestick') {
3434
allBoxes.each(function(boxData) {
3535
var thisBox = d3.select(this);
36-
var container = trace[boxData.candle]; // candle = 'increasing' or 'decreasing'
36+
var container = trace[boxData.dir]; // dir = 'increasing' or 'decreasing'
3737
styleBox(thisBox, container.line.width, container.line.color, container.fillcolor);
3838
// TODO: custom selection style for candlesticks
3939
thisBox.style('opacity', trace.selectedpoints && !boxData.selected ? 0.3 : 1);

src/traces/candlestick/calc.js

Lines changed: 15 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -9,69 +9,40 @@
99
'use strict';
1010

1111
var Lib = require('../../lib');
12-
var _ = Lib._;
1312
var Axes = require('../../plots/cartesian/axes');
1413

14+
var calcCommon = require('../ohlc/calc').calcCommon;
15+
1516
module.exports = function(gd, trace) {
1617
var fullLayout = gd._fullLayout;
1718
var xa = Axes.getFromId(gd, trace.xaxis);
1819
var ya = Axes.getFromId(gd, trace.yaxis);
19-
var cd = [];
2020

2121
var x = xa.makeCalcdata(trace, 'x');
22-
var o = ya.makeCalcdata(trace, 'open');
23-
var h = ya.makeCalcdata(trace, 'high');
24-
var l = ya.makeCalcdata(trace, 'low');
25-
var c = ya.makeCalcdata(trace, 'close');
26-
27-
var hasTextArray = Array.isArray(trace.text);
28-
29-
for(var i = 0; i < x.length; i++) {
30-
var xi = x[i];
31-
var oi = o[i];
32-
var hi = h[i];
33-
var li = l[i];
34-
var ci = c[i];
35-
36-
if(xi !== undefined && oi !== undefined && hi !== undefined && li !== undefined && ci !== undefined) {
37-
var increasing = ci >= oi;
38-
39-
var pt = {
40-
pos: xi,
41-
min: li,
42-
q1: increasing ? oi : ci,
43-
med: ci,
44-
q3: increasing ? ci : oi,
45-
max: hi,
46-
i: i,
47-
candle: increasing ? 'increasing' : 'decreasing'
48-
};
4922

50-
if(hasTextArray) pt.tx = trace.text[i];
51-
52-
cd.push(pt);
53-
}
54-
}
55-
56-
Axes.expand(ya, l.concat(h), {padded: true});
23+
var cd = calcCommon(gd, trace, x, ya, ptFunc);
5724

5825
if(cd.length) {
59-
cd[0].t = {
26+
Lib.extendFlat(cd[0].t, {
6027
num: fullLayout._numBoxes,
6128
dPos: Lib.distinctVals(x).minDiff / 2,
6229
posLetter: 'x',
6330
valLetter: 'y',
64-
labels: {
65-
open: _(gd, 'open:') + ' ',
66-
high: _(gd, 'high:') + ' ',
67-
low: _(gd, 'low:') + ' ',
68-
close: _(gd, 'close:') + ' '
69-
}
70-
};
31+
});
7132

7233
fullLayout._numBoxes++;
7334
return cd;
7435
} else {
7536
return [{t: {empty: true}}];
7637
}
7738
};
39+
40+
function ptFunc(o, h, l, c) {
41+
return {
42+
min: l,
43+
q1: Math.min(o, c),
44+
med: c,
45+
q3: Math.max(o, c),
46+
max: h,
47+
};
48+
}

src/traces/candlestick/defaults.js

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
2525
return;
2626
}
2727

28-
// TODO: get rid of _inputLength and do this in ohlc_defaults once that's not a transform either
29-
traceOut._length = len;
30-
3128
coerce('line.width');
3229

3330
handleDirection(traceIn, traceOut, coerce, 'increasing');

src/traces/candlestick/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,6 @@ module.exports = {
3737
calc: require('./calc'),
3838
plot: require('../box/plot').plot,
3939
style: require('../box/style'),
40-
hoverPoints: require('./hover'),
41-
selectPoints: require('./select')
40+
hoverPoints: require('../ohlc/hover'),
41+
selectPoints: require('../ohlc/select')
4242
};

src/traces/ohlc/attributes.js

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,6 @@ var lineAttrs = scatterAttrs.line;
2020

2121
function directionAttrs(lineColorDefault) {
2222
return {
23-
name: {
24-
valType: 'string',
25-
role: 'info',
26-
editType: 'style',
27-
description: [
28-
'Sets the segment name.',
29-
'The segment name appear as the legend item and on hover.'
30-
].join(' ')
31-
},
32-
33-
showlegend: {
34-
valType: 'boolean',
35-
role: 'info',
36-
dflt: true,
37-
editType: 'style',
38-
description: [
39-
'Determines whether or not an item corresponding to this',
40-
'segment is shown in the legend.'
41-
].join(' ')
42-
},
43-
4423
line: {
4524
color: extendFlat({}, lineAttrs.color, {dflt: lineColorDefault}),
4625
width: lineAttrs.width,

0 commit comments

Comments
 (0)