diff --git a/package.json b/package.json
index 303f6c4993e..f073cd1194a 100644
--- a/package.json
+++ b/package.json
@@ -56,6 +56,8 @@
"3d-view": "^2.0.0",
"@plotly/d3-sankey": "^0.5.0",
"alpha-shape": "^1.0.0",
+ "bubleify": "^1.0.0",
+ "canvas-fit": "^1.5.0",
"color-rgba": "^1.1.1",
"convex-hull": "^1.0.3",
"country-regex": "^1.1.0",
@@ -84,8 +86,10 @@
"gl-spikes2d": "^1.0.1",
"gl-surface3d": "^1.3.1",
"has-hover": "^1.0.1",
+ "kdgrass": "^1.0.1",
"mapbox-gl": "^0.22.0",
"matrix-camera-controller": "^2.1.3",
+ "minify-stream": "^1.1.0",
"mouse-change": "^1.4.0",
"mouse-event-offset": "^3.0.2",
"mouse-wheel": "^1.0.2",
@@ -95,11 +99,15 @@
"ndarray-ops": "^1.2.2",
"polybooljs": "^1.2.0",
"regl": "^1.3.0",
+ "regl-error2d": "^2.0.3",
+ "regl-line2d": "^2.1.0",
+ "regl-scatter2d": "^2.1.6",
"right-now": "^1.0.0",
"robust-orientation": "^1.1.3",
"sane-topojson": "^2.0.0",
"strongly-connected-components": "^1.0.1",
"superscript-text": "^1.0.0",
+ "svg-path-sdf": "^1.1.1",
"tinycolor2": "^1.3.0",
"topojson-client": "^2.1.0",
"webgl-context": "^2.2.0",
@@ -109,6 +117,7 @@
"brfs": "^1.4.3",
"browserify": "^14.1.0",
"browserify-transform-tools": "^1.7.0",
+ "cross-spawn": "^5.1.0",
"deep-equal": "^1.0.1",
"ecstatic": "^2.1.0",
"eslint": "^3.17.1",
@@ -140,7 +149,6 @@
"read-last-lines": "^1.1.0",
"requirejs": "^2.3.1",
"through2": "^2.0.3",
- "uglify-js": "^2.8.12",
"watchify": "^3.9.0",
"xml2js": "^0.4.16"
}
diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js
index 1c244ba7c69..b1aa90ad957 100644
--- a/src/components/drawing/index.js
+++ b/src/components/drawing/index.js
@@ -213,6 +213,7 @@ drawing.symbolNames = [];
drawing.symbolFuncs = [];
drawing.symbolNeedLines = {};
drawing.symbolNoDot = {};
+drawing.symbolNoFill = {};
drawing.symbolList = [];
Object.keys(SYMBOLDEFS).forEach(function(k) {
@@ -231,6 +232,9 @@ Object.keys(SYMBOLDEFS).forEach(function(k) {
drawing.symbolList = drawing.symbolList.concat(
[symDef.n + 200, k + '-dot', symDef.n + 300, k + '-open-dot']);
}
+ if(symDef.noFill) {
+ drawing.symbolNoFill[symDef.n] = true;
+ }
});
var MAXSYMBOL = drawing.symbolNames.length,
// add a dot in the middle of the symbol
diff --git a/src/components/drawing/symbol_defs.js b/src/components/drawing/symbol_defs.js
index 548a8c5c307..1c337876c7f 100644
--- a/src/components/drawing/symbol_defs.js
+++ b/src/components/drawing/symbol_defs.js
@@ -355,7 +355,8 @@ module.exports = {
return 'M0,' + rc + 'V-' + rc + 'M' + rc + ',0H-' + rc;
},
needLine: true,
- noDot: true
+ noDot: true,
+ noFill: true
},
'x-thin': {
n: 34,
@@ -365,7 +366,8 @@ module.exports = {
'M' + rx + ',-' + rx + 'L-' + rx + ',' + rx;
},
needLine: true,
- noDot: true
+ noDot: true,
+ noFill: true
},
asterisk: {
n: 35,
@@ -377,7 +379,8 @@ module.exports = {
'M' + rs + ',-' + rs + 'L-' + rs + ',' + rs;
},
needLine: true,
- noDot: true
+ noDot: true,
+ noFill: true
},
hash: {
n: 36,
@@ -389,7 +392,8 @@ module.exports = {
'M' + r2 + ',' + r1 + 'H-' + r2 +
'm0,-' + r2 + 'H' + r2;
},
- needLine: true
+ needLine: true,
+ noFill: true
},
'y-up': {
n: 37,
@@ -400,7 +404,8 @@ module.exports = {
return 'M-' + x + ',' + y1 + 'L0,0M' + x + ',' + y1 + 'L0,0M0,-' + y0 + 'L0,0';
},
needLine: true,
- noDot: true
+ noDot: true,
+ noFill: true
},
'y-down': {
n: 38,
@@ -411,7 +416,8 @@ module.exports = {
return 'M-' + x + ',-' + y1 + 'L0,0M' + x + ',-' + y1 + 'L0,0M0,' + y0 + 'L0,0';
},
needLine: true,
- noDot: true
+ noDot: true,
+ noFill: true
},
'y-left': {
n: 39,
@@ -422,7 +428,8 @@ module.exports = {
return 'M' + x1 + ',' + y + 'L0,0M' + x1 + ',-' + y + 'L0,0M-' + x0 + ',0L0,0';
},
needLine: true,
- noDot: true
+ noDot: true,
+ noFill: true
},
'y-right': {
n: 40,
@@ -433,7 +440,8 @@ module.exports = {
return 'M-' + x1 + ',' + y + 'L0,0M-' + x1 + ',-' + y + 'L0,0M' + x0 + ',0L0,0';
},
needLine: true,
- noDot: true
+ noDot: true,
+ noFill: true
},
'line-ew': {
n: 41,
@@ -442,7 +450,8 @@ module.exports = {
return 'M' + rc + ',0H-' + rc;
},
needLine: true,
- noDot: true
+ noDot: true,
+ noFill: true
},
'line-ns': {
n: 42,
@@ -451,7 +460,8 @@ module.exports = {
return 'M0,' + rc + 'V-' + rc;
},
needLine: true,
- noDot: true
+ noDot: true,
+ noFill: true
},
'line-ne': {
n: 43,
@@ -460,7 +470,8 @@ module.exports = {
return 'M' + rx + ',-' + rx + 'L-' + rx + ',' + rx;
},
needLine: true,
- noDot: true
+ noDot: true,
+ noFill: true
},
'line-nw': {
n: 44,
@@ -469,6 +480,7 @@ module.exports = {
return 'M' + rx + ',' + rx + 'L-' + rx + ',-' + rx;
},
needLine: true,
- noDot: true
+ noDot: true,
+ noFill: true
}
};
diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js
index 3df5fd605ae..6f0cff967e2 100644
--- a/src/components/fx/hover.js
+++ b/src/components/fx/hover.js
@@ -519,7 +519,7 @@ function createHoverText(hoverData, opts, gd) {
var i, traceHoverinfo;
for(i = 0; i < hoverData.length; i++) {
traceHoverinfo = hoverData[i].hoverinfo || hoverData[i].trace.hoverinfo;
- var parts = traceHoverinfo.split('+');
+ var parts = Array.isArray(traceHoverinfo) ? traceHoverinfo : traceHoverinfo.split('+');
if(parts.indexOf('all') === -1 &&
parts.indexOf(hovermode) === -1) {
showCommonLabel = false;
@@ -1077,8 +1077,9 @@ function cleanPoint(d, hovermode) {
}
var infomode = d.hoverinfo || d.trace.hoverinfo;
+
if(infomode !== 'all') {
- infomode = infomode.split('+');
+ infomode = Array.isArray(infomode) ? infomode : infomode.split('+');
if(infomode.indexOf('x') === -1) d.xLabel = undefined;
if(infomode.indexOf('y') === -1) d.yLabel = undefined;
if(infomode.indexOf('z') === -1) d.zLabel = undefined;
diff --git a/src/fonts/ploticon/config.json b/src/fonts/ploticon/config.json
index 851669be315..6bdb659f75d 100644
--- a/src/fonts/ploticon/config.json
+++ b/src/fonts/ploticon/config.json
@@ -87,7 +87,7 @@
"width": 1500
},
"search": [
- "tooltip_basic"
+ "tooltip_basic"
]
},
{
diff --git a/src/lib/gl_format_color.js b/src/lib/gl_format_color.js
index 83052c63360..8d93b7e1f92 100644
--- a/src/lib/gl_format_color.js
+++ b/src/lib/gl_format_color.js
@@ -59,6 +59,7 @@ function formatColor(containerIn, opacityIn, len) {
if(isArrayColorIn) {
getColor = function(c, i) {
+ // FIXME: there is double work, considering that sclFunc does the opposite
return c[i] === undefined ? colorDfltRgba : rgba(sclFunc(c[i]));
};
}
diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js
index 205994bf1a6..e40fb0263af 100644
--- a/src/plot_api/plot_api.js
+++ b/src/plot_api/plot_api.js
@@ -220,16 +220,11 @@ Plotly.plot = function(gd, data, layout, config) {
'left': 0,
'width': '100%',
'height': '100%',
- 'overflow': 'visible'
+ 'overflow': 'visible',
+ 'pointer-events': 'none'
})
.attr('width', fullLayout.width)
.attr('height', fullLayout.height);
-
- fullLayout._glcanvas.filter(function(d) {
- return !d.pick;
- }).style({
- 'pointer-events': 'none'
- });
}
return Lib.syncOrAsync([
@@ -2804,6 +2799,7 @@ function makePlotFramework(gd) {
// FIXME: parcoords reuses this object, not the best pattern
fullLayout._glcontainer = fullLayout._paperdiv.selectAll('.gl-container')
.data([{}]);
+
fullLayout._glcontainer.enter().append('div')
.classed('gl-container', true);
diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js
index f59434c9963..1d943300628 100644
--- a/src/plot_api/subroutines.js
+++ b/src/plot_api/subroutines.js
@@ -498,7 +498,6 @@ exports.doModeBar = function(gd) {
if(updateFx) updateFx(fullLayout);
}
-
return Plots.previousPromises(gd);
};
diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js
index 1b7ca54a061..8029679da70 100644
--- a/src/plots/cartesian/dragbox.js
+++ b/src/plots/cartesian/dragbox.js
@@ -759,7 +759,9 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) {
}
// don't scale at all if neither axis is scalable here
- if(!xScaleFactor2 && !yScaleFactor2) continue;
+ if(!xScaleFactor2 && !yScaleFactor2) {
+ continue;
+ }
// but if only one is, reset the other axis scaling
if(!xScaleFactor2) xScaleFactor2 = 1;
diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js
index a9db08dd172..118472c6eb5 100644
--- a/src/plots/cartesian/index.js
+++ b/src/plots/cartesian/index.js
@@ -137,7 +137,7 @@ function plotOne(gd, plotinfo, cdSubplot, transitionOpts, makeOnCompleteCallback
}
}
- _module.plot(gd, plotinfo, cdModule, transitionOpts, makeOnCompleteCallback);
+ if(_module.plot) _module.plot(gd, plotinfo, cdModule, transitionOpts, makeOnCompleteCallback);
}
}
@@ -145,13 +145,14 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
var oldModules = oldFullLayout._modules || [],
newModules = newFullLayout._modules || [];
- var hadScatter, hasScatter, i;
+ var hadScatter, hasScatter, hadGl, hasGl, i, oldPlots, ids, subplotInfo;
+
for(i = 0; i < oldModules.length; i++) {
if(oldModules[i].name === 'scatter') {
hadScatter = true;
- break;
}
+ break;
}
for(i = 0; i < newModules.length; i++) {
@@ -161,12 +162,26 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
}
}
+ for(i = 0; i < oldModules.length; i++) {
+ if(oldModules[i].name === 'scattergl') {
+ hadGl = true;
+ }
+ break;
+ }
+
+ for(i = 0; i < newModules.length; i++) {
+ if(newModules[i].name === 'scattergl') {
+ hasGl = true;
+ break;
+ }
+ }
+
if(hadScatter && !hasScatter) {
- var oldPlots = oldFullLayout._plots,
- ids = Object.keys(oldPlots || {});
+ oldPlots = oldFullLayout._plots;
+ ids = Object.keys(oldPlots || {});
for(i = 0; i < ids.length; i++) {
- var subplotInfo = oldPlots[ids[i]];
+ subplotInfo = oldPlots[ids[i]];
if(subplotInfo.plot) {
subplotInfo.plot.select('g.scatterlayer')
@@ -181,6 +196,19 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
.remove();
}
+ if(hadGl && !hasGl) {
+ oldPlots = oldFullLayout._plots;
+ ids = Object.keys(oldPlots || {});
+
+ for(i = 0; i < ids.length; i++) {
+ subplotInfo = oldPlots[ids[i]];
+
+ if(subplotInfo._scene) {
+ subplotInfo._scene.destroy();
+ }
+ }
+ }
+
var hadCartesian = (oldFullLayout._has && oldFullLayout._has('cartesian'));
var hasCartesian = (newFullLayout._has && newFullLayout._has('cartesian'));
@@ -222,7 +250,6 @@ exports.drawFramework = function(gd) {
plotinfo.overlays = [];
makeSubplotLayer(plotinfo);
-
// fill in list of overlay subplots
if(plotinfo.mainplot) {
var mainplot = fullLayout._plots[plotinfo.mainplot];
diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js
index e1429f6e911..9fdd82faa53 100644
--- a/src/plots/cartesian/select.js
+++ b/src/plots/cartesian/select.js
@@ -62,12 +62,11 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
if(mode === 'lasso') {
filterPoly = filteredPolygon([[x0, y0]], constants.BENDPX);
}
-
- var outlines = zoomLayer.selectAll('path.select-outline').data([1, 2]);
+ var outlines = zoomLayer.selectAll('path.select-outline-' + plotinfo.id).data([1, 2]);
outlines.enter()
.append('path')
- .attr('class', function(d) { return 'select-outline select-outline-' + d; })
+ .attr('class', function(d) { return 'select-outline select-outline-' + d + ' select-outline-' + plotinfo.id; })
.attr('transform', 'translate(' + xs + ', ' + ys + ')')
.attr('d', path0 + 'Z');
@@ -148,7 +147,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
}
};
} else {
- fillRangeItems = function(eventData, currentPolygon, filterPoly) {
+ fillRangeItems = function(eventData, poly, filterPoly) {
var dataPts = eventData.lassoPoints = {};
for(i = 0; i < allAxes.length; i++) {
@@ -225,7 +224,8 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
var ppts = mergedPolygons[i];
paths.push(ppts.join('L') + 'L' + ppts[0]);
}
- outlines.attr('d', 'M' + paths.join('M') + 'Z');
+ outlines
+ .attr('d', 'M' + paths.join('M') + 'Z');
throttle.throttle(
throttleID,
@@ -233,14 +233,15 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
function() {
selection = [];
- var traceSelections = [], traceSelection;
+ var thisSelection, traceSelections = [], traceSelection;
for(i = 0; i < searchTraces.length; i++) {
searchInfo = searchTraces[i];
traceSelection = searchInfo.selectPoints(searchInfo, testPoly);
traceSelections.push(traceSelection);
- var thisSelection = fillSelectionItem(traceSelection, searchInfo);
+ thisSelection = fillSelectionItem(traceSelection, searchInfo);
+
if(selection.length) {
for(var j = 0; j < thisSelection.length; j++) {
selection.push(thisSelection[j]);
@@ -308,8 +309,8 @@ function updateSelectedState(gd, searchTraces, eventData) {
var fullData = pt.fullData;
if(pt.pointIndices) {
- data.selectedpoints = data.selectedpoints.concat(pt.pointIndices);
- fullData.selectedpoints = fullData.selectedpoints.concat(pt.pointIndices);
+ [].push.apply(data.selectedpoints, pt.pointIndices);
+ [].push.apply(fullData.selectedpoints, pt.pointIndices);
} else {
data.selectedpoints.push(pt.pointIndex);
fullData.selectedpoints.push(pt.pointIndex);
@@ -321,6 +322,11 @@ function updateSelectedState(gd, searchTraces, eventData) {
trace = searchTraces[i].cd[0].trace;
delete trace.selectedpoints;
delete trace._input.selectedpoints;
+
+ // delete scattergl selection
+ if(searchTraces[i].cd[0].t && searchTraces[i].cd[0].t.scene) {
+ searchTraces[i].cd[0].t.scene.clearSelect();
+ }
}
}
diff --git a/src/plots/plots.js b/src/plots/plots.js
index 5d0515a0a42..aabf96f2609 100644
--- a/src/plots/plots.js
+++ b/src/plots/plots.js
@@ -161,6 +161,7 @@ plots.getSubplotData = function getSubplotData(data, type, subplotId) {
return subplotData;
};
+
/**
* Get calcdata traces(s) associated with a given subplot
*
@@ -572,7 +573,6 @@ plots.createTransitionData = function(gd) {
// or trace has a category
plots._hasPlotType = function(category) {
// check plot
-
var basePlotModules = this._basePlotModules || [];
var i;
@@ -681,10 +681,6 @@ plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLa
if(oldSubplot) {
plotinfo = newSubplots[id] = oldSubplot;
- if(plotinfo._scene2d) {
- plotinfo._scene2d.updateRefs(newFullLayout);
- }
-
if(plotinfo.xaxis.layer !== xaxis.layer) {
plotinfo.xlines.attr('d', null);
plotinfo.xaxislayer.selectAll('*').remove();
@@ -814,7 +810,6 @@ plots.supplyDataDefaults = function(dataIn, dataOut, layout, fullLayout) {
var _module = fullTrace._module;
if(!_module) return;
-
Lib.pushUnique(modules, _module);
Lib.pushUnique(basePlotModules, fullTrace._module.basePlotModule);
@@ -860,7 +855,6 @@ plots.supplyDataDefaults = function(dataIn, dataOut, layout, fullLayout) {
}
}
else {
-
// add identify refs for consistency with transformed traces
fullTrace._fullInput = fullTrace;
fullTrace._expandedInput = fullTrace;
@@ -1000,7 +994,7 @@ plots.supplyTraceDefaults = function(traceIn, traceOutIndex, layout, traceInInde
var subplotType = subplotTypes[i];
// done below (only when visible is true)
- // TODO unified this pattern
+ // TODO unify this pattern
if(['cartesian', 'gl2d'].indexOf(subplotType) !== -1) continue;
var attr = subplotsRegistry[subplotType].attr;
@@ -1008,13 +1002,14 @@ plots.supplyTraceDefaults = function(traceIn, traceOutIndex, layout, traceInInde
if(attr) coerceSubplotAttr(subplotType, attr);
}
+
+ var _module = plots.getModule(traceOut);
+ traceOut._module = _module;
+
if(visible) {
coerce('customdata');
coerce('ids');
- var _module = plots.getModule(traceOut);
- traceOut._module = _module;
-
if(plots.traceIs(traceOut, 'showLegend')) {
coerce('showlegend');
coerce('legendgroup');
@@ -1046,7 +1041,7 @@ plots.supplyTraceDefaults = function(traceIn, traceOutIndex, layout, traceInInde
traceOut.visible = !!traceOut.visible;
}
- if(_module && _module.selectPoints && traceOut.type !== 'scattergl') {
+ if(_module && _module.selectPoints) {
coerce('selectedpoints');
}
@@ -1326,7 +1321,6 @@ plots.supplyLayoutModuleDefaults = function(layoutIn, layoutOut, fullData, trans
// Remove all plotly attributes from a div so it can be replotted fresh
// TODO: these really need to be encapsulated into a much smaller set...
plots.purge = function(gd) {
-
// note: we DO NOT remove _context because it doesn't change when we insert
// a new plot, and may have been set outside of our scope.
diff --git a/src/traces/parcoords/attributes.js b/src/traces/parcoords/attributes.js
index e994a5d2c9a..d7b34b20d9d 100644
--- a/src/traces/parcoords/attributes.js
+++ b/src/traces/parcoords/attributes.js
@@ -19,7 +19,6 @@ var extendDeepAll = extend.extendDeepAll;
var extendFlat = extend.extendFlat;
module.exports = {
-
domain: {
x: {
valType: 'info_array',
@@ -136,6 +135,7 @@ module.exports = {
line: extendFlat(
// the default autocolorscale isn't quite usable for parcoords due to context ambiguity around 0 (grey, off-white)
+
// autocolorscale therefore defaults to false too, to avoid being overridden by the blue-white-red autocolor palette
extendDeepAll(
colorAttributes('line', 'calc'),
@@ -153,7 +153,6 @@ module.exports = {
'The default value is false, so that `parcoords` colorscale can default to `Viridis`.'
].join(' ')
}
-
}
),
diff --git a/src/traces/parcoords/base_plot.js b/src/traces/parcoords/base_plot.js
index c562787b289..9a2a5074191 100644
--- a/src/traces/parcoords/base_plot.js
+++ b/src/traces/parcoords/base_plot.js
@@ -33,7 +33,6 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
};
exports.toSVG = function(gd) {
-
var imageRoot = gd._fullLayout._glimages;
var root = d3.select(gd).selectAll('.svg-container');
var canvases = root.filter(function(d, i) {return i === root.size() - 1;})
@@ -47,11 +46,11 @@ exports.toSVG = function(gd) {
image.attr({
xmlns: xmlnsNamespaces.svg,
'xlink:href': imageData,
+ preserveAspectRatio: 'none',
x: 0,
y: 0,
width: canvas.width,
- height: canvas.height,
- preserveAspectRatio: 'none'
+ height: canvas.height
});
}
diff --git a/src/traces/parcoords/lines.js b/src/traces/parcoords/lines.js
index b63b14b13f8..31d861eb99e 100644
--- a/src/traces/parcoords/lines.js
+++ b/src/traces/parcoords/lines.js
@@ -55,7 +55,8 @@ function renderBlock(regl, glAes, renderState, blockLineCount, sampleCount, item
item.offset = sectionVertexCount * blockNumber * blockLineCount;
item.count = sectionVertexCount * count;
if(blockNumber === 0) {
- window.cancelAnimationFrame(renderState.currentRafs[rafKey]); // stop drawing possibly stale glyphs before clearing
+ // stop drawing possibly stale glyphs before clearing
+ window.cancelAnimationFrame(renderState.currentRafs[rafKey]);
delete renderState.currentRafs[rafKey];
clear(regl, item.scissorX, item.scissorY, item.scissorWidth, item.viewBoxSize[1]);
}
@@ -353,6 +354,7 @@ module.exports = function(canvasGL, d, scatter) {
colorClamp: colorClamp,
scatter: scatter || 0,
+
scissorX: (I === leftmost ? 0 : x + overdrag) + (model.pad.l - overdrag) + model.layoutWidth * domain.x[0],
scissorWidth: (I === rightmost ? canvasWidth - x + overdrag : panelSizeX + 0.5) + (I === leftmost ? x + overdrag : 0),
scissorY: y + model.pad.b + model.layoutHeight * domain.y[0],
@@ -431,6 +433,7 @@ module.exports = function(canvasGL, d, scatter) {
}
function destroy() {
+ canvasGL.style['pointer-events'] = 'none';
paletteTexture.destroy();
}
diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js
index 96b83392650..3246b1a3bf8 100644
--- a/src/traces/parcoords/parcoords.js
+++ b/src/traces/parcoords/parcoords.js
@@ -305,6 +305,7 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca
.filter(function(d) {
return d.pick;
})
+ .style('pointer-events', 'auto')
.on('mousemove', function(d) {
if(linePickActive && d.lineLayer && callbacks && callbacks.hover) {
var event = d3.event;
diff --git a/src/traces/parcoords/plot.js b/src/traces/parcoords/plot.js
index 433938ec1b9..284a25f4766 100644
--- a/src/traces/parcoords/plot.js
+++ b/src/traces/parcoords/plot.js
@@ -12,7 +12,6 @@ var parcoords = require('./parcoords');
var createRegl = require('regl');
module.exports = function plot(gd, cdparcoords) {
-
var fullLayout = gd._fullLayout;
var svg = fullLayout._toppaper;
var root = fullLayout._paperdiv;
diff --git a/src/traces/pointcloud/attributes.js b/src/traces/pointcloud/attributes.js
index 17f4af0418a..32f5499c158 100644
--- a/src/traces/pointcloud/attributes.js
+++ b/src/traces/pointcloud/attributes.js
@@ -8,7 +8,7 @@
'use strict';
-var scatterglAttrs = require('../scattergl/attributes');
+var scatterglAttrs = require('../scatter/attributes');
module.exports = {
x: scatterglAttrs.x,
diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js
index cf225193709..437c266c3ac 100644
--- a/src/traces/scattergl/attributes.js
+++ b/src/traces/scattergl/attributes.js
@@ -12,7 +12,6 @@ var scatterAttrs = require('../scatter/attributes');
var colorAttributes = require('../../components/colorscale/color_attributes');
var DASHES = require('../../constants/gl2d_dashes');
-var MARKERS = require('../../constants/gl2d_markers');
var extendFlat = require('../../lib/extend').extendFlat;
var overrideAll = require('../../plot_api/edit_types').overrideAll;
@@ -58,14 +57,7 @@ var attrs = module.exports = overrideAll({
}
},
marker: extendFlat({}, colorAttributes('marker'), {
- symbol: {
- valType: 'enumerated',
- values: Object.keys(MARKERS),
- dflt: 'circle',
- arrayOk: true,
- role: 'style',
- description: 'Sets the marker symbol type.'
- },
+ symbol: scatterMarkerAttrs.symbol,
size: scatterMarkerAttrs.size,
sizeref: scatterMarkerAttrs.sizeref,
sizemin: scatterMarkerAttrs.sizemin,
@@ -78,11 +70,18 @@ var attrs = module.exports = overrideAll({
})
}),
connectgaps: scatterAttrs.connectgaps,
- fill: extendFlat({}, scatterAttrs.fill, {
- values: ['none', 'tozeroy', 'tozerox']
- }),
+ fill: scatterAttrs.fill,
fillcolor: scatterAttrs.fillcolor,
+ hoveron: scatterAttrs.hoveron,
+
+ selected: {
+ marker: scatterAttrs.selected.marker
+ },
+ unselected: {
+ marker: scatterAttrs.unselected.marker
+ },
+
error_y: scatterAttrs.error_y,
error_x: scatterAttrs.error_x
}, 'calc', 'nested');
diff --git a/src/traces/scattergl/calc.js b/src/traces/scattergl/calc.js
deleted file mode 100644
index 1be524af55e..00000000000
--- a/src/traces/scattergl/calc.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/**
-* Copyright 2012-2017, 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 arraysToCalcdata = require('../scatter/arrays_to_calcdata');
-var calcColorscales = require('../scatter/colorscale_calc');
-
-module.exports = function calc(gd, trace) {
- var dragmode = gd._fullLayout.dragmode;
- var cd;
-
- if(dragmode === 'lasso' || dragmode === 'select') {
- var xa = Axes.getFromId(gd, trace.xaxis || 'x');
- var ya = Axes.getFromId(gd, trace.yaxis || 'y');
-
- var x = xa.makeCalcdata(trace, 'x');
- var y = ya.makeCalcdata(trace, 'y');
-
- var serieslen = Math.min(x.length, y.length), i;
-
- // create the "calculated data" to plot
- cd = new Array(serieslen);
-
- for(i = 0; i < serieslen; i++) {
- cd[i] = {x: x[i], y: y[i]};
- }
- } else {
- cd = [{x: false, y: false, trace: trace, t: {}}];
- arraysToCalcdata(cd, trace);
- }
-
- calcColorscales(trace);
-
- return cd;
-};
diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js
deleted file mode 100644
index 5b7be4466d9..00000000000
--- a/src/traces/scattergl/convert.js
+++ /dev/null
@@ -1,768 +0,0 @@
-/**
-* Copyright 2012-2017, 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 createScatter = require('gl-scatter2d');
-var createFancyScatter = require('gl-scatter2d-sdf');
-var createLine = require('gl-line2d');
-var createError = require('gl-error2d');
-var isNumeric = require('fast-isnumeric');
-
-var Lib = require('../../lib');
-var Axes = require('../../plots/cartesian/axes');
-var autoType = require('../../plots/cartesian/axis_autotype');
-var ErrorBars = require('../../components/errorbars');
-var str2RGBArray = require('../../lib/str2rgbarray');
-var truncate = require('../../lib/typed_array_truncate');
-var formatColor = require('../../lib/gl_format_color');
-var subTypes = require('../scatter/subtypes');
-var makeBubbleSizeFn = require('../scatter/make_bubble_size_func');
-var getTraceColor = require('../scatter/get_trace_color');
-var MARKER_SYMBOLS = require('../../constants/gl2d_markers');
-var DASHES = require('../../constants/gl2d_dashes');
-var DESELECTDIM = require('../../constants/interactions').DESELECTDIM;
-
-var AXES = ['xaxis', 'yaxis'];
-var TRANSPARENT = [0, 0, 0, 0];
-
-function LineWithMarkers(scene, uid) {
- this.scene = scene;
- this.uid = uid;
- this.type = 'scattergl';
-
- this.pickXData = [];
- this.pickYData = [];
- this.xData = [];
- this.yData = [];
- this.textLabels = [];
- this.color = 'rgb(0, 0, 0)';
- this.name = '';
- this.hoverinfo = 'all';
- this.connectgaps = true;
-
- this.index = null;
- this.idToIndex = [];
- this.bounds = [0, 0, 0, 0];
-
- this.isVisible = false;
- this.hasLines = false;
- this.hasErrorX = false;
- this.hasErrorY = false;
- this.hasMarkers = false;
-
- this.line = this.initObject(createLine, {
- positions: new Float64Array(0),
- color: [0, 0, 0, 1],
- width: 1,
- fill: [false, false, false, false],
- fillColor: [
- [0, 0, 0, 1],
- [0, 0, 0, 1],
- [0, 0, 0, 1],
- [0, 0, 0, 1]],
- dashes: [1],
- }, 0);
-
- this.errorX = this.initObject(createError, {
- positions: new Float64Array(0),
- errors: new Float64Array(0),
- lineWidth: 1,
- capSize: 0,
- color: [0, 0, 0, 1]
- }, 1);
-
- this.errorY = this.initObject(createError, {
- positions: new Float64Array(0),
- errors: new Float64Array(0),
- lineWidth: 1,
- capSize: 0,
- color: [0, 0, 0, 1]
- }, 2);
-
- var scatterOptions0 = {
- positions: new Float64Array(0),
- sizes: [],
- colors: [],
- glyphs: [],
- borderWidths: [],
- borderColors: [],
- size: 12,
- color: [0, 0, 0, 1],
- borderSize: 1,
- borderColor: [0, 0, 0, 1],
- snapPoints: true
- };
- var scatterOptions1 = Lib.extendFlat({}, scatterOptions0, {snapPoints: false});
-
- this.scatter = this.initObject(createScatter, scatterOptions0, 3);
- this.fancyScatter = this.initObject(createFancyScatter, scatterOptions0, 4);
- this.selectScatter = this.initObject(createScatter, scatterOptions1, 5);
-}
-
-var proto = LineWithMarkers.prototype;
-
-proto.initObject = function(createFn, options, objIndex) {
- var _this = this;
- var glplot = _this.scene.glplot;
- var options0 = Lib.extendFlat({}, options);
- var obj = null;
-
- function update() {
- if(!obj) {
- obj = createFn(glplot, options);
- obj._trace = _this;
- obj._index = objIndex;
- }
- obj.update(options);
- }
-
- function clear() {
- if(obj) obj.update(options0);
- }
-
- function dispose() {
- if(obj) obj.dispose();
- }
-
- return {
- options: options,
- update: update,
- clear: clear,
- dispose: dispose
- };
-};
-
-proto.handlePick = function(pickResult) {
- var index = pickResult.pointId;
-
- if(pickResult.object !== this.line || this.connectgaps) {
- index = this.idToIndex[pickResult.pointId];
- }
-
- var x = this.pickXData[index];
-
- return {
- trace: this,
- dataCoord: pickResult.dataCoord,
- traceCoord: [
- isNumeric(x) || !Lib.isDateTime(x) ? x : Lib.dateTime2ms(x),
- this.pickYData[index]
- ],
- textLabel: Array.isArray(this.textLabels) ?
- this.textLabels[index] :
- this.textLabels,
- color: Array.isArray(this.color) ?
- this.color[index] :
- this.color,
- name: this.name,
- pointIndex: index,
- hoverinfo: this.hoverinfo
- };
-};
-
-// check if trace is fancy
-proto.isFancy = function(options) {
- if(this.scene.xaxis.type !== 'linear' && this.scene.xaxis.type !== 'date') return true;
- if(this.scene.yaxis.type !== 'linear') return true;
-
- if(!options.x || !options.y) return true;
-
- if(this.hasMarkers) {
- var marker = options.marker || {};
-
- if(Array.isArray(marker.symbol) ||
- marker.symbol !== 'circle' ||
- Array.isArray(marker.size) ||
- Array.isArray(marker.color) ||
- Array.isArray(marker.line.width) ||
- Array.isArray(marker.line.color) ||
- Array.isArray(marker.opacity)
- ) return true;
- }
-
- if(this.hasLines && !this.connectgaps) return true;
-
- if(this.hasErrorX) return true;
- if(this.hasErrorY) return true;
-
- return false;
-};
-
-// handle the situation where values can be array-like or not array like
-function convertArray(convert, data, count) {
- if(!Array.isArray(data)) data = [data];
-
- return _convertArray(convert, data, count);
-}
-
-function _convertArray(convert, data, count) {
- var result = new Array(count),
- data0 = data[0];
-
- for(var i = 0; i < count; ++i) {
- result[i] = (i >= data.length) ?
- convert(data0) :
- convert(data[i]);
- }
-
- return result;
-}
-
-var convertNumber = convertArray.bind(null, function(x) { return +x; });
-var convertColorBase = convertArray.bind(null, str2RGBArray);
-var convertSymbol = convertArray.bind(null, function(x) {
- return MARKER_SYMBOLS[x] ? x : 'circle';
-});
-
-function convertColor(color, opacity, count) {
- return _convertColor(
- convertColorBase(color, count),
- convertNumber(opacity, count),
- count
- );
-}
-
-function convertColorScale(containerIn, markerOpacity, traceOpacity, count) {
- var colors = formatColor(containerIn, markerOpacity, count);
-
- colors = Array.isArray(colors[0]) ?
- colors :
- _convertArray(Lib.identity, [colors], count);
-
- return _convertColor(
- colors,
- convertNumber(traceOpacity, count),
- count
- );
-}
-
-function _convertColor(colors, opacities, count) {
- var result = new Array(4 * count);
-
- for(var i = 0; i < count; ++i) {
- for(var j = 0; j < 3; ++j) result[4 * i + j] = colors[i][j];
-
- result[4 * i + 3] = colors[i][3] * opacities[i];
- }
-
- return result;
-}
-
-function isSymbolOpen(symbol) {
- return symbol.split('-open')[1] === '';
-}
-
-function fillColor(colorIn, colorOut, offsetIn, offsetOut, isDimmed) {
- var dim = isDimmed ? DESELECTDIM : 1;
- var j;
-
- for(j = 0; j < 3; j++) {
- colorIn[4 * offsetIn + j] = colorOut[4 * offsetOut + j];
- }
- colorIn[4 * offsetIn + j] = dim * colorOut[4 * offsetOut + j];
-}
-
-proto.update = function(options, cdscatter) {
- if(options.visible !== true) {
- this.isVisible = false;
- this.hasLines = false;
- this.hasErrorX = false;
- this.hasErrorY = false;
- this.hasMarkers = false;
- }
- else {
- this.isVisible = true;
- this.hasLines = subTypes.hasLines(options);
- this.hasErrorX = options.error_x.visible === true;
- this.hasErrorY = options.error_y.visible === true;
- this.hasMarkers = subTypes.hasMarkers(options);
- }
-
- this.textLabels = options.text;
- this.name = options.name;
- this.hoverinfo = options.hoverinfo;
- this.bounds = [Infinity, Infinity, -Infinity, -Infinity];
- this.connectgaps = !!options.connectgaps;
-
- if(!this.isVisible) {
- this.line.clear();
- this.errorX.clear();
- this.errorY.clear();
- this.scatter.clear();
- this.fancyScatter.clear();
- }
- else if(this.isFancy(options)) {
- this.updateFancy(options);
- }
- else {
- this.updateFast(options);
- }
-
- // sort objects so that order is preserve on updates:
- // - lines
- // - errorX
- // - errorY
- // - markers
- this.scene.glplot.objects.sort(function(a, b) {
- return a._index - b._index;
- });
-
- // set trace index so that scene2d can sort object per traces
- this.index = options.index;
-
- // not quite on-par with 'scatter', but close enough for now
- // does not handle the colorscale case
- this.color = getTraceColor(options, {});
-
- // provide reference for selecting points
- if(cdscatter && cdscatter[0] && !cdscatter[0]._glTrace) {
- cdscatter[0]._glTrace = this;
- }
-};
-
-// We'd ideally know that all values are of fast types; sampling gives no certainty but faster
-// (for the future, typed arrays can guarantee it, and Date values can be done with
-// representing the epoch milliseconds in a typed array;
-// also, perhaps the Python / R interfaces take care of String->Date conversions
-// such that there's no need to check for string dates in plotly.js)
-// Patterned from axis_autotype.js:moreDates
-// Code DRYing is not done to preserve the most direct compilation possible for speed;
-// also, there are quite a few differences
-function allFastTypesLikely(a) {
- var len = a.length,
- inc = Math.max(1, (len - 1) / Math.min(Math.max(len, 1), 1000)),
- ai;
-
- for(var i = 0; i < len; i += inc) {
- ai = a[Math.floor(i)];
- if(!isNumeric(ai) && !(ai instanceof Date)) {
- return false;
- }
- }
-
- return true;
-}
-
-proto.updateFast = function(options) {
- var x = this.xData = this.pickXData = options.x;
- var y = this.yData = this.pickYData = options.y;
-
- var len = x.length,
- idToIndex = new Array(len),
- positions = new Float64Array(2 * len),
- bounds = this.bounds,
- pId = 0,
- ptr = 0,
- selection = options.selection,
- i, selPositions, l;
-
- var xx, yy;
-
- var xcalendar = options.xcalendar;
-
- var fastType = allFastTypesLikely(x);
- var isDateTime = !fastType && autoType(x, xcalendar) === 'date';
-
- // TODO add 'very fast' mode that bypasses this loop
- // TODO bypass this on modebar +/- zoom
- if(fastType || isDateTime) {
-
- for(i = 0; i < len; ++i) {
- xx = x[i];
- yy = y[i];
-
- if(isNumeric(yy)) {
-
- if(!fastType) {
- xx = Lib.dateTime2ms(xx, xcalendar);
- }
-
- positions[ptr++] = xx;
- positions[ptr++] = yy;
-
- idToIndex[pId++] = i;
-
- bounds[0] = Math.min(bounds[0], xx);
- bounds[1] = Math.min(bounds[1], yy);
- bounds[2] = Math.max(bounds[2], xx);
- bounds[3] = Math.max(bounds[3], yy);
- }
- }
- }
-
- positions = truncate(positions, ptr);
- this.idToIndex = idToIndex;
-
- // form selected set
- if(selection && selection.length) {
- selPositions = new Float64Array(2 * selection.length);
-
- for(i = 0, l = selection.length; i < l; i++) {
- selPositions[i * 2 + 0] = selection[i].x;
- selPositions[i * 2 + 1] = selection[i].y;
- }
- }
-
- this.updateLines(options, positions);
- this.updateError('X', options);
- this.updateError('Y', options);
-
- var markerSize;
-
- if(this.hasMarkers) {
- var markerColor, borderColor, opacity;
-
- // if we have selPositions array - means we have to render all points transparent, and selected points opaque
- if(selPositions) {
- this.scatter.options.positions = null;
-
- markerColor = str2RGBArray(options.marker.color);
- borderColor = str2RGBArray(options.marker.line.color);
- opacity = (options.opacity) * (options.marker.opacity) * DESELECTDIM;
-
- markerColor[3] *= opacity;
- this.scatter.options.color = markerColor;
-
- borderColor[3] *= opacity;
- this.scatter.options.borderColor = borderColor;
-
- markerSize = options.marker.size;
- this.scatter.options.size = markerSize;
- this.scatter.options.borderSize = options.marker.line.width;
-
- this.scatter.update();
- this.scatter.options.positions = positions;
-
-
- this.selectScatter.options.positions = selPositions;
-
- markerColor = str2RGBArray(options.marker.color);
- borderColor = str2RGBArray(options.marker.line.color);
- opacity = (options.opacity) * (options.marker.opacity);
-
- markerColor[3] *= opacity;
- this.selectScatter.options.color = markerColor;
-
- borderColor[3] *= opacity;
- this.selectScatter.options.borderColor = borderColor;
-
- markerSize = options.marker.size;
- this.selectScatter.options.size = markerSize;
- this.selectScatter.options.borderSize = options.marker.line.width;
-
- this.selectScatter.update();
- }
-
- else {
- this.scatter.options.positions = positions;
-
- markerColor = str2RGBArray(options.marker.color);
- borderColor = str2RGBArray(options.marker.line.color);
- opacity = (options.opacity) * (options.marker.opacity);
- markerColor[3] *= opacity;
- this.scatter.options.color = markerColor;
-
- borderColor[3] *= opacity;
- this.scatter.options.borderColor = borderColor;
-
- markerSize = options.marker.size;
- this.scatter.options.size = markerSize;
- this.scatter.options.borderSize = options.marker.line.width;
-
- this.scatter.update();
- }
-
- }
- else {
- this.scatter.clear();
- }
-
- // turn off fancy scatter plot
- this.fancyScatter.clear();
-
- // add item for autorange routine
- this.expandAxesFast(bounds, markerSize);
-};
-
-proto.updateFancy = function(options) {
- var scene = this.scene,
- xaxis = scene.xaxis,
- yaxis = scene.yaxis,
- bounds = this.bounds,
- selection = options.selection;
-
- // makeCalcdata runs d2c (data-to-coordinate) on every point
- var x = this.pickXData = xaxis.makeCalcdata(options, 'x').slice();
- var y = this.pickYData = yaxis.makeCalcdata(options, 'y').slice();
-
- this.xData = x.slice();
- this.yData = y.slice();
-
- // get error values
- var errorVals = ErrorBars.calcFromTrace(options, scene.fullLayout);
-
- var len = x.length,
- idToIndex = new Array(len),
- positions = new Float64Array(2 * len),
- errorsX = new Float64Array(4 * len),
- errorsY = new Float64Array(4 * len),
- pId = 0,
- ptr = 0,
- ptrX = 0,
- ptrY = 0;
-
- var getX = (xaxis.type === 'log') ? xaxis.d2l : function(x) { return x; };
- var getY = (yaxis.type === 'log') ? yaxis.d2l : function(y) { return y; };
-
- var i, xx, yy, ex0, ex1, ey0, ey1;
-
- for(i = 0; i < len; ++i) {
- this.xData[i] = xx = getX(x[i]);
- this.yData[i] = yy = getY(y[i]);
-
- if(isNaN(xx) || isNaN(yy)) continue;
-
- idToIndex[pId++] = i;
-
- positions[ptr++] = xx;
- positions[ptr++] = yy;
-
- ex0 = errorsX[ptrX++] = xx - errorVals[i].xs || 0;
- ex1 = errorsX[ptrX++] = errorVals[i].xh - xx || 0;
- errorsX[ptrX++] = 0;
- errorsX[ptrX++] = 0;
-
- errorsY[ptrY++] = 0;
- errorsY[ptrY++] = 0;
- ey0 = errorsY[ptrY++] = yy - errorVals[i].ys || 0;
- ey1 = errorsY[ptrY++] = errorVals[i].yh - yy || 0;
-
- bounds[0] = Math.min(bounds[0], xx - ex0);
- bounds[1] = Math.min(bounds[1], yy - ey0);
- bounds[2] = Math.max(bounds[2], xx + ex1);
- bounds[3] = Math.max(bounds[3], yy + ey1);
- }
-
- positions = truncate(positions, ptr);
- this.idToIndex = idToIndex;
-
- this.updateLines(options, positions);
- this.updateError('X', options, positions, errorsX);
- this.updateError('Y', options, positions, errorsY);
-
- var sizes, selIds;
-
- if(selection && selection.length) {
- selIds = {};
- for(i = 0; i < selection.length; i++) {
- selIds[selection[i].pointNumber] = true;
- }
- }
-
- if(this.hasMarkers) {
- this.scatter.options.positions = positions;
-
- // TODO rewrite convert function so that
- // we don't have to loop through the data another time
-
- this.scatter.options.sizes = new Array(pId);
- this.scatter.options.glyphs = new Array(pId);
- this.scatter.options.borderWidths = new Array(pId);
- this.scatter.options.colors = new Array(pId * 4);
- this.scatter.options.borderColors = new Array(pId * 4);
-
- var markerSizeFunc = makeBubbleSizeFn(options);
- var markerOpts = options.marker;
- var markerOpacity = markerOpts.opacity;
- var traceOpacity = options.opacity;
- var symbols = convertSymbol(markerOpts.symbol, len);
- var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len);
- var borderWidths = convertNumber(markerOpts.line.width, len);
- var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len);
- var index, size, symbol, symbolSpec, isOpen, isDimmed, _colors, _borderColors, bw, minBorderWidth;
-
- sizes = convertArray(markerSizeFunc, markerOpts.size, len);
-
- for(i = 0; i < pId; ++i) {
- index = idToIndex[i];
-
- symbol = symbols[index];
- symbolSpec = MARKER_SYMBOLS[symbol];
- isOpen = isSymbolOpen(symbol);
- isDimmed = selIds && !selIds[index];
-
- if(symbolSpec.noBorder && !isOpen) {
- _colors = borderColors;
- } else {
- _colors = colors;
- }
-
- if(isOpen) {
- _borderColors = colors;
- } else {
- _borderColors = borderColors;
- }
-
- // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798
- // for more info on this logic
- size = sizes[index];
- bw = borderWidths[index];
- minBorderWidth = (symbolSpec.noBorder || symbolSpec.noFill) ? 0.1 * size : 0;
-
- this.scatter.options.sizes[i] = 4.0 * size;
- this.scatter.options.glyphs[i] = symbolSpec.unicode;
- this.scatter.options.borderWidths[i] = 0.5 * ((bw > minBorderWidth) ? bw - minBorderWidth : 0);
-
- if(isOpen && !symbolSpec.noBorder && !symbolSpec.noFill) {
- fillColor(this.scatter.options.colors, TRANSPARENT, i, 0);
- } else {
- fillColor(this.scatter.options.colors, _colors, i, index, isDimmed);
- }
- fillColor(this.scatter.options.borderColors, _borderColors, i, index, isDimmed);
- }
-
- // prevent scatter from resnapping points
- if(selIds) {
- this.scatter.options.positions = null;
- this.fancyScatter.update();
- this.scatter.options.positions = positions;
- }
- else {
- this.fancyScatter.update();
- }
- }
- else {
- this.fancyScatter.clear();
- }
-
- // turn off fast scatter plot
- this.scatter.clear();
-
- // add item for autorange routine
- this.expandAxesFancy(x, y, sizes);
-};
-
-proto.updateLines = function(options, positions) {
- var i;
-
- if(this.hasLines) {
- var linePositions = positions;
-
- if(!options.connectgaps) {
- var p = 0;
- var x = this.xData;
- var y = this.yData;
- linePositions = new Float64Array(2 * x.length);
-
- for(i = 0; i < x.length; ++i) {
- linePositions[p++] = x[i];
- linePositions[p++] = y[i];
- }
- }
-
- this.line.options.positions = linePositions;
-
- var lineColor = convertColor(options.line.color, options.opacity, 1),
- lineWidth = Math.round(0.5 * this.line.options.width),
- dashes = (DASHES[options.line.dash] || [1]).slice();
-
- for(i = 0; i < dashes.length; ++i) dashes[i] *= lineWidth;
-
- switch(options.fill) {
- case 'tozeroy':
- this.line.options.fill = [false, true, false, false];
- break;
- case 'tozerox':
- this.line.options.fill = [true, false, false, false];
- break;
- default:
- this.line.options.fill = [false, false, false, false];
- break;
- }
-
- var fillColor = str2RGBArray(options.fillcolor);
-
- this.line.options.color = lineColor;
- this.line.options.width = 2.0 * options.line.width;
- this.line.options.dashes = dashes;
- this.line.options.fillColor = [fillColor, fillColor, fillColor, fillColor];
-
- this.line.update();
- }
- else {
- this.line.clear();
- }
-};
-
-proto.updateError = function(axLetter, options, positions, errors) {
- var errorObj = this['error' + axLetter],
- errorOptions = options['error_' + axLetter.toLowerCase()];
-
- if(axLetter.toLowerCase() === 'x' && errorOptions.copy_ystyle) {
- errorOptions = options.error_y;
- }
-
- if(this['hasError' + axLetter]) {
- errorObj.options.positions = positions;
- errorObj.options.errors = errors;
- errorObj.options.capSize = errorOptions.width;
- errorObj.options.lineWidth = errorOptions.thickness / 2; // ballpark rescaling
- errorObj.options.color = convertColor(errorOptions.color, 1, 1);
-
- errorObj.update();
- }
- else {
- errorObj.clear();
- }
-};
-
-proto.expandAxesFast = function(bounds, markerSize) {
- var pad = markerSize || 10;
- var ax, min, max;
-
- for(var i = 0; i < 2; i++) {
- ax = this.scene[AXES[i]];
-
- min = ax._min;
- if(!min) min = [];
- min.push({ val: bounds[i], pad: pad });
-
- max = ax._max;
- if(!max) max = [];
- max.push({ val: bounds[i + 2], pad: pad });
- }
-};
-
-// not quite on-par with 'scatter' (scatter fill in several other expand options)
-// but close enough for now
-proto.expandAxesFancy = function(x, y, ppad) {
- var scene = this.scene,
- expandOpts = { padded: true, ppad: ppad };
-
- Axes.expand(scene.xaxis, x, expandOpts);
- Axes.expand(scene.yaxis, y, expandOpts);
-};
-
-proto.dispose = function() {
- this.line.dispose();
- this.errorX.dispose();
- this.errorY.dispose();
- this.scatter.dispose();
- this.fancyScatter.dispose();
-};
-
-function createLineWithMarkers(scene, data, cdscatter) {
- var plot = new LineWithMarkers(scene, data.uid);
- plot.update(data, cdscatter);
-
- return plot;
-}
-
-module.exports = createLineWithMarkers;
diff --git a/src/traces/scattergl/defaults.js b/src/traces/scattergl/defaults.js
index b2bbdf5ca98..e3c2e80f730 100644
--- a/src/traces/scattergl/defaults.js
+++ b/src/traces/scattergl/defaults.js
@@ -41,8 +41,11 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce);
}
+ var dfltHoverOn = [];
+
if(subTypes.hasMarkers(traceOut)) {
- handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce, {noSelect: true});
+ handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce);
+ dfltHoverOn.push('points');
}
coerce('fill');
@@ -50,6 +53,14 @@ module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout
handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce);
}
+ if(traceOut.fill === 'tonext' || traceOut.fill === 'toself') {
+ dfltHoverOn.push('fills');
+ }
+
+ coerce('hoveron', dfltHoverOn.join('+') || 'points');
+
errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'});
errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'});
+
+ Lib.coerceSelectionMarkerOpacity(traceOut, coerce);
};
diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js
index 6ad0666d16a..5e09b2bfbcf 100644
--- a/src/traces/scattergl/index.js
+++ b/src/traces/scattergl/index.js
@@ -8,29 +8,1133 @@
'use strict';
-var ScatterGl = {};
+var Lib = require('../../lib');
+var getTraceColor = require('../scatter/get_trace_color');
+var ErrorBars = require('../../components/errorbars');
+var extend = require('object-assign');
+var Axes = require('../../plots/cartesian/axes');
+var kdtree = require('kdgrass');
+var Fx = require('../../components/fx');
+var subTypes = require('../scatter/subtypes');
+var calcColorscales = require('../scatter/colorscale_calc');
+var Drawing = require('../../components/drawing');
+var makeBubbleSizeFn = require('../scatter/make_bubble_size_func');
+var DASHES = require('../../constants/gl2d_dashes');
+var formatColor = require('../../lib/gl_format_color');
+var linkTraces = require('../scatter/link_traces');
+var createScatter = require('regl-scatter2d');
+var createLine = require('regl-line2d');
+var createError = require('regl-error2d');
+var svgSdf = require('svg-path-sdf');
+var createRegl = require('regl');
+var fillHoverText = require('../scatter/fill_hover_text');
+var isNumeric = require('fast-isnumeric');
+var Scatter = require('../scatter');
+var MAXDIST = Fx.constants.MAXDIST;
+var SYMBOL_SDF_SIZE = 200;
+var SYMBOL_SIZE = 20;
+var SYMBOL_STROKE = SYMBOL_SIZE / 20;
+var SYMBOL_SDF = {};
+var SYMBOL_SVG_CIRCLE = Drawing.symbolFuncs[0](SYMBOL_SIZE * 0.05);
+var TOO_MANY_POINTS = 1e5;
+var DOT_RE = /-dot/;
+
+
+var ScatterGl = module.exports = {};
+
+ScatterGl.name = 'scattergl';
+ScatterGl.categories = ['gl', 'regl', 'cartesian', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like'];
ScatterGl.attributes = require('./attributes');
ScatterGl.supplyDefaults = require('./defaults');
-ScatterGl.colorbar = require('../scatter/colorbar');
-ScatterGl.hoverPoints = require('../scatter/hover');
+ScatterGl.cleanData = Scatter.cleanData;
+ScatterGl.arraysToCalcdata = Scatter.arraysToCalcdata;
+ScatterGl.colorbar = Scatter.colorbar;
+ScatterGl.meta = Scatter.meta;
+ScatterGl.animatable = true;
+ScatterGl.hasLines = subTypes.hasLines;
+ScatterGl.hasMarkers = subTypes.hasMarkers;
+ScatterGl.hasText = subTypes.hasText;
+ScatterGl.isBubble = subTypes.isBubble;
+ScatterGl.moduleType = 'trace';
+ScatterGl.basePlotModule = require('../../plots/cartesian');
-// reuse the Scatter3D 'dummy' calc step so that legends know what to do
-ScatterGl.calc = require('./calc');
-ScatterGl.plot = require('./convert');
-ScatterGl.selectPoints = require('./select');
-ScatterGl.moduleType = 'trace';
-ScatterGl.name = 'scattergl';
-ScatterGl.basePlotModule = require('../../plots/gl2d');
-ScatterGl.categories = ['gl', 'gl2d', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like'];
-ScatterGl.meta = {
- description: [
- 'The data visualized as scatter point or lines is set in `x` and `y`',
- 'using the WebGl plotting engine.',
- 'Bubble charts are achieved by setting `marker.size` and/or `marker.color`',
- 'to a numerical arrays.'
- ].join(' ')
+ScatterGl.calc = function calc(container, trace) {
+ var layout = container._fullLayout;
+ var positions;
+ var stash = {};
+ var xaxis = Axes.getFromId(container, trace.xaxis);
+ var yaxis = Axes.getFromId(container, trace.yaxis);
+ var markerOpts = trace.marker;
+
+ // FIXME: is it the best way to obtain subplot object from trace
+ var subplot = layout._plots[trace.xaxis + trace.yaxis];
+ // makeCalcdata runs d2c (data-to-coordinate) on every point
+ var x = xaxis.type === 'linear' ? trace.x : xaxis.makeCalcdata(trace, 'x');
+ var y = yaxis.type === 'linear' ? trace.y : yaxis.makeCalcdata(trace, 'y');
+ var count = (x || y).length, i, l, xx, yy, ptrX = 0, ptrY = 0;
+
+ if(!x) {
+ x = Array(count);
+ for(i = 0; i < count; i++) {
+ x[i] = i;
+ }
+ }
+ if(!y) {
+ y = Array(count);
+ for(i = 0; i < count; i++) {
+ y[i] = i;
+ }
+ }
+
+ var lineOptions, markerOptions, errorXOptions, errorYOptions, fillOptions, selectedOptions, unselectedOptions;
+ var hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill;
+ var linePositions;
+
+ // get log converted positions
+ var rawx, rawy;
+ if(xaxis.type === 'log') {
+ rawx = Array(x.length);
+ for(i = 0, l = x.length; i < l; i++) {
+ rawx[i] = x[i];
+ x[i] = xaxis.d2l(x[i]);
+ }
+ }
+ else {
+ rawx = x;
+ for(i = 0, l = x.length; i < l; i++) {
+ x[i] = parseFloat(x[i]);
+ }
+ }
+ if(yaxis.type === 'log') {
+ rawy = Array(y.length);
+ for(i = 0, l = y.length; i < l; i++) {
+ rawy[i] = y[i];
+ y[i] = yaxis.d2l(y[i]);
+ }
+ }
+ else {
+ rawy = y;
+ for(i = 0, l = y.length; i < l; i++) {
+ y[i] = parseFloat(y[i]);
+ }
+ }
+
+ // we need hi-precision for scatter2d
+ positions = new Array(count * 2);
+
+ for(i = 0; i < count; i++) {
+ // if no x defined, we are creating simple int sequence (API)
+ // we use parseFloat because it gives NaN (we need that for empty values to avoid drawing lines) and it is incredibly fast
+ xx = isNumeric(x[i]) ? +x[i] : NaN;
+ yy = isNumeric(y[i]) ? +y[i] : NaN;
+
+ positions[i * 2] = xx;
+ positions[i * 2 + 1] = yy;
+ }
+
+ calcColorscales(trace);
+
+ // we don't build a tree for log axes since it takes long to convert log2px
+ // and it is also
+ if(xaxis.type !== 'log' && yaxis.type !== 'log') {
+ // FIXME: delegate this to webworker
+ stash.tree = kdtree(positions, 512);
+ }
+ else {
+ var ids = stash.ids = Array(count);
+ for(i = 0; i < count; i++) {
+ ids[i] = i;
+ }
+ }
+
+ if(trace.visible !== true) {
+ hasLines = false;
+ hasErrorX = false;
+ hasErrorY = false;
+ hasMarkers = false;
+ hasFill = false;
+ }
+ else {
+ hasLines = subTypes.hasLines(trace) && positions.length > 2;
+ hasErrorX = trace.error_x.visible === true;
+ hasErrorY = trace.error_y.visible === true;
+ hasError = hasErrorX || hasErrorY;
+ hasMarkers = subTypes.hasMarkers(trace);
+ hasFill = !!trace.fill && trace.fill !== 'none';
+ }
+
+ // get error values
+ var errorVals = hasError ? ErrorBars.calcFromTrace(trace, layout) : null;
+
+ if(hasErrorX) {
+ errorXOptions = {};
+ errorXOptions.positions = positions;
+ var errorsX = new Float64Array(4 * count);
+
+ for(i = 0; i < count; ++i) {
+ errorsX[ptrX++] = rawx[i] - errorVals[i].xs || 0;
+ errorsX[ptrX++] = errorVals[i].xh - rawx[i] || 0;
+ errorsX[ptrX++] = 0;
+ errorsX[ptrX++] = 0;
+ }
+
+ if(trace.error_x.copy_ystyle) {
+ trace.error_x = trace.error_y;
+ }
+
+ errorXOptions.positions = positions;
+ errorXOptions.errors = errorsX;
+ errorXOptions.capSize = trace.error_x.width * 2;
+ errorXOptions.lineWidth = trace.error_x.thickness;
+ errorXOptions.color = trace.error_x.color;
+ }
+
+ if(hasErrorY) {
+ errorYOptions = {};
+ errorYOptions.positions = positions;
+ var errorsY = new Float64Array(4 * count);
+
+ for(i = 0; i < count; ++i) {
+ errorsY[ptrY++] = 0;
+ errorsY[ptrY++] = 0;
+ errorsY[ptrY++] = rawy[i] - errorVals[i].ys || 0;
+ errorsY[ptrY++] = errorVals[i].yh - rawy[i] || 0;
+ }
+
+ errorYOptions.positions = positions;
+ errorYOptions.errors = errorsY;
+ errorYOptions.capSize = trace.error_y.width * 2;
+ errorYOptions.lineWidth = trace.error_y.thickness;
+ errorYOptions.color = trace.error_y.color;
+ }
+
+ if(hasLines) {
+ lineOptions = {};
+ lineOptions.thickness = trace.line.width;
+ lineOptions.color = trace.line.color;
+ lineOptions.opacity = trace.opacity;
+ lineOptions.overlay = true;
+
+ var dashes = (DASHES[trace.line.dash] || [1]).slice();
+ for(i = 0; i < dashes.length; ++i) dashes[i] *= lineOptions.thickness;
+ lineOptions.dashes = dashes;
+
+ if(trace.line.shape === 'hv') {
+ linePositions = [];
+ for(i = 0; i < Math.floor(positions.length / 2) - 1; i++) {
+ if(isNaN(positions[i * 2]) || isNaN(positions[i * 2 + 1])) {
+ linePositions.push(NaN);
+ linePositions.push(NaN);
+ linePositions.push(NaN);
+ linePositions.push(NaN);
+ }
+ else {
+ linePositions.push(positions[i * 2]);
+ linePositions.push(positions[i * 2 + 1]);
+ linePositions.push(positions[i * 2 + 2]);
+ linePositions.push(positions[i * 2 + 1]);
+ }
+ }
+ linePositions.push(positions[positions.length - 2]);
+ linePositions.push(positions[positions.length - 1]);
+ }
+ else if(trace.line.shape === 'vh') {
+ linePositions = [];
+ for(i = 0; i < Math.floor(positions.length / 2) - 1; i++) {
+ if(isNaN(positions[i * 2]) || isNaN(positions[i * 2 + 1])) {
+ linePositions.push(NaN);
+ linePositions.push(NaN);
+ linePositions.push(NaN);
+ linePositions.push(NaN);
+ }
+ else {
+ linePositions.push(positions[i * 2]);
+ linePositions.push(positions[i * 2 + 1]);
+ linePositions.push(positions[i * 2]);
+ linePositions.push(positions[i * 2 + 3]);
+ }
+ }
+ linePositions.push(positions[positions.length - 2]);
+ linePositions.push(positions[positions.length - 1]);
+ }
+ else {
+ linePositions = positions;
+ }
+
+ // If we have data with gaps, we ought to use rect joins
+ // FIXME: get rid of this
+ var hasNaN = false;
+ for(i = 0; i < linePositions.length; i++) {
+ if(isNaN(linePositions[i])) {
+ hasNaN = true;
+ break;
+ }
+ }
+ lineOptions.join = (hasNaN || linePositions.length > TOO_MANY_POINTS) ? 'rect' : hasMarkers ? 'rect' : 'round';
+
+ // fill gaps
+ if(hasNaN && trace.connectgaps) {
+ var lastX = linePositions[0], lastY = linePositions[1];
+ for(i = 0; i < linePositions.length; i += 2) {
+ if(isNaN(linePositions[i]) || isNaN(linePositions[i + 1])) {
+ linePositions[i] = lastX;
+ linePositions[i + 1] = lastY;
+ }
+ else {
+ lastX = linePositions[i];
+ lastY = linePositions[i + 1];
+ }
+ }
+ }
+
+ lineOptions.positions = linePositions;
+ }
+
+ if(hasFill) {
+ fillOptions = {};
+ fillOptions.fill = trace.fillcolor;
+ fillOptions.thickness = 0;
+ fillOptions.closed = true;
+ }
+
+ if(hasMarkers) {
+ markerOptions = makeMarkerOptions(markerOpts);
+ selectedOptions = trace.selected ? makeMarkerOptions(extend({}, markerOpts, trace.selected.marker)) : markerOptions;
+ unselectedOptions = trace.unselected ? makeMarkerOptions(extend({}, markerOpts, trace.unselected.marker)) : markerOptions;
+
+ markerOptions.positions = positions;
+ }
+ // expand no-markers axes
+ else {
+ Axes.expand(xaxis, rawx, { padded: true });
+ Axes.expand(yaxis, rawy, { padded: true });
+ }
+
+ function makeMarkerOptions(markerOpts) {
+ var markerOptions = {};
+
+ // get basic symbol info
+ var multiMarker = Array.isArray(markerOpts.symbol);
+ var isOpen, symbol;
+ if(!multiMarker) {
+ isOpen = /-open/.test(markerOpts.symbol);
+ }
+ // prepare colors
+ if(multiMarker || Array.isArray(markerOpts.color) || Array.isArray(markerOpts.line.color) || Array.isArray(markerOpts.line) || Array.isArray(markerOpts.opacity)) {
+ markerOptions.colors = new Array(count);
+ markerOptions.borderColors = new Array(count);
+ var colors = formatColor(markerOpts, markerOpts.opacity, count);
+ var borderColors = formatColor(markerOpts.line, markerOpts.opacity, count);
+
+ if(!Array.isArray(borderColors[0])) {
+ var borderColor = borderColors;
+ borderColors = Array(count);
+ for(i = 0; i < count; i++) {
+ borderColors[i] = borderColor;
+ }
+ }
+ if(!Array.isArray(colors[0])) {
+ var color = colors;
+ colors = Array(count);
+ for(i = 0; i < count; i++) {
+ colors[i] = color;
+ }
+ }
+
+ markerOptions.colors = colors;
+ markerOptions.borderColors = borderColors;
+
+ for(i = 0; i < count; i++) {
+ if(multiMarker) {
+ symbol = markerOpts.symbol[i];
+ isOpen = /-open/.test(symbol);
+ }
+ if(isOpen) {
+ borderColors[i] = colors[i].slice();
+ colors[i] = colors[i].slice();
+ colors[i][3] = 0;
+ }
+ }
+
+ markerOptions.opacity = trace.opacity;
+ }
+ else {
+ markerOptions.color = markerOpts.color;
+ markerOptions.borderColor = markerOpts.line.color;
+ markerOptions.opacity = trace.opacity * markerOpts.opacity;
+
+ if(isOpen) {
+ markerOptions.borderColor = markerOptions.color.slice();
+ markerOptions.color = markerOptions.color.slice();
+ markerOptions.color[3] = 0;
+ }
+ }
+
+ // prepare markers
+ if(Array.isArray(markerOpts.symbol)) {
+ markerOptions.markers = new Array(count);
+ for(i = 0; i < count; ++i) {
+ markerOptions.markers[i] = getSymbolSdf(markerOpts.symbol[i]);
+ }
+ }
+ else {
+ markerOptions.marker = getSymbolSdf(markerOpts.symbol);
+ }
+
+ // prepare sizes and expand axes
+ var multiSize = markerOpts && (Array.isArray(markerOpts.size) || Array.isArray(markerOpts.line.width));
+ var xbounds = [Infinity, -Infinity], ybounds = [Infinity, -Infinity];
+ var markerSizeFunc = makeBubbleSizeFn(trace);
+ var size, sizes;
+
+ if(multiSize) {
+ sizes = markerOptions.sizes = new Array(count);
+ var borderSizes = markerOptions.borderSizes = new Array(count);
+
+ if(Array.isArray(markerOpts.size)) {
+ for(i = 0; i < count; ++i) {
+ sizes[i] = markerSizeFunc(markerOpts.size[i]);
+ }
+ }
+ else {
+ size = markerSizeFunc(markerOpts.size);
+ for(i = 0; i < count; ++i) {
+ sizes[i] = size;
+ }
+ }
+
+ // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798
+ if(Array.isArray(markerOpts.line.width)) {
+ for(i = 0; i < count; ++i) {
+ borderSizes[i] = markerOpts.line.width[i] * 0.5;
+ }
+ }
+ else {
+ size = markerSizeFunc(markerOpts.line.width) * 0.5;
+ for(i = 0; i < count; ++i) {
+ borderSizes[i] = size;
+ }
+ }
+
+ Axes.expand(xaxis, rawx, { padded: true, ppad: sizes });
+ Axes.expand(yaxis, rawy, { padded: true, ppad: sizes });
+ }
+ else {
+ size = markerOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10);
+ markerOptions.borderSizes = markerOpts.line.width * 0.5;
+
+ // axes bounds
+ for(i = 0; i < count; i++) {
+ xx = x[i], yy = y[i];
+ if(xbounds[0] > xx) xbounds[0] = xx;
+ if(xbounds[1] < xx) xbounds[1] = xx;
+ if(ybounds[0] > yy) ybounds[0] = yy;
+ if(ybounds[1] < yy) ybounds[1] = yy;
+ }
+
+ // FIXME: is there a better way to separate expansion?
+ if(count < TOO_MANY_POINTS) {
+ Axes.expand(xaxis, rawx, { padded: true, ppad: size });
+ Axes.expand(yaxis, rawy, { padded: true, ppad: size });
+ }
+ // update axes fast for big number of points
+ else {
+ var pad = markerOptions.size;
+ if(xaxis._min) {
+ xaxis._min.push({ val: xbounds[0], pad: pad });
+ }
+ if(xaxis._max) {
+ xaxis._max.push({ val: xbounds[1], pad: pad });
+ }
+
+ if(yaxis._min) {
+ yaxis._min.push({ val: ybounds[0], pad: pad });
+ }
+ if(yaxis._max) {
+ yaxis._max.push({ val: ybounds[1], pad: pad });
+ }
+ }
+ }
+
+ return markerOptions;
+ }
+
+
+ // make sure scene exists
+ var scene = subplot._scene;
+
+ if(!subplot._scene) {
+ scene = subplot._scene = {
+ // number of traces in subplot, since scene:subplot → 1:1
+ count: 0,
+
+ // whether scene requires init hook in plot call (dirty plot call)
+ dirty: true,
+
+ // last used options
+ lineOptions: [],
+ fillOptions: [],
+ markerOptions: [],
+ selectedOptions: [],
+ unselectedOptions: [],
+ errorXOptions: [],
+ errorYOptions: [],
+ selectBatch: null,
+ unselectBatch: null,
+
+ // regl- component stubs, initialized in dirty plot call
+ fill2d: hasFill,
+ scatter2d: hasMarkers,
+ error2d: hasError,
+ line2d: hasLines,
+ select2d: null
+ };
+
+ // apply new option to all regl components
+ scene.update = function update(opt) {
+ var opts = Array(scene.count);
+ for(var i = 0; i < scene.count; i++) {
+ opts[i] = opt;
+ }
+ if(scene.fill2d) scene.fill2d.update(opts);
+ if(scene.scatter2d) scene.scatter2d.update(opts);
+ if(scene.line2d) scene.line2d.update(opts);
+ if(scene.error2d) scene.error2d.update([].push.apply(opts, opts));
+ if(scene.select2d) scene.select2d.update(opts);
+
+ scene.draw();
+ };
+
+ // draw traces in proper order
+ scene.draw = function draw() {
+ for(var i = 0; i < scene.count; i++) {
+ if(scene.fill2d) scene.fill2d.draw(i);
+ if(scene.line2d) {
+ scene.line2d.draw(i);
+ }
+ if(scene.error2d) {
+ scene.error2d.draw(i);
+ scene.error2d.draw(i + scene.count);
+ }
+ if(scene.scatter2d && !scene.selectBatch) {
+ scene.scatter2d.draw(i);
+ }
+ }
+
+ // persistent selection draw
+ if(scene.select2d && scene.selectBatch) {
+ scene.select2d.draw(scene.selectBatch);
+ scene.scatter2d.draw(scene.unselectBatch);
+ }
+
+ scene.dirty = false;
+ };
+
+ // make sure canvas is clear
+ scene.clear = function clear() {
+ var vpSize = layout._size, width = layout.width, height = layout.height;
+ var vp = [
+ vpSize.l + xaxis.domain[0] * vpSize.w,
+ vpSize.b + yaxis.domain[0] * vpSize.h,
+ (width - vpSize.r) - (1 - xaxis.domain[1]) * vpSize.w,
+ (height - vpSize.t) - (1 - yaxis.domain[1]) * vpSize.h
+ ];
+
+ var gl, regl;
+
+ regl = scene.select2d.regl;
+ gl = regl._gl;
+ gl.enable(gl.SCISSOR_TEST);
+ gl.scissor(vp[0], vp[1], vp[2] - vp[0], vp[3] - vp[1]);
+ gl.clearColor(0, 0, 0, 0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+
+ regl = scene.scatter2d.regl;
+ gl = regl._gl;
+ gl.enable(gl.SCISSOR_TEST);
+ gl.scissor(vp[0], vp[1], vp[2] - vp[0], vp[3] - vp[1]);
+ gl.clearColor(0, 0, 0, 0);
+ gl.clear(gl.COLOR_BUFFER_BIT);
+ };
+
+ // remove selection
+ scene.clearSelect = function clearSelect() {
+ if(!scene.selectBatch) return;
+ scene.selectBatch = null;
+ scene.unselectBatch = null;
+ scene.scatter2d.update(scene.markerOptions);
+ scene.clear();
+ scene.draw();
+ };
+
+ // remove scene resources
+ scene.destroy = function destroy() {
+ if(scene.fill2d) scene.fill2d.destroy();
+ if(scene.scatter2d) scene.scatter2d.destroy();
+ if(scene.error2d) scene.error2d.destroy();
+ if(scene.line2d) scene.line2d.destroy();
+ if(scene.select2d) scene.select2d.destroy();
+
+ scene.lineOptions = null;
+ scene.fillOptions = null;
+ scene.markerOptions = null;
+ scene.selectedOptions = null;
+ scene.unselectedOptions = null;
+ scene.errorXOptions = null;
+ scene.errorYOptions = null;
+ scene.selectBatch = null;
+ scene.unselectBatch = null;
+
+ delete subplot._scene;
+ };
+ }
+ else {
+ if(hasFill && !scene.fill2d) scene.fill2d = true;
+ if(hasMarkers && !scene.scatter2d) scene.scatter2d = true;
+ if(hasLines && !scene.line2d) scene.line2d = true;
+ if(hasError && !scene.error2d) scene.error2d = true;
+ }
+
+ // In case if we have scene from the last calc - reset data
+ if(!scene.dirty) {
+ scene.dirty = true;
+ scene.count = 0;
+ scene.lineOptions = [];
+ scene.fillOptions = [];
+ scene.markerOptions = [];
+ scene.selectedOptions = [];
+ scene.unselectedOptions = [];
+ scene.errorXOptions = [];
+ scene.errorYOptions = [];
+ }
+
+ // save initial batch
+ scene.lineOptions.push(hasLines ? lineOptions : null);
+ scene.errorXOptions.push(hasErrorX ? errorXOptions : null);
+ scene.errorYOptions.push(hasErrorY ? errorYOptions : null);
+ scene.fillOptions.push(hasFill ? fillOptions : null);
+ scene.markerOptions.push(hasMarkers ? markerOptions : null);
+ scene.selectedOptions.push(hasMarkers ? selectedOptions : null);
+ scene.unselectedOptions.push(hasMarkers ? unselectedOptions : null);
+ scene.count++;
+
+ // stash scene ref
+ stash.scene = scene;
+ stash.index = scene.count - 1;
+ stash.x = x;
+ stash.y = y;
+ stash.rawx = rawx;
+ stash.rawy = rawy;
+ stash.positions = positions;
+ stash.count = count;
+
+ return [{x: false, y: false, t: stash, trace: trace}];
};
-module.exports = ScatterGl;
+
+function getSymbolSdf(symbol) {
+ if(symbol === 'circle') return null;
+
+ var symbolPath, symbolSdf;
+ var symbolNumber = Drawing.symbolNumber(symbol);
+ var symbolFunc = Drawing.symbolFuncs[symbolNumber % 100];
+ var symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100];
+ var symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100];
+
+ var isDot = DOT_RE.test(symbol);
+
+ // get symbol sdf from cache or generate it
+ if(SYMBOL_SDF[symbol]) return SYMBOL_SDF[symbol];
+
+ if(isDot && !symbolNoDot) {
+ symbolPath = symbolFunc(SYMBOL_SIZE * 1.1) + SYMBOL_SVG_CIRCLE;
+ }
+ else {
+ symbolPath = symbolFunc(SYMBOL_SIZE);
+ }
+
+ symbolSdf = svgSdf(symbolPath, {
+ w: SYMBOL_SDF_SIZE,
+ h: SYMBOL_SDF_SIZE,
+ viewBox: [-SYMBOL_SIZE, -SYMBOL_SIZE, SYMBOL_SIZE, SYMBOL_SIZE],
+ stroke: symbolNoFill ? SYMBOL_STROKE : -SYMBOL_STROKE
+ });
+ SYMBOL_SDF[symbol] = symbolSdf;
+
+ return symbolSdf || null;
+}
+
+
+ScatterGl.plot = function plot(container, subplot, cdata) {
+ var layout = container._fullLayout;
+ var scene = subplot._scene;
+
+ // we may have more subplots than initialized data due to Axes.getSubplots method
+ if(!scene) return;
+
+ var vpSize = layout._size, width = layout.width, height = layout.height;
+
+ // make sure proper regl instances are created
+ layout._glcanvas.each(function(d) {
+ if(d.regl || d.pick) return;
+ d.regl = createRegl({
+ canvas: this,
+ attributes: {
+ antialias: !d.pick,
+ preserveDrawingBuffer: true
+ },
+ extensions: ['ANGLE_instanced_arrays', 'OES_element_index_uint'],
+ pixelRatio: container._context.plotGlPixelRatio || global.devicePixelRatio
+ });
+ });
+
+ var regl = layout._glcanvas.data()[0].regl;
+
+ // that is needed for fills
+ linkTraces(container, subplot, cdata);
+
+ if(scene.dirty) {
+ // make sure scenes are created
+ if(scene.error2d === true) {
+ scene.error2d = createError(regl);
+ }
+ if(scene.line2d === true) {
+ scene.line2d = createLine(regl);
+ }
+ if(scene.scatter2d === true) {
+ scene.scatter2d = createScatter(regl);
+ }
+ if(scene.fill2d === true) {
+ scene.fill2d = createLine(regl);
+ }
+
+ if(scene.line2d) {
+ scene.line2d.update(scene.lineOptions);
+ }
+ if(scene.error2d) {
+ var errorBatch = (scene.errorXOptions || []).concat(scene.errorYOptions || []);
+ scene.error2d.update(errorBatch);
+ }
+ if(scene.scatter2d) {
+ if(!scene.selectBatch) {
+ scene.scatter2d.update(scene.markerOptions);
+ }
+ else {
+ scene.scatter2d.update(scene.unselectedOptions);
+ scene.select2d.update(scene.selectedOptions);
+ }
+ }
+ // fill requires linked traces, so we generate it's positions here
+ if(scene.fill2d) {
+ scene.fillOptions.forEach(function(fillOptions, i) {
+ var cdscatter = cdata[i];
+ if(!cdscatter || !cdscatter[0] || !cdscatter[0].trace) return;
+ var cd = cdscatter[0];
+ var trace = cd.trace;
+ var stash = cd.t;
+ var lineOptions = scene.lineOptions[i];
+ var last, j;
+
+ var pos = [], srcPos = (lineOptions && lineOptions.positions) || stash.positions;
+
+ if(trace.fill === 'tozeroy') {
+ pos = [srcPos[0], 0];
+ pos = pos.concat(srcPos);
+ pos.push(srcPos[srcPos.length - 2]);
+ pos.push(0);
+ }
+ else if(trace.fill === 'tozerox') {
+ pos = [0, srcPos[1]];
+ pos = pos.concat(srcPos);
+ pos.push(0);
+ pos.push(srcPos[srcPos.length - 1]);
+ }
+ else if(trace.fill === 'toself' || trace.fill === 'tonext') {
+ pos = [];
+ last = 0;
+ for(j = 0; j < srcPos.length; j += 2) {
+ if(isNaN(srcPos[j]) || isNaN(srcPos[j + 1])) {
+ pos = pos.concat(srcPos.slice(last, j));
+ pos.push(srcPos[last], srcPos[last + 1]);
+ last = j + 2;
+ }
+ }
+ pos = pos.concat(srcPos.slice(last));
+ if(last) {
+ pos.push(srcPos[last], srcPos[last + 1]);
+ }
+ }
+ else {
+ var nextTrace = trace._nexttrace;
+
+ if(nextTrace) {
+ var nextOptions = scene.lineOptions[i + 1];
+
+ if(nextOptions) {
+ var nextPos = nextOptions.positions;
+ if(trace.fill === 'tonexty') {
+ pos = srcPos.slice();
+
+ for(i = Math.floor(nextPos.length / 2); i--;) {
+ var xx = nextPos[i * 2], yy = nextPos[i * 2 + 1];
+ if(isNaN(xx) || isNaN(yy)) continue;
+ pos.push(xx);
+ pos.push(yy);
+ }
+ fillOptions.fill = nextTrace.fillcolor;
+ }
+ }
+ }
+ }
+
+ // detect prev trace positions to exclude from current fill
+ if(trace._prevtrace && trace._prevtrace.fill === 'tonext') {
+ var prevLinePos = scene.lineOptions[i - 1].positions;
+
+ // FIXME: likely this logic should be tested better
+ var offset = pos.length / 2;
+ last = offset;
+ var hole = [last];
+ for(j = 0; j < prevLinePos.length; j += 2) {
+ if(isNaN(prevLinePos[j]) || isNaN(prevLinePos[j + 1])) {
+ hole.push(j / 2 + offset + 1);
+ last = j + 2;
+ }
+ }
+
+ pos = pos.concat(prevLinePos);
+ fillOptions.hole = hole;
+ }
+
+ fillOptions.opacity = trace.opacity;
+ fillOptions.positions = pos;
+ });
+
+ scene.fill2d.update(scene.fillOptions);
+ }
+ }
+
+ // make sure selection layer is initialized if we require selection
+ var dragmode = layout.dragmode;
+
+ if(dragmode === 'lasso' || dragmode === 'select') {
+ if(scene.select2d && scene.selectBatch) {
+ scene.scatter2d.update(scene.unselectedOptions);
+ }
+ }
+
+ // provide viewport and range
+ var vpRange = cdata.map(function(cdscatter) {
+ if(!cdscatter || !cdscatter[0] || !cdscatter[0].trace) return;
+ var cd = cdscatter[0];
+ var trace = cd.trace;
+ var stash = cd.t;
+ var x = stash.rawx,
+ y = stash.rawy;
+ var xaxis = Axes.getFromId(container, trace.xaxis || 'x');
+ var yaxis = Axes.getFromId(container, trace.yaxis || 'y');
+ var i;
+
+ var range = [
+ xaxis._rl[0],
+ yaxis._rl[0],
+ xaxis._rl[1],
+ yaxis._rl[1]
+ ];
+
+ var viewport = [
+ vpSize.l + xaxis.domain[0] * vpSize.w,
+ vpSize.b + yaxis.domain[0] * vpSize.h,
+ (width - vpSize.r) - (1 - xaxis.domain[1]) * vpSize.w,
+ (height - vpSize.t) - (1 - yaxis.domain[1]) * vpSize.h
+ ];
+
+ if(trace.selectedpoints || dragmode === 'lasso' || dragmode === 'select') {
+ // create select2d
+ if(!scene.select2d && scene.scatter2d) {
+ var selectRegl = layout._glcanvas.data()[1].regl;
+
+ // smol hack to create scatter instance by cloning scatter2d
+ scene.select2d = createScatter(selectRegl, {clone: scene.scatter2d});
+ scene.select2d.update(scene.selectedOptions);
+
+ // create selection style once we have something selected
+ if(trace.selectedpoints && !scene.selectBatch) {
+ scene.selectBatch = Array(scene.count);
+ scene.unselectBatch = Array(scene.count);
+ scene.scatter2d.update(scene.unselectedOptions);
+ }
+ }
+
+ // form unselected batch
+ if(trace.selectedpoints && !scene.unselectBatch[stash.index]) {
+ scene.selectBatch[stash.index] = trace.selectedpoints;
+ var selPts = trace.selectedpoints;
+ var selDict = {};
+ for(i = 0; i < selPts.length; i++) {
+ selDict[selPts[i]] = true;
+ }
+ var unselPts = [];
+ for(i = 0; i < stash.count; i++) {
+ if(!selDict[i]) unselPts.push(i);
+ }
+ scene.unselectBatch[stash.index] = unselPts;
+ }
+
+ // precalculate px coords since we are not going to pan during select
+ var xpx = Array(stash.count), ypx = Array(stash.count);
+ for(i = 0; i < stash.count; i++) {
+ xpx[i] = xaxis.c2p(x[i]);
+ ypx[i] = yaxis.c2p(y[i]);
+ }
+ stash.xpx = xpx;
+ stash.ypx = ypx;
+ }
+ else {
+ stash.xpx = stash.ypx = null;
+ }
+
+ return trace.visible ? {
+ viewport: viewport,
+ range: range
+ } : null;
+ });
+
+ // uploat batch data to GPU
+ if(scene.fill2d) {
+ scene.fill2d.update(vpRange);
+ }
+ if(scene.line2d) {
+ scene.line2d.update(vpRange);
+ }
+ if(scene.error2d) {
+ scene.error2d.update(vpRange.concat(vpRange));
+ }
+ if(scene.scatter2d) {
+ scene.scatter2d.update(vpRange);
+ }
+ if(scene.select2d) {
+ scene.select2d.update(vpRange);
+ }
+
+ scene.draw();
+
+ return;
+};
+
+
+ScatterGl.hoverPoints = function hover(pointData, xval, yval, hovermode) {
+ var cd = pointData.cd,
+ stash = cd[0].t,
+ trace = cd[0].trace,
+ xa = pointData.xa,
+ ya = pointData.ya,
+ x = stash.rawx,
+ y = stash.rawy,
+ xpx = xa.c2p(xval),
+ ypx = ya.c2p(yval),
+ ids;
+
+ // FIXME: make sure this is a proper way to calc search radius
+ if(stash.tree) {
+ if(hovermode === 'x') {
+ ids = stash.tree.range(
+ xa.p2c(xpx - MAXDIST), ya._rl[0],
+ xa.p2c(xpx + MAXDIST), ya._rl[1]
+ );
+ }
+ else {
+ ids = stash.tree.range(
+ xa.p2c(xpx - MAXDIST), ya.p2c(ypx + MAXDIST),
+ xa.p2c(xpx + MAXDIST), ya.p2c(ypx - MAXDIST)
+ );
+ }
+ }
+ else if(stash.ids) {
+ ids = stash.ids;
+ }
+ else return [pointData];
+
+ // pick the id closest to the point
+ // note that point possibly may not be found
+ var min = MAXDIST, id, ptx, pty, i, dx, dy, dist;
+
+ if(hovermode === 'x') {
+ for(i = 0; i < ids.length; i++) {
+ ptx = x[ids[i]];
+ dx = Math.abs(xa.c2p(ptx) - xpx);
+ if(dx < min) {
+ min = dx;
+ id = ids[i];
+ }
+ }
+ }
+ else {
+ for(i = 0; i < ids.length; i++) {
+ ptx = x[ids[i]];
+ pty = y[ids[i]];
+ dx = xa.c2p(ptx) - xpx, dy = ya.c2p(pty) - ypx;
+
+ dist = Math.sqrt(dx * dx + dy * dy);
+ if(dist < min) {
+ min = dist;
+ id = ids[i];
+ }
+ }
+ }
+
+ pointData.index = id;
+
+ if(id === undefined) return [pointData];
+
+ // the closest data point
+ var di = {
+ pointNumber: id,
+ x: x[id],
+ y: y[id]
+ };
+
+ // that is single-item arrays_to_calcdata excerpt, since we are doing it for a single point and we don't have to do it beforehead for 1e6 points
+ di.tx = Array.isArray(trace.text) ? trace.text[id] : trace.text;
+ di.htx = Array.isArray(trace.hovertext) ? trace.hovertext[id] : trace.hovertext;
+ di.data = Array.isArray(trace.customdata) ? trace.customdata[id] : trace.customdata;
+ di.tp = Array.isArray(trace.textposition) ? trace.textposition[id] : trace.textposition;
+
+ var font = trace.textfont;
+ if(font) {
+ di.ts = Array.isArray(font.size) ? font.size[id] : font.size;
+ di.tc = Array.isArray(font.color) ? font.color[id] : font.color;
+ di.tf = Array.isArray(font.family) ? font.family[id] : font.family;
+ }
+
+ var marker = trace.marker;
+ if(marker) {
+ di.ms = Array.isArray(marker.size) ? marker.size[id] : marker.size;
+ di.mo = Array.isArray(marker.opacity) ? marker.opacity[id] : marker.opacity;
+ di.mx = Array.isArray(marker.symbol) ? marker.symbol[id] : marker.symbol;
+ di.mc = Array.isArray(marker.color) ? marker.color[id] : marker.color;
+ }
+
+ var line = marker && marker.line;
+ if(line) {
+ di.mlc = Array.isArray(line.color) ? line.color[id] : line.color;
+ di.mlw = Array.isArray(line.width) ? line.width[id] : line.width;
+ }
+
+ var grad = marker && marker.gradient;
+ if(grad && grad.type !== 'none') {
+ di.mgt = Array.isArray(grad.type) ? grad.type[id] : grad.type;
+ di.mgc = Array.isArray(grad.color) ? grad.color[id] : grad.color;
+ }
+
+ var xc = xa.c2p(di.x, true),
+ yc = ya.c2p(di.y, true),
+ rad = di.mrc || 1;
+
+ var hoverlabel = trace.hoverlabel;
+
+ if(hoverlabel) {
+ di.hbg = Array.isArray(hoverlabel.bgcolor) ? hoverlabel.bgcolor[id] : hoverlabel.bgcolor;
+ di.hbc = Array.isArray(hoverlabel.bordercolor) ? hoverlabel.bordercolor[id] : hoverlabel.bordercolor;
+ di.hts = Array.isArray(hoverlabel.font.size) ? hoverlabel.font.size[id] : hoverlabel.font.size;
+ di.htc = Array.isArray(hoverlabel.font.color) ? hoverlabel.font.color[id] : hoverlabel.font.color;
+ di.htf = Array.isArray(hoverlabel.font.family) ? hoverlabel.font.family[id] : hoverlabel.font.family;
+ di.hnl = Array.isArray(hoverlabel.namelength) ? hoverlabel.namelength[id] : hoverlabel.namelength;
+ }
+ var hoverinfo = trace.hoverinfo;
+ if(hoverinfo) {
+ di.hi = Array.isArray(hoverinfo) ? hoverinfo[id] : hoverinfo;
+ }
+
+ var fakeCd = {};
+ fakeCd[pointData.index] = di;
+
+ Lib.extendFlat(pointData, {
+ color: getTraceColor(trace, di),
+
+ x0: xc - rad,
+ x1: xc + rad,
+ xLabelVal: di.x,
+
+ y0: yc - rad,
+ y1: yc + rad,
+ yLabelVal: di.y,
+
+ cd: fakeCd
+ });
+
+ if(di.htx) pointData.text = di.htx;
+ else if(di.tx) pointData.text = di.tx;
+ else if(trace.text) pointData.text = trace.text;
+
+ fillHoverText(di, trace, pointData);
+ ErrorBars.hoverInfo(di, trace, pointData);
+
+ return [pointData];
+};
+
+
+ScatterGl.selectPoints = function select(searchInfo, polygon) {
+ var cd = searchInfo.cd,
+ selection = [],
+ trace = cd[0].trace,
+ stash = cd[0].t,
+ x = stash.x,
+ y = stash.y;
+
+ var scene = stash.scene;
+
+ if(!scene) return selection;
+
+ var hasOnlyLines = (!subTypes.hasMarkers(trace) && !subTypes.hasText(trace));
+ if(trace.visible !== true || hasOnlyLines) return selection;
+
+ // degenerate polygon does not enable selection
+ // filter out points by visible scatter ones
+ var els = null, unels = null, i;
+ if(polygon !== false && !polygon.degenerate) {
+ els = [], unels = [];
+ for(i = 0; i < stash.count; i++) {
+ if(polygon.contains([stash.xpx[i], stash.ypx[i]])) {
+ els.push(i);
+ selection.push({
+ pointNumber: i,
+ x: x[i],
+ y: y[i]
+ });
+ }
+ else {
+ unels.push(i);
+ }
+ }
+ }
+ else {
+ unels = Array(stash.count);
+ for(i = 0; i < stash.count; i++) {
+ unels[i] = i;
+ }
+ }
+
+ // create selection style once we have something selected
+ if(!scene.selectBatch) {
+ scene.selectBatch = Array(scene.count);
+ scene.unselectBatch = Array(scene.count);
+ scene.scatter2d.update(scene.unselectedOptions);
+ }
+ scene.selectBatch[stash.index] = els;
+ scene.unselectBatch[stash.index] = unels;
+
+ return selection;
+};
+
+
+ScatterGl.style = function style(gd, cd) {
+ if(cd) {
+ var stash = cd[0].t;
+ var scene = stash.scene;
+ scene.clear();
+ scene.draw();
+ }
+};
diff --git a/src/traces/scattergl/select.js b/src/traces/scattergl/select.js
deleted file mode 100644
index e35b7ac4325..00000000000
--- a/src/traces/scattergl/select.js
+++ /dev/null
@@ -1,60 +0,0 @@
-/**
-* Copyright 2012-2017, 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 subtypes = require('../scatter/subtypes');
-
-module.exports = function selectPoints(searchInfo, polygon) {
- var cd = searchInfo.cd,
- xa = searchInfo.xaxis,
- ya = searchInfo.yaxis,
- selection = [],
- trace = cd[0].trace,
- i,
- di,
- x,
- y;
-
- var glTrace = cd[0]._glTrace;
- var scene = glTrace.scene;
-
- var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace));
- if(hasOnlyLines) return [];
-
- // filter out points by visible scatter ones
- if(polygon === false) {
- // clear selection
- for(i = 0; i < cd.length; i++) cd[i].dim = 0;
- }
- else {
- for(i = 0; i < cd.length; i++) {
- di = cd[i];
- x = xa.c2p(di.x);
- y = ya.c2p(di.y);
- if(polygon.contains([x, y])) {
- selection.push({
- pointNumber: i,
- x: xa.c2d(di.x),
- y: ya.c2d(di.y)
- });
- di.dim = 0;
- }
- else di.dim = 1;
- }
- }
-
- // highlight selected points here
- trace.selection = selection;
-
- glTrace.update(trace, cd);
- scene.glplot.setDirty();
-
- return selection;
-};
diff --git a/tasks/bundle.js b/tasks/bundle.js
index b1a175d4654..dd767f018ff 100644
--- a/tasks/bundle.js
+++ b/tasks/bundle.js
@@ -33,6 +33,7 @@ _bundle(constants.pathToPlotlyIndex, constants.pathToPlotlyDist, {
pathToMinBundle: constants.pathToPlotlyDistMin
});
+
// Browserify the geo assets
_bundle(constants.pathToPlotlyGeoAssetsSrc, constants.pathToPlotlyGeoAssetsDist, {
standalone: 'PlotlyGeoAssets'
diff --git a/tasks/stats.js b/tasks/stats.js
index 6478db4a47a..63227f1f5b4 100644
--- a/tasks/stats.js
+++ b/tasks/stats.js
@@ -1,6 +1,6 @@
var path = require('path');
var fs = require('fs');
-var spawn = require('child_process').spawn;
+var spawn = require('cross-spawn');
var falafel = require('falafel');
var gzipSize = require('gzip-size');
diff --git a/tasks/util/browserify_wrapper.js b/tasks/util/browserify_wrapper.js
index fa57092764e..65a8de6c07b 100644
--- a/tasks/util/browserify_wrapper.js
+++ b/tasks/util/browserify_wrapper.js
@@ -2,11 +2,10 @@ var fs = require('fs');
var path = require('path');
var browserify = require('browserify');
-var UglifyJS = require('uglify-js');
+var minify = require('minify-stream');
var constants = require('./constants');
var compressAttributes = require('./compress_attributes');
-var patchMinified = require('./patch_minified');
var strictD3 = require('./strict_d3');
/** Convenience browserify wrapper
@@ -46,30 +45,28 @@ module.exports = function _bundle(pathToIndex, pathToBundle, opts) {
}
var b = browserify(pathToIndex, browserifyOpts);
- var bundleWriteStream = fs.createWriteStream(pathToBundle);
- bundleWriteStream.on('finish', function() {
- logger(pathToBundle);
- if(opts.then) {
- opts.then();
- }
- });
-
- b.bundle(function(err, buf) {
+ var bundleStream = b.bundle(function(err) {
if(err) throw err;
+ });
- if(outputMinified) {
- var minifiedCode = UglifyJS.minify(buf.toString(), constants.uglifyOptions).code;
- minifiedCode = patchMinified(minifiedCode);
-
- fs.writeFile(pathToMinBundle, minifiedCode, function(err) {
- if(err) throw err;
-
+ if(outputMinified) {
+ bundleStream
+ .pipe(minify(constants.uglifyOptions))
+ .pipe(fs.createWriteStream(pathToMinBundle))
+ .on('finish', function() {
logger(pathToMinBundle);
});
- }
- })
- .pipe(bundleWriteStream);
+ }
+
+ bundleStream
+ .pipe(fs.createWriteStream(pathToBundle))
+ .on('finish', function() {
+ logger(pathToBundle);
+ if(opts.then) {
+ opts.then();
+ }
+ });
};
function logger(pathToOutput) {
diff --git a/tasks/util/constants.js b/tasks/util/constants.js
index 1815c740616..46f0ba357d4 100644
--- a/tasks/util/constants.js
+++ b/tasks/util/constants.js
@@ -84,16 +84,15 @@ module.exports = {
testContainerHome: '/var/www/streambed/image_server/plotly.js',
uglifyOptions: {
- fromString: true,
mangle: true,
compress: {
- warnings: false,
- screw_ie8: true
+ warnings: false
},
output: {
beautify: false,
ascii_only: true
- }
+ },
+ sourceMap: false
},
licenseDist: [
diff --git a/tasks/util/patch_minified.js b/tasks/util/patch_minified.js
deleted file mode 100644
index e1388c71fa4..00000000000
--- a/tasks/util/patch_minified.js
+++ /dev/null
@@ -1,22 +0,0 @@
-var PATTERN = /require\("\+(\w)\((\w)\)\+"\)/;
-var NEW_SUBSTR = 'require("+ $1($2) +")';
-
-/* Uber hacky in-house fix to
- *
- * https://github.com/substack/webworkify/issues/29
- *
- * so that plotly.min.js loads in Jupyter NBs, more info here:
- *
- * - https://github.com/plotly/plotly.py/pull/545
- * - https://github.com/plotly/plotly.js/pull/914
- * - https://github.com/plotly/plotly.js/pull/1094
- *
- * For example, this routine replaces
- * 'require("+o(s)+")' -> 'require("+ o(s) +")'
- *
- * But works for any 1-letter variable that uglify-js may output.
- *
- */
-module.exports = function patchMinified(minifiedCode) {
- return minifiedCode.replace(PATTERN, NEW_SUBSTR);
-};
diff --git a/test/image/baselines/gl2d_parcoords_2.png b/test/image/baselines/gl2d_parcoords_2.png
index a31ada48935..91d01c98f60 100644
Binary files a/test/image/baselines/gl2d_parcoords_2.png and b/test/image/baselines/gl2d_parcoords_2.png differ
diff --git a/test/image/mocks/gl2d_12.json b/test/image/mocks/gl2d_12.json
index 01d477ea386..1dd1a744dad 100644
--- a/test/image/mocks/gl2d_12.json
+++ b/test/image/mocks/gl2d_12.json
@@ -529,6 +529,7 @@
15389.924680000002,
20509.64777,
10808.47561,
+ 9101.25,
9786.534714,
18678.31435,
25768.25759,
@@ -561,6 +562,7 @@
75.563,
78.098,
72.476,
+ 67.59,
74.002,
74.663,
77.926,
@@ -595,6 +597,7 @@
"Country: Poland
Life Expectancy: 75.563
GDP per capita: 15389.92468
Population: 38518241.0
Year: 2007",
"Country: Portugal
Life Expectancy: 78.098
GDP per capita: 20509.64777
Population: 10642836.0
Year: 2007",
"Country: Romania
Life Expectancy: 72.476
GDP per capita: 10808.47561
Population: 22276056.0
Year: 2007",
+ "Country: Russia
Life Expectancy: 67.59
GDP per capita: 9101.25
Population: 142800000.0
Year: 2007",
"Country: Serbia
Life Expectancy: 74.002
GDP per capita: 9786.534714
Population: 10150265.0
Year: 2007",
"Country: Slovak Republic
Life Expectancy: 74.663
GDP per capita: 18678.31435
Population: 5447502.0
Year: 2007",
"Country: Slovenia
Life Expectancy: 77.926
GDP per capita: 25768.25759
Population: 2009245.0
Year: 2007",
diff --git a/test/image/mocks/gl2d_ultra_zoom.json b/test/image/mocks/gl2d_ultra_zoom.json
new file mode 100644
index 00000000000..ecb7f0f02dd
--- /dev/null
+++ b/test/image/mocks/gl2d_ultra_zoom.json
@@ -0,0 +1,53 @@
+{
+ "data": [
+ {
+ "x": [
+ 1.0e-2,
+ 1.0000001e-2,
+ 1.0000002e-2,
+ 1.0000003e-2,
+ 1.0000004e-2,
+ 1.0000005e-2,
+ 1.0000006e-2,
+ 1.0000007e-2,
+ 1.0000008e-2,
+ 1.0000009e-2,
+ 1.0000010e-2
+ ],
+ "y": [
+ 1.0e-2,
+ 1.0000001e-2,
+ 1.0000002e-2,
+ 1.0000003e-2,
+ 1.0000004e-2,
+ 1.0000005e-2,
+ 1.0000006e-2,
+ 1.0000007e-2,
+ 1.0000008e-2,
+ 1.0000009e-2,
+ 1.0000010e-2
+ ],
+ "mode": "markers",
+ "type": "scatter"
+ }
+ ],
+ "layout": {
+ "autosize": true,
+ "xaxis": {
+ "range": [
+ 1.0e-2,
+ 1.0000010e-2
+ ],
+ "type": "linear",
+ "autorange": false
+ },
+ "yaxis": {
+ "range": [
+ 1.0e-2,
+ 1.0000010e-2
+ ],
+ "type": "linear",
+ "autorange": false
+ }
+ }
+}
diff --git a/test/jasmine/assets/read_pixel.js b/test/jasmine/assets/read_pixel.js
new file mode 100644
index 00000000000..fadc705079c
--- /dev/null
+++ b/test/jasmine/assets/read_pixel.js
@@ -0,0 +1,13 @@
+'use strict';
+
+module.exports = function(canvas, x, y) {
+ if(!canvas) return null;
+
+ var gl = canvas.getContext('webgl');
+
+ var pixels = new Uint8Array(4);
+
+ gl.readPixels(x, y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
+
+ return pixels;
+};
diff --git a/test/jasmine/tests/axes_test.js b/test/jasmine/tests/axes_test.js
index fb269708016..c7b8812294f 100644
--- a/test/jasmine/tests/axes_test.js
+++ b/test/jasmine/tests/axes_test.js
@@ -289,7 +289,7 @@ describe('Test axes', function() {
}];
supplyLayoutDefaults(layoutIn, layoutOut, fullData);
- expect(layoutOut._basePlotModules).toEqual([]);
+ expect(layoutOut._basePlotModules[0].name).toEqual('cartesian');
});
it('should detect orphan axes (gl2d + cartesian case)', function() {
diff --git a/test/jasmine/tests/gl2d_click_test.js b/test/jasmine/tests/gl2d_click_test.js
index 70609851a15..593d896d03a 100644
--- a/test/jasmine/tests/gl2d_click_test.js
+++ b/test/jasmine/tests/gl2d_click_test.js
@@ -194,6 +194,7 @@ describe('Test hover and click interactions', function() {
}
};
_mock.data[0].hoverinfo = _mock.data[0].x.map(function(_, i) { return i % 2 ? 'y' : 'x'; });
+
_mock.data[0].hoverlabel = {
bgcolor: 'blue',
bordercolor: _mock.data[0].x.map(function(_, i) { return i % 2 ? 'red' : 'green'; })
@@ -357,11 +358,11 @@ describe('Test hover and click interactions', function() {
y: 18,
curveNumber: 2,
pointNumber: 0,
- bgcolor: 'rgb(255, 127, 14)',
- bordercolor: 'rgb(68, 68, 68)',
+ bgcolor: 'rgb(44, 160, 44)',
+ bordercolor: 'rgb(255, 255, 255)',
fontSize: 13,
fontFamily: 'Arial',
- fontColor: 'rgb(68, 68, 68)'
+ fontColor: 'rgb(255, 255, 255)'
}, {
msg: 'scattergl after visibility restyle'
});
@@ -405,11 +406,11 @@ describe('Test hover and click interactions', function() {
y: 18,
curveNumber: 2,
pointNumber: 0,
- bgcolor: 'rgb(255, 127, 14)',
- bordercolor: 'rgb(68, 68, 68)',
+ bgcolor: 'rgb(44, 160, 44)',
+ bordercolor: 'rgb(255, 255, 255)',
fontSize: 13,
fontFamily: 'Arial',
- fontColor: 'rgb(68, 68, 68)'
+ fontColor: 'rgb(255, 255, 255)'
}, {
msg: 'scattergl fancy after visibility restyle'
});
@@ -464,7 +465,7 @@ describe('@noCI Test gl2d lasso/select:', function() {
});
var gd;
- var selectPath = [[93, 193], [143, 193]];
+ var selectPath = [[103, 193], [113, 193]];
var lassoPath = [[316, 171], [318, 239], [335, 243], [328, 169]];
var lassoPath2 = [[93, 193], [143, 193], [143, 500], [93, 500], [93, 193]];
@@ -508,9 +509,6 @@ describe('@noCI Test gl2d lasso/select:', function() {
});
}
- function countGlObjects() {
- return gd._fullLayout._plots.xy._scene2d.glplot.objects.length;
- }
it('should work under fast mode with *select* dragmode', function(done) {
var _mock = Lib.extendDeep({}, mockFast);
@@ -520,19 +518,19 @@ describe('@noCI Test gl2d lasso/select:', function() {
Plotly.plot(gd, _mock)
.then(delay(100))
.then(function() {
- expect(countGlObjects()).toBe(1, 'has on gl-scatter2d object');
+ expect(gd._fullLayout._plots.xy._scene.select2d).not.toBe(undefined, 'scatter2d renderer');
return select(selectPath);
})
.then(function(eventData) {
assertEventData(eventData, {
points: [
- {x: 3.911, y: 0.401},
- {x: 5.34, y: 0.403},
- {x: 6.915, y: 0.411}
+ {pointNumber: 25, x: 1.425, y: 0.538},
+ {pointNumber: 26, x: 1.753, y: 0.5},
+ {pointNumber: 27, x: 2.22, y: 0.45}
]
});
- expect(countGlObjects()).toBe(2, 'adds a dimmed gl-scatter2d objects');
+
})
.catch(fail)
.then(done);
@@ -546,19 +544,16 @@ describe('@noCI Test gl2d lasso/select:', function() {
Plotly.plot(gd, _mock)
.then(delay(100))
.then(function() {
- expect(countGlObjects()).toBe(1);
-
return select(lassoPath2);
})
.then(function(eventData) {
assertEventData(eventData, {
points: [
- {x: 3.911, y: 0.401},
- {x: 5.34, y: 0.403},
- {x: 6.915, y: 0.411}
+ {pointNumber: 25, x: 1.425, y: 0.538},
+ {pointNumber: 26, x: 1.753, y: 0.5},
+ {pointNumber: 27, x: 2.22, y: 0.45}
]
});
- expect(countGlObjects()).toBe(2);
})
.catch(fail)
.then(done);
@@ -572,15 +567,12 @@ describe('@noCI Test gl2d lasso/select:', function() {
Plotly.plot(gd, _mock)
.then(delay(100))
.then(function() {
- expect(countGlObjects()).toBe(2, 'has a gl-line2d and a gl-scatter2d-sdf');
-
return select(selectPath);
})
.then(function(eventData) {
assertEventData(eventData, {
points: [{x: 0.004, y: 12.5}]
});
- expect(countGlObjects()).toBe(2, 'only changes colors of gl-scatter2d-sdf object');
})
.catch(fail)
.then(done);
@@ -594,15 +586,12 @@ describe('@noCI Test gl2d lasso/select:', function() {
Plotly.plot(gd, _mock)
.then(delay(100))
.then(function() {
- expect(countGlObjects()).toBe(2, 'has a gl-line2d and a gl-scatter2d-sdf');
-
return select(lassoPath);
})
.then(function(eventData) {
assertEventData(eventData, {
points: [{ x: 0.099, y: 2.75 }]
});
- expect(countGlObjects()).toBe(2, 'only changes colors of gl-scatter2d-sdf object');
})
.catch(fail)
.then(done);
diff --git a/test/jasmine/tests/gl2d_date_axis_render_test.js b/test/jasmine/tests/gl2d_date_axis_render_test.js
index 7a7f5d8a173..d3eff9200e3 100644
--- a/test/jasmine/tests/gl2d_date_axis_render_test.js
+++ b/test/jasmine/tests/gl2d_date_axis_render_test.js
@@ -31,12 +31,13 @@ describe('date axis', function() {
expect(gd._fullLayout.xaxis.type).toBe('date');
expect(gd._fullLayout.yaxis.type).toBe('linear');
expect(gd._fullData[0].type).toBe('scattergl');
- expect(gd._fullData[0]._module.basePlotModule.name).toBe('gl2d');
+ expect(gd._fullData[0]._module.basePlotModule.name).toBe('cartesian');
- // one way of check which renderer - fancy vs not - we're using
- var objs = gd._fullLayout._plots.xy._scene2d.glplot.objects;
- expect(objs.length).toEqual(2);
- expect(objs[1].points.length).toEqual(4);
+ // one way of check which renderer - fancy vs not - we're
+ var scene = gd._fullLayout._plots.xy._scene;
+ expect(scene.scatter2d).toBeDefined();
+ expect(scene.markerOptions[0].positions.length).toEqual(4);
+ expect(scene.line2d).not.toBeUndefined();
});
it('should use the fancy gl-vis/gl-scatter2d once again', function() {
@@ -57,18 +58,18 @@ describe('date axis', function() {
expect(gd._fullLayout.xaxis.type).toBe('date');
expect(gd._fullLayout.yaxis.type).toBe('linear');
expect(gd._fullData[0].type).toBe('scattergl');
- expect(gd._fullData[0]._module.basePlotModule.name).toBe('gl2d');
+ expect(gd._fullData[0]._module.basePlotModule.name).toBe('cartesian');
- // one way of check which renderer - fancy vs not - we're using
- var objs = gd._fullLayout._plots.xy._scene2d.glplot.objects;
- expect(objs.length).toEqual(2);
- expect(objs[1].points.length).toEqual(4);
+ var scene = gd._fullLayout._plots.xy._scene;
+ expect(scene.scatter2d).toBeDefined();
+ expect(scene.markerOptions[0].positions.length).toEqual(4);
+ expect(scene.line2d).toBeDefined();
});
it('should now use the non-fancy gl-vis/gl-scatter2d', function() {
Plotly.plot(gd, [{
type: 'scattergl',
- mode: 'markers', // important, as otherwise lines are assumed (which needs fancy)
+ mode: 'markers',
x: [new Date('2016-10-10'), new Date('2016-10-11')],
y: [15, 16]
}]);
@@ -76,17 +77,18 @@ describe('date axis', function() {
expect(gd._fullLayout.xaxis.type).toBe('date');
expect(gd._fullLayout.yaxis.type).toBe('linear');
expect(gd._fullData[0].type).toBe('scattergl');
- expect(gd._fullData[0]._module.basePlotModule.name).toBe('gl2d');
+ expect(gd._fullData[0]._module.basePlotModule.name).toBe('cartesian');
- var objs = gd._fullLayout._plots.xy._scene2d.glplot.objects;
- expect(objs.length).toEqual(1);
- expect(objs[0].pointCount).toEqual(2);
+ var scene = gd._fullLayout._plots.xy._scene;
+ expect(scene.scatter2d).toBeDefined();
+ expect(scene.markerOptions[0].positions.length).toEqual(4);
+ expect(scene.line2d).toBeDefined();
});
it('should use the non-fancy gl-vis/gl-scatter2d with string dates', function() {
Plotly.plot(gd, [{
type: 'scattergl',
- mode: 'markers', // important, as otherwise lines are assumed (which needs fancy)
+ mode: 'markers',
x: ['2016-10-10', '2016-10-11'],
y: [15, 16]
}]);
@@ -94,10 +96,11 @@ describe('date axis', function() {
expect(gd._fullLayout.xaxis.type).toBe('date');
expect(gd._fullLayout.yaxis.type).toBe('linear');
expect(gd._fullData[0].type).toBe('scattergl');
- expect(gd._fullData[0]._module.basePlotModule.name).toBe('gl2d');
+ expect(gd._fullData[0]._module.basePlotModule.name).toBe('cartesian');
- var objs = gd._fullLayout._plots.xy._scene2d.glplot.objects;
- expect(objs.length).toEqual(1);
- expect(objs[0].pointCount).toEqual(2);
+ var scene = gd._fullLayout._plots.xy._scene;
+ expect(scene.scatter2d).toBeDefined();
+ expect(scene.markerOptions[0].positions.length).toEqual(4);
+ expect(scene.line2d).toBeDefined();
});
});
diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js
index f52f2d80485..1c76f573f9c 100644
--- a/test/jasmine/tests/gl_plot_interact_test.js
+++ b/test/jasmine/tests/gl_plot_interact_test.js
@@ -11,6 +11,7 @@ var fail = require('../assets/fail_test');
var mouseEvent = require('../assets/mouse_event');
var selectButton = require('../assets/modebar_button');
var delay = require('../assets/delay');
+var readPixel = require('../assets/read_pixel');
var customAssertions = require('../assets/custom_assertions');
var assertHoverLabelStyle = customAssertions.assertHoverLabelStyle;
@@ -839,6 +840,7 @@ describe('Test gl2d plots', function() {
beforeEach(function() {
gd = createGraphDiv();
+ jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000;
});
afterEach(function() {
@@ -855,12 +857,13 @@ describe('Test gl2d plots', function() {
it('should respond to drag interactions', function(done) {
var _mock = Lib.extendDeep({}, mock);
+
var relayoutCallback = jasmine.createSpy('relayoutCallback');
var originalX = [-0.3037383177570093, 5.303738317757009];
var originalY = [-0.5532219548705213, 6.191112269783224];
var newX = [-0.5373831775700935, 5.070093457943925];
- var newY = [-1.7575673521301187, 4.986766872523626];
+ var newY = [-1.7575673521301185, 4.986766872523626];
var precision = 5;
Plotly.plot(gd, _mock)
@@ -931,9 +934,10 @@ describe('Test gl2d plots', function() {
// a callback value structure and contents check
expect(relayoutCallback).toHaveBeenCalledWith(jasmine.objectContaining({
- lastInputTime: jasmine.any(Number),
- xaxis: [jasmine.any(Number), jasmine.any(Number)],
- yaxis: [jasmine.any(Number), jasmine.any(Number)]
+ 'xaxis.range[0]': jasmine.any(Number),
+ 'xaxis.range[1]': jasmine.any(Number),
+ 'yaxis.range[0]': jasmine.any(Number),
+ 'yaxis.range[1]': jasmine.any(Number)
}));
})
.then(done);
@@ -942,60 +946,53 @@ describe('Test gl2d plots', function() {
it('should be able to toggle visibility', function(done) {
var _mock = Lib.extendDeep({}, mock);
- // a line object + scatter fancy
- var OBJECT_PER_TRACE = 2;
-
- var objects = function() {
- return gd._fullLayout._plots.xy._scene2d.glplot.objects;
- };
-
Plotly.plot(gd, _mock)
.then(delay(20))
.then(function() {
- expect(objects().length).toEqual(OBJECT_PER_TRACE);
-
return Plotly.restyle(gd, 'visible', 'legendonly');
})
.then(function() {
- expect(objects().length).toEqual(OBJECT_PER_TRACE);
- expect(objects()[0].data.length).toEqual(0);
+ expect(readPixel(gd.querySelector('.gl-canvas-context'), 108, 100)[0]).toBe(0);
return Plotly.restyle(gd, 'visible', true);
})
.then(function() {
- expect(objects().length).toEqual(OBJECT_PER_TRACE);
- expect(objects()[0].data.length).not.toEqual(0);
+ expect(readPixel(gd.querySelector('.gl-canvas-context'), 108, 100)[0]).not.toBe(0);
return Plotly.restyle(gd, 'visible', false);
})
.then(function() {
- expect(gd._fullLayout._plots.xy._scene2d).toBeUndefined();
+ expect(gd.querySelector('.gl-canvas-context')).not.toBe(null);
return Plotly.restyle(gd, 'visible', true);
})
.then(function() {
- expect(objects().length).toEqual(OBJECT_PER_TRACE);
- expect(objects()[0].data.length).not.toEqual(0);
+ expect(readPixel(gd.querySelector('.gl-canvas-context'), 108, 100)[0]).not.toBe(0);
})
.then(done);
});
- it('should clear orphan cartesian subplots on addTraces', function(done) {
- Plotly.newPlot(gd, [], {
- xaxis: { title: 'X' },
- yaxis: { title: 'Y' }
+ it('should be able to toggle from svg to gl', function(done) {
+ Plotly.plot(gd, [{
+ y: [1, 2, 1],
+ }])
+ .then(function() {
+ expect(countCanvases()).toBe(0);
+ expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(1);
+
+ return Plotly.restyle(gd, 'type', 'scattergl');
})
.then(function() {
- return Plotly.addTraces(gd, [{
- type: 'scattergl',
- x: [1, 2, 3, 4, 5, 6, 7],
- y: [0, 5, 8, 9, 8, 5, 0]
- }]);
+ expect(countCanvases()).toBe(3);
+ expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(0);
+
+ return Plotly.restyle(gd, 'type', 'scatter');
})
.then(function() {
- expect(d3.select('.xtitle').size()).toEqual(0);
- expect(d3.select('.ytitle').size()).toEqual(0);
+ expect(countCanvases()).toBe(0);
+ expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(1);
})
+ .catch(fail)
.then(done);
});
@@ -1033,8 +1030,8 @@ describe('Test gl2d plots', function() {
// no change - too small
mouseTo([centerX, centerY], [centerX - 5, centerY + 5]);
- expect(gd.layout.xaxis.range).toBeCloseToArray([6, 8], 3);
- expect(gd.layout.yaxis.range).toBeCloseToArray([5, 7], 3);
+ expect(gd.layout.xaxis.range).toBeCloseToArray([0, 16], 3);
+ expect(gd.layout.yaxis.range).toBeCloseToArray([0, 16], 3);
})
.catch(fail)
.then(done);
@@ -1080,8 +1077,8 @@ describe('Test gl2d plots', function() {
// no change - too small
mouseTo([centerX, centerY], [centerX - 5, centerY + 5]);
- expect(gd.layout.xaxis.range).toBeCloseToArray([4, 6], 3);
- expect(gd.layout.yaxis.range).toBeCloseToArray([9, 10], 3);
+ expect(gd.layout.xaxis.range).toBeCloseToArray([-8, 24], 3);
+ expect(gd.layout.yaxis.range).toBeCloseToArray([0, 16], 3);
return Plotly.relayout(gd, {
'xaxis.autorange': true,
@@ -1089,8 +1086,8 @@ describe('Test gl2d plots', function() {
});
})
.then(function() {
- expect(gd.layout.xaxis.range).toBeCloseToArray([-8.09195, 24.09195], 3);
- expect(gd.layout.yaxis.range).toBeCloseToArray([-0.04598, 16.04598], 3);
+ expect(gd.layout.xaxis.range).toBeCloseToArray([-8.091954022988505, 24.091954022988503], 3);
+ expect(gd.layout.yaxis.range).toBeCloseToArray([-0.04597701149425282, 16.04597701149425], 3);
})
.catch(fail)
.then(done);
@@ -1098,7 +1095,6 @@ describe('Test gl2d plots', function() {
it('should change plot type with incomplete data', function(done) {
Plotly.plot(gd, [{}]);
-
expect(function() {
Plotly.restyle(gd, {type: 'scattergl', x: [[1]]}, 0);
}).not.toThrow();
@@ -1143,10 +1139,10 @@ describe('Test removal of gl contexts', function() {
y: [2, 1, 3]
}])
.then(function() {
- expect(gd._fullLayout._plots.xy._scene2d.glplot).toBeDefined();
-
+ expect(gd._fullLayout._plots.xy._scene).toBeDefined();
Plots.cleanPlot([], {}, gd._fullData, gd._fullLayout);
- expect(gd._fullLayout._plots).toEqual({});
+
+ expect(gd._fullLayout._plots.xy._scene).toBeUndefined();
})
.then(done);
});
@@ -1204,8 +1200,8 @@ describe('Test removal of gl contexts', function() {
y: [2, 1, 3]
}])
.then(function() {
- firstGlplotObject = gd._fullLayout._plots.xy._scene2d.glplot;
- firstGlContext = firstGlplotObject.gl;
+ firstGlplotObject = gd._fullLayout._plots.xy._scene;
+ firstGlContext = firstGlplotObject.scatter2d.gl;
firstCanvas = firstGlContext.canvas;
expect(firstGlplotObject).toBeDefined();
@@ -1219,8 +1215,8 @@ describe('Test removal of gl contexts', function() {
}], {});
})
.then(function() {
- var secondGlplotObject = gd._fullLayout._plots.xy._scene2d.glplot;
- var secondGlContext = secondGlplotObject.gl;
+ var secondGlplotObject = gd._fullLayout._plots.xy._scene;
+ var secondGlContext = secondGlplotObject.scatter2d.gl;
var secondCanvas = secondGlContext.canvas;
expect(Object.keys(gd._fullLayout._plots).length === 1);
@@ -1285,27 +1281,33 @@ describe('Test gl plot side effects', function() {
y: [2, 1, 2]
}];
- Plotly.plot(gd, []).then(function() {
+ Plotly.plot(gd, [])
+ .then(function() {
countCanvases(0);
return Plotly.plot(gd, data);
- }).then(function() {
+ })
+ .then(function() {
countCanvases(3);
return Plotly.purge(gd);
- }).then(function() {
+ })
+ .then(function() {
countCanvases(0);
return Plotly.plot(gd, data);
- }).then(function() {
+ })
+ .then(function() {
countCanvases(3);
return Plotly.deleteTraces(gd, [0]);
- }).then(function() {
+ })
+ .then(function() {
countCanvases(0);
return Plotly.purge(gd);
- }).then(done);
+ })
+ .then(done);
});
it('should be able to switch trace type', function(done) {
@@ -1357,8 +1359,8 @@ describe('Test gl2d interactions', function() {
var ann = d3.select('g.annotation-text-g').select('g');
var translate = Drawing.getTranslate(ann);
- expect(translate.x).toBeWithin(xy[0], 1.5);
- expect(translate.y).toBeWithin(xy[1], 1.5);
+ expect(translate.x).toBeWithin(xy[0], 3.5);
+ expect(translate.y).toBeWithin(xy[1], 3.5);
}
Plotly.plot(gd, [{
@@ -1374,10 +1376,10 @@ describe('Test gl2d interactions', function() {
dragmode: 'pan'
})
.then(function() {
- assertAnnotation([327, 315]);
+ assertAnnotation([327, 312]);
drag([250, 200], [200, 150]);
- assertAnnotation([277, 265]);
+ assertAnnotation([277, 262]);
return Plotly.relayout(gd, {
'xaxis.range': [1.5, 2.5],