diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js
index 4215a388ac8..a929b9907a0 100644
--- a/src/components/fx/hover.js
+++ b/src/components/fx/hover.js
@@ -826,13 +826,6 @@ function createHoverText(hoverData, opts, gd) {
}
}
- // used by other modules (initially just ternary) that
- // manage their own hoverinfo independent of cleanPoint
- // the rest of this will still apply, so such modules
- // can still put things in (x|y|z)Label, text, and name
- // and hoverinfo will still determine their visibility
- if(d.extraText !== undefined) text += d.extraText;
-
if(d.zLabel !== undefined) {
if(d.xLabel !== undefined) text += 'x: ' + d.xLabel + '
';
if(d.yLabel !== undefined) text += 'y: ' + d.yLabel + '
';
@@ -851,6 +844,13 @@ function createHoverText(hoverData, opts, gd) {
text += (text ? '
' : '') + d.text;
}
+ // used by other modules (initially just ternary) that
+ // manage their own hoverinfo independent of cleanPoint
+ // the rest of this will still apply, so such modules
+ // can still put things in (x|y|z)Label, text, and name
+ // and hoverinfo will still determine their visibility
+ if(d.extraText !== undefined) text += (text ? '
' : '') + d.extraText;
+
// if 'text' is empty at this point,
// put 'name' in main label and don't show secondary label
if(text === '') {
diff --git a/src/components/legend/draw.js b/src/components/legend/draw.js
index 98c1e766652..fbedb04763f 100644
--- a/src/components/legend/draw.js
+++ b/src/components/legend/draw.js
@@ -384,20 +384,10 @@ function drawTexts(g, gd) {
if(!this.text()) text = ' \u0020\u0020 ';
- var transforms, direction;
var fullInput = legendItem.trace._fullInput || {};
var update = {};
- // 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) {
- transforms = legendItem.trace.transforms;
- direction = transforms[transforms.length - 1].direction;
-
- update[direction + '.name'] = text;
- } else if(Registry.hasTransform(fullInput, 'groupby')) {
+ if(Registry.hasTransform(fullInput, 'groupby')) {
var groupbyIndices = Registry.getTransformIndices(fullInput, 'groupby');
var index = groupbyIndices[groupbyIndices.length - 1];
diff --git a/src/components/legend/style.js b/src/components/legend/style.js
index b5a4878b9f7..fd022a9eb41 100644
--- a/src/components/legend/style.js
+++ b/src/components/legend/style.js
@@ -52,7 +52,9 @@ module.exports = function style(s, gd) {
.each(styleBoxes)
.each(stylePies)
.each(styleLines)
- .each(stylePoints);
+ .each(stylePoints)
+ .each(styleCandles)
+ .each(styleOHLC);
function styleLines(d) {
var trace = d[0].trace;
@@ -207,7 +209,61 @@ module.exports = function style(s, gd) {
.call(Color.fill, trace.fillcolor);
if(w) {
- p.call(Color.stroke, trace.line.color);
+ Color.stroke(p, trace.line.color);
+ }
+ });
+ }
+
+ function styleCandles(d) {
+ var trace = d[0].trace,
+ pts = d3.select(this).select('g.legendpoints')
+ .selectAll('path.legendcandle')
+ .data(trace.type === 'candlestick' && trace.visible ? [d, d] : []);
+ pts.enter().append('path').classed('legendcandle', true)
+ .attr('d', function(_, i) {
+ if(i) return 'M-15,0H-8M-8,6V-6H8Z'; // increasing
+ return 'M15,0H8M8,-6V6H-8Z'; // decreasing
+ })
+ .attr('transform', 'translate(20,0)')
+ .style('stroke-miterlimit', 1);
+ pts.exit().remove();
+ pts.each(function(_, i) {
+ var container = trace[i ? 'increasing' : 'decreasing'];
+ var w = container.line.width,
+ p = d3.select(this);
+
+ p.style('stroke-width', w + 'px')
+ .call(Color.fill, container.fillcolor);
+
+ if(w) {
+ Color.stroke(p, container.line.color);
+ }
+ });
+ }
+
+ function styleOHLC(d) {
+ var trace = d[0].trace,
+ pts = d3.select(this).select('g.legendpoints')
+ .selectAll('path.legendohlc')
+ .data(trace.type === 'ohlc' && trace.visible ? [d, d] : []);
+ pts.enter().append('path').classed('legendohlc', true)
+ .attr('d', function(_, i) {
+ if(i) return 'M-15,0H0M-8,-6V0'; // increasing
+ return 'M15,0H0M8,6V0'; // decreasing
+ })
+ .attr('transform', 'translate(20,0)')
+ .style('stroke-miterlimit', 1);
+ pts.exit().remove();
+ pts.each(function(_, i) {
+ var container = trace[i ? 'increasing' : 'decreasing'];
+ var w = container.line.width,
+ p = d3.select(this);
+
+ p.style('fill', 'none')
+ .call(Drawing.dashLine, container.line.dash, w);
+
+ if(w) {
+ Color.stroke(p, container.line.color);
}
});
}
diff --git a/src/components/rangeslider/defaults.js b/src/components/rangeslider/defaults.js
index 5202aa2a7a6..651bfc72e7f 100644
--- a/src/components/rangeslider/defaults.js
+++ b/src/components/rangeslider/defaults.js
@@ -14,16 +14,18 @@ var oppAxisAttrs = require('./oppaxis_attributes');
var axisIds = require('../../plots/cartesian/axis_ids');
module.exports = function handleDefaults(layoutIn, layoutOut, axName) {
- if(!layoutIn[axName].rangeslider) return;
+ var axIn = layoutIn[axName];
+ var axOut = layoutOut[axName];
+
+ if(!(axIn.rangeslider || layoutOut._requestRangeslider[axOut._id])) return;
// not super proud of this (maybe store _ in axis object instead
- if(!Lib.isPlainObject(layoutIn[axName].rangeslider)) {
- layoutIn[axName].rangeslider = {};
+ if(!Lib.isPlainObject(axIn.rangeslider)) {
+ axIn.rangeslider = {};
}
- var containerIn = layoutIn[axName].rangeslider,
- axOut = layoutOut[axName],
- containerOut = axOut.rangeslider = {};
+ var containerIn = axIn.rangeslider;
+ var containerOut = axOut.rangeslider = {};
function coerce(attr, dflt) {
return Lib.coerce(containerIn, containerOut, attributes, attr, dflt);
diff --git a/src/components/rangeslider/draw.js b/src/components/rangeslider/draw.js
index 8aff445e6a0..ea673113a2f 100644
--- a/src/components/rangeslider/draw.js
+++ b/src/components/rangeslider/draw.js
@@ -455,7 +455,8 @@ function drawRangePlot(rangeSlider, gd, axisOpts, opts) {
id: id,
plotgroup: plotgroup,
xaxis: xa,
- yaxis: ya
+ yaxis: ya,
+ isRangePlot: true
};
if(isMainPlot) mainplotinfo = plotinfo;
diff --git a/src/lib/coerce.js b/src/lib/coerce.js
index bb025e3f2c5..bab4077e28f 100644
--- a/src/lib/coerce.js
+++ b/src/lib/coerce.js
@@ -434,9 +434,7 @@ exports.coerceFont = function(coerce, attr, dfltObj) {
*/
exports.coerceHoverinfo = function(traceIn, traceOut, layoutOut) {
var moduleAttrs = traceOut._module.attributes;
- var attrs = moduleAttrs.hoverinfo ?
- {hoverinfo: moduleAttrs.hoverinfo} :
- baseTraceAttrs;
+ var attrs = moduleAttrs.hoverinfo ? moduleAttrs : baseTraceAttrs;
var valObj = attrs.hoverinfo;
var dflt;
diff --git a/src/plot_api/helpers.js b/src/plot_api/helpers.js
index d4f44af785a..b0912b31020 100644
--- a/src/plot_api/helpers.js
+++ b/src/plot_api/helpers.js
@@ -330,6 +330,32 @@ exports.cleanData = function(data, existingData) {
}
}
+ // fixes from converting finance from transforms to real trace types
+ if(trace.type === 'candlestick' || trace.type === 'ohlc') {
+ var increasingShowlegend = (trace.increasing || {}).showlegend !== false;
+ var decreasingShowlegend = (trace.decreasing || {}).showlegend !== false;
+ var increasingName = cleanFinanceDir(trace.increasing);
+ var decreasingName = cleanFinanceDir(trace.decreasing);
+
+ // now figure out something smart to do with the separate direction
+ // names we removed
+ if((increasingName !== false) && (decreasingName !== false)) {
+ // both sub-names existed: base name previously had no effect
+ // so ignore it and try to find a shared part of the sub-names
+
+ var newName = commonPrefix(
+ increasingName, decreasingName,
+ increasingShowlegend, decreasingShowlegend
+ );
+ // if no common part, leave whatever name was (or wasn't) there
+ if(newName) trace.name = newName;
+ }
+ else if((increasingName || decreasingName) && !trace.name) {
+ // one sub-name existed but not the base name - just use the sub-name
+ trace.name = increasingName || decreasingName;
+ }
+ }
+
// transforms backward compatibility fixes
if(Array.isArray(trace.transforms)) {
var transforms = trace.transforms;
@@ -388,6 +414,38 @@ exports.cleanData = function(data, existingData) {
}
};
+function cleanFinanceDir(dirContainer) {
+ if(!Lib.isPlainObject(dirContainer)) return false;
+
+ var dirName = dirContainer.name;
+
+ delete dirContainer.name;
+ delete dirContainer.showlegend;
+
+ return (typeof dirName === 'string' || typeof dirName === 'number') && String(dirName);
+}
+
+function commonPrefix(name1, name2, show1, show2) {
+ // if only one is shown in the legend, use that
+ if(show1 && !show2) return name1;
+ if(show2 && !show1) return name2;
+
+ // if both or neither are in the legend, check if one is blank (or whitespace)
+ // and use the other one
+ // note that hover labels can still use the name even if the legend doesn't
+ if(!name1.trim()) return name2;
+ if(!name2.trim()) return name1;
+
+ var minLen = Math.min(name1.length, name2.length);
+ var i;
+ for(i = 0; i < minLen; i++) {
+ if(name1.charAt(i) !== name2.charAt(i)) break;
+ }
+
+ var out = name1.substr(0, i);
+ return out.trim();
+}
+
// textposition - support partial attributes (ie just 'top')
// and incorrect use of middle / center etc.
function cleanTextPosition(textposition) {
diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js
index 94638f8ff50..f5681c44a3a 100644
--- a/src/plot_api/plot_schema.js
+++ b/src/plot_api/plot_schema.js
@@ -256,12 +256,13 @@ exports.getTraceValObject = function(trace, parts) {
var moduleAttrs, valObject;
if(head === 'transforms') {
- if(!Array.isArray(trace.transforms)) return false;
+ var transforms = trace.transforms;
+ if(!Array.isArray(transforms) || !transforms.length) return false;
var tNum = parts[1];
- if(!isIndex(tNum) || tNum >= trace.transforms.length) {
+ if(!isIndex(tNum) || tNum >= transforms.length) {
return false;
}
- moduleAttrs = (Registry.transformsRegistry[trace.transforms[tNum].type] || {}).attributes;
+ moduleAttrs = (Registry.transformsRegistry[transforms[tNum].type] || {}).attributes;
valObject = moduleAttrs && moduleAttrs[parts[2]];
i = 3; // start recursing only inside the transform
}
diff --git a/src/plots/cartesian/constants.js b/src/plots/cartesian/constants.js
index a134bd71cf5..59ac6f1b87f 100644
--- a/src/plots/cartesian/constants.js
+++ b/src/plots/cartesian/constants.js
@@ -68,6 +68,7 @@ module.exports = {
'carpetlayer',
'violinlayer',
'boxlayer',
+ 'ohlclayer',
'scatterlayer'
],
diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js
index 3304737bfb6..8ffa10cdc7b 100644
--- a/src/plots/cartesian/index.js
+++ b/src/plots/cartesian/index.js
@@ -192,7 +192,7 @@ function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback
// remaining plot traces should also be able to do this. Once implemented,
// we won't need this - which should sometimes be a big speedup.
if(plotinfo.plot) {
- plotinfo.plot.selectAll('g:not(.scatterlayer)').selectAll('g.trace').remove();
+ plotinfo.plot.selectAll('g:not(.scatterlayer):not(.ohlclayer)').selectAll('g.trace').remove();
}
// plot all traces for each module at once
@@ -200,12 +200,16 @@ function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback
var _module = modules[j];
// skip over non-cartesian trace modules
- if(_module.basePlotModule.name !== 'cartesian') continue;
+ if(!_module.plot || _module.basePlotModule.name !== 'cartesian') continue;
// plot all traces of this type on this subplot at once
- var cdModule = getModuleCalcData(cdSubplot, _module);
+ var cdModuleAndOthers = getModuleCalcData(cdSubplot, _module);
+ var cdModule = cdModuleAndOthers[0];
+ // don't need to search the found traces again - in fact we need to NOT
+ // so that if two modules share the same plotter we don't double-plot
+ cdSubplot = cdModuleAndOthers[1];
- if(_module.plot) _module.plot(gd, plotinfo, cdModule, transitionOpts, makeOnCompleteCallback);
+ _module.plot(gd, plotinfo, cdModule, transitionOpts, makeOnCompleteCallback);
}
}
@@ -215,6 +219,7 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
var oldPlots = oldFullLayout._plots || {};
var hadScatter, hasScatter;
+ var hadOHLC, hasOHLC;
var hadGl, hasGl;
var i, k, subplotInfo, moduleName;
@@ -232,28 +237,36 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
moduleName = oldModules[i].name;
if(moduleName === 'scatter') hadScatter = true;
else if(moduleName === 'scattergl') hadGl = true;
+ else if(moduleName === 'ohlc') hadOHLC = true;
}
for(i = 0; i < newModules.length; i++) {
moduleName = newModules[i].name;
if(moduleName === 'scatter') hasScatter = true;
else if(moduleName === 'scattergl') hasGl = true;
+ else if(moduleName === 'ohlc') hasOHLC = true;
}
- if(hadScatter && !hasScatter) {
- for(k in oldPlots) {
- subplotInfo = oldPlots[k];
- if(subplotInfo.plot) {
- subplotInfo.plot.select('g.scatterlayer')
- .selectAll('g.trace')
- .remove();
+ var layersToEmpty = [];
+ if(hadScatter && !hasScatter) layersToEmpty.push('g.scatterlayer');
+ if(hadOHLC && !hasOHLC) layersToEmpty.push('g.ohlclayer');
+
+ if(layersToEmpty.length) {
+ for(var layeri = 0; layeri < layersToEmpty.length; layeri++) {
+ for(k in oldPlots) {
+ subplotInfo = oldPlots[k];
+ if(subplotInfo.plot) {
+ subplotInfo.plot.select(layersToEmpty[layeri])
+ .selectAll('g.trace')
+ .remove();
+ }
}
- }
- oldFullLayout._infolayer.selectAll('g.rangeslider-container')
- .select('g.scatterlayer')
- .selectAll('g.trace')
- .remove();
+ oldFullLayout._infolayer.selectAll('g.rangeslider-container')
+ .select(layersToEmpty[layeri])
+ .selectAll('g.trace')
+ .remove();
+ }
}
if(hadGl && !hasGl) {
diff --git a/src/plots/get_data.js b/src/plots/get_data.js
index 763fb255d30..e06ca4608c3 100644
--- a/src/plots/get_data.js
+++ b/src/plots/get_data.js
@@ -12,11 +12,11 @@ var Registry = require('../registry');
var SUBPLOT_PATTERN = require('./cartesian/constants').SUBPLOT_PATTERN;
/**
- * Get calcdata traces(s) associated with a given subplot
+ * Get calcdata trace(s) associated with a given subplot
*
- * @param {array} calcData (as in gd.calcdata)
- * @param {string} type subplot type
- * @param {string} subplotId subplot id to look for
+ * @param {array} calcData: as in gd.calcdata
+ * @param {string} type: subplot type
+ * @param {string} subplotId: subplot id to look for
*
* @return {array} array of calcdata traces
*/
@@ -36,20 +36,40 @@ exports.getSubplotCalcData = function(calcData, type, subplotId) {
return subplotCalcData;
};
-
+/**
+ * Get calcdata trace(s) that can be plotted with a given module
+ * NOTE: this isn't necessarily just exactly matching trace type,
+ * if multiple trace types use the same plotting routine, they will be
+ * collected here.
+ * In order to not plot the same thing multiple times, we return two arrays,
+ * the calcdata we *will* plot with this module, and the ones we *won't*
+ *
+ * @param {array} calcdata: as in gd.calcdata
+ * @param {object|string} typeOrModule: the plotting module, or its name
+ *
+ * @return {array[array]} [foundCalcdata, remainingCalcdata]
+ */
exports.getModuleCalcData = function(calcdata, typeOrModule) {
var moduleCalcData = [];
+ var remainingCalcData = [];
var _module = typeof typeOrModule === 'string' ? Registry.getModule(typeOrModule) : typeOrModule;
- if(!_module) return moduleCalcData;
+ if(!_module) return [moduleCalcData, calcdata];
for(var i = 0; i < calcdata.length; i++) {
var cd = calcdata[i];
var trace = cd[0].trace;
+ if(trace.visible !== true) continue;
- if((trace._module === _module) && (trace.visible === true)) moduleCalcData.push(cd);
+ // we use this to find data to plot - so if there's a .plot
+ if(trace._module.plot === _module.plot) {
+ moduleCalcData.push(cd);
+ }
+ else {
+ remainingCalcData.push(cd);
+ }
}
- return moduleCalcData;
+ return [moduleCalcData, remainingCalcData];
};
/**
diff --git a/src/plots/plots.js b/src/plots/plots.js
index 80af2b79bc5..05aa915e073 100644
--- a/src/plots/plots.js
+++ b/src/plots/plots.js
@@ -373,6 +373,10 @@ plots.supplyDefaults = function(gd) {
var splomAxes = newFullLayout._splomAxes = {x: {}, y: {}};
var splomSubplots = newFullLayout._splomSubplots = {};
+ // for traces to request a default rangeslider on their x axes
+ // eg set `_requestRangeslider.x2 = true` for xaxis2
+ newFullLayout._requestRangeslider = {};
+
// then do the data
newFullLayout._globalTransforms = (gd._context || {}).globalTransforms;
plots.supplyDataDefaults(newData, newFullData, newLayout, newFullLayout);
diff --git a/src/traces/bar/plot.js b/src/traces/bar/plot.js
index 30914fecb14..28f81a985d3 100644
--- a/src/traces/bar/plot.js
+++ b/src/traces/bar/plot.js
@@ -42,9 +42,11 @@ module.exports = function plot(gd, plotinfo, cdbar) {
bartraces.enter().append('g')
.attr('class', 'trace bars');
- bartraces.each(function(d) {
- d[0].node3 = d3.select(this);
- });
+ if(!plotinfo.isRangePlot) {
+ bartraces.each(function(d) {
+ d[0].node3 = d3.select(this);
+ });
+ }
bartraces.append('g')
.attr('class', 'points')
diff --git a/src/traces/box/calc.js b/src/traces/box/calc.js
index 9452bc3390a..0c4c75dc58a 100644
--- a/src/traces/box/calc.js
+++ b/src/traces/box/calc.js
@@ -143,11 +143,6 @@ module.exports = function calc(gd, trace) {
}
};
- // don't show labels in candlestick hover labels
- if(trace._fullInput && trace._fullInput.type === 'candlestick') {
- delete cd[0].t.labels;
- }
-
fullLayout[numKey]++;
return cd;
} else {
diff --git a/src/traces/box/hover.js b/src/traces/box/hover.js
index 3ff3fe024ef..79e24509360 100644
--- a/src/traces/box/hover.js
+++ b/src/traces/box/hover.js
@@ -58,25 +58,26 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) {
hoverPseudoDistance, spikePseudoDistance;
var boxDelta = t.bdPos;
+ var posAcceptance = t.wHover;
var shiftPos = function(di) { return di.pos + t.bPos - pVal; };
if(isViolin && trace.side !== 'both') {
if(trace.side === 'positive') {
dPos = function(di) {
var pos = shiftPos(di);
- return Fx.inbox(pos, pos + boxDelta, hoverPseudoDistance);
+ return Fx.inbox(pos, pos + posAcceptance, hoverPseudoDistance);
};
}
if(trace.side === 'negative') {
dPos = function(di) {
var pos = shiftPos(di);
- return Fx.inbox(pos - boxDelta, pos, hoverPseudoDistance);
+ return Fx.inbox(pos - posAcceptance, pos, hoverPseudoDistance);
};
}
} else {
dPos = function(di) {
var pos = shiftPos(di);
- return Fx.inbox(pos - boxDelta, pos + boxDelta, hoverPseudoDistance);
+ return Fx.inbox(pos - posAcceptance, pos + posAcceptance, hoverPseudoDistance);
};
}
@@ -133,10 +134,9 @@ function hoverOnBoxes(pointData, xval, yval, hovermode) {
else if(Color.opacity(mc) && trace.boxpoints) pointData.color = mc;
else pointData.color = trace.fillcolor;
- pointData[pLetter + '0'] = pAxis.c2p(di.pos + t.bPos - t.bdPos, true);
- pointData[pLetter + '1'] = pAxis.c2p(di.pos + t.bPos + t.bdPos, true);
+ pointData[pLetter + '0'] = pAxis.c2p(di.pos + t.bPos - boxDelta, true);
+ pointData[pLetter + '1'] = pAxis.c2p(di.pos + t.bPos + boxDelta, true);
- Axes.tickText(pAxis, pAxis.c2l(di.pos), 'hover').text;
pointData[pLetter + 'LabelVal'] = di.pos;
var spikePosAttr = pLetter + 'Spike';
diff --git a/src/traces/box/index.js b/src/traces/box/index.js
index ad32d7000aa..621f825f813 100644
--- a/src/traces/box/index.js
+++ b/src/traces/box/index.js
@@ -24,7 +24,7 @@ Box.selectPoints = require('./select');
Box.moduleType = 'trace';
Box.name = 'box';
Box.basePlotModule = require('../../plots/cartesian');
-Box.categories = ['cartesian', 'svg', 'symbols', 'oriented', 'box-violin', 'showLegend', 'draggedPts'];
+Box.categories = ['cartesian', 'svg', 'symbols', 'oriented', 'box-violin', 'showLegend', 'draggedPts', 'boxLayout'];
Box.meta = {
description: [
'In vertical (horizontal) box plots,',
diff --git a/src/traces/box/layout_defaults.js b/src/traces/box/layout_defaults.js
index fd136614907..68af8851d0b 100644
--- a/src/traces/box/layout_defaults.js
+++ b/src/traces/box/layout_defaults.js
@@ -8,13 +8,15 @@
'use strict';
+var Registry = require('../../registry');
var Lib = require('../../lib');
var layoutAttributes = require('./layout_attributes');
function _supply(layoutIn, layoutOut, fullData, coerce, traceType) {
var hasTraceType;
+ var category = traceType + 'Layout';
for(var i = 0; i < fullData.length; i++) {
- if(fullData[i].type === traceType) {
+ if(Registry.traceIs(fullData[i], category)) {
hasTraceType = true;
break;
}
diff --git a/src/traces/box/plot.js b/src/traces/box/plot.js
index 8f8bea84b5c..042c7363d10 100644
--- a/src/traces/box/plot.js
+++ b/src/traces/box/plot.js
@@ -32,19 +32,22 @@ function plot(gd, plotinfo, cdbox) {
var cd0 = d[0];
var t = cd0.t;
var trace = cd0.trace;
- var sel = cd0.node3 = d3.select(this);
+ var sel = d3.select(this);
+ if(!plotinfo.isRangePlot) cd0.node3 = sel;
var numBoxes = fullLayout._numBoxes;
+ var groupFraction = (1 - fullLayout.boxgap);
+
var group = (fullLayout.boxmode === 'group' && numBoxes > 1);
// box half width
- var bdPos = t.dPos * (1 - fullLayout.boxgap) * (1 - fullLayout.boxgroupgap) / (group ? numBoxes : 1);
+ var bdPos = t.dPos * groupFraction * (1 - fullLayout.boxgroupgap) / (group ? numBoxes : 1);
// box center offset
- var bPos = group ? 2 * t.dPos * (-0.5 + (t.num + 0.5) / numBoxes) * (1 - fullLayout.boxgap) : 0;
+ var bPos = group ? 2 * t.dPos * (-0.5 + (t.num + 0.5) / numBoxes) * groupFraction : 0;
// whisker width
var wdPos = bdPos * trace.whiskerwidth;
if(trace.visible !== true || t.empty) {
- d3.select(this).remove();
+ sel.remove();
return;
}
@@ -62,6 +65,9 @@ function plot(gd, plotinfo, cdbox) {
t.bPos = bPos;
t.bdPos = bdPos;
t.wdPos = wdPos;
+ // half-width within which to accept hover for this box
+ // always split the distance to the closest box
+ t.wHover = t.dPos * (group ? groupFraction / numBoxes : 1);
// boxes and whiskers
plotBoxAndWhiskers(sel, {pos: posAxis, val: valAxis}, trace, t);
@@ -121,8 +127,16 @@ function plotBoxAndWhiskers(sel, axes, trace, t) {
valAxis.c2p(d.med, true),
Math.min(q1, q3) + 1, Math.max(q1, q3) - 1
);
- var lf = valAxis.c2p(trace.boxpoints === false ? d.min : d.lf, true);
- var uf = valAxis.c2p(trace.boxpoints === false ? d.max : d.uf, true);
+
+ // for compatibility with box, violin, and candlestick
+ // perhaps we should put this into cd0.t instead so it's more explicit,
+ // but what we have now is:
+ // - box always has d.lf, but boxpoints can be anything
+ // - violin has d.lf and should always use it (boxpoints is undefined)
+ // - candlestick has only min/max
+ var useExtremes = (d.lf === undefined) || (trace.boxpoints === false);
+ var lf = valAxis.c2p(useExtremes ? d.min : d.lf, true);
+ var uf = valAxis.c2p(useExtremes ? d.max : d.uf, true);
var ln = valAxis.c2p(d.ln, true);
var un = valAxis.c2p(d.un, true);
diff --git a/src/traces/box/set_positions.js b/src/traces/box/set_positions.js
index 21437c39a25..ca460fd8297 100644
--- a/src/traces/box/set_positions.js
+++ b/src/traces/box/set_positions.js
@@ -25,21 +25,23 @@ function setPositions(gd, plotinfo) {
var minPad = 0;
var maxPad = 0;
- // make list of boxes
+ // make list of boxes / candlesticks
+ // For backward compatibility, candlesticks are treated as if they *are* box traces here
for(var j = 0; j < calcdata.length; j++) {
var cd = calcdata[j];
var t = cd[0].t;
var trace = cd[0].trace;
- if(trace.visible === true && trace.type === 'box' &&
+ if(trace.visible === true &&
+ (trace.type === 'box' || trace.type === 'candlestick') &&
!t.empty &&
- trace.orientation === orientation &&
+ (trace.orientation || 'v') === orientation &&
trace.xaxis === xa._id &&
trace.yaxis === ya._id
) {
boxList.push(j);
- if(trace.boxpoints !== false) {
+ if(trace.boxpoints) {
minPad = Math.max(minPad, trace.jitter - trace.pointpos - 1);
maxPad = Math.max(maxPad, trace.jitter + trace.pointpos - 1);
}
diff --git a/src/traces/box/style.js b/src/traces/box/style.js
index 265692eedb2..aef404372b7 100644
--- a/src/traces/box/style.js
+++ b/src/traces/box/style.js
@@ -22,20 +22,35 @@ module.exports = function style(gd, cd) {
var trace = d[0].trace;
var lineWidth = trace.line.width;
- el.selectAll('path.box')
- .style('stroke-width', lineWidth + 'px')
- .call(Color.stroke, trace.line.color)
- .call(Color.fill, trace.fillcolor);
-
- el.selectAll('path.mean')
- .style({
- 'stroke-width': lineWidth,
- 'stroke-dasharray': (2 * lineWidth) + 'px,' + lineWidth + 'px'
- })
- .call(Color.stroke, trace.line.color);
-
- var pts = el.selectAll('path.point');
- Drawing.pointStyle(pts, trace, gd);
- Drawing.selectedPointStyle(pts, trace);
+ function styleBox(boxSel, lineWidth, lineColor, fillColor) {
+ boxSel.style('stroke-width', lineWidth + 'px')
+ .call(Color.stroke, lineColor)
+ .call(Color.fill, fillColor);
+ }
+
+ var allBoxes = el.selectAll('path.box');
+
+ if(trace.type === 'candlestick') {
+ allBoxes.each(function(boxData) {
+ var thisBox = d3.select(this);
+ var container = trace[boxData.dir]; // dir = 'increasing' or 'decreasing'
+ styleBox(thisBox, container.line.width, container.line.color, container.fillcolor);
+ // TODO: custom selection style for candlesticks
+ thisBox.style('opacity', trace.selectedpoints && !boxData.selected ? 0.3 : 1);
+ });
+ }
+ else {
+ styleBox(allBoxes, lineWidth, trace.line.color, trace.fillcolor);
+ el.selectAll('path.mean')
+ .style({
+ 'stroke-width': lineWidth,
+ 'stroke-dasharray': (2 * lineWidth) + 'px,' + lineWidth + 'px'
+ })
+ .call(Color.stroke, trace.line.color);
+
+ var pts = el.selectAll('path.point');
+ Drawing.pointStyle(pts, trace, gd);
+ Drawing.selectedPointStyle(pts, trace);
+ }
});
};
diff --git a/src/traces/candlestick/attributes.js b/src/traces/candlestick/attributes.js
index f4a6c8c9394..ac007ffc9af 100644
--- a/src/traces/candlestick/attributes.js
+++ b/src/traces/candlestick/attributes.js
@@ -15,9 +15,6 @@ var boxAttrs = require('../box/attributes');
function directionAttrs(lineColorDefault) {
return {
- name: OHLCattrs.increasing.name,
- showlegend: OHLCattrs.increasing.showlegend,
-
line: {
color: extendFlat({}, boxAttrs.line.color, {dflt: lineColorDefault}),
width: boxAttrs.line.width,
diff --git a/src/traces/candlestick/calc.js b/src/traces/candlestick/calc.js
new file mode 100644
index 00000000000..72933f236a3
--- /dev/null
+++ b/src/traces/candlestick/calc.js
@@ -0,0 +1,48 @@
+/**
+* Copyright 2012-2018, 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 Axes = require('../../plots/cartesian/axes');
+
+var calcCommon = require('../ohlc/calc').calcCommon;
+
+module.exports = function(gd, trace) {
+ var fullLayout = gd._fullLayout;
+ var xa = Axes.getFromId(gd, trace.xaxis);
+ var ya = Axes.getFromId(gd, trace.yaxis);
+
+ var x = xa.makeCalcdata(trace, 'x');
+
+ var cd = calcCommon(gd, trace, x, ya, ptFunc);
+
+ if(cd.length) {
+ Lib.extendFlat(cd[0].t, {
+ num: fullLayout._numBoxes,
+ dPos: Lib.distinctVals(x).minDiff / 2,
+ posLetter: 'x',
+ valLetter: 'y',
+ });
+
+ fullLayout._numBoxes++;
+ return cd;
+ } else {
+ return [{t: {empty: true}}];
+ }
+};
+
+function ptFunc(o, h, l, c) {
+ return {
+ min: l,
+ q1: Math.min(o, c),
+ med: c,
+ q3: Math.max(o, c),
+ max: h,
+ };
+}
diff --git a/src/traces/candlestick/defaults.js b/src/traces/candlestick/defaults.js
index ee5dcdf7682..e00862ca43a 100644
--- a/src/traces/candlestick/defaults.js
+++ b/src/traces/candlestick/defaults.js
@@ -10,14 +10,11 @@
'use strict';
var Lib = require('../../lib');
+var Color = require('../../components/color');
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, defaultColor, layout) {
- helpers.pushDummyTransformOpts(traceIn, traceOut);
-
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
@@ -35,12 +32,12 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
coerce('text');
coerce('whiskerwidth');
+
+ layout._requestRangeslider[traceOut.xaxis] = true;
};
function handleDirection(traceIn, traceOut, coerce, direction) {
- handleDirectionDefaults(traceIn, traceOut, coerce, direction);
-
- coerce(direction + '.line.color');
+ var lineColor = coerce(direction + '.line.color');
coerce(direction + '.line.width', traceOut.line.width);
- coerce(direction + '.fillcolor');
+ coerce(direction + '.fillcolor', Color.addOpacity(lineColor, 0.5));
}
diff --git a/src/traces/candlestick/index.js b/src/traces/candlestick/index.js
index ef94d47bc17..4a6372585b7 100644
--- a/src/traces/candlestick/index.js
+++ b/src/traces/candlestick/index.js
@@ -8,13 +8,11 @@
'use strict';
-var Registry = require('../../registry');
-
module.exports = {
moduleType: 'trace',
name: 'candlestick',
basePlotModule: require('../../plots/cartesian'),
- categories: ['cartesian', 'svg', 'showLegend', 'candlestick'],
+ categories: ['cartesian', 'svg', 'showLegend', 'candlestick', 'boxLayout'],
meta: {
description: [
'The candlestick is a style of financial chart describing',
@@ -32,8 +30,13 @@ module.exports = {
},
attributes: require('./attributes'),
+ layoutAttributes: require('../box/layout_attributes'),
+ supplyLayoutDefaults: require('../box/layout_defaults').supplyLayoutDefaults,
+ setPositions: require('../box/set_positions').setPositions,
supplyDefaults: require('./defaults'),
+ calc: require('./calc'),
+ plot: require('../box/plot').plot,
+ style: require('../box/style'),
+ hoverPoints: require('../ohlc/hover'),
+ selectPoints: require('../ohlc/select')
};
-
-Registry.register(require('../box'));
-Registry.register(require('./transform'));
diff --git a/src/traces/candlestick/transform.js b/src/traces/candlestick/transform.js
deleted file mode 100644
index 36b1e5de6c7..00000000000
--- a/src/traces/candlestick/transform.js
+++ /dev/null
@@ -1,129 +0,0 @@
-/**
-* Copyright 2012-2018, 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 isNumeric = require('fast-isnumeric');
-
-var Lib = require('../../lib');
-var helpers = require('../ohlc/helpers');
-
-exports.moduleType = 'transform';
-
-exports.name = 'candlestick';
-
-exports.attributes = {};
-
-exports.supplyDefaults = function(transformIn, traceOut, layout, traceIn) {
- helpers.clearEphemeralTransformOpts(traceIn);
- helpers.copyOHLC(transformIn, traceOut);
-
- return transformIn;
-};
-
-exports.transform = function transform(dataIn, state) {
- var dataOut = [];
-
- for(var i = 0; i < dataIn.length; i++) {
- var traceIn = dataIn[i];
-
- if(traceIn.type !== 'candlestick') {
- dataOut.push(traceIn);
- continue;
- }
-
- dataOut.push(
- makeTrace(traceIn, state, 'increasing'),
- makeTrace(traceIn, state, 'decreasing')
- );
- }
-
- helpers.addRangeSlider(dataOut, state.layout);
-
- return dataOut;
-};
-
-function makeTrace(traceIn, state, direction) {
- var traceOut = {
- type: 'box',
- boxpoints: false,
-
- visible: traceIn.visible,
- hoverinfo: traceIn.hoverinfo,
- opacity: traceIn.opacity,
- xaxis: traceIn.xaxis,
- yaxis: traceIn.yaxis,
-
- transforms: helpers.makeTransform(traceIn, state, direction),
- _inputLength: traceIn._inputLength
- };
-
- // the rest of below may not have been coerced
-
- var directionOpts = traceIn[direction];
-
- if(directionOpts) {
- Lib.extendFlat(traceOut, {
-
- // to make autotype catch date axes soon!!
- x: traceIn.x || [0],
- xcalendar: traceIn.xcalendar,
-
- // concat low and high to get correct autorange
- y: [].concat(traceIn.low).concat(traceIn.high),
-
- whiskerwidth: traceIn.whiskerwidth,
- text: traceIn.text,
-
- name: directionOpts.name,
- showlegend: directionOpts.showlegend,
- line: directionOpts.line,
- fillcolor: directionOpts.fillcolor
- });
- }
-
- return traceOut;
-}
-
-exports.calcTransform = function calcTransform(gd, trace, opts) {
- var direction = opts.direction,
- filterFn = helpers.getFilterFn(direction);
-
- var open = trace.open,
- high = trace.high,
- low = trace.low,
- close = trace.close;
-
- var len = trace._inputLength,
- x = [],
- y = [];
-
- var appendX = trace._fullInput.x ?
- function(i) {
- var v = trace.x[i];
- x.push(v, v, v, v, v, v);
- } :
- function(i) {
- x.push(i, i, i, i, i, i);
- };
-
- var appendY = function(o, h, l, c) {
- y.push(l, o, c, c, c, h);
- };
-
- for(var i = 0; i < len; i++) {
- if(filterFn(open[i], close[i]) && isNumeric(high[i]) && isNumeric(low[i])) {
- appendX(i);
- appendY(open[i], high[i], low[i], close[i]);
- }
- }
-
- trace.x = x;
- trace.y = y;
-};
diff --git a/src/traces/ohlc/attributes.js b/src/traces/ohlc/attributes.js
index 6cb5b7a77e5..e37213cbdc0 100644
--- a/src/traces/ohlc/attributes.js
+++ b/src/traces/ohlc/attributes.js
@@ -20,27 +20,6 @@ var lineAttrs = scatterAttrs.line;
function directionAttrs(lineColorDefault) {
return {
- name: {
- valType: 'string',
- role: 'info',
- editType: 'style',
- description: [
- 'Sets the segment name.',
- 'The segment name appear as the legend item and on hover.'
- ].join(' ')
- },
-
- showlegend: {
- valType: 'boolean',
- role: 'info',
- dflt: true,
- editType: 'style',
- description: [
- 'Determines whether or not an item corresponding to this',
- 'segment is shown in the legend.'
- ].join(' ')
- },
-
line: {
color: extendFlat({}, lineAttrs.color, {dflt: lineColorDefault}),
width: lineAttrs.width,
diff --git a/src/traces/ohlc/calc.js b/src/traces/ohlc/calc.js
new file mode 100644
index 00000000000..42de0e1a086
--- /dev/null
+++ b/src/traces/ohlc/calc.js
@@ -0,0 +1,164 @@
+/**
+* Copyright 2012-2018, 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 _ = Lib._;
+var Axes = require('../../plots/cartesian/axes');
+var BADNUM = require('../../constants/numerical').BADNUM;
+
+function calc(gd, trace) {
+ var xa = Axes.getFromId(gd, trace.xaxis);
+ var ya = Axes.getFromId(gd, trace.yaxis);
+
+ var tickLen = convertTickWidth(gd, xa, trace);
+ var minDiff = trace._minDiff;
+ trace._minDiff = null;
+ var x = trace._xcalc;
+ trace._xcalc = null;
+
+ var cd = calcCommon(gd, trace, x, ya, ptFunc);
+
+ Axes.expand(xa, x, {vpad: minDiff / 2});
+
+ if(cd.length) {
+ Lib.extendFlat(cd[0].t, {
+ wHover: minDiff / 2,
+ tickLen: tickLen
+ });
+ return cd;
+ } else {
+ return [{t: {empty: true}}];
+ }
+}
+
+function ptFunc(o, h, l, c) {
+ return {
+ o: o,
+ h: h,
+ l: l,
+ c: c
+ };
+}
+
+
+// shared between OHLC and candlestick
+// ptFunc makes a calcdata point specific to each trace type, from oi, hi, li, ci
+function calcCommon(gd, trace, x, ya, ptFunc) {
+ var o = ya.makeCalcdata(trace, 'open');
+ var h = ya.makeCalcdata(trace, 'high');
+ var l = ya.makeCalcdata(trace, 'low');
+ var c = ya.makeCalcdata(trace, 'close');
+
+ var hasTextArray = Array.isArray(trace.text);
+
+ // we're optimists - before we have any changing data, assume increasing
+ var increasing = true;
+ var cPrev = null;
+
+ var cd = [];
+ for(var i = 0; i < x.length; i++) {
+ var xi = x[i];
+ var oi = o[i];
+ var hi = h[i];
+ var li = l[i];
+ var ci = c[i];
+
+ if(xi !== BADNUM && oi !== BADNUM && hi !== BADNUM && li !== BADNUM && ci !== BADNUM) {
+ if(ci === oi) {
+ // if open == close, look for a change from the previous close
+ if(cPrev !== null && ci !== cPrev) increasing = ci > cPrev;
+ // else (c === cPrev or cPrev is null) no change
+ }
+ else increasing = ci > oi;
+
+ cPrev = ci;
+
+ var pt = ptFunc(oi, hi, li, ci);
+
+ pt.pos = xi;
+ pt.yc = (oi + ci) / 2;
+ pt.i = i;
+ pt.dir = increasing ? 'increasing' : 'decreasing';
+
+ if(hasTextArray) pt.tx = trace.text[i];
+
+ cd.push(pt);
+ }
+ }
+
+ Axes.expand(ya, l.concat(h), {padded: true});
+
+ if(cd.length) {
+ cd[0].t = {
+ labels: {
+ open: _(gd, 'open:') + ' ',
+ high: _(gd, 'high:') + ' ',
+ low: _(gd, 'low:') + ' ',
+ close: _(gd, 'close:') + ' '
+ }
+ };
+ }
+
+ return cd;
+}
+
+/*
+ * find min x-coordinates difference of all traces
+ * attached to this x-axis and stash the result in _minDiff
+ * in all traces; when a trace uses this in its
+ * calc step it deletes _minDiff, so that next calc this is
+ * done again in case the data changed.
+ * also since we need it here, stash _xcalc on the trace
+ */
+function convertTickWidth(gd, xa, trace) {
+ var minDiff = trace._minDiff;
+
+ if(!minDiff) {
+ var fullData = gd._fullData,
+ ohlcTracesOnThisXaxis = [];
+
+ minDiff = Infinity;
+
+ var i;
+
+ for(i = 0; i < fullData.length; i++) {
+ var tracei = fullData[i];
+
+ if(tracei.type === 'ohlc' &&
+ tracei.visible === true &&
+ tracei.xaxis === xa._id
+ ) {
+ ohlcTracesOnThisXaxis.push(tracei);
+
+ var xcalc = xa.makeCalcdata(tracei, 'x');
+ tracei._xcalc = xcalc;
+
+ var _minDiff = Lib.distinctVals(xcalc).minDiff;
+ if(_minDiff && isFinite(_minDiff)) {
+ minDiff = Math.min(minDiff, _minDiff);
+ }
+ }
+ }
+
+ // if minDiff is still Infinity here, set it to 1
+ if(minDiff === Infinity) minDiff = 1;
+
+ for(i = 0; i < ohlcTracesOnThisXaxis.length; i++) {
+ ohlcTracesOnThisXaxis[i]._minDiff = minDiff;
+ }
+ }
+
+ return minDiff * trace.tickwidth;
+}
+
+module.exports = {
+ calc: calc,
+ calcCommon: calcCommon
+};
diff --git a/src/traces/ohlc/defaults.js b/src/traces/ohlc/defaults.js
index ee9a9948ce4..d8ddf1b03ce 100644
--- a/src/traces/ohlc/defaults.js
+++ b/src/traces/ohlc/defaults.js
@@ -11,13 +11,9 @@
var Lib = require('../../lib');
var handleOHLC = require('./ohlc_defaults');
-var handleDirectionDefaults = require('./direction_defaults');
var attributes = require('./attributes');
-var helpers = require('./helpers');
module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) {
- helpers.pushDummyTransformOpts(traceIn, traceOut);
-
function coerce(attr, dflt) {
return Lib.coerce(traceIn, traceOut, attributes, attr, dflt);
}
@@ -36,11 +32,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
coerce('text');
coerce('tickwidth');
+
+ layout._requestRangeslider[traceOut.xaxis] = true;
};
function handleDirection(traceIn, traceOut, coerce, direction) {
- handleDirectionDefaults(traceIn, traceOut, coerce, direction);
-
coerce(direction + '.line.color');
coerce(direction + '.line.width', traceOut.line.width);
coerce(direction + '.line.dash', traceOut.line.dash);
diff --git a/src/traces/ohlc/direction_defaults.js b/src/traces/ohlc/direction_defaults.js
deleted file mode 100644
index 4bed650bb69..00000000000
--- a/src/traces/ohlc/direction_defaults.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/**
-* Copyright 2012-2018, Plotly, Inc.
-* All rights reserved.
-*
-* This source code is licensed under the MIT license found in the
-* LICENSE file in the root directory of this source tree.
-*/
-
-
-'use strict';
-
-
-module.exports = function handleDirectionDefaults(traceIn, traceOut, coerce, direction) {
- coerce(direction + '.showlegend');
-
- // trace-wide *showlegend* overrides direction *showlegend*
- if(traceIn.showlegend === false) {
- traceOut[direction].showlegend = false;
- }
-
- var nameDflt = traceOut.name + ' - ' + direction;
-
- coerce(direction + '.name', nameDflt);
-};
diff --git a/src/traces/ohlc/helpers.js b/src/traces/ohlc/helpers.js
deleted file mode 100644
index eff64c4f05c..00000000000
--- a/src/traces/ohlc/helpers.js
+++ /dev/null
@@ -1,147 +0,0 @@
-/**
-* Copyright 2012-2018, 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 isNumeric = require('fast-isnumeric');
-
-var Lib = require('../../lib');
-
-// This routine gets called during the trace supply-defaults step.
-//
-// This is a hacky way to make 'ohlc' and 'candlestick' trace types
-// go through the transform machinery.
-//
-// Note that, we must mutate user data (here traceIn) as opposed
-// to full data (here traceOut) as - at the moment - transform
-// defaults (which are called after trace defaults) start
-// from a clear transforms container. The mutations inflicted are
-// cleared in exports.clearEphemeralTransformOpts.
-exports.pushDummyTransformOpts = function(traceIn, traceOut) {
- var transformOpts = {
-
- // give dummy transform the same type as trace
- type: traceOut.type,
-
- // track ephemeral transforms in user data
- _ephemeral: true
- };
-
- if(Array.isArray(traceIn.transforms)) {
- traceIn.transforms.push(transformOpts);
- }
- else {
- traceIn.transforms = [transformOpts];
- }
-};
-
-// This routine gets called during the transform supply-defaults step
-// where it clears ephemeral transform opts in user data
-// and effectively put back user date in its pre-supplyDefaults state.
-exports.clearEphemeralTransformOpts = function(traceIn) {
- var transformsIn = traceIn.transforms;
-
- if(!Array.isArray(transformsIn)) return;
-
- for(var i = 0; i < transformsIn.length; i++) {
- if(transformsIn[i]._ephemeral) transformsIn.splice(i, 1);
- }
-
- if(transformsIn.length === 0) delete traceIn.transforms;
-};
-
-// This routine gets called during the transform supply-defaults step
-// where it passes 'ohlc' and 'candlestick' attributes
-// (found the transform container via exports.makeTransform)
-// to the traceOut container such that they can
-// be compatible with filter and groupby transforms.
-//
-// Note that this routine only has an effect during the
-// second round of transform defaults done on generated traces
-exports.copyOHLC = function(container, traceOut) {
- if(container.open) traceOut.open = container.open;
- if(container.high) traceOut.high = container.high;
- if(container.low) traceOut.low = container.low;
- if(container.close) traceOut.close = container.close;
-};
-
-// This routine gets called during the applyTransform step.
-//
-// We need to track trace attributes and which direction
-// ('increasing' or 'decreasing')
-// the generated correspond to for the calcTransform step.
-//
-// To make sure that the attributes reach the calcTransform,
-// store it in the transform opts object.
-exports.makeTransform = function(traceIn, state, direction) {
- var out = Lib.extendFlat([], traceIn.transforms);
-
- out[state.transformIndex] = {
- type: traceIn.type,
- direction: direction,
-
- // these are copied to traceOut during exports.copyOHLC
- open: traceIn.open,
- high: traceIn.high,
- low: traceIn.low,
- close: traceIn.close
- };
-
- return out;
-};
-
-exports.getFilterFn = function(direction) {
- return new _getFilterFn(direction);
-};
-
-function _getFilterFn(direction) {
- // we're optimists - before we have any changing data, assume increasing
- var isPrevIncreasing = true;
- var cPrev = null;
-
- function _isIncreasing(o, c) {
- if(o === c) {
- if(c > cPrev) {
- isPrevIncreasing = true; // increasing
- } else if(c < cPrev) {
- isPrevIncreasing = false; // decreasing
- }
- // else isPrevIncreasing is not changed
- }
- else isPrevIncreasing = (o < c);
- cPrev = c;
- return isPrevIncreasing;
- }
-
- function isIncreasing(o, c) {
- return isNumeric(o) && isNumeric(c) && _isIncreasing(+o, +c);
- }
-
- function isDecreasing(o, c) {
- return isNumeric(o) && isNumeric(c) && !_isIncreasing(+o, +c);
- }
-
- return direction === 'increasing' ? isIncreasing : isDecreasing;
-}
-
-exports.addRangeSlider = function(data, layout) {
- var hasOneVisibleTrace = false;
-
- for(var i = 0; i < data.length; i++) {
- if(data[i].visible === true) {
- hasOneVisibleTrace = true;
- break;
- }
- }
-
- if(hasOneVisibleTrace) {
- if(!layout.xaxis) layout.xaxis = {};
- if(!layout.xaxis.rangeslider) layout.xaxis.rangeslider = {};
- }
-};
diff --git a/src/traces/ohlc/hover.js b/src/traces/ohlc/hover.js
new file mode 100644
index 00000000000..ef4ff08d7ac
--- /dev/null
+++ b/src/traces/ohlc/hover.js
@@ -0,0 +1,109 @@
+/**
+* Copyright 2012-2018, 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 Axes = require('../../plots/cartesian/axes');
+var Fx = require('../../components/fx');
+var Color = require('../../components/color');
+var fillHoverText = require('../scatter/fill_hover_text');
+
+var DIRSYMBOL = {
+ increasing: '▲',
+ decreasing: '▼'
+};
+
+module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
+ var cd = pointData.cd;
+ var xa = pointData.xa;
+ var ya = pointData.ya;
+ var trace = cd[0].trace;
+ var t = cd[0].t;
+
+ var type = trace.type;
+ var minAttr = type === 'ohlc' ? 'l' : 'min';
+ var maxAttr = type === 'ohlc' ? 'h' : 'max';
+
+ // potentially shift xval for grouped candlesticks
+ var centerShift = t.bPos || 0;
+ var x0 = xval - centerShift;
+
+ // ohlc and candlestick call displayHalfWidth different things...
+ var displayHalfWidth = t.bdPos || t.tickLen;
+ var hoverHalfWidth = t.wHover;
+
+ // if two items are overlaying, let the narrowest one win
+ var pseudoDistance = Math.min(1, displayHalfWidth / Math.abs(xa.r2c(xa.range[1]) - xa.r2c(xa.range[0])));
+ var hoverPseudoDistance = pointData.maxHoverDistance - pseudoDistance;
+ var spikePseudoDistance = pointData.maxSpikeDistance - pseudoDistance;
+
+ function dx(di) {
+ var pos = di.pos - x0;
+ return Fx.inbox(pos - hoverHalfWidth, pos + hoverHalfWidth, hoverPseudoDistance);
+ }
+
+ function dy(di) {
+ return Fx.inbox(di[minAttr] - yval, di[maxAttr] - yval, hoverPseudoDistance);
+ }
+
+ function dxy(di) { return (dx(di) + dy(di)) / 2; }
+ var distfn = Fx.getDistanceFunction(hovermode, dx, dy, dxy);
+ Fx.getClosest(cd, distfn, pointData);
+
+ // skip the rest (for this trace) if we didn't find a close point
+ if(pointData.index === false) return [];
+
+ // we don't make a calcdata point if we're missing any piece (x/o/h/l/c)
+ // so we need to fix the index here to point to the data arrays
+ var cdIndex = pointData.index;
+ var di = cd[cdIndex];
+ var i = pointData.index = di.i;
+
+ var dir = di.dir;
+ var container = trace[dir];
+ var lc = container.line.color;
+
+ if(Color.opacity(lc) && container.line.width) pointData.color = lc;
+ else pointData.color = container.fillcolor;
+
+ pointData.x0 = xa.c2p(di.pos + centerShift - displayHalfWidth, true);
+ pointData.x1 = xa.c2p(di.pos + centerShift + displayHalfWidth, true);
+
+ pointData.xLabelVal = di.pos;
+
+ pointData.spikeDistance = dxy(di) * spikePseudoDistance / hoverPseudoDistance;
+ pointData.xSpike = xa.c2p(di.pos, true);
+
+ function getLabelLine(attr) {
+ return t.labels[attr] + Axes.hoverLabelText(ya, trace[attr][i]);
+ }
+
+ var hoverinfo = trace.hoverinfo;
+ var hoverParts = hoverinfo.split('+');
+ var isAll = hoverinfo === 'all';
+ var hasY = isAll || hoverParts.indexOf('y') !== -1;
+ var hasText = isAll || hoverParts.indexOf('text') !== -1;
+
+ var textParts = hasY ? [
+ getLabelLine('open'),
+ getLabelLine('high'),
+ getLabelLine('low'),
+ getLabelLine('close') + ' ' + DIRSYMBOL[dir]
+ ] : [];
+ if(hasText) fillHoverText(di, trace, textParts);
+
+ // don't make .yLabelVal or .text, since we're managing hoverinfo
+ // put it all in .extraText
+ pointData.extraText = textParts.join('
');
+
+ // this puts the label *and the spike* at the midpoint of the box, ie
+ // halfway between open and close, not between high and low.
+ pointData.y0 = pointData.y1 = ya.c2p(di.yc, true);
+
+ return [pointData];
+};
diff --git a/src/traces/ohlc/index.js b/src/traces/ohlc/index.js
index cabac0d8568..8d116b066a8 100644
--- a/src/traces/ohlc/index.js
+++ b/src/traces/ohlc/index.js
@@ -8,8 +8,6 @@
'use strict';
-var Registry = require('../../registry');
-
module.exports = {
moduleType: 'trace',
name: 'ohlc',
@@ -26,14 +24,16 @@ module.exports = {
'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',
+ 'By default, increasing items are drawn in green whereas',
'decreasing are drawn in red.'
].join(' ')
},
attributes: require('./attributes'),
supplyDefaults: require('./defaults'),
+ calc: require('./calc').calc,
+ plot: require('./plot'),
+ style: require('./style'),
+ hoverPoints: require('./hover'),
+ selectPoints: require('./select')
};
-
-Registry.register(require('../scatter'));
-Registry.register(require('./transform'));
diff --git a/src/traces/ohlc/ohlc_defaults.js b/src/traces/ohlc/ohlc_defaults.js
index 261e92b7922..65c9fe14e0d 100644
--- a/src/traces/ohlc/ohlc_defaults.js
+++ b/src/traces/ohlc/ohlc_defaults.js
@@ -13,11 +13,11 @@ var Registry = require('../../registry');
module.exports = function handleOHLC(traceIn, traceOut, coerce, layout) {
- var x = coerce('x'),
- open = coerce('open'),
- high = coerce('high'),
- low = coerce('low'),
- close = coerce('close');
+ var x = coerce('x');
+ var open = coerce('open');
+ var high = coerce('high');
+ var low = coerce('low');
+ var close = coerce('close');
var handleCalendarDefaults = Registry.getComponentMethod('calendars', 'handleTraceDefaults');
handleCalendarDefaults(traceIn, traceOut, ['x'], layout);
@@ -28,7 +28,7 @@ module.exports = function handleOHLC(traceIn, traceOut, coerce, layout) {
if(x) len = Math.min(len, x.length);
- traceOut._inputLength = len;
+ traceOut._length = len;
return len;
};
diff --git a/src/traces/ohlc/plot.js b/src/traces/ohlc/plot.js
new file mode 100644
index 00000000000..6a54b48a347
--- /dev/null
+++ b/src/traces/ohlc/plot.js
@@ -0,0 +1,66 @@
+/**
+* Copyright 2012-2018, 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 d3 = require('d3');
+
+var Lib = require('../../lib');
+
+module.exports = function plot(gd, plotinfo, cdOHLC) {
+ var xa = plotinfo.xaxis;
+ var ya = plotinfo.yaxis;
+
+ var ohlcLayer = plotinfo.plot.select('g.ohlclayer');
+
+ var traces = ohlcLayer.selectAll('g.trace')
+ .data(cdOHLC, function(d) { return d[0].trace.uid; });
+
+ traces.enter().append('g')
+ .attr('class', 'trace ohlc');
+
+ traces.exit().remove();
+
+ traces.order();
+
+ traces.each(function(d) {
+ var cd0 = d[0];
+ var t = cd0.t;
+ var trace = cd0.trace;
+ var sel = d3.select(this);
+ if(!plotinfo.isRangePlot) cd0.node3 = sel;
+
+ if(trace.visible !== true || t.empty) {
+ sel.remove();
+ return;
+ }
+
+ var tickLen = t.tickLen;
+
+ var paths = sel.selectAll('path').data(Lib.identity);
+
+ paths.enter().append('path');
+
+ paths.exit().remove();
+
+ paths.attr('d', function(d) {
+ var x = xa.c2p(d.pos, true);
+ var xo = xa.c2p(d.pos - tickLen, true);
+ var xc = xa.c2p(d.pos + tickLen, true);
+
+ var yo = ya.c2p(d.o, true);
+ var yh = ya.c2p(d.h, true);
+ var yl = ya.c2p(d.l, true);
+ var yc = ya.c2p(d.c, true);
+
+ return 'M' + xo + ',' + yo + 'H' + x +
+ 'M' + x + ',' + yh + 'V' + yl +
+ 'M' + xc + ',' + yc + 'H' + x;
+ });
+ });
+};
diff --git a/src/traces/ohlc/select.js b/src/traces/ohlc/select.js
new file mode 100644
index 00000000000..29bed35028f
--- /dev/null
+++ b/src/traces/ohlc/select.js
@@ -0,0 +1,43 @@
+/**
+* Copyright 2012-2018, 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 selectPoints(searchInfo, polygon) {
+ var cd = searchInfo.cd;
+ var xa = searchInfo.xaxis;
+ var ya = searchInfo.yaxis;
+ var selection = [];
+ var i;
+ // for (potentially grouped) candlesticks
+ var posOffset = cd[0].t.bPos || 0;
+
+ if(polygon === false) {
+ // clear selection
+ for(i = 0; i < cd.length; i++) {
+ cd[i].selected = 0;
+ }
+ } else {
+ for(i = 0; i < cd.length; i++) {
+ var di = cd[i];
+
+ if(polygon.contains([xa.c2p(di.pos + posOffset), ya.c2p(di.yc)])) {
+ selection.push({
+ pointNumber: di.i,
+ x: xa.c2d(di.pos),
+ y: ya.c2d(di.yc)
+ });
+ di.selected = 1;
+ } else {
+ di.selected = 0;
+ }
+ }
+ }
+
+ return selection;
+};
diff --git a/src/traces/ohlc/style.js b/src/traces/ohlc/style.js
new file mode 100644
index 00000000000..db85aea96bb
--- /dev/null
+++ b/src/traces/ohlc/style.js
@@ -0,0 +1,35 @@
+/**
+* Copyright 2012-2018, 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 d3 = require('d3');
+var Drawing = require('../../components/drawing');
+var Color = require('../../components/color');
+
+module.exports = function style(gd, cd) {
+ var s = cd ? cd[0].node3 : d3.select(gd).selectAll('g.ohlclayer').selectAll('g.trace');
+
+ s.style('opacity', function(d) {
+ return d[0].trace.opacity;
+ });
+
+ s.each(function(d) {
+ var trace = d[0].trace;
+
+ d3.select(this).selectAll('path').each(function(di) {
+ var dirLine = trace[di.dir].line;
+ d3.select(this)
+ .style('fill', 'none')
+ .call(Color.stroke, dirLine.color)
+ .call(Drawing.dashLine, dirLine.dash, dirLine.width)
+ // TODO: custom selection style for OHLC
+ .style('opacity', trace.selectedpoints && !di.selected ? 0.3 : 1);
+ });
+ });
+};
diff --git a/src/traces/ohlc/transform.js b/src/traces/ohlc/transform.js
deleted file mode 100644
index af93b1d5946..00000000000
--- a/src/traces/ohlc/transform.js
+++ /dev/null
@@ -1,268 +0,0 @@
-/**
-* Copyright 2012-2018, 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 isNumeric = require('fast-isnumeric');
-
-var Lib = require('../../lib');
-var _ = Lib._;
-var helpers = require('./helpers');
-var Axes = require('../../plots/cartesian/axes');
-var axisIds = require('../../plots/cartesian/axis_ids');
-
-exports.moduleType = 'transform';
-
-exports.name = 'ohlc';
-
-exports.attributes = {};
-
-exports.supplyDefaults = function(transformIn, traceOut, layout, traceIn) {
- helpers.clearEphemeralTransformOpts(traceIn);
- helpers.copyOHLC(transformIn, traceOut);
-
- return transformIn;
-};
-
-exports.transform = function transform(dataIn, state) {
- var dataOut = [];
-
- for(var i = 0; i < dataIn.length; i++) {
- var traceIn = dataIn[i];
-
- if(traceIn.type !== 'ohlc') {
- dataOut.push(traceIn);
- continue;
- }
-
- dataOut.push(
- makeTrace(traceIn, state, 'increasing'),
- makeTrace(traceIn, state, 'decreasing')
- );
- }
-
- helpers.addRangeSlider(dataOut, state.layout);
-
- return dataOut;
-};
-
-function makeTrace(traceIn, state, direction) {
- var len = traceIn._inputLength;
- var traceOut = {
- type: 'scatter',
- mode: 'lines',
- connectgaps: false,
-
- visible: traceIn.visible,
- opacity: traceIn.opacity,
- xaxis: traceIn.xaxis,
- yaxis: traceIn.yaxis,
-
- hoverinfo: makeHoverInfo(traceIn),
- transforms: helpers.makeTransform(traceIn, state, direction),
- _inputLength: len
- };
-
- // the rest of below may not have been coerced
-
- var directionOpts = traceIn[direction];
-
- if(directionOpts) {
- Lib.extendFlat(traceOut, {
-
- // to make autotype catch date axes soon!!
- x: traceIn.x || [0],
- xcalendar: traceIn.xcalendar,
-
- // concat low and high to get correct autorange
- y: traceIn.low.slice(0, len).concat(traceIn.high.slice(0, len)),
-
- text: traceIn.text,
-
- name: directionOpts.name,
- showlegend: directionOpts.showlegend,
- line: directionOpts.line
- });
- }
-
- return traceOut;
-}
-
-// Let scatter hoverPoint format 'x' coordinates, if desired.
-//
-// Note that, this solution isn't perfect: it shows open and close
-// values at slightly different 'x' coordinates then the rest of the
-// segments, but is for more robust than calling `Axes.tickText` during
-// calcTransform.
-//
-// A future iteration should perhaps try to add a hook for transforms in
-// the hoverPoints handlers.
-function makeHoverInfo(traceIn) {
- var hoverinfo = traceIn.hoverinfo;
-
- if(hoverinfo === 'all') return 'x+text+name';
-
- var parts = hoverinfo.split('+'),
- indexOfY = parts.indexOf('y'),
- indexOfText = parts.indexOf('text');
-
- if(indexOfY !== -1) {
- parts.splice(indexOfY, 1);
-
- if(indexOfText === -1) parts.push('text');
- }
-
- return parts.join('+');
-}
-
-exports.calcTransform = function calcTransform(gd, trace, opts) {
- var direction = opts.direction,
- filterFn = helpers.getFilterFn(direction);
-
- var xa = axisIds.getFromTrace(gd, trace, 'x'),
- ya = axisIds.getFromTrace(gd, trace, 'y'),
- tickWidth = convertTickWidth(gd, xa, trace);
-
- var open = trace.open,
- high = trace.high,
- low = trace.low,
- close = trace.close,
- textIn = trace.text;
-
- var openName = _(gd, 'open:') + ' ';
- var highName = _(gd, 'high:') + ' ';
- var lowName = _(gd, 'low:') + ' ';
- var closeName = _(gd, 'close:') + ' ';
-
- var len = trace._inputLength,
- x = [],
- y = [],
- textOut = [];
-
- var appendX;
- if(trace._fullInput.x) {
- appendX = function(i) {
- var xi = trace.x[i],
- xcalendar = trace.xcalendar,
- xcalc = xa.d2c(xi, 0, xcalendar);
-
- x.push(
- xa.c2d(xcalc - tickWidth, 0, xcalendar),
- xi, xi, xi, xi,
- xa.c2d(xcalc + tickWidth, 0, xcalendar),
- null);
- };
- }
- else {
- appendX = function(i) {
- x.push(
- i - tickWidth,
- i, i, i, i,
- i + 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 getTextItem = Array.isArray(textIn) ?
- function(i) { return textIn[i] || ''; } :
- function() { return textIn; };
-
- var appendText = function(i, o, h, l, c) {
- var t = [];
-
- if(hasY) {
- t.push(openName + format(ya, o));
- t.push(highName + format(ya, h));
- t.push(lowName + format(ya, l));
- t.push(closeName + 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]) && isNumeric(high[i]) && isNumeric(low[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;
- trace._length = x.length;
-};
-
-function convertTickWidth(gd, xa, trace) {
- var fullInput = trace._fullInput,
- tickWidth = fullInput.tickwidth,
- minDiff = fullInput._minDiff;
-
- if(!minDiff) {
- var fullData = gd._fullData,
- ohlcTracesOnThisXaxis = [];
-
- minDiff = Infinity;
-
- // find min x-coordinates difference of all traces
- // attached to this x-axis and stash the result
-
- var i;
-
- for(i = 0; i < fullData.length; i++) {
- var _trace = fullData[i]._fullInput;
-
- if(_trace.type === 'ohlc' &&
- _trace.visible === true &&
- _trace.xaxis === xa._id
- ) {
- ohlcTracesOnThisXaxis.push(_trace);
-
- // - _trace.x may be undefined here,
- // it is filled later in calcTransform
- //
- // - handle trace of length 1 separately.
-
- if(_trace.x && _trace.x.length > 1) {
- var xcalc = Lib.simpleMap(_trace.x, xa.d2c, 0, trace.xcalendar),
- _minDiff = Lib.distinctVals(xcalc).minDiff;
- minDiff = Math.min(minDiff, _minDiff);
- }
- }
- }
-
- // if minDiff is still Infinity here, set it to 1
- if(minDiff === Infinity) minDiff = 1;
-
- for(i = 0; i < ohlcTracesOnThisXaxis.length; i++) {
- ohlcTracesOnThisXaxis[i]._minDiff = minDiff;
- }
- }
-
- return minDiff * tickWidth;
-}
diff --git a/src/traces/parcoords/base_plot.js b/src/traces/parcoords/base_plot.js
index 25fbfd48b29..f50461e77c3 100644
--- a/src/traces/parcoords/base_plot.js
+++ b/src/traces/parcoords/base_plot.js
@@ -18,7 +18,7 @@ var PARCOORDS = 'parcoords';
exports.name = PARCOORDS;
exports.plot = function(gd) {
- var calcData = getModuleCalcData(gd.calcdata, PARCOORDS);
+ var calcData = getModuleCalcData(gd.calcdata, PARCOORDS)[0];
if(calcData.length) parcoordsPlot(gd, calcData);
};
diff --git a/src/traces/pie/base_plot.js b/src/traces/pie/base_plot.js
index b6dc4549031..7bcb8e4462c 100644
--- a/src/traces/pie/base_plot.js
+++ b/src/traces/pie/base_plot.js
@@ -15,7 +15,7 @@ exports.name = 'pie';
exports.plot = function(gd) {
var Pie = Registry.getModule('pie');
- var cdPie = getModuleCalcData(gd.calcdata, Pie);
+ var cdPie = getModuleCalcData(gd.calcdata, Pie)[0];
if(cdPie.length) Pie.plot(gd, cdPie);
};
diff --git a/src/traces/sankey/base_plot.js b/src/traces/sankey/base_plot.js
index 5c1afc1dbb0..5aaac032417 100644
--- a/src/traces/sankey/base_plot.js
+++ b/src/traces/sankey/base_plot.js
@@ -22,7 +22,7 @@ exports.baseLayoutAttrOverrides = overrideAll({
}, 'plot', 'nested');
exports.plot = function(gd) {
- var calcData = getModuleCalcData(gd.calcdata, SANKEY);
+ var calcData = getModuleCalcData(gd.calcdata, SANKEY)[0];
plot(gd, calcData);
};
diff --git a/src/traces/scatter/plot.js b/src/traces/scatter/plot.js
index db0456edf8a..c9e92ee72e0 100644
--- a/src/traces/scatter/plot.js
+++ b/src/traces/scatter/plot.js
@@ -21,7 +21,7 @@ var linkTraces = require('./link_traces');
var polygonTester = require('../../lib/polygon').tester;
module.exports = function plot(gd, plotinfo, cdscatter, transitionOpts, makeOnCompleteCallback) {
- var i, uids, selection, join, onComplete;
+ var i, uids, join, onComplete;
var scatterlayer = plotinfo.plot.select('g.scatterlayer');
@@ -30,9 +30,8 @@ module.exports = function plot(gd, plotinfo, cdscatter, transitionOpts, makeOnCo
var isFullReplot = !transitionOpts;
var hasTransition = !!transitionOpts && transitionOpts.duration > 0;
- selection = scatterlayer.selectAll('g.trace');
-
- join = selection.data(cdscatter, function(d) { return d[0].trace.uid; });
+ join = scatterlayer.selectAll('g.trace')
+ .data(cdscatter, function(d) { return d[0].trace.uid; });
// Append new traces:
join.enter().append('g')
@@ -177,7 +176,7 @@ function plotOne(gd, idx, plotinfo, cdscatter, cdscatterAll, element, transition
if(ownFillDir !== 'x' && ownFillDir !== 'y') ownFillDir = '';
// store node for tweaking by selectPoints
- cdscatter[0].node3 = tr;
+ if(!plotinfo.isRangePlot) cdscatter[0].node3 = tr;
var prevRevpath = '';
var prevPolygons = [];
diff --git a/src/traces/splom/base_plot.js b/src/traces/splom/base_plot.js
index 73fbc59d11f..10b062e2c38 100644
--- a/src/traces/splom/base_plot.js
+++ b/src/traces/splom/base_plot.js
@@ -22,7 +22,7 @@ var SPLOM = 'splom';
function plot(gd) {
var fullLayout = gd._fullLayout;
var _module = Registry.getModule(SPLOM);
- var splomCalcData = getModuleCalcData(gd.calcdata, _module);
+ var splomCalcData = getModuleCalcData(gd.calcdata, _module)[0];
prepareRegl(gd, ['ANGLE_instanced_arrays', 'OES_element_index_uint']);
diff --git a/src/traces/table/base_plot.js b/src/traces/table/base_plot.js
index 9004d109f5f..6e074e8cc44 100644
--- a/src/traces/table/base_plot.js
+++ b/src/traces/table/base_plot.js
@@ -16,7 +16,7 @@ var TABLE = 'table';
exports.name = TABLE;
exports.plot = function(gd) {
- var calcData = getModuleCalcData(gd.calcdata, TABLE);
+ var calcData = getModuleCalcData(gd.calcdata, TABLE)[0];
if(calcData.length) tablePlot(gd, calcData);
};
diff --git a/src/traces/violin/index.js b/src/traces/violin/index.js
index 26e39f644f6..ac0d06ac031 100644
--- a/src/traces/violin/index.js
+++ b/src/traces/violin/index.js
@@ -23,7 +23,7 @@ module.exports = {
moduleType: 'trace',
name: 'violin',
basePlotModule: require('../../plots/cartesian'),
- categories: ['cartesian', 'svg', 'symbols', 'oriented', 'box-violin', 'showLegend', 'draggedPts'],
+ categories: ['cartesian', 'svg', 'symbols', 'oriented', 'box-violin', 'showLegend', 'draggedPts', 'violinLayout'],
meta: {
description: [
'In vertical (horizontal) violin plots,',
diff --git a/src/traces/violin/plot.js b/src/traces/violin/plot.js
index a0c670c3467..4b89227cad6 100644
--- a/src/traces/violin/plot.js
+++ b/src/traces/violin/plot.js
@@ -42,13 +42,18 @@ module.exports = function plot(gd, plotinfo, cd) {
var cd0 = d[0];
var t = cd0.t;
var trace = cd0.trace;
- var sel = cd0.node3 = d3.select(this);
+ var sel = d3.select(this);
+ if(!plotinfo.isRangePlot) cd0.node3 = sel;
var numViolins = fullLayout._numViolins;
var group = (fullLayout.violinmode === 'group' && numViolins > 1);
+ var groupFraction = 1 - fullLayout.violingap;
// violin max half width
- var bdPos = t.bdPos = t.dPos * (1 - fullLayout.violingap) * (1 - fullLayout.violingroupgap) / (group ? numViolins : 1);
+ var bdPos = t.bdPos = t.dPos * groupFraction * (1 - fullLayout.violingroupgap) / (group ? numViolins : 1);
// violin center offset
- var bPos = t.bPos = group ? 2 * t.dPos * (-0.5 + (t.num + 0.5) / numViolins) * (1 - fullLayout.violingap) : 0;
+ var bPos = t.bPos = group ? 2 * t.dPos * (-0.5 + (t.num + 0.5) / numViolins) * groupFraction : 0;
+ // half-width within which to accept hover for this violin
+ // always split the distance to the closest violin
+ t.wHover = t.dPos * (group ? groupFraction / numViolins : 1);
if(trace.visible !== true || t.empty) {
d3.select(this).remove();
diff --git a/test/image/baselines/candlestick_double-y-axis.png b/test/image/baselines/candlestick_double-y-axis.png
index f423ad6fd0f..7db85e0a0c8 100644
Binary files a/test/image/baselines/candlestick_double-y-axis.png and b/test/image/baselines/candlestick_double-y-axis.png differ
diff --git a/test/image/baselines/candlestick_rangeslider_thai.png b/test/image/baselines/candlestick_rangeslider_thai.png
index 76894bfb9de..901883613cd 100644
Binary files a/test/image/baselines/candlestick_rangeslider_thai.png and b/test/image/baselines/candlestick_rangeslider_thai.png differ
diff --git a/test/image/baselines/finance_style.png b/test/image/baselines/finance_style.png
index ac72e76f013..a969a040c7e 100644
Binary files a/test/image/baselines/finance_style.png and b/test/image/baselines/finance_style.png differ
diff --git a/test/image/baselines/finance_subplots_categories.png b/test/image/baselines/finance_subplots_categories.png
new file mode 100644
index 00000000000..737202f3241
Binary files /dev/null and b/test/image/baselines/finance_subplots_categories.png differ
diff --git a/test/image/baselines/ohlc_first.png b/test/image/baselines/ohlc_first.png
index ddac6bf2b50..8615422ad20 100644
Binary files a/test/image/baselines/ohlc_first.png and b/test/image/baselines/ohlc_first.png differ
diff --git a/test/image/mocks/finance_style.json b/test/image/mocks/finance_style.json
index 7adc4f2bdd6..b0f274298a3 100644
--- a/test/image/mocks/finance_style.json
+++ b/test/image/mocks/finance_style.json
@@ -121,7 +121,7 @@
"decreasing": {
"line": {
"color": "rgb(128, 128, 128)",
- "dash": "dash"
+ "dash": "dot"
}
},
"line": {
@@ -268,7 +268,7 @@
"calendar": "islamic",
"title": "Islamic dates"
},
- "showlegend": false,
+ "showlegend": true,
"height": 450,
"width": 1100,
"autosize": true
diff --git a/test/image/mocks/finance_subplots_categories.json b/test/image/mocks/finance_subplots_categories.json
new file mode 100644
index 00000000000..5a12d9facb2
--- /dev/null
+++ b/test/image/mocks/finance_subplots_categories.json
@@ -0,0 +1,73 @@
+{
+ "data": [{
+ "type": "ohlc",
+ "x": [
+ "2018-04-17", "2018-04-18", "2018-04-19", "2018-04-20",
+ "2018-04-23", "2018-04-24", "2018-04-25", "2018-04-26", "2018-04-27"
+ ],
+ "open": [10, 11, 12, 13, 12, 13, 14, 15, 16],
+ "high": [15, 16, 17, 18, 17, 18, 19, 20, 21],
+ "low": [7, 8, 9, 10, 9, 10, 11, 12, 13],
+ "close": [9, 10, 12, 13, 13, 12, 14, 14, 17],
+ "name": "Date OHLC"
+ }, {
+ "type": "candlestick",
+ "x": [
+ "2018-04-17", "2018-04-18", "2018-04-19", "2018-04-20",
+ "2018-04-23", "2018-04-24", "2018-04-25", "2018-04-26", "2018-04-27"
+ ],
+ "open": [20, 21, 22, 23, 22, 23, 24, 25, 26],
+ "high": [25, 26, 27, 28, 27, 28, 29, 30, 31],
+ "low": [17, 18, 19, 20, 19, 20, 21, 22, 23],
+ "close": [19, 20, 22, 23, 23, 22, 24, 24, 27],
+ "name": "Date Candlestick"
+ }, {
+ "type": "ohlc",
+ "xaxis": "x2",
+ "x": [
+ "2018-04-17", "2018-04-18", "2018-04-19", "2018-04-20",
+ "2018-04-23", "2018-04-24", "2018-04-25", "2018-04-26", "2018-04-27"
+ ],
+ "open": [10, 11, 12, 13, 12, 13, 14, 15, 16],
+ "high": [15, 16, 17, 18, 17, 18, 19, 20, 21],
+ "low": [7, 8, 9, 10, 9, 10, 11, 12, 13],
+ "close": [9, 10, 12, 13, 13, 12, 14, 14, 17],
+ "increasing": {"line": {"color": "orange"}},
+ "decreasing": {"line": {"color": "blue"}},
+ "name": "Category OHLC"
+ }, {
+ "type": "candlestick",
+ "xaxis": "x2",
+ "x": [
+ "2018-04-17", "2018-04-18", "2018-04-19", "2018-04-20",
+ "2018-04-23", "2018-04-24", "2018-04-25", "2018-04-26", "2018-04-27"
+ ],
+ "open": [20, 21, 22, 23, 22, 23, 24, 25, 26],
+ "high": [25, 26, 27, 28, 27, 28, 29, 30, 31],
+ "low": [17, 18, 19, 20, 19, 20, 21, 22, 23],
+ "close": [19, 20, 22, 23, 23, 22, 24, 24, 27],
+ "increasing": {"line": {"color": "orange"}},
+ "decreasing": {"line": {"color": "blue"}},
+ "name": "Category Candlestick"
+ }, {
+ "type": "box",
+ "x": [
+ "2018-04-23", "2018-04-23", "2018-04-23", "2018-04-23", "2018-04-23",
+ "2018-04-23", "2018-04-23", "2018-04-23", "2018-04-23", "2018-04-23",
+ "2018-04-24", "2018-04-24", "2018-04-24", "2018-04-24", "2018-04-24",
+ "2018-04-24", "2018-04-24", "2018-04-24", "2018-04-24", "2018-04-24"
+ ],
+ "y": [
+ 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
+ 20, 21, 22, 23, 24, 25, 26, 27, 28, 29
+ ],
+ "name": "Box"
+ }],
+ "layout": {
+ "xaxis": {"domain": [0, 0.45], "title": "Date"},
+ "xaxis2": {"domain": [0.55, 1], "title": "Category", "type": "category"},
+ "width": 800,
+ "height": 500,
+ "boxgroupgap": 0
+ }
+}
diff --git a/test/jasmine/bundle_tests/finance_test.js b/test/jasmine/bundle_tests/finance_test.js
index b56e10e14b6..33f26511b92 100644
--- a/test/jasmine/bundle_tests/finance_test.js
+++ b/test/jasmine/bundle_tests/finance_test.js
@@ -13,16 +13,18 @@ describe('Bundle with finance trace type', function() {
var mock = require('@mocks/finance_style.json');
- it('should register the correct trace modules for the generated traces', function() {
+ it('should not register transforms anymore', function() {
var transformModules = Object.keys(Plotly.Plots.transformsRegistry);
- expect(transformModules).toEqual(['ohlc', 'candlestick']);
+ expect(transformModules).toEqual([]);
});
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']);
+ // scatter is registered no matter what
+ // ohlc uses some parts of box by direct require but does not need to register it.
+ expect(traceModules).toEqual(['scatter', 'ohlc', 'candlestick']);
});
it('should graph ohlc and candlestick traces', function(done) {
@@ -30,8 +32,8 @@ describe('Bundle with finance trace type', function() {
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);
+ expect(gSubplot.selectAll('g.trace.ohlc').size()).toEqual(1);
+ expect(gSubplot.selectAll('g.trace.boxes').size()).toEqual(1);
destroyGraphDiv();
done();
diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js
index dd95ea474b6..22c99aac4bc 100644
--- a/test/jasmine/tests/axes_test.js
+++ b/test/jasmine/tests/axes_test.js
@@ -183,7 +183,8 @@ describe('Test axes', function() {
_has: Plots._hasPlotType,
_basePlotModules: [],
_dfltTitle: {x: 'x', y: 'y'},
- _subplots: {cartesian: ['xy'], xaxis: ['x'], yaxis: ['y']}
+ _subplots: {cartesian: ['xy'], xaxis: ['x'], yaxis: ['y']},
+ _requestRangeslider: {}
};
fullData = [];
});
diff --git a/test/jasmine/tests/box_test.js b/test/jasmine/tests/box_test.js
index a50e578ad1c..d2d76b5ac58 100644
--- a/test/jasmine/tests/box_test.js
+++ b/test/jasmine/tests/box_test.js
@@ -172,7 +172,7 @@ describe('Test box hover:', function() {
return Plotly.plot(gd, fig).then(function() {
mouseEvent('mousemove', pos[0], pos[1]);
- assertHoverLabelContent(specs);
+ assertHoverLabelContent(specs, specs.desc);
});
}
diff --git a/test/jasmine/tests/finance_test.js b/test/jasmine/tests/finance_test.js
index 770fe352dba..7d0f4c85a76 100644
--- a/test/jasmine/tests/finance_test.js
+++ b/test/jasmine/tests/finance_test.js
@@ -6,6 +6,7 @@ var d3 = require('d3');
var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');
var supplyAllDefaults = require('../assets/supply_defaults');
+var failTest = require('../assets/fail_test');
var mock0 = {
open: [33.01, 33.31, 33.50, 32.06, 34.12, 33.05, 33.31, 33.50],
@@ -47,13 +48,8 @@ describe('finance charts defaults:', function() {
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']);
+ // not sure this test is really necessary anymore, since these are real traces...
+ expect(out._fullData.length).toEqual(2);
});
it('should not mutate user data', function() {
@@ -98,7 +94,7 @@ describe('finance charts defaults:', function() {
var out = _supply([trace0, trace1]);
expect(out.data.length).toEqual(2);
- expect(out._fullData.length).toEqual(4);
+ expect(out._fullData.length).toEqual(2);
var transformTypesIn = out.data.map(function(trace) {
return trace.transforms.map(function(opts) {
@@ -114,21 +110,20 @@ describe('finance charts defaults:', function() {
});
});
- // dummy 'ohlc' and 'candlestick' transforms are pushed at the end
- // of the 'transforms' array container
-
- expect(transformTypesOut).toEqual([
- ['filter', 'ohlc'], ['filter', 'ohlc'],
- ['filter', 'candlestick'], ['filter', 'candlestick']
- ]);
+ expect(transformTypesOut).toEqual([ ['filter'], ['filter'] ]);
});
- it('should slice data array according to minimum supplied length', function() {
+ it('should not slice data arrays but record minimum supplied length', function() {
- function assertDataLength(fullTrace, len) {
+ function assertDataLength(trace, fullTrace, len) {
expect(fullTrace.visible).toBe(true);
- expect(fullTrace._inputLength).toBe(len);
+ expect(fullTrace._length).toBe(len);
+
+ expect(fullTrace.open).toBe(trace.open);
+ expect(fullTrace.close).toBe(trace.close);
+ expect(fullTrace.high).toBe(trace.high);
+ expect(fullTrace.low).toBe(trace.low);
}
var trace0 = Lib.extendDeep({}, mock0, { type: 'ohlc' });
@@ -139,15 +134,11 @@ describe('finance charts defaults:', function() {
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);
+ assertDataLength(trace0, out._fullData[0], 5);
+ assertDataLength(trace1, out._fullData[1], 4);
expect(out._fullData[0]._fullInput.x).toBeUndefined();
- expect(out._fullData[1]._fullInput.x).toBeUndefined();
- expect(out._fullData[2]._fullInput.x).toBeDefined();
- expect(out._fullData[3]._fullInput.x).toBeDefined();
+ expect(out._fullData[1]._fullInput.x).toBeDefined();
});
it('should set visible to *false* when minimum supplied length is 0', function() {
@@ -160,13 +151,13 @@ describe('finance charts defaults:', function() {
var out = _supply([trace0, trace1]);
expect(out.data.length).toEqual(2);
- expect(out._fullData.length).toEqual(4);
+ expect(out._fullData.length).toEqual(2);
var visibilities = out._fullData.map(function(fullTrace) {
return fullTrace.visible;
});
- expect(visibilities).toEqual([false, false, false, false]);
+ expect(visibilities).toEqual([false, false]);
});
it('direction *showlegend* should be inherited from trace-wide *showlegend*', function() {
@@ -188,10 +179,10 @@ describe('finance charts defaults:', function() {
return fullTrace.showlegend;
});
- expect(visibilities).toEqual([false, false, false, false]);
+ expect(visibilities).toEqual([false, false]);
});
- it('direction *name* should be inherited from trace-wide *name*', function() {
+ it('direction *name* should be ignored if there\'s a trace-wide *name*', function() {
var trace0 = Lib.extendDeep({}, mock0, {
type: 'ohlc',
name: 'Company A'
@@ -211,10 +202,8 @@ describe('finance charts defaults:', function() {
});
expect(names).toEqual([
- 'Company A - increasing',
- 'Company A - decreasing',
- 'B - UP',
- 'B - DOWN'
+ 'Company A',
+ 'Company B'
]);
});
@@ -238,11 +227,9 @@ describe('finance charts defaults:', function() {
});
expect(names).toEqual([
- 'trace 0 - increasing',
- 'trace 0 - decreasing',
+ 'trace 0',
'trace 1',
- 'trace 2 - increasing',
- 'trace 2 - decreasing',
+ 'trace 2',
'trace 3'
]);
});
@@ -267,22 +254,14 @@ describe('finance charts defaults:', function() {
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);
+ assertLine(fullData[0].increasing, 1, 'dash');
+ assertLine(fullData[0].decreasing, 1, 'dot');
+ assertLine(fullData[1].increasing, 0);
+ assertLine(fullData[1].decreasing, 3);
});
- it('trace-wide *visible* should be passed to generated traces', function() {
+ it('trace-wide *visible* should work', function() {
var trace0 = Lib.extendDeep({}, mock0, {
type: 'ohlc',
visible: 'legendonly'
@@ -301,7 +280,7 @@ describe('finance charts defaults:', function() {
// only three items here as visible: false traces are not transformed
- expect(visibilities).toEqual(['legendonly', 'legendonly', false]);
+ expect(visibilities).toEqual(['legendonly', false]);
});
it('should add a few layout settings by default', function() {
@@ -362,7 +341,7 @@ describe('finance charts defaults:', function() {
out._fullData.forEach(function(fullTrace, i) {
- expect(fullTrace.xcalendar).toBe(i < 2 ? 'hebrew' : 'julian');
+ expect(fullTrace.xcalendar).toBe(i < 1 ? 'hebrew' : 'julian');
});
});
@@ -374,14 +353,14 @@ describe('finance charts defaults:', function() {
});
});
-describe('finance charts calc transforms:', function() {
+describe('finance charts calc', function() {
'use strict';
function calcDatatoTrace(calcTrace) {
return calcTrace[0].trace;
}
- function _calcRaw(data, layout) {
+ function _calcGd(data, layout) {
var gd = {
data: data,
layout: layout || {}
@@ -389,7 +368,19 @@ describe('finance charts calc transforms:', function() {
supplyAllDefaults(gd);
Plots.doCalcdata(gd);
- return gd.calcdata;
+ gd.calcdata.forEach(function(cd) {
+ // fill in some stuff that happens during setPositions or plot
+ if(cd[0].trace.type === 'candlestick') {
+ var diff = cd[1].pos - cd[0].pos;
+ cd[0].t.wHover = diff / 2;
+ cd[0].t.bdPos = diff / 4;
+ }
+ });
+ return gd;
+ }
+
+ function _calcRaw(data, layout) {
+ return _calcGd(data, layout).calcdata;
}
function _calc(data, layout) {
@@ -408,6 +399,10 @@ describe('finance charts calc transforms:', function() {
trace.close.push(1, 1, 1, 'close');
}
+ function mapGet(array, attr) {
+ return array.map(function(di) { return di[attr]; });
+ }
+
it('should fill when *x* is not present', function() {
var trace0 = Lib.extendDeep({}, mock0, {
type: 'ohlc',
@@ -419,84 +414,16 @@ describe('finance charts calc transforms:', function() {
});
addJunk(trace1);
- var out = _calc([trace0, trace1]);
-
- expect(out[0].x).toEqual([
- -0.3, 0, 0, 0, 0, 0.3, null,
- 2.7, 3, 3, 3, 3, 3.3, null,
- 4.7, 5, 5, 5, 5, 5.3, null,
- 6.7, 7, 7, 7, 7, 7.3, null
- ]);
- expect(out[1].x).toEqual([
- 0.7, 1, 1, 1, 1, 1.3, null,
- 1.7, 2, 2, 2, 2, 2.3, null,
- 3.7, 4, 4, 4, 4, 4.3, null,
- 5.7, 6, 6, 6, 6, 6.3, null
- ]);
- expect(out[2].x).toEqual([
- 0, 0, 0, 0, 0, 0,
- 3, 3, 3, 3, 3, 3,
- 5, 5, 5, 5, 5, 5,
- 7, 7, 7, 7, 7, 7
- ]);
- expect(out[3].x).toEqual([
- 1, 1, 1, 1, 1, 1,
- 2, 2, 2, 2, 2, 2,
- 4, 4, 4, 4, 4, 4,
- 6, 6, 6, 6, 6, 6
- ]);
- });
-
- it('should fill *text* for OHLC hover labels', function() {
- var trace0 = Lib.extendDeep({}, mock0, {
- type: 'ohlc',
- text: ['A', 'B', 'C', 'D']
- });
-
- var trace1 = Lib.extendDeep({}, mock1, {
- type: 'ohlc',
- text: 'IMPORTANT',
- hoverinfo: 'x+text',
- xaxis: 'x2'
- });
-
- var trace2 = Lib.extendDeep({}, mock1, {
- type: 'ohlc',
- hoverinfo: 'y',
- xaxis: 'x2'
- });
-
- var trace3 = Lib.extendDeep({}, mock0, {
- type: 'ohlc',
- hoverinfo: 'x',
- });
-
- var out = _calc([trace0, trace1, trace2, trace3]);
-
- expect(out[0].hoverinfo).toEqual('x+text+name');
- 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+name');
- expect(out[1].text[0])
- .toEqual('open: 33.31
high: 34.37
low: 30.75
close: 31.93
B');
+ var out = _calcRaw([trace0, trace1]);
+ var indices = [0, 1, 2, 3, 4, 5, 6, 7];
+ var i = 'increasing';
+ var d = 'decreasing';
+ var directions = [i, d, d, i, d, i, d, i];
- 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('');
+ expect(mapGet(out[0], 'pos')).toEqual(indices);
+ expect(mapGet(out[0], 'dir')).toEqual(directions);
+ expect(mapGet(out[1], 'pos')).toEqual(indices);
+ expect(mapGet(out[1], 'dir')).toEqual(directions);
});
it('should work with *filter* transforms', function() {
@@ -523,42 +450,21 @@ describe('finance charts calc transforms:', function() {
var out = _calc([trace0, trace1]);
- expect(out.length).toEqual(4);
+ expect(out.length).toEqual(2);
expect(out[0].x).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
+ '2016-09-01', '2016-09-02', '2016-09-03', '2016-09-05', '2016-09-06', '2016-09-07', '2016-09-10'
]);
- 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).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[0].open).toEqual([
+ 33.01, 33.31, 33.50, 34.12, 33.05, 33.31, 33.50
]);
- 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[1].x).toEqual([
+ '2016-09-01', '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[1].close).toEqual([
+ 34.10, 33.70
]);
-
- expect(out[3].x).toEqual([]);
- expect(out[3].y).toEqual([]);
});
it('should work with *groupby* transforms (ohlc)', function() {
@@ -575,35 +481,23 @@ describe('finance charts calc transforms:', function() {
var out = _calc([trace0]);
- expect(out[0].name).toEqual('trace 0 - increasing');
+ expect(out.length).toBe(2);
+
+ expect(out[0].name).toBe('b');
expect(out[0].x).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-01', '2016-09-02', '2016-09-03'
]);
- expect(out[0].y).toEqual([
- 33.01, 33.01, 34.2, 31.7, 34.1, 34.1, null,
+ expect(out[0].open).toEqual([
+ 33.01, 33.31, 33.5
]);
- expect(out[1].name).toEqual('trace 0 - decreasing');
+ expect(out[1].name).toBe('a');
expect(out[1].x).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'
]);
- 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[1].open).toEqual([
+ 32.06
]);
-
- expect(out[2].name).toEqual('trace 0 - increasing');
- expect(out[2].x).toEqual([
- '2016-09-03 22:48', '2016-09-04', '2016-09-04', '2016-09-04', '2016-09-04', '2016-09-04 01:12', null
- ]);
- expect(out[2].y).toEqual([
- 32.06, 32.06, 34.25, 31.62, 33.18, 33.18, null
- ]);
-
- expect(out[3].name).toEqual('trace 0 - decreasing');
- expect(out[3].x).toEqual([]);
- expect(out[3].y).toEqual([]);
});
it('should work with *groupby* transforms (candlestick)', function() {
@@ -619,109 +513,78 @@ describe('finance charts calc transforms:', function() {
var out = _calc([trace0]);
- expect(out[0].name).toEqual('trace 0 - increasing');
+ expect(out[0].name).toEqual('a');
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'
+ '2016-09-01', '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[0].open).toEqual([
+ 33.01, 32.06
]);
- expect(out[1].name).toEqual('trace 0 - decreasing');
- expect(out[1].x).toEqual([]);
- expect(out[1].y).toEqual([]);
-
- expect(out[2].name).toEqual('trace 0 - increasing');
- expect(out[2].x).toEqual([]);
- expect(out[2].y).toEqual([]);
-
- expect(out[3].name).toEqual('trace 0 - decreasing');
- expect(out[3].x).toEqual([
- '2016-09-02', '2016-09-02', '2016-09-02', '2016-09-02', '2016-09-02', '2016-09-02',
- '2016-09-03', '2016-09-03', '2016-09-03', '2016-09-03', '2016-09-03', '2016-09-03'
+ expect(out[1].name).toEqual('b');
+ expect(out[1].x).toEqual([
+ '2016-09-02', '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
+ expect(out[1].open).toEqual([
+ 33.31, 33.5
]);
});
it('should use the smallest trace minimum x difference to convert *tickwidth* to data coords for all traces attached to a given x-axis', function() {
var trace0 = Lib.extendDeep({}, mock1, {
- type: 'ohlc',
- tickwidth: 0.5
+ type: 'ohlc'
});
var trace1 = Lib.extendDeep({}, mock1, {
type: 'ohlc',
- tickwidth: 0.5
+ // shift time coordinates by 10 hours
+ x: mock1.x.map(function(d) { return d + ' 10:00'; })
});
- // shift time coordinates by 10 hours
- trace1.x = trace1.x.map(function(d) {
- return d + ' 10:00';
- });
-
- var out = _calc([trace0, trace1]);
+ var out = _calcRaw([trace0, trace1]);
- expect(out[0].x).toEqual([
- '2016-08-31 12:00', '2016-09-01', '2016-09-01', '2016-09-01', '2016-09-01', '2016-09-01 12:00', null,
- '2016-09-03 12:00', '2016-09-04', '2016-09-04', '2016-09-04', '2016-09-04', '2016-09-04 12:00', null,
- '2016-09-05 12:00', '2016-09-06', '2016-09-06', '2016-09-06', '2016-09-06', '2016-09-06 12:00', null,
- '2016-09-09 12:00', '2016-09-10', '2016-09-10', '2016-09-10', '2016-09-10', '2016-09-10 12:00', null
- ]);
+ var oneDay = 1000 * 3600 * 24;
+ expect(out[0][0].t.tickLen).toBeCloseTo(oneDay * 0.3, 0);
+ expect(out[0][0].t.wHover).toBeCloseTo(oneDay * 0.5, 0);
+ expect(out[1][0].t.tickLen).toBe(out[0][0].t.tickLen);
+ expect(out[1][0].t.wHover).toBe(out[0][0].t.wHover);
+ });
- expect(out[1].x).toEqual([
- '2016-09-01 12:00', '2016-09-02', '2016-09-02', '2016-09-02', '2016-09-02', '2016-09-02 12:00', null,
- '2016-09-02 12:00', '2016-09-03', '2016-09-03', '2016-09-03', '2016-09-03', '2016-09-03 12:00', null,
- '2016-09-04 12:00', '2016-09-05', '2016-09-05', '2016-09-05', '2016-09-05', '2016-09-05 12:00', null,
- '2016-09-06 12:00', '2016-09-07', '2016-09-07', '2016-09-07', '2016-09-07', '2016-09-07 12:00', null
- ]);
+ it('works with category x data', function() {
+ // see https://github.com/plotly/plotly.js/issues/2004
+ // fixed automatically as part of the refactor to a non-transform trace
+ var trace0 = Lib.extendDeep({}, mock0, {
+ type: 'ohlc',
+ x: ['a', 'b', 'c', 'd', 'e']
+ });
- expect(out[2].x).toEqual([
- '2016-08-31 22:00', '2016-09-01 10:00', '2016-09-01 10:00', '2016-09-01 10:00', '2016-09-01 10:00', '2016-09-01 22:00', null,
- '2016-09-03 22:00', '2016-09-04 10:00', '2016-09-04 10:00', '2016-09-04 10:00', '2016-09-04 10:00', '2016-09-04 22:00', null,
- '2016-09-05 22:00', '2016-09-06 10:00', '2016-09-06 10:00', '2016-09-06 10:00', '2016-09-06 10:00', '2016-09-06 22:00', null,
- '2016-09-09 22:00', '2016-09-10 10:00', '2016-09-10 10:00', '2016-09-10 10:00', '2016-09-10 10:00', '2016-09-10 22:00', null
- ]);
+ var out = _calcRaw([trace0]);
- expect(out[3].x).toEqual([
- '2016-09-01 22:00', '2016-09-02 10:00', '2016-09-02 10:00', '2016-09-02 10:00', '2016-09-02 10:00', '2016-09-02 22:00', null,
- '2016-09-02 22:00', '2016-09-03 10:00', '2016-09-03 10:00', '2016-09-03 10:00', '2016-09-03 10:00', '2016-09-03 22:00', null,
- '2016-09-04 22:00', '2016-09-05 10:00', '2016-09-05 10:00', '2016-09-05 10:00', '2016-09-05 10:00', '2016-09-05 22:00', null,
- '2016-09-06 22:00', '2016-09-07 10:00', '2016-09-07 10:00', '2016-09-07 10:00', '2016-09-07 10:00', '2016-09-07 22:00', null
- ]);
+ expect(out[0][0].t.tickLen).toBeCloseTo(0.3, 5);
+ expect(out[0][0].t.wHover).toBeCloseTo(0.5, 5);
});
- it('should fallback to a minimum x difference of 0.5 in one-item traces', function() {
- var trace0 = Lib.extendDeep({}, mock1, {
+ it('should fallback to a spacing of 1 in one-item traces', function() {
+ var trace0 = Lib.extendDeep({}, mock0, {
type: 'ohlc',
- tickwidth: 0.5
+ x: ['2016-01-01']
});
- trace0.x = [ '2016-01-01' ];
var trace1 = Lib.extendDeep({}, mock0, {
type: 'ohlc',
- tickwidth: 0.5
+ x: [10],
+ xaxis: 'x2'
});
- trace1.x = [ 10 ];
-
- var out = _calc([trace0, trace1]);
-
- var x0 = Lib.simpleMap(out[0].x, Lib.dateTime2ms);
- expect(x0[x0.length - 2] - x0[0]).toEqual(1);
-
- var x2 = Lib.simpleMap(out[2].x, Lib.dateTime2ms);
- expect(x2[x2.length - 2] - x2[0]).toEqual(1);
- expect(out[1].x).toEqual([]);
- expect(out[3].x).toEqual([]);
+ var out = _calcRaw([trace0, trace1]);
+ expect(out[0][0].t.tickLen).toBeCloseTo(0.3, 5);
+ expect(out[0][0].t.wHover).toBeCloseTo(0.5, 5);
+ expect(out[1][0].t.tickLen).toBeCloseTo(0.3, 5);
+ expect(out[1][0].t.wHover).toBeCloseTo(0.5, 5);
});
it('should handle cases where \'open\' and \'close\' entries are equal', function() {
- var out = _calc([{
+ var out = _calcRaw([{
type: 'ohlc',
open: [0, 1, 0, 2, 1, 1, 2, 2],
high: [3, 3, 3, 3, 3, 3, 3, 3],
@@ -736,36 +599,30 @@ describe('finance charts calc transforms:', function() {
close: [0, 1, 0, 2]
}]);
- expect(out[0].x).toEqual([
- 0, 0, 0, 0, 0, 0, null,
- 1, 1, 1, 1, 1, 1, null,
- 6, 6, 6, 6, 6, 6, null,
- 7, 7, 7, 7, 7, 7, null
- ]);
- expect(out[1].x).toEqual([
- 2, 2, 2, 2, 2, 2, null,
- 3, 3, 3, 3, 3, 3, null,
- 4, 4, 4, 4, 4, 4, null,
- 5, 5, 5, 5, 5, 5, null
+ expect(mapGet(out[0], 'dir')).toEqual([
+ 'increasing', 'increasing', 'decreasing', 'decreasing',
+ 'decreasing', 'decreasing', 'increasing', 'increasing'
]);
- expect(out[2].x).toEqual([
- 0, 0, 0, 0, 0, 0,
- 3, 3, 3, 3, 3, 3
- ]);
- expect(out[3].x).toEqual([
- 1, 1, 1, 1, 1, 1,
- 2, 2, 2, 2, 2, 2
+ expect(mapGet(out[1], 'dir')).toEqual([
+ 'increasing', 'decreasing', 'decreasing', 'increasing'
]);
});
- it('should not include box hover labels prefix in candlestick calcdata', function() {
- var trace0 = Lib.extendDeep({}, mock0, {
- type: 'candlestick',
- });
- var out = _calcRaw([trace0]);
+ it('should include finance hover labels prefix in calcdata', function() {
+ ['candlestick', 'ohlc'].forEach(function(type) {
+ var trace0 = Lib.extendDeep({}, mock0, {
+ type: type,
+ });
+ var out = _calcRaw([trace0]);
- expect(out[0][0].t.labels).toBeUndefined();
+ expect(out[0][0].t.labels).toEqual({
+ open: 'open: ',
+ high: 'high: ',
+ low: 'low: ',
+ close: 'close: '
+ });
+ });
});
});
@@ -783,8 +640,8 @@ describe('finance charts updates:', function() {
destroyGraphDiv();
});
- function countScatterTraces() {
- return d3.select('g.cartesianlayer').selectAll('g.trace.scatter').size();
+ function countOHLCTraces() {
+ return d3.select('g.cartesianlayer').selectAll('g.trace.ohlc').size();
}
function countBoxTraces() {
@@ -801,18 +658,18 @@ describe('finance charts updates:', function() {
var path0;
Plotly.plot(gd, [trace0]).then(function() {
- expect(gd.calcdata[0][0].x).toEqual(-0.3);
- expect(gd.calcdata[0][0].y).toEqual(33.01);
+ expect(gd.calcdata[0][0].t.tickLen).toBeCloseTo(0.3, 5);
+ expect(gd.calcdata[0][0].o).toEqual(33.01);
return Plotly.restyle(gd, 'tickwidth', 0.5);
})
.then(function() {
- expect(gd.calcdata[0][0].x).toEqual(-0.5);
+ expect(gd.calcdata[0][0].t.tickLen).toBeCloseTo(0.5, 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);
+ expect(gd.calcdata[0][0].o).toEqual(0);
return Plotly.restyle(gd, {
type: 'candlestick',
@@ -821,15 +678,15 @@ describe('finance charts updates:', function() {
})
.then(function() {
path0 = d3.select('path.box').attr('d');
+ expect(path0).toBeDefined();
return Plotly.restyle(gd, 'whiskerwidth', 0.2);
})
.then(function() {
expect(d3.select('path.box').attr('d')).not.toEqual(path0);
-
- done();
- });
-
+ })
+ .catch(failTest)
+ .then(done);
});
it('should be able to toggle visibility', function(done) {
@@ -839,47 +696,47 @@ describe('finance charts updates:', function() {
];
Plotly.plot(gd, data).then(function() {
- expect(countScatterTraces()).toEqual(2);
- expect(countBoxTraces()).toEqual(2);
+ expect(countOHLCTraces()).toEqual(1);
+ expect(countBoxTraces()).toEqual(1);
return Plotly.restyle(gd, 'visible', false);
})
.then(function() {
- expect(countScatterTraces()).toEqual(0);
+ expect(countOHLCTraces()).toEqual(0);
expect(countBoxTraces()).toEqual(0);
return Plotly.restyle(gd, 'visible', 'legendonly', [1]);
})
.then(function() {
- expect(countScatterTraces()).toEqual(0);
+ expect(countOHLCTraces()).toEqual(0);
expect(countBoxTraces()).toEqual(0);
return Plotly.restyle(gd, 'visible', true, [1]);
})
.then(function() {
- expect(countScatterTraces()).toEqual(0);
- expect(countBoxTraces()).toEqual(2);
+ expect(countOHLCTraces()).toEqual(0);
+ expect(countBoxTraces()).toEqual(1);
return Plotly.restyle(gd, 'visible', true, [0]);
})
.then(function() {
- expect(countScatterTraces()).toEqual(2);
- expect(countBoxTraces()).toEqual(2);
+ expect(countOHLCTraces()).toEqual(1);
+ expect(countBoxTraces()).toEqual(1);
return Plotly.restyle(gd, 'visible', 'legendonly', [0]);
})
.then(function() {
- expect(countScatterTraces()).toEqual(0);
- expect(countBoxTraces()).toEqual(2);
+ expect(countOHLCTraces()).toEqual(0);
+ expect(countBoxTraces()).toEqual(1);
return Plotly.restyle(gd, 'visible', true);
})
.then(function() {
- expect(countScatterTraces()).toEqual(2);
- expect(countBoxTraces()).toEqual(2);
-
- done();
- });
+ expect(countOHLCTraces()).toEqual(1);
+ expect(countBoxTraces()).toEqual(1);
+ })
+ .catch(failTest)
+ .then(done);
});
it('Plotly.relayout should work', function(done) {
@@ -892,9 +749,9 @@ describe('finance charts updates:', function() {
})
.then(function() {
expect(countRangeSliders()).toEqual(0);
-
- done();
- });
+ })
+ .catch(failTest)
+ .then(done);
});
@@ -904,13 +761,9 @@ describe('finance charts updates:', function() {
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);
+ expect(gd.calcdata[0].length).toEqual(8);
+ expect(gd.calcdata[1].length).toEqual(8);
return Plotly.extendTraces(gd, {
open: [[ 34, 35 ]],
@@ -920,10 +773,8 @@ describe('finance charts updates:', function() {
}, [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);
+ expect(gd.calcdata[0].length).toEqual(8);
+ expect(gd.calcdata[1].length).toEqual(10);
return Plotly.extendTraces(gd, {
open: [[ 34, 35 ]],
@@ -933,13 +784,11 @@ describe('finance charts updates:', function() {
}, [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();
- });
+ expect(gd.calcdata[0].length).toEqual(10);
+ expect(gd.calcdata[1].length).toEqual(10);
+ })
+ .catch(failTest)
+ .then(done);
});
it('Plotly.deleteTraces / addTraces should work', function(done) {
@@ -949,19 +798,19 @@ describe('finance charts updates:', function() {
];
Plotly.plot(gd, data).then(function() {
- expect(countScatterTraces()).toEqual(2);
- expect(countBoxTraces()).toEqual(2);
+ expect(countOHLCTraces()).toEqual(1);
+ expect(countBoxTraces()).toEqual(1);
return Plotly.deleteTraces(gd, [1]);
})
.then(function() {
- expect(countScatterTraces()).toEqual(2);
+ expect(countOHLCTraces()).toEqual(1);
expect(countBoxTraces()).toEqual(0);
return Plotly.deleteTraces(gd, [0]);
})
.then(function() {
- expect(countScatterTraces()).toEqual(0);
+ expect(countOHLCTraces()).toEqual(0);
expect(countBoxTraces()).toEqual(0);
var trace = Lib.extendDeep({}, mock0, { type: 'candlestick' });
@@ -969,68 +818,66 @@ describe('finance charts updates:', function() {
return Plotly.addTraces(gd, [trace]);
})
.then(function() {
- expect(countScatterTraces()).toEqual(0);
- expect(countBoxTraces()).toEqual(2);
+ expect(countOHLCTraces()).toEqual(0);
+ expect(countBoxTraces()).toEqual(1);
var trace = Lib.extendDeep({}, mock0, { type: 'ohlc' });
return Plotly.addTraces(gd, [trace]);
})
.then(function() {
- expect(countScatterTraces()).toEqual(2);
- expect(countBoxTraces()).toEqual(2);
-
- done();
- });
+ expect(countOHLCTraces()).toEqual(1);
+ expect(countBoxTraces()).toEqual(1);
+ })
+ .catch(failTest)
+ .then(done);
});
it('Plotly.addTraces + Plotly.relayout should update candlestick box position values', function(done) {
- function assertBoxPosFields(dPos) {
- expect(gd.calcdata.length).toEqual(dPos.length);
+ function assertBoxPosFields(bPos) {
+ expect(gd.calcdata.length).toEqual(bPos.length);
gd.calcdata.forEach(function(calcTrace, i) {
- if(dPos[i] === undefined) {
- expect(calcTrace[0].t.dPos).toBeUndefined();
- }
- else {
- expect(calcTrace[0].t.dPos).toEqual(dPos[i]);
- }
+ expect(calcTrace[0].t.bPos).toBeCloseTo(bPos[i], 0);
});
}
var trace0 = {
type: 'candlestick',
- x: ['2011-01-01'],
- open: [0],
- high: [3],
- low: [1],
- close: [3]
+ x: ['2011-01-01', '2011-01-02'],
+ open: [1, 2],
+ high: [3, 4],
+ low: [0, 1],
+ close: [2, 3]
};
- Plotly.plot(gd, [trace0]).then(function() {
- assertBoxPosFields([0.5, undefined]);
+ Plotly.plot(gd, [trace0], {boxmode: 'group'})
+ .then(function() {
+ assertBoxPosFields([0]);
- return Plotly.addTraces(gd, {});
+ return Plotly.addTraces(gd, [Lib.extendDeep({}, trace0)]);
})
.then(function() {
+ assertBoxPosFields([-15120000, 15120000]);
+
var update = {
type: 'candlestick',
- x: [['2011-02-02']],
- open: [[0]],
- high: [[3]],
- low: [[1]],
- close: [[3]]
+ x: [['2011-01-01', '2011-01-05'], ['2011-01-01', '2011-01-03']],
+ open: [[1, 0]],
+ high: [[3, 2]],
+ low: [[0, -1]],
+ close: [[2, 1]]
};
return Plotly.restyle(gd, update);
})
.then(function() {
- assertBoxPosFields([0.5, undefined, 0.5, undefined]);
-
- done();
- });
+ assertBoxPosFields([-30240000, 30240000]);
+ })
+ .catch(failTest)
+ .then(done);
});
it('Plotly.plot with data-less trace and adding with Plotly.restyle', function(done) {
@@ -1041,7 +888,7 @@ describe('finance charts updates:', function() {
];
Plotly.plot(gd, data).then(function() {
- expect(countScatterTraces()).toEqual(0);
+ expect(countOHLCTraces()).toEqual(0);
expect(countBoxTraces()).toEqual(0);
expect(countRangeSliders()).toEqual(0);
@@ -1053,8 +900,8 @@ describe('finance charts updates:', function() {
}, [0]);
})
.then(function() {
- expect(countScatterTraces()).toEqual(0);
- expect(countBoxTraces()).toEqual(2);
+ expect(countOHLCTraces()).toEqual(0);
+ expect(countBoxTraces()).toEqual(1);
expect(countRangeSliders()).toEqual(1);
return Plotly.restyle(gd, {
@@ -1065,16 +912,18 @@ describe('finance charts updates:', function() {
}, [1]);
})
.then(function() {
- expect(countScatterTraces()).toEqual(2);
- expect(countBoxTraces()).toEqual(2);
+ expect(countOHLCTraces()).toEqual(1);
+ expect(countBoxTraces()).toEqual(1);
expect(countRangeSliders()).toEqual(1);
})
+ .catch(failTest)
.then(done);
});
});
describe('finance charts *special* handlers:', function() {
+ // not special anymore - just test that they work as normal
afterEach(destroyGraphDiv);
@@ -1112,7 +961,7 @@ describe('finance charts *special* handlers:', function() {
.then(function(gd) {
return new Promise(function(resolve) {
gd.once('plotly_restyle', function(eventData) {
- expect(eventData[0]['increasing.name']).toEqual('0');
+ expect(eventData[0].name).toEqual('0');
expect(eventData[1]).toEqual([0]);
delayedResolve(resolve);
});
@@ -1123,36 +972,15 @@ describe('finance charts *special* handlers:', function() {
.then(function(gd) {
return new Promise(function(resolve) {
gd.once('plotly_restyle', function(eventData) {
- expect(eventData[0]['decreasing.name']).toEqual('1');
- expect(eventData[1]).toEqual([0]);
- delayedResolve(resolve);
- });
-
- editText(1, '1');
- });
- })
- .then(function(gd) {
- return new Promise(function(resolve) {
- gd.once('plotly_restyle', function(eventData) {
- expect(eventData[0]['decreasing.name']).toEqual('2');
- expect(eventData[1]).toEqual([1]);
- delayedResolve(resolve);
- });
-
- editText(3, '2');
- });
- })
- .then(function(gd) {
- return new Promise(function(resolve) {
- gd.once('plotly_restyle', function(eventData) {
- expect(eventData[0]['increasing.name']).toEqual('3');
+ expect(eventData[0].name).toEqual('1');
expect(eventData[1]).toEqual([1]);
delayedResolve(resolve);
});
- editText(2, '3');
+ editText(1, '1');
});
})
+ .catch(failTest)
.then(done);
});
diff --git a/test/jasmine/tests/hover_label_test.js b/test/jasmine/tests/hover_label_test.js
index 1ad54ea3b6c..87907aea7bd 100644
--- a/test/jasmine/tests/hover_label_test.js
+++ b/test/jasmine/tests/hover_label_test.js
@@ -12,7 +12,7 @@ var mouseEvent = require('../assets/mouse_event');
var click = require('../assets/click');
var delay = require('../assets/delay');
var doubleClick = require('../assets/double_click');
-var fail = require('../assets/fail_test');
+var failTest = require('../assets/fail_test');
var customAssertions = require('../assets/custom_assertions');
var assertHoverLabelStyle = customAssertions.assertHoverLabelStyle;
@@ -512,7 +512,7 @@ describe('hover info', function() {
name: 'one'
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -548,7 +548,7 @@ describe('hover info', function() {
name: 'one'
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -587,7 +587,7 @@ describe('hover info', function() {
});
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -623,7 +623,7 @@ describe('hover info', function() {
name: 'one'
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -693,7 +693,7 @@ describe('hover info', function() {
assertHoverLabelStyle(d3.select(this), styles[i]);
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -731,7 +731,7 @@ describe('hover info', function() {
name: 'one'
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -769,7 +769,7 @@ describe('hover info', function() {
name: 'one'
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
});
@@ -791,7 +791,7 @@ describe('hover info', function() {
axis: '2'
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
});
@@ -833,7 +833,7 @@ describe('hover info', function() {
expect(pt.xaxis).toBe(gd._fullLayout.xaxis);
expect(pt.yaxis).toBe(gd._fullLayout.yaxis);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -858,7 +858,7 @@ describe('hover info', function() {
axis: '3.3'
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -886,7 +886,7 @@ describe('hover info', function() {
axis: 'soup - nuts'
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
});
@@ -912,7 +912,7 @@ describe('hover info', function() {
nums: 'x: 3 - 5\ny: 4 - 6\nz: 3'
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -936,11 +936,114 @@ describe('hover info', function() {
nums: 'x: 3.3\ny: 4.2\nz: 3'
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
});
+ ['candlestick', 'ohlc'].forEach(function(type) {
+ describe(type + ' hoverinfo', function() {
+ var gd;
+
+ function financeMock(traceUpdates, layoutUpdates) {
+ return {
+ data: [Lib.extendFlat({}, {
+ type: type,
+ x: ['2011-01-01', '2011-01-02', '2011-01-03'],
+ open: [1, 2, 3],
+ high: [3, 4, 5],
+ low: [0, 1, 2],
+ close: [0, 3, 2]
+ }, traceUpdates || {})],
+ layout: Lib.extendDeep({}, {
+ width: 400,
+ height: 400,
+ margin: {l: 50, r: 50, t: 50, b: 50}
+ }, layoutUpdates || {})
+ };
+ }
+
+ beforeEach(function() {
+ gd = createGraphDiv();
+ });
+
+ it('has the right basic and event behavior', function(done) {
+ var pts;
+ Plotly.plot(gd, financeMock({
+ customdata: [11, 22, 33]
+ }))
+ .then(function() {
+ gd.on('plotly_hover', function(e) { pts = e.points; });
+
+ _hoverNatural(gd, 150, 150);
+ assertHoverLabelContent({
+ nums: 'open: 2\nhigh: 4\nlow: 1\nclose: 3 ▲',
+ axis: 'Jan 2, 2011'
+ });
+ })
+ .then(function() {
+ expect(pts).toBeDefined();
+ expect(pts.length).toBe(1);
+ expect(pts[0]).toEqual(jasmine.objectContaining({
+ x: '2011-01-02',
+ open: 2,
+ high: 4,
+ low: 1,
+ close: 3,
+ customdata: 22,
+ curveNumber: 0,
+ pointNumber: 1,
+ data: gd.data[0],
+ fullData: gd._fullData[0],
+ xaxis: gd._fullLayout.xaxis,
+ yaxis: gd._fullLayout.yaxis
+ }));
+
+ return Plotly.relayout(gd, {hovermode: 'closest'});
+ })
+ .then(function() {
+ _hoverNatural(gd, 150, 150);
+ assertHoverLabelContent({
+ nums: 'Jan 2, 2011\nopen: 2\nhigh: 4\nlow: 1\nclose: 3 ▲'
+ });
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
+ it('shows text iff text is in hoverinfo', function(done) {
+ Plotly.plot(gd, financeMock({text: ['A', 'B', 'C']}))
+ .then(function() {
+ _hover(gd, 150, 150);
+ assertHoverLabelContent({
+ nums: 'open: 2\nhigh: 4\nlow: 1\nclose: 3 ▲\nB',
+ axis: 'Jan 2, 2011'
+ });
+
+ return Plotly.restyle(gd, {hoverinfo: 'x+text'});
+ })
+ .then(function() {
+ _hover(gd, 150, 150);
+ assertHoverLabelContent({
+ nums: 'B',
+ axis: 'Jan 2, 2011'
+ });
+
+ return Plotly.restyle(gd, {hoverinfo: 'x+y'});
+ })
+ .then(function() {
+ _hover(gd, 150, 150);
+ assertHoverLabelContent({
+ nums: 'open: 2\nhigh: 4\nlow: 1\nclose: 3 ▲',
+ axis: 'Jan 2, 2011'
+ });
+ })
+ .catch(failTest)
+ .then(done);
+ });
+ });
+ });
+
describe('hoverformat', function() {
var data = [{
x: [1, 2, 3],
@@ -1043,7 +1146,7 @@ describe('hover info', function() {
.then(function() {
expect(hoverHandler).not.toHaveBeenCalled();
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -1070,7 +1173,7 @@ describe('hover info', function() {
.then(function() {
expect(hoverHandler).toHaveBeenCalledTimes(1);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
});
@@ -1107,7 +1210,7 @@ describe('hover info', function() {
expect(labelCount()).toBe(7);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -1410,7 +1513,7 @@ describe('hover on many lines+bars', function() {
expect(d3.select(gd).selectAll('g.hovertext').size()).toBe(2);
expect(d3.select(gd).selectAll('g.axistext').size()).toBe(1);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
});
@@ -1559,7 +1662,7 @@ describe('hover on fill', function() {
// gives same results w/o closing point
assertLabelsCorrect([200, 200], [73.75, 250], 'trace 0');
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -1596,7 +1699,7 @@ describe('hover on fill', function() {
// then make sure we can still select a *different* item afterward
assertLabelsCorrect([237, 218], [266.75, 265], 'trace 1');
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -1622,7 +1725,7 @@ describe('hover on fill', function() {
// hover on the cartesian trace in the corner
assertLabelsCorrect([363, 122], [363, 122], 'trace 38');
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
});
@@ -1696,7 +1799,9 @@ describe('hover updates', function() {
}).then(function() {
// Assert label restored:
assertLabelsCorrect(null, [103, 100], 'trace 10.5', 'animation/update 3');
- }).catch(fail).then(done);
+ })
+ .catch(failTest)
+ .then(done);
});
it('should not trigger infinite loop of plotly_unhover events', function(done) {
@@ -1951,7 +2056,7 @@ describe('Test hover label custom styling:', function() {
text: [13, 'Arial', 'rgb(255, 255, 255)']
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -2007,36 +2112,11 @@ describe('Test hover label custom styling:', function() {
text: [11, 'Gravitas', 'rgb(255, 0, 0)']
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
});
-describe('ohlc hover interactions', function() {
- var data = [{
- type: 'candlestick',
- x: ['2011-01-01', '2012-01-01'],
- open: [2, 2],
- high: [3, 3],
- low: [0, 0],
- close: [3, 3],
- }];
-
- beforeEach(function() {
- this.gd = createGraphDiv();
- });
-
- afterEach(destroyGraphDiv);
-
- // See: https://github.com/plotly/plotly.js/issues/1807
- it('should not fail in appendArrayPointValue', function() {
- Plotly.plot(this.gd, data);
- mouseEvent('mousemove', 203, 213);
-
- expect(d3.select('.hovertext').size()).toBe(1);
- });
-});
-
describe('hover distance', function() {
'use strict';
diff --git a/test/jasmine/tests/plot_api_test.js b/test/jasmine/tests/plot_api_test.js
index c5177d04480..3302fe9a137 100644
--- a/test/jasmine/tests/plot_api_test.js
+++ b/test/jasmine/tests/plot_api_test.js
@@ -2319,6 +2319,90 @@ describe('Test plot api', function() {
expect(gd.layout.shapes[2].yref).toEqual('y');
});
+
+ it('removes direction names and showlegend from finance traces', function() {
+ var data = [{
+ type: 'ohlc', open: [1], high: [3], low: [0], close: [2],
+ increasing: {
+ showlegend: true,
+ name: 'Yeti goes up'
+ },
+ decreasing: {
+ showlegend: 'legendonly',
+ name: 'Yeti goes down'
+ },
+ name: 'Snowman'
+ }, {
+ type: 'candlestick', open: [1], high: [3], low: [0], close: [2],
+ increasing: {
+ name: 'Bigfoot'
+ },
+ decreasing: {
+ showlegend: false,
+ name: 'Biggerfoot'
+ },
+ name: 'Nobody'
+ }, {
+ type: 'ohlc', open: [1], high: [3], low: [0], close: [2],
+ increasing: {
+ name: 'Batman'
+ },
+ decreasing: {
+ showlegend: true
+ },
+ name: 'Robin'
+ }, {
+ type: 'candlestick', open: [1], high: [3], low: [0], close: [2],
+ increasing: {
+ showlegend: false,
+ },
+ decreasing: {
+ name: 'Fred'
+ }
+ }, {
+ type: 'ohlc', open: [1], high: [3], low: [0], close: [2],
+ increasing: {
+ showlegend: false,
+ name: 'Gruyere heating up'
+ },
+ decreasing: {
+ showlegend: false,
+ name: 'Gruyere cooling off'
+ },
+ name: 'Emmenthaler'
+ }];
+
+ Plotly.plot(gd, data);
+
+ // Even if both showlegends are false, leave trace.showlegend out
+ // My rationale for this is that legends are sufficiently different
+ // now that it's worthwhile resetting their existence to default
+ gd.data.forEach(function(trace) {
+ expect(trace.increasing.name).toBeUndefined();
+ expect(trace.increasing.showlegend).toBeUndefined();
+ expect(trace.decreasing.name).toBeUndefined();
+ expect(trace.decreasing.showlegend).toBeUndefined();
+ });
+
+ // Both directions have names: ignore trace.name, as it
+ // had no effect on the output previously
+ // Ideally 'Yeti goes' would be smart enough to truncate
+ // at 'Yeti' but I don't see how to do that...
+ expect(gd.data[0].name).toBe('Yeti goes');
+ // One direction has empty or hidden name so use the other
+ // Note that even '' in both names would render trace.name impact-less
+ expect(gd.data[1].name).toBe('Bigfoot');
+
+ // One direction has a name but trace.name is there too:
+ // just use trace.name
+ expect(gd.data[2].name).toBe('Robin');
+
+ // No trace.name, only one direction name: use the direction name
+ expect(gd.data[3].name).toBe('Fred');
+
+ // both names exist but hidden from the legend: still look for common prefix
+ expect(gd.data[4].name).toBe('Gruyere');
+ });
});
describe('Plotly.newPlot', function() {
@@ -2824,6 +2908,53 @@ describe('Test plot api', function() {
.then(done);
});
+ it('can change data in candlesticks multiple times', function(done) {
+ // test that we've fixed the original issue in
+ // https://github.com/plotly/plotly.js/issues/2510
+
+ function assertCalc(open, high, low, close) {
+ expect(gd.calcdata[0][0]).toEqual(jasmine.objectContaining({
+ min: low,
+ max: high,
+ med: close,
+ q1: Math.min(open, close),
+ q3: Math.max(open, close),
+ dir: close >= open ? 'increasing' : 'decreasing'
+ }));
+ }
+ var trace = {
+ type: 'candlestick',
+ low: [1],
+ open: [2],
+ close: [3],
+ high: [4]
+ };
+ Plotly.newPlot(gd, [trace])
+ .then(function() {
+ assertCalc(2, 4, 1, 3);
+
+ trace.low = [0];
+ return Plotly.react(gd, [trace]);
+ })
+ .then(function() {
+ assertCalc(2, 4, 0, 3);
+
+ trace.low = [-1];
+ return Plotly.react(gd, [trace]);
+ })
+ .then(function() {
+ assertCalc(2, 4, -1, 3);
+
+ trace.close = [1];
+ return Plotly.react(gd, [trace]);
+ })
+ .then(function() {
+ assertCalc(2, 4, -1, 1);
+ })
+ .catch(failTest)
+ .then(done);
+ });
+
it('can change frames without redrawing', function(done) {
var data = [{y: [1, 2, 3]}];
var layout = {};
@@ -2954,10 +3085,7 @@ describe('Test plot api', function() {
return Plotly.react(gd, mock);
})
.then(function() {
- // TODO: remove this exemption once we fix finance
- if(mockSpec[0] !== 'finance_style') {
- expect(fullJson()).toEqual(initialJson);
- }
+ expect(fullJson()).toEqual(initialJson);
countCalls({});
})
.catch(failTest)
diff --git a/test/jasmine/tests/polar_test.js b/test/jasmine/tests/polar_test.js
index ac5304d72a7..32ca0d61b6b 100644
--- a/test/jasmine/tests/polar_test.js
+++ b/test/jasmine/tests/polar_test.js
@@ -798,7 +798,7 @@ describe('Test polar interactions:', function() {
.then(done);
});
- it('should response to drag interactions on plot area', function(done) {
+ it('@flaky should respond to drag interactions on plot area', function(done) {
var fig = Lib.extendDeep({}, require('@mocks/polar_scatter.json'));
// to avoid dragging on hover labels
diff --git a/test/jasmine/tests/range_slider_test.js b/test/jasmine/tests/range_slider_test.js
index 1b4fc6cd83d..abbad0f08f1 100644
--- a/test/jasmine/tests/range_slider_test.js
+++ b/test/jasmine/tests/range_slider_test.js
@@ -1,6 +1,7 @@
var Plotly = require('@lib/index');
var Lib = require('@src/lib');
var setConvert = require('@src/plots/cartesian/set_convert');
+var name2id = require('@src/plots/cartesian/axis_ids').name2id;
var RangeSlider = require('@src/components/rangeslider');
var constants = require('@src/components/rangeslider/constants');
@@ -509,7 +510,11 @@ describe('Rangeslider handleDefaults function', function() {
function _supply(layoutIn, layoutOut, axName) {
setConvert(layoutOut[axName]);
+ layoutOut[axName]._id = name2id(axName);
+ if(!layoutOut._requestRangeslider) layoutOut._requestRangeslider = {};
RangeSlider.handleDefaults(layoutIn, layoutOut, axName);
+ // we don't care about this after it's done its job
+ delete layoutOut._requestRangeslider;
}
it('should not coerce anything if rangeslider isn\'t set', function() {
@@ -519,6 +524,7 @@ describe('Rangeslider handleDefaults function', function() {
_supply(layoutIn, layoutOut, 'xaxis');
expect(layoutIn).toEqual(expected);
+ expect(layoutOut.xaxis.rangeslider).toBeUndefined();
});
it('should not mutate layoutIn', function() {
@@ -547,6 +553,27 @@ describe('Rangeslider handleDefaults function', function() {
expect(layoutOut.xaxis.rangeslider).toEqual(expected);
});
+ it('should set defaults if rangeslider is requested', function() {
+ var layoutIn = { xaxis: {}},
+ layoutOut = { xaxis: {}, _requestRangeslider: {x: true} },
+ expected = {
+ visible: true,
+ autorange: true,
+ thickness: 0.15,
+ bgcolor: '#fff',
+ borderwidth: 0,
+ bordercolor: '#444',
+ _input: {}
+ };
+
+ _supply(layoutIn, layoutOut, 'xaxis');
+ // in fact we DO mutate layoutIn - which we should probably try not to do,
+ // but that's a problem for another time.
+ // see https://github.com/plotly/plotly.js/issues/1473
+ expect(layoutIn).toEqual({xaxis: {rangeslider: {}}});
+ expect(layoutOut.xaxis.rangeslider).toEqual(expected);
+ });
+
it('should set defaults if rangeslider.visible is true', function() {
var layoutIn = { xaxis: { rangeslider: { visible: true }} },
layoutOut = { xaxis: { rangeslider: {}} },
diff --git a/test/jasmine/tests/select_test.js b/test/jasmine/tests/select_test.js
index aef6ca104b3..992a54b5f3a 100644
--- a/test/jasmine/tests/select_test.js
+++ b/test/jasmine/tests/select_test.js
@@ -6,7 +6,7 @@ var doubleClick = require('../assets/double_click');
var createGraphDiv = require('../assets/create_graph_div');
var destroyGraphDiv = require('../assets/destroy_graph_div');
-var fail = require('../assets/fail_test');
+var failTest = require('../assets/fail_test');
var mouseEvent = require('../assets/mouse_event');
var touchEvent = require('../assets/touch_event');
@@ -190,7 +190,7 @@ describe('@flaky Test select box and lasso in general:', function() {
.then(function() {
expect(doubleClickData).toBe(null, 'with the correct deselect data');
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -282,7 +282,7 @@ describe('@flaky Test select box and lasso in general:', function() {
.then(function() {
expect(doubleClickData).toBe(null, 'with the correct deselect data');
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -336,7 +336,7 @@ describe('@flaky Test select box and lasso in general:', function() {
.then(function() {
expect(doubleClickData).toBe(null, 'with the correct deselect data');
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -355,7 +355,7 @@ describe('@flaky Test select box and lasso in general:', function() {
.then(function() {
expect(gd.data[0].selectedpoints).toBeUndefined();
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -374,7 +374,7 @@ describe('@flaky Test select box and lasso in general:', function() {
.then(function() {
expect(gd._fullData[0].selectedpoints).toBeUndefined();
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -408,7 +408,7 @@ describe('@flaky Test select box and lasso in general:', function() {
.then(function() {
expect(doubleClickData).toBe(null, 'with the correct deselect data');
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
});
@@ -488,7 +488,7 @@ describe('@flaky Test select box and lasso in general:', function() {
.then(function() {
checkPointCount(0, '(multiple invisible traces lasso)');
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -527,7 +527,7 @@ describe('@flaky Test select box and lasso in general:', function() {
expect(selectedData.points[0].x).toBe(0);
expect(selectedData.points[0].y).toBe(0);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -549,7 +549,7 @@ describe('@flaky Test select box and lasso in general:', function() {
.then(function() {
assertSelectionNodes(0, 0);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
});
@@ -730,7 +730,7 @@ describe('@flaky Test select box and lasso per trace:', function() {
LASSOEVENTS, 'scatterternary lasso after relayout'
);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -766,7 +766,7 @@ describe('@flaky Test select box and lasso per trace:', function() {
null, LASSOEVENTS, 'scattercarpet lasso'
);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -823,7 +823,7 @@ describe('@flaky Test select box and lasso per trace:', function() {
[[370, 120], [500, 200]], null, null, NOEVENTS, 'scattermapbox pan'
);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
}, LONG_TIMEOUT_INTERVAL);
@@ -889,7 +889,7 @@ describe('@flaky Test select box and lasso per trace:', function() {
[[370, 120], [500, 200]], null, null, NOEVENTS, 'scattergeo pan'
);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
}, LONG_TIMEOUT_INTERVAL);
@@ -927,7 +927,7 @@ describe('@flaky Test select box and lasso per trace:', function() {
LASSOEVENTS, 'scatterpolar lasso'
);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -990,7 +990,7 @@ describe('@flaky Test select box and lasso per trace:', function() {
[[370, 120], [500, 200]], null, [280, 190], NOEVENTS, 'choropleth pan'
);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
}, LONG_TIMEOUT_INTERVAL);
@@ -1063,7 +1063,7 @@ describe('@flaky Test select box and lasso per trace:', function() {
null, BOXEVENTS, 'bar select'
);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -1123,7 +1123,7 @@ describe('@flaky Test select box and lasso per trace:', function() {
null, BOXEVENTS, 'date/category select'
);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -1172,7 +1172,7 @@ describe('@flaky Test select box and lasso per trace:', function() {
null, BOXEVENTS, 'histogram select'
);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -1236,7 +1236,7 @@ describe('@flaky Test select box and lasso per trace:', function() {
null, BOXEVENTS, 'box select'
);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -1299,10 +1299,87 @@ describe('@flaky Test select box and lasso per trace:', function() {
null, BOXEVENTS, 'violin select'
);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
+ ['ohlc', 'candlestick'].forEach(function(type) {
+ it('should work for ' + type + ' traces', function(done) {
+ var assertPoints = makeAssertPoints(['curveNumber', 'x', 'open', 'high', 'low', 'close']);
+ var assertSelectedPoints = makeAssertSelectedPoints();
+ var assertRanges = makeAssertRanges();
+ var assertLassoPoints = makeAssertLassoPoints();
+ var l0 = 275;
+ var lv0 = '2011-01-03 18:00';
+ var r0 = 325;
+ var rv0 = '2011-01-04 06:00';
+ var l1 = 75;
+ var lv1 = '2011-01-01 18:00';
+ var r1 = 125;
+ var rv1 = '2011-01-02 06:00';
+ var t = 75;
+ var tv = 7.565;
+ var b = 225;
+ var bv = -1.048;
+
+ function countUnSelectedPaths(selector) {
+ var unselected = 0;
+ d3.select(gd).selectAll(selector).each(function() {
+ var opacity = this.style.opacity;
+ if(opacity < 1) unselected++;
+ });
+ return unselected;
+ }
+
+ Plotly.newPlot(gd, [{
+ type: type,
+ x: ['2011-01-02', '2011-01-03', '2011-01-04'],
+ open: [1, 2, 3],
+ high: [3, 4, 5],
+ low: [0, 1, 2],
+ close: [0, 3, 2]
+ }], {
+ width: 400,
+ height: 400,
+ margin: {l: 50, r: 50, t: 50, b: 50},
+ yaxis: {range: [-3, 9]},
+ dragmode: 'lasso'
+ })
+ .then(function() {
+ return _run(
+ [[l0, t], [l0, b], [r0, b], [r0, t], [l0, t]],
+ function() {
+ assertPoints([[0, '2011-01-04', 3, 5, 2, 2]]);
+ assertSelectedPoints([[2]]);
+ assertLassoPoints([
+ [lv0, lv0, rv0, rv0, lv0],
+ [tv, bv, bv, tv, tv]
+ ]);
+ expect(countUnSelectedPaths('.cartesianlayer .trace path')).toBe(2);
+ expect(countUnSelectedPaths('.rangeslider-rangeplot .trace path')).toBe(0);
+ },
+ null, LASSOEVENTS, type + ' lasso'
+ );
+ })
+ .then(function() {
+ return Plotly.relayout(gd, 'dragmode', 'select');
+ })
+ .then(function() {
+ return _run(
+ [[l1, t], [r1, b]],
+ function() {
+ assertPoints([[0, '2011-01-02', 1, 3, 0, 0]]);
+ assertSelectedPoints([[0]]);
+ assertRanges([[lv1, rv1], [bv, tv]]);
+ },
+ null, BOXEVENTS, type + ' select'
+ );
+ })
+ .catch(failTest)
+ .then(done);
+ });
+ });
+
it('should work on traces with enabled transforms', function(done) {
var assertSelectedPoints = makeAssertSelectedPoints();
@@ -1342,7 +1419,7 @@ describe('@flaky Test select box and lasso per trace:', function() {
BOXEVENTS, 'transformed trace select (all points selected)'
);
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
});
@@ -1399,7 +1476,7 @@ describe('Test that selections persist:', function() {
style: [0.2, 1, 0.2]
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -1445,7 +1522,7 @@ describe('Test that selections persist:', function() {
style: [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 1, 1, 1],
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
@@ -1487,7 +1564,7 @@ describe('Test that selections persist:', function() {
style: [0.2, 1, 0.2, 0.2, 0.2],
});
})
- .catch(fail)
+ .catch(failTest)
.then(done);
});
});
diff --git a/test/jasmine/tests/transform_multi_test.js b/test/jasmine/tests/transform_multi_test.js
index d322679b8a4..9ad0c331c87 100644
--- a/test/jasmine/tests/transform_multi_test.js
+++ b/test/jasmine/tests/transform_multi_test.js
@@ -17,7 +17,8 @@ var mockFullLayout = {
_modules: [],
_basePlotModules: [],
_has: function() {},
- _dfltTitle: {x: 'xxx', y: 'yyy'}
+ _dfltTitle: {x: 'xxx', y: 'yyy'},
+ _requestRangeslider: {}
};