Skip to content

Commit 2a628df

Browse files
authored
Merge pull request #2561 from plotly/finance-refactor
Finance refactor
2 parents 7bacbd0 + 13204a9 commit 2a628df

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+1478
-1214
lines changed

src/components/fx/hover.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -826,13 +826,6 @@ function createHoverText(hoverData, opts, gd) {
826826
}
827827
}
828828

829-
// used by other modules (initially just ternary) that
830-
// manage their own hoverinfo independent of cleanPoint
831-
// the rest of this will still apply, so such modules
832-
// can still put things in (x|y|z)Label, text, and name
833-
// and hoverinfo will still determine their visibility
834-
if(d.extraText !== undefined) text += d.extraText;
835-
836829
if(d.zLabel !== undefined) {
837830
if(d.xLabel !== undefined) text += 'x: ' + d.xLabel + '<br>';
838831
if(d.yLabel !== undefined) text += 'y: ' + d.yLabel + '<br>';
@@ -851,6 +844,13 @@ function createHoverText(hoverData, opts, gd) {
851844
text += (text ? '<br>' : '') + d.text;
852845
}
853846

847+
// used by other modules (initially just ternary) that
848+
// manage their own hoverinfo independent of cleanPoint
849+
// the rest of this will still apply, so such modules
850+
// can still put things in (x|y|z)Label, text, and name
851+
// and hoverinfo will still determine their visibility
852+
if(d.extraText !== undefined) text += (text ? '<br>' : '') + d.extraText;
853+
854854
// if 'text' is empty at this point,
855855
// put 'name' in main label and don't show secondary label
856856
if(text === '') {

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' and 'candlestick',
394-
// but should be generalized for other one-to-many transforms
395-
if(['ohlc', 'candlestick'].indexOf(fullInput.type) !== -1) {
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: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ module.exports = function style(s, gd) {
5252
.each(styleBoxes)
5353
.each(stylePies)
5454
.each(styleLines)
55-
.each(stylePoints);
55+
.each(stylePoints)
56+
.each(styleCandles)
57+
.each(styleOHLC);
5658

5759
function styleLines(d) {
5860
var trace = d[0].trace;
@@ -207,7 +209,61 @@ module.exports = function style(s, gd) {
207209
.call(Color.fill, trace.fillcolor);
208210

209211
if(w) {
210-
p.call(Color.stroke, trace.line.color);
212+
Color.stroke(p, trace.line.color);
213+
}
214+
});
215+
}
216+
217+
function styleCandles(d) {
218+
var trace = d[0].trace,
219+
pts = d3.select(this).select('g.legendpoints')
220+
.selectAll('path.legendcandle')
221+
.data(trace.type === 'candlestick' && trace.visible ? [d, d] : []);
222+
pts.enter().append('path').classed('legendcandle', true)
223+
.attr('d', function(_, i) {
224+
if(i) return 'M-15,0H-8M-8,6V-6H8Z'; // increasing
225+
return 'M15,0H8M8,-6V6H-8Z'; // decreasing
226+
})
227+
.attr('transform', 'translate(20,0)')
228+
.style('stroke-miterlimit', 1);
229+
pts.exit().remove();
230+
pts.each(function(_, i) {
231+
var container = trace[i ? 'increasing' : 'decreasing'];
232+
var w = container.line.width,
233+
p = d3.select(this);
234+
235+
p.style('stroke-width', w + 'px')
236+
.call(Color.fill, container.fillcolor);
237+
238+
if(w) {
239+
Color.stroke(p, container.line.color);
240+
}
241+
});
242+
}
243+
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);
211267
}
212268
});
213269
}

src/components/rangeslider/defaults.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,18 @@ var oppAxisAttrs = require('./oppaxis_attributes');
1414
var axisIds = require('../../plots/cartesian/axis_ids');
1515

1616
module.exports = function handleDefaults(layoutIn, layoutOut, axName) {
17-
if(!layoutIn[axName].rangeslider) return;
17+
var axIn = layoutIn[axName];
18+
var axOut = layoutOut[axName];
19+
20+
if(!(axIn.rangeslider || layoutOut._requestRangeslider[axOut._id])) return;
1821

1922
// not super proud of this (maybe store _ in axis object instead
20-
if(!Lib.isPlainObject(layoutIn[axName].rangeslider)) {
21-
layoutIn[axName].rangeslider = {};
23+
if(!Lib.isPlainObject(axIn.rangeslider)) {
24+
axIn.rangeslider = {};
2225
}
2326

24-
var containerIn = layoutIn[axName].rangeslider,
25-
axOut = layoutOut[axName],
26-
containerOut = axOut.rangeslider = {};
27+
var containerIn = axIn.rangeslider;
28+
var containerOut = axOut.rangeslider = {};
2729

2830
function coerce(attr, dflt) {
2931
return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);

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/lib/coerce.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,9 +434,7 @@ exports.coerceFont = function(coerce, attr, dfltObj) {
434434
*/
435435
exports.coerceHoverinfo = function(traceIn, traceOut, layoutOut) {
436436
var moduleAttrs = traceOut._module.attributes;
437-
var attrs = moduleAttrs.hoverinfo ?
438-
{hoverinfo: moduleAttrs.hoverinfo} :
439-
baseTraceAttrs;
437+
var attrs = moduleAttrs.hoverinfo ? moduleAttrs : baseTraceAttrs;
440438

441439
var valObj = attrs.hoverinfo;
442440
var dflt;

src/plot_api/helpers.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,32 @@ exports.cleanData = function(data, existingData) {
330330
}
331331
}
332332

333+
// fixes from converting finance from transforms to real trace types
334+
if(trace.type === 'candlestick' || trace.type === 'ohlc') {
335+
var increasingShowlegend = (trace.increasing || {}).showlegend !== false;
336+
var decreasingShowlegend = (trace.decreasing || {}).showlegend !== false;
337+
var increasingName = cleanFinanceDir(trace.increasing);
338+
var decreasingName = cleanFinanceDir(trace.decreasing);
339+
340+
// now figure out something smart to do with the separate direction
341+
// names we removed
342+
if((increasingName !== false) && (decreasingName !== false)) {
343+
// both sub-names existed: base name previously had no effect
344+
// so ignore it and try to find a shared part of the sub-names
345+
346+
var newName = commonPrefix(
347+
increasingName, decreasingName,
348+
increasingShowlegend, decreasingShowlegend
349+
);
350+
// if no common part, leave whatever name was (or wasn't) there
351+
if(newName) trace.name = newName;
352+
}
353+
else if((increasingName || decreasingName) && !trace.name) {
354+
// one sub-name existed but not the base name - just use the sub-name
355+
trace.name = increasingName || decreasingName;
356+
}
357+
}
358+
333359
// transforms backward compatibility fixes
334360
if(Array.isArray(trace.transforms)) {
335361
var transforms = trace.transforms;
@@ -388,6 +414,38 @@ exports.cleanData = function(data, existingData) {
388414
}
389415
};
390416

417+
function cleanFinanceDir(dirContainer) {
418+
if(!Lib.isPlainObject(dirContainer)) return false;
419+
420+
var dirName = dirContainer.name;
421+
422+
delete dirContainer.name;
423+
delete dirContainer.showlegend;
424+
425+
return (typeof dirName === 'string' || typeof dirName === 'number') && String(dirName);
426+
}
427+
428+
function commonPrefix(name1, name2, show1, show2) {
429+
// if only one is shown in the legend, use that
430+
if(show1 && !show2) return name1;
431+
if(show2 && !show1) return name2;
432+
433+
// if both or neither are in the legend, check if one is blank (or whitespace)
434+
// and use the other one
435+
// note that hover labels can still use the name even if the legend doesn't
436+
if(!name1.trim()) return name2;
437+
if(!name2.trim()) return name1;
438+
439+
var minLen = Math.min(name1.length, name2.length);
440+
var i;
441+
for(i = 0; i < minLen; i++) {
442+
if(name1.charAt(i) !== name2.charAt(i)) break;
443+
}
444+
445+
var out = name1.substr(0, i);
446+
return out.trim();
447+
}
448+
391449
// textposition - support partial attributes (ie just 'top')
392450
// and incorrect use of middle / center etc.
393451
function cleanTextPosition(textposition) {

src/plot_api/plot_schema.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -256,12 +256,13 @@ exports.getTraceValObject = function(trace, parts) {
256256
var moduleAttrs, valObject;
257257

258258
if(head === 'transforms') {
259-
if(!Array.isArray(trace.transforms)) return false;
259+
var transforms = trace.transforms;
260+
if(!Array.isArray(transforms) || !transforms.length) return false;
260261
var tNum = parts[1];
261-
if(!isIndex(tNum) || tNum >= trace.transforms.length) {
262+
if(!isIndex(tNum) || tNum >= transforms.length) {
262263
return false;
263264
}
264-
moduleAttrs = (Registry.transformsRegistry[trace.transforms[tNum].type] || {}).attributes;
265+
moduleAttrs = (Registry.transformsRegistry[transforms[tNum].type] || {}).attributes;
265266
valObject = moduleAttrs && moduleAttrs[parts[2]];
266267
i = 3; // start recursing only inside the transform
267268
}

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: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -192,20 +192,24 @@ function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback
192192
// remaining plot traces should also be able to do this. Once implemented,
193193
// we won't need this - which should sometimes be a big speedup.
194194
if(plotinfo.plot) {
195-
plotinfo.plot.selectAll('g:not(.scatterlayer)').selectAll('g.trace').remove();
195+
plotinfo.plot.selectAll('g:not(.scatterlayer):not(.ohlclayer)').selectAll('g.trace').remove();
196196
}
197197

198198
// plot all traces for each module at once
199199
for(var j = 0; j < modules.length; j++) {
200200
var _module = modules[j];
201201

202202
// skip over non-cartesian trace modules
203-
if(_module.basePlotModule.name !== 'cartesian') continue;
203+
if(!_module.plot || _module.basePlotModule.name !== 'cartesian') continue;
204204

205205
// plot all traces of this type on this subplot at once
206-
var cdModule = getModuleCalcData(cdSubplot, _module);
206+
var cdModuleAndOthers = getModuleCalcData(cdSubplot, _module);
207+
var cdModule = cdModuleAndOthers[0];
208+
// don't need to search the found traces again - in fact we need to NOT
209+
// so that if two modules share the same plotter we don't double-plot
210+
cdSubplot = cdModuleAndOthers[1];
207211

208-
if(_module.plot) _module.plot(gd, plotinfo, cdModule, transitionOpts, makeOnCompleteCallback);
212+
_module.plot(gd, plotinfo, cdModule, transitionOpts, makeOnCompleteCallback);
209213
}
210214
}
211215

@@ -215,6 +219,7 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
215219
var oldPlots = oldFullLayout._plots || {};
216220

217221
var hadScatter, hasScatter;
222+
var hadOHLC, hasOHLC;
218223
var hadGl, hasGl;
219224
var i, k, subplotInfo, moduleName;
220225

@@ -232,28 +237,36 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
232237
moduleName = oldModules[i].name;
233238
if(moduleName === 'scatter') hadScatter = true;
234239
else if(moduleName === 'scattergl') hadGl = true;
240+
else if(moduleName === 'ohlc') hadOHLC = true;
235241
}
236242

237243
for(i = 0; i < newModules.length; i++) {
238244
moduleName = newModules[i].name;
239245
if(moduleName === 'scatter') hasScatter = true;
240246
else if(moduleName === 'scattergl') hasGl = true;
247+
else if(moduleName === 'ohlc') hasOHLC = true;
241248
}
242249

243-
if(hadScatter && !hasScatter) {
244-
for(k in oldPlots) {
245-
subplotInfo = oldPlots[k];
246-
if(subplotInfo.plot) {
247-
subplotInfo.plot.select('g.scatterlayer')
248-
.selectAll('g.trace')
249-
.remove();
250+
var layersToEmpty = [];
251+
if(hadScatter && !hasScatter) layersToEmpty.push('g.scatterlayer');
252+
if(hadOHLC && !hasOHLC) layersToEmpty.push('g.ohlclayer');
253+
254+
if(layersToEmpty.length) {
255+
for(var layeri = 0; layeri < layersToEmpty.length; layeri++) {
256+
for(k in oldPlots) {
257+
subplotInfo = oldPlots[k];
258+
if(subplotInfo.plot) {
259+
subplotInfo.plot.select(layersToEmpty[layeri])
260+
.selectAll('g.trace')
261+
.remove();
262+
}
250263
}
251-
}
252264

253-
oldFullLayout._infolayer.selectAll('g.rangeslider-container')
254-
.select('g.scatterlayer')
255-
.selectAll('g.trace')
256-
.remove();
265+
oldFullLayout._infolayer.selectAll('g.rangeslider-container')
266+
.select(layersToEmpty[layeri])
267+
.selectAll('g.trace')
268+
.remove();
269+
}
257270
}
258271

259272
if(hadGl && !hasGl) {

0 commit comments

Comments
 (0)