From bb939a414021b04bb350a2eeb2cd3cdfa5db98da Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 10 Jul 2017 16:38:00 -0400 Subject: [PATCH 001/151] Outline basic trace --- lib/index.js | 1 + lib/scatterregl.js | 11 ++++ src/traces/scatterregl/attributes.js | 88 ++++++++++++++++++++++++++++ src/traces/scatterregl/calc.js | 43 ++++++++++++++ src/traces/scatterregl/defaults.js | 55 +++++++++++++++++ src/traces/scatterregl/index.js | 36 ++++++++++++ src/traces/scatterregl/plot.js | 43 ++++++++++++++ src/traces/scatterregl/select.js | 60 +++++++++++++++++++ 8 files changed, 337 insertions(+) create mode 100644 lib/scatterregl.js create mode 100644 src/traces/scatterregl/attributes.js create mode 100644 src/traces/scatterregl/calc.js create mode 100644 src/traces/scatterregl/defaults.js create mode 100644 src/traces/scatterregl/index.js create mode 100644 src/traces/scatterregl/plot.js create mode 100644 src/traces/scatterregl/select.js diff --git a/lib/index.js b/lib/index.js index 8c1b11fff73..eca8c5c1cc2 100644 --- a/lib/index.js +++ b/lib/index.js @@ -31,6 +31,7 @@ Plotly.register([ require('./choropleth'), require('./scattergl'), + require('./scatterregl'), require('./pointcloud'), require('./heatmapgl'), require('./parcoords'), diff --git a/lib/scatterregl.js b/lib/scatterregl.js new file mode 100644 index 00000000000..05a7a88ad7f --- /dev/null +++ b/lib/scatterregl.js @@ -0,0 +1,11 @@ +/** +* 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'; + +module.exports = require('../src/traces/scatterregl'); diff --git a/src/traces/scatterregl/attributes.js b/src/traces/scatterregl/attributes.js new file mode 100644 index 00000000000..637b6812338 --- /dev/null +++ b/src/traces/scatterregl/attributes.js @@ -0,0 +1,88 @@ +/** +* 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 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 extendDeep = require('../../lib/extend').extendDeep; + +var scatterLineAttrs = scatterAttrs.line, + scatterMarkerAttrs = scatterAttrs.marker, + scatterMarkerLineAttrs = scatterMarkerAttrs.line; + +module.exports = { + x: scatterAttrs.x, + x0: scatterAttrs.x0, + dx: scatterAttrs.dx, + y: scatterAttrs.y, + y0: scatterAttrs.y0, + dy: scatterAttrs.dy, + + text: extendFlat({}, scatterAttrs.text, { + description: [ + 'Sets text elements associated with each (x,y) pair to appear on hover.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + 'this trace\'s (x,y) coordinates.' + ].join(' ') + }), + mode: { + valType: 'flaglist', + flags: ['lines', 'markers'], + extras: ['none'], + role: 'info', + description: [ + 'Determines the drawing mode for this scatter trace.' + ].join(' ') + }, + line: { + color: scatterLineAttrs.color, + width: scatterLineAttrs.width, + dash: { + valType: 'enumerated', + values: Object.keys(DASHES), + dflt: 'solid', + role: 'style', + description: 'Sets the style of the lines.' + } + }, + marker: extendDeep({}, colorAttributes('marker'), { + symbol: { + valType: 'enumerated', + values: Object.keys(MARKERS), + dflt: 'circle', + arrayOk: true, + role: 'style', + description: 'Sets the marker symbol type.' + }, + size: scatterMarkerAttrs.size, + sizeref: scatterMarkerAttrs.sizeref, + sizemin: scatterMarkerAttrs.sizemin, + sizemode: scatterMarkerAttrs.sizemode, + opacity: scatterMarkerAttrs.opacity, + showscale: scatterMarkerAttrs.showscale, + colorbar: scatterMarkerAttrs.colorbar, + line: extendDeep({}, colorAttributes('marker.line'), { + width: scatterMarkerLineAttrs.width + }) + }), + connectgaps: scatterAttrs.connectgaps, + fill: extendFlat({}, scatterAttrs.fill, { + values: ['none', 'tozeroy', 'tozerox'] + }), + fillcolor: scatterAttrs.fillcolor, + + error_y: scatterAttrs.error_y, + error_x: scatterAttrs.error_x +}; diff --git a/src/traces/scatterregl/calc.js b/src/traces/scatterregl/calc.js new file mode 100644 index 00000000000..1be524af55e --- /dev/null +++ b/src/traces/scatterregl/calc.js @@ -0,0 +1,43 @@ +/** +* 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/scatterregl/defaults.js b/src/traces/scatterregl/defaults.js new file mode 100644 index 00000000000..442363ae113 --- /dev/null +++ b/src/traces/scatterregl/defaults.js @@ -0,0 +1,55 @@ +/** +* 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 Lib = require('../../lib'); + +var constants = require('../scatter/constants'); +var subTypes = require('../scatter/subtypes'); +var handleXYDefaults = require('../scatter/xy_defaults'); +var handleMarkerDefaults = require('../scatter/marker_defaults'); +var handleLineDefaults = require('../scatter/line_defaults'); +var handleFillColorDefaults = require('../scatter/fillcolor_defaults'); +var errorBarsSupplyDefaults = require('../../components/errorbars/defaults'); + +var attributes = require('./attributes'); + + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + var len = handleXYDefaults(traceIn, traceOut, layout, coerce); + if(!len) { + traceOut.visible = false; + return; + } + + coerce('text'); + coerce('mode', len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'); + + if(subTypes.hasLines(traceOut)) { + coerce('connectgaps'); + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); + } + + if(subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce); + } + + coerce('fill'); + if(traceOut.fill !== 'none') { + handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); + } + + errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'}); + errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'}); +}; diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js new file mode 100644 index 00000000000..3144432b26c --- /dev/null +++ b/src/traces/scatterregl/index.js @@ -0,0 +1,36 @@ +/** +* 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 ScatterGl = {}; + +ScatterGl.attributes = require('./attributes'); +ScatterGl.supplyDefaults = require('./defaults'); +ScatterGl.colorbar = require('../scatter/colorbar'); +ScatterGl.hoverPoints = require('../scatter/hover'); + +// reuse the Scatter3D 'dummy' calc step so that legends know what to do +ScatterGl.calc = require('./calc'); +ScatterGl.plot = require('./plot'); +ScatterGl.selectPoints = require('./select'); + +ScatterGl.moduleType = 'trace'; +ScatterGl.name = 'scatterregl'; +ScatterGl.basePlotModule = require('../../plots/cartesian'); +ScatterGl.categories = ['cartesian', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend']; +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(' ') +}; + +module.exports = ScatterGl; diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js new file mode 100644 index 00000000000..5f893c6e630 --- /dev/null +++ b/src/traces/scatterregl/plot.js @@ -0,0 +1,43 @@ +/** +* 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 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'); + +function plot(scene, data, cdscatter) { + console.log(scene, data, cdscatter) + + var container = scene.querySelector('.gl-container') + + //FIXME: avoid forcing absolute style by disabling forced plotly background + var canvas = container.appendChild(document.createElement('canvas')) + canvas.style.position = 'absolute' + + var ctx = canvas.getContext('2d') + + ctx.fillStyle = 'black' + ctx.fillRect(0,0,10,10) + console.log(canvas) + + return plot; +} + +module.exports = plot; diff --git a/src/traces/scatterregl/select.js b/src/traces/scatterregl/select.js new file mode 100644 index 00000000000..548824ad740 --- /dev/null +++ b/src/traces/scatterregl/select.js @@ -0,0 +1,60 @@ +/** +* 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 scattergl = cd[0].glTrace; + var scene = cd[0].glTrace.scene; + + var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace)); + if(trace.visible !== true || 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: di.x, + y: di.y + }); + di.dim = 0; + } + else di.dim = 1; + } + } + + // highlight selected points here + trace.selection = selection; + + scattergl.update(trace, cd); + scene.glplot.setDirty(); + + return selection; +}; From f054bc6e15b23aaa9133b48db9523b30b759d453 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 10 Jul 2017 17:18:20 -0400 Subject: [PATCH 002/151] Make trace draw canvas point --- src/plot_api/subroutines.js | 1 - src/traces/scatterregl/plot.js | 20 +++++++++++++------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index 64d1663910d..811fb873bf8 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -164,7 +164,6 @@ exports.lsInner = function(gd) { 'height': ya._length }); - plotinfo.plot.call(Drawing.setTranslate, xa._offset, ya._offset); plotinfo.plot.call(Drawing.setClipUrl, plotinfo.clipId); diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 5f893c6e630..c7c111581af 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -22,20 +22,26 @@ var getTraceColor = require('../scatter/get_trace_color'); var MARKER_SYMBOLS = require('../../constants/gl2d_markers'); var DASHES = require('../../constants/gl2d_dashes'); -function plot(scene, data, cdscatter) { - console.log(scene, data, cdscatter) +function plot(container, data, cdscatter) { + console.log(container, data, cdscatter) - var container = scene.querySelector('.gl-container') + var layout = container._fullLayout + var xa = layout.xaxis + var ya = layout.yaxis + var container = container.querySelector('.gl-container') //FIXME: avoid forcing absolute style by disabling forced plotly background var canvas = container.appendChild(document.createElement('canvas')) - canvas.style.position = 'absolute' + canvas.style.position = 'absolute'; + canvas.style.transform = 'translate(' + xa._offset + 'px, ' + ya._offset + 'px)'; + canvas.width = 500; + canvas.height = 500; var ctx = canvas.getContext('2d') - ctx.fillStyle = 'black' - ctx.fillRect(0,0,10,10) - console.log(canvas) + ctx.clearRect(0, 0, canvas.width, canvas.height) + ctx.fillStyle = 'black'; + ctx.fillRect(xa.c2p(-1),ya.c2p(1),10,10) return plot; } From eb3b122819c1e595465ad0530367da89fd954228 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 11 Jul 2017 09:41:33 -0400 Subject: [PATCH 003/151] Add temp files --- package.json | 1 + src/traces/scatter/hover.js | 1 + src/traces/scatterregl/attributes.js | 88 ---------------------------- src/traces/scatterregl/calc.js | 43 -------------- src/traces/scatterregl/defaults.js | 55 ----------------- src/traces/scatterregl/hover.js | 17 ++++++ src/traces/scatterregl/index.js | 29 ++------- src/traces/scatterregl/plot.js | 40 ++++++++++--- src/traces/scatterregl/select.js | 60 ------------------- 9 files changed, 56 insertions(+), 278 deletions(-) delete mode 100644 src/traces/scatterregl/attributes.js delete mode 100644 src/traces/scatterregl/calc.js delete mode 100644 src/traces/scatterregl/defaults.js create mode 100644 src/traces/scatterregl/hover.js delete mode 100644 src/traces/scatterregl/select.js diff --git a/package.json b/package.json index 750cda97278..b1307713635 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "ndarray-fill": "^1.0.2", "ndarray-homography": "^1.0.0", "ndarray-ops": "^1.2.2", + "object-assign": "^4.1.1", "regl": "^1.3.0", "right-now": "^1.0.0", "robust-orientation": "^1.1.3", diff --git a/src/traces/scatter/hover.js b/src/traces/scatter/hover.js index ee968926c64..52579f45f5a 100644 --- a/src/traces/scatter/hover.js +++ b/src/traces/scatter/hover.js @@ -52,6 +52,7 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { Fx.getClosest(cd, distfn, pointData); + // skip the rest (for this trace) if we didn't find a close point if(pointData.index !== false) { diff --git a/src/traces/scatterregl/attributes.js b/src/traces/scatterregl/attributes.js deleted file mode 100644 index 637b6812338..00000000000 --- a/src/traces/scatterregl/attributes.js +++ /dev/null @@ -1,88 +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 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 extendDeep = require('../../lib/extend').extendDeep; - -var scatterLineAttrs = scatterAttrs.line, - scatterMarkerAttrs = scatterAttrs.marker, - scatterMarkerLineAttrs = scatterMarkerAttrs.line; - -module.exports = { - x: scatterAttrs.x, - x0: scatterAttrs.x0, - dx: scatterAttrs.dx, - y: scatterAttrs.y, - y0: scatterAttrs.y0, - dy: scatterAttrs.dy, - - text: extendFlat({}, scatterAttrs.text, { - description: [ - 'Sets text elements associated with each (x,y) pair to appear on hover.', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to the', - 'this trace\'s (x,y) coordinates.' - ].join(' ') - }), - mode: { - valType: 'flaglist', - flags: ['lines', 'markers'], - extras: ['none'], - role: 'info', - description: [ - 'Determines the drawing mode for this scatter trace.' - ].join(' ') - }, - line: { - color: scatterLineAttrs.color, - width: scatterLineAttrs.width, - dash: { - valType: 'enumerated', - values: Object.keys(DASHES), - dflt: 'solid', - role: 'style', - description: 'Sets the style of the lines.' - } - }, - marker: extendDeep({}, colorAttributes('marker'), { - symbol: { - valType: 'enumerated', - values: Object.keys(MARKERS), - dflt: 'circle', - arrayOk: true, - role: 'style', - description: 'Sets the marker symbol type.' - }, - size: scatterMarkerAttrs.size, - sizeref: scatterMarkerAttrs.sizeref, - sizemin: scatterMarkerAttrs.sizemin, - sizemode: scatterMarkerAttrs.sizemode, - opacity: scatterMarkerAttrs.opacity, - showscale: scatterMarkerAttrs.showscale, - colorbar: scatterMarkerAttrs.colorbar, - line: extendDeep({}, colorAttributes('marker.line'), { - width: scatterMarkerLineAttrs.width - }) - }), - connectgaps: scatterAttrs.connectgaps, - fill: extendFlat({}, scatterAttrs.fill, { - values: ['none', 'tozeroy', 'tozerox'] - }), - fillcolor: scatterAttrs.fillcolor, - - error_y: scatterAttrs.error_y, - error_x: scatterAttrs.error_x -}; diff --git a/src/traces/scatterregl/calc.js b/src/traces/scatterregl/calc.js deleted file mode 100644 index 1be524af55e..00000000000 --- a/src/traces/scatterregl/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/scatterregl/defaults.js b/src/traces/scatterregl/defaults.js deleted file mode 100644 index 442363ae113..00000000000 --- a/src/traces/scatterregl/defaults.js +++ /dev/null @@ -1,55 +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 Lib = require('../../lib'); - -var constants = require('../scatter/constants'); -var subTypes = require('../scatter/subtypes'); -var handleXYDefaults = require('../scatter/xy_defaults'); -var handleMarkerDefaults = require('../scatter/marker_defaults'); -var handleLineDefaults = require('../scatter/line_defaults'); -var handleFillColorDefaults = require('../scatter/fillcolor_defaults'); -var errorBarsSupplyDefaults = require('../../components/errorbars/defaults'); - -var attributes = require('./attributes'); - - -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } - - var len = handleXYDefaults(traceIn, traceOut, layout, coerce); - if(!len) { - traceOut.visible = false; - return; - } - - coerce('text'); - coerce('mode', len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'); - - if(subTypes.hasLines(traceOut)) { - coerce('connectgaps'); - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); - } - - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce); - } - - coerce('fill'); - if(traceOut.fill !== 'none') { - handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); - } - - errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'}); - errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'}); -}; diff --git a/src/traces/scatterregl/hover.js b/src/traces/scatterregl/hover.js new file mode 100644 index 00000000000..dc303c05b8d --- /dev/null +++ b/src/traces/scatterregl/hover.js @@ -0,0 +1,17 @@ +'use strict' + +module.exports = hover + +function hover (pointData, xval, yval, hovermode) { + var cd = pointData.cd, + trace = cd[0].trace, + xa = pointData.xa, + ya = pointData.ya, + xpx = xa.c2p(xval), + ypx = ya.c2p(yval), + pt = [xpx, ypx], + hoveron = trace.hoveron || ''; + + console.log(xpx, ypx, trace) + // console.log(pointData, xval, yval, hovermode) +} diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 3144432b26c..7ca669da941 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -8,29 +8,12 @@ 'use strict'; -var ScatterGl = {}; +var extend = require('object-assign') -ScatterGl.attributes = require('./attributes'); -ScatterGl.supplyDefaults = require('./defaults'); -ScatterGl.colorbar = require('../scatter/colorbar'); -ScatterGl.hoverPoints = require('../scatter/hover'); +var Scatter = extend({}, require('../scatter/index')) -// reuse the Scatter3D 'dummy' calc step so that legends know what to do -ScatterGl.calc = require('./calc'); -ScatterGl.plot = require('./plot'); -ScatterGl.selectPoints = require('./select'); +Scatter.name = 'scatterregl' +Scatter.plot = require('./plot') +Scatter.hoverPoints = require('./hover') -ScatterGl.moduleType = 'trace'; -ScatterGl.name = 'scatterregl'; -ScatterGl.basePlotModule = require('../../plots/cartesian'); -ScatterGl.categories = ['cartesian', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend']; -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(' ') -}; - -module.exports = ScatterGl; +module.exports = Scatter; diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index c7c111581af..694de71c3c1 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -22,26 +22,48 @@ var getTraceColor = require('../scatter/get_trace_color'); var MARKER_SYMBOLS = require('../../constants/gl2d_markers'); var DASHES = require('../../constants/gl2d_dashes'); +// var createScatter = require('regl-scatter2d') + function plot(container, data, cdscatter) { - console.log(container, data, cdscatter) + // console.log(container, data, cdscatter) var layout = container._fullLayout + var data = container._fullData[0] var xa = layout.xaxis var ya = layout.yaxis var container = container.querySelector('.gl-container') - //FIXME: avoid forcing absolute style by disabling forced plotly background - var canvas = container.appendChild(document.createElement('canvas')) - canvas.style.position = 'absolute'; - canvas.style.transform = 'translate(' + xa._offset + 'px, ' + ya._offset + 'px)'; - canvas.width = 500; - canvas.height = 500; + //FIXME: find proper way to get plot holder + //FIXME: handle multiple subplots + var subplotObj = layout._plots.xy + var scatter = subplotObj._scatterplot + + //create regl-scatter, if not defined + if (scatter === undefined) { + //TODO: enhance picking + //TODO: create PR with questions + // if () + + //FIXME: avoid forcing absolute style by disabling forced plotly background + var canvas = container.appendChild(document.createElement('canvas')) + canvas.style.position = 'absolute'; + canvas.style.transform = 'translate(' + xa._offset + 'px, ' + ya._offset + 'px)'; + canvas.style.pointerEvents = 'none'; + canvas.width = 700; + canvas.height = 500; + scatter = subplotObj._scatterplot = {canvas: canvas} + } + + var canvas = scatter.canvas var ctx = canvas.getContext('2d') ctx.clearRect(0, 0, canvas.width, canvas.height) - ctx.fillStyle = 'black'; - ctx.fillRect(xa.c2p(-1),ya.c2p(1),10,10) + ctx.fillStyle = 'rgba(100,200,255,.8)'; + + for (var i = 0, l = data.x.length; i < l; i++) { + ctx.fillRect(xa.c2p(data.x[i]),ya.c2p(data.y[i]),5,5) + } return plot; } diff --git a/src/traces/scatterregl/select.js b/src/traces/scatterregl/select.js deleted file mode 100644 index 548824ad740..00000000000 --- a/src/traces/scatterregl/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 scattergl = cd[0].glTrace; - var scene = cd[0].glTrace.scene; - - var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace)); - if(trace.visible !== true || 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: di.x, - y: di.y - }); - di.dim = 0; - } - else di.dim = 1; - } - } - - // highlight selected points here - trace.selection = selection; - - scattergl.update(trace, cd); - scene.glplot.setDirty(); - - return selection; -}; From 5738dd96180c17efb0374658b900dfc185dbf6b1 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 11 Jul 2017 10:18:58 -0400 Subject: [PATCH 004/151] Add calc tpl --- src/traces/scatterregl/calc.js | 129 ++++++++++++++++++++++++++++++++ src/traces/scatterregl/hover.js | 2 +- src/traces/scatterregl/index.js | 1 + 3 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 src/traces/scatterregl/calc.js diff --git a/src/traces/scatterregl/calc.js b/src/traces/scatterregl/calc.js new file mode 100644 index 00000000000..1830656ba79 --- /dev/null +++ b/src/traces/scatterregl/calc.js @@ -0,0 +1,129 @@ +/** +* 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 isNumeric = require('fast-isnumeric'); + +var Axes = require('../../plots/cartesian/axes'); +var BADNUM = require('../../constants/numerical').BADNUM; + +var subTypes = require('../scatter/subtypes'); +var calcColorscale = require('../scatter/colorscale_calc'); +var arraysToCalcdata = require('../scatter/arrays_to_calcdata'); + + +module.exports = function calc(gd, trace) { + var xa = Axes.getFromId(gd, trace.xaxis || 'x'), + ya = Axes.getFromId(gd, trace.yaxis || 'y'); + + var x = xa.makeCalcdata(trace, 'x'), + y = ya.makeCalcdata(trace, 'y'); + + var serieslen = Math.min(x.length, y.length), + marker, + s, + i; + + // cancel minimum tick spacings (only applies to bars and boxes) + xa._minDtick = 0; + ya._minDtick = 0; + + if(x.length > serieslen) x.splice(serieslen, x.length - serieslen); + if(y.length > serieslen) y.splice(serieslen, y.length - serieslen); + + // check whether bounds should be tight, padded, extended to zero... + // most cases both should be padded on both ends, so start with that. + var xOptions = {padded: true}, + yOptions = {padded: true}; + + if(subTypes.hasMarkers(trace)) { + + // Treat size like x or y arrays --- Run d2c + // this needs to go before ppad computation + marker = trace.marker; + s = marker.size; + + if(Array.isArray(s)) { + // I tried auto-type but category and dates dont make much sense. + var ax = {type: 'linear'}; + Axes.setConvert(ax); + s = ax.makeCalcdata(trace.marker, 'size'); + if(s.length > serieslen) s.splice(serieslen, s.length - serieslen); + } + + var sizeref = 1.6 * (trace.marker.sizeref || 1), + markerTrans; + if(trace.marker.sizemode === 'area') { + markerTrans = function(v) { + return Math.max(Math.sqrt((v || 0) / sizeref), 3); + }; + } + else { + markerTrans = function(v) { + return Math.max((v || 0) / sizeref, 3); + }; + } + xOptions.ppad = yOptions.ppad = Array.isArray(s) ? + s.map(markerTrans) : markerTrans(s); + } + + calcColorscale(trace); + + // TODO: text size + + // include zero (tight) and extremes (padded) if fill to zero + // (unless the shape is closed, then it's just filling the shape regardless) + if(((trace.fill === 'tozerox') || + ((trace.fill === 'tonextx') && gd.firstscatter)) && + ((x[0] !== x[serieslen - 1]) || (y[0] !== y[serieslen - 1]))) { + xOptions.tozero = true; + } + + // if no error bars, markers or text, or fill to y=0 remove x padding + else if(!trace.error_y.visible && ( + ['tonexty', 'tozeroy'].indexOf(trace.fill) !== -1 || + (!subTypes.hasMarkers(trace) && !subTypes.hasText(trace)) + )) { + xOptions.padded = false; + xOptions.ppad = 0; + } + + // now check for y - rather different logic, though still mostly padded both ends + // include zero (tight) and extremes (padded) if fill to zero + // (unless the shape is closed, then it's just filling the shape regardless) + if(((trace.fill === 'tozeroy') || ((trace.fill === 'tonexty') && gd.firstscatter)) && + ((x[0] !== x[serieslen - 1]) || (y[0] !== y[serieslen - 1]))) { + yOptions.tozero = true; + } + + // tight y: any x fill + else if(['tonextx', 'tozerox'].indexOf(trace.fill) !== -1) { + yOptions.padded = false; + } + + Axes.expand(xa, x, xOptions); + Axes.expand(ya, y, yOptions); + + // create the "calculated data" to plot + var cd = new Array(serieslen); + for(i = 0; i < serieslen; i++) { + cd[i] = (isNumeric(x[i]) && isNumeric(y[i])) ? + {x: x[i], y: y[i]} : {x: BADNUM, y: BADNUM}; + + if(trace.ids) { + cd[i].id = String(trace.ids[i]); + } + } + + arraysToCalcdata(cd, trace); + + gd.firstscatter = false; + return cd; +}; diff --git a/src/traces/scatterregl/hover.js b/src/traces/scatterregl/hover.js index dc303c05b8d..c8a4720c113 100644 --- a/src/traces/scatterregl/hover.js +++ b/src/traces/scatterregl/hover.js @@ -12,6 +12,6 @@ function hover (pointData, xval, yval, hovermode) { pt = [xpx, ypx], hoveron = trace.hoveron || ''; - console.log(xpx, ypx, trace) + // console.log(xpx, ypx, trace) // console.log(pointData, xval, yval, hovermode) } diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 7ca669da941..7adbc38a998 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -15,5 +15,6 @@ var Scatter = extend({}, require('../scatter/index')) Scatter.name = 'scatterregl' Scatter.plot = require('./plot') Scatter.hoverPoints = require('./hover') +Scatter.calc = require('./calc') module.exports = Scatter; From 5cdb90b29df252069aa86b45488be87a1a266fc0 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 11 Jul 2017 11:26:41 -0400 Subject: [PATCH 005/151] Make O(logN) scatter2d hover --- package.json | 1 + src/traces/scatterregl/calc.js | 120 ++++---------------------------- src/traces/scatterregl/hover.js | 46 +++++++++++- src/traces/scatterregl/index.js | 2 +- src/traces/scatterregl/plot.js | 37 ++++++---- 5 files changed, 79 insertions(+), 127 deletions(-) diff --git a/package.json b/package.json index b1307713635..69f92d191a1 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "gl-shader": "4.2.0", "gl-spikes2d": "^1.0.1", "gl-surface3d": "^1.3.0", + "kdgrass": "^1.0.1", "mapbox-gl": "^0.22.0", "matrix-camera-controller": "^2.1.3", "mouse-change": "^1.4.0", diff --git a/src/traces/scatterregl/calc.js b/src/traces/scatterregl/calc.js index 1830656ba79..3af6b459707 100644 --- a/src/traces/scatterregl/calc.js +++ b/src/traces/scatterregl/calc.js @@ -9,121 +9,25 @@ 'use strict'; -var isNumeric = require('fast-isnumeric'); - -var Axes = require('../../plots/cartesian/axes'); -var BADNUM = require('../../constants/numerical').BADNUM; - -var subTypes = require('../scatter/subtypes'); -var calcColorscale = require('../scatter/colorscale_calc'); -var arraysToCalcdata = require('../scatter/arrays_to_calcdata'); - +var calcScatter = require('../scatter/calc') +var kdtree = require('kdgrass') module.exports = function calc(gd, trace) { - var xa = Axes.getFromId(gd, trace.xaxis || 'x'), - ya = Axes.getFromId(gd, trace.yaxis || 'y'); - - var x = xa.makeCalcdata(trace, 'x'), - y = ya.makeCalcdata(trace, 'y'); - - var serieslen = Math.min(x.length, y.length), - marker, - s, - i; - - // cancel minimum tick spacings (only applies to bars and boxes) - xa._minDtick = 0; - ya._minDtick = 0; + var cd = calcScatter(gd, trace) - if(x.length > serieslen) x.splice(serieslen, x.length - serieslen); - if(y.length > serieslen) y.splice(serieslen, y.length - serieslen); + //TODO: delegate this to webworker if possible - // check whether bounds should be tight, padded, extended to zero... - // most cases both should be padded on both ends, so start with that. - var xOptions = {padded: true}, - yOptions = {padded: true}; - - if(subTypes.hasMarkers(trace)) { - - // Treat size like x or y arrays --- Run d2c - // this needs to go before ppad computation - marker = trace.marker; - s = marker.size; - - if(Array.isArray(s)) { - // I tried auto-type but category and dates dont make much sense. - var ax = {type: 'linear'}; - Axes.setConvert(ax); - s = ax.makeCalcdata(trace.marker, 'size'); - if(s.length > serieslen) s.splice(serieslen, s.length - serieslen); - } - - var sizeref = 1.6 * (trace.marker.sizeref || 1), - markerTrans; - if(trace.marker.sizemode === 'area') { - markerTrans = function(v) { - return Math.max(Math.sqrt((v || 0) / sizeref), 3); - }; - } - else { - markerTrans = function(v) { - return Math.max((v || 0) / sizeref, 3); - }; - } - xOptions.ppad = yOptions.ppad = Array.isArray(s) ? - s.map(markerTrans) : markerTrans(s); + //FIXME: bench if it is faster than mapping to kdbush + var positions = Array(cd.length*2) + for (var i = 0, j = 0; i < positions.length; i+=2, j++) { + positions[i] = cd[j].x + positions[i+1] = cd[j].y } - calcColorscale(trace); - - // TODO: text size - - // include zero (tight) and extremes (padded) if fill to zero - // (unless the shape is closed, then it's just filling the shape regardless) - if(((trace.fill === 'tozerox') || - ((trace.fill === 'tonextx') && gd.firstscatter)) && - ((x[0] !== x[serieslen - 1]) || (y[0] !== y[serieslen - 1]))) { - xOptions.tozero = true; - } - - // if no error bars, markers or text, or fill to y=0 remove x padding - else if(!trace.error_y.visible && ( - ['tonexty', 'tozeroy'].indexOf(trace.fill) !== -1 || - (!subTypes.hasMarkers(trace) && !subTypes.hasText(trace)) - )) { - xOptions.padded = false; - xOptions.ppad = 0; - } - - // now check for y - rather different logic, though still mostly padded both ends - // include zero (tight) and extremes (padded) if fill to zero - // (unless the shape is closed, then it's just filling the shape regardless) - if(((trace.fill === 'tozeroy') || ((trace.fill === 'tonexty') && gd.firstscatter)) && - ((x[0] !== x[serieslen - 1]) || (y[0] !== y[serieslen - 1]))) { - yOptions.tozero = true; - } - - // tight y: any x fill - else if(['tonextx', 'tozerox'].indexOf(trace.fill) !== -1) { - yOptions.padded = false; - } - - Axes.expand(xa, x, xOptions); - Axes.expand(ya, y, yOptions); - - // create the "calculated data" to plot - var cd = new Array(serieslen); - for(i = 0; i < serieslen; i++) { - cd[i] = (isNumeric(x[i]) && isNumeric(y[i])) ? - {x: x[i], y: y[i]} : {x: BADNUM, y: BADNUM}; - - if(trace.ids) { - cd[i].id = String(trace.ids[i]); - } - } + var tree = kdtree(positions, 2) - arraysToCalcdata(cd, trace); + //FIXME: make sure it is a good place to store the tree + trace._tree = tree - gd.firstscatter = false; return cd; }; diff --git a/src/traces/scatterregl/hover.js b/src/traces/scatterregl/hover.js index c8a4720c113..187910c8b94 100644 --- a/src/traces/scatterregl/hover.js +++ b/src/traces/scatterregl/hover.js @@ -1,5 +1,9 @@ 'use strict' +var Lib = require('../../lib'); +var getTraceColor = require('../scatter/get_trace_color'); +var ErrorBars = require('../../components/errorbars'); + module.exports = hover function hover (pointData, xval, yval, hovermode) { @@ -10,8 +14,44 @@ function hover (pointData, xval, yval, hovermode) { xpx = xa.c2p(xval), ypx = ya.c2p(yval), pt = [xpx, ypx], - hoveron = trace.hoveron || ''; + hoveron = trace.hoveron || '', + tree = trace._tree + + if (!tree) return [pointData] + + //FIXME: use proper radius, pixel-size dependent + var ids = tree.within(xval, yval, 500) + + pointData.index = ids[0] + + if(pointData.index != null) { + // the closest data point + var di = cd[pointData.index], + xc = xa.c2p(di.x, true), + yc = ya.c2p(di.y, true), + rad = di.mrc || 1; + + 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 + }); + + if(di.htx) pointData.text = di.htx; + else if(trace.hovertext) pointData.text = trace.hovertext; + else if(di.tx) pointData.text = di.tx; + else if(trace.text) pointData.text = trace.text; + + ErrorBars.hoverInfo(di, trace, pointData); + + return [pointData]; + } - // console.log(xpx, ypx, trace) - // console.log(pointData, xval, yval, hovermode) + return [pointData] } diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 7adbc38a998..e19be9b5ead 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -14,7 +14,7 @@ var Scatter = extend({}, require('../scatter/index')) Scatter.name = 'scatterregl' Scatter.plot = require('./plot') -Scatter.hoverPoints = require('./hover') Scatter.calc = require('./calc') +Scatter.hoverPoints = require('./hover') module.exports = Scatter; diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 694de71c3c1..8dcc9bb4250 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -22,7 +22,7 @@ var getTraceColor = require('../scatter/get_trace_color'); var MARKER_SYMBOLS = require('../../constants/gl2d_markers'); var DASHES = require('../../constants/gl2d_dashes'); -// var createScatter = require('regl-scatter2d') +var createScatter = require('../../../../regl-scatter2d') function plot(container, data, cdscatter) { // console.log(container, data, cdscatter) @@ -36,34 +36,41 @@ function plot(container, data, cdscatter) { //FIXME: find proper way to get plot holder //FIXME: handle multiple subplots var subplotObj = layout._plots.xy - var scatter = subplotObj._scatterplot + var scatter = subplotObj._scatter2d //create regl-scatter, if not defined if (scatter === undefined) { //TODO: enhance picking - //TODO: create PR with questions - // if () - + //TODO: decide whether we should share canvas or create it every scatter plot + //TODO: decide if canvas should be the full-width with viewport or multiple instances //FIXME: avoid forcing absolute style by disabling forced plotly background var canvas = container.appendChild(document.createElement('canvas')) canvas.style.position = 'absolute'; canvas.style.transform = 'translate(' + xa._offset + 'px, ' + ya._offset + 'px)'; canvas.style.pointerEvents = 'none'; - canvas.width = 700; - canvas.height = 500; + canvas.width = xa._length; + canvas.height = ya._length; + + // scatter = subplotObj._scatter2d = {canvas: canvas} - scatter = subplotObj._scatterplot = {canvas: canvas} + scatter = subplotObj._scatter2d = createScatter({canvas: canvas}) } - var canvas = scatter.canvas - var ctx = canvas.getContext('2d') + //feed in positions + var positions = scatter({ + positions: positions + }) - ctx.clearRect(0, 0, canvas.width, canvas.height) - ctx.fillStyle = 'rgba(100,200,255,.8)'; - for (var i = 0, l = data.x.length; i < l; i++) { - ctx.fillRect(xa.c2p(data.x[i]),ya.c2p(data.y[i]),5,5) - } + // var canvas = scatter.canvas + // var ctx = canvas.getContext('2d') + + // ctx.clearRect(0, 0, canvas.width, canvas.height) + // ctx.fillStyle = 'rgba(100,200,255,.8)'; + + // for (var i = 0, l = data.x.length; i < l; i++) { + // ctx.fillRect(xa.c2p(data.x[i]),ya.c2p(data.y[i]),5,5) + // } return plot; } From 76f1d07809665a7c55831bb12a5cfa5ec87885dd Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 11 Jul 2017 12:00:14 -0400 Subject: [PATCH 006/151] Fix hover radius --- src/traces/scatterregl/hover.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/traces/scatterregl/hover.js b/src/traces/scatterregl/hover.js index 187910c8b94..d20c724b1af 100644 --- a/src/traces/scatterregl/hover.js +++ b/src/traces/scatterregl/hover.js @@ -1,8 +1,10 @@ 'use strict' var Lib = require('../../lib'); +var Fx = require('../../components/fx'); var getTraceColor = require('../scatter/get_trace_color'); var ErrorBars = require('../../components/errorbars'); +var MAXDIST = Fx.constants.MAXDIST; module.exports = hover @@ -19,8 +21,8 @@ function hover (pointData, xval, yval, hovermode) { if (!tree) return [pointData] - //FIXME: use proper radius, pixel-size dependent - var ids = tree.within(xval, yval, 500) + //FIXME: make sure this is a proper way to calc search radius + var ids = tree.within(xval, yval, MAXDIST / xa._m) pointData.index = ids[0] From a0d1e529b16f13295531dc44c5e49bfc0271eaf1 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 11 Jul 2017 15:50:46 -0400 Subject: [PATCH 007/151] Enhance hover point detection --- src/traces/scatterregl/hover.js | 11 ++++++++++- src/traces/scatterregl/plot.js | 10 +++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/traces/scatterregl/hover.js b/src/traces/scatterregl/hover.js index d20c724b1af..47c379df2bf 100644 --- a/src/traces/scatterregl/hover.js +++ b/src/traces/scatterregl/hover.js @@ -24,7 +24,16 @@ function hover (pointData, xval, yval, hovermode) { //FIXME: make sure this is a proper way to calc search radius var ids = tree.within(xval, yval, MAXDIST / xa._m) - pointData.index = ids[0] + //pick the id closest to the point + var min = MAXDIST, id = ids[0] + for (var i = 0; i < ids.length; i++) { + var pt = cd[ids[i]] + var dx = pt.x - xval, dy = pt.y - yval + var dist = Math.sqrt(dx*dx + dy*dy) + if (dist < min) id = ids[i] + } + + pointData.index = id if(pointData.index != null) { // the closest data point diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 8dcc9bb4250..f37c0b9257f 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -57,7 +57,15 @@ function plot(container, data, cdscatter) { } //feed in positions - var positions = scatter({ + var bounds = [xa._rl[0], ya._rl[0], xa._rl[1], ya._rl[1]] + var positions = Array(data.x.length*2) + for (var i = 0, l = data.x.length; i < l; i++) { + positions[i*2] = data.x[i] + positions[i*2+1] = data.y[i] + } + + scatter({ + range: bounds, positions: positions }) From 18127d7eb07d08bc4d0eb7d66109899e77a3cafd Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 11 Jul 2017 16:30:15 -0400 Subject: [PATCH 008/151] Enhance tree building --- src/traces/scatterregl/calc.js | 2 +- src/traces/scatterregl/hover.js | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/traces/scatterregl/calc.js b/src/traces/scatterregl/calc.js index 3af6b459707..ce007892ea5 100644 --- a/src/traces/scatterregl/calc.js +++ b/src/traces/scatterregl/calc.js @@ -24,7 +24,7 @@ module.exports = function calc(gd, trace) { positions[i+1] = cd[j].y } - var tree = kdtree(positions, 2) + var tree = kdtree(positions, 512) //FIXME: make sure it is a good place to store the tree trace._tree = tree diff --git a/src/traces/scatterregl/hover.js b/src/traces/scatterregl/hover.js index 47c379df2bf..5cc7239ca17 100644 --- a/src/traces/scatterregl/hover.js +++ b/src/traces/scatterregl/hover.js @@ -30,7 +30,10 @@ function hover (pointData, xval, yval, hovermode) { var pt = cd[ids[i]] var dx = pt.x - xval, dy = pt.y - yval var dist = Math.sqrt(dx*dx + dy*dy) - if (dist < min) id = ids[i] + if (dist < min) { + min = dist + id = ids[i] + } } pointData.index = id From f8a4c3ffe3b0a179c396f143c0881e9377facaf5 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 11 Jul 2017 17:00:44 -0400 Subject: [PATCH 009/151] Attempt to do drag callback --- src/plots/cartesian/dragbox.js | 5 ++++- src/traces/scatterregl/plot.js | 1 - 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 603e88d2f78..a56cf7a30ba 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -526,6 +526,10 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { } updateSubplots([x0, y0, pw - dx, ph - dy]); + + //FIXME: Etienne I need help with that, ideally we should do event emitter + if (plotinfo.ondrag) plotinfo.ondrag.call([x0, y0, pw-dx, ph-dy]) + ticksAndAnnotations(yActive, xActive); } @@ -708,7 +712,6 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { } for(i = 0; i < subplots.length; i++) { - var subplot = plotinfos[subplots[i]], xa2 = subplot.xaxis, ya2 = subplot.yaxis, diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index f37c0b9257f..4d759aacba8 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -52,7 +52,6 @@ function plot(container, data, cdscatter) { canvas.height = ya._length; // scatter = subplotObj._scatter2d = {canvas: canvas} - scatter = subplotObj._scatter2d = createScatter({canvas: canvas}) } From 08b854e98538f803401b4c2078c85b7cb58be337 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 13 Jul 2017 14:32:53 -0400 Subject: [PATCH 010/151] Add border color --- src/traces/scatterregl/plot.js | 67 +++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 4d759aacba8..319f53ac79c 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -21,16 +21,19 @@ 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 colormap = require('colormap') +var rgba = require('color-rgba') var createScatter = require('../../../../regl-scatter2d') function plot(container, data, cdscatter) { - // console.log(container, data, cdscatter) + // console.log(container, data, cd) var layout = container._fullLayout var data = container._fullData[0] var xa = layout.xaxis var ya = layout.yaxis + var cd = cdscatter[0] var container = container.querySelector('.gl-container') //FIXME: find proper way to get plot holder @@ -44,6 +47,7 @@ function plot(container, data, cdscatter) { //TODO: decide whether we should share canvas or create it every scatter plot //TODO: decide if canvas should be the full-width with viewport or multiple instances //FIXME: avoid forcing absolute style by disabling forced plotly background + //TODO: figure out if there is a way to detect only new passed options var canvas = container.appendChild(document.createElement('canvas')) canvas.style.position = 'absolute'; canvas.style.transform = 'translate(' + xa._offset + 'px, ' + ya._offset + 'px)'; @@ -55,30 +59,57 @@ function plot(container, data, cdscatter) { scatter = subplotObj._scatter2d = createScatter({canvas: canvas}) } - //feed in positions - var bounds = [xa._rl[0], ya._rl[0], xa._rl[1], ya._rl[1]] - var positions = Array(data.x.length*2) - for (var i = 0, l = data.x.length; i < l; i++) { - positions[i*2] = data.x[i] - positions[i*2+1] = data.y[i] + var len = cd.length + var marker = data.marker + var bounds = [xa._rl[0], ya._rl[0], xa._rl[1], ya._rl[1]]; + + // get positions + var positions = Array(cd.length*2); + for (var i = 0; i < len; i++) { + positions[i*2] = cd[i].x; + positions[i*2+1] = cd[i].y; + } + + // create colormap, if required + var markerPalette, markerColor = marker.color + if (marker.colorscale) { + var cmax = marker.cmax, cmin = marker.cmin, range = cmax - cmin + var cmap = [], cscale = marker.colorscale + for (var i = 0; i < cscale.length; i++) { + cmap.push({index: cscale[i][0], rgb: rgba(cscale[i][1], false).slice(0, 3)}) + } + //FIXME: making direct palette generator would be faster + markerPalette = colormap({ + colormap: cmap, + nshades: cmax - cmin, + format: 'rgbaString' + }) + + if (cmin !== 0) { + markerColor = marker.color.map(function (v) {return v - cmin}) + } } + // if (!Array.isArray(data.marker.size)) { + // size = markerSizeFunc(data.marker.size) + // } else { + // var sizes = data.marker.sizes + // size = [] + // for (var i = 0; i < sizes.length; i++) { + // size[i] = markerSizeFunc(sizes[i]) + // } + // } + + // redraw plot scatter({ + color: markerColor, + borderColor: marker.line && marker.line.color, + palette: markerPalette, + size: marker.size, range: bounds, positions: positions }) - - // var canvas = scatter.canvas - // var ctx = canvas.getContext('2d') - - // ctx.clearRect(0, 0, canvas.width, canvas.height) - // ctx.fillStyle = 'rgba(100,200,255,.8)'; - - // for (var i = 0, l = data.x.length; i < l; i++) { - // ctx.fillRect(xa.c2p(data.x[i]),ya.c2p(data.y[i]),5,5) - // } - return plot; } From eb1b1c0aa1fb533aaafa5e64feade98148ae2bbc Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 14 Jul 2017 17:51:55 -0400 Subject: [PATCH 011/151] Reuse scattergl convert --- src/traces/scatter/get_trace_color.js | 1 - src/traces/scattergl/convert.js | 1 - src/traces/scatterregl/hover.js | 3 - src/traces/scatterregl/plot.js | 550 +++++++++++++++++++++++--- 4 files changed, 497 insertions(+), 58 deletions(-) diff --git a/src/traces/scatter/get_trace_color.js b/src/traces/scatter/get_trace_color.js index cbf0708217c..bef495af0dd 100644 --- a/src/traces/scatter/get_trace_color.js +++ b/src/traces/scatter/get_trace_color.js @@ -17,7 +17,6 @@ module.exports = function getTraceColor(trace, di) { var lc, tc; // TODO: text modes - if(trace.mode === 'lines') { lc = trace.line.color; return (lc && Color.opacity(lc)) ? diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js index f99a4a73a62..536e7cdaf84 100644 --- a/src/traces/scattergl/convert.js +++ b/src/traces/scattergl/convert.js @@ -686,7 +686,6 @@ proto.updateLines = function(options, positions) { this.line.options.fill = [false, false, false, false]; break; } - var fillColor = str2RGBArray(options.fillcolor); this.line.options.color = lineColor; diff --git a/src/traces/scatterregl/hover.js b/src/traces/scatterregl/hover.js index 5cc7239ca17..b2869d9475f 100644 --- a/src/traces/scatterregl/hover.js +++ b/src/traces/scatterregl/hover.js @@ -61,10 +61,7 @@ function hover (pointData, xval, yval, hovermode) { else if(trace.hovertext) pointData.text = trace.hovertext; else if(di.tx) pointData.text = di.tx; else if(trace.text) pointData.text = trace.text; - ErrorBars.hoverInfo(di, trace, pointData); - - return [pointData]; } return [pointData] diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 319f53ac79c..88de6169be8 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -9,32 +9,38 @@ 'use strict'; +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 colormap = require('colormap') -var rgba = require('color-rgba') + +var AXES = ['xaxis', 'yaxis']; +var DESELECTDIM = 0.2; +var TRANSPARENT = [0, 0, 0, 0]; var createScatter = require('../../../../regl-scatter2d') -function plot(container, data, cdscatter) { - // console.log(container, data, cd) + +module.exports = createLineWithMarkers + + +function createLineWithMarkers(container, data, cdscatter) { var layout = container._fullLayout var data = container._fullData[0] var xa = layout.xaxis var ya = layout.yaxis var cd = cdscatter[0] - var container = container.querySelector('.gl-container') + var glContainer = container.querySelector('.gl-container') //FIXME: find proper way to get plot holder //FIXME: handle multiple subplots @@ -48,7 +54,7 @@ function plot(container, data, cdscatter) { //TODO: decide if canvas should be the full-width with viewport or multiple instances //FIXME: avoid forcing absolute style by disabling forced plotly background //TODO: figure out if there is a way to detect only new passed options - var canvas = container.appendChild(document.createElement('canvas')) + var canvas = glContainer.appendChild(document.createElement('canvas')) canvas.style.position = 'absolute'; canvas.style.transform = 'translate(' + xa._offset + 'px, ' + ya._offset + 'px)'; canvas.style.pointerEvents = 'none'; @@ -56,61 +62,499 @@ function plot(container, data, cdscatter) { canvas.height = ya._length; // scatter = subplotObj._scatter2d = {canvas: canvas} - scatter = subplotObj._scatter2d = createScatter({canvas: canvas}) + container.glContainer = glContainer + container.canvas = canvas + scatter = subplotObj._scatter2d = createScatterScene(container) + } + + scatter.update(data, cdscatter); + + return plot; +} + +function createScatterScene(container) { + if (!(this instanceof createScatterScene)) return new createScatterScene(container) + + this.container = container; + 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; + + var scatterOptions0 = { + positions: Array(), + sizes: [], + colors: [], + glyphs: [], + borderWidths: [], + borderColors: [], + size: 12, + color: [0, 0, 0, 1], + borderSize: 1, + borderColor: [0, 0, 0, 1], + snapPoints: true, + canvas: container.canvas + }; + + this.scatter = createScatter(scatterOptions0); + this.scatter.options = scatterOptions0 + this.scatter._trace = this + + return this +} + +var proto = createScatterScene.prototype; + + +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 { + this.updateFancy(options); + } + + // sort objects so that order is preserve on updates: + // - lines + // - errorX + // - errorY + // - markers + // this.container.glplot.objects.sort(function(a, b) { + // return a._index - b._index; + // }); - var len = cd.length - var marker = data.marker - var bounds = [xa._rl[0], ya._rl[0], xa._rl[1], ya._rl[1]]; + // set trace index so that scene2d can sort object per traces + this.index = options.index; - // get positions - var positions = Array(cd.length*2); - for (var i = 0; i < len; i++) { - positions[i*2] = cd[i].x; - positions[i*2+1] = cd[i].y; + // 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; } +}; + +proto.updateFancy = function(options) { + var container = this.container, + xaxis = container._fullLayout.xaxis, + yaxis = container._fullLayout.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(); - // create colormap, if required - var markerPalette, markerColor = marker.color - if (marker.colorscale) { - var cmax = marker.cmax, cmin = marker.cmin, range = cmax - cmin - var cmap = [], cscale = marker.colorscale - for (var i = 0; i < cscale.length; i++) { - cmap.push({index: cscale[i][0], rgb: rgba(cscale[i][1], false).slice(0, 3)}) + // get error values + var errorVals = ErrorBars.calcFromTrace(options, container._fullLayout); + + var len = x.length, + idToIndex = new Array(len), + positions = Array(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 = positions.slice(0, 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; } - //FIXME: making direct palette generator would be faster - markerPalette = colormap({ - colormap: cmap, - nshades: cmax - cmin, - format: 'rgbaString' - }) - - if (cmin !== 0) { - markerColor = marker.color.map(function (v) {return v - cmin}) + } + + 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); + this.scatter.options.borderColors = new Array(pId); + + 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] = 2.0 * size; + this.scatter.options.glyphs[i] = symbolSpec.unicode; + this.scatter.options.borderWidths[i] = 0.5 * ((bw > minBorderWidth) ? bw - minBorderWidth : 0); + + var optColors = this.scatter.options.colors + var dim = isDimmed ? DESELECTDIM : 1; + if (!optColors[i]) optColors[i] = [] + if(isOpen && !symbolSpec.noBorder && !symbolSpec.noFill) { + optColors[i][0] = TRANSPARENT[0]; + optColors[i][1] = TRANSPARENT[1]; + optColors[i][2] = TRANSPARENT[2]; + optColors[i][3] = TRANSPARENT[3]; + } else { + optColors[i][0] = _colors[4*index + 0] * 255; + optColors[i][1] = _colors[4*index + 1] * 255; + optColors[i][2] = _colors[4*index + 2] * 255; + optColors[i][3] = dim * _colors[4*index + 3] * 255; + } + if (!this.scatter.options.borderColors[i]) this.scatter.options.borderColors[i] = [] + this.scatter.options.borderColors[i][0] = _borderColors[4*index + 0] * 255; + this.scatter.options.borderColors[i][1] = _borderColors[4*index + 1] * 255; + this.scatter.options.borderColors[i][2] = _borderColors[4*index + 2] * 255; + this.scatter.options.borderColors[i][3] = dim * _borderColors[4*index + 3] * 255; } + + var bounds = [xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1]]; + this.scatter.options.range = bounds; + + // prevent scatter from resnapping points + this.scatter(this.scatter.options); } - // if (!Array.isArray(data.marker.size)) { - // size = markerSizeFunc(data.marker.size) - // } else { - // var sizes = data.marker.sizes - // size = [] - // for (var i = 0; i < sizes.length; i++) { - // size[i] = markerSizeFunc(sizes[i]) - // } - // } - - // redraw plot - scatter({ - color: markerColor, - borderColor: marker.line && marker.line.color, - palette: markerPalette, - size: marker.size, - range: bounds, - positions: positions - }) + // add item for autorange routine + this.expandAxesFancy(x, y, sizes); +}; - return plot; +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.container[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 container = this.container, + expandOpts = { padded: true, ppad: ppad }; + + Axes.expand(container._fullLayout.xaxis, x, expandOpts); + Axes.expand(container._fullLayout.yaxis, y, expandOpts); +}; + + + + +// 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 +// }; +// }; + + +// 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] === ''; +} + +// 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; } -module.exports = plot; From 34de32d27ea29619f6f927b5b1d1365f40c67b19 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 17 Jul 2017 13:52:12 -0400 Subject: [PATCH 012/151] Use proper border-size --- src/traces/scattergl/convert.js | 1 + src/traces/scatterregl/plot.js | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js index 536e7cdaf84..c9e56c55e54 100644 --- a/src/traces/scattergl/convert.js +++ b/src/traces/scattergl/convert.js @@ -584,6 +584,7 @@ proto.updateFancy = function(options) { var traceOpacity = options.opacity; var symbols = convertSymbol(markerOpts.symbol, len); var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); + console.log(colors.slice(0,4).map(v => v*255)) 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; diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 88de6169be8..a3fb2dd1259 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -69,7 +69,7 @@ function createLineWithMarkers(container, data, cdscatter) { scatter.update(data, cdscatter); - return plot; + return scatter } function createScatterScene(container) { @@ -103,7 +103,7 @@ function createScatterScene(container) { sizes: [], colors: [], glyphs: [], - borderWidths: [], + borderSizes: [], borderColors: [], size: 12, color: [0, 0, 0, 1], @@ -261,7 +261,7 @@ proto.updateFancy = function(options) { this.scatter.options.sizes = new Array(pId); this.scatter.options.glyphs = new Array(pId); - this.scatter.options.borderWidths = new Array(pId); + this.scatter.options.borderSizes = new Array(pId); this.scatter.options.colors = new Array(pId); this.scatter.options.borderColors = new Array(pId); @@ -271,7 +271,7 @@ proto.updateFancy = function(options) { 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 borderSizes = convertNumber(markerOpts.line.width, len); var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len); var index, size, symbol, symbolSpec, isOpen, isDimmed, _colors, _borderColors, bw, minBorderWidth; @@ -300,12 +300,12 @@ proto.updateFancy = function(options) { // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798 // for more info on this logic size = sizes[index]; - bw = borderWidths[index]; + bw = borderSizes[index]; minBorderWidth = (symbolSpec.noBorder || symbolSpec.noFill) ? 0.1 * size : 0; this.scatter.options.sizes[i] = 2.0 * size; this.scatter.options.glyphs[i] = symbolSpec.unicode; - this.scatter.options.borderWidths[i] = 0.5 * ((bw > minBorderWidth) ? bw - minBorderWidth : 0); + this.scatter.options.borderSizes[i] = 0.5 * ((bw > minBorderWidth) ? bw - minBorderWidth : 0); var optColors = this.scatter.options.colors var dim = isDimmed ? DESELECTDIM : 1; From 033f9b63d50eb3895bae238a5b5eba19cf63f819 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 17 Jul 2017 15:33:48 -0400 Subject: [PATCH 013/151] Take on multiple data --- package.json | 1 + src/traces/scattergl/convert.js | 1 - src/traces/scatterregl/plot.js | 43 ++++++++++++++++++++++++++------- 3 files changed, 35 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 69f92d191a1..5cbf0a7a8a1 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "3d-view": "^2.0.0", "@plotly/d3-sankey": "^0.5.0", "alpha-shape": "^1.0.0", + "canvas-fit": "^1.5.0", "color-rgba": "^1.1.0", "convex-hull": "^1.0.3", "country-regex": "^1.1.0", diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js index c9e56c55e54..536e7cdaf84 100644 --- a/src/traces/scattergl/convert.js +++ b/src/traces/scattergl/convert.js @@ -584,7 +584,6 @@ proto.updateFancy = function(options) { var traceOpacity = options.opacity; var symbols = convertSymbol(markerOpts.symbol, len); var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); - console.log(colors.slice(0,4).map(v => v*255)) 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; diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index a3fb2dd1259..6c68e19d8ea 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -22,6 +22,7 @@ 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 fit = require('canvas-fit') var AXES = ['xaxis', 'yaxis']; var DESELECTDIM = 0.2; @@ -34,12 +35,10 @@ var createScatter = require('../../../../regl-scatter2d') module.exports = createLineWithMarkers -function createLineWithMarkers(container, data, cdscatter) { +function createLineWithMarkers(container, plotinfo, cdscatter) { var layout = container._fullLayout - var data = container._fullData[0] var xa = layout.xaxis var ya = layout.yaxis - var cd = cdscatter[0] var glContainer = container.querySelector('.gl-container') //FIXME: find proper way to get plot holder @@ -55,19 +54,27 @@ function createLineWithMarkers(container, data, cdscatter) { //FIXME: avoid forcing absolute style by disabling forced plotly background //TODO: figure out if there is a way to detect only new passed options var canvas = glContainer.appendChild(document.createElement('canvas')) + + //FIXME: make sure this is the right place for that + glContainer.style.height = '100%'; + glContainer.style.width = '100%'; + canvas.style.position = 'absolute'; - canvas.style.transform = 'translate(' + xa._offset + 'px, ' + ya._offset + 'px)'; + canvas.style.top = '0px'; + canvas.style.left = '0px'; canvas.style.pointerEvents = 'none'; - canvas.width = xa._length; - canvas.height = ya._length; - // scatter = subplotObj._scatter2d = {canvas: canvas} + //TODO: fit canvas every window.resize or plotly.resize or whatever + fit(canvas, glContainer); + container.glContainer = glContainer container.canvas = canvas scatter = subplotObj._scatter2d = createScatterScene(container) } - scatter.update(data, cdscatter); + container._fullData.forEach(function(data, i) { + scatter.update(data, cdscatter[i]); + }) return scatter } @@ -76,7 +83,7 @@ function createScatterScene(container) { if (!(this instanceof createScatterScene)) return new createScatterScene(container) this.container = container; - this.type = 'scattergl'; + this.type = 'scatterregl'; this.pickXData = []; this.pickYData = []; @@ -98,6 +105,9 @@ function createScatterScene(container) { this.hasErrorY = false; this.hasMarkers = false; + this.xaxis = container._fullLayout.xaxis + this.yaxis = container._fullLayout.yaxis + var scatterOptions0 = { positions: Array(), sizes: [], @@ -117,6 +127,7 @@ function createScatterScene(container) { this.scatter.options = scatterOptions0 this.scatter._trace = this + return this } @@ -328,6 +339,20 @@ proto.updateFancy = function(options) { this.scatter.options.borderColors[i][3] = dim * _borderColors[4*index + 3] * 255; } + + var size = container._fullLayout._size, + domainX = container._fullLayout.xaxis.domain, + domainY = container._fullLayout.yaxis.domain, + width = container._fullLayout.width, + height = container._fullLayout.height; + + var viewBox = [ + size.l + domainX[0] * size.w, + size.b + domainY[0] * size.h, + (width - size.r) - (1 - domainX[1]) * size.w, + (height - size.t) - (1 - domainY[1]) * size.h + ]; + var bounds = [xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1]]; this.scatter.options.range = bounds; From 4c4df5e52dc6488bdd1d8df9de45dd3dc26ff62d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 20 Jul 2017 14:32:47 -0400 Subject: [PATCH 014/151] Show generic SVG markers --- src/fonts/ploticon/config.json | 4 +-- src/traces/scatterregl/plot.js | 47 ++++++++++++++++++---------------- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/fonts/ploticon/config.json b/src/fonts/ploticon/config.json index 851669be315..70be69f3e79 100644 --- a/src/fonts/ploticon/config.json +++ b/src/fonts/ploticon/config.json @@ -87,7 +87,7 @@ "width": 1500 }, "search": [ - "tooltip_basic" + "tooltip_basic" ] }, { @@ -1513,4 +1513,4 @@ "src": "fontawesome" } ] -} \ No newline at end of file +} diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 6c68e19d8ea..e7cdcdfdab3 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -21,6 +21,7 @@ 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 MARKER_SVG_SYMBOLS = require('../../components/drawing/symbol_defs'); var DASHES = require('../../constants/gl2d_dashes'); var fit = require('canvas-fit') @@ -112,7 +113,7 @@ function createScatterScene(container) { positions: Array(), sizes: [], colors: [], - glyphs: [], + markers: [], borderSizes: [], borderColors: [], size: 12, @@ -271,7 +272,7 @@ proto.updateFancy = function(options) { // 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.markers = new Array(pId); this.scatter.options.borderSizes = new Array(pId); this.scatter.options.colors = new Array(pId); this.scatter.options.borderColors = new Array(pId); @@ -280,7 +281,8 @@ proto.updateFancy = function(options) { var markerOpts = options.marker; var markerOpacity = markerOpts.opacity; var traceOpacity = options.opacity; - var symbols = convertSymbol(markerOpts.symbol, len); + + var symbols = markerOpts.symbol//convertSymbol(markerOpts.symbol, len); var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); var borderSizes = convertNumber(markerOpts.line.width, len); var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len); @@ -292,46 +294,47 @@ proto.updateFancy = function(options) { index = idToIndex[i]; symbol = symbols[index]; - symbolSpec = MARKER_SYMBOLS[symbol]; + symbolSpec = MARKER_SVG_SYMBOLS[symbol] || {}; isOpen = isSymbolOpen(symbol); isDimmed = selIds && !selIds[index]; - if(symbolSpec.noBorder && !isOpen) { - _colors = borderColors; - } else { + // if(symbolSpec.noBorder && !isOpen) { + // _colors = borderColors; + // } else { _colors = colors; - } + // } - if(isOpen) { - _borderColors = colors; - } else { + // 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 = borderSizes[index]; - minBorderWidth = (symbolSpec.noBorder || symbolSpec.noFill) ? 0.1 * size : 0; + // minBorderWidth = (symbolSpec.noBorder || symbolSpec.noFill) ? 0.1 * size : 0; + minBorderWidth = 0 - this.scatter.options.sizes[i] = 2.0 * size; - this.scatter.options.glyphs[i] = symbolSpec.unicode; + this.scatter.options.sizes[i] = 3.0 * size; + this.scatter.options.markers[i] = symbolSpec.f && symbolSpec.f(40) || null; this.scatter.options.borderSizes[i] = 0.5 * ((bw > minBorderWidth) ? bw - minBorderWidth : 0); var optColors = this.scatter.options.colors var dim = isDimmed ? DESELECTDIM : 1; if (!optColors[i]) optColors[i] = [] - if(isOpen && !symbolSpec.noBorder && !symbolSpec.noFill) { - optColors[i][0] = TRANSPARENT[0]; - optColors[i][1] = TRANSPARENT[1]; - optColors[i][2] = TRANSPARENT[2]; - optColors[i][3] = TRANSPARENT[3]; - } else { + // if(isOpen && !symbolSpec.noBorder && !symbolSpec.noFill) { + // optColors[i][0] = TRANSPARENT[0]; + // optColors[i][1] = TRANSPARENT[1]; + // optColors[i][2] = TRANSPARENT[2]; + // optColors[i][3] = TRANSPARENT[3]; + // } else { optColors[i][0] = _colors[4*index + 0] * 255; optColors[i][1] = _colors[4*index + 1] * 255; optColors[i][2] = _colors[4*index + 2] * 255; optColors[i][3] = dim * _colors[4*index + 3] * 255; - } + // } if (!this.scatter.options.borderColors[i]) this.scatter.options.borderColors[i] = [] this.scatter.options.borderColors[i][0] = _borderColors[4*index + 0] * 255; this.scatter.options.borderColors[i][1] = _borderColors[4*index + 1] * 255; From c245aa513386f571758a1a6f47497b334f6ce01e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 20 Jul 2017 14:36:24 -0400 Subject: [PATCH 015/151] Handle open symbols better --- src/traces/scatterregl/plot.js | 35 +++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index e7cdcdfdab3..6363910024e 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -286,7 +286,7 @@ proto.updateFancy = function(options) { var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); var borderSizes = convertNumber(markerOpts.line.width, len); var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len); - var index, size, symbol, symbolSpec, isOpen, isDimmed, _colors, _borderColors, bw, minBorderWidth; + var index, size, symbol, symbolName, symbolSpec, isOpen, isDimmed, _colors, _borderColors, bw, minBorderWidth; sizes = convertArray(markerSizeFunc, markerOpts.size, len); @@ -294,21 +294,22 @@ proto.updateFancy = function(options) { index = idToIndex[i]; symbol = symbols[index]; - symbolSpec = MARKER_SVG_SYMBOLS[symbol] || {}; + symbolName = symbol.split(/-open|-dot/)[0] + symbolSpec = MARKER_SVG_SYMBOLS[symbolName] || {}; isOpen = isSymbolOpen(symbol); isDimmed = selIds && !selIds[index]; - // if(symbolSpec.noBorder && !isOpen) { - // _colors = borderColors; - // } else { + if(symbolSpec.noBorder && !isOpen) { + _colors = borderColors; + } else { _colors = colors; - // } + } - // if(isOpen) { - // _borderColors = colors; - // } else { + if(isOpen) { + _borderColors = colors; + } else { _borderColors = borderColors; - // } + } // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798 // for more info on this logic @@ -324,17 +325,17 @@ proto.updateFancy = function(options) { var optColors = this.scatter.options.colors var dim = isDimmed ? DESELECTDIM : 1; if (!optColors[i]) optColors[i] = [] - // if(isOpen && !symbolSpec.noBorder && !symbolSpec.noFill) { - // optColors[i][0] = TRANSPARENT[0]; - // optColors[i][1] = TRANSPARENT[1]; - // optColors[i][2] = TRANSPARENT[2]; - // optColors[i][3] = TRANSPARENT[3]; - // } else { + if(isOpen && !symbolSpec.noBorder && !symbolSpec.noFill) { + optColors[i][0] = TRANSPARENT[0]; + optColors[i][1] = TRANSPARENT[1]; + optColors[i][2] = TRANSPARENT[2]; + optColors[i][3] = TRANSPARENT[3]; + } else { optColors[i][0] = _colors[4*index + 0] * 255; optColors[i][1] = _colors[4*index + 1] * 255; optColors[i][2] = _colors[4*index + 2] * 255; optColors[i][3] = dim * _colors[4*index + 3] * 255; - // } + } if (!this.scatter.options.borderColors[i]) this.scatter.options.borderColors[i] = [] this.scatter.options.borderColors[i][0] = _borderColors[4*index + 0] * 255; this.scatter.options.borderColors[i][1] = _borderColors[4*index + 1] * 255; From f8f8af467fc35db70af58859625e95008b33d437 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 26 Jul 2017 03:09:49 -0400 Subject: [PATCH 016/151] Enhance open detection --- src/traces/scatterregl/plot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 6363910024e..06f2fca0afd 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -561,7 +561,7 @@ function _convertColor(colors, opacities, count) { } function isSymbolOpen(symbol) { - return symbol.split('-open')[1] === ''; + return /-open/.test(symbol); } // We'd ideally know that all values are of fast types; sampling gives no certainty but faster From 9d73b00e5d321e08fb002f4b52561cf527d1d566 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 22 Aug 2017 11:40:55 +0300 Subject: [PATCH 017/151] Fix scales --- src/traces/scattergl/attributes.js | 10 +------ src/traces/scattergl/convert.js | 3 ++- src/traces/scatterregl/plot.js | 43 ++++++++++++++++++++++++------ 3 files changed, 38 insertions(+), 18 deletions(-) diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js index 637b6812338..a0e1347551d 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 extendDeep = require('../../lib/extend').extendDeep; @@ -58,14 +57,7 @@ module.exports = { } }, marker: extendDeep({}, 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, diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js index 536e7cdaf84..a1c42a2eac7 100644 --- a/src/traces/scattergl/convert.js +++ b/src/traces/scattergl/convert.js @@ -102,6 +102,7 @@ function LineWithMarkers(scene, uid) { 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); } @@ -130,7 +131,6 @@ proto.initObject = function(createFn, options, objIndex) { function dispose() { if(obj) obj.dispose(); } - return { options: options, update: update, @@ -617,6 +617,7 @@ proto.updateFancy = function(options) { 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); diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 06f2fca0afd..e96d1c4561d 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -20,16 +20,17 @@ 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 MARKER_SVG_SYMBOLS = require('../../components/drawing/symbol_defs'); var DASHES = require('../../constants/gl2d_dashes'); var fit = require('canvas-fit') +var createScatter = require('../../../../regl-scatter2d') +var createLine = require('../../../../regl-line2d') +var Drawing = require('../../components/drawing'); var AXES = ['xaxis', 'yaxis']; var DESELECTDIM = 0.2; var TRANSPARENT = [0, 0, 0, 0]; -var createScatter = require('../../../../regl-scatter2d') @@ -121,7 +122,8 @@ function createScatterScene(container) { borderSize: 1, borderColor: [0, 0, 0, 1], snapPoints: true, - canvas: container.canvas + canvas: container.canvas, + pixelRatio: container._context.plotGlPixelRatio || window.devicePixelRatio }; this.scatter = createScatter(scatterOptions0); @@ -129,6 +131,20 @@ function createScatterScene(container) { this.scatter._trace = this + this.line = 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); + + return this } @@ -252,7 +268,7 @@ proto.updateFancy = function(options) { positions = positions.slice(0, ptr); this.idToIndex = idToIndex; - // this.updateLines(options, positions); + this.updateLines(options, positions); // this.updateError('X', options, positions, errorsX); // this.updateError('Y', options, positions, errorsY); @@ -278,11 +294,13 @@ proto.updateFancy = function(options) { this.scatter.options.borderColors = new Array(pId); var markerSizeFunc = makeBubbleSizeFn(options); + var markerOpts = options.marker; var markerOpacity = markerOpts.opacity; var traceOpacity = options.opacity; - var symbols = markerOpts.symbol//convertSymbol(markerOpts.symbol, len); + var symbols = convertSymbol(markerOpts.symbol, len); + var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); var borderSizes = convertNumber(markerOpts.line.width, len); var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len); @@ -296,6 +314,7 @@ proto.updateFancy = function(options) { symbol = symbols[index]; symbolName = symbol.split(/-open|-dot/)[0] symbolSpec = MARKER_SVG_SYMBOLS[symbolName] || {}; + isOpen = isSymbolOpen(symbol); isDimmed = selIds && !selIds[index]; @@ -318,8 +337,15 @@ proto.updateFancy = function(options) { // minBorderWidth = (symbolSpec.noBorder || symbolSpec.noFill) ? 0.1 * size : 0; minBorderWidth = 0 - this.scatter.options.sizes[i] = 3.0 * size; - this.scatter.options.markers[i] = symbolSpec.f && symbolSpec.f(40) || null; + this.scatter.options.sizes[i] = 2.0 * size; + + //FIXME: add better handler here + if (symbolName === 'circle') { + this.scatter.options.markers[i] = null; + } + else { + this.scatter.options.markers[i] = symbolSpec.f && symbolSpec.f(40) || null; + } this.scatter.options.borderSizes[i] = 0.5 * ((bw > minBorderWidth) ? bw - minBorderWidth : 0); var optColors = this.scatter.options.colors @@ -359,6 +385,7 @@ proto.updateFancy = function(options) { var bounds = [xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1]]; this.scatter.options.range = bounds; + this.scatter.options.viewport = viewBox // prevent scatter from resnapping points this.scatter(this.scatter.options); @@ -523,7 +550,7 @@ function _convertArray(convert, data, count) { 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'; + return MARKER_SVG_SYMBOLS[x] ? x : 'circle'; }); function convertColor(color, opacity, count) { From 4c7e321f500c1989f48787b5896042832f253ed2 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 24 Aug 2017 02:42:53 +0300 Subject: [PATCH 018/151] Generate svg sdf --- package.json | 1 + src/traces/scatterregl/plot.js | 78 +++++++++++++++++++++------------- 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/package.json b/package.json index 5cbf0a7a8a1..5b0487a1eba 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "sane-topojson": "^2.0.0", "strongly-connected-components": "^1.0.1", "superscript-text": "^1.0.0", + "svg-path-sdf": "^1.0.0", "tinycolor2": "^1.3.0", "topojson-client": "^2.1.0", "webgl-context": "^2.2.0", diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index e96d1c4561d..e488816e10f 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -20,18 +20,21 @@ 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_SVG_SYMBOLS = require('../../components/drawing/symbol_defs'); var DASHES = require('../../constants/gl2d_dashes'); var fit = require('canvas-fit') var createScatter = require('../../../../regl-scatter2d') var createLine = require('../../../../regl-line2d') var Drawing = require('../../components/drawing'); +var MARKER_SVG_SYMBOLS = require('../../components/drawing/symbol_defs'); +var svgSdf = require('svg-path-sdf') var AXES = ['xaxis', 'yaxis']; var DESELECTDIM = 0.2; var TRANSPARENT = [0, 0, 0, 0]; - +// tables with symbol SDF values +var SYMBOL_SDF_SIZE = 100 +var SYMBOL_SDF = {} module.exports = createLineWithMarkers @@ -131,18 +134,18 @@ function createScatterScene(container) { this.scatter._trace = this - this.line = 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.line = 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); return this @@ -174,11 +177,11 @@ proto.update = function(options, cdscatter) { this.connectgaps = !!options.connectgaps; if(!this.isVisible) { - this.line.clear(); - this.errorX.clear(); - this.errorY.clear(); - this.scatter.clear(); - this.fancyScatter.clear(); + // this.line.clear(); + // this.errorX.clear(); + // this.errorY.clear(); + // this.scatter(); + // this.fancyScatter.clear(); } else { this.updateFancy(options); @@ -268,7 +271,7 @@ proto.updateFancy = function(options) { positions = positions.slice(0, ptr); this.idToIndex = idToIndex; - this.updateLines(options, positions); + // this.updateLines(options, positions); // this.updateError('X', options, positions, errorsX); // this.updateError('Y', options, positions, errorsY); @@ -304,21 +307,24 @@ proto.updateFancy = function(options) { var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); var borderSizes = convertNumber(markerOpts.line.width, len); var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len); - var index, size, symbol, symbolName, symbolSpec, isOpen, isDimmed, _colors, _borderColors, bw, minBorderWidth; + var index, size, symbol, symbolName, symbolSpec, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, minBorderWidth, noBorder, symbolFunc, noDot, symbolSdf; sizes = convertArray(markerSizeFunc, markerOpts.size, len); for(i = 0; i < pId; ++i) { index = idToIndex[i]; - symbol = symbols[index]; - symbolName = symbol.split(/-open|-dot/)[0] - symbolSpec = MARKER_SVG_SYMBOLS[symbolName] || {}; + symbolNumber = Drawing.symbolNumber(symbol) + symbolName = Drawing.symbolNames[symbolNumber % 100] + symbolFunc = Drawing.symbolFuncs[symbolNumber % 100] + noBorder = !!Drawing.symbolNeedLines[symbolNumber % 100] + noDot = !!Drawing.symbolNoDot[symbolNumber % 100] isOpen = isSymbolOpen(symbol); isDimmed = selIds && !selIds[index]; - if(symbolSpec.noBorder && !isOpen) { + + if(noBorder && !isOpen) { _colors = borderColors; } else { _colors = colors; @@ -334,24 +340,36 @@ proto.updateFancy = function(options) { // for more info on this logic size = sizes[index]; bw = borderSizes[index]; - // minBorderWidth = (symbolSpec.noBorder || symbolSpec.noFill) ? 0.1 * size : 0; + // minBorderWidth = (noBorder) ? 0.1 * size : 0; minBorderWidth = 0 - this.scatter.options.sizes[i] = 2.0 * size; + this.scatter.options.sizes[i] = size; + - //FIXME: add better handler here if (symbolName === 'circle') { this.scatter.options.markers[i] = null; } else { - this.scatter.options.markers[i] = symbolSpec.f && symbolSpec.f(40) || null; + //get symbol sdf from cache or generate it + if (SYMBOL_SDF[symbolName]) { + symbolSdf = SYMBOL_SDF[symbolName] + } else { + symbolSdf = svgSdf(symbolFunc(10), { + w: SYMBOL_SDF_SIZE, + h: SYMBOL_SDF_SIZE, + viewBox: [-10,-10,10,10] + }) + SYMBOL_SDF[symbolName] = symbolSdf + } + + this.scatter.options.markers[i] = symbolSdf || null; } this.scatter.options.borderSizes[i] = 0.5 * ((bw > minBorderWidth) ? bw - minBorderWidth : 0); var optColors = this.scatter.options.colors var dim = isDimmed ? DESELECTDIM : 1; if (!optColors[i]) optColors[i] = [] - if(isOpen && !symbolSpec.noBorder && !symbolSpec.noFill) { + if(isOpen && !noBorder) { optColors[i][0] = TRANSPARENT[0]; optColors[i][1] = TRANSPARENT[1]; optColors[i][2] = TRANSPARENT[2]; From f85e8b535835366f0a350961cf15574b6d80c1ec Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 24 Aug 2017 14:06:57 +0300 Subject: [PATCH 019/151] Make scatterregl cover all svg markers --- src/components/drawing/index.js | 4 ++ src/components/drawing/symbol_defs.js | 36 ++++++++++------ src/traces/scatterregl/plot.js | 60 ++++++++++++++------------- 3 files changed, 60 insertions(+), 40 deletions(-) diff --git a/src/components/drawing/index.js b/src/components/drawing/index.js index e234c5d2a68..fa08ad1a63c 100644 --- a/src/components/drawing/index.js +++ b/src/components/drawing/index.js @@ -195,6 +195,7 @@ drawing.symbolNames = []; drawing.symbolFuncs = []; drawing.symbolNeedLines = {}; drawing.symbolNoDot = {}; +drawing.symbolNoFill = {}; drawing.symbolList = []; Object.keys(SYMBOLDEFS).forEach(function(k) { @@ -213,6 +214,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/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index e488816e10f..5978de5f01c 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -26,15 +26,18 @@ var createScatter = require('../../../../regl-scatter2d') var createLine = require('../../../../regl-line2d') var Drawing = require('../../components/drawing'); var MARKER_SVG_SYMBOLS = require('../../components/drawing/symbol_defs'); -var svgSdf = require('svg-path-sdf') +var svgSdf = require('../../../../svg-path-sdf') var AXES = ['xaxis', 'yaxis']; var DESELECTDIM = 0.2; var TRANSPARENT = [0, 0, 0, 0]; // tables with symbol SDF values -var SYMBOL_SDF_SIZE = 100 +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 * .05) module.exports = createLineWithMarkers @@ -301,13 +304,12 @@ proto.updateFancy = function(options) { var markerOpts = options.marker; var markerOpacity = markerOpts.opacity; var traceOpacity = options.opacity; - - var symbols = convertSymbol(markerOpts.symbol, len); + var symbols = markerOpts.symbol; var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); var borderSizes = convertNumber(markerOpts.line.width, len); var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len); - var index, size, symbol, symbolName, symbolSpec, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, minBorderWidth, noBorder, symbolFunc, noDot, symbolSdf; + var index, size, symbol, symbolName, symbolSpec, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, minBorderWidth, symbolNeedLine, symbolFunc, symbolNoDot, symbolSdf, symbolNoFill, symbolPath, isDot; sizes = convertArray(markerSizeFunc, markerOpts.size, len); @@ -317,18 +319,19 @@ proto.updateFancy = function(options) { symbolNumber = Drawing.symbolNumber(symbol) symbolName = Drawing.symbolNames[symbolNumber % 100] symbolFunc = Drawing.symbolFuncs[symbolNumber % 100] - noBorder = !!Drawing.symbolNeedLines[symbolNumber % 100] - noDot = !!Drawing.symbolNoDot[symbolNumber % 100] + symbolNeedLine = !!Drawing.symbolNeedLines[symbolNumber % 100] + symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100] + symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100] - isOpen = isSymbolOpen(symbol); + isOpen = /-open/.test(symbol); + isDot = /-dot/.test(symbol); isDimmed = selIds && !selIds[index]; - - if(noBorder && !isOpen) { - _colors = borderColors; - } else { + // if(symbolNeedLine && !isOpen) { + // _colors = borderColors; + // } else { _colors = colors; - } + // } if(isOpen) { _borderColors = colors; @@ -340,26 +343,34 @@ proto.updateFancy = function(options) { // for more info on this logic size = sizes[index]; bw = borderSizes[index]; - // minBorderWidth = (noBorder) ? 0.1 * size : 0; + // minBorderWidth = (symbolNeedLine) ? 0.1 * size : 0; minBorderWidth = 0 this.scatter.options.sizes[i] = size; - if (symbolName === 'circle') { + if (symbol === 'circle') { this.scatter.options.markers[i] = null; } else { //get symbol sdf from cache or generate it - if (SYMBOL_SDF[symbolName]) { - symbolSdf = SYMBOL_SDF[symbolName] + if (SYMBOL_SDF[symbol]) { + symbolSdf = SYMBOL_SDF[symbol] } else { - symbolSdf = svgSdf(symbolFunc(10), { + 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: [-10,-10,10,10] + viewBox: [-SYMBOL_SIZE,-SYMBOL_SIZE,SYMBOL_SIZE,SYMBOL_SIZE], + stroke: symbolNoFill ? SYMBOL_STROKE : -SYMBOL_STROKE }) - SYMBOL_SDF[symbolName] = symbolSdf + SYMBOL_SDF[symbol] = symbolSdf } this.scatter.options.markers[i] = symbolSdf || null; @@ -369,7 +380,7 @@ proto.updateFancy = function(options) { var optColors = this.scatter.options.colors var dim = isDimmed ? DESELECTDIM : 1; if (!optColors[i]) optColors[i] = [] - if(isOpen && !noBorder) { + if(isOpen || symbolNoFill) { optColors[i][0] = TRANSPARENT[0]; optColors[i][1] = TRANSPARENT[1]; optColors[i][2] = TRANSPARENT[2]; @@ -567,9 +578,6 @@ function _convertArray(convert, data, count) { var convertNumber = convertArray.bind(null, function(x) { return +x; }); var convertColorBase = convertArray.bind(null, str2RGBArray); -var convertSymbol = convertArray.bind(null, function(x) { - return MARKER_SVG_SYMBOLS[x] ? x : 'circle'; -}); function convertColor(color, opacity, count) { return _convertColor( @@ -605,10 +613,6 @@ function _convertColor(colors, opacities, count) { return result; } -function isSymbolOpen(symbol) { - return /-open/.test(symbol); -} - // 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; From a9e314c8be337a590cb949bcb0b53a01494c47ba Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 24 Aug 2017 17:59:20 +0300 Subject: [PATCH 020/151] Lintify --- src/plots/cartesian/dragbox.js | 4 +- src/traces/scatterregl/calc.js | 24 +-- src/traces/scatterregl/hover.js | 42 +++--- src/traces/scatterregl/index.js | 12 +- src/traces/scatterregl/plot.js | 250 ++++++++++++-------------------- 5 files changed, 135 insertions(+), 197 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index a56cf7a30ba..2441c687ba3 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -527,8 +527,8 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { updateSubplots([x0, y0, pw - dx, ph - dy]); - //FIXME: Etienne I need help with that, ideally we should do event emitter - if (plotinfo.ondrag) plotinfo.ondrag.call([x0, y0, pw-dx, ph-dy]) + // FIXME: Etienne I need help with that, ideally we should do event emitter + if(plotinfo.ondrag) plotinfo.ondrag.call([x0, y0, pw - dx, ph - dy]); ticksAndAnnotations(yActive, xActive); } diff --git a/src/traces/scatterregl/calc.js b/src/traces/scatterregl/calc.js index ce007892ea5..3a1cfad7c22 100644 --- a/src/traces/scatterregl/calc.js +++ b/src/traces/scatterregl/calc.js @@ -9,25 +9,25 @@ 'use strict'; -var calcScatter = require('../scatter/calc') -var kdtree = require('kdgrass') +var calcScatter = require('../scatter/calc'); +var kdtree = require('kdgrass'); module.exports = function calc(gd, trace) { - var cd = calcScatter(gd, trace) + var cd = calcScatter(gd, trace); - //TODO: delegate this to webworker if possible + // TODO: delegate this to webworker if possible - //FIXME: bench if it is faster than mapping to kdbush - var positions = Array(cd.length*2) - for (var i = 0, j = 0; i < positions.length; i+=2, j++) { - positions[i] = cd[j].x - positions[i+1] = cd[j].y + // FIXME: bench if it is faster than mapping to kdbush + var positions = Array(cd.length * 2); + for(var i = 0, j = 0; i < positions.length; i += 2, j++) { + positions[i] = cd[j].x; + positions[i + 1] = cd[j].y; } - var tree = kdtree(positions, 512) + var tree = kdtree(positions, 512); - //FIXME: make sure it is a good place to store the tree - trace._tree = tree + // FIXME: make sure it is a good place to store the tree + trace._tree = tree; return cd; }; diff --git a/src/traces/scatterregl/hover.js b/src/traces/scatterregl/hover.js index b2869d9475f..3e87f6b10d4 100644 --- a/src/traces/scatterregl/hover.js +++ b/src/traces/scatterregl/hover.js @@ -1,4 +1,4 @@ -'use strict' +'use strict'; var Lib = require('../../lib'); var Fx = require('../../components/fx'); @@ -6,39 +6,39 @@ var getTraceColor = require('../scatter/get_trace_color'); var ErrorBars = require('../../components/errorbars'); var MAXDIST = Fx.constants.MAXDIST; -module.exports = hover +module.exports = hover; -function hover (pointData, xval, yval, hovermode) { - var cd = pointData.cd, +function hover(pointData, xval, yval) { + var cd = pointData.cd, trace = cd[0].trace, xa = pointData.xa, ya = pointData.ya, xpx = xa.c2p(xval), ypx = ya.c2p(yval), pt = [xpx, ypx], - hoveron = trace.hoveron || '', - tree = trace._tree + // hoveron = trace.hoveron || '', + tree = trace._tree; - if (!tree) return [pointData] + if(!tree) return [pointData]; - //FIXME: make sure this is a proper way to calc search radius - var ids = tree.within(xval, yval, MAXDIST / xa._m) + // FIXME: make sure this is a proper way to calc search radius + var ids = tree.within(xval, yval, MAXDIST / xa._m); - //pick the id closest to the point - var min = MAXDIST, id = ids[0] - for (var i = 0; i < ids.length; i++) { - var pt = cd[ids[i]] - var dx = pt.x - xval, dy = pt.y - yval - var dist = Math.sqrt(dx*dx + dy*dy) - if (dist < min) { - min = dist - id = ids[i] + // pick the id closest to the point + var min = MAXDIST, id = ids[0]; + for(var i = 0; i < ids.length; i++) { + pt = cd[ids[i]]; + var dx = pt.x - xval, dy = pt.y - yval; + var dist = Math.sqrt(dx * dx + dy * dy); + if(dist < min) { + min = dist; + id = ids[i]; } } - pointData.index = id + pointData.index = id; - if(pointData.index != null) { + if(pointData.index !== undefined) { // the closest data point var di = cd[pointData.index], xc = xa.c2p(di.x, true), @@ -64,5 +64,5 @@ function hover (pointData, xval, yval, hovermode) { ErrorBars.hoverInfo(di, trace, pointData); } - return [pointData] + return [pointData]; } diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index e19be9b5ead..b1b223b7b1f 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -8,13 +8,13 @@ 'use strict'; -var extend = require('object-assign') +var extend = require('object-assign'); -var Scatter = extend({}, require('../scatter/index')) +var Scatter = extend({}, require('../scatter/index')); -Scatter.name = 'scatterregl' -Scatter.plot = require('./plot') -Scatter.calc = require('./calc') -Scatter.hoverPoints = require('./hover') +Scatter.name = 'scatterregl'; +Scatter.plot = require('./plot'); +Scatter.calc = require('./calc'); +Scatter.hoverPoints = require('./hover'); module.exports = Scatter; diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 5978de5f01c..f23b1363ef1 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -9,11 +9,8 @@ 'use strict'; -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 formatColor = require('../../lib/gl_format_color'); @@ -21,49 +18,47 @@ var subTypes = require('../scatter/subtypes'); var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); var getTraceColor = require('../scatter/get_trace_color'); var DASHES = require('../../constants/gl2d_dashes'); -var fit = require('canvas-fit') -var createScatter = require('../../../../regl-scatter2d') -var createLine = require('../../../../regl-line2d') +var fit = require('canvas-fit'); +var createScatter = require('../../../../regl-scatter2d'); +// var createLine = require('../../../../regl-line2d'); var Drawing = require('../../components/drawing'); -var MARKER_SVG_SYMBOLS = require('../../components/drawing/symbol_defs'); -var svgSdf = require('../../../../svg-path-sdf') +var svgSdf = require('../../../../svg-path-sdf'); -var AXES = ['xaxis', 'yaxis']; var DESELECTDIM = 0.2; var TRANSPARENT = [0, 0, 0, 0]; // tables with symbol SDF values -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 * .05) +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 convertNumber, convertColorBase; -module.exports = createLineWithMarkers +module.exports = createLineWithMarkers; function createLineWithMarkers(container, plotinfo, cdscatter) { - var layout = container._fullLayout - var xa = layout.xaxis - var ya = layout.yaxis - var glContainer = container.querySelector('.gl-container') - - //FIXME: find proper way to get plot holder - //FIXME: handle multiple subplots - var subplotObj = layout._plots.xy - var scatter = subplotObj._scatter2d - - //create regl-scatter, if not defined - if (scatter === undefined) { - //TODO: enhance picking - //TODO: decide whether we should share canvas or create it every scatter plot - //TODO: decide if canvas should be the full-width with viewport or multiple instances - //FIXME: avoid forcing absolute style by disabling forced plotly background - //TODO: figure out if there is a way to detect only new passed options - var canvas = glContainer.appendChild(document.createElement('canvas')) - - //FIXME: make sure this is the right place for that + var layout = container._fullLayout; + var glContainer = container.querySelector('.gl-container'); + + // FIXME: find proper way to get plot holder + // FIXME: handle multiple subplots + var subplotObj = layout._plots.xy; + var scatter = subplotObj._scatter2d; + + // create regl-scatter, if not defined + if(scatter === undefined) { + // TODO: enhance picking + // TODO: decide whether we should share canvas or create it every scatter plot + // TODO: decide if canvas should be the full-width with viewport or multiple instances + // FIXME: avoid forcing absolute style by disabling forced plotly background + // TODO: figure out if there is a way to detect only new passed options + var canvas = glContainer.appendChild(document.createElement('canvas')); + + // FIXME: make sure this is the right place for that glContainer.style.height = '100%'; glContainer.style.width = '100%'; @@ -72,23 +67,23 @@ function createLineWithMarkers(container, plotinfo, cdscatter) { canvas.style.left = '0px'; canvas.style.pointerEvents = 'none'; - //TODO: fit canvas every window.resize or plotly.resize or whatever + // TODO: fit canvas every window.resize or plotly.resize or whatever fit(canvas, glContainer); - container.glContainer = glContainer - container.canvas = canvas - scatter = subplotObj._scatter2d = createScatterScene(container) + container.glContainer = glContainer; + container.canvas = canvas; + scatter = subplotObj._scatter2d = new ScatterScene(container); } container._fullData.forEach(function(data, i) { scatter.update(data, cdscatter[i]); - }) + }); - return scatter + return scatter; } -function createScatterScene(container) { - if (!(this instanceof createScatterScene)) return new createScatterScene(container) +function ScatterScene(container) { + if(!(this instanceof ScatterScene)) return new ScatterScene(container); this.container = container; this.type = 'scatterregl'; @@ -113,8 +108,8 @@ function createScatterScene(container) { this.hasErrorY = false; this.hasMarkers = false; - this.xaxis = container._fullLayout.xaxis - this.yaxis = container._fullLayout.yaxis + this.xaxis = container._fullLayout.xaxis; + this.yaxis = container._fullLayout.yaxis; var scatterOptions0 = { positions: Array(), @@ -133,8 +128,8 @@ function createScatterScene(container) { }; this.scatter = createScatter(scatterOptions0); - this.scatter.options = scatterOptions0 - this.scatter._trace = this + this.scatter.options = scatterOptions0; + this.scatter._trace = this; // this.line = createLine({ @@ -151,10 +146,10 @@ function createScatterScene(container) { // }, 0); - return this + return this; } -var proto = createScatterScene.prototype; +var proto = ScatterScene.prototype; proto.update = function(options, cdscatter) { @@ -214,8 +209,8 @@ proto.update = function(options, cdscatter) { proto.updateFancy = function(options) { var container = this.container, - xaxis = container._fullLayout.xaxis, - yaxis = container._fullLayout.yaxis, + xaxis = Axes.getFromId(container, options.xaxis || 'x'), + yaxis = Axes.getFromId(container, options.yaxis || 'y'), bounds = this.bounds, selection = options.selection; @@ -309,29 +304,24 @@ proto.updateFancy = function(options) { var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); var borderSizes = convertNumber(markerOpts.line.width, len); var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len); - var index, size, symbol, symbolName, symbolSpec, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, minBorderWidth, symbolNeedLine, symbolFunc, symbolNoDot, symbolSdf, symbolNoFill, symbolPath, isDot; + var index, size, symbol, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, symbolNeedLine, symbolFunc, symbolNoDot, symbolSdf, symbolNoFill, symbolPath, isDot; sizes = convertArray(markerSizeFunc, markerOpts.size, len); for(i = 0; i < pId; ++i) { index = idToIndex[i]; - symbol = symbols[index]; - symbolNumber = Drawing.symbolNumber(symbol) - symbolName = Drawing.symbolNames[symbolNumber % 100] - symbolFunc = Drawing.symbolFuncs[symbolNumber % 100] - symbolNeedLine = !!Drawing.symbolNeedLines[symbolNumber % 100] - symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100] - symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100] + symbol = Array.isArray(symbols) ? symbols[index] : symbols; + symbolNumber = Drawing.symbolNumber(symbol); + symbolFunc = Drawing.symbolFuncs[symbolNumber % 100]; + symbolNeedLine = !!Drawing.symbolNeedLines[symbolNumber % 100]; + symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100]; + symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100]; isOpen = /-open/.test(symbol); isDot = /-dot/.test(symbol); isDimmed = selIds && !selIds[index]; - // if(symbolNeedLine && !isOpen) { - // _colors = borderColors; - // } else { - _colors = colors; - // } + _colors = colors; if(isOpen) { _borderColors = colors; @@ -343,85 +333,86 @@ proto.updateFancy = function(options) { // for more info on this logic size = sizes[index]; bw = borderSizes[index]; - // minBorderWidth = (symbolNeedLine) ? 0.1 * size : 0; - minBorderWidth = 0 this.scatter.options.sizes[i] = size; - - if (symbol === 'circle') { + if(symbol === 'circle') { this.scatter.options.markers[i] = null; } else { - //get symbol sdf from cache or generate it - if (SYMBOL_SDF[symbol]) { - symbolSdf = SYMBOL_SDF[symbol] + // get symbol sdf from cache or generate it + if(SYMBOL_SDF[symbol]) { + symbolSdf = SYMBOL_SDF[symbol]; } else { - if (isDot && !symbolNoDot) { - symbolPath = symbolFunc(SYMBOL_SIZE * 1.1) + SYMBOL_SVG_CIRCLE + if(isDot && !symbolNoDot) { + symbolPath = symbolFunc(SYMBOL_SIZE * 1.1) + SYMBOL_SVG_CIRCLE; } else { - symbolPath = symbolFunc(SYMBOL_SIZE) + 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 + viewBox: [-SYMBOL_SIZE, -SYMBOL_SIZE, SYMBOL_SIZE, SYMBOL_SIZE], + stroke: symbolNoFill ? SYMBOL_STROKE : symbolNeedLine ? -SYMBOL_STROKE : 0 + }); + SYMBOL_SDF[symbol] = symbolSdf; } this.scatter.options.markers[i] = symbolSdf || null; } - this.scatter.options.borderSizes[i] = 0.5 * ((bw > minBorderWidth) ? bw - minBorderWidth : 0); + this.scatter.options.borderSizes[i] = 0.5 * bw; - var optColors = this.scatter.options.colors + var optColors = this.scatter.options.colors; var dim = isDimmed ? DESELECTDIM : 1; - if (!optColors[i]) optColors[i] = [] + if(!optColors[i]) optColors[i] = []; if(isOpen || symbolNoFill) { optColors[i][0] = TRANSPARENT[0]; optColors[i][1] = TRANSPARENT[1]; optColors[i][2] = TRANSPARENT[2]; optColors[i][3] = TRANSPARENT[3]; } else { - optColors[i][0] = _colors[4*index + 0] * 255; - optColors[i][1] = _colors[4*index + 1] * 255; - optColors[i][2] = _colors[4*index + 2] * 255; - optColors[i][3] = dim * _colors[4*index + 3] * 255; + optColors[i][0] = _colors[4 * index + 0] * 255; + optColors[i][1] = _colors[4 * index + 1] * 255; + optColors[i][2] = _colors[4 * index + 2] * 255; + optColors[i][3] = dim * _colors[4 * index + 3] * 255; } - if (!this.scatter.options.borderColors[i]) this.scatter.options.borderColors[i] = [] - this.scatter.options.borderColors[i][0] = _borderColors[4*index + 0] * 255; - this.scatter.options.borderColors[i][1] = _borderColors[4*index + 1] * 255; - this.scatter.options.borderColors[i][2] = _borderColors[4*index + 2] * 255; - this.scatter.options.borderColors[i][3] = dim * _borderColors[4*index + 3] * 255; + if(!this.scatter.options.borderColors[i]) this.scatter.options.borderColors[i] = []; + this.scatter.options.borderColors[i][0] = _borderColors[4 * index + 0] * 255; + this.scatter.options.borderColors[i][1] = _borderColors[4 * index + 1] * 255; + this.scatter.options.borderColors[i][2] = _borderColors[4 * index + 2] * 255; + this.scatter.options.borderColors[i][3] = dim * _borderColors[4 * index + 3] * 255; } - var size = container._fullLayout._size, - domainX = container._fullLayout.xaxis.domain, - domainY = container._fullLayout.yaxis.domain, + var vpSize = container._fullLayout._size, + domainX = xaxis.domain, + domainY = yaxis.domain, width = container._fullLayout.width, height = container._fullLayout.height; var viewBox = [ - size.l + domainX[0] * size.w, - size.b + domainY[0] * size.h, - (width - size.r) - (1 - domainX[1]) * size.w, - (height - size.t) - (1 - domainY[1]) * size.h + vpSize.l + domainX[0] * vpSize.w, + vpSize.b + domainY[0] * vpSize.h, + (width - vpSize.r) - (1 - domainX[1]) * vpSize.w, + (height - vpSize.t) - (1 - domainY[1]) * vpSize.h ]; - var bounds = [xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1]]; - this.scatter.options.range = bounds; - this.scatter.options.viewport = viewBox + var range = [xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1]]; + + this.scatter.options.range = range; + this.scatter.options.viewport = viewBox; + // prevent scatter from resnapping points this.scatter(this.scatter.options); } // add item for autorange routine - this.expandAxesFancy(x, y, sizes); + // former expandAxesFancy + Axes.expand(xaxis, x, {padded: true, ppad: sizes}); + Axes.expand(yaxis, y, {padded: true, ppad: sizes}); }; proto.updateLines = function(options, positions) { @@ -497,35 +488,6 @@ proto.updateError = function(axLetter, options, positions, errors) { } }; -proto.expandAxesFast = function(bounds, markerSize) { - var pad = markerSize || 10; - var ax, min, max; - - for(var i = 0; i < 2; i++) { - ax = this.container[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 container = this.container, - expandOpts = { padded: true, ppad: ppad }; - - Axes.expand(container._fullLayout.xaxis, x, expandOpts); - Axes.expand(container._fullLayout.yaxis, y, expandOpts); -}; - - - // proto.handlePick = function(pickResult) { // var index = pickResult.pointId; @@ -555,6 +517,8 @@ proto.expandAxesFancy = function(x, y, ppad) { // }; // }; +convertNumber = convertArray.bind(null, function(x) { return +x; }); +convertColorBase = convertArray.bind(null, str2RGBArray); // handle the situation where values can be array-like or not array like function convertArray(convert, data, count) { @@ -576,8 +540,6 @@ function _convertArray(convert, data, count) { return result; } -var convertNumber = convertArray.bind(null, function(x) { return +x; }); -var convertColorBase = convertArray.bind(null, str2RGBArray); function convertColor(color, opacity, count) { return _convertColor( @@ -612,27 +574,3 @@ function _convertColor(colors, opacities, count) { return result; } - -// 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; -} - From 79022cddf53d933c2a404632fe065785deaeb10f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 24 Aug 2017 18:25:43 +0300 Subject: [PATCH 021/151] Lintify & fix deps --- package.json | 3 ++- src/traces/scatterregl/plot.js | 9 ++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index b8e2752d5b5..88f7b052946 100644 --- a/package.json +++ b/package.json @@ -83,9 +83,9 @@ "gl-select-box": "^1.0.1", "gl-shader": "4.2.0", "gl-spikes2d": "^1.0.1", - "kdgrass": "^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", "mouse-change": "^1.4.0", @@ -97,6 +97,7 @@ "ndarray-ops": "^1.2.2", "object-assign": "^4.1.1", "regl": "^1.3.0", + "regl-scatter2d": "^1.0.2", "right-now": "^1.0.0", "robust-orientation": "^1.1.3", "sane-topojson": "^2.0.0", diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index f23b1363ef1..f1b57b4c975 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -19,10 +19,10 @@ var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); var getTraceColor = require('../scatter/get_trace_color'); var DASHES = require('../../constants/gl2d_dashes'); var fit = require('canvas-fit'); -var createScatter = require('../../../../regl-scatter2d'); +var createScatter = require('regl-scatter2d'); // var createLine = require('../../../../regl-line2d'); var Drawing = require('../../components/drawing'); -var svgSdf = require('../../../../svg-path-sdf'); +var svgSdf = require('svg-path-sdf'); var DESELECTDIM = 0.2; var TRANSPARENT = [0, 0, 0, 0]; @@ -304,7 +304,7 @@ proto.updateFancy = function(options) { var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); var borderSizes = convertNumber(markerOpts.line.width, len); var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len); - var index, size, symbol, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, symbolNeedLine, symbolFunc, symbolNoDot, symbolSdf, symbolNoFill, symbolPath, isDot; + var index, size, symbol, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, symbolFunc, symbolNoDot, symbolSdf, symbolNoFill, symbolPath, isDot; sizes = convertArray(markerSizeFunc, markerOpts.size, len); @@ -313,7 +313,6 @@ proto.updateFancy = function(options) { symbol = Array.isArray(symbols) ? symbols[index] : symbols; symbolNumber = Drawing.symbolNumber(symbol); symbolFunc = Drawing.symbolFuncs[symbolNumber % 100]; - symbolNeedLine = !!Drawing.symbolNeedLines[symbolNumber % 100]; symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100]; symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100]; @@ -355,7 +354,7 @@ proto.updateFancy = function(options) { w: SYMBOL_SDF_SIZE, h: SYMBOL_SDF_SIZE, viewBox: [-SYMBOL_SIZE, -SYMBOL_SIZE, SYMBOL_SIZE, SYMBOL_SIZE], - stroke: symbolNoFill ? SYMBOL_STROKE : symbolNeedLine ? -SYMBOL_STROKE : 0 + stroke: symbolNoFill ? SYMBOL_STROKE : -SYMBOL_STROKE }); SYMBOL_SDF[symbol] = symbolSdf; } From 125cca1a343dafef049aa7bbf2b88e082b9ba352 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 24 Aug 2017 19:42:14 +0300 Subject: [PATCH 022/151] Fix deps --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 88f7b052946..7a8412f6a8a 100644 --- a/package.json +++ b/package.json @@ -97,13 +97,13 @@ "ndarray-ops": "^1.2.2", "object-assign": "^4.1.1", "regl": "^1.3.0", - "regl-scatter2d": "^1.0.2", + "regl-scatter2d": "^1.0.3", "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.0.0", + "svg-path-sdf": "^1.1.1", "tinycolor2": "^1.3.0", "topojson-client": "^2.1.0", "webgl-context": "^2.2.0", From 41c294df77485479321b32b09ccd64057478bc00 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 24 Aug 2017 20:46:06 +0300 Subject: [PATCH 023/151] Add ultrazoom case for gl2d --- test/image/mocks/gl2d_ultra_zoom.json | 53 +++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 test/image/mocks/gl2d_ultra_zoom.json 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 + } + } +} From 6290a838c6bf73de1ebc9650ca4abe86f5377334 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Sun, 3 Sep 2017 11:27:04 -0400 Subject: [PATCH 024/151] Chill parcoord attribs out --- src/traces/parcoords/attributes.js | 41 +++++++++++------------------- src/traces/parcoords/parcoords.js | 1 - 2 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/traces/parcoords/attributes.js b/src/traces/parcoords/attributes.js index 83855a2da16..46729697a6e 100644 --- a/src/traces/parcoords/attributes.js +++ b/src/traces/parcoords/attributes.js @@ -18,7 +18,6 @@ var extendDeep = require('../../lib/extend').extendDeep; var extendFlat = require('../../lib/extend').extendFlat; module.exports = { - domain: { x: { valType: 'info_array', @@ -120,36 +119,26 @@ module.exports = { description: 'The dimensions (variables) of the parallel coordinates chart. 2..60 dimensions are supported.' }, - line: extendFlat({}, - + 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 + // autocolorscale therefore defaults to false too, to avoid being overridden by the blue-white-red autocolor palette extendDeep( - {}, colorAttributes('line'), { - colorscale: extendDeep( - {}, - colorAttributes('line').colorscale, - {dflt: colorscales.Viridis} - ), - autocolorscale: extendDeep( - {}, - colorAttributes('line').autocolorscale, - { - dflt: false, - description: [ - 'Has an effect only if line.color` is set to a numerical array.', - 'Determines whether the colorscale is a default palette (`autocolorscale: true`)', - 'or the palette determined by `line.colorscale`.', - 'In case `colorscale` is unspecified or `autocolorscale` is true, the default ', - 'palette will be chosen according to whether numbers in the `color` array are', - 'all positive, all negative or mixed.', - 'The default value is false, so that `parcoords` colorscale can default to `Viridis`.' - ].join(' ') - } - ) + colorscale: {dflt: colorscales.Viridis}, + autocolorscale: { + dflt: false, + description: [ + 'Has an effect only if line.color` is set to a numerical array.', + 'Determines whether the colorscale is a default palette (`autocolorscale: true`)', + 'or the palette determined by `line.colorscale`.', + 'In case `colorscale` is unspecified or `autocolorscale` is true, the default ', + 'palette will be chosen according to whether numbers in the `color` array are', + 'all positive, all negative or mixed.', + 'The default value is false, so that `parcoords` colorscale can default to `Viridis`.' + ].join(' ') + } } ), diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js index 455e76f08f9..cfbc9459f1f 100644 --- a/src/traces/parcoords/parcoords.js +++ b/src/traces/parcoords/parcoords.js @@ -254,7 +254,6 @@ function styleExtentTexts(selection) { } module.exports = function(root, svg, styledData, layout, callbacks) { - var domainBrushing = false; var linePickActive = true; From 63af430833c87fb604ddd601b481552a291231ac Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 5 Sep 2017 19:26:31 -0400 Subject: [PATCH 025/151] Make parcoords use shared canvases --- src/plot_api/plot_api.js | 38 +++++++++++++++-- src/traces/parcoords/parcoords.js | 68 ++++++++++++------------------- src/traces/parcoords/plot.js | 2 + 3 files changed, 62 insertions(+), 46 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index a6a4a62f9f1..15f1e454153 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -3027,9 +3027,41 @@ function makePlotFramework(gd) { // TODO: sort out all the ordering so we don't have to // explicitly delete anything fullLayout._glcontainer = fullLayout._paperdiv.selectAll('.gl-container') - .data([0]); - fullLayout._glcontainer.enter().append('div') - .classed('gl-container', true); + .data([{}]); + + // FIXME: bring this constant to some plotly constants module + // it is taken from parcoords lineLayerModel + fullLayout._glcanvas = fullLayout._glcontainer.enter().append('div') + .classed('gl-container', true) + .selectAll('.gl-canvas') + .data([{ + key: 'contextLayer' + }, { + key: 'focusLayer' + }, { + key: 'pickLayer' + }]); + + // create canvases only in case if there is at least one regl component + // FIXME: probably there is a better d3 way of doing so + for(var i = 0; i < fullLayout._modules.length; i++) { + var module = fullLayout._modules[i]; + if(module.categories && module.categories.indexOf('gl') >= 0) { + fullLayout._glcanvas.enter().append('canvas') + .attr('class', function(d) { + return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); + }) + .attr('width', fullLayout.width) + .attr('height', fullLayout.height) + .style('position', 'absolute') + .style('top', 0) + .style('left', 0) + .style('pointer-events', 'none') + .style('overflow', 'visible'); + + break; + } + } fullLayout._paperdiv.selectAll('.main-svg').remove(); diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js index cfbc9459f1f..422f983fe6d 100644 --- a/src/traces/parcoords/parcoords.js +++ b/src/traces/parcoords/parcoords.js @@ -233,18 +233,6 @@ function viewModel(model) { return viewModel; } -function lineLayerModel(vm) { - return c.layers.map(function(key) { - return { - key: key, - context: key === 'contextLineLayer', - pick: key === 'pickLineLayer', - viewModel: vm, - model: vm.model - }; - }); -} - function styleExtentTexts(selection) { selection .classed('axisExtentText', true) @@ -253,7 +241,7 @@ function styleExtentTexts(selection) { .style('user-select', 'none'); } -module.exports = function(root, svg, styledData, layout, callbacks) { +module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, callbacks) { var domainBrushing = false; var linePickActive = true; @@ -300,37 +288,31 @@ module.exports = function(root, svg, styledData, layout, callbacks) { .map(model.bind(0, layout)) .map(viewModel); - root.selectAll('.parcoords-line-layers').remove(); - - var parcoordsLineLayers = root.selectAll('.parcoords-line-layers') - .data(vm, keyFun); - - parcoordsLineLayers.enter() - .insert('div', '.' + svg.attr('class').split(' ').join(' .')) // not hardcoding .main-svg - .classed('parcoords-line-layers', true) - .style('box-sizing', 'content-box'); + parcoordsLineLayers.each(function(d, i) { + return Lib.extendFlat(d, vm[i]); + }); parcoordsLineLayers .style('transform', function(d) { return 'translate(' + (d.model.translateX - c.overdrag) + 'px,' + d.model.translateY + 'px)'; }); - var parcoordsLineLayer = parcoordsLineLayers.selectAll('.parcoords-lines') - .data(lineLayerModel, keyFun); + var parcoordsLineLayer = parcoordsLineLayers.selectAll('.gl-canvas') + .each(function(d) { + var key = d.key; + d.context = key === 'contextLayer'; + d.pick = key === 'pickLayer'; + + // FIXME: figure out how to handle multiple instances + d.viewModel = vm[0]; + d.model = vm[0].model; + }); var tweakables = {renderers: [], dimensions: []}; var lastHovered = null; - parcoordsLineLayer.enter() - .append('canvas') - .attr('class', function(d) {return 'parcoords-lines ' + (d.context ? 'context' : d.pick ? 'pick' : 'focus');}) - .style('box-sizing', 'content-box') - .style('float', 'left') - .style('clear', 'both') - .style('left', 0) - .style('overflow', 'visible') - .style('position', function(d, i) {return i > 0 ? 'absolute' : 'absolute';}) + parcoordsLineLayer .filter(function(d) {return d.pick;}) .on('mousemove', function(d) { if(linePickActive && d.lineLayer && callbacks && callbacks.hover) { @@ -512,8 +494,8 @@ module.exports = function(root, svg, styledData, layout, callbacks) { .attr('transform', function(d) {return 'translate(' + d.xScale(d.xIndex) + ', 0)';}); d3.select(this).attr('transform', 'translate(' + d.x + ', 0)'); yAxis.each(function(dd, i, ii) {if(ii === d.parent.key) p.dimensions[i] = dd;}); - p.contextLineLayer && p.contextLineLayer.render(p.panels, false, !someFiltersActive(p)); - p.focusLineLayer.render && p.focusLineLayer.render(p.panels); + p.contextLayer && p.contextLayer.render(p.panels, false, !someFiltersActive(p)); + p.focusLayer.render && p.focusLayer.render(p.panels); }) .on('dragend', function(d) { var p = d.parent; @@ -528,9 +510,9 @@ module.exports = function(root, svg, styledData, layout, callbacks) { updatePanelLayout(yAxis, p); d3.select(this) .attr('transform', function(d) {return 'translate(' + d.x + ', 0)';}); - p.contextLineLayer && p.contextLineLayer.render(p.panels, false, !someFiltersActive(p)); - p.focusLineLayer && p.focusLineLayer.render(p.panels); - p.pickLineLayer && p.pickLineLayer.render(p.panels, true); + p.contextLayer && p.contextLayer.render(p.panels, false, !someFiltersActive(p)); + p.focusLayer && p.focusLayer.render(p.panels); + p.pickLayer && p.pickLayer.render(p.panels, true); linePickActive = true; if(callbacks && callbacks.axesMoved) { @@ -742,13 +724,13 @@ module.exports = function(root, svg, styledData, layout, callbacks) { var newExtent = reset ? [0, 1] : extent.slice(); if(newExtent[0] !== filter[0] || newExtent[1] !== filter[1]) { dimensions[dimension.xIndex].filter = newExtent; - p.focusLineLayer && p.focusLineLayer.render(p.panels, true); + p.focusLayer && p.focusLayer.render(p.panels, true); var filtersActive = someFiltersActive(p); if(!contextShown && filtersActive) { - p.contextLineLayer && p.contextLineLayer.render(p.panels, true); + p.contextLayer && p.contextLayer.render(p.panels, true); contextShown = true; } else if(contextShown && !filtersActive) { - p.contextLineLayer && p.contextLineLayer.render(p.panels, true, true); + p.contextLayer && p.contextLayer.render(p.panels, true, true); contextShown = false; } } @@ -769,9 +751,9 @@ module.exports = function(root, svg, styledData, layout, callbacks) { f[1] = Math.min(1, f[1] + 0.05); } d3.select(this).transition().duration(150).call(dimension.brush.extent(f)); - p.focusLineLayer.render(p.panels, true); + p.focusLayer.render(p.panels, true); } - p.pickLineLayer && p.pickLineLayer.render(p.panels, true); + p.pickLayer && p.pickLayer.render(p.panels, true); linePickActive = true; domainBrushing = 'ending'; if(callbacks && callbacks.filterChanged) { diff --git a/src/traces/parcoords/plot.js b/src/traces/parcoords/plot.js index 90cc3353846..aa6be64e6d9 100644 --- a/src/traces/parcoords/plot.js +++ b/src/traces/parcoords/plot.js @@ -15,6 +15,7 @@ module.exports = function plot(gd, cdparcoords) { var fullLayout = gd._fullLayout; var svg = fullLayout._paper; var root = fullLayout._paperdiv; + var container = fullLayout._glcontainer; var gdDimensions = {}; var gdDimensionsOriginalOrder = {}; @@ -98,6 +99,7 @@ module.exports = function plot(gd, cdparcoords) { parcoords( root, svg, + container, cdparcoords, { width: size.w, From 943a74370869a2bc348ad62e5c1dc7cd751fdc5a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 5 Sep 2017 20:02:53 -0400 Subject: [PATCH 026/151] Use top paper --- src/traces/parcoords/plot.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/parcoords/plot.js b/src/traces/parcoords/plot.js index aa6be64e6d9..8abee427202 100644 --- a/src/traces/parcoords/plot.js +++ b/src/traces/parcoords/plot.js @@ -13,7 +13,7 @@ var parcoords = require('./parcoords'); module.exports = function plot(gd, cdparcoords) { var fullLayout = gd._fullLayout; - var svg = fullLayout._paper; + var svg = fullLayout._toppaper; var root = fullLayout._paperdiv; var container = fullLayout._glcontainer; From 7858201f7edaf60931d0b42213d3f4325185f44f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Sep 2017 14:43:39 -0400 Subject: [PATCH 027/151] Simplify line creation method --- src/traces/parcoords/lines.js | 14 +++++++++++++- src/traces/parcoords/parcoords.js | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/traces/parcoords/lines.js b/src/traces/parcoords/lines.js index 92776dabf0a..4faef839bbe 100644 --- a/src/traces/parcoords/lines.js +++ b/src/traces/parcoords/lines.js @@ -165,7 +165,19 @@ function valid(i, offset, panelCount) { return i + offset <= panelCount; } -module.exports = function(canvasGL, lines, canvasWidth, canvasHeight, initialDimensions, initialPanels, unitToColor, context, pick, scatter) { +module.exports = function(canvasGL, d, scatter) { + var model = d.model, + vm = d.viewModel; + + var lines = model.lines, + canvasWidth = model.canvasWidth, + canvasHeight = model.canvasHeight, + initialDimensions = vm.dimensions, + initialPanels = vm.panels, + unitToColor = model.unitToColor, + context = d.context, + pick = d.pick; + var renderState = { currentRafs: {}, diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js index 422f983fe6d..ab9c865316b 100644 --- a/src/traces/parcoords/parcoords.js +++ b/src/traces/parcoords/parcoords.js @@ -460,7 +460,7 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca parcoordsLineLayer .each(function(d) { - d.lineLayer = lineLayerMaker(this, d.model.lines, d.model.canvasWidth, d.model.canvasHeight, d.viewModel.dimensions, d.viewModel.panels, d.model.unitToColor, d.context, d.pick, c.scatter); + d.lineLayer = lineLayerMaker(this, d, c.scatter); d.viewModel[d.key] = d.lineLayer; tweakables.renderers.push(function() {d.lineLayer.render(d.viewModel.panels, true);}); d.lineLayer.render(d.viewModel.panels, !d.context); From 4d17a53c54189976e39bae751167b8be76755f41 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Sep 2017 16:05:58 -0400 Subject: [PATCH 028/151] Fix image generation --- src/traces/parcoords/base_plot.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/traces/parcoords/base_plot.js b/src/traces/parcoords/base_plot.js index e5be3285b75..afd81d1fdd3 100644 --- a/src/traces/parcoords/base_plot.js +++ b/src/traces/parcoords/base_plot.js @@ -28,8 +28,6 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) var hasParcoords = (newFullLayout._has && newFullLayout._has('parcoords')); if(hadParcoords && !hasParcoords) { - oldFullLayout._paperdiv.selectAll('.parcoords-line-layers').remove(); - oldFullLayout._paperdiv.selectAll('.parcoords-line-layers').remove(); oldFullLayout._paperdiv.selectAll('.parcoords').remove(); oldFullLayout._paperdiv.selectAll('.parcoords').remove(); oldFullLayout._glimages.selectAll('*').remove(); @@ -41,7 +39,7 @@ 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;}) - .selectAll('.parcoords-lines.context, .parcoords-lines.focus'); + .selectAll('.gl-canvas-context, .gl-canvas-focus'); function canvasToImage(d) { var canvas = this; From 314dfdc7ebaf76a870df57bef17d1d638dfbd50a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Sep 2017 17:11:40 -0400 Subject: [PATCH 029/151] Use viewport to limit painting area instead of css --- src/traces/parcoords/lines.js | 24 +++++++++++++++++------- src/traces/parcoords/parcoords.js | 16 +++------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/traces/parcoords/lines.js b/src/traces/parcoords/lines.js index 4faef839bbe..07af4f390c8 100644 --- a/src/traces/parcoords/lines.js +++ b/src/traces/parcoords/lines.js @@ -10,7 +10,7 @@ var createREGL = require('regl'); var glslify = require('glslify'); -var verticalPadding = require('./constants').verticalPadding; +var c = require('./constants'); var vertexShaderSource = glslify('./shaders/vertex.glsl'); var pickVertexShaderSource = glslify('./shaders/pick_vertex.glsl'); var fragmentShaderSource = glslify('./shaders/fragment.glsl'); @@ -56,7 +56,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]); } @@ -167,7 +168,8 @@ function valid(i, offset, panelCount) { module.exports = function(canvasGL, d, scatter) { var model = d.model, - vm = d.viewModel; + vm = d.viewModel, + domain = model.domain; var lines = model.lines, canvasWidth = model.canvasWidth, @@ -178,7 +180,6 @@ module.exports = function(canvasGL, d, scatter) { context = d.context, pick = d.pick; - var renderState = { currentRafs: {}, drawCompleted: true, @@ -260,6 +261,15 @@ module.exports = function(canvasGL, d, scatter) { } }, + viewport: () => { + return { + x: model.pad.l - overdrag + model.layoutWidth * domain.x[0], + y: model.pad.b + model.layoutHeight * domain.y[0], + width: canvasWidth, + height: canvasHeight + } + }, + dither: false, vert: pick ? pickVertexShaderSource : vertexShaderSource, @@ -309,7 +319,7 @@ module.exports = function(canvasGL, d, scatter) { function makeItem(i, ii, x, y, panelSizeX, canvasPanelSizeY, crossfilterDimensionIndex, scatter, I, leftmost, rightmost) { var loHi, abcd, d, index; var leftRight = [i, ii]; - var filterEpsilon = verticalPadding / canvasPanelSizeY; + var filterEpsilon = c.verticalPadding / canvasPanelSizeY; var dims = [0, 1].map(function() {return [0, 1, 2, 3].map(function() {return new Float32Array(16);});}); var lims = [0, 1].map(function() {return [0, 1, 2, 3].map(function() {return new Float32Array(16);});}); @@ -353,9 +363,9 @@ module.exports = function(canvasGL, d, scatter) { colorClamp: colorClamp, scatter: scatter || 0, - scissorX: I === leftmost ? 0 : x + overdrag, + 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, + scissorY: y + model.pad.b + model.layoutHeight * domain.y[0], scissorHeight: canvasPanelSizeY }; } diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js index ab9c865316b..33119b9fdb0 100644 --- a/src/traces/parcoords/parcoords.js +++ b/src/traces/parcoords/parcoords.js @@ -166,6 +166,9 @@ function model(layout, d, i) { labelFont: labelFont, tickFont: tickFont, rangeFont: rangeFont, + layoutWidth: width, + layoutHeight: layout.height, + domain: domain, translateX: domain.x[0] * width, translateY: layout.height - domain.y[1] * layout.height, pad: pad, @@ -292,11 +295,6 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca return Lib.extendFlat(d, vm[i]); }); - parcoordsLineLayers - .style('transform', function(d) { - return 'translate(' + (d.model.translateX - c.overdrag) + 'px,' + d.model.translateY + 'px)'; - }); - var parcoordsLineLayer = parcoordsLineLayers.selectAll('.gl-canvas') .each(function(d) { var key = d.key; @@ -350,14 +348,6 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca }); parcoordsLineLayer - .style('margin', function(d) { - var p = d.model.pad; - return p.t + 'px ' + p.r + 'px ' + p.b + 'px ' + p.l + 'px'; - }) - .attr('width', function(d) {return d.model.canvasWidth;}) - .attr('height', function(d) {return d.model.canvasHeight;}) - .style('width', function(d) {return (d.model.width + 2 * c.overdrag) + 'px';}) - .style('height', function(d) {return d.model.height + 'px';}) .style('opacity', function(d) {return d.pick ? 0.01 : 1;}); svg.style('background', 'rgba(255, 255, 255, 0)'); From aefdab16d0564267831bb0f97e4eb5637d198ddd Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Sep 2017 17:29:24 -0400 Subject: [PATCH 030/151] Eslint, enhance props propagation --- src/traces/parcoords/base_plot.js | 12 ++---------- src/traces/parcoords/lines.js | 20 ++++++++++++-------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/traces/parcoords/base_plot.js b/src/traces/parcoords/base_plot.js index afd81d1fdd3..1321fed4816 100644 --- a/src/traces/parcoords/base_plot.js +++ b/src/traces/parcoords/base_plot.js @@ -12,7 +12,6 @@ var d3 = require('d3'); var Plots = require('../../plots/plots'); var parcoordsPlot = require('./plot'); var xmlnsNamespaces = require('../../constants/xmlns_namespaces'); -var c = require('./constants'); exports.name = 'parcoords'; @@ -41,21 +40,14 @@ exports.toSVG = function(gd) { var canvases = root.filter(function(d, i) {return i === root.size() - 1;}) .selectAll('.gl-canvas-context, .gl-canvas-focus'); - function canvasToImage(d) { + function canvasToImage() { var canvas = this; var imageData = canvas.toDataURL('image/png'); var image = imageRoot.append('svg:image'); - var size = gd._fullLayout._size; - var domain = gd._fullData[d.model.key].domain; image.attr({ xmlns: xmlnsNamespaces.svg, - 'xlink:href': imageData, - x: size.l + size.w * domain.x[0] - c.overdrag, - y: size.t + size.h * (1 - domain.y[1]), - width: (domain.x[1] - domain.x[0]) * size.w + 2 * c.overdrag, - height: (domain.y[1] - domain.y[0]) * size.h, - preserveAspectRatio: 'none' + 'xlink:href': imageData }); } diff --git a/src/traces/parcoords/lines.js b/src/traces/parcoords/lines.js index 07af4f390c8..98495e53ad8 100644 --- a/src/traces/parcoords/lines.js +++ b/src/traces/parcoords/lines.js @@ -261,13 +261,11 @@ module.exports = function(canvasGL, d, scatter) { } }, - viewport: () => { - return { - x: model.pad.l - overdrag + model.layoutWidth * domain.x[0], - y: model.pad.b + model.layoutHeight * domain.y[0], - width: canvasWidth, - height: canvasHeight - } + viewport: { + x: regl.prop('viewportX'), + y: regl.prop('viewportY'), + width: regl.prop('viewportWidth'), + height: regl.prop('viewportHeight') }, dither: false, @@ -363,10 +361,16 @@ 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], - scissorHeight: canvasPanelSizeY + scissorHeight: canvasPanelSizeY, + + viewportX: model.pad.l - overdrag + model.layoutWidth * domain.x[0], + viewportY: model.pad.b + model.layoutHeight * domain.y[0], + viewportWidth: canvasWidth, + viewportHeight: canvasHeight }; } From 58ff5a3aceaf6beb95fd62e3753d799dcd1bceae Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Sep 2017 17:49:04 -0400 Subject: [PATCH 031/151] Provide image attribs hoping CI will trigger --- src/traces/parcoords/base_plot.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/traces/parcoords/base_plot.js b/src/traces/parcoords/base_plot.js index 1321fed4816..d34583c8be7 100644 --- a/src/traces/parcoords/base_plot.js +++ b/src/traces/parcoords/base_plot.js @@ -47,7 +47,12 @@ exports.toSVG = function(gd) { image.attr({ xmlns: xmlnsNamespaces.svg, - 'xlink:href': imageData + 'xlink:href': imageData, + preserveAspectRatio: 'none', + x: 0, + y: 0, + width: canvas.width, + height: canvas.height }); } From dd829616358fb714859a402c36f9aa2cf2c2068c Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Sep 2017 18:25:17 -0400 Subject: [PATCH 032/151] Update failing baseline --- test/image/baselines/gl2d_parcoords_2.png | Bin 37011 -> 29852 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/test/image/baselines/gl2d_parcoords_2.png b/test/image/baselines/gl2d_parcoords_2.png index 4eebd736916a6d359cd61783a0606ba230b76460..91d01c98f600993ce93b564bfb91672d85871f18 100644 GIT binary patch literal 29852 zcmeEu^;?u}w>AvjDJYGkbSNEzAV?!AjY^1=bi*Jjpp?`I2n--4Dcv9;B`|ajB{9@c zL)UlX^E~_8@4NT@58fZzbN%%|W_ z(Yjactjvs$9;M{t<6J>`zcrLlDqoYGf=m{nd7$+a&77WB%489%0G<@0BtM~UaI5i909$z0qsB`mx z4~7xq9HM1S^`|xPlpI<4Utgxtfq-kkT;&A+w8rK_fa5?+FubSNw<|0{T8b;vAU1Ol^k0NOlDp%^i7AXk**8JtsSzd#QZX3Z#+o{;JpF-##Y@NO{VdfTH=|cRe33WN z!O8&VK8);~f*|Y`*5ws;$CV{NC=Fb*k=^O_C&rEP#A2h_I1*?adFwO4coXnMx_|mM z#staMHuXI~FKi4B7uPOe<^uu`ka+>(@AcT+tY_bjoq@NYGOF7DTFEgOl*a0vW82mCr-SjE z4)RGJOO)J3z61W+LWhR0XPO=_KJWYUcb=Q?`(aUP$>f(mog};rnXDo3@I$KgN5A{C zoT5SqF4{Xtp{++(jozO3MEd#sH!ZR>rh46m7tj90vOXR#46nvvJrmC;I7#cq0%egP zI`}*y{B6rEf8_B+^knlGGIK<+=2u0>dte8_IB&iIoeI-`+8tC>+kDpGFZleN9Etun zE9^KClEVFsa>BI85cOoVrph;6>l6UrJQinN)VELUY%{bXd+ty1KfcNAsNA+L0!+8> zn*O=JVJ6VSzipTo5#<0ku3z&!`AoJ*h!)KBXzF?UpZ!p~#R5<4P0JE`KO0&oub%hE zDU<=HV0Kn$n<`hEvF>Z2|BhJCr24l#ay9+*T1yyyu%+x+qefgOy6lVy`^f*tsq*y! z8=N>jmbfnKeg3SkiRinFAv>JFBggA+BPHD!>8;b_+THMBv1!zF#AFG!lp^Y1jSw;O z>Gcy~xcj!!7X7V1`-y7rO!{LaeID`;V3(`kBJVTBaP_?zU8}yO^=3lY> z%eb32-mJcoAU2$IV3Z+)rWbc!n6k_0d`mzTD<6FIRf;HKr}j7L_B`ldtkCF1D+H6< ztX3$A-}HT*``WAE!Bp{5u{%h3CKjEzb7Zb&>e8=d(c8k$v0qJ;L_sZsj0;Gy>vijl zZ{H+kt@VU2!5NiU1khVpvR$R&flZAQtjF_h7ZZeSf7-*mr;V>wJyV%=^j7rz97~6}ATBi`exd1;*rm zMwtdixkehXo82p|%&gv$CZEaqG69C_)V0?6>@Ar{G=W1vDRzh=V^QmYFLzX8n8q4i zFx^qJb@9{pZF;HM+1afo%bul(r;FNYd2EjJuOB;1lw>~1R=zFn%%GO2dwkCrct8K; zyg4;uzc5v>XLIs)R&lhmgJsACnuLCDV1J!1`~sJfit4+=gaE%s`aL~Pl40iH3t_-w zVRC_IR>vp=a;hS4H6-laI}V!%!B+uJnt@9l5y**B=H4X!)aN3s@o%m!&$E@I=z1#w z$5rir^T&(F4FQMhlK!-t@f>D16FoV>lv^+&N$HiS5p;s-sdxbvkWzOy98Vw zPDpqlb-sZuzdwIhO`!!d&(*k71E!svC^FVF&Z9#1ezjHj^Qt@W!Z0u6(;nBFJh4}F z@^)0*DjWbGSAi(~T;>`j<<5|7Bxg^aJaJY;m1mo`Z3EL9E^()IW0v;fQ-d1bYz$(; zVlD7bUpxz6A-5pSf#+WQkpig*>l*(;!(w>03R#|}E9Lvrr{A-xGo2UP-aRe1ju^?U zEjF#WDSI?S#0+#I)0 zonLmWYdt@n5AMQa4uJ3&d}H^Wt+4GMhRD8zvusbMY=vJheH3J&|4m547p@K!$b8Wi z{c^G_b9E|1%9F4?j7;Sn;VpQRzjuxl`G0>YV3>iu&g^DJ#ArTKigUN#DEsw$P4*+4 z8mVHMGyMXBv319D{#A(eLD^%oVPP@jk5DgyZb7qJNO(BD2Ws}tN*~wGGPK|0cUMf< zRtj|2A5rgFL!LMM2Q(dC`%FkR-U65q z$ULN*@6Am}ilv|i4)H^A_E~*mw1z49jPQ!qK_sm11w8>ea{RZW;Emt2fNy#VAft3P zAkZN|RaCumQFc5ZwkXZyE&lqZ4c`OjZUz2Lhy*JGH5Qq(AQke*SVqoY;WisMbx&j; zgAH<=%AZj^&qn@_+Zr7Fb8>U?>B$BzQRH^QXyyWoFLhD zcF53-m*-g7%9vd-SnmjN>hG^I6XWPl!!GMCHh34_(Y_fn+35{9dB$R=Wz~*5h>HKd zFe_=bV=-8Ayw5{|tO!zhxVm96w?4m8P2a+D#BmcNueTnz%*ZWBta1oacgN$!YOg(T zBJ|k)7CMxU?_nN6!3H^=!>V?{`#`3(d5emUsjg@-OQI<$M$3Q6$I$2i-PkBv1D zhL>KxnH(n@R?G`SO}WRf8XRHfsojDZ@Lmqni+hy1p1K8!nKg)&dGWJ^LfJv)6Pb3} zRuQ@S1jrEg=Gy@~?(K$eD@reV?#vLVW$J6Rg`3I)lK8w6JBou$FOXw~T5C zahi$a@&RTPd|j+MS5*)lTJj%g!X`ZT=#45`lCldnzn<+XCl~s++E)c8Ar$W(;MnY%J=8i8 zQb?J{@vM`(!;d|$FTtr|erXJNg`<$hm!C+@_htvbs3u>Z9r}&72VI9?)enf;ZG>jM z-PxR&*yLr(`1*;>Q|IEm)ICVn4}&?t>Me&5nKt!fB4qGYfy%3nIZiDNW~BV(SHYR? z>kleC)q;bcC*KwEH&pboDta`?_)>OfFq3m@ea1-OOJn)5K9R}pcc&j>U zaptCqNnT4i-WGZp>z=x0bpItxg>zXlp~vafxq$$LW{3gEQp=;(qKO-@{c5t`me`@kC3y_V5F>eUPg^BWM1fL}F zVb*(Ka%!p(u+=r2JXok(L(eaVl$aQV6|)<`)B`z!>$I|PS9){i9{SI)2iI9}65it7 z2!&wi<63n^d|0jwZfTZFlWmYAxTgcoz5GFh&0=2ujyWXI&`*%-o^2%1 zNGJZ4ng_kUIPjCJbLYY`?~M=nW}^I|n*Zz-NvlXEl?^!zS(?RiD?k$DrYv(Ut~;tD0Zzr8ea%R24Q^ zQlvNI*!um2l#Wr!=)@kv>4Rx4`O7)8n$CNAx;qBnjgobGvC$lhb z2^Ytk6NgeC`GXHMH@2x?9g<=P3~twGLyepwH0-HYmwFuL=3bZQ zZVwCRt~Zn+MBsG-qCvNLv{);x66f{oF4sBc#+Z7u#lsa5?p{c_t>*rG=jLRMHnSNc zUbXl9m?ybVAI7~NM!0$c9M8yDrK(019k75IxSQlR6Mq>rq{&GbJ$F6hg0<6#6<(4R z&ptKC63=rgt%$A>Q zziSuuG5DUatp`W!PsX>ZgSq0>_j(Z=fb*c$94%nyhZEpjjRNtfcWpp{$q6#&ML}sA zx2?_18vT>=2`9HL*W>aZ7Mrx5?49Krdv7NQe|`A`=4jl6ik_=L92FXQ&#egL+0qDV z$u0}Pp^uY)`>=Z^23QaCPW#dAdp}XcI=i@(h}GMEKN7Ak&syzE0F-LS&%ka?od^N% z^9RSX+5WRf{ccAs4OsO)(HX%dVUgRIyySjh!9Um5KM$)6L)dM;Lu!)!2` z0*uKPKTfbMD(*RVjA#l@%@}6BA$>Z*&J+@SC8>=KlZ?tL&)y|yo@B*QS zu}G{!#S4`tr|1Gzsz4!{z^$I379WX{%-}rJG`L9 z5mGbRtYNas)?(F$Kf z`8e{+!umwnkJUU{g;krd_bEN&dn(9OvChQLT^CAB!- zD7T8U^+YxM-rLU5#5-48Q+^U=UM16t5ER6sAWlv?DQw<6F62?$yQ6-kMBdHxC}J~v zjyqtE4$eiXkfI`5^_TPU)-I{UoFe(PoCGnwR&E;!S081qQw--Qdp?V*w6vv#32dgg zJ#QVNf;z0YhzNi3^z<*E-*G0NZZwWz>J~T?pC8>}fy=HRwpy?qaf@x1MUrzAIYfsG$ z&*M%j_6g;Y8k5zhV;jU`Q;3vgvQxjEOon&nM3!5y~^g17w^si{I+YKOduZiBX zua3&<{9gnu-n}K~sKcr!9v4}UwZZFA$2A}@`mn~gU0scwAw%+g|sc9oY8kx>Q`&Q9u6B#lJZ~NeAtz z;41L<4iwFh$)>y$d&#!+PQ61d$=BNQBZFN(RJ*iB3AIH(5Jl(g)fyxT2XqMFW@ z4EDi7k6Q34-i(~v{GO?v0uj#yx4ATGTd{>>(AmI$B|aE4@KuZu{J37FhAE^0f{`;Y*uS&FHKi-844%ickL5y-)MzdM1KN z!o7-?ZWws`h@koRcQ-#sR4{!Xya8FFglf?cxqwq#T|z!>4p^UNWl zLxM@W!oOz;(MuU6+auuIqj%-LHrD5fiY_MbvB65!sJ3~NY(Y6_8vUl{?L)Dxo=*n; zalh^Mi+bN0jDKeoPpNmvQ(CYt7hnxeX6UAb$oBk{CRl7hxj{viE29`b#YaJ+RdL`@ zrj8KMhmfFGvd=U4Z}2WZKkYocmKkOx-!kidqd2g-?Ys!MCzM;zIo7!HZ8Reu=|oJ$ zW8fTY`#{UzP`&b{fLO$hxRbHgCNyU2B=NW4Qd1vYoloZe!VDJ<%7TxLRbdaZ!od)o z=`^u=2J*P`*xI|yhnsRWLG^==K)I_XC+pJT<1}5YdJSA_U6yUArc?Q*xffXd?ZYDq z3BS!bka=$cn`5~E4_1bc#?q6Vs*;Tmf?Y~M>eyyl(^yYu|F{QszJnnePdmLBWAt-v zL){{E9hD$lgCXc_4ILZB)Gcl(NkMcMC(g1PlWDr9ZxXiGeq`^#y-c4y51>Lw`n4>V zEYU5>7r$5;7uaOB;S`J+cjmX?<2=x_MSLoX zspph*;iI~J4lN77tk(eIZ{iO_T$Q2TX5%3YcPpB8?gi9j3fRn|@h+8^uVKgP_C0=j z>c{HX8grO}>JLEn-$_n|BG`Uy`}SBHarV1?eIhnG#mY4Z*(%ZAc5ry!4g!jkyY}{v zsRs2cPx+h>Sh0V=-@ES z6rt5k>X(esx1Yk`WPpj@AzcLjnAr)`K?^B;G5a`ahwt%z_SPMSk9#_pQ;qCcl8nJP_$V)P%yXS;=|n3vKjReOW>~E_!kL} zEAp$Fqu{4UYa(yM-etcoY4gdOlg>9abNbw0xm5m~wE!GJroOPuu~u{6qAm!BH`{Qj zC!Fj1&M&#f^4(io5_MR<F|@%98Dgi@r#ru5 zi1E->Dy|DxT#3`*j>WNX-$r}@dFHK>)}uYz$TE7u6MPRHSm~lGTf;Q}K^)o~A)9x! z@VJzX4qn4B|3+T>I$}*-K1Nyu5%MUQ?_tB~_U_=!HH*3rjb6>C#B5MH4+~4lp)vo~ zW;2<$Z8KG(d`V=Ijmc86iHrTw_41nb(oDC@lM*Y*?K{X*C+#Bp7Uk51BTJm}1BQRJ z08`9i1B`96v}?99uu|y{Se+UB`nHNCA0=uDg*^lC&KBFlgdHb^mIu=4v$rjK8_X;I zFNk@+By`69|A3eoqxl3Zn;8HYt}TL!OQ+0AK}lJ8$=^FerRb`D7b<4e zvyjMJc(q!qJ|VC;bT4M-l&(xcSIfs?n#wr(O&@?hMDvwU1J2%#O5b_tubnhGL5L&b-wf-vbCTetu4i^H`*z=^t#&c zr4FhQiawNl?|z-;PFP1~DbG+yTGTdNtzJd&Ju!~PjZ7FOXwi6kBu`sqeK^N;r8kip zO#2xCp6Re~iNOFw`Q33!}28e<}q%{l~c`|7^FF-pMKlsG*jJ$Ng`;eHFxN*!2nz-}{Lzna?J#Xhr(G zQqt9v3K+pmBs3bvEpSwT`72H^kj ztVcwSlVu9$gF}kY`IQ;~%FRk@j5_ez7%PP0E{Qo!TLIfazT;os=zZ-qUG0>L`x-NN zyvUe>iYoNsJHkbh)0gn=`%csMx27uavX|57aXCN;7FI>cR6QfD%+V9w$;+S4)V;=! z9eRt*D=L4yQrN_qZZQvOKAp6^e_bvaZ6Wia_Z_c;MZh{JN6{E}JdKe39?RTuRqDj9 zLMT~WdYJu8-eN=dPF%^U-_FGCgOS@L`}-Ff4q_w^b_$cx{--g$FElQNeOTtPOujm_ zxl;+fJY*@_E~CLIr!D(B4J=(M8jB;6gd>m3u;VH*+_$o90_^?kxUjUj4d61qK!VW7 zC;jI%Lh2H8>A4!oZ{EJe=GPFM^BMReIixAJHK#REVy+0F;6l!`5(zv8gaC*=Ze`K6 z7(cvJ)z#Vg4DcO%ciF5|i%`8&AQ(Q8;6-qT{+fqg|LlsAA5YinxJ>)nIF6w1&892+}Cbw z46xtvnr;b}ulHDggF{5a((t?M-Gxpx_*hYfcb13M{f(YDoRgRI~{iZAQ*-O(B{%NR-3y$&Rd*UMS?6icF$~k&1kE`JI!Yro# z`PY;YI=`00%h!iL4f;R2Tx-$vALL-1pS){A@wDF$ppT{ar0m#@vbPuHDeI{Uc@mJDX&sDiu@x zm2kFplO75S9}(rz{xT-VG9u|1h?lO4a*6I5zpC4hsh+!fk{z>a*0Uy4Y}jp-m0-IrMox;Sy=*Y;-ho*EKZ^xA8*aa#~rmh;+r>Qs?dRP|9DMd!vW z<0C}FZ_*Ll0HXgMKyf{HLfa|N9sN#_U+M7fm@MvDm#N&(2KlDK?KgvQ_lQ2s8Bk*} z(}&xs%Q^V=Pl6qIg;v*upC*^-&LCG)qRk}UXc0*%M7yna$6zC@f`c-c8N_SgGAEXmNVGCFvU{K{4>hCdvpF#WwMM;!{#Kg|%w$oPY0 zsDSyyu4u-pGCzPwbln)eN6ak8aEnJDe2h91R+h}=%sd4+Iha}^4_$X>0~B!(Bkw#5 zUB^d9USXGC{^7Jj0UoA##f5>pa8hyplJPmu{0pCMb88Ag^Jxy2me-4*yLa!x>)h?~ z4qs30$pu~bbQ9UKUs%*Qn{TJ^8OK>xRyiZ2x}Q8C_)W{LgTZfFtrX9xNvvxz`2OD* zq4^^+QkhwB0X{`T^COMUhD%WWFd<&P5J(o>CSv**i=zinfJecv%ghvWGzG6VMLP6Z zZaC+E5eAgzy)J8Ju%pC9BFh<}UfX4Su|M}y?hmdBcd(WAA zv|&_8p8hnOzm)+PAxyK)Hr=wdXB8edV?u0h)_;DV)KK?)_{)+hlqHu>)&qnPiJ_!_ zc*EY~mjFEmONWLU(A_J3yx0GcLXVUAM(_Y+K{S8(uCgMa#c_J)k8i-3_QKDZdewn3 zlLD(J|B`R$0^*D7AR4?vaYGa5JxdzaTqRl7$Gey}NQ3yNY=(*OVyMxy+*GOhBk80G4;7z}u8_ z$^W@-V+imP6nY6@yKY%o^~IgnjAdJy$}0BwzI1BJ1`x0}?pbV$z~VxDe`1Q_-q-;} zprzK$W&qsVW17_-$O@q^NRGWBCGkiBQ#+lExtyfOlzmwXhF7`?6a?DmvG=B+m>(fz z$tasgm-?;Hwf4cgn*I&A=oe7e-Fux(XWvf4o5k^D0m(>ipqPf9FdHr(Xzo6u=fmwH z2*qI74+BCIFFJUP<0dP zjhr8NgqHQDL}L4K%W(cEeo+R@Laz35U|3d<<>_FUU6r1HA}(6*ddaFiNsUOLd5c$x zmIZ#J>hWTI`t%ltsq=(C6~?J|EM+@dfik@_w*Vs~oi7F#J#xLc;4a8hm9nJ1$|4mk z_V?Z)Z`t03HBiM^1R#?W#$IUvK5F~j-VbTJPEax4z6|zGjDm<4vKrS|VAm7D2Q4~( zC<7T(r#pGtG3iYg#nSNG!{y{yetr<>{OK^gB423otRY}7%(K%%iOH{ty64=@yXs17 zoHvVq&_K?|*AX%x6o@}<7H*Jy)9jst#AH#`um#^$zoXK8V&VWytBj1yddf;4^aYUVNw_Xy-!SXl-H&B1(If9SfJl?+W+Eg?3p*|@Cp~u+fRS9% zX@XK(YLX9XztntqhdN}G!g%ls!@S;S4i_X_8Pe>t_go`GjuAMje0$(Kj21`) zT}aeR+V(3%FccQ=tc&k_|td^JleljzEl< z3;JD<3LlkgH2uh#hziesQfLAB1k8}RJrmOCd#J7w1^O9{b9JsQrU1$aB3vFUy*}Em z0hd~|Mo@txC|kKQFaSoZg#vkXSm;H1IZrQven@{UvcHdd?rwMYY~Ytg*sKep{v81o zC+yKj8bDYQdX!NZb~PZq1JlWT_;R8oZN{WORh(=o5Ll|@`mxhYwLM0Lw6{RHZU2YQ zpKtTWh|l@3t*6+J+5)J(81c~#MK^N zEI)k+%HGO9HN=%Scx}_sJ$UsNuvDggf$mF0eHHG3l;;M!Ih%#QnHd9( zfLVB1nNV=f1d0nz@@c3o`D?{{XS@c31!sNj+*`S8*lodyeF>NVE6QV_mTUy@xRZ_k z-j_88Yo2Ay(zw5pv;C1EH~(*jB;gU5bPwEc>&+}{1a^PmwN?z~TG+jxjjtMl1Z{JY zR=wH`=JNlf_+79C>|yvkf|8&U;9F3Au|jTbQu?Nwe(Ty7m$PyJT!^8ISmd?CD6LoEKHgD z2Yg}e5OmtHA#>t5ueTCn)_ttjm{xn$s#thAag6r3R}(l zj+5kUD$(rlL=zk$^#TYjxMp6X6d|feNV6bYn6_ht^9u9{%XpXJZtN!Xe5lCFi9^)7Kpq6bRF1hUwq4n!XRS?OzrPe{tWIcz4VQytf1h!g^W`k(kT9If@nx zQpx1_7nXhmufwx)dGJF}AarVk#Hg|`+-?Buky{3NJB#paENb|;Ccx_cFA1wT;9>V& zW*;oe`RrOb)yOEKnq$NpN9b_^FzR0%p{HeOXTCv>%`gW`X3KG24uK?mbXtOFwO%0F zq=nqg#Q02Hv0@Ac-pFN_!jO7)u9!x9_=F1);j`fxo>Z2Bx88SbO$^z0OER=Qk_m4N zU;j*$qy8?_<=WJzLea3u@=kOgv|Z@5uh1v|3g-gy;smGbHN`~LzF$dyT2@wocZfg) zjay4%xZ)?2c&zVu`<$hL<4+HzutKc*EkkJ49w4-Ga9B`J;&Yv*uJPP7bi!+J{2b5b ztoWXI=;2Xx>XP;G1<+=1&VR>uenB5))r}Ck$Di^k1DrR3YB1le{o=npkdEUhwy@I@ z!e@+j=s38#aJSlf4h!@L@=@y@lW{l^&FBQ2mJeQ9)#|V0e1k$7LRx`HK>K9xFy}V9ogE{mc4qp&=fN}d zlFOe{-madb*Fym<)k_vY!30S29*lb7d(3yuM7riF05f)utb>1?@T8vX6~6B~-<8OF zmis``01fCmz8RcJ?pHZ@#*#7MZ_N_$nG=4I#Oi)8HO0V1A5ANOZ4r3u@na0P<=1o6 z;sFw6))DD3Mg7v8T|xlm+|&D#NeZI4ofRqUL*{-@OqOW~vdHQ!Ievs{*m-__v_0+X zKBd96xw1d}`R=0+>|+9~te7|Qszxj2wjDqWHr9B|cXWpf0{l}U`dd%(5eSD6X49AS za=2sC+nF56M5pn-i4Wsu0bd^2=(lFb6)XB*`YjAd+pyq0e{pz#jQbgbryPxabxBK3 z7SKx_nAh-irv@Lgmr6pS0gHcD*P(jFloR zd$c5To>?C28t?ic>ZfetkG=>nS^AM2G+>iv7uC=@T2Sx3zYyB`;g+A52n3L^u-LhF z7RK&oH`cTU{Sq#QzN#(O!3aS;Wtx}{6bqwH&vwuv#yc-9eyg$49rxD4oGJuGjHalK zXvrE)S8Nds;>2xjaYtU6s_we8z(eQpEC4|ME$e0o6(@%G{B}UY);^+!?DGSn+8}dz z)0uC#c2`r^TlCpJgKMDujQbX;iuK~w`hs%i&tz(?e*cV`HW*tj^Wy=|8{9K)v##&l zRm^sE;z43(KaRcY{H+LCT?s;%C=m%}J-@p!eGf|;r+N_$_^2kMU{~cjw=>ytXKum% z$;LY;C%-E#zdgf+NsotE_*+HBLyk3!v=z43J<}8Erj2^&&ZKKUP(2akaeAD2BI!eP z!33njqgxk(j;k7%B*y!suZ)Qj4i)z%S5|0g1u*K|u_j75cCHeQEPB;N*{SCnyr%`n zgy`YxRp%~(>C50&>B42_G#P$I0uMVf0A*P&Ao}$H+t12_QY3-pKg>&vX|mnSiOzC>;fJ~wvlTClue zJba^@-NY3naJHb;;#J36{2D^Y2FGG|gq0`OnDDjHlffGQVho>P1ua+u-q};L zj^upX<~vC--YXxkab;sa0#d!eN*D`*bMv>GhNgud>*gvTd!@zH|gzthH zs-<4OOSD<^Zd5?x?w9+#8Bpzx45l#45ij>-hrw46<8jz0`w9f77M1L$uMz^ThM3-H zZvrEbdUTzQIFL0XQieW?_wst{L&4gHiV!&_J6G0kpLDSrd4q)Klq>*($>^1LrqNUU zx3eRZi53mM8UmjoWZ2zz+`O6HGQvk{sS|(VEn`{5N&bc|kt}n;3D6i%p^4?huCNgY z@lxS$_#=3f%RnjST%Gr^VYYvq&9*S+9mE-$WYI7c$yX<*?6>#r;o`SmVV6?Luvz&w z`C7^`u)SwRSMW!H+SLD^lcT2j*?hE&p3sT? zOA!fF7B_>F@yBZr?S4}LYTNVJ^E`=YhbP*+4~mNRc=reif$ak1_e4!?BKUw;hNau9 zMd2(}*bR>J%$vx?k%A9hCEw4cDWWnKxt*0{3r1lgU8Txg)U1Cb=Rk>I16)#bAY&Bu z#M85ELtj;XzImnZGyEvdufKY{aoDuDH=m950jQ?xC#vJWJ9oF<9Euit$DEB!FI#U0 zH%_NUxrwW5f39M}uGbrU9TxDKWQ;yG&rmNrrjG(S0FLQmW%(9uq8MQr#!U@W$zW z+JEn5yQE^$Z$(Ob*nF{H?IafKb#@y<4q?}s`U|@k z9o<(jXY4Y5mE?}B*yAc!9@8=Dbl9VZ({cQD`Qn|$Gu*ec?d4G#6$nE#D)4Yx)a9BB z?Z_TF;WKtRpTL|bTDS~COP(-JWqrF>ycE1gzjs%S;-7^PWBB<+X%U@dlKMMmsh1wd z2a_54AP(s2OAC|l5;RwKcKc%^-M`~aWm_-kKubDYeiCtO0)^W~qyW3RE(W^N<2&UR zFsS0Nb7Q^-cZgZa3gK&zY+VI`haj;$26TVY1VgDxKS=?%zO)Q_{%Zp<;^2Mcp=fg{ zSrvhI6DCe4qrN;zOdt(~F4bg7_TK1FZ(xa1Au23cSz*N=4TJd>{@lJR>tl&G&LKV- z(U8US_1K5*?EKxy_RIGw_yAy{(@<_<%S!BF*>rB@b9ATEL7Obv)(;|6%zHD$yU7rl zCo2hFD|IK9L)a_-wQ)2!z@&^QdTX*KQ1=-6cOgEeMj*r;4HY0GH6_B{}09+s66hIZ868&00$BV+^uj}!T*JG@^3WJx&PlZkrN1B z>-vH}L)31-sw12t-@HN0BFH!xuj_wMM6}8=Oi@VWOZ{S#F`;7ozv@-&dLu@9?#ZyM z2_`kzL0-w#>}I0tyVlO~KG_5aN=tVyOWb)j91y=V1GU$~k2L)78W+paZw9>U`xZ@p z_AJgLxg@|yczexU+UZ}==haR#_aQ_l05Nw{SW@%ouZM$|jJt3IGN;ojrNkKv8Jdq3S*e?>SW>Nze2 zj>TV%;saTSVu5ZByx!A+WEH3qgDaIAk7{cr_{?g_pFMka^$`OC7FSXxvU>>Q-86G) z@~`mSR&ITgIH^4T$8Fs%-n@Czv)RXUhPJ+7&X3v90?P-URpP$zR$Y*J@iUTIo>9^b z+ok!G6MKV0Euj^KXc(%n)9%Oeev7&|J*awc2r!gcmMuZtreZ6f>VRs`KK>0*zrqwk zaZ|V~ngdS1iX89E#|W6$Tm4<_v5HV2-*>-U)WG-9^KdpPBipnTFNm(eEes5+rgmg! zz~c8}x!EVIK#m~g#e6&vB2nvzKv#qjA z_f>8XCE)p)=GvA$PPY zXRK%$#r%Z5ceURs$kPA59Hw?w(je=xVi@VOh1QU1Wr?XwXYH;S=81g|S~n=;Hl;J| zpeZLF>LCia7x4%nKdY*|_I{cxqC)Y=egbgNZMWkY(B}|40F+E$w=OSan3$$g3Cei2 z9U$w@{1{AkFK3Ih+4VOr;7YR}wSaS)?9#tTWhj41+3Q8UFJpv!c)iarF4*04IXcW@)<8x-gDj@FP1Zr?SYBw+qQu=xa3F03^WZ&~j_z$ooC+O`2eG+hOHc|rjvubn8TfD0r1>d8;l zUN&oI$mQ$ie86?vLveJtEEmQQcwj~L3AB!cG*jKLs=RlEW_WupydJ z`u+OIPUqzJ*5GT*7t*aREtKITpmJgCy#u23qDmASs@ru&G z-?9fToy2CpqWmeS1H&+>c8n0*^_clb3&5$Fa>7;Hhrq7?1K5EOZ_)^T>J{bqV`fcv zn6Io8t4`hy(CG2Rhj0arD`NHN{lKwR3OrSP8X;C)rA ztALmPA4*~-I0I6wI(}FBG>}& z{QHa6FOm?S{~HY|DV3Kx9d}r5Qioho8D=SsHmUK}$zR=O>KwtAnqT=V9FobR%bv&2 zR!70e(gbSS1N*6rs?_M~s@L-0P-I>NU*mi;YKe%TDrIwDb@VytUyk+9QENpGL4G@w zv)zSERfGt(U@v#?z%zRuY)n%4AX#CSE!MD>lPpsAJ(SIxSi`&U_%RBYORHN#B+u8; z-RnsC5W4&uo<(h208My6jx2Z(`Dg5~iHxW2D!MtLt|hV$7HGSJxvnIq6=ys~Yj(`J zjRbi8j&a2tAyjbnL{P3K7K1on-?<7>}ZVQInLM(NberOE0m0?yv2c!rq`AG|! za$$F0FfY2&fg^bK`wNxf)jat6cI&71rj3KFA8&bucYiU^lrPnK>usQWOy!s3MjF*_ZmI8C12ZBGB+OS#_k0M^f)@%C1rNj*0)SINo2wwN$D7vHkk)}T@NDdF3EzF1th+>uO~-;+ZY!Hl8wBXH@NBv)RYX`-NY^x7+rzNb zlaQygSNE%D23T*FTl_S#{K@I)?fq?CudWOQj{8}EiK_0Hd}bK?L~GTw3j*)~zwxDl z#w)3^m!IVDd$*Rlr25r^%r^wP zrj(;cGw}9W^E8IM>($S${9z78mkjkj^YNUREju$2fQ0AS{@1(QdaFDs4;mtaOD(!~ zGao%^nKOU9X#>y~qVSp*tV|KTzYFf%aYGE8xaUJSZz`g2Ssae|B>s>JJJ0-?khwgv zGM9{I3!lsSAnH5=-oOzZKfBk86i9o5K@#7d4Iiw>mXTB3rLyotbdFQtS#jG=@RuKh zEexH>qSrayOXoYUaJQ5of`=rlvXpM~Auc*{8v;3FlN7<2D?^cgyf5w9yTA)!mds8b z0}q{yB zY%sqH(1y*^0Pan7@I;s=uaxrKW4`)C-3Pn>hW+C@C)m*RKf+i_yFSTg5|Hs!!#jIN z1V+uaRyjZ}qeA!J^GUwo%~X+GiXXo-8wo;3g3heM-pKAv_Y=E$?+%O#3cowWHeG{} zDhs?Lf_uLZ;8D4A5a^;kQ_XQj*t>*tBX=CN{^gq1m|E}UWYvZp=%h1Funm?EGLwJd zZxAdPp2W_cA(#Ou_C4Eyl}J_V;NJ6%aV_C;!!&iiE+>E#71)(gzsmHlP#8{|aL$#g z$Ex{UEA7)D8VHg#zJ+CROVk9@~TFITYQA4 z6l->RZ;_b~O!g{)$0tUlLQ{uiJzS)_9AV;A?0O@^RHcZSpxsqJ?IvfJ{hLl)wm4!Z zfSQ73j08NluUx2+94AWSA|p(}t&2U`!VKL1lD9kWW;=V(m$Yu@SmT11tEG8PED++X z5mta3$PF<|@GQzJb=Xcnifyr|$y=G6xs^Y$?59U2YR1cSw(~h^~vk2yKE^wE^!E^1&w?(8L=AVw7c zjf_GV+xI4Riq_E;dGurYUUWBg5>|NEizht(7*CnWlvr-=MUK%~bSEmqQnUIPSl2(Q zlx9&hzmefDouMi5}96M9pSv1+vceo~X*)i-_enpzR>AEX2LVVzOA1KhYD*iBqI z2X@&eF!7<&k)HHQM8<{Ux&=WU^A9_pyBqI^UzJ7-MrI+E{s}Z}@X&EP<&e9vFm0zc zitZ}sn@svp4^G_Q`UXf@I?=4WOr{44mBgMzAtj9sO?$nU1z+T0-bF817SjyRe=Su} zAqQBGvrnkCADJI?-EV64HV81J{=s`=PJbuqJDa#5bK~Gbkgp$!8VMTUtlGDWZ71_- zG)mKM*5euw>G$NG{PXy9#OcBLhZ9Hc$cY;ZF^l$M98)4OcdRQQv)?mOji_H#T=ipZ z8q8G$?8Xm_P^~0Tuu~=c$hHRUu0^R%Y*r`tAX#f zEwUZFfz+HUe}6^Bne}Xx!^QdFeA;GD(p7x!Mk3MT)H8jD9ErJZsSURG_%XL7BAyjw zix~)O@&X~I6y>A#d;~V$46UZx2B|OYJPc}7{r|g2vMtI6URQm0F_lmA5{yHI4%wZ? zDu!}hIwd)D06#PlIzd0X!X&e;m~9P0#0C*kcQ@_Tk2pMrZG5(K4$=qP26W|uVLmq&1 z2nBvB;CDv^RX0?SIo$`qfB?Bm-%J@JAu=Qpi3rJ@(nf5U<<{M zpF#E}@W*bgy3Y67F$I2#W_Q?4mv*J#z5Y}Y2qEh~yF?sD9!xi|m6erYrs|~x^^2c^ z1A_$?(;pcVbA2dZm-h3MKl174LCP!Xg$9FfrY?0cp>pSbO=8oNXI>WhR`)dm8SG8=B{G;zs!S~hZ9?l1R)H^jc+SYzYaRS4&WH8M(+&^y={E!CBJc^iu&`<>ttQ2>(5-o$&FM z3*Ui|EN;_%bN%x(-Wz~oIA2AYzWnXT^7j7^;r0jpZ%KVg*gDI3gFxV>f)kh`lKu3n zF!7E+MRkW)sbflZTYHA}! zxlKvd3oc+~x+!R4Q9>mQGWvu2H;=ucgF9X4#uo4%x>E~1t!ssDYf@uhh~cQ~#v;l~x&L@@!*|&#!(GFy}$~9Cgu?P z9r1iX&Z}~o%3n55qIG@L>ewE~bGLokMLT^d6J6_?dtzEj5J-5>PO%E)q6}EOMdP^)HYmtbMx8hTibl#|8cOh9>>ysbEgq%RjJ#Ve76%{JAg9@ zrF8yT+i~jS0)b$F@)Ec);V1cnobu97ba$Uolrv$_Yf>?v)odZDL?V`(NJ@j5xITF*Mf%VYwYZ1|zSJsW0*k2RBTxPdOJsl@X*ppp zw~IKEpmV-iQJ>aXU$;k=HBAod%t%d(km!D(od2<3@*|EVA}9#kYU-I6K62zyU*Vlcl{`9`$dJ>;vo8y z3Ul}u6(+EOW+_2E@X219G06UhDc_ppcw>^jGHf=$138jDB0NyQmIJQ-Gf~}G!-@uZ z?O>W|=6o!y;O6#cfxh!!r+tFZ{-pLpB$7^KDv4yX1&RNtHvCjsQH}_#bqz}3Czj4Q zgU5I92kmZbI^cIm+Y_S|w;Na{&E68@u5ye^=u%@Bs2?<&5G)Q#$c=FREJ*aY3Hf9G zs{2KR2>#4nXB&e7A*oNHXH@0yGobR0q25GuN;dhC8EkwdUuugXcAdj;)&)*x=IfGL zT4H@p$+^|OcQTm?p%1jpQfth#sS>$2xGBp*GUZxC;=&iiNu^u#JeQuwv*HrEyY!Ol zRQEtY=I}SH9oRS!OW!fhW6*fZD2lkWhW*?j-5yb9(?{|CfH7{>oaR*O0l@Ih%Jjsz zJw8(0M;7{$eZ-O63X3R;p%{m6Mx)ck$xw^2I zkGl|vu0+Kbf>0kpP^+pS#Q!dcB)~y}b;!Gq#_}e1H zbY`up@om!)F^FRrIzyV6nl*9aj~!*lwFQ%8_qM3{L#qUOZ>cDb>7;PYbKt= zOlQx`8`qYvWGiewuZWO$NThRHM@7)MY@w_fxu$rkx5llS%8T&!!GE=-}r?yrz84w2rirjT?T!1u(}+YY}Z#;sni^Z|}ZQw8xh z0Ti7ZH~NZ zbZ!++H&3^dZnw8h=1!%~y_Mzca6?r@UNsOTyx|6-SEqq}`y{uSUY@r-SctNvc;( zw)U*&aJ~J5I%#Qj)*l4bqBBIMSCNz&#>|FYfqV|ZEFvV*u8Z@JK2nS5-w`pPxV(;$ za;A_!yu8dSlTGAS5n_C4mf{?9qI0GgY#vCiDCPRx<35;z2hELG2&FfkQp*qhHY42T z=&?j}!5D>3bzeSdji8UIXC8HCA~Of;aMNy zgl^^KtmEi-7X4fK`%Ji<&%5qNy~BmgmmC^tM1^d=gh1D#BK$?^mN~U)u$z*|%OKv` zUn8R2KOybUo91q{UEI*$d9kC|Q#x@2a$&P1;m@(N572a~8L3;(G6u(U5Y1vjqCIy@ z%qQ+$7ctx1u&`;ApqJyJTegSSD^{|Zuu0qaUzr?KlU?}~9ppI;S^3DBHm_Oc4hajUl1_s&$377iXxAZCK?9W4I@%UP8x{o zlRx#6FMOF#fi;1ifGb+{7{it*%!twVTSv`h4o;OGG%9GQi90ls44KZcvy$Jx(NPBq z7YVxMP9cgtI`)=M0Ams6nJ%}#Fa8L$)>|DZ+$?|IZ-xA{v*&A|d75C6+BlsZ2B^zx%_vCbJer111O}?h;VQC*(ub14*iPa;l`Yr@79>D zLG2^H3#JW`@qD*mf+3j~a2^vvN9#RBppqX7EG?cJF&uejP4d9D3|(7Wd+m@YVip7D zXkQ=^6EG;{2K;Uv`&pfRPZ0pDPP?A&0TKkFls^14nYXA;UyXI@IvbUq^L<4$YlKJ6 zaZ;QodrQ7IJyybD7+7ieVQ&BJDbH`S9SN-BHZ)I;p9>E9Sw^E6vI{`yMe}B&;}?us zyAp*zOxC)-Ynty#V-Y*|f4IVR-2(|mWOVd3kPvFtKC)gKe@Ff5M(lNhOir)J-oO*O zc*}0;=_IXzP3Rl8A66y>=3PsGLUmLDv0s^E`~?_cDZoH~kwZ-xu0u{%YCj~Xnkmcn z{~@8@5kCJxsTM6YeY4JHywYy8!j>Kq8|%>z0Dzv&aTkBYQQCb}jV^!qokB(R)2ewA zY!+-U|N5bC;2N${O z04mClvQ{%TU%GH4-L^Y98|OCLehidbO!~#f{S6Cwc>+B`NtF=mE=#TdCoNaO)O@nl zRRUByn5Rifd-4)Wb{#--ev|S5yuaJR04TZ6D{d`~(R$}mxhY#!dYpyj$pfLcQ~q_v z**A+ZG$w2W>bXK|wn->hTL#ivyvvYtTdxr@d%rX+XSg(Y+HfN|2GIN#?80YF zNZ4pU*%AAFe8LPsWNoo(ikMT-h0+qkinAvrALMx-k|Dx51-tvb#Ayx4YNa0pzq_f| ztlD>@bpM)>vKdQ+$46L{akLih2h_C_?_?grLFTh8;k*8kWt`4)yb`!m0Xl-=(?4W^HdHEeHv#c2%4yswwnpzI6SGU+P> z-Vpn#ah#6IC*OO@D;X+hL4&e(Y=EqP=U}7noyqdQ0+#pD?;EQq-dlArf|;FBwbPJC zX;*mdgRyc8$d1~x6q>-$4$wl&`Q8jK$gF98$cInTCJfZEk-IjZZZC4s*RwMe&7!Zc z%Om#Fg?1qv*JN+W1m`!i0GtG;*9`Gw03O#K&lh##QIh*Q4AqwqdU@3Bi@$KnKtLwn z&NYHQTw2|KVSvwVX;hQC0QeFdz=3F4`|?3E`)b(h9F_l|YP!C1`!uR&>$l`m`QJ~L z2J03dsSnf@Dt2Hl&YC8CNyZvrW`n4npPwA)&7VD7q*o#Cy zI}?!f_(|YFOBi)zWF$(~t8Me)`XUrfN|xjQvbwX1H&+KJH-tl12Dto3j|(7Q^uNBv zq%539P&zr-yg@X-)OS*oDYq}sQQ)!}*4#iTQ(@-%jX2)vS_diEUL5tAQF!yTXW>&U>L$LW>Q&7H6yRq5NhADb6=E zUiBm@=_Aiir;m#Gw(!z$$Okx8)NjWvp3EbT;-=S%C^t}MrW)L^xX;II4U>P1Mm>kYBG! zdP1oJ_tr3$Chmxv_Y2V*REyqA5SZ!Pu!uj)sz*x#q+!3# zNX6}2RZra$M4-{kvIl2)tA|0flaH`d%wmaX=YaF7OEUD`uk&iNL#)(f8diF3P>ZHA!1dxRvA0n8EYGT?M;#@=!lvNXhfc_|f#6eM03lRJMJfczdGLt80C89KN>&alN$TtS>2GedZ(>u4=R2-K;j6Y0b& z>a>_luN5{$PRWa*$9Hz_DayJL*UIS@P_(uZMW2)=#f@G>I+-ZS*KICUyp@XYQ!1Av z!5W#h@`xo^BGHvR4Z_fm2$cEp!u&cq18ir=5Yzb)Gjc#r%WWyc>C}4P{tL@~f17f= z@>abL4F2}Wg=p~f1$qJX+&#+bOKR`OZpRswCWsp(T=ArM!a_+wbJNKymQ%6V_{yv` zZKD>S`PPG(oTNafDXpVc0dyV!hvEdX`Y-)V8_u5OOO241qkConQ}P>o%S1nfQOdgu znmV4r#?Mga&(0jhE`XRpIsCE@CP!sqx`xQ_yUNT@>zOW*puJ5}Q@-2GAa&cg{M%VU z3PdvPmt_Kor4`G>t_&8AcaN%O9Q_05^ws@evZL7)klN*b;`#Z;5@}n`)4&S@<71QC z@C3w?)B@u~+2lk-#}x?Q2zWS0qw$7D)Rs;om#*|7&)Q;v;#HD3-iVDiHD5ji@!bx5 z=MdDLB8>7PR|6kPKDuWkVBi`>`voi{$?8aWZI6MfF8M~#zmla9IF7z-{i+2auKv&N(fHR!W#l*JOq_r&1* zz}`|%Sltho|L%C{;u0xa!iuSNq*^5e=ZW6WM;U%;D<2X}lO^e%*}`*{(0-94WN$TF zfA@6Who2U)g3$ui>a~CQDR8k16oNR7afVlZ`O%7jFDDmML=`@76V+5R5xyo*$eg-l zRUT0N^6exzfCli>IjTcHmDpR*r-7w9GyZpL;#Xf$TQ!zQmMWfxtp7V;OcBo=76B$$ zO9UQ0-AK9McGimnZ#Q0JRV8Gnq=&~zzWye z$_fKM!TB25!F%u$K=n^rZi{T{9DfK;AQ6draUd-MkvE0(cL}x{G2w#U;+{kMl}o#4O8WOss;uI27#_;%TJ@^dA&oC ziaW45LD)Rx@D6n4D+g9Eh|zx&qa=d4@3-?MV!XmO76NRiCRy8F4&6cnO=wDZZN>@w z5^Og8RyXIaQPOi(5(BlYQ6=>PPAwTnwjWKf!(v>?N)suz66H{=W2@2h%GMwWY`} zk~LVVp!&JD#6vLKAXv*k{iSqCw`SM8(I=1dKa3OW@Y*?5Rq)+e8zt`U>e2>P zSLMSmSBJnZSk7mw@?yZ)fr1a-R=i+Kl;)jv*9m5!xw!@&%r(5+tQEa{AI{j!U_U#m*2 z&IU7_VPs!jm4O&s&Ne$-O1kErlzuR&@EV!s^L0+kOUXz}D_*a})UnA81l-Xa(pqT0 z?FNbT0QJJxGD1hwBhWeNyh3B8C2nQZXWww^xsYO=pp(5$LK zUT*WP{fmN}?w*CA|5X_5XtK;iKbZXNwhAAbs@+|`W zK5uCetohrIFPESS$W@Ym&K#X8tSK>TGUYBev_nT>mM5wmOVv;y|9eX>zoQ7)S@p%k z+Y=DLF2jG;_51)qq_#OqZ3#3|`M}{Ef1#ihJ1vb5LGv;#RW{mn?w-A2gkKNHn!Tj3 zQ?iY_JMy#m7?$fmbC&YJ;YjlKje+hn-*di?73kZDR~Om~=jX6D)vb@DR^5E+5S+;s zzW9O(jVz%hdEq612_u(r`5I~(fO9W0thf$hH<5?QAEQ_%A{M&&Tq}M`HgOzx>+l0A zWWGs#6=HMITK^lp*(2~$&iA{Ud3z;H?yfJ|?0EY@iK@Hd$hF*(OuY}!!|x7xzpzg# zz%~Cka)lq2a3Rh|Sii3?z*LHV>Zzvh22KiLGb~>B!lP?iJ3vc{3EKeq=w(WrrxG$g zCO9)8;T4Z^_+k-CN9-b z>k1I)m6Zzs3=4RBePatPe4e(?``HBWciwxPLgxgfQQG61arw`Td8=H>0OpQ0@`}F<&-wzj?6u|p^j*sse7=%=8H-K6B8`uax9zFmX=2kfZ zz(uz&E#y7P7nKRQSL89?rDqac+TuxbV1`fx3Fuf{TExPqr);mlz2UOV9@!L|HI~92 zr{u6%JTf8wyN@$3reCIass9zVPKKH81j6yNdKZ4sf1`IPBXzBx?9@pFrk^qj<(Z5` zQz_$QdzH+%$OKIyk-!bSQ}4N2mDJZG=ec3%=4#jg>B|;ZBrtVj=D>>*sG?(iY0!8# z?$JiRiArhKPSEnJ6eS+V7$LC^z&ox zNJp)hXJs6!zU_N0SnF0k^%!Tis-KH?AK(v>_ns-2Q4*$bn zVecZ z9@+aYsuOXU((g8dz+)s6w!QJyC=VHfVsipqlCsG2aG~3);zoV`*I#`U9dv+8G29wr zO8l5-J~zsg;YbPOCuYKKSmaf~rCx4S#XtRuNNL;Wrg!I5i9!W09E8t~i7F|uha)6< zg|GofNOaA^dXLJ9t)B59gPyIOHxJH)i+sIG=u>1WJQ%X6jom&hG@)okrixOJ$UfwO zOH5?>s=(;8n!dF}MMA|rW7tfF(_vj)g|oF$2`NZuULK#qNqE$!@H`!&I#KZI(JLYI z;nIz{jMf-#=t(9yRUMkNS$oAFmvZW_|9vbvc-zG^dzGF<_ujwtj49my#5^+KB-v4_ zD@xnGhvIV3Is+R#{F127XJ{f;38@o&2gSQ_$MOb)A`x*P$ytn9c=q9iUM2i_DxDA{ zlUv`s!smaC!-;Tv4w#=C40$IA!|Bam#!DDZTezb_SoTRc=dvh+78%@vS9T9<5imd8 zVkBf!tJ$;n9RAj6gdXeSN7#tzDT4f4j%G0b^T0%j>V!boG#K(Yp^ePGfHyaYh{;a# zng{y}(CCUl&rVYf1`^glL&E4W!WyW6F$)qMREPMWkQ;?&{HNy6Vp1&fuE0cR1uI6w zFvNDtJU*oaca)`OaBHFr%$>yTiOc*oE&=q)Lr-A$F<`*h7NQKZ|2FulD7-L@E-$n% zMm5-~ghB`4{UCatpb{)hGUR;p;Gm2mpK3k$7OOA}Zc)$U<6maTc-rIkHYIXp1{j<*E9zueNa06ejMKR_D%z;_`d{5}d=>-a{SO*_ZPFM#8yVtloDzHvqhZ>2LoNE0 zj2h*_l-Ib z;lBj{8;l#!q}nML2?ZLc6_1)RcB?eN_dv=XHuCn$++z>d z+NnY~a_b3ynKrPsO*%*wtVf8{s)I8)oKD~4eJ-hA+&cQ}W}GCuySrjZ-?^q$o1t1> zJUFPcf7}Pe1WR&T`&>`Nv!%8t_P1A^SA(34v% zR(286D@1|W-7?c=vE+r?`gki2=qORpX_1F`KF|O*IUGQ$0 zr|-({g1qI@!x=>%!$#0k*FF3aYy-7d3fhzRLESU_!B@4%IF(BdQ3(nXD`h9W?YV_r z!uc)E3sF8B6`}vgJZf1EH&>BqS$6ZQ{@**9bqn-*O!J%Y#8R%igi}@w-~6=pV7lD; z_I@gSSC6#~T-U7R^Apq33cyV6EeyvZDmz5`Lh#D-gx?AKCY%JkiYQ!-xDc!un7KQ! zRsZ?^!u}KsN6NR}!e3XYE;Dn(@ytd;=m-(90&I`5G8Lx@v&}uD6Nr@gZYBEb3fTb_ zgPX!i_Y=aEz(8%{|2FpDVf^m``R{x7Z#xK{_WG^3i+29>mhjb4qFdMQ{8oI`JotYA DdOSk} literal 37011 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 From 891539292343668c22aa5b7fd765b1b6ad769432 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Sep 2017 18:53:01 -0400 Subject: [PATCH 033/151] Make gl-components use pre-created canvases --- src/plot_api/plot_api.js | 2 ++ src/plots/gl2d/scene2d.js | 6 ++---- src/plots/gl3d/scene.js | 6 ++---- src/traces/contourgl/index.js | 2 +- src/traces/heatmapgl/index.js | 2 +- src/traces/mesh3d/index.js | 2 +- src/traces/pointcloud/index.js | 2 +- src/traces/scatter3d/index.js | 2 +- src/traces/scattergl/index.js | 2 +- src/traces/surface/index.js | 2 +- 10 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 15f1e454153..20141cf1cb1 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -3056,6 +3056,8 @@ function makePlotFramework(gd) { .style('position', 'absolute') .style('top', 0) .style('left', 0) + .style('width', '100%') + .style('height', '100%') .style('pointer-events', 'none') .style('overflow', 'visible'); diff --git a/src/plots/gl2d/scene2d.js b/src/plots/gl2d/scene2d.js index 57b9e6f9bbf..a19404f4f3f 100644 --- a/src/plots/gl2d/scene2d.js +++ b/src/plots/gl2d/scene2d.js @@ -111,7 +111,7 @@ proto.makeFramework = function() { this.gl = STATIC_CONTEXT; } else { - var liveCanvas = document.createElement('canvas'); + var liveCanvas = this.container.querySelector('.gl-canvas-focus'); var gl = getContext({ canvas: liveCanvas, @@ -139,7 +139,7 @@ proto.makeFramework = function() { // disabling user select on the canvas // sanitizes double-clicks interactions // ref: https://github.com/plotly/plotly.js/issues/744 - canvas.className += 'user-select-none'; + canvas.className += ' user-select-none'; // create SVG container for hover text var svgContainer = this.svgContainer = document.createElementNS( @@ -158,7 +158,6 @@ proto.makeFramework = function() { // append canvas, hover svg and mouse div to container var container = this.container; - container.appendChild(canvas); container.appendChild(svgContainer); container.appendChild(mouseContainer); @@ -369,7 +368,6 @@ proto.destroy = function() { this.glplot.dispose(); - if(!this.staticPlot) this.container.removeChild(this.canvas); this.container.removeChild(this.svgContainer); this.container.removeChild(this.mouseContainer); diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index 071310a78ec..98f0f329359 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -136,9 +136,9 @@ function render(scene) { function initializeGLPlot(scene, fullLayout, canvas, gl) { var glplotOptions = { - canvas: canvas, gl: gl, container: scene.container, + canvas: scene.container.querySelector('.gl-canvas-focus'), axes: scene.axesOptions, spikes: scene.spikeOptions, pickRadius: 10, @@ -228,8 +228,7 @@ function initializeGLPlot(scene, fullLayout, canvas, gl) { function Scene(options, fullLayout) { // create sub container for plot - var sceneContainer = document.createElement('div'); - var plotContainer = options.container; + var sceneContainer = fullLayout._glcontainer.node(); // keep a ref to the graph div to fire hover+click events this.graphDiv = options.graphDiv; @@ -251,7 +250,6 @@ function Scene(options, fullLayout) { sceneContainer.style.position = 'absolute'; sceneContainer.style.top = sceneContainer.style.left = '0px'; sceneContainer.style.width = sceneContainer.style.height = '100%'; - plotContainer.appendChild(sceneContainer); this.fullLayout = fullLayout; this.id = options.id || 'scene'; diff --git a/src/traces/contourgl/index.js b/src/traces/contourgl/index.js index ac4fca3b72d..a1d6f961cd6 100644 --- a/src/traces/contourgl/index.js +++ b/src/traces/contourgl/index.js @@ -21,7 +21,7 @@ ContourGl.plot = require('./convert'); ContourGl.moduleType = 'trace'; ContourGl.name = 'contourgl'; ContourGl.basePlotModule = require('../../plots/gl2d'); -ContourGl.categories = ['gl2d', '2dMap']; +ContourGl.categories = ['gl', 'gl2d', '2dMap']; ContourGl.meta = { description: [ 'WebGL contour (beta)' diff --git a/src/traces/heatmapgl/index.js b/src/traces/heatmapgl/index.js index 19ac6fe15f4..dca703e7f85 100644 --- a/src/traces/heatmapgl/index.js +++ b/src/traces/heatmapgl/index.js @@ -21,7 +21,7 @@ HeatmapGl.plot = require('./convert'); HeatmapGl.moduleType = 'trace'; HeatmapGl.name = 'heatmapgl'; HeatmapGl.basePlotModule = require('../../plots/gl2d'); -HeatmapGl.categories = ['gl2d', '2dMap']; +HeatmapGl.categories = ['gl', 'gl2d', '2dMap']; HeatmapGl.meta = { description: [ 'WebGL version of the heatmap trace type.' diff --git a/src/traces/mesh3d/index.js b/src/traces/mesh3d/index.js index 8506fb81814..41d6b49b12b 100644 --- a/src/traces/mesh3d/index.js +++ b/src/traces/mesh3d/index.js @@ -20,7 +20,7 @@ Mesh3D.plot = require('./convert'); Mesh3D.moduleType = 'trace'; Mesh3D.name = 'mesh3d', Mesh3D.basePlotModule = require('../../plots/gl3d'); -Mesh3D.categories = ['gl3d']; +Mesh3D.categories = ['gl', 'gl3d']; Mesh3D.meta = { description: [ 'Draws sets of triangles with coordinates given by', diff --git a/src/traces/pointcloud/index.js b/src/traces/pointcloud/index.js index b5cef7bdd2c..dd710caafad 100644 --- a/src/traces/pointcloud/index.js +++ b/src/traces/pointcloud/index.js @@ -20,7 +20,7 @@ pointcloud.plot = require('./convert'); pointcloud.moduleType = 'trace'; pointcloud.name = 'pointcloud'; pointcloud.basePlotModule = require('../../plots/gl2d'); -pointcloud.categories = ['gl2d', 'showLegend']; +pointcloud.categories = ['gl', 'gl2d', 'showLegend']; pointcloud.meta = { description: [ 'The data visualized as a point cloud set in `x` and `y`', diff --git a/src/traces/scatter3d/index.js b/src/traces/scatter3d/index.js index 0e77661748c..a426d2dfd4b 100644 --- a/src/traces/scatter3d/index.js +++ b/src/traces/scatter3d/index.js @@ -20,7 +20,7 @@ Scatter3D.calc = require('./calc'); Scatter3D.moduleType = 'trace'; Scatter3D.name = 'scatter3d'; Scatter3D.basePlotModule = require('../../plots/gl3d'); -Scatter3D.categories = ['gl3d', 'symbols', 'markerColorscale', 'showLegend']; +Scatter3D.categories = ['gl', 'gl3d', 'symbols', 'markerColorscale', 'showLegend']; Scatter3D.meta = { hrName: 'scatter_3d', description: [ diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 35d292f1c90..6ad0666d16a 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -23,7 +23,7 @@ ScatterGl.selectPoints = require('./select'); ScatterGl.moduleType = 'trace'; ScatterGl.name = 'scattergl'; ScatterGl.basePlotModule = require('../../plots/gl2d'); -ScatterGl.categories = ['gl2d', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; +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`', diff --git a/src/traces/surface/index.js b/src/traces/surface/index.js index 03e64f5762e..b756fe59144 100644 --- a/src/traces/surface/index.js +++ b/src/traces/surface/index.js @@ -20,7 +20,7 @@ Surface.plot = require('./convert'); Surface.moduleType = 'trace'; Surface.name = 'surface'; Surface.basePlotModule = require('../../plots/gl3d'); -Surface.categories = ['gl3d', '2dMap', 'noOpacity']; +Surface.categories = ['gl', 'gl3d', '2dMap', 'noOpacity']; Surface.meta = { description: [ 'The data the describes the coordinates of the surface is set in `z`.', From 368fe44f16b154aa44a1e828a9b1f546f4e5ca01 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 10:48:36 -0400 Subject: [PATCH 034/151] Fix karma tests in windows --- tasks/util/strict_d3.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasks/util/strict_d3.js b/tasks/util/strict_d3.js index 1e51ab8912b..18b8ff25915 100644 --- a/tasks/util/strict_d3.js +++ b/tasks/util/strict_d3.js @@ -18,7 +18,7 @@ module.exports = transformTools.makeRequireTransform('requireTransform', var pathOut; if(pathIn === 'd3' && opts.file !== pathToStrictD3Module) { - pathOut = 'require(\'' + pathToStrictD3Module + '\')'; + pathOut = 'require(' + JSON.stringify(pathToStrictD3Module) + ')'; } if(pathOut) return cb(null, pathOut); From 4710c1265d5c614fe349c186e6e48fe23602c889 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 11:27:41 -0400 Subject: [PATCH 035/151] Update canvas size properly --- src/plot_api/plot_api.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 20141cf1cb1..2c124625bdf 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -3051,8 +3051,6 @@ function makePlotFramework(gd) { .attr('class', function(d) { return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); }) - .attr('width', fullLayout.width) - .attr('height', fullLayout.height) .style('position', 'absolute') .style('top', 0) .style('left', 0) @@ -3065,6 +3063,10 @@ function makePlotFramework(gd) { } } + fullLayout._glcanvas + .attr('width', fullLayout.width) + .attr('height', fullLayout.width); + fullLayout._paperdiv.selectAll('.main-svg').remove(); fullLayout._paper = fullLayout._paperdiv.insert('svg', ':first-child') From 6ebd70cd2cbe876428e3e4b866cd08ca62a65735 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 11:34:01 -0400 Subject: [PATCH 036/151] Revert 3d-plots shared context changes --- src/plots/gl3d/scene.js | 6 ++++-- src/traces/mesh3d/index.js | 2 +- src/traces/scatter3d/index.js | 2 +- src/traces/surface/index.js | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/plots/gl3d/scene.js b/src/plots/gl3d/scene.js index 98f0f329359..071310a78ec 100644 --- a/src/plots/gl3d/scene.js +++ b/src/plots/gl3d/scene.js @@ -136,9 +136,9 @@ function render(scene) { function initializeGLPlot(scene, fullLayout, canvas, gl) { var glplotOptions = { + canvas: canvas, gl: gl, container: scene.container, - canvas: scene.container.querySelector('.gl-canvas-focus'), axes: scene.axesOptions, spikes: scene.spikeOptions, pickRadius: 10, @@ -228,7 +228,8 @@ function initializeGLPlot(scene, fullLayout, canvas, gl) { function Scene(options, fullLayout) { // create sub container for plot - var sceneContainer = fullLayout._glcontainer.node(); + var sceneContainer = document.createElement('div'); + var plotContainer = options.container; // keep a ref to the graph div to fire hover+click events this.graphDiv = options.graphDiv; @@ -250,6 +251,7 @@ function Scene(options, fullLayout) { sceneContainer.style.position = 'absolute'; sceneContainer.style.top = sceneContainer.style.left = '0px'; sceneContainer.style.width = sceneContainer.style.height = '100%'; + plotContainer.appendChild(sceneContainer); this.fullLayout = fullLayout; this.id = options.id || 'scene'; diff --git a/src/traces/mesh3d/index.js b/src/traces/mesh3d/index.js index 41d6b49b12b..8506fb81814 100644 --- a/src/traces/mesh3d/index.js +++ b/src/traces/mesh3d/index.js @@ -20,7 +20,7 @@ Mesh3D.plot = require('./convert'); Mesh3D.moduleType = 'trace'; Mesh3D.name = 'mesh3d', Mesh3D.basePlotModule = require('../../plots/gl3d'); -Mesh3D.categories = ['gl', 'gl3d']; +Mesh3D.categories = ['gl3d']; Mesh3D.meta = { description: [ 'Draws sets of triangles with coordinates given by', diff --git a/src/traces/scatter3d/index.js b/src/traces/scatter3d/index.js index a426d2dfd4b..0e77661748c 100644 --- a/src/traces/scatter3d/index.js +++ b/src/traces/scatter3d/index.js @@ -20,7 +20,7 @@ Scatter3D.calc = require('./calc'); Scatter3D.moduleType = 'trace'; Scatter3D.name = 'scatter3d'; Scatter3D.basePlotModule = require('../../plots/gl3d'); -Scatter3D.categories = ['gl', 'gl3d', 'symbols', 'markerColorscale', 'showLegend']; +Scatter3D.categories = ['gl3d', 'symbols', 'markerColorscale', 'showLegend']; Scatter3D.meta = { hrName: 'scatter_3d', description: [ diff --git a/src/traces/surface/index.js b/src/traces/surface/index.js index b756fe59144..03e64f5762e 100644 --- a/src/traces/surface/index.js +++ b/src/traces/surface/index.js @@ -20,7 +20,7 @@ Surface.plot = require('./convert'); Surface.moduleType = 'trace'; Surface.name = 'surface'; Surface.basePlotModule = require('../../plots/gl3d'); -Surface.categories = ['gl', 'gl3d', '2dMap', 'noOpacity']; +Surface.categories = ['gl3d', '2dMap', 'noOpacity']; Surface.meta = { description: [ 'The data the describes the coordinates of the surface is set in `z`.', From 8546576ab74ac4f888779ea328c5fcc08e2a4d78 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 12:31:01 -0400 Subject: [PATCH 037/151] Fix glcanvas resizing typo --- src/plot_api/plot_api.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 2c124625bdf..de28d120abc 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -3065,7 +3065,7 @@ function makePlotFramework(gd) { fullLayout._glcanvas .attr('width', fullLayout.width) - .attr('height', fullLayout.width); + .attr('height', fullLayout.height); fullLayout._paperdiv.selectAll('.main-svg').remove(); From 7ed4484dd6ca28ddd659a728db805af7210e818c Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 15:13:17 -0400 Subject: [PATCH 038/151] Make hasCategory method --- src/plots/plots.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/plots/plots.js b/src/plots/plots.js index e8ff7e50bc8..9e29fcbfa9b 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -458,6 +458,7 @@ plots.supplyDefaults = function(gd) { // attach helper method to check whether a plot type is present on graph newFullLayout._has = plots._hasPlotType.bind(newFullLayout); + newFullLayout._hasCategory = plots._hasCategory.bind(newFullLayout); // special cases that introduce interactions between traces var _modules = newFullLayout._modules; @@ -576,6 +577,21 @@ plots._hasPlotType = function(category) { return false; }; +// check whether trace has a category +plots._hasCategory = function(category) { + var modules = this._modules || []; + + // create canvases only in case if there is at least one regl component + for(var i = 0; i < modules.length; i++) { + var _ = modules[i]; + if(_.categories && _.categories.indexOf(category) >= 0) { + return true; + } + } + + return false; +}; + plots.cleanPlot = function(newFullData, newFullLayout, oldFullData, oldFullLayout) { var i, j; From c399fd957bb1c7fc1586768d45983af0d8a255fd Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 15:52:16 -0400 Subject: [PATCH 039/151] Make use of _hasCategory method --- src/plot_api/plot_api.js | 52 +++++++++++++++++----------------------- 1 file changed, 22 insertions(+), 30 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index de28d120abc..3097eb0e9c9 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -194,6 +194,24 @@ Plotly.plot = function(gd, data, layout, config) { } } + if(fullLayout._hasCategory('gl') && fullLayout._glcanvas.empty()) { + fullLayout._glcanvas.enter().append('canvas') + .attr('class', function(d) { + return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); + }) + .style('position', 'absolute') + .style('top', 0) + .style('left', 0) + .style('width', '100%') + .style('height', '100%') + .style('pointer-events', 'none') + .style('overflow', 'visible'); + } + + fullLayout._glcanvas + .attr('width', fullLayout.width) + .attr('height', fullLayout.height); + return Lib.syncOrAsync([ subroutines.layoutStyles ], gd); @@ -3029,11 +3047,10 @@ function makePlotFramework(gd) { fullLayout._glcontainer = fullLayout._paperdiv.selectAll('.gl-container') .data([{}]); - // FIXME: bring this constant to some plotly constants module - // it is taken from parcoords lineLayerModel - fullLayout._glcanvas = fullLayout._glcontainer.enter().append('div') - .classed('gl-container', true) - .selectAll('.gl-canvas') + fullLayout._glcontainer.enter().append('div') + .classed('gl-container', true); + + fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas') .data([{ key: 'contextLayer' }, { @@ -3042,31 +3059,6 @@ function makePlotFramework(gd) { key: 'pickLayer' }]); - // create canvases only in case if there is at least one regl component - // FIXME: probably there is a better d3 way of doing so - for(var i = 0; i < fullLayout._modules.length; i++) { - var module = fullLayout._modules[i]; - if(module.categories && module.categories.indexOf('gl') >= 0) { - fullLayout._glcanvas.enter().append('canvas') - .attr('class', function(d) { - return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); - }) - .style('position', 'absolute') - .style('top', 0) - .style('left', 0) - .style('width', '100%') - .style('height', '100%') - .style('pointer-events', 'none') - .style('overflow', 'visible'); - - break; - } - } - - fullLayout._glcanvas - .attr('width', fullLayout.width) - .attr('height', fullLayout.height); - fullLayout._paperdiv.selectAll('.main-svg').remove(); fullLayout._paper = fullLayout._paperdiv.insert('svg', ':first-child') From 0454d649ffb35b371c04cd88806233d0a82d281b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 16:30:09 -0400 Subject: [PATCH 040/151] Fix canvas counting cases --- test/jasmine/tests/gl_plot_interact_test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js index 7e06a5de92f..39682498707 100644 --- a/test/jasmine/tests/gl_plot_interact_test.js +++ b/test/jasmine/tests/gl_plot_interact_test.js @@ -1286,7 +1286,7 @@ describe('Test gl plot side effects', function() { return Plotly.plot(gd, data); }).then(function() { - countCanvases(1); + countCanvases(3); return Plotly.purge(gd); }).then(function() { @@ -1294,7 +1294,7 @@ describe('Test gl plot side effects', function() { return Plotly.plot(gd, data); }).then(function() { - countCanvases(1); + countCanvases(3); return Plotly.deleteTraces(gd, [0]); }).then(function() { From 9b73d70584b0fd56daca0107cd83f7ae565725e8 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 18:19:50 -0400 Subject: [PATCH 041/151] Make proper canvas recycling --- src/plot_api/plot_api.js | 46 +++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 24 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 3097eb0e9c9..5d536b1d504 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -194,24 +194,30 @@ Plotly.plot = function(gd, data, layout, config) { } } - if(fullLayout._hasCategory('gl') && fullLayout._glcanvas.empty()) { - fullLayout._glcanvas.enter().append('canvas') - .attr('class', function(d) { - return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); - }) - .style('position', 'absolute') - .style('top', 0) - .style('left', 0) - .style('width', '100%') - .style('height', '100%') - .style('pointer-events', 'none') - .style('overflow', 'visible'); - } - - fullLayout._glcanvas + fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas').data(fullLayout._hasCategory('gl') ? [{ + key: 'contextLayer' + }, { + key: 'focusLayer' + }, { + key: 'pickLayer' + }] : []); + + fullLayout._glcanvas.enter().append('canvas') + .attr('class', function(d) { + return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); + }) + .style('position', 'absolute') + .style('top', 0) + .style('left', 0) + .style('width', '100%') + .style('height', '100%') + .style('pointer-events', 'none') + .style('overflow', 'visible') .attr('width', fullLayout.width) .attr('height', fullLayout.height); + fullLayout._glcanvas.exit().remove(); + return Lib.syncOrAsync([ subroutines.layoutStyles ], gd); @@ -3049,15 +3055,7 @@ function makePlotFramework(gd) { fullLayout._glcontainer.enter().append('div') .classed('gl-container', true); - - fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas') - .data([{ - key: 'contextLayer' - }, { - key: 'focusLayer' - }, { - key: 'pickLayer' - }]); + fullLayout._glcanvas; fullLayout._paperdiv.selectAll('.main-svg').remove(); From dbc23e53f869bf0b2ed6918ea95f12f0c89f0845 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 7 Sep 2017 18:27:48 -0400 Subject: [PATCH 042/151] Poke CI --- src/plot_api/plot_api.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 5d536b1d504..c17f12befaa 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -3055,7 +3055,9 @@ function makePlotFramework(gd) { fullLayout._glcontainer.enter().append('div') .classed('gl-container', true); - fullLayout._glcanvas; + + // That is initialized in drawFramework if there are `gl` traces + fullLayout._glcanvas = null; fullLayout._paperdiv.selectAll('.main-svg').remove(); From 97a6a58fc9973cc4d21b731e881eb876347a4da9 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 14 Sep 2017 14:25:19 -0400 Subject: [PATCH 043/151] Reuse global canvas for regl component --- src/traces/scatterregl/index.js | 1 + src/traces/scatterregl/plot.js | 176 ++++++++++++++------------------ 2 files changed, 78 insertions(+), 99 deletions(-) diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index b1b223b7b1f..bb385c247d2 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -16,5 +16,6 @@ Scatter.name = 'scatterregl'; Scatter.plot = require('./plot'); Scatter.calc = require('./calc'); Scatter.hoverPoints = require('./hover'); +Scatter.categories = ['gl', 'gl2d', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; module.exports = Scatter; diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index f1b57b4c975..418c2fd3e0b 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -18,9 +18,8 @@ var subTypes = require('../scatter/subtypes'); var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); var getTraceColor = require('../scatter/get_trace_color'); var DASHES = require('../../constants/gl2d_dashes'); -var fit = require('canvas-fit'); var createScatter = require('regl-scatter2d'); -// var createLine = require('../../../../regl-line2d'); +var createLine = require('regl-line2d'); var Drawing = require('../../components/drawing'); var svgSdf = require('svg-path-sdf'); @@ -42,36 +41,15 @@ module.exports = createLineWithMarkers; function createLineWithMarkers(container, plotinfo, cdscatter) { var layout = container._fullLayout; - var glContainer = container.querySelector('.gl-container'); - // FIXME: find proper way to get plot holder - // FIXME: handle multiple subplots var subplotObj = layout._plots.xy; var scatter = subplotObj._scatter2d; // create regl-scatter, if not defined if(scatter === undefined) { // TODO: enhance picking - // TODO: decide whether we should share canvas or create it every scatter plot - // TODO: decide if canvas should be the full-width with viewport or multiple instances - // FIXME: avoid forcing absolute style by disabling forced plotly background // TODO: figure out if there is a way to detect only new passed options - var canvas = glContainer.appendChild(document.createElement('canvas')); - // FIXME: make sure this is the right place for that - glContainer.style.height = '100%'; - glContainer.style.width = '100%'; - - canvas.style.position = 'absolute'; - canvas.style.top = '0px'; - canvas.style.left = '0px'; - canvas.style.pointerEvents = 'none'; - - // TODO: fit canvas every window.resize or plotly.resize or whatever - fit(canvas, glContainer); - - container.glContainer = glContainer; - container.canvas = canvas; scatter = subplotObj._scatter2d = new ScatterScene(container); } @@ -111,7 +89,9 @@ function ScatterScene(container) { this.xaxis = container._fullLayout.xaxis; this.yaxis = container._fullLayout.yaxis; - var scatterOptions0 = { + this.canvas = container.querySelector('.gl-canvas-focus') + + this.scatterOptions = { positions: Array(), sizes: [], colors: [], @@ -123,28 +103,27 @@ function ScatterScene(container) { borderSize: 1, borderColor: [0, 0, 0, 1], snapPoints: true, - canvas: container.canvas, + canvas: this.canvas, pixelRatio: container._context.plotGlPixelRatio || window.devicePixelRatio }; - this.scatter = createScatter(scatterOptions0); - this.scatter.options = scatterOptions0; - this.scatter._trace = this; - + this.scatter = createScatter(this.scatterOptions); - // this.line = 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.lineOptions = { + positions: new Float64Array(0), + color: [0, 0, 0, 1], + thickness: 1, + canvas: this.canvas, + // fill: [false, false, false, false], + // fillColor: [ + // [0, 0, 0, 1], + // [0, 0, 0, 1], + // [0, 0, 0, 1], + // [0, 0, 0, 1]], + dashes: [1], + }; + this.line = createLine(this.lineOptions); return this; } @@ -269,7 +248,7 @@ proto.updateFancy = function(options) { positions = positions.slice(0, ptr); this.idToIndex = idToIndex; - // this.updateLines(options, positions); + this.updateLines(options, positions); // this.updateError('X', options, positions, errorsX); // this.updateError('Y', options, positions, errorsY); @@ -283,16 +262,16 @@ proto.updateFancy = function(options) { } if(this.hasMarkers) { - this.scatter.options.positions = positions; + this.scatterOptions.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.markers = new Array(pId); - this.scatter.options.borderSizes = new Array(pId); - this.scatter.options.colors = new Array(pId); - this.scatter.options.borderColors = new Array(pId); + this.scatterOptions.sizes = new Array(pId); + this.scatterOptions.markers = new Array(pId); + this.scatterOptions.borderSizes = new Array(pId); + this.scatterOptions.colors = new Array(pId); + this.scatterOptions.borderColors = new Array(pId); var markerSizeFunc = makeBubbleSizeFn(options); @@ -333,10 +312,10 @@ proto.updateFancy = function(options) { size = sizes[index]; bw = borderSizes[index]; - this.scatter.options.sizes[i] = size; + this.scatterOptions.sizes[i] = size; if(symbol === 'circle') { - this.scatter.options.markers[i] = null; + this.scatterOptions.markers[i] = null; } else { // get symbol sdf from cache or generate it @@ -359,11 +338,11 @@ proto.updateFancy = function(options) { SYMBOL_SDF[symbol] = symbolSdf; } - this.scatter.options.markers[i] = symbolSdf || null; + this.scatterOptions.markers[i] = symbolSdf || null; } - this.scatter.options.borderSizes[i] = 0.5 * bw; + this.scatterOptions.borderSizes[i] = 0.5 * bw; - var optColors = this.scatter.options.colors; + var optColors = this.scatterOptions.colors; var dim = isDimmed ? DESELECTDIM : 1; if(!optColors[i]) optColors[i] = []; if(isOpen || symbolNoFill) { @@ -377,11 +356,11 @@ proto.updateFancy = function(options) { optColors[i][2] = _colors[4 * index + 2] * 255; optColors[i][3] = dim * _colors[4 * index + 3] * 255; } - if(!this.scatter.options.borderColors[i]) this.scatter.options.borderColors[i] = []; - this.scatter.options.borderColors[i][0] = _borderColors[4 * index + 0] * 255; - this.scatter.options.borderColors[i][1] = _borderColors[4 * index + 1] * 255; - this.scatter.options.borderColors[i][2] = _borderColors[4 * index + 2] * 255; - this.scatter.options.borderColors[i][3] = dim * _borderColors[4 * index + 3] * 255; + if(!this.scatterOptions.borderColors[i]) this.scatterOptions.borderColors[i] = []; + this.scatterOptions.borderColors[i][0] = _borderColors[4 * index + 0] * 255; + this.scatterOptions.borderColors[i][1] = _borderColors[4 * index + 1] * 255; + this.scatterOptions.borderColors[i][2] = _borderColors[4 * index + 2] * 255; + this.scatterOptions.borderColors[i][3] = dim * _borderColors[4 * index + 3] * 255; } @@ -400,12 +379,11 @@ proto.updateFancy = function(options) { var range = [xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1]]; - this.scatter.options.range = range; - this.scatter.options.viewport = viewBox; - + this.scatterOptions.range = range; + this.scatterOptions.viewport = viewBox; // prevent scatter from resnapping points - this.scatter(this.scatter.options); + this.scatter(this.scatterOptions); } // add item for autorange routine @@ -417,52 +395,52 @@ proto.updateFancy = function(options) { proto.updateLines = function(options, positions) { var i; - if(this.hasLines) { - var linePositions = positions; + if (!this.hasLines) return - if(!options.connectgaps) { - var p = 0; - var x = this.xData; - var y = this.yData; - linePositions = new Float64Array(2 * x.length); + console.log(options, positions) +return; - for(i = 0; i < x.length; ++i) { - linePositions[p++] = x[i]; - linePositions[p++] = y[i]; - } - } + var linePositions = positions; - this.line.options.positions = linePositions; + if(!options.connectgaps) { + var p = 0; + var x = this.xData; + var y = this.yData; + linePositions = new Float64Array(2 * x.length); - 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 < x.length; ++i) { + linePositions[p++] = x[i]; + linePositions[p++] = y[i]; + } + } - for(i = 0; i < dashes.length; ++i) dashes[i] *= lineWidth; + this.lineOptions.positions = linePositions; - 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); + var lineColor = convertColor(options.line.color, options.opacity, 1), + lineWidth = Math.round(0.5 * this.lineOptions.width), + dashes = (DASHES[options.line.dash] || [1]).slice(); - 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]; + for(i = 0; i < dashes.length; ++i) dashes[i] *= lineWidth; - this.line.update(); - } - else { - this.line.clear(); + switch(options.fill) { + case 'tozeroy': + this.lineOptions.fill = [false, true, false, false]; + break; + case 'tozerox': + this.lineOptions.fill = [true, false, false, false]; + break; + default: + this.lineOptions.fill = [false, false, false, false]; + break; } + var fillColor = str2RGBArray(options.fillcolor); + + this.lineOptions.color = lineColor; + this.lineOptions.width = 2.0 * options.line.width; + this.lineOptions.dashes = dashes; + this.lineOptions.fillColor = [fillColor, fillColor, fillColor, fillColor]; + + this.line.update(); }; proto.updateError = function(axLetter, options, positions, errors) { From 701ea44efdfaec5f3332e5fa8e9ca8bd21c21891 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 18 Sep 2017 16:09:10 -0400 Subject: [PATCH 044/151] Introduce regl-line2d --- src/traces/scatterregl/plot.js | 77 +++++++++++++++++++++------------- 1 file changed, 48 insertions(+), 29 deletions(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 418c2fd3e0b..5c87c715d35 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -22,6 +22,7 @@ var createScatter = require('regl-scatter2d'); var createLine = require('regl-line2d'); var Drawing = require('../../components/drawing'); var svgSdf = require('svg-path-sdf'); +var createRegl = require('regl'); var DESELECTDIM = 0.2; var TRANSPARENT = [0, 0, 0, 0]; @@ -89,9 +90,14 @@ function ScatterScene(container) { this.xaxis = container._fullLayout.xaxis; this.yaxis = container._fullLayout.yaxis; - this.canvas = container.querySelector('.gl-canvas-focus') + this.canvas = container.querySelector('.gl-canvas-focus'); + this.regl = createRegl({ + canvas: this.canvas, + extensions: ['ANGLE_instanced_arrays', 'OES_element_index_uint'] + }); this.scatterOptions = { + regl: this.regl, positions: Array(), sizes: [], colors: [], @@ -110,9 +116,11 @@ function ScatterScene(container) { this.scatter = createScatter(this.scatterOptions); this.lineOptions = { + regl: this.regl, positions: new Float64Array(0), color: [0, 0, 0, 1], thickness: 1, + miterLimit: 2, canvas: this.canvas, // fill: [false, false, false, false], // fillColor: [ @@ -132,6 +140,11 @@ var proto = ScatterScene.prototype; proto.update = function(options, cdscatter) { + var container = this.container, + xaxis = Axes.getFromId(container, options.xaxis || 'x'), + yaxis = Axes.getFromId(container, options.yaxis || 'y'); + + if(options.visible !== true) { this.isVisible = false; this.hasLines = false; @@ -153,6 +166,29 @@ proto.update = function(options, cdscatter) { this.bounds = [Infinity, Infinity, -Infinity, -Infinity]; this.connectgaps = !!options.connectgaps; + + // update viewport & range + var vpSize = container._fullLayout._size, + domainX = xaxis.domain, + domainY = yaxis.domain, + width = container._fullLayout.width, + height = container._fullLayout.height; + + var viewBox = [ + vpSize.l + domainX[0] * vpSize.w, + vpSize.b + domainY[0] * vpSize.h, + (width - vpSize.r) - (1 - domainX[1]) * vpSize.w, + (height - vpSize.t) - (1 - domainY[1]) * vpSize.h + ]; + + var range = [xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1]]; + + this.scatterOptions.range = + this.lineOptions.range = range; + this.scatterOptions.viewport = + this.lineOptions.viewport = viewBox; + + if(!this.isVisible) { // this.line.clear(); // this.errorX.clear(); @@ -363,26 +399,8 @@ proto.updateFancy = function(options) { this.scatterOptions.borderColors[i][3] = dim * _borderColors[4 * index + 3] * 255; } - - var vpSize = container._fullLayout._size, - domainX = xaxis.domain, - domainY = yaxis.domain, - width = container._fullLayout.width, - height = container._fullLayout.height; - - var viewBox = [ - vpSize.l + domainX[0] * vpSize.w, - vpSize.b + domainY[0] * vpSize.h, - (width - vpSize.r) - (1 - domainX[1]) * vpSize.w, - (height - vpSize.t) - (1 - domainY[1]) * vpSize.h - ]; - - var range = [xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1]]; - - this.scatterOptions.range = range; - this.scatterOptions.viewport = viewBox; - // prevent scatter from resnapping points + this.regl._refresh(); this.scatter(this.scatterOptions); } @@ -395,10 +413,7 @@ proto.updateFancy = function(options) { proto.updateLines = function(options, positions) { var i; - if (!this.hasLines) return - - console.log(options, positions) -return; + if(!this.hasLines) return; var linePositions = positions; @@ -416,12 +431,15 @@ return; this.lineOptions.positions = linePositions; - var lineColor = convertColor(options.line.color, options.opacity, 1), - lineWidth = Math.round(0.5 * this.lineOptions.width), + this.lineOptions.thickness = options.line.width; + + var lineColor = options.line.color, + lineWidth = this.lineOptions.thickness, dashes = (DASHES[options.line.dash] || [1]).slice(); for(i = 0; i < dashes.length; ++i) dashes[i] *= lineWidth; + // FIXME: make regl renderer for fills switch(options.fill) { case 'tozeroy': this.lineOptions.fill = [false, true, false, false]; @@ -436,11 +454,12 @@ return; var fillColor = str2RGBArray(options.fillcolor); this.lineOptions.color = lineColor; - this.lineOptions.width = 2.0 * options.line.width; this.lineOptions.dashes = dashes; + this.lineOptions.fillColor = [fillColor, fillColor, fillColor, fillColor]; - this.line.update(); + this.regl._refresh(); + this.line(this.lineOptions); }; proto.updateError = function(axLetter, options, positions, errors) { @@ -455,7 +474,7 @@ proto.updateError = function(axLetter, options, positions, errors) { errorObj.options.positions = positions; errorObj.options.errors = errors; errorObj.options.capSize = errorOptions.width; - errorObj.options.lineWidth = errorOptions.thickness / 2; // ballpark rescaling + errorObj.options.lineWidth = errorOptions.thickness; // ballpark rescaling errorObj.options.color = convertColor(errorOptions.color, 1, 1); errorObj.update(); From 6ec58d3aba75c66dedf2754fc1bd79aea9603acd Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 18 Sep 2017 16:34:58 -0400 Subject: [PATCH 045/151] Fix sizes --- src/traces/scatterregl/plot.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 5c87c715d35..6702c1ffd1f 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -93,7 +93,8 @@ function ScatterScene(container) { this.canvas = container.querySelector('.gl-canvas-focus'); this.regl = createRegl({ canvas: this.canvas, - extensions: ['ANGLE_instanced_arrays', 'OES_element_index_uint'] + extensions: ['ANGLE_instanced_arrays', 'OES_element_index_uint'], + pixelRatio: container._context.plotGlPixelRatio || global.devicePixelRatio }); this.scatterOptions = { @@ -109,8 +110,7 @@ function ScatterScene(container) { borderSize: 1, borderColor: [0, 0, 0, 1], snapPoints: true, - canvas: this.canvas, - pixelRatio: container._context.plotGlPixelRatio || window.devicePixelRatio + canvas: this.canvas }; this.scatter = createScatter(this.scatterOptions); From 7105b443c1668b31ef972eb3cd1ed7d0dd8e8635 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 18 Sep 2017 19:09:41 -0400 Subject: [PATCH 046/151] Fix single-trace panning interaction --- src/plots/cartesian/dragbox.js | 10 +++ src/traces/scatterregl/plot.js | 133 +++++++++++++++++---------------- 2 files changed, 77 insertions(+), 66 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index ca9a89ae5a7..b3724c78155 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -766,6 +766,16 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { scatterPoints.selectAll('.textpoint') .call(Drawing.setTextPointsScale, xScaleFactor2, yScaleFactor2) .call(Drawing.hideOutsideRangePoints, subplot); + + //scattergl translate + if (subplot._scattergl) { + // FIXME: possibly we could update axis internal _r and _rl here + var xaRange = Lib.simpleMap(xa2.range, xa2.r2l), + yaRange = Lib.simpleMap(ya2.range, ya2.r2l) + subplot._scattergl.updateRange( + [xaRange[0], yaRange[0], xaRange[1], yaRange[1]] + ) + } } } diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 6702c1ffd1f..2cdcbd524aa 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -44,14 +44,14 @@ function createLineWithMarkers(container, plotinfo, cdscatter) { var layout = container._fullLayout; var subplotObj = layout._plots.xy; - var scatter = subplotObj._scatter2d; + var scatter = subplotObj._scattergl; // create regl-scatter, if not defined if(scatter === undefined) { // TODO: enhance picking // TODO: figure out if there is a way to detect only new passed options - scatter = subplotObj._scatter2d = new ScatterScene(container); + scatter = subplotObj._scattergl = new ScatterScene(container); } container._fullData.forEach(function(data, i) { @@ -97,8 +97,10 @@ function ScatterScene(container) { pixelRatio: container._context.plotGlPixelRatio || global.devicePixelRatio }); + this.range; + this.viewport; + this.scatterOptions = { - regl: this.regl, positions: Array(), sizes: [], colors: [], @@ -110,10 +112,12 @@ function ScatterScene(container) { borderSize: 1, borderColor: [0, 0, 0, 1], snapPoints: true, - canvas: this.canvas + range: this.range, + viewport: this.viewport }; - this.scatter = createScatter(this.scatterOptions); + this.scatter = createScatter({regl: this.regl}); + this.line = createLine({regl: this.regl}); this.lineOptions = { regl: this.regl, @@ -122,6 +126,8 @@ function ScatterScene(container) { thickness: 1, miterLimit: 2, canvas: this.canvas, + range: this.range, + viewport: this.viewport, // fill: [false, false, false, false], // fillColor: [ // [0, 0, 0, 1], @@ -131,7 +137,6 @@ function ScatterScene(container) { dashes: [1], }; - this.line = createLine(this.lineOptions); return this; } @@ -139,11 +144,36 @@ function ScatterScene(container) { var proto = ScatterScene.prototype; +proto.updateRange = function(range) { + this.range = this.scatterOptions.range = this.lineOptions.range = range; + + this.regl._refresh(); + this.scatter({ + range: range + }); + this.regl._refresh(); + this.line({ + range: range + }); +} + + proto.update = function(options, cdscatter) { - var container = this.container, + var container = this.container, xaxis = Axes.getFromId(container, options.xaxis || 'x'), - yaxis = Axes.getFromId(container, options.yaxis || 'y'); + yaxis = Axes.getFromId(container, options.yaxis || 'y'), + bounds = this.bounds, + selection = options.selection; + var vpSize = container._fullLayout._size, + width = container._fullLayout.width, + height = container._fullLayout.height; + // 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(); if(options.visible !== true) { this.isVisible = false; @@ -166,28 +196,15 @@ proto.update = function(options, cdscatter) { this.bounds = [Infinity, Infinity, -Infinity, -Infinity]; this.connectgaps = !!options.connectgaps; - // update viewport & range - var vpSize = container._fullLayout._size, - domainX = xaxis.domain, - domainY = yaxis.domain, - width = container._fullLayout.width, - height = container._fullLayout.height; - - var viewBox = [ - vpSize.l + domainX[0] * vpSize.w, - vpSize.b + domainY[0] * vpSize.h, - (width - vpSize.r) - (1 - domainX[1]) * vpSize.w, - (height - vpSize.t) - (1 - domainY[1]) * vpSize.h + this.viewport = this.scatterOptions.viewport = this.lineOptions.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 ]; - var range = [xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1]]; - - this.scatterOptions.range = - this.lineOptions.range = range; - this.scatterOptions.viewport = - this.lineOptions.viewport = viewBox; - + this.range = this.scatterOptions.range = this.lineOptions.range = [xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1]]; if(!this.isVisible) { // this.line.clear(); @@ -196,45 +213,6 @@ proto.update = function(options, cdscatter) { // this.scatter(); // this.fancyScatter.clear(); } - else { - this.updateFancy(options); - } - - // sort objects so that order is preserve on updates: - // - lines - // - errorX - // - errorY - // - markers - // this.container.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; - } -}; - -proto.updateFancy = function(options) { - var container = this.container, - xaxis = Axes.getFromId(container, options.xaxis || 'x'), - yaxis = Axes.getFromId(container, options.yaxis || 'y'), - 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, container._fullLayout); @@ -408,8 +386,30 @@ proto.updateFancy = function(options) { // former expandAxesFancy Axes.expand(xaxis, x, {padded: true, ppad: sizes}); Axes.expand(yaxis, y, {padded: true, ppad: sizes}); + + // sort objects so that order is preserve on updates: + // - lines + // - errorX + // - errorY + // - markers + // this.container.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; + } }; + proto.updateLines = function(options, positions) { var i; @@ -453,6 +453,7 @@ proto.updateLines = function(options, positions) { } var fillColor = str2RGBArray(options.fillcolor); + this.lineOptions.opacity = options.opacity; this.lineOptions.color = lineColor; this.lineOptions.dashes = dashes; From 45a0247b287b50f94adbef57bc35d668c3621eb0 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 21 Sep 2017 13:15:17 -0400 Subject: [PATCH 047/151] Read options directly from data --- src/traces/scatterregl/plot.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 2cdcbd524aa..0eac56081a7 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -55,7 +55,7 @@ function createLineWithMarkers(container, plotinfo, cdscatter) { } container._fullData.forEach(function(data, i) { - scatter.update(data, cdscatter[i]); + scatter.update(cdscatter[i]); }); return scatter; @@ -158,8 +158,10 @@ proto.updateRange = function(range) { } -proto.update = function(options, cdscatter) { - var container = this.container, +proto.update = function(cdscatter) { + var options = cdscatter[0].trace; + + var container = this.container, xaxis = Axes.getFromId(container, options.xaxis || 'x'), yaxis = Axes.getFromId(container, options.yaxis || 'y'), bounds = this.bounds, From e3af57f0cdf0cf809018fbbd9abd31b0233df257 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 2 Oct 2017 15:26:53 -0400 Subject: [PATCH 048/151] Make sequential line/scatter rendering --- package.json | 1 + src/traces/scatterregl/plot.js | 616 ++++++++++++++------------------- 2 files changed, 258 insertions(+), 359 deletions(-) diff --git a/package.json b/package.json index 7a8412f6a8a..16213318a6e 100644 --- a/package.json +++ b/package.json @@ -97,6 +97,7 @@ "ndarray-ops": "^1.2.2", "object-assign": "^4.1.1", "regl": "^1.3.0", + "regl-line2d": "^1.1.1", "regl-scatter2d": "^1.0.3", "right-now": "^1.0.0", "robust-orientation": "^1.1.3", diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 0eac56081a7..a617031face 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -40,7 +40,7 @@ var convertNumber, convertColorBase; module.exports = createLineWithMarkers; -function createLineWithMarkers(container, plotinfo, cdscatter) { +function createLineWithMarkers(container, plotinfo, cdata) { var layout = container._fullLayout; var subplotObj = layout._plots.xy; @@ -54,42 +54,17 @@ function createLineWithMarkers(container, plotinfo, cdscatter) { scatter = subplotObj._scattergl = new ScatterScene(container); } - container._fullData.forEach(function(data, i) { - scatter.update(cdscatter[i]); - }); + scatter.update(cdata) return scatter; } function ScatterScene(container) { - if(!(this instanceof ScatterScene)) return new ScatterScene(container); - this.container = container; this.type = 'scatterregl'; - 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.xaxis = container._fullLayout.xaxis; - this.yaxis = container._fullLayout.yaxis; - this.canvas = container.querySelector('.gl-canvas-focus'); this.regl = createRegl({ canvas: this.canvas, @@ -97,46 +72,21 @@ function ScatterScene(container) { pixelRatio: container._context.plotGlPixelRatio || global.devicePixelRatio }); - this.range; - this.viewport; - - this.scatterOptions = { - positions: Array(), - sizes: [], - colors: [], - markers: [], - borderSizes: [], - borderColors: [], + this.scatter = createScatter({ + regl: this.regl, size: 12, color: [0, 0, 0, 1], borderSize: 1, - borderColor: [0, 0, 0, 1], - snapPoints: true, - range: this.range, - viewport: this.viewport - }; - - this.scatter = createScatter({regl: this.regl}); - this.line = createLine({regl: this.regl}); - - this.lineOptions = { + borderColor: [0, 0, 0, 1] + }); + this.line = createLine({ regl: this.regl, - positions: new Float64Array(0), color: [0, 0, 0, 1], thickness: 1, miterLimit: 2, - canvas: this.canvas, - range: this.range, - viewport: this.viewport, - // fill: [false, false, false, false], - // fillColor: [ - // [0, 0, 0, 1], - // [0, 0, 0, 1], - // [0, 0, 0, 1], - // [0, 0, 0, 1]], - dashes: [1], - }; - + overlap: true, + dashes: [1] + }); return this; } @@ -145,326 +95,302 @@ var proto = ScatterScene.prototype; proto.updateRange = function(range) { - this.range = this.scatterOptions.range = this.lineOptions.range = range; + // this.range = this.scatterOptions.range = this.lineOptions.range = range; - this.regl._refresh(); - this.scatter({ - range: range - }); - this.regl._refresh(); - this.line({ - range: range - }); + // this.scatter({ + // range: range + // }); + // this.line({ + // range: range + // }); + throw 'Unimplemented'; } -proto.update = function(cdscatter) { - var options = cdscatter[0].trace; - - var container = this.container, - xaxis = Axes.getFromId(container, options.xaxis || 'x'), - yaxis = Axes.getFromId(container, options.yaxis || 'y'), - bounds = this.bounds, - selection = options.selection; - var vpSize = container._fullLayout._size, - width = container._fullLayout.width, - height = container._fullLayout.height; +proto.update = function(cdscatters) { + var container = this.container + + var batch = [] + + cdscatters.forEach(function (cdscatter) { + if (!cdscatter) return; + + var lineOptions = {} + var scatterOptions = {} + batch.push({ + line: lineOptions, + scatter: scatterOptions + }) + + var options = cdscatter[0].trace; + var xaxis = Axes.getFromId(container, options.xaxis || 'x'), + yaxis = Axes.getFromId(container, options.yaxis || 'y'), + selection = options.selection; + var vpSize = container._fullLayout._size, + width = container._fullLayout.width, + height = container._fullLayout.height; + + // makeCalcdata runs d2c (data-to-coordinate) on every point + var x = options.x; + var y = options.y; + var xData = Array(x.length); + var yData = Array(y.length); + + var isVisible = false; + var hasLines = false; + var hasErrorX = false; + var hasErrorY = false; + var hasMarkers = false; + + if(options.visible !== true) { + isVisible = false; + hasLines = false; + hasErrorX = false; + hasErrorY = false; + hasMarkers = false; + } + else { + isVisible = true; + hasLines = subTypes.hasLines(options); + hasErrorX = options.error_x.visible === true; + hasErrorY = options.error_y.visible === true; + hasMarkers = subTypes.hasMarkers(options); + } - // 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(); + var connectgaps = !!options.connectgaps; - this.xData = x.slice(); - this.yData = y.slice(); + // update viewport & range + 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(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); - } + var range = [ + xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1] + ]; - this.textLabels = options.text; - this.name = options.name; - this.hoverinfo = options.hoverinfo; - this.bounds = [Infinity, Infinity, -Infinity, -Infinity]; - this.connectgaps = !!options.connectgaps; - - // update viewport & range - this.viewport = this.scatterOptions.viewport = this.lineOptions.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 - ]; - - this.range = this.scatterOptions.range = this.lineOptions.range = [xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1]]; - - if(!this.isVisible) { - // this.line.clear(); - // this.errorX.clear(); - // this.errorY.clear(); - // this.scatter(); - // this.fancyScatter.clear(); - } + // get error values + var errorVals = ErrorBars.calcFromTrace(options, container._fullLayout); - // get error values - var errorVals = ErrorBars.calcFromTrace(options, container._fullLayout); + var len = x.length, + positions = Array(2 * len), + errorsX = new Float64Array(4 * len), + errorsY = new Float64Array(4 * len), + pId = 0, + ptr = 0, + ptrX = 0, + ptrY = 0; - var len = x.length, - idToIndex = new Array(len), - positions = Array(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 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; - var i, xx, yy, ex0, ex1, ey0, ey1; + for(i = 0; i < len; ++i) { + xData[i] = xx = getX(x[i]); + yData[i] = yy = getY(y[i]); - 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; - if(isNaN(xx) || isNaN(yy)) continue; + positions[ptr++] = parseFloat(xx); + positions[ptr++] = parseFloat(yy); - idToIndex[pId++] = i; + ex0 = errorsX[ptrX++] = xx - errorVals[i].xs || 0; + ex1 = errorsX[ptrX++] = errorVals[i].xh - xx || 0; + errorsX[ptrX++] = 0; + errorsX[ptrX++] = 0; - positions[ptr++] = xx; - positions[ptr++] = yy; + errorsY[ptrY++] = 0; + errorsY[ptrY++] = 0; + ey0 = errorsY[ptrY++] = yy - errorVals[i].ys || 0; + ey1 = errorsY[ptrY++] = errorVals[i].yh - yy || 0; + } - ex0 = errorsX[ptrX++] = xx - errorVals[i].xs || 0; - ex1 = errorsX[ptrX++] = errorVals[i].xh - xx || 0; - errorsX[ptrX++] = 0; - errorsX[ptrX++] = 0; + //update lines + if(hasLines) { + lineOptions.positions = positions, + lineOptions.thickness = options.line.width, + lineOptions.color = options.line.color, + lineOptions.opacity = options.opacity, + lineOptions.join = 'round' + + var lineWidth = lineOptions.thickness, + dashes = (DASHES[options.line.dash] || [1]).slice(); + + for(i = 0; i < dashes.length; ++i) dashes[i] *= lineWidth; + + // FIXME: make regl renderer for fills + switch(options.fill) { + case 'tozeroy': + // lineOptions.fill = [false, true, false, false]; + break; + case 'tozerox': + // lineOptions.fill = [true, false, false, false]; + break; + default: + // lineOptions.fill = [false, false, false, false]; + break; + } + var fillColor = str2RGBArray(options.fillcolor); - errorsY[ptrY++] = 0; - errorsY[ptrY++] = 0; - ey0 = errorsY[ptrY++] = yy - errorVals[i].ys || 0; - ey1 = errorsY[ptrY++] = errorVals[i].yh - yy || 0; + lineOptions.dashes = dashes; + // lineOptions.fillColor = [fillColor, fillColor, fillColor, fillColor]; - 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); - } + lineOptions.viewport = viewport + lineOptions.range = range + } - positions = positions.slice(0, ptr); - this.idToIndex = idToIndex; - this.updateLines(options, positions); - // this.updateError('X', options, positions, errorsX); - // this.updateError('Y', options, positions, errorsY); + // updateError('X', options, positions, errorsX); + // updateError('Y', options, positions, errorsY); - var sizes, selIds; + var sizes, selIds; - if(selection && selection.length) { - selIds = {}; - for(i = 0; i < selection.length; i++) { - selIds[selection[i].pointNumber] = true; + if(selection && selection.length) { + selIds = {}; + for(i = 0; i < selection.length; i++) { + selIds[selection[i].pointNumber] = true; + } } - } - if(this.hasMarkers) { - this.scatterOptions.positions = positions; + if(hasMarkers) { + scatterOptions.positions = positions; - // TODO rewrite convert function so that - // we don't have to loop through the data another time + // TODO rewrite convert function so that + // we don't have to loop through the data another time - this.scatterOptions.sizes = new Array(pId); - this.scatterOptions.markers = new Array(pId); - this.scatterOptions.borderSizes = new Array(pId); - this.scatterOptions.colors = new Array(pId); - this.scatterOptions.borderColors = new Array(pId); + scatterOptions.sizes = new Array(len); + scatterOptions.markers = new Array(len); + scatterOptions.borderSizes = new Array(len); + scatterOptions.colors = new Array(len); + scatterOptions.borderColors = new Array(len); - var markerSizeFunc = makeBubbleSizeFn(options); + var markerSizeFunc = makeBubbleSizeFn(options); - var markerOpts = options.marker; - var markerOpacity = markerOpts.opacity; - var traceOpacity = options.opacity; - var symbols = markerOpts.symbol; + var markerOpts = options.marker; + var markerOpacity = markerOpts.opacity; + var traceOpacity = options.opacity; + var symbols = markerOpts.symbol; - var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); - var borderSizes = convertNumber(markerOpts.line.width, len); - var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len); - var index, size, symbol, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, symbolFunc, symbolNoDot, symbolSdf, symbolNoFill, symbolPath, isDot; + var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); + var borderSizes = convertNumber(markerOpts.line.width, len); + var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len); + var size, symbol, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, symbolFunc, symbolNoDot, symbolSdf, symbolNoFill, symbolPath, isDot; - sizes = convertArray(markerSizeFunc, markerOpts.size, len); + sizes = convertArray(markerSizeFunc, markerOpts.size, len); - for(i = 0; i < pId; ++i) { - index = idToIndex[i]; - symbol = Array.isArray(symbols) ? symbols[index] : symbols; - symbolNumber = Drawing.symbolNumber(symbol); - symbolFunc = Drawing.symbolFuncs[symbolNumber % 100]; - symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100]; - symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100]; + for(i = 0; i < len; ++i) { + symbol = Array.isArray(symbols) ? symbols[i] : symbols; + symbolNumber = Drawing.symbolNumber(symbol); + symbolFunc = Drawing.symbolFuncs[symbolNumber % 100]; + symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100]; + symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100]; - isOpen = /-open/.test(symbol); - isDot = /-dot/.test(symbol); - isDimmed = selIds && !selIds[index]; + isOpen = /-open/.test(symbol); + isDot = /-dot/.test(symbol); + isDimmed = selIds && !selIds[i]; - _colors = colors; + _colors = colors; - if(isOpen) { - _borderColors = colors; - } else { - _borderColors = borderColors; - } + 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 = borderSizes[index]; + // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798 + // for more info on this logic + size = sizes[i]; + bw = borderSizes[i]; - this.scatterOptions.sizes[i] = size; + scatterOptions.sizes[i] = size; - if(symbol === 'circle') { - this.scatterOptions.markers[i] = null; - } - else { - // get symbol sdf from cache or generate it - if(SYMBOL_SDF[symbol]) { - symbolSdf = SYMBOL_SDF[symbol]; - } else { - if(isDot && !symbolNoDot) { - symbolPath = symbolFunc(SYMBOL_SIZE * 1.1) + SYMBOL_SVG_CIRCLE; - } - else { - symbolPath = symbolFunc(SYMBOL_SIZE); + if(symbol === 'circle') { + scatterOptions.markers[i] = null; + } + else { + // get symbol sdf from cache or generate it + if(SYMBOL_SDF[symbol]) { + symbolSdf = SYMBOL_SDF[symbol]; + } else { + 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; } - 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; + scatterOptions.markers[i] = symbolSdf || null; } - - this.scatterOptions.markers[i] = symbolSdf || null; - } - this.scatterOptions.borderSizes[i] = 0.5 * bw; - - var optColors = this.scatterOptions.colors; - var dim = isDimmed ? DESELECTDIM : 1; - if(!optColors[i]) optColors[i] = []; - if(isOpen || symbolNoFill) { - optColors[i][0] = TRANSPARENT[0]; - optColors[i][1] = TRANSPARENT[1]; - optColors[i][2] = TRANSPARENT[2]; - optColors[i][3] = TRANSPARENT[3]; - } else { - optColors[i][0] = _colors[4 * index + 0] * 255; - optColors[i][1] = _colors[4 * index + 1] * 255; - optColors[i][2] = _colors[4 * index + 2] * 255; - optColors[i][3] = dim * _colors[4 * index + 3] * 255; + scatterOptions.borderSizes[i] = 0.5 * bw; + + var optColors = scatterOptions.colors; + var dim = isDimmed ? DESELECTDIM : 1; + if(!optColors[i]) optColors[i] = []; + if(isOpen || symbolNoFill) { + optColors[i][0] = TRANSPARENT[0]; + optColors[i][1] = TRANSPARENT[1]; + optColors[i][2] = TRANSPARENT[2]; + optColors[i][3] = TRANSPARENT[3]; + } else { + optColors[i][0] = _colors[4 * i + 0] * 255; + optColors[i][1] = _colors[4 * i + 1] * 255; + optColors[i][2] = _colors[4 * i + 2] * 255; + optColors[i][3] = dim * _colors[4 * i + 3] * 255; + } + if(!scatterOptions.borderColors[i]) scatterOptions.borderColors[i] = []; + scatterOptions.borderColors[i][0] = _borderColors[4 * i + 0] * 255; + scatterOptions.borderColors[i][1] = _borderColors[4 * i + 1] * 255; + scatterOptions.borderColors[i][2] = _borderColors[4 * i + 2] * 255; + scatterOptions.borderColors[i][3] = dim * _borderColors[4 * i + 3] * 255; } - if(!this.scatterOptions.borderColors[i]) this.scatterOptions.borderColors[i] = []; - this.scatterOptions.borderColors[i][0] = _borderColors[4 * index + 0] * 255; - this.scatterOptions.borderColors[i][1] = _borderColors[4 * index + 1] * 255; - this.scatterOptions.borderColors[i][2] = _borderColors[4 * index + 2] * 255; - this.scatterOptions.borderColors[i][3] = dim * _borderColors[4 * index + 3] * 255; - } - // prevent scatter from resnapping points - this.regl._refresh(); - this.scatter(this.scatterOptions); - } - - // add item for autorange routine - // former expandAxesFancy - Axes.expand(xaxis, x, {padded: true, ppad: sizes}); - Axes.expand(yaxis, y, {padded: true, ppad: sizes}); - - // sort objects so that order is preserve on updates: - // - lines - // - errorX - // - errorY - // - markers - // this.container.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; - } -}; - - -proto.updateLines = function(options, positions) { - var i; - - if(!this.hasLines) return; - - 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]; + scatterOptions.viewport = viewport + scatterOptions.range = range } - } - - this.lineOptions.positions = linePositions; - this.lineOptions.thickness = options.line.width; - - var lineColor = options.line.color, - lineWidth = this.lineOptions.thickness, - dashes = (DASHES[options.line.dash] || [1]).slice(); - - for(i = 0; i < dashes.length; ++i) dashes[i] *= lineWidth; + // add item for autorange routine + // former expandAxesFancy + Axes.expand(xaxis, x, {padded: true, ppad: sizes}); + Axes.expand(yaxis, y, {padded: true, ppad: sizes}); + + // sort objects so that order is preserve on updates: + // - lines + // - errorX + // - errorY + // - markers + // this.container.glplot.objects.sort(function(a, b) { + // return a._index - b._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[0].glTrace) { + cdscatter[0].glTrace = this; + } + }) - // FIXME: make regl renderer for fills - switch(options.fill) { - case 'tozeroy': - this.lineOptions.fill = [false, true, false, false]; - break; - case 'tozerox': - this.lineOptions.fill = [true, false, false, false]; - break; - default: - this.lineOptions.fill = [false, false, false, false]; - break; + for (var i = 0; i < batch.length; i++) { + this.scatter(batch[i].scatter) + this.line(batch[i].line) } - var fillColor = str2RGBArray(options.fillcolor); - - this.lineOptions.opacity = options.opacity; - this.lineOptions.color = lineColor; - this.lineOptions.dashes = dashes; - - this.lineOptions.fillColor = [fillColor, fillColor, fillColor, fillColor]; - - this.regl._refresh(); - this.line(this.lineOptions); }; + proto.updateError = function(axLetter, options, positions, errors) { var errorObj = this['error' + axLetter], errorOptions = options['error_' + axLetter.toLowerCase()]; @@ -488,34 +414,6 @@ proto.updateError = function(axLetter, options, positions, errors) { }; -// 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 -// }; -// }; - convertNumber = convertArray.bind(null, function(x) { return +x; }); convertColorBase = convertArray.bind(null, str2RGBArray); From 52664291fd8ba7f7809599baaab4c9acb2f3353f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 3 Oct 2017 16:13:36 -0400 Subject: [PATCH 049/151] Make batch line rendering --- src/plots/cartesian/dragbox.js | 8 +-- src/traces/scatterregl/plot.js | 109 +++++++++++++++++++-------------- 2 files changed, 66 insertions(+), 51 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index b3724c78155..1a14c07f40f 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -767,14 +767,14 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { .call(Drawing.setTextPointsScale, xScaleFactor2, yScaleFactor2) .call(Drawing.hideOutsideRangePoints, subplot); - //scattergl translate - if (subplot._scattergl) { + // scattergl translate + if(subplot._scattergl) { // FIXME: possibly we could update axis internal _r and _rl here var xaRange = Lib.simpleMap(xa2.range, xa2.r2l), - yaRange = Lib.simpleMap(ya2.range, ya2.r2l) + yaRange = Lib.simpleMap(ya2.range, ya2.r2l); subplot._scattergl.updateRange( [xaRange[0], yaRange[0], xaRange[1], yaRange[1]] - ) + ); } } } diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index a617031face..be327d6605d 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -16,7 +16,6 @@ var str2RGBArray = require('../../lib/str2rgbarray'); 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 DASHES = require('../../constants/gl2d_dashes'); var createScatter = require('regl-scatter2d'); var createLine = require('regl-line2d'); @@ -54,7 +53,7 @@ function createLineWithMarkers(container, plotinfo, cdata) { scatter = subplotObj._scattergl = new ScatterScene(container); } - scatter.update(cdata) + scatter.update(cdata); return scatter; } @@ -84,10 +83,11 @@ function ScatterScene(container) { color: [0, 0, 0, 1], thickness: 1, miterLimit: 2, - overlap: true, dashes: [1] }); + this.count = 0; + return this; } @@ -95,32 +95,55 @@ var proto = ScatterScene.prototype; proto.updateRange = function(range) { - // this.range = this.scatterOptions.range = this.lineOptions.range = range; - - // this.scatter({ - // range: range - // }); - // this.line({ - // range: range - // }); - throw 'Unimplemented'; -} + var batch = []; + + for(var i = 0; i < this.count; i++) { + batch.push({ + line: {range: range}, + scatter: {range: range} + }); + } + + this.updateBatch(batch); +}; + + +proto.updateBatch = function(batch) { + // update options of line and scatter components directly + var lineBatch = []; + var scatterBatch = []; + for(var i = 0; i < batch.length; i++) { + lineBatch.push(batch[i].line || null); + scatterBatch.push(batch[i].scatter || null); + } + + this.line.update(lineBatch); + // this.scatter.update(scatterBatch) + + // rendering requires preserving order of line/scatter layers + for(var i = 0; i < batch.length; i++) { + var lineBatch = Array(batch.length); + var scatterBatch = Array(batch.length); + for(var j = 0; j < batch.length; j++) { + lineBatch[j] = i === j; + } + this.line.draw(lineBatch); + // this.scatter.draw(scatterBatch) + } + + this.count = batch.length; +}; proto.update = function(cdscatters) { - var container = this.container + var container = this.container; - var batch = [] + var batch = []; - cdscatters.forEach(function (cdscatter) { - if (!cdscatter) return; + cdscatters.forEach(function(cdscatter) { + if(!cdscatter) return; - var lineOptions = {} - var scatterOptions = {} - batch.push({ - line: lineOptions, - scatter: scatterOptions - }) + var lineOptions, scatterOptions; var options = cdscatter[0].trace; var xaxis = Axes.getFromId(container, options.xaxis || 'x'), @@ -208,13 +231,15 @@ proto.update = function(cdscatters) { ey1 = errorsY[ptrY++] = errorVals[i].yh - yy || 0; } - //update lines + // update lines if(hasLines) { + lineOptions = {}; lineOptions.positions = positions, lineOptions.thickness = options.line.width, lineOptions.color = options.line.color, lineOptions.opacity = options.opacity, - lineOptions.join = 'round' + lineOptions.join = options.opacity === 1.0 ? 'rect' : 'round'; + lineOptions.overlay = true; var lineWidth = lineOptions.thickness, dashes = (DASHES[options.line.dash] || [1]).slice(); @@ -238,8 +263,8 @@ proto.update = function(cdscatters) { lineOptions.dashes = dashes; // lineOptions.fillColor = [fillColor, fillColor, fillColor, fillColor]; - lineOptions.viewport = viewport - lineOptions.range = range + lineOptions.viewport = viewport; + lineOptions.range = range; } @@ -256,6 +281,7 @@ proto.update = function(cdscatters) { } if(hasMarkers) { + scatterOptions = {}; scatterOptions.positions = positions; // TODO rewrite convert function so that @@ -356,38 +382,27 @@ proto.update = function(cdscatters) { scatterOptions.borderColors[i][3] = dim * _borderColors[4 * i + 3] * 255; } - scatterOptions.viewport = viewport - scatterOptions.range = range + scatterOptions.viewport = viewport; + scatterOptions.range = range; } + batch.push({ + scatter: scatterOptions, + line: lineOptions + }); + // add item for autorange routine // former expandAxesFancy Axes.expand(xaxis, x, {padded: true, ppad: sizes}); Axes.expand(yaxis, y, {padded: true, ppad: sizes}); - // sort objects so that order is preserve on updates: - // - lines - // - errorX - // - errorY - // - markers - // this.container.glplot.objects.sort(function(a, b) { - // return a._index - b._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[0].glTrace) { cdscatter[0].glTrace = this; } - }) + }); - for (var i = 0; i < batch.length; i++) { - this.scatter(batch[i].scatter) - this.line(batch[i].line) - } + this.updateBatch(batch); }; From 176d260a6fee68a673532c69d483f50a8216bed1 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 6 Oct 2017 15:00:54 -0400 Subject: [PATCH 050/151] Make batch rendering for scatter plot --- src/traces/scatterregl/plot.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index be327d6605d..7a45e501b53 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -118,7 +118,7 @@ proto.updateBatch = function(batch) { } this.line.update(lineBatch); - // this.scatter.update(scatterBatch) + this.scatter.update(scatterBatch); // rendering requires preserving order of line/scatter layers for(var i = 0; i < batch.length; i++) { @@ -126,9 +126,10 @@ proto.updateBatch = function(batch) { var scatterBatch = Array(batch.length); for(var j = 0; j < batch.length; j++) { lineBatch[j] = i === j; + scatterBatch[j] = i === j; } this.line.draw(lineBatch); - // this.scatter.draw(scatterBatch) + this.scatter.draw(scatterBatch); } this.count = batch.length; From f66894a09f02897f96250724e58fbfae290a0d51 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 6 Oct 2017 15:30:47 -0400 Subject: [PATCH 051/151] Make better-js style of scatter component --- src/plots/cartesian/dragbox.js | 2 +- src/traces/scatterregl/plot.js | 590 ++++++++++++++++----------------- 2 files changed, 294 insertions(+), 298 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 1a14c07f40f..0a6e0fc1ed0 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -772,7 +772,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { // FIXME: possibly we could update axis internal _r and _rl here var xaRange = Lib.simpleMap(xa2.range, xa2.r2l), yaRange = Lib.simpleMap(ya2.range, ya2.r2l); - subplot._scattergl.updateRange( + subplot._scattergl.range( [xaRange[0], yaRange[0], xaRange[1], yaRange[1]] ); } diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 7a45e501b53..6c588c2798d 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -22,6 +22,7 @@ var createLine = require('regl-line2d'); var Drawing = require('../../components/drawing'); var svgSdf = require('svg-path-sdf'); var createRegl = require('regl'); +var nanoraf = require('nanoraf'); var DESELECTDIM = 0.2; var TRANSPARENT = [0, 0, 0, 0]; @@ -49,385 +50,380 @@ function createLineWithMarkers(container, plotinfo, cdata) { if(scatter === undefined) { // TODO: enhance picking // TODO: figure out if there is a way to detect only new passed options - - scatter = subplotObj._scattergl = new ScatterScene(container); + scatter = subplotObj._scattergl = createScatterScene(container); } - scatter.update(cdata); + scatter(cdata); return scatter; } -function ScatterScene(container) { - this.container = container; - this.type = 'scatterregl'; +function createScatterScene(container) { + var connectgaps = true; - this.connectgaps = true; + var canvas = container.querySelector('.gl-canvas-focus'); - this.canvas = container.querySelector('.gl-canvas-focus'); - this.regl = createRegl({ - canvas: this.canvas, + var regl = createRegl({ + canvas: canvas, extensions: ['ANGLE_instanced_arrays', 'OES_element_index_uint'], pixelRatio: container._context.plotGlPixelRatio || global.devicePixelRatio }); - this.scatter = createScatter({ - regl: this.regl, + var scatter = createScatter({ + regl: regl, size: 12, color: [0, 0, 0, 1], borderSize: 1, borderColor: [0, 0, 0, 1] }); - this.line = createLine({ - regl: this.regl, + var line = createLine({ + regl: regl, color: [0, 0, 0, 1], thickness: 1, miterLimit: 2, dashes: [1] }); - this.count = 0; - - return this; -} - -var proto = ScatterScene.prototype; + var count = 0; + function updateRange(range) { + var batch = []; -proto.updateRange = function(range) { - var batch = []; - - for(var i = 0; i < this.count; i++) { - batch.push({ - line: {range: range}, - scatter: {range: range} - }); - } - - this.updateBatch(batch); -}; - - -proto.updateBatch = function(batch) { - // update options of line and scatter components directly - var lineBatch = []; - var scatterBatch = []; - for(var i = 0; i < batch.length; i++) { - lineBatch.push(batch[i].line || null); - scatterBatch.push(batch[i].scatter || null); - } + for(var i = 0; i < count; i++) { + batch.push({ + line: {range: range}, + scatter: {range: range} + }); + } - this.line.update(lineBatch); - this.scatter.update(scatterBatch); + updateBatch(batch); + }; - // rendering requires preserving order of line/scatter layers - for(var i = 0; i < batch.length; i++) { - var lineBatch = Array(batch.length); - var scatterBatch = Array(batch.length); - for(var j = 0; j < batch.length; j++) { - lineBatch[j] = i === j; - scatterBatch[j] = i === j; + function updateBatch(batch) { + // update options of line and scatter components directly + var lineBatch = []; + var scatterBatch = []; + for(var i = 0; i < batch.length; i++) { + lineBatch.push(batch[i].line || null); + scatterBatch.push(batch[i].scatter || null); } - this.line.draw(lineBatch); - this.scatter.draw(scatterBatch); - } - - this.count = batch.length; -}; + line.update(lineBatch); + scatter.update(scatterBatch); -proto.update = function(cdscatters) { - var container = this.container; + count = batch.length; - var batch = []; + //rendering requires proper batch sequence + for(var i = 0; i < count; i++) { + var lineBatch = Array(batch.length); + var scatterBatch = Array(batch.length); + for(var j = 0; j < batch.length; j++) { + lineBatch[j] = i === j; + scatterBatch[j] = i === j; + } + line.draw(lineBatch); + scatter.draw(scatterBatch); + } + }; + + //update based on calc data + function update(cdscatters) { + var batch = []; + + cdscatters.forEach(function(cdscatter) { + if(!cdscatter) return; + + var lineOptions, scatterOptions; + + var options = cdscatter[0].trace; + var xaxis = Axes.getFromId(container, options.xaxis || 'x'), + yaxis = Axes.getFromId(container, options.yaxis || 'y'), + selection = options.selection; + var vpSize = container._fullLayout._size, + width = container._fullLayout.width, + height = container._fullLayout.height; + + // makeCalcdata runs d2c (data-to-coordinate) on every point + var x = options.x; + var y = options.y; + var xData = Array(x.length); + var yData = Array(y.length); + + var isVisible = false; + var hasLines = false; + var hasErrorX = false; + var hasErrorY = false; + var hasMarkers = false; + + if(options.visible !== true) { + isVisible = false; + hasLines = false; + hasErrorX = false; + hasErrorY = false; + hasMarkers = false; + } + else { + isVisible = true; + hasLines = subTypes.hasLines(options); + hasErrorX = options.error_x.visible === true; + hasErrorY = options.error_y.visible === true; + hasMarkers = subTypes.hasMarkers(options); + } - cdscatters.forEach(function(cdscatter) { - if(!cdscatter) return; + var connectgaps = !!options.connectgaps; - var lineOptions, scatterOptions; + // update viewport & range + 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 + ]; - var options = cdscatter[0].trace; - var xaxis = Axes.getFromId(container, options.xaxis || 'x'), - yaxis = Axes.getFromId(container, options.yaxis || 'y'), - selection = options.selection; - var vpSize = container._fullLayout._size, - width = container._fullLayout.width, - height = container._fullLayout.height; + var range = [ + xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1] + ]; - // makeCalcdata runs d2c (data-to-coordinate) on every point - var x = options.x; - var y = options.y; - var xData = Array(x.length); - var yData = Array(y.length); + // get error values + var errorVals = ErrorBars.calcFromTrace(options, container._fullLayout); - var isVisible = false; - var hasLines = false; - var hasErrorX = false; - var hasErrorY = false; - var hasMarkers = false; + var len = x.length, + positions = Array(2 * len), + errorsX = new Float64Array(4 * len), + errorsY = new Float64Array(4 * len), + pId = 0, + ptr = 0, + ptrX = 0, + ptrY = 0; - if(options.visible !== true) { - isVisible = false; - hasLines = false; - hasErrorX = false; - hasErrorY = false; - hasMarkers = false; - } - else { - isVisible = true; - hasLines = subTypes.hasLines(options); - hasErrorX = options.error_x.visible === true; - hasErrorY = options.error_y.visible === true; - hasMarkers = subTypes.hasMarkers(options); - } + var getX = (xaxis.type === 'log') ? xaxis.d2l : function(x) { return x; }; + var getY = (yaxis.type === 'log') ? yaxis.d2l : function(y) { return y; }; - var connectgaps = !!options.connectgaps; + var i, xx, yy, ex0, ex1, ey0, ey1; - // update viewport & range - 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 - ]; + for(i = 0; i < len; ++i) { + xData[i] = xx = getX(x[i]); + yData[i] = yy = getY(y[i]); - var range = [ - xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1] - ]; + // if(isNaN(xx) || isNaN(yy)) continue; - // get error values - var errorVals = ErrorBars.calcFromTrace(options, container._fullLayout); + positions[ptr++] = parseFloat(xx); + positions[ptr++] = parseFloat(yy); - var len = x.length, - positions = Array(2 * len), - errorsX = new Float64Array(4 * len), - errorsY = new Float64Array(4 * len), - pId = 0, - ptr = 0, - ptrX = 0, - ptrY = 0; + ex0 = errorsX[ptrX++] = xx - errorVals[i].xs || 0; + ex1 = errorsX[ptrX++] = errorVals[i].xh - xx || 0; + errorsX[ptrX++] = 0; + errorsX[ptrX++] = 0; - var getX = (xaxis.type === 'log') ? xaxis.d2l : function(x) { return x; }; - var getY = (yaxis.type === 'log') ? yaxis.d2l : function(y) { return y; }; + errorsY[ptrY++] = 0; + errorsY[ptrY++] = 0; + ey0 = errorsY[ptrY++] = yy - errorVals[i].ys || 0; + ey1 = errorsY[ptrY++] = errorVals[i].yh - yy || 0; + } - var i, xx, yy, ex0, ex1, ey0, ey1; + // update lines + if(hasLines) { + lineOptions = {}; + lineOptions.positions = positions, + lineOptions.thickness = options.line.width, + lineOptions.color = options.line.color, + lineOptions.opacity = options.opacity, + lineOptions.join = options.opacity === 1.0 ? 'rect' : 'round'; + lineOptions.overlay = true; + + var lineWidth = lineOptions.thickness, + dashes = (DASHES[options.line.dash] || [1]).slice(); + + for(i = 0; i < dashes.length; ++i) dashes[i] *= lineWidth; + + // FIXME: make regl renderer for fills + switch(options.fill) { + case 'tozeroy': + // lineOptions.fill = [false, true, false, false]; + break; + case 'tozerox': + // lineOptions.fill = [true, false, false, false]; + break; + default: + // lineOptions.fill = [false, false, false, false]; + break; + } + var fillColor = str2RGBArray(options.fillcolor); - for(i = 0; i < len; ++i) { - xData[i] = xx = getX(x[i]); - yData[i] = yy = getY(y[i]); + lineOptions.dashes = dashes; + // lineOptions.fillColor = [fillColor, fillColor, fillColor, fillColor]; - // if(isNaN(xx) || isNaN(yy)) continue; + lineOptions.viewport = viewport; + lineOptions.range = range; + } - positions[ptr++] = parseFloat(xx); - positions[ptr++] = parseFloat(yy); - ex0 = errorsX[ptrX++] = xx - errorVals[i].xs || 0; - ex1 = errorsX[ptrX++] = errorVals[i].xh - xx || 0; - errorsX[ptrX++] = 0; - errorsX[ptrX++] = 0; + // updateError('X', options, positions, errorsX); + // updateError('Y', options, positions, errorsY); - errorsY[ptrY++] = 0; - errorsY[ptrY++] = 0; - ey0 = errorsY[ptrY++] = yy - errorVals[i].ys || 0; - ey1 = errorsY[ptrY++] = errorVals[i].yh - yy || 0; - } + var sizes, selIds; - // update lines - if(hasLines) { - lineOptions = {}; - lineOptions.positions = positions, - lineOptions.thickness = options.line.width, - lineOptions.color = options.line.color, - lineOptions.opacity = options.opacity, - lineOptions.join = options.opacity === 1.0 ? 'rect' : 'round'; - lineOptions.overlay = true; - - var lineWidth = lineOptions.thickness, - dashes = (DASHES[options.line.dash] || [1]).slice(); - - for(i = 0; i < dashes.length; ++i) dashes[i] *= lineWidth; - - // FIXME: make regl renderer for fills - switch(options.fill) { - case 'tozeroy': - // lineOptions.fill = [false, true, false, false]; - break; - case 'tozerox': - // lineOptions.fill = [true, false, false, false]; - break; - default: - // lineOptions.fill = [false, false, false, false]; - break; + if(selection && selection.length) { + selIds = {}; + for(i = 0; i < selection.length; i++) { + selIds[selection[i].pointNumber] = true; + } } - var fillColor = str2RGBArray(options.fillcolor); - lineOptions.dashes = dashes; - // lineOptions.fillColor = [fillColor, fillColor, fillColor, fillColor]; + if(hasMarkers) { + scatterOptions = {}; + scatterOptions.positions = positions; - lineOptions.viewport = viewport; - lineOptions.range = range; - } + // TODO rewrite convert function so that + // we don't have to loop through the data another time + scatterOptions.sizes = new Array(len); + scatterOptions.markers = new Array(len); + scatterOptions.borderSizes = new Array(len); + scatterOptions.colors = new Array(len); + scatterOptions.borderColors = new Array(len); - // updateError('X', options, positions, errorsX); - // updateError('Y', options, positions, errorsY); + var markerSizeFunc = makeBubbleSizeFn(options); - var sizes, selIds; + var markerOpts = options.marker; + var markerOpacity = markerOpts.opacity; + var traceOpacity = options.opacity; + var symbols = markerOpts.symbol; - if(selection && selection.length) { - selIds = {}; - for(i = 0; i < selection.length; i++) { - selIds[selection[i].pointNumber] = true; - } - } + var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); + var borderSizes = convertNumber(markerOpts.line.width, len); + var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len); + var size, symbol, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, symbolFunc, symbolNoDot, symbolSdf, symbolNoFill, symbolPath, isDot; - if(hasMarkers) { - scatterOptions = {}; - scatterOptions.positions = positions; + sizes = convertArray(markerSizeFunc, markerOpts.size, len); - // TODO rewrite convert function so that - // we don't have to loop through the data another time + for(i = 0; i < len; ++i) { + symbol = Array.isArray(symbols) ? symbols[i] : symbols; + symbolNumber = Drawing.symbolNumber(symbol); + symbolFunc = Drawing.symbolFuncs[symbolNumber % 100]; + symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100]; + symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100]; - scatterOptions.sizes = new Array(len); - scatterOptions.markers = new Array(len); - scatterOptions.borderSizes = new Array(len); - scatterOptions.colors = new Array(len); - scatterOptions.borderColors = new Array(len); + isOpen = /-open/.test(symbol); + isDot = /-dot/.test(symbol); + isDimmed = selIds && !selIds[i]; - var markerSizeFunc = makeBubbleSizeFn(options); + _colors = colors; - var markerOpts = options.marker; - var markerOpacity = markerOpts.opacity; - var traceOpacity = options.opacity; - var symbols = markerOpts.symbol; - - var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); - var borderSizes = convertNumber(markerOpts.line.width, len); - var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len); - var size, symbol, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, symbolFunc, symbolNoDot, symbolSdf, symbolNoFill, symbolPath, isDot; - - sizes = convertArray(markerSizeFunc, markerOpts.size, len); - - for(i = 0; i < len; ++i) { - symbol = Array.isArray(symbols) ? symbols[i] : symbols; - symbolNumber = Drawing.symbolNumber(symbol); - symbolFunc = Drawing.symbolFuncs[symbolNumber % 100]; - symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100]; - symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100]; - - isOpen = /-open/.test(symbol); - isDot = /-dot/.test(symbol); - isDimmed = selIds && !selIds[i]; - - _colors = colors; - - if(isOpen) { - _borderColors = colors; - } else { - _borderColors = borderColors; - } + 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[i]; - bw = borderSizes[i]; + // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798 + // for more info on this logic + size = sizes[i]; + bw = borderSizes[i]; - scatterOptions.sizes[i] = size; + scatterOptions.sizes[i] = size; - if(symbol === 'circle') { - scatterOptions.markers[i] = null; - } - else { - // get symbol sdf from cache or generate it - if(SYMBOL_SDF[symbol]) { - symbolSdf = SYMBOL_SDF[symbol]; - } else { - if(isDot && !symbolNoDot) { - symbolPath = symbolFunc(SYMBOL_SIZE * 1.1) + SYMBOL_SVG_CIRCLE; - } - else { - symbolPath = symbolFunc(SYMBOL_SIZE); + if(symbol === 'circle') { + scatterOptions.markers[i] = null; + } + else { + // get symbol sdf from cache or generate it + if(SYMBOL_SDF[symbol]) { + symbolSdf = SYMBOL_SDF[symbol]; + } else { + 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; } - 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; + scatterOptions.markers[i] = symbolSdf || null; } - - scatterOptions.markers[i] = symbolSdf || null; - } - scatterOptions.borderSizes[i] = 0.5 * bw; - - var optColors = scatterOptions.colors; - var dim = isDimmed ? DESELECTDIM : 1; - if(!optColors[i]) optColors[i] = []; - if(isOpen || symbolNoFill) { - optColors[i][0] = TRANSPARENT[0]; - optColors[i][1] = TRANSPARENT[1]; - optColors[i][2] = TRANSPARENT[2]; - optColors[i][3] = TRANSPARENT[3]; - } else { - optColors[i][0] = _colors[4 * i + 0] * 255; - optColors[i][1] = _colors[4 * i + 1] * 255; - optColors[i][2] = _colors[4 * i + 2] * 255; - optColors[i][3] = dim * _colors[4 * i + 3] * 255; + scatterOptions.borderSizes[i] = 0.5 * bw; + + var optColors = scatterOptions.colors; + var dim = isDimmed ? DESELECTDIM : 1; + if(!optColors[i]) optColors[i] = []; + if(isOpen || symbolNoFill) { + optColors[i][0] = TRANSPARENT[0]; + optColors[i][1] = TRANSPARENT[1]; + optColors[i][2] = TRANSPARENT[2]; + optColors[i][3] = TRANSPARENT[3]; + } else { + optColors[i][0] = _colors[4 * i + 0] * 255; + optColors[i][1] = _colors[4 * i + 1] * 255; + optColors[i][2] = _colors[4 * i + 2] * 255; + optColors[i][3] = dim * _colors[4 * i + 3] * 255; + } + if(!scatterOptions.borderColors[i]) scatterOptions.borderColors[i] = []; + scatterOptions.borderColors[i][0] = _borderColors[4 * i + 0] * 255; + scatterOptions.borderColors[i][1] = _borderColors[4 * i + 1] * 255; + scatterOptions.borderColors[i][2] = _borderColors[4 * i + 2] * 255; + scatterOptions.borderColors[i][3] = dim * _borderColors[4 * i + 3] * 255; } - if(!scatterOptions.borderColors[i]) scatterOptions.borderColors[i] = []; - scatterOptions.borderColors[i][0] = _borderColors[4 * i + 0] * 255; - scatterOptions.borderColors[i][1] = _borderColors[4 * i + 1] * 255; - scatterOptions.borderColors[i][2] = _borderColors[4 * i + 2] * 255; - scatterOptions.borderColors[i][3] = dim * _borderColors[4 * i + 3] * 255; + + scatterOptions.viewport = viewport; + scatterOptions.range = range; } - scatterOptions.viewport = viewport; - scatterOptions.range = range; - } + batch.push({ + scatter: scatterOptions, + line: lineOptions + }); - batch.push({ - scatter: scatterOptions, - line: lineOptions + // add item for autorange routine + // former expandAxesFancy + Axes.expand(xaxis, x, {padded: true, ppad: sizes}); + Axes.expand(yaxis, y, {padded: true, ppad: sizes}); + + // provide reference for selecting points + if(!cdscatter[0].glTrace) { + cdscatter[0].glTrace = this; + } }); - // add item for autorange routine - // former expandAxesFancy - Axes.expand(xaxis, x, {padded: true, ppad: sizes}); - Axes.expand(yaxis, y, {padded: true, ppad: sizes}); + updateBatch(batch); + }; - // provide reference for selecting points - if(!cdscatter[0].glTrace) { - cdscatter[0].glTrace = this; + + function updateError(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; } - }); - this.updateBatch(batch); -}; + if(this['hasError' + axLetter]) { + errorObj.options.positions = positions; + errorObj.options.errors = errors; + errorObj.options.capSize = errorOptions.width; + errorObj.options.lineWidth = errorOptions.thickness; // ballpark rescaling + errorObj.options.color = convertColor(errorOptions.color, 1, 1); + errorObj.update(); + } + else { + errorObj.clear(); + } + }; -proto.updateError = function(axLetter, options, positions, errors) { - var errorObj = this['error' + axLetter], - errorOptions = options['error_' + axLetter.toLowerCase()]; + update.range = updateRange; - if(axLetter.toLowerCase() === 'x' && errorOptions.copy_ystyle) { - errorOptions = options.error_y; - } + return update; +} - if(this['hasError' + axLetter]) { - errorObj.options.positions = positions; - errorObj.options.errors = errors; - errorObj.options.capSize = errorOptions.width; - errorObj.options.lineWidth = errorOptions.thickness; // ballpark rescaling - errorObj.options.color = convertColor(errorOptions.color, 1, 1); - errorObj.update(); - } - else { - errorObj.clear(); - } -}; convertNumber = convertArray.bind(null, function(x) { return +x; }); From 6b0acabaa5a6b4ed197456835f0e7f6ba33e324a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 6 Oct 2017 15:38:18 -0400 Subject: [PATCH 052/151] Review comments --- src/fonts/ploticon/config.json | 2 +- src/plot_api/plot_api.js | 16 +++++++++------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/fonts/ploticon/config.json b/src/fonts/ploticon/config.json index 70be69f3e79..6bdb659f75d 100644 --- a/src/fonts/ploticon/config.json +++ b/src/fonts/ploticon/config.json @@ -1513,4 +1513,4 @@ "src": "fontawesome" } ] -} +} \ No newline at end of file diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index c17f12befaa..4dd65095e07 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -206,13 +206,15 @@ Plotly.plot = function(gd, data, layout, config) { .attr('class', function(d) { return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); }) - .style('position', 'absolute') - .style('top', 0) - .style('left', 0) - .style('width', '100%') - .style('height', '100%') - .style('pointer-events', 'none') - .style('overflow', 'visible') + .style({ + 'position': 'absolute', + 'top': 0, + 'left': 0, + 'width': '100%', + 'height': '100%', + 'pointer-events': 'none', + 'overflow': 'visible' + }) .attr('width', fullLayout.width) .attr('height', fullLayout.height); From 3b29829b185f87f83d9327d18818d830ab88dd7b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 6 Oct 2017 15:51:50 -0400 Subject: [PATCH 053/151] Merge hasCategory and hasPlotType --- src/plot_api/plot_api.js | 2 +- src/plot_api/subroutines.js | 6 ------ src/plots/cartesian/dragbox.js | 4 ++-- src/plots/plots.js | 10 +++------- src/traces/scatterregl/plot.js | 4 ++-- 5 files changed, 8 insertions(+), 18 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 4dd65095e07..995461545e6 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -194,7 +194,7 @@ Plotly.plot = function(gd, data, layout, config) { } } - fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas').data(fullLayout._hasCategory('gl') ? [{ + fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas').data(fullLayout._has('gl') ? [{ key: 'contextLayer' }, { key: 'focusLayer' diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index 4e907028c32..dc18ea7ae3c 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -501,12 +501,6 @@ exports.doModeBar = function(gd) { subplotObj.updateFx(fullLayout.dragmode, fullLayout.hovermode); } - subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d'); - for(i = 0; i < subplotIds.length; i++) { - subplotObj = fullLayout._plots[subplotIds[i]]._scene2d; - subplotObj.updateFx(fullLayout.dragmode); - } - subplotIds = Plots.getSubplotIds(fullLayout, 'mapbox'); for(i = 0; i < subplotIds.length; i++) { subplotObj = fullLayout[subplotIds[i]]._subplot; diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 0a6e0fc1ed0..4cfca6ff13f 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -768,11 +768,11 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { .call(Drawing.hideOutsideRangePoints, subplot); // scattergl translate - if(subplot._scattergl) { + if(subplot._scene && subplot._scene.range) { // FIXME: possibly we could update axis internal _r and _rl here var xaRange = Lib.simpleMap(xa2.range, xa2.r2l), yaRange = Lib.simpleMap(ya2.range, ya2.r2l); - subplot._scattergl.range( + subplot._scene.range( [xaRange[0], yaRange[0], xaRange[1], yaRange[1]] ); } diff --git a/src/plots/plots.js b/src/plots/plots.js index 9e29fcbfa9b..d11b4b16810 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -458,7 +458,6 @@ plots.supplyDefaults = function(gd) { // attach helper method to check whether a plot type is present on graph newFullLayout._has = plots._hasPlotType.bind(newFullLayout); - newFullLayout._hasCategory = plots._hasCategory.bind(newFullLayout); // special cases that introduce interactions between traces var _modules = newFullLayout._modules; @@ -565,7 +564,9 @@ plots.createTransitionData = function(gd) { // helper function to be bound to fullLayout to check // whether a certain plot type is present on plot +// or trace has a category plots._hasPlotType = function(category) { + // check plot var basePlotModules = this._basePlotModules || []; for(var i = 0; i < basePlotModules.length; i++) { @@ -574,14 +575,9 @@ plots._hasPlotType = function(category) { if(_module.name === category) return true; } - return false; -}; - -// check whether trace has a category -plots._hasCategory = function(category) { + // check trace var modules = this._modules || []; - // create canvases only in case if there is at least one regl component for(var i = 0; i < modules.length; i++) { var _ = modules[i]; if(_.categories && _.categories.indexOf(category) >= 0) { diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 6c588c2798d..34a1f14a604 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -44,13 +44,13 @@ function createLineWithMarkers(container, plotinfo, cdata) { var layout = container._fullLayout; var subplotObj = layout._plots.xy; - var scatter = subplotObj._scattergl; + var scatter = subplotObj._scene; // create regl-scatter, if not defined if(scatter === undefined) { // TODO: enhance picking // TODO: figure out if there is a way to detect only new passed options - scatter = subplotObj._scattergl = createScatterScene(container); + scatter = subplotObj._scene = createScatterScene(container); } scatter(cdata); From 1da90623ef5547dfc2bc0572fb690f68daf7ab61 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 6 Oct 2017 16:45:32 -0400 Subject: [PATCH 054/151] Some lint fixes --- src/plots/plots.js | 5 +++-- src/traces/scatterregl/plot.js | 36 ++++++++++++++++------------------ 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index d11b4b16810..46430fe737f 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -568,8 +568,9 @@ plots.createTransitionData = function(gd) { plots._hasPlotType = function(category) { // check plot var basePlotModules = this._basePlotModules || []; + var i; - for(var i = 0; i < basePlotModules.length; i++) { + for(i = 0; i < basePlotModules.length; i++) { var _module = basePlotModules[i]; if(_module.name === category) return true; @@ -578,7 +579,7 @@ plots._hasPlotType = function(category) { // check trace var modules = this._modules || []; - for(var i = 0; i < modules.length; i++) { + for(i = 0; i < modules.length; i++) { var _ = modules[i]; if(_.categories && _.categories.indexOf(category) >= 0) { return true; diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 34a1f14a604..803c5ec1f20 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -22,7 +22,6 @@ var createLine = require('regl-line2d'); var Drawing = require('../../components/drawing'); var svgSdf = require('svg-path-sdf'); var createRegl = require('regl'); -var nanoraf = require('nanoraf'); var DESELECTDIM = 0.2; var TRANSPARENT = [0, 0, 0, 0]; @@ -59,8 +58,6 @@ function createLineWithMarkers(container, plotinfo, cdata) { } function createScatterScene(container) { - var connectgaps = true; - var canvas = container.querySelector('.gl-canvas-focus'); var regl = createRegl({ @@ -97,13 +94,18 @@ function createScatterScene(container) { } updateBatch(batch); - }; + } function updateBatch(batch) { + // make sure no old graphics on the canvas + regl.clear({ + color: [0, 0, 0, 0] + }); + + var lineBatch = [], scatterBatch = [], i; + // update options of line and scatter components directly - var lineBatch = []; - var scatterBatch = []; - for(var i = 0; i < batch.length; i++) { + for(i = 0; i < batch.length; i++) { lineBatch.push(batch[i].line || null); scatterBatch.push(batch[i].scatter || null); } @@ -113,10 +115,10 @@ function createScatterScene(container) { count = batch.length; - //rendering requires proper batch sequence - for(var i = 0; i < count; i++) { - var lineBatch = Array(batch.length); - var scatterBatch = Array(batch.length); + // rendering requires proper batch sequence + for(i = 0; i < count; i++) { + lineBatch = Array(batch.length); + scatterBatch = Array(batch.length); for(var j = 0; j < batch.length; j++) { lineBatch[j] = i === j; scatterBatch[j] = i === j; @@ -124,9 +126,9 @@ function createScatterScene(container) { line.draw(lineBatch); scatter.draw(scatterBatch); } - }; + } - //update based on calc data + // update based on calc data function update(cdscatters) { var batch = []; @@ -170,8 +172,6 @@ function createScatterScene(container) { hasMarkers = subTypes.hasMarkers(options); } - var connectgaps = !!options.connectgaps; - // update viewport & range var viewport = [ vpSize.l + xaxis.domain[0] * vpSize.w, @@ -393,7 +393,7 @@ function createScatterScene(container) { }); updateBatch(batch); - }; + } function updateError(axLetter, options, positions, errors) { @@ -416,7 +416,7 @@ function createScatterScene(container) { else { errorObj.clear(); } - }; + } update.range = updateRange; @@ -424,8 +424,6 @@ function createScatterScene(container) { } - - convertNumber = convertArray.bind(null, function(x) { return +x; }); convertColorBase = convertArray.bind(null, str2RGBArray); From 753b3f09dc07e9cf17dff0b32998e1bfaf23d253 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 10 Oct 2017 14:21:53 -0400 Subject: [PATCH 055/151] Flatten structure more --- src/traces/scatterregl/plot.js | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 803c5ec1f20..8c8da865899 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -36,28 +36,20 @@ var SYMBOL_SVG_CIRCLE = Drawing.symbolFuncs[0](SYMBOL_SIZE * 0.05); var convertNumber, convertColorBase; -module.exports = createLineWithMarkers; +module.exports = createScatterScene; -function createLineWithMarkers(container, plotinfo, cdata) { +function createScatterScene(container, plotinfo, cdata) { var layout = container._fullLayout; + var subplotObj = layout._plots[plotinfo.id]; - var subplotObj = layout._plots.xy; - var scatter = subplotObj._scene; - - // create regl-scatter, if not defined - if(scatter === undefined) { - // TODO: enhance picking - // TODO: figure out if there is a way to detect only new passed options - scatter = subplotObj._scene = createScatterScene(container); + if(subplotObj._scene) { + subplotObj._scene(cdata); + return; } - scatter(cdata); - - return scatter; -} + subplotObj._scene = update; -function createScatterScene(container) { var canvas = container.querySelector('.gl-canvas-focus'); var regl = createRegl({ @@ -98,9 +90,9 @@ function createScatterScene(container) { function updateBatch(batch) { // make sure no old graphics on the canvas - regl.clear({ - color: [0, 0, 0, 0] - }); + // regl.clear({ + // color: [0, 0, 0, 0] + // }); var lineBatch = [], scatterBatch = [], i; @@ -420,6 +412,8 @@ function createScatterScene(container) { update.range = updateRange; + update(cdata); + return update; } From bd667bed504193a6a0ed58458de49952e6d63bb1 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 10 Oct 2017 15:30:38 -0400 Subject: [PATCH 056/151] Preserve drawing buffer redraw --- src/traces/scatterregl/plot.js | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 8c8da865899..5a885030ce9 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -54,6 +54,9 @@ function createScatterScene(container, plotinfo, cdata) { var regl = createRegl({ canvas: canvas, + attributes: { + preserveDrawingBuffer: false + }, extensions: ['ANGLE_instanced_arrays', 'OES_element_index_uint'], pixelRatio: container._context.plotGlPixelRatio || global.devicePixelRatio }); @@ -73,7 +76,7 @@ function createScatterScene(container, plotinfo, cdata) { dashes: [1] }); - var count = 0; + var count = 0, viewport; function updateRange(range) { var batch = []; @@ -90,9 +93,18 @@ function createScatterScene(container, plotinfo, cdata) { function updateBatch(batch) { // make sure no old graphics on the canvas - // regl.clear({ - // color: [0, 0, 0, 0] - // }); + var gl = regl._gl; + gl.enable(gl.SCISSOR_TEST); + gl.scissor( + viewport[0] - 1, + viewport[1] - 1, + viewport[2] - viewport[0] + 2, + viewport[3] - viewport[1] + 2 + ); + gl.clearColor(0,0,0,0); + gl.clear(gl.COLOR_BUFFER_BIT); + // FIXME: why ↓ does not suffice here? regl bug? + // regl.clear({color: [0, 0, 0, 0], depth: 1}); var lineBatch = [], scatterBatch = [], i; @@ -133,9 +145,9 @@ function createScatterScene(container, plotinfo, cdata) { var xaxis = Axes.getFromId(container, options.xaxis || 'x'), yaxis = Axes.getFromId(container, options.yaxis || 'y'), selection = options.selection; - var vpSize = container._fullLayout._size, - width = container._fullLayout.width, - height = container._fullLayout.height; + var vpSize = layout._size, + width = layout.width, + height = layout.height; // makeCalcdata runs d2c (data-to-coordinate) on every point var x = options.x; @@ -165,7 +177,7 @@ function createScatterScene(container, plotinfo, cdata) { } // update viewport & range - var viewport = [ + viewport = [ vpSize.l + xaxis.domain[0] * vpSize.w, vpSize.b + yaxis.domain[0] * vpSize.h, (width - vpSize.r) - (1 - xaxis.domain[1]) * vpSize.w, @@ -177,7 +189,7 @@ function createScatterScene(container, plotinfo, cdata) { ]; // get error values - var errorVals = ErrorBars.calcFromTrace(options, container._fullLayout); + var errorVals = ErrorBars.calcFromTrace(options, layout); var len = x.length, positions = Array(2 * len), From 791211c8602db120bdb28bb12fb8f27959c08797 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 10 Oct 2017 16:31:24 -0400 Subject: [PATCH 057/151] Fix multiplots rendering --- src/plots/cartesian/dragbox.js | 24 ++++++++++++---------- src/plots/cartesian/index.js | 8 ++++++++ src/traces/scatterregl/plot.js | 37 +++++++++++++++++++--------------- 3 files changed, 42 insertions(+), 27 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 4cfca6ff13f..e678596463d 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -718,6 +718,16 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { editX2 = editX && !xa2.fixedrange && (xa.indexOf(xa2) !== -1), editY2 = editY && !ya2.fixedrange && (ya.indexOf(ya2) !== -1); + // scattergl translate + if(subplot._scene && subplot._scene.range) { + // FIXME: possibly we could update axis internal _r and _rl here + var xaRange = Lib.simpleMap(xa2.range, xa2.r2l), + yaRange = Lib.simpleMap(ya2.range, ya2.r2l); + subplot._scene.range( + [xaRange[0], yaRange[0], xaRange[1], yaRange[1]] + ); + } + if(editX2) { xScaleFactor2 = xScaleFactor; clipDx = ew ? viewBox[0] : getShift(xa2, xScaleFactor2); @@ -737,7 +747,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; @@ -766,16 +778,6 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { scatterPoints.selectAll('.textpoint') .call(Drawing.setTextPointsScale, xScaleFactor2, yScaleFactor2) .call(Drawing.hideOutsideRangePoints, subplot); - - // scattergl translate - if(subplot._scene && subplot._scene.range) { - // FIXME: possibly we could update axis internal _r and _rl here - var xaRange = Lib.simpleMap(xa2.range, xa2.r2l), - yaRange = Lib.simpleMap(ya2.range, ya2.r2l); - subplot._scene.range( - [xaRange[0], yaRange[0], xaRange[1], yaRange[1]] - ); - } } } diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index 3ca43838d02..9f68da3b0f9 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -48,6 +48,14 @@ exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) { } } + // clear gl frame, if any + for(var id in fullLayout._plots) { + var scatterScene = fullLayout._plots[id]._scene; + if(scatterScene && scatterScene.clear) { + scatterScene.clear(); + } + } + for(i = 0; i < subplots.length; i++) { var subplot = subplots[i], subplotInfo = fullLayout._plots[subplot]; diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 5a885030ce9..55f52731bd6 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -36,10 +36,10 @@ var SYMBOL_SVG_CIRCLE = Drawing.symbolFuncs[0](SYMBOL_SIZE * 0.05); var convertNumber, convertColorBase; -module.exports = createScatterScene; +module.exports = plot; -function createScatterScene(container, plotinfo, cdata) { +function plot(container, plotinfo, cdata) { var layout = container._fullLayout; var subplotObj = layout._plots[plotinfo.id]; @@ -92,20 +92,6 @@ function createScatterScene(container, plotinfo, cdata) { } function updateBatch(batch) { - // make sure no old graphics on the canvas - var gl = regl._gl; - gl.enable(gl.SCISSOR_TEST); - gl.scissor( - viewport[0] - 1, - viewport[1] - 1, - viewport[2] - viewport[0] + 2, - viewport[3] - viewport[1] + 2 - ); - gl.clearColor(0,0,0,0); - gl.clear(gl.COLOR_BUFFER_BIT); - // FIXME: why ↓ does not suffice here? regl bug? - // regl.clear({color: [0, 0, 0, 0], depth: 1}); - var lineBatch = [], scatterBatch = [], i; // update options of line and scatter components directly @@ -422,7 +408,26 @@ function createScatterScene(container, plotinfo, cdata) { } } + function clear() { + if(!viewport) return; + + // make sure no old graphics on the canvas + var gl = regl._gl; + gl.enable(gl.SCISSOR_TEST); + gl.scissor( + viewport[0] - 1, + viewport[1] - 1, + viewport[2] - viewport[0] + 2, + viewport[3] - viewport[1] + 2 + ); + gl.clearColor(0, 0, 0, 0); + gl.clear(gl.COLOR_BUFFER_BIT); + // FIXME: why ↓ does not suffice here? regl bug? + // regl.clear({color: [0, 0, 0, 0], depth: 1}); + } + update.range = updateRange; + update.clear = clear; update(cdata); From 56949e6d3ebc0b7d67bb3212b0ded94f468ca0a5 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 10 Oct 2017 18:06:03 -0400 Subject: [PATCH 058/151] Handle dates, disable batch render --- src/traces/scatterregl/plot.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 55f52731bd6..3a5dc2a70c5 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -106,15 +106,11 @@ function plot(container, plotinfo, cdata) { count = batch.length; // rendering requires proper batch sequence + //FIXME: add id/batch sequencing here + var last = 0 for(i = 0; i < count; i++) { - lineBatch = Array(batch.length); - scatterBatch = Array(batch.length); - for(var j = 0; j < batch.length; j++) { - lineBatch[j] = i === j; - scatterBatch[j] = i === j; - } - line.draw(lineBatch); - scatter.draw(scatterBatch); + scatter.draw(i) + line.draw(i) } } @@ -136,11 +132,13 @@ function plot(container, plotinfo, cdata) { height = layout.height; // makeCalcdata runs d2c (data-to-coordinate) on every point - var x = options.x; - var y = options.y; + var x = xaxis.makeCalcdata(options, 'x'); + var y = yaxis.makeCalcdata(options, 'y'); + var xData = Array(x.length); var yData = Array(y.length); + var isVisible = false; var hasLines = false; var hasErrorX = false; @@ -212,7 +210,7 @@ function plot(container, plotinfo, cdata) { } // update lines - if(hasLines) { + if(hasLines && x.length > 1 && options.line) { lineOptions = {}; lineOptions.positions = positions, lineOptions.thickness = options.line.width, From deefccd1950656ffdd6e71bb6ef3fe314ca6337b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 12 Oct 2017 14:28:07 -0400 Subject: [PATCH 059/151] Introduce regl-error2d --- src/traces/scatterregl/plot.js | 112 ++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 45 deletions(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 3a5dc2a70c5..572c480933b 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -19,6 +19,7 @@ var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); var DASHES = require('../../constants/gl2d_dashes'); var createScatter = require('regl-scatter2d'); var createLine = require('regl-line2d'); +var createError = require('regl-error2d'); var Drawing = require('../../components/drawing'); var svgSdf = require('svg-path-sdf'); var createRegl = require('regl'); @@ -61,56 +62,77 @@ function plot(container, plotinfo, cdata) { pixelRatio: container._context.plotGlPixelRatio || global.devicePixelRatio }); - var scatter = createScatter({ + //FIXME: provide defaults to lazy init + var scatter2d, line2d, errorX, errorY, fill2d; + var scatter2d = createScatter({ regl: regl, size: 12, color: [0, 0, 0, 1], borderSize: 1, borderColor: [0, 0, 0, 1] }); - var line = createLine({ + var line2d = createLine({ regl: regl, color: [0, 0, 0, 1], thickness: 1, miterLimit: 2, dashes: [1] }); + var errorX = createError(regl) + var errorY = createError(regl) + var fill2d = createLine(regl) var count = 0, viewport; function updateRange(range) { var batch = []; - + range = {range: range}; for(var i = 0; i < count; i++) { batch.push({ - line: {range: range}, - scatter: {range: range} + line: range, + scatter: range, + errorX: range, + errorY: range, + fill: range }); } updateBatch(batch); } + //update multi-traces data and render in proper layers order function updateBatch(batch) { - var lineBatch = [], scatterBatch = [], i; + var lineBatch = [], + scatterBatch = [], + errorXBatch = [], + errorYBatch = [], + fillBatch = [], + i; - // update options of line and scatter components directly for(i = 0; i < batch.length; i++) { - lineBatch.push(batch[i].line || null); - scatterBatch.push(batch[i].scatter || null); + lineBatch.push(batch[i].line); + scatterBatch.push(batch[i].scatter); + errorXBatch.push(batch[i].errorX); + errorYBatch.push(batch[i].errorY); + fillBatch.push(batch[i].fill); } - line.update(lineBatch); - scatter.update(scatterBatch); + line2d.update(lineBatch); + scatter2d.update(scatterBatch); + errorX.update(errorXBatch); + errorY.update(errorYBatch); + fill2d.update(fillBatch); count = batch.length; // rendering requires proper batch sequence - //FIXME: add id/batch sequencing here var last = 0 for(i = 0; i < count; i++) { - scatter.draw(i) - line.draw(i) + if (lineBatch[i]) line2d.draw(i) + if (scatterBatch[i]) scatter2d.draw(i) + if (errorXBatch[i]) errorX.draw(i) + if (errorYBatch[i]) errorY.draw(i) + if (fillBatch[i]) fill2d.draw(i) } } @@ -121,7 +143,7 @@ function plot(container, plotinfo, cdata) { cdscatters.forEach(function(cdscatter) { if(!cdscatter) return; - var lineOptions, scatterOptions; + var lineOptions, scatterOptions, errorXOptions, errorYOptions, fillOptions; var options = cdscatter[0].trace; var xaxis = Axes.getFromId(container, options.xaxis || 'x'), @@ -193,8 +215,6 @@ function plot(container, plotinfo, cdata) { xData[i] = xx = getX(x[i]); yData[i] = yy = getY(y[i]); - // if(isNaN(xx) || isNaN(yy)) continue; - positions[ptr++] = parseFloat(xx); positions[ptr++] = parseFloat(yy); @@ -209,6 +229,32 @@ function plot(container, plotinfo, cdata) { ey1 = errorsY[ptrY++] = errorVals[i].yh - yy || 0; } + if (hasErrorX) { + var errorOptions = options.error_x; + if(errorOptions.copy_ystyle) { + errorOptions = options.error_y; + } + errorXOptions = {}; + errorXOptions.positions = positions; + errorXOptions.errors = errorsX; + errorXOptions.capSize = errorOptions.width; + errorXOptions.lineWidth = errorOptions.thickness; + errorXOptions.color = convertColor(errorOptions.color, 1, 1); + errorXOptions.viewport = viewport; + errorXOptions.range = range; + } + if (hasErrorY) { + var errorOptions = options.error_y; + errorYOptions = {}; + errorYOptions.positions = positions; + errorYOptions.errors = errorsY; + errorYOptions.capSize = errorOptions.width; + errorYOptions.lineWidth = errorOptions.thickness; + errorYOptions.color = convertColor(errorOptions.color, 1, 1); + errorYOptions.viewport = viewport; + errorYOptions.range = range; + } + // update lines if(hasLines && x.length > 1 && options.line) { lineOptions = {}; @@ -245,10 +291,6 @@ function plot(container, plotinfo, cdata) { lineOptions.range = range; } - - // updateError('X', options, positions, errorsX); - // updateError('Y', options, positions, errorsY); - var sizes, selIds; if(selection && selection.length) { @@ -366,7 +408,10 @@ function plot(container, plotinfo, cdata) { batch.push({ scatter: scatterOptions, - line: lineOptions + line: lineOptions, + errorX: errorXOptions, + errorY: errorYOptions, + fill: fillOptions }); // add item for autorange routine @@ -383,29 +428,6 @@ function plot(container, plotinfo, cdata) { updateBatch(batch); } - - function updateError(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; // ballpark rescaling - errorObj.options.color = convertColor(errorOptions.color, 1, 1); - - errorObj.update(); - } - else { - errorObj.clear(); - } - } - function clear() { if(!viewport) return; From 903943938f8b2b27a94d4fca3f2c4865a96e5950 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 12 Oct 2017 15:42:58 -0400 Subject: [PATCH 060/151] Normalize error bars --- src/traces/scatterregl/plot.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 572c480933b..29814e3481b 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -237,9 +237,9 @@ function plot(container, plotinfo, cdata) { errorXOptions = {}; errorXOptions.positions = positions; errorXOptions.errors = errorsX; - errorXOptions.capSize = errorOptions.width; + errorXOptions.capSize = errorOptions.width * 2; errorXOptions.lineWidth = errorOptions.thickness; - errorXOptions.color = convertColor(errorOptions.color, 1, 1); + errorXOptions.color = errorOptions.color; errorXOptions.viewport = viewport; errorXOptions.range = range; } @@ -248,9 +248,9 @@ function plot(container, plotinfo, cdata) { errorYOptions = {}; errorYOptions.positions = positions; errorYOptions.errors = errorsY; - errorYOptions.capSize = errorOptions.width; + errorYOptions.capSize = errorOptions.width * 2; errorYOptions.lineWidth = errorOptions.thickness; - errorYOptions.color = convertColor(errorOptions.color, 1, 1); + errorYOptions.color = errorOptions.color; errorYOptions.viewport = viewport; errorYOptions.range = range; } From 14063c9e8cc471fb8197cfe52a7ccf84c610003c Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 12 Oct 2017 17:07:38 -0400 Subject: [PATCH 061/151] Outline basic fill strategy --- src/traces/scatterregl/plot.js | 154 ++++++++++++++++++--------------- 1 file changed, 84 insertions(+), 70 deletions(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 29814e3481b..1e59177511b 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -15,6 +15,7 @@ var ErrorBars = require('../../components/errorbars'); var str2RGBArray = require('../../lib/str2rgbarray'); var formatColor = require('../../lib/gl_format_color'); var subTypes = require('../scatter/subtypes'); +var linkTraces = require('../scatter/link_traces'); var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); var DASHES = require('../../constants/gl2d_dashes'); var createScatter = require('regl-scatter2d'); @@ -44,6 +45,9 @@ function plot(container, plotinfo, cdata) { var layout = container._fullLayout; var subplotObj = layout._plots[plotinfo.id]; + // that is needed for fills + linkTraces(container, plotinfo, cdata); + if(subplotObj._scene) { subplotObj._scene(cdata); return; @@ -145,41 +149,49 @@ function plot(container, plotinfo, cdata) { var lineOptions, scatterOptions, errorXOptions, errorYOptions, fillOptions; - var options = cdscatter[0].trace; - var xaxis = Axes.getFromId(container, options.xaxis || 'x'), - yaxis = Axes.getFromId(container, options.yaxis || 'y'), - selection = options.selection; + var trace = cdscatter[0].trace; + var xaxis = Axes.getFromId(container, trace.xaxis || 'x'), + yaxis = Axes.getFromId(container, trace.yaxis || 'y'), + selection = trace.selection; var vpSize = layout._size, width = layout.width, height = layout.height; // makeCalcdata runs d2c (data-to-coordinate) on every point - var x = xaxis.makeCalcdata(options, 'x'); - var y = yaxis.makeCalcdata(options, 'y'); + var x = xaxis.makeCalcdata(trace, 'x'); + var y = yaxis.makeCalcdata(trace, 'y'); - var xData = Array(x.length); - var yData = Array(y.length); + // add item for autorange routine + // former expandAxesFancy + Axes.expand(xaxis, x, {padded: true, ppad: sizes}); + Axes.expand(yaxis, y, {padded: true, ppad: sizes}); + // convert log axes + if (xaxis.type === 'log') x = x.map(xaxis.d2l) + if (yaxis.type === 'log') y = y.map(xaxis.d2l) var isVisible = false; var hasLines = false; var hasErrorX = false; var hasErrorY = false; var hasMarkers = false; + var hasFill = false; - if(options.visible !== true) { + if(trace.visible !== true) { isVisible = false; hasLines = false; hasErrorX = false; hasErrorY = false; hasMarkers = false; + hasFill = false; } else { isVisible = true; - hasLines = subTypes.hasLines(options); - hasErrorX = options.error_x.visible === true; - hasErrorY = options.error_y.visible === true; - hasMarkers = subTypes.hasMarkers(options); + hasLines = subTypes.hasLines(trace) && x.length > 1 && trace.line; + hasErrorX = trace.error_x.visible === true; + hasErrorY = trace.error_y.visible === true; + hasMarkers = subTypes.hasMarkers(trace); + hasFill = hasLines && trace.fill; } // update viewport & range @@ -195,28 +207,18 @@ function plot(container, plotinfo, cdata) { ]; // get error values - var errorVals = ErrorBars.calcFromTrace(options, layout); + var errorVals = ErrorBars.calcFromTrace(trace, layout); - var len = x.length, - positions = Array(2 * len), + var positions = getPositions(x, y), + len = positions.length * .5, 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; }; + errorsY = new Float64Array(4 * len); - var i, xx, yy, ex0, ex1, ey0, ey1; + var i, xx, yy, ex0, ex1, ey0, ey1, ptrX = 0, ptrY = 0; for(i = 0; i < len; ++i) { - xData[i] = xx = getX(x[i]); - yData[i] = yy = getY(y[i]); - - positions[ptr++] = parseFloat(xx); - positions[ptr++] = parseFloat(yy); + xx = x[i]; + yy = y[i]; ex0 = errorsX[ptrX++] = xx - errorVals[i].xs || 0; ex1 = errorsX[ptrX++] = errorVals[i].xh - xx || 0; @@ -230,9 +232,9 @@ function plot(container, plotinfo, cdata) { } if (hasErrorX) { - var errorOptions = options.error_x; + var errorOptions = trace.error_x; if(errorOptions.copy_ystyle) { - errorOptions = options.error_y; + errorOptions = trace.error_y; } errorXOptions = {}; errorXOptions.positions = positions; @@ -244,7 +246,7 @@ function plot(container, plotinfo, cdata) { errorXOptions.range = range; } if (hasErrorY) { - var errorOptions = options.error_y; + var errorOptions = trace.error_y; errorYOptions = {}; errorYOptions.positions = positions; errorYOptions.errors = errorsY; @@ -255,42 +257,48 @@ function plot(container, plotinfo, cdata) { errorYOptions.range = range; } - // update lines - if(hasLines && x.length > 1 && options.line) { + if(hasLines) { lineOptions = {}; lineOptions.positions = positions, - lineOptions.thickness = options.line.width, - lineOptions.color = options.line.color, - lineOptions.opacity = options.opacity, - lineOptions.join = options.opacity === 1.0 ? 'rect' : 'round'; + lineOptions.thickness = trace.line.width, + lineOptions.color = trace.line.color, + lineOptions.opacity = trace.opacity, + lineOptions.join = trace.opacity === 1.0 ? 'rect' : 'round'; lineOptions.overlay = true; var lineWidth = lineOptions.thickness, - dashes = (DASHES[options.line.dash] || [1]).slice(); + dashes = (DASHES[trace.line.dash] || [1]).slice(); for(i = 0; i < dashes.length; ++i) dashes[i] *= lineWidth; - // FIXME: make regl renderer for fills - switch(options.fill) { - case 'tozeroy': - // lineOptions.fill = [false, true, false, false]; - break; - case 'tozerox': - // lineOptions.fill = [true, false, false, false]; - break; - default: - // lineOptions.fill = [false, false, false, false]; - break; - } - var fillColor = str2RGBArray(options.fillcolor); - - lineOptions.dashes = dashes; - // lineOptions.fillColor = [fillColor, fillColor, fillColor, fillColor]; - lineOptions.viewport = viewport; lineOptions.range = range; } + if (hasFill) { + fillOptions = {}; + fillOptions.fill = trace.fillcolor; + fillOptions.thickness = 0; + fillOptions.viewport = viewport; + fillOptions.range = range; + // console.log(trace.fill, trace._prevtrace, trace._nexttrace) + + if (trace.fill === 'tozeroy') { + var pos = [positions[0], 0] + pos = pos.concat(positions) + pos.push(positions[positions.length - 2]) + pos.push(0) + fillOptions.positions = pos + } + else if (trace.fill === 'tozerox') { + var pos = [0, positions[1]] + pos = pos.concat(positions) + pos.push(0) + pos.push(positions[positions.length - 1]) + fillOptions.positions = pos + } + } + var sizes, selIds; if(selection && selection.length) { @@ -313,11 +321,11 @@ function plot(container, plotinfo, cdata) { scatterOptions.colors = new Array(len); scatterOptions.borderColors = new Array(len); - var markerSizeFunc = makeBubbleSizeFn(options); + var markerSizeFunc = makeBubbleSizeFn(trace); - var markerOpts = options.marker; + var markerOpts = trace.marker; var markerOpacity = markerOpts.opacity; - var traceOpacity = options.opacity; + var traceOpacity = trace.opacity; var symbols = markerOpts.symbol; var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); @@ -413,16 +421,6 @@ function plot(container, plotinfo, cdata) { errorY: errorYOptions, fill: fillOptions }); - - // add item for autorange routine - // former expandAxesFancy - Axes.expand(xaxis, x, {padded: true, ppad: sizes}); - Axes.expand(yaxis, y, {padded: true, ppad: sizes}); - - // provide reference for selecting points - if(!cdscatter[0].glTrace) { - cdscatter[0].glTrace = this; - } }); updateBatch(batch); @@ -455,6 +453,22 @@ function plot(container, plotinfo, cdata) { } +//pack x,y arrays into single positions array +function getPositions (x, y) { + var len = Math.max(x.length, y.length) * 2 + var positions = [] + + for (var i = 0, j = 0; i < len; i++) { + var xx = parseFloat(x[i]) + var yy = parseFloat(y[i]) + if (isNaN(xx) || isNaN(yy)) continue; + positions.push(xx); + positions.push(yy); + } + return positions +} + + convertNumber = convertArray.bind(null, function(x) { return +x; }); convertColorBase = convertArray.bind(null, str2RGBArray); From 7fec2592be9c206efcd72e54d642ee50a4931204 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 12 Oct 2017 18:33:13 -0400 Subject: [PATCH 062/151] Step forward tonexty logic --- src/traces/scatterregl/plot.js | 54 ++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 1e59177511b..88464f7e5a6 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -209,16 +209,19 @@ function plot(container, plotinfo, cdata) { // get error values var errorVals = ErrorBars.calcFromTrace(trace, layout); - var positions = getPositions(x, y), - len = positions.length * .5, + var len = x.length, + positions = [len * 2], errorsX = new Float64Array(4 * len), errorsY = new Float64Array(4 * len); var i, xx, yy, ex0, ex1, ey0, ey1, ptrX = 0, ptrY = 0; for(i = 0; i < len; ++i) { - xx = x[i]; - yy = y[i]; + xx = parseFloat(x[i]); + yy = parseFloat(y[i]); + + positions[i * 2] = xx; + positions[i * 2 + 1] = yy; ex0 = errorsX[ptrX++] = xx - errorVals[i].xs || 0; ex1 = errorsX[ptrX++] = errorVals[i].xh - xx || 0; @@ -269,8 +272,10 @@ function plot(container, plotinfo, cdata) { var lineWidth = lineOptions.thickness, dashes = (DASHES[trace.line.dash] || [1]).slice(); + for(i = 0; i < dashes.length; ++i) dashes[i] *= lineWidth; + lineOptions.dashes = dashes; lineOptions.viewport = viewport; lineOptions.range = range; } @@ -281,22 +286,37 @@ function plot(container, plotinfo, cdata) { fillOptions.thickness = 0; fillOptions.viewport = viewport; fillOptions.range = range; - // console.log(trace.fill, trace._prevtrace, trace._nexttrace) + var pos = [] if (trace.fill === 'tozeroy') { - var pos = [positions[0], 0] + pos = [positions[0], 0] pos = pos.concat(positions) pos.push(positions[positions.length - 2]) pos.push(0) - fillOptions.positions = pos } else if (trace.fill === 'tozerox') { - var pos = [0, positions[1]] + pos = [0, positions[1]] pos = pos.concat(positions) pos.push(0) pos.push(positions[positions.length - 1]) - fillOptions.positions = pos } + else { + if (trace.fill === 'tonexty' && trace._prevtrace) { + pos = positions.slice(); + var nextPos = [], + nextX = trace._prevtrace.x, + nextY = trace._prevtrace.y, + len = nextX.length, xx, yy; + + for (var i = len; i--;) { + xx = parseFloat(nextX[i]), yy = parseFloat(nextY[i]); + if (isNaN(xx) || isNaN(yy)) continue + pos.push(xx) + pos.push(yy) + } + } + } + fillOptions.positions = pos } var sizes, selIds; @@ -453,22 +473,6 @@ function plot(container, plotinfo, cdata) { } -//pack x,y arrays into single positions array -function getPositions (x, y) { - var len = Math.max(x.length, y.length) * 2 - var positions = [] - - for (var i = 0, j = 0; i < len; i++) { - var xx = parseFloat(x[i]) - var yy = parseFloat(y[i]) - if (isNaN(xx) || isNaN(yy)) continue; - positions.push(xx); - positions.push(yy); - } - return positions -} - - convertNumber = convertArray.bind(null, function(x) { return +x; }); convertColorBase = convertArray.bind(null, str2RGBArray); From c7bf83c4f1903a95399b963067ce989c4f968a70 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 12 Oct 2017 19:33:40 -0400 Subject: [PATCH 063/151] Implement proper fillTo logic --- src/traces/scatterregl/plot.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index 88464f7e5a6..f03d27e6680 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -132,11 +132,11 @@ function plot(container, plotinfo, cdata) { // rendering requires proper batch sequence var last = 0 for(i = 0; i < count; i++) { - if (lineBatch[i]) line2d.draw(i) - if (scatterBatch[i]) scatter2d.draw(i) + if (fillBatch[i]) fill2d.draw(i) if (errorXBatch[i]) errorX.draw(i) if (errorYBatch[i]) errorY.draw(i) - if (fillBatch[i]) fill2d.draw(i) + if (lineBatch[i]) line2d.draw(i) + if (scatterBatch[i]) scatter2d.draw(i) } } @@ -191,7 +191,7 @@ function plot(container, plotinfo, cdata) { hasErrorX = trace.error_x.visible === true; hasErrorY = trace.error_y.visible === true; hasMarkers = subTypes.hasMarkers(trace); - hasFill = hasLines && trace.fill; + hasFill = trace.fill; } // update viewport & range @@ -301,19 +301,22 @@ function plot(container, plotinfo, cdata) { pos.push(positions[positions.length - 1]) } else { - if (trace.fill === 'tonexty' && trace._prevtrace) { - pos = positions.slice(); + var nextTrace = trace._nexttrace + if (nextTrace && trace.fill === 'tonexty') { + var pos = positions.slice() + var nextPos = [], - nextX = trace._prevtrace.x, - nextY = trace._prevtrace.y, - len = nextX.length, xx, yy; + nextX = nextTrace.x, + nextY = nextTrace.y, + xx, yy; - for (var i = len; i--;) { + for (var i = nextX.length; i--;) { xx = parseFloat(nextX[i]), yy = parseFloat(nextY[i]); if (isNaN(xx) || isNaN(yy)) continue pos.push(xx) pos.push(yy) } + fillOptions.fill = nextTrace.fillcolor; } } fillOptions.positions = pos @@ -331,10 +334,6 @@ function plot(container, plotinfo, cdata) { if(hasMarkers) { scatterOptions = {}; scatterOptions.positions = positions; - - // TODO rewrite convert function so that - // we don't have to loop through the data another time - scatterOptions.sizes = new Array(len); scatterOptions.markers = new Array(len); scatterOptions.borderSizes = new Array(len); From 26998d736743c08b4bede5f580ea31ae97f05fed Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 13 Oct 2017 20:16:36 -0400 Subject: [PATCH 064/151] Optimize rendering, make scatter fills --- src/traces/scatterregl/plot.js | 620 ++++++++++++++++++--------------- 1 file changed, 330 insertions(+), 290 deletions(-) diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index f03d27e6680..a9b62d28626 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -52,39 +52,43 @@ function plot(container, plotinfo, cdata) { subplotObj._scene(cdata); return; } - - subplotObj._scene = update; + else { + subplotObj._scene = update; + } var canvas = container.querySelector('.gl-canvas-focus'); - var regl = createRegl({ - canvas: canvas, - attributes: { - preserveDrawingBuffer: false - }, - extensions: ['ANGLE_instanced_arrays', 'OES_element_index_uint'], - pixelRatio: container._context.plotGlPixelRatio || global.devicePixelRatio - }); + var regl = layout._regl; + if(!regl) { + regl = layout._regl = createRegl({ + canvas: canvas, + attributes: { + preserveDrawingBuffer: false + }, + extensions: ['ANGLE_instanced_arrays', 'OES_element_index_uint'], + pixelRatio: container._context.plotGlPixelRatio || global.devicePixelRatio + }); + } - //FIXME: provide defaults to lazy init + // FIXME: provide defaults to lazy init var scatter2d, line2d, errorX, errorY, fill2d; - var scatter2d = createScatter({ + scatter2d = createScatter({ regl: regl, size: 12, color: [0, 0, 0, 1], borderSize: 1, borderColor: [0, 0, 0, 1] }); - var line2d = createLine({ + line2d = createLine({ regl: regl, color: [0, 0, 0, 1], thickness: 1, miterLimit: 2, dashes: [1] }); - var errorX = createError(regl) - var errorY = createError(regl) - var fill2d = createLine(regl) + errorX = createError(regl); + errorY = createError(regl); + fill2d = createLine(regl); var count = 0, viewport; @@ -104,8 +108,10 @@ function plot(container, plotinfo, cdata) { updateBatch(batch); } - //update multi-traces data and render in proper layers order + // update multi-traces data and render in proper layers order function updateBatch(batch) { + if(!batch.length) return; + var lineBatch = [], scatterBatch = [], errorXBatch = [], @@ -130,13 +136,12 @@ function plot(container, plotinfo, cdata) { count = batch.length; // rendering requires proper batch sequence - var last = 0 for(i = 0; i < count; i++) { - if (fillBatch[i]) fill2d.draw(i) - if (errorXBatch[i]) errorX.draw(i) - if (errorYBatch[i]) errorY.draw(i) - if (lineBatch[i]) line2d.draw(i) - if (scatterBatch[i]) scatter2d.draw(i) + if(fillBatch[i]) fill2d.draw(i); + if(errorXBatch[i]) errorX.draw(i); + if(errorYBatch[i]) errorY.draw(i); + if(lineBatch[i]) line2d.draw(i); + if(scatterBatch[i]) scatter2d.draw(i); } } @@ -144,305 +149,348 @@ function plot(container, plotinfo, cdata) { function update(cdscatters) { var batch = []; - cdscatters.forEach(function(cdscatter) { + cdscatters.forEach(function(cdscatter, order) { if(!cdscatter) return; - - var lineOptions, scatterOptions, errorXOptions, errorYOptions, fillOptions; - var trace = cdscatter[0].trace; - var xaxis = Axes.getFromId(container, trace.xaxis || 'x'), - yaxis = Axes.getFromId(container, trace.yaxis || 'y'), - selection = trace.selection; - var vpSize = layout._size, - width = layout.width, - height = layout.height; - - // makeCalcdata runs d2c (data-to-coordinate) on every point - var x = xaxis.makeCalcdata(trace, 'x'); - var y = yaxis.makeCalcdata(trace, 'y'); - - // add item for autorange routine - // former expandAxesFancy - Axes.expand(xaxis, x, {padded: true, ppad: sizes}); - Axes.expand(yaxis, y, {padded: true, ppad: sizes}); - - // convert log axes - if (xaxis.type === 'log') x = x.map(xaxis.d2l) - if (yaxis.type === 'log') y = y.map(xaxis.d2l) - - var isVisible = false; - var hasLines = false; - var hasErrorX = false; - var hasErrorY = false; - var hasMarkers = false; - var hasFill = false; - - if(trace.visible !== true) { - isVisible = false; - hasLines = false; - hasErrorX = false; - hasErrorY = false; - hasMarkers = false; - hasFill = false; - } - else { - isVisible = true; - hasLines = subTypes.hasLines(trace) && x.length > 1 && trace.line; - hasErrorX = trace.error_x.visible === true; - hasErrorY = trace.error_y.visible === true; - hasMarkers = subTypes.hasMarkers(trace); - hasFill = trace.fill; - } - - // update viewport & range - 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 - ]; - - var range = [ - xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1] - ]; - - // get error values - var errorVals = ErrorBars.calcFromTrace(trace, layout); + batch[order] = getTraceOptions(trace); + }); - var len = x.length, - positions = [len * 2], - errorsX = new Float64Array(4 * len), - errorsY = new Float64Array(4 * len); + updateBatch(batch); + } - var i, xx, yy, ex0, ex1, ey0, ey1, ptrX = 0, ptrY = 0; + function getTraceOptions(trace) { + var lineOptions, scatterOptions, errorXOptions, errorYOptions, fillOptions; + var xaxis = Axes.getFromId(container, trace.xaxis || 'x'), + yaxis = Axes.getFromId(container, trace.yaxis || 'y'), + selection = trace.selection; + var vpSize = layout._size, + width = layout.width, + height = layout.height; + var sizes, selIds; + + // makeCalcdata runs d2c (data-to-coordinate) on every point + var x = xaxis.makeCalcdata(trace, 'x'); + var y = yaxis.makeCalcdata(trace, 'y'); + + // convert log axes + if(xaxis.type === 'log') x = x.map(xaxis.d2l); + if(yaxis.type === 'log') y = y.map(xaxis.d2l); + + var hasLines = false; + var hasErrorX = false; + var hasErrorY = false; + var hasMarkers = false; + var hasFill = false; + + if(trace.visible !== true) { + hasLines = false; + hasErrorX = false; + hasErrorY = false; + hasMarkers = false; + hasFill = false; + } + else { + hasLines = subTypes.hasLines(trace) && x.length > 1 && trace.line; + hasErrorX = trace.error_x.visible === true; + hasErrorY = trace.error_y.visible === true; + hasMarkers = subTypes.hasMarkers(trace); + hasFill = trace.fill; + } - for(i = 0; i < len; ++i) { - xx = parseFloat(x[i]); - yy = parseFloat(y[i]); + // update viewport & range + 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 + ]; + + var range = [ + xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1] + ]; + + // get error values + var errorVals = ErrorBars.calcFromTrace(trace, layout); + + var len = x.length, + positions = [len * 2], + errorsX = new Float64Array(4 * len), + errorsY = new Float64Array(4 * len), + linePositions; + + var i, xx, yy, ptrX = 0, ptrY = 0, errorOptions; + + for(i = 0; i < len; ++i) { + xx = parseFloat(x[i]); + yy = parseFloat(y[i]); + + positions[i * 2] = xx; + positions[i * 2 + 1] = yy; + + errorsX[ptrX++] = xx - errorVals[i].xs || 0; + errorsX[ptrX++] = errorVals[i].xh - xx || 0; + errorsX[ptrX++] = 0; + errorsX[ptrX++] = 0; + + errorsY[ptrY++] = 0; + errorsY[ptrY++] = 0; + errorsY[ptrY++] = yy - errorVals[i].ys || 0; + errorsY[ptrY++] = errorVals[i].yh - yy || 0; + } - positions[i * 2] = xx; - positions[i * 2 + 1] = yy; + if(hasErrorX) { + errorOptions = trace.error_x; + if(errorOptions.copy_ystyle) { + errorOptions = trace.error_y; + } + errorXOptions = {}; + errorXOptions.positions = positions; + errorXOptions.errors = errorsX; + errorXOptions.capSize = errorOptions.width * 2; + errorXOptions.lineWidth = errorOptions.thickness; + errorXOptions.color = errorOptions.color; + errorXOptions.viewport = viewport; + errorXOptions.range = range; + } - ex0 = errorsX[ptrX++] = xx - errorVals[i].xs || 0; - ex1 = errorsX[ptrX++] = errorVals[i].xh - xx || 0; - errorsX[ptrX++] = 0; - errorsX[ptrX++] = 0; + if(hasErrorY) { + errorOptions = trace.error_y; + errorYOptions = {}; + errorYOptions.positions = positions; + errorYOptions.errors = errorsY; + errorYOptions.capSize = errorOptions.width * 2; + errorYOptions.lineWidth = errorOptions.thickness; + errorYOptions.color = errorOptions.color; + errorYOptions.viewport = viewport; + errorYOptions.range = range; + } - errorsY[ptrY++] = 0; - errorsY[ptrY++] = 0; - ey0 = errorsY[ptrY++] = yy - errorVals[i].ys || 0; - ey1 = errorsY[ptrY++] = errorVals[i].yh - yy || 0; + if(hasLines) { + lineOptions = {}; + lineOptions.thickness = trace.line.width; + lineOptions.color = trace.line.color; + lineOptions.opacity = trace.opacity; + lineOptions.join = trace.opacity === 1.0 ? 'rect' : 'round'; + lineOptions.overlay = true; + + var lineWidth = lineOptions.thickness, + dashes = (DASHES[trace.line.dash] || [1]).slice(); + for(i = 0; i < dashes.length; ++i) dashes[i] *= lineWidth; + 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]); } - - if (hasErrorX) { - var errorOptions = trace.error_x; - if(errorOptions.copy_ystyle) { - errorOptions = trace.error_y; + 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]); + } } - errorXOptions = {}; - errorXOptions.positions = positions; - errorXOptions.errors = errorsX; - errorXOptions.capSize = errorOptions.width * 2; - errorXOptions.lineWidth = errorOptions.thickness; - errorXOptions.color = errorOptions.color; - errorXOptions.viewport = viewport; - errorXOptions.range = range; + linePositions.push(positions[positions.length - 2]); + linePositions.push(positions[positions.length - 1]); } - if (hasErrorY) { - var errorOptions = trace.error_y; - errorYOptions = {}; - errorYOptions.positions = positions; - errorYOptions.errors = errorsY; - errorYOptions.capSize = errorOptions.width * 2; - errorYOptions.lineWidth = errorOptions.thickness; - errorYOptions.color = errorOptions.color; - errorYOptions.viewport = viewport; - errorYOptions.range = range; + else { + linePositions = positions; } + lineOptions.positions = linePositions; + lineOptions.viewport = viewport; + lineOptions.range = range; + } - if(hasLines) { - lineOptions = {}; - lineOptions.positions = positions, - lineOptions.thickness = trace.line.width, - lineOptions.color = trace.line.color, - lineOptions.opacity = trace.opacity, - lineOptions.join = trace.opacity === 1.0 ? 'rect' : 'round'; - lineOptions.overlay = true; - - var lineWidth = lineOptions.thickness, - dashes = (DASHES[trace.line.dash] || [1]).slice(); - + if(hasFill) { + fillOptions = {}; + fillOptions.fill = trace.fillcolor; + fillOptions.thickness = 0; + fillOptions.viewport = viewport; + fillOptions.range = range; + fillOptions.closed = true; + + var pos = [], srcPos = linePositions || 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 { + var nextTrace = trace._nexttrace; + if(nextTrace && trace.fill === 'tonexty') { + pos = srcPos.slice(); - for(i = 0; i < dashes.length; ++i) dashes[i] *= lineWidth; + // FIXME: overcalculation here + var nextOptions = getTraceOptions(nextTrace); - lineOptions.dashes = dashes; - lineOptions.viewport = viewport; - lineOptions.range = range; - } + if(nextOptions && nextOptions.line) { + var nextPos = nextOptions.line.positions; - if (hasFill) { - fillOptions = {}; - fillOptions.fill = trace.fillcolor; - fillOptions.thickness = 0; - fillOptions.viewport = viewport; - fillOptions.range = range; - - var pos = [] - if (trace.fill === 'tozeroy') { - pos = [positions[0], 0] - pos = pos.concat(positions) - pos.push(positions[positions.length - 2]) - pos.push(0) - } - else if (trace.fill === 'tozerox') { - pos = [0, positions[1]] - pos = pos.concat(positions) - pos.push(0) - pos.push(positions[positions.length - 1]) - } - else { - var nextTrace = trace._nexttrace - if (nextTrace && trace.fill === 'tonexty') { - var pos = positions.slice() - - var nextPos = [], - nextX = nextTrace.x, - nextY = nextTrace.y, - xx, yy; - - for (var i = nextX.length; i--;) { - xx = parseFloat(nextX[i]), yy = parseFloat(nextY[i]); - if (isNaN(xx) || isNaN(yy)) continue - pos.push(xx) - pos.push(yy) + for(i = Math.floor(nextPos.length / 2); i--;) { + 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; } } - fillOptions.positions = pos } + fillOptions.positions = pos; + } - var sizes, selIds; - - if(selection && selection.length) { - selIds = {}; - for(i = 0; i < selection.length; i++) { - selIds[selection[i].pointNumber] = true; - } + if(selection && selection.length) { + selIds = {}; + for(i = 0; i < selection.length; i++) { + selIds[selection[i].pointNumber] = true; } + } - if(hasMarkers) { - scatterOptions = {}; - scatterOptions.positions = positions; - scatterOptions.sizes = new Array(len); - scatterOptions.markers = new Array(len); - scatterOptions.borderSizes = new Array(len); - scatterOptions.colors = new Array(len); - scatterOptions.borderColors = new Array(len); + if(hasMarkers) { + scatterOptions = {}; + scatterOptions.positions = positions; + scatterOptions.sizes = new Array(len); + scatterOptions.markers = new Array(len); + scatterOptions.borderSizes = new Array(len); + scatterOptions.colors = new Array(len); + scatterOptions.borderColors = new Array(len); - var markerSizeFunc = makeBubbleSizeFn(trace); + var markerSizeFunc = makeBubbleSizeFn(trace); - var markerOpts = trace.marker; - var markerOpacity = markerOpts.opacity; - var traceOpacity = trace.opacity; - var symbols = markerOpts.symbol; + var markerOpts = trace.marker; + var markerOpacity = markerOpts.opacity; + var traceOpacity = trace.opacity; + var symbols = markerOpts.symbol; - var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); - var borderSizes = convertNumber(markerOpts.line.width, len); - var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len); - var size, symbol, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, symbolFunc, symbolNoDot, symbolSdf, symbolNoFill, symbolPath, isDot; + var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); + var borderSizes = convertNumber(markerOpts.line.width, len); + var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len); + var size, symbol, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, symbolFunc, symbolNoDot, symbolSdf, symbolNoFill, symbolPath, isDot; - sizes = convertArray(markerSizeFunc, markerOpts.size, len); + sizes = convertArray(markerSizeFunc, markerOpts.size, len); - for(i = 0; i < len; ++i) { - symbol = Array.isArray(symbols) ? symbols[i] : symbols; - symbolNumber = Drawing.symbolNumber(symbol); - symbolFunc = Drawing.symbolFuncs[symbolNumber % 100]; - symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100]; - symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100]; + for(i = 0; i < len; ++i) { + symbol = Array.isArray(symbols) ? symbols[i] : symbols; + symbolNumber = Drawing.symbolNumber(symbol); + symbolFunc = Drawing.symbolFuncs[symbolNumber % 100]; + symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100]; + symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100]; + + isOpen = /-open/.test(symbol); + isDot = /-dot/.test(symbol); + isDimmed = selIds && !selIds[i]; + + _colors = colors; + + if(isOpen) { + _borderColors = colors; + } else { + _borderColors = borderColors; + } - isOpen = /-open/.test(symbol); - isDot = /-dot/.test(symbol); - isDimmed = selIds && !selIds[i]; + // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798 + // for more info on this logic + size = sizes[i]; + bw = borderSizes[i]; - _colors = colors; + scatterOptions.sizes[i] = size; - if(isOpen) { - _borderColors = colors; + if(symbol === 'circle') { + scatterOptions.markers[i] = null; + } + else { + // get symbol sdf from cache or generate it + if(SYMBOL_SDF[symbol]) { + symbolSdf = SYMBOL_SDF[symbol]; } else { - _borderColors = borderColors; - } - - // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798 - // for more info on this logic - size = sizes[i]; - bw = borderSizes[i]; - - scatterOptions.sizes[i] = size; - - if(symbol === 'circle') { - scatterOptions.markers[i] = null; - } - else { - // get symbol sdf from cache or generate it - if(SYMBOL_SDF[symbol]) { - symbolSdf = SYMBOL_SDF[symbol]; - } else { - 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; + if(isDot && !symbolNoDot) { + symbolPath = symbolFunc(SYMBOL_SIZE * 1.1) + SYMBOL_SVG_CIRCLE; + } + else { + symbolPath = symbolFunc(SYMBOL_SIZE); } - scatterOptions.markers[i] = symbolSdf || null; - } - scatterOptions.borderSizes[i] = 0.5 * bw; - - var optColors = scatterOptions.colors; - var dim = isDimmed ? DESELECTDIM : 1; - if(!optColors[i]) optColors[i] = []; - if(isOpen || symbolNoFill) { - optColors[i][0] = TRANSPARENT[0]; - optColors[i][1] = TRANSPARENT[1]; - optColors[i][2] = TRANSPARENT[2]; - optColors[i][3] = TRANSPARENT[3]; - } else { - optColors[i][0] = _colors[4 * i + 0] * 255; - optColors[i][1] = _colors[4 * i + 1] * 255; - optColors[i][2] = _colors[4 * i + 2] * 255; - optColors[i][3] = dim * _colors[4 * i + 3] * 255; + 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; } - if(!scatterOptions.borderColors[i]) scatterOptions.borderColors[i] = []; - scatterOptions.borderColors[i][0] = _borderColors[4 * i + 0] * 255; - scatterOptions.borderColors[i][1] = _borderColors[4 * i + 1] * 255; - scatterOptions.borderColors[i][2] = _borderColors[4 * i + 2] * 255; - scatterOptions.borderColors[i][3] = dim * _borderColors[4 * i + 3] * 255; - } - scatterOptions.viewport = viewport; - scatterOptions.range = range; + scatterOptions.markers[i] = symbolSdf || null; + } + scatterOptions.borderSizes[i] = 0.5 * bw; + + var optColors = scatterOptions.colors; + var dim = isDimmed ? DESELECTDIM : 1; + if(!optColors[i]) optColors[i] = []; + if(isOpen || symbolNoFill) { + optColors[i][0] = TRANSPARENT[0]; + optColors[i][1] = TRANSPARENT[1]; + optColors[i][2] = TRANSPARENT[2]; + optColors[i][3] = TRANSPARENT[3]; + } else { + optColors[i][0] = _colors[4 * i + 0] * 255; + optColors[i][1] = _colors[4 * i + 1] * 255; + optColors[i][2] = _colors[4 * i + 2] * 255; + optColors[i][3] = dim * _colors[4 * i + 3] * 255; + } + if(!scatterOptions.borderColors[i]) scatterOptions.borderColors[i] = []; + scatterOptions.borderColors[i][0] = _borderColors[4 * i + 0] * 255; + scatterOptions.borderColors[i][1] = _borderColors[4 * i + 1] * 255; + scatterOptions.borderColors[i][2] = _borderColors[4 * i + 2] * 255; + scatterOptions.borderColors[i][3] = dim * _borderColors[4 * i + 3] * 255; } - batch.push({ - scatter: scatterOptions, - line: lineOptions, - errorX: errorXOptions, - errorY: errorYOptions, - fill: fillOptions - }); - }); + scatterOptions.viewport = viewport; + scatterOptions.range = range; + } - updateBatch(batch); + // add item for autorange routine + // former expandAxesFancy + Axes.expand(xaxis, x, {padded: true, ppad: sizes}); + Axes.expand(yaxis, y, {padded: true, ppad: sizes}); + + + return { + scatter: scatterOptions, + line: lineOptions, + errorX: errorXOptions, + errorY: errorYOptions, + fill: fillOptions + }; } function clear() { @@ -496,14 +544,6 @@ function _convertArray(convert, data, count) { } -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); From f6480ad2fc930e3f6d11ce2e5abff7449ccdf322 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 17 Oct 2017 17:49:17 -0400 Subject: [PATCH 065/151] Introduce performance optimizations --- src/traces/scattergl/convert.js | 2 + src/traces/scatterregl/calc.js | 33 ------- src/traces/scatterregl/hover.js | 68 -------------- src/traces/scatterregl/index.js | 162 +++++++++++++++++++++++++++++++- src/traces/scatterregl/plot.js | 138 +++++++++++++++------------ 5 files changed, 236 insertions(+), 167 deletions(-) delete mode 100644 src/traces/scatterregl/calc.js delete mode 100644 src/traces/scatterregl/hover.js diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js index 892b573960b..72fbe489ece 100644 --- a/src/traces/scattergl/convert.js +++ b/src/traces/scattergl/convert.js @@ -270,6 +270,7 @@ function fillColor(colorIn, colorOut, offsetIn, offsetOut, isDimmed) { } proto.update = function(options, cdscatter) { + console.time('update') if(options.visible !== true) { this.isVisible = false; this.hasLines = false; @@ -325,6 +326,7 @@ proto.update = function(options, cdscatter) { if(cdscatter && cdscatter[0] && !cdscatter[0]._glTrace) { cdscatter[0]._glTrace = this; } + console.timeEnd('update') }; // We'd ideally know that all values are of fast types; sampling gives no certainty but faster diff --git a/src/traces/scatterregl/calc.js b/src/traces/scatterregl/calc.js deleted file mode 100644 index 3a1cfad7c22..00000000000 --- a/src/traces/scatterregl/calc.js +++ /dev/null @@ -1,33 +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 calcScatter = require('../scatter/calc'); -var kdtree = require('kdgrass'); - -module.exports = function calc(gd, trace) { - var cd = calcScatter(gd, trace); - - // TODO: delegate this to webworker if possible - - // FIXME: bench if it is faster than mapping to kdbush - var positions = Array(cd.length * 2); - for(var i = 0, j = 0; i < positions.length; i += 2, j++) { - positions[i] = cd[j].x; - positions[i + 1] = cd[j].y; - } - - var tree = kdtree(positions, 512); - - // FIXME: make sure it is a good place to store the tree - trace._tree = tree; - - return cd; -}; diff --git a/src/traces/scatterregl/hover.js b/src/traces/scatterregl/hover.js deleted file mode 100644 index 3e87f6b10d4..00000000000 --- a/src/traces/scatterregl/hover.js +++ /dev/null @@ -1,68 +0,0 @@ -'use strict'; - -var Lib = require('../../lib'); -var Fx = require('../../components/fx'); -var getTraceColor = require('../scatter/get_trace_color'); -var ErrorBars = require('../../components/errorbars'); -var MAXDIST = Fx.constants.MAXDIST; - -module.exports = hover; - -function hover(pointData, xval, yval) { - var cd = pointData.cd, - trace = cd[0].trace, - xa = pointData.xa, - ya = pointData.ya, - xpx = xa.c2p(xval), - ypx = ya.c2p(yval), - pt = [xpx, ypx], - // hoveron = trace.hoveron || '', - tree = trace._tree; - - if(!tree) return [pointData]; - - // FIXME: make sure this is a proper way to calc search radius - var ids = tree.within(xval, yval, MAXDIST / xa._m); - - // pick the id closest to the point - var min = MAXDIST, id = ids[0]; - for(var i = 0; i < ids.length; i++) { - pt = cd[ids[i]]; - var dx = pt.x - xval, dy = pt.y - yval; - var dist = Math.sqrt(dx * dx + dy * dy); - if(dist < min) { - min = dist; - id = ids[i]; - } - } - - pointData.index = id; - - if(pointData.index !== undefined) { - // the closest data point - var di = cd[pointData.index], - xc = xa.c2p(di.x, true), - yc = ya.c2p(di.y, true), - rad = di.mrc || 1; - - 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 - }); - - if(di.htx) pointData.text = di.htx; - else if(trace.hovertext) pointData.text = trace.hovertext; - else if(di.tx) pointData.text = di.tx; - else if(trace.text) pointData.text = trace.text; - ErrorBars.hoverInfo(di, trace, pointData); - } - - return [pointData]; -} diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index bb385c247d2..43c958c871d 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -9,13 +9,167 @@ 'use strict'; var extend = require('object-assign'); +var Axes = require('../../plots/cartesian/axes'); +var kdtree = require('kdgrass'); +var Lib = require('../../lib'); +var Fx = require('../../components/fx'); +var getTraceColor = require('../scatter/get_trace_color'); +var ErrorBars = require('../../components/errorbars'); +var MAXDIST = Fx.constants.MAXDIST; +var subtypes = require('../scatter/subtypes'); +var arraysToCalcdata = require('../scatter/arrays_to_calcdata'); +var calcColorscales = require('../scatter/colorscale_calc'); var Scatter = extend({}, require('../scatter/index')); +module.exports = Scatter; + Scatter.name = 'scatterregl'; -Scatter.plot = require('./plot'); -Scatter.calc = require('./calc'); -Scatter.hoverPoints = require('./hover'); Scatter.categories = ['gl', 'gl2d', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; -module.exports = Scatter; +Scatter.plot = require('./plot'); + +Scatter.calc = function calc(gd, trace) { + var positions + + var xa = Axes.getFromId(gd, trace.xaxis || 'x'); + var ya = Axes.getFromId(gd, trace.yaxis || 'y'); + + // makeCalcdata runs d2c (data-to-coordinate) on every point + var x = xa.makeCalcdata(trace, 'x'); + var y = ya.makeCalcdata(trace, 'y'); + + var serieslen = Math.max(x.length, y.length), + i; + + // create the "calculated data" to plot + positions = new Array(serieslen*2); + + for(i = 0; i < serieslen; i++) { + positions[i*2] = x[i] + positions[i*2 + 1] = y[i]; + } + + calcColorscales(trace); + + // TODO: delegate this to webworker if possible + // FIXME: make sure it is a good place to store the tree + trace._tree = kdtree(positions, 512); + + // stash data for performance + trace._x = x; + trace._y = y; + trace._positions = positions; + + Axes.expand(xa, x, 0); + Axes.expand(ya, y, 0); + + return [{x: false, y: false, trace: trace}]; +}; + +Scatter.hoverPoints = function hover(pointData, xval, yval) { + var cd = pointData.cd, + trace = cd[0].trace, + xa = pointData.xa, + ya = pointData.ya, + positions = trace + ._positions, + // hoveron = trace.hoveron || '', + tree = trace._tree; + + if(!tree) return [pointData]; + + // FIXME: make sure this is a proper way to calc search radius + var ids = tree.within(xval, yval, MAXDIST / xa._m); + + // pick the id closest to the point + var min = MAXDIST, id = ids[0], ptx, pty; + for(var i = 0; i < ids.length; i++) { + ptx = positions[ids[i] * 2]; + pty = positions[ids[i] * 2 + 1]; + var dx = ptx - xval, dy = pty - yval; + var dist = Math.sqrt(dx * dx + dy * dy); + if(dist < min) { + min = dist; + id = ids[i]; + } + } + + pointData.index = id; + + if(id !== undefined) { + //FIXME: make proper hover eval here + // the closest data point + // var di = cd[pointData.index], + // xc = xa.c2p(di.x, true), + // yc = ya.c2p(di.y, true), + // rad = di.mrc || 1; + + // 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 + // }); + + // if(di.htx) pointData.text = di.htx; + // else if(trace.hovertext) pointData.text = trace.hovertext; + // else if(di.tx) pointData.text = di.tx; + // else if(trace.text) pointData.text = trace.text; + // ErrorBars.hoverInfo(di, trace, pointData); + } + + return [pointData]; +}; + +Scatter.selectPoints = function select(searchInfo, polygon) { + var cd = searchInfo.cd, + xa = searchInfo.xaxis, + ya = searchInfo.yaxis, + selection = [], + trace = cd[0].trace, + i, + di, + x, + y; + + var scene = cd[0] && cd[0].trace && cd[0].trace._scene; + + if (!scene) return; + + var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace)); + if(trace.visible !== true || 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: di.x, + y: di.y + }); + di.dim = 0; + } + else di.dim = 1; + } + } + + trace.selection = selection; + scene([cd]); + + return selection; +}; + diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index a9b62d28626..d789b12e813 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -24,6 +24,7 @@ var createError = require('regl-error2d'); var Drawing = require('../../components/drawing'); var svgSdf = require('svg-path-sdf'); var createRegl = require('regl'); +var Scatter = require('./'); var DESELECTDIM = 0.2; var TRANSPARENT = [0, 0, 0, 0]; @@ -44,16 +45,24 @@ module.exports = plot; function plot(container, plotinfo, cdata) { var layout = container._fullLayout; var subplotObj = layout._plots[plotinfo.id]; + var scene = subplotObj._scene ? subplotObj._scene : updateScene; // that is needed for fills linkTraces(container, plotinfo, cdata); + // put references to traces for selectPoints + cdata.forEach(function (cd) { + if (!cd[0] || !cd[0].trace) return; + cd[0].trace._scene = scene; + }); + + // avoid creating scene if it exists already if(subplotObj._scene) { - subplotObj._scene(cdata); + scene(cdata); return; } else { - subplotObj._scene = update; + subplotObj._scene = scene; } var canvas = container.querySelector('.gl-canvas-focus'); @@ -146,11 +155,11 @@ function plot(container, plotinfo, cdata) { } // update based on calc data - function update(cdscatters) { + function updateScene(cdscatters) { var batch = []; cdscatters.forEach(function(cdscatter, order) { - if(!cdscatter) return; + if(!cdscatter || !cdscatter[0] || !cdscatter[0].trace) return; var trace = cdscatter[0].trace; batch[order] = getTraceOptions(trace); }); @@ -160,28 +169,47 @@ function plot(container, plotinfo, cdata) { function getTraceOptions(trace) { var lineOptions, scatterOptions, errorXOptions, errorYOptions, fillOptions; - var xaxis = Axes.getFromId(container, trace.xaxis || 'x'), - yaxis = Axes.getFromId(container, trace.yaxis || 'y'), - selection = trace.selection; + var xaxis = Axes.getFromId(container, trace.xaxis || 'x'); + var yaxis = Axes.getFromId(container, trace.yaxis || 'y'); + var selection = trace.selection; var vpSize = layout._size, width = layout.width, height = layout.height; var sizes, selIds; - - // makeCalcdata runs d2c (data-to-coordinate) on every point - var x = xaxis.makeCalcdata(trace, 'x'); - var y = yaxis.makeCalcdata(trace, 'y'); - - // convert log axes - if(xaxis.type === 'log') x = x.map(xaxis.d2l); - if(yaxis.type === 'log') y = y.map(xaxis.d2l); - + var i, l, xx, yy, ptrX = 0, ptrY = 0, errorOptions; var hasLines = false; var hasErrorX = false; var hasErrorY = false; var hasMarkers = false; var hasFill = false; + var x = trace._x; + var y = trace._y; + var count = Math.max(x.length, y.length); + var positions = trace._positions; + + // convert log axes + // if(xaxis.type === 'log') { + // for (i = 0, l = x.length; i < l; i++) { + // x[i] = xaxis.d2l(x[i]); + // } + // } + // else { + // for (i = 0, l = x.length; i < l; i++) { + // x[i] = parseFloat(x[i]); + // } + // } + // if(yaxis.type === 'log') { + // for (i = 0, l = y.length; i < l; i++) { + // y[i] = yaxis.d2l(y[i]); + // } + // } + // else { + // for (i = 0, l = y.length; i < l; i++) { + // y[i] = parseFloat(y[i]); + // } + // } + if(trace.visible !== true) { hasLines = false; hasErrorX = false; @@ -194,7 +222,7 @@ function plot(container, plotinfo, cdata) { hasErrorX = trace.error_x.visible === true; hasErrorY = trace.error_y.visible === true; hasMarkers = subTypes.hasMarkers(trace); - hasFill = trace.fill; + hasFill = trace.fill && trace.fill !== 'none'; } // update viewport & range @@ -210,35 +238,20 @@ function plot(container, plotinfo, cdata) { ]; // get error values - var errorVals = ErrorBars.calcFromTrace(trace, layout); + var errorVals = (hasErrorX || hasErrorY) ? ErrorBars.calcFromTrace(trace, layout) : null; - var len = x.length, - positions = [len * 2], - errorsX = new Float64Array(4 * len), - errorsY = new Float64Array(4 * len), + var errorsX = new Float64Array(4 * count), + errorsY = new Float64Array(4 * count), linePositions; - var i, xx, yy, ptrX = 0, ptrY = 0, errorOptions; - - for(i = 0; i < len; ++i) { - xx = parseFloat(x[i]); - yy = parseFloat(y[i]); - - positions[i * 2] = xx; - positions[i * 2 + 1] = yy; - - errorsX[ptrX++] = xx - errorVals[i].xs || 0; - errorsX[ptrX++] = errorVals[i].xh - xx || 0; - errorsX[ptrX++] = 0; - errorsX[ptrX++] = 0; - - errorsY[ptrY++] = 0; - errorsY[ptrY++] = 0; - errorsY[ptrY++] = yy - errorVals[i].ys || 0; - errorsY[ptrY++] = errorVals[i].yh - yy || 0; - } - if(hasErrorX) { + for(i = 0; i < count; ++i) { + errorsX[ptrX++] = x[i] - errorVals[i].xs || 0; + errorsX[ptrX++] = errorVals[i].xh - x[i] || 0; + errorsX[ptrX++] = 0; + errorsX[ptrX++] = 0; + } + errorOptions = trace.error_x; if(errorOptions.copy_ystyle) { errorOptions = trace.error_y; @@ -254,6 +267,13 @@ function plot(container, plotinfo, cdata) { } if(hasErrorY) { + for(i = 0; i < count; ++i) { + errorsY[ptrY++] = 0; + errorsY[ptrY++] = 0; + errorsY[ptrY++] = y[i] - errorVals[i].ys || 0; + errorsY[ptrY++] = errorVals[i].yh - y[i] || 0; + } + errorOptions = trace.error_y; errorYOptions = {}; errorYOptions.positions = positions; @@ -379,11 +399,11 @@ function plot(container, plotinfo, cdata) { if(hasMarkers) { scatterOptions = {}; scatterOptions.positions = positions; - scatterOptions.sizes = new Array(len); - scatterOptions.markers = new Array(len); - scatterOptions.borderSizes = new Array(len); - scatterOptions.colors = new Array(len); - scatterOptions.borderColors = new Array(len); + scatterOptions.sizes = new Array(count); + scatterOptions.markers = new Array(count); + scatterOptions.borderSizes = new Array(count); + scatterOptions.colors = new Array(count); + scatterOptions.borderColors = new Array(count); var markerSizeFunc = makeBubbleSizeFn(trace); @@ -392,14 +412,14 @@ function plot(container, plotinfo, cdata) { var traceOpacity = trace.opacity; var symbols = markerOpts.symbol; - var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, len); - var borderSizes = convertNumber(markerOpts.line.width, len); - var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, len); + var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, count); + var borderSizes = convertNumber(markerOpts.line.width, count); + var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, count); var size, symbol, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, symbolFunc, symbolNoDot, symbolSdf, symbolNoFill, symbolPath, isDot; - sizes = convertArray(markerSizeFunc, markerOpts.size, len); + sizes = convertArray(markerSizeFunc, markerOpts.size, count); - for(i = 0; i < len; ++i) { + for(i = 0; i < count; ++i) { symbol = Array.isArray(symbols) ? symbols[i] : symbols; symbolNumber = Drawing.symbolNumber(symbol); symbolFunc = Drawing.symbolFuncs[symbolNumber % 100]; @@ -478,11 +498,6 @@ function plot(container, plotinfo, cdata) { scatterOptions.range = range; } - // add item for autorange routine - // former expandAxesFancy - Axes.expand(xaxis, x, {padded: true, ppad: sizes}); - Axes.expand(yaxis, y, {padded: true, ppad: sizes}); - return { scatter: scatterOptions, @@ -511,12 +526,12 @@ function plot(container, plotinfo, cdata) { // regl.clear({color: [0, 0, 0, 0], depth: 1}); } - update.range = updateRange; - update.clear = clear; + updateScene.range = updateRange; + updateScene.clear = clear; - update(cdata); + updateScene(cdata); - return update; + return updateScene; } @@ -543,7 +558,6 @@ function _convertArray(convert, data, count) { return result; } - function convertColorScale(containerIn, markerOpacity, traceOpacity, count) { var colors = formatColor(containerIn, markerOpacity, count); From e7416cd9a6bab2b3166423713247a1adc57c04d8 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 17 Oct 2017 20:09:18 -0400 Subject: [PATCH 066/151] Fix lints, make basics work --- src/traces/scattergl/convert.js | 2 - src/traces/scatterregl/index.js | 55 +++++---- src/traces/scatterregl/plot.js | 194 +++++++++++++++----------------- 3 files changed, 120 insertions(+), 131 deletions(-) diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js index 72fbe489ece..892b573960b 100644 --- a/src/traces/scattergl/convert.js +++ b/src/traces/scattergl/convert.js @@ -270,7 +270,6 @@ function fillColor(colorIn, colorOut, offsetIn, offsetOut, isDimmed) { } proto.update = function(options, cdscatter) { - console.time('update') if(options.visible !== true) { this.isVisible = false; this.hasLines = false; @@ -326,7 +325,6 @@ proto.update = function(options, cdscatter) { if(cdscatter && cdscatter[0] && !cdscatter[0]._glTrace) { cdscatter[0]._glTrace = this; } - console.timeEnd('update') }; // We'd ideally know that all values are of fast types; sampling gives no certainty but faster diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 43c958c871d..96c9819d60f 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -11,13 +11,9 @@ var extend = require('object-assign'); var Axes = require('../../plots/cartesian/axes'); var kdtree = require('kdgrass'); -var Lib = require('../../lib'); var Fx = require('../../components/fx'); -var getTraceColor = require('../scatter/get_trace_color'); -var ErrorBars = require('../../components/errorbars'); var MAXDIST = Fx.constants.MAXDIST; var subtypes = require('../scatter/subtypes'); -var arraysToCalcdata = require('../scatter/arrays_to_calcdata'); var calcColorscales = require('../scatter/colorscale_calc'); var Scatter = extend({}, require('../scatter/index')); @@ -30,28 +26,43 @@ Scatter.categories = ['gl', 'gl2d', 'symbols', 'errorBarsOK', 'markerColorscale' Scatter.plot = require('./plot'); Scatter.calc = function calc(gd, trace) { - var positions + var positions; - var xa = Axes.getFromId(gd, trace.xaxis || 'x'); - var ya = Axes.getFromId(gd, trace.yaxis || 'y'); + var xaxis = Axes.getFromId(gd, trace.xaxis || 'x'); + var yaxis = Axes.getFromId(gd, trace.yaxis || 'y'); // makeCalcdata runs d2c (data-to-coordinate) on every point - var x = xa.makeCalcdata(trace, 'x'); - var y = ya.makeCalcdata(trace, 'y'); + var x = xaxis.type === 'linear' ? trace.x : xaxis.makeCalcdata(trace, 'x'); + var y = yaxis.type === 'linear' ? trace.y : yaxis.makeCalcdata(trace, 'y'); var serieslen = Math.max(x.length, y.length), - i; + i, l; + + // calculate axes range + // FIXME: probably we may want to have more complex ppad calculation + // FIXME: that is pretty slow thing here @etpinard your assistance required + Axes.expand(xaxis, x, 0); + Axes.expand(yaxis, y, 0); + + // convert log axes + if(xaxis.type === 'log') { + for(i = 0, l = x.length; i < l; i++) { + x[i] = xaxis.d2l(x[i]); + } + } + if(yaxis.type === 'log') { + for(i = 0, l = y.length; i < l; i++) { + y[i] = yaxis.d2l(y[i]); + } + } - // create the "calculated data" to plot - positions = new Array(serieslen*2); + positions = new Array(serieslen * 2); for(i = 0; i < serieslen; i++) { - positions[i*2] = x[i] - positions[i*2 + 1] = y[i]; + positions[i * 2] = parseFloat(x[i]); + positions[i * 2 + 1] = parseFloat(y[i]); } - calcColorscales(trace); - // TODO: delegate this to webworker if possible // FIXME: make sure it is a good place to store the tree trace._tree = kdtree(positions, 512); @@ -61,8 +72,7 @@ Scatter.calc = function calc(gd, trace) { trace._y = y; trace._positions = positions; - Axes.expand(xa, x, 0); - Axes.expand(ya, y, 0); + calcColorscales(trace); return [{x: false, y: false, trace: trace}]; }; @@ -71,9 +81,7 @@ Scatter.hoverPoints = function hover(pointData, xval, yval) { var cd = pointData.cd, trace = cd[0].trace, xa = pointData.xa, - ya = pointData.ya, - positions = trace - ._positions, + positions = trace._positions, // hoveron = trace.hoveron || '', tree = trace._tree; @@ -98,7 +106,7 @@ Scatter.hoverPoints = function hover(pointData, xval, yval) { pointData.index = id; if(id !== undefined) { - //FIXME: make proper hover eval here + // FIXME: make proper hover eval here // the closest data point // var di = cd[pointData.index], // xc = xa.c2p(di.x, true), @@ -140,7 +148,7 @@ Scatter.selectPoints = function select(searchInfo, polygon) { var scene = cd[0] && cd[0].trace && cd[0].trace._scene; - if (!scene) return; + if(!scene) return; var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace)); if(trace.visible !== true || hasOnlyLines) return; @@ -172,4 +180,3 @@ Scatter.selectPoints = function select(searchInfo, polygon) { return selection; }; - diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js index d789b12e813..f87e0ebb810 100644 --- a/src/traces/scatterregl/plot.js +++ b/src/traces/scatterregl/plot.js @@ -24,7 +24,6 @@ var createError = require('regl-error2d'); var Drawing = require('../../components/drawing'); var svgSdf = require('svg-path-sdf'); var createRegl = require('regl'); -var Scatter = require('./'); var DESELECTDIM = 0.2; var TRANSPARENT = [0, 0, 0, 0]; @@ -36,7 +35,7 @@ var SYMBOL_STROKE = SYMBOL_SIZE / 20; var SYMBOL_SDF = {}; var SYMBOL_SVG_CIRCLE = Drawing.symbolFuncs[0](SYMBOL_SIZE * 0.05); -var convertNumber, convertColorBase; +var convertNumber; module.exports = plot; @@ -51,8 +50,8 @@ function plot(container, plotinfo, cdata) { linkTraces(container, plotinfo, cdata); // put references to traces for selectPoints - cdata.forEach(function (cd) { - if (!cd[0] || !cd[0].trace) return; + cdata.forEach(function(cd) { + if(!cd[0] || !cd[0].trace) return; cd[0].trace._scene = scene; }); @@ -176,7 +175,7 @@ function plot(container, plotinfo, cdata) { width = layout.width, height = layout.height; var sizes, selIds; - var i, l, xx, yy, ptrX = 0, ptrY = 0, errorOptions; + var i, xx, yy, ptrX = 0, ptrY = 0, errorOptions; var hasLines = false; var hasErrorX = false; var hasErrorY = false; @@ -188,28 +187,6 @@ function plot(container, plotinfo, cdata) { var count = Math.max(x.length, y.length); var positions = trace._positions; - // convert log axes - // if(xaxis.type === 'log') { - // for (i = 0, l = x.length; i < l; i++) { - // x[i] = xaxis.d2l(x[i]); - // } - // } - // else { - // for (i = 0, l = x.length; i < l; i++) { - // x[i] = parseFloat(x[i]); - // } - // } - // if(yaxis.type === 'log') { - // for (i = 0, l = y.length; i < l; i++) { - // y[i] = yaxis.d2l(y[i]); - // } - // } - // else { - // for (i = 0, l = y.length; i < l; i++) { - // y[i] = parseFloat(y[i]); - // } - // } - if(trace.visible !== true) { hasLines = false; hasErrorX = false; @@ -399,106 +376,114 @@ function plot(container, plotinfo, cdata) { if(hasMarkers) { scatterOptions = {}; scatterOptions.positions = positions; - scatterOptions.sizes = new Array(count); - scatterOptions.markers = new Array(count); - scatterOptions.borderSizes = new Array(count); - scatterOptions.colors = new Array(count); - scatterOptions.borderColors = new Array(count); var markerSizeFunc = makeBubbleSizeFn(trace); - var markerOpts = trace.marker; var markerOpacity = markerOpts.opacity; var traceOpacity = trace.opacity; var symbols = markerOpts.symbol; - var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, count); - var borderSizes = convertNumber(markerOpts.line.width, count); - var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, count); - var size, symbol, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, symbolFunc, symbolNoDot, symbolSdf, symbolNoFill, symbolPath, isDot; + if(Array.isArray(markerOpts.color) || Array.isArray(markerOpts.size)) { + scatterOptions.sizes = new Array(count); + scatterOptions.markers = new Array(count); + scatterOptions.borderSizes = new Array(count); + scatterOptions.colors = new Array(count); + scatterOptions.borderColors = new Array(count); - sizes = convertArray(markerSizeFunc, markerOpts.size, count); + var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, count); + var borderSizes = convertNumber(markerOpts.line.width, count); + var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, count); + var size, symbol, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, symbolFunc, symbolNoDot, symbolSdf, symbolNoFill, symbolPath, isDot; - for(i = 0; i < count; ++i) { - symbol = Array.isArray(symbols) ? symbols[i] : symbols; - symbolNumber = Drawing.symbolNumber(symbol); - symbolFunc = Drawing.symbolFuncs[symbolNumber % 100]; - symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100]; - symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100]; - - isOpen = /-open/.test(symbol); - isDot = /-dot/.test(symbol); - isDimmed = selIds && !selIds[i]; - - _colors = colors; - - if(isOpen) { - _borderColors = colors; - } else { - _borderColors = borderColors; - } + sizes = convertArray(markerSizeFunc, markerOpts.size, count); - // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798 - // for more info on this logic - size = sizes[i]; - bw = borderSizes[i]; + for(i = 0; i < count; ++i) { + symbol = Array.isArray(symbols) ? symbols[i] : symbols; + symbolNumber = Drawing.symbolNumber(symbol); + symbolFunc = Drawing.symbolFuncs[symbolNumber % 100]; + symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100]; + symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100]; - scatterOptions.sizes[i] = size; + isOpen = /-open/.test(symbol); + isDot = /-dot/.test(symbol); + isDimmed = selIds && !selIds[i]; - if(symbol === 'circle') { - scatterOptions.markers[i] = null; - } - else { - // get symbol sdf from cache or generate it - if(SYMBOL_SDF[symbol]) { - symbolSdf = SYMBOL_SDF[symbol]; + _colors = colors; + + if(isOpen) { + _borderColors = colors; } else { - if(isDot && !symbolNoDot) { - symbolPath = symbolFunc(SYMBOL_SIZE * 1.1) + SYMBOL_SVG_CIRCLE; - } - else { - symbolPath = symbolFunc(SYMBOL_SIZE); - } + _borderColors = borderColors; + } + + // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798 + // for more info on this logic + size = sizes[i]; + bw = borderSizes[i]; - 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; + scatterOptions.sizes[i] = size; + + if(symbol === 'circle') { + scatterOptions.markers[i] = null; } + else { + // get symbol sdf from cache or generate it + if(SYMBOL_SDF[symbol]) { + symbolSdf = SYMBOL_SDF[symbol]; + } else { + 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; + } - scatterOptions.markers[i] = symbolSdf || null; - } - scatterOptions.borderSizes[i] = 0.5 * bw; - - var optColors = scatterOptions.colors; - var dim = isDimmed ? DESELECTDIM : 1; - if(!optColors[i]) optColors[i] = []; - if(isOpen || symbolNoFill) { - optColors[i][0] = TRANSPARENT[0]; - optColors[i][1] = TRANSPARENT[1]; - optColors[i][2] = TRANSPARENT[2]; - optColors[i][3] = TRANSPARENT[3]; - } else { - optColors[i][0] = _colors[4 * i + 0] * 255; - optColors[i][1] = _colors[4 * i + 1] * 255; - optColors[i][2] = _colors[4 * i + 2] * 255; - optColors[i][3] = dim * _colors[4 * i + 3] * 255; + scatterOptions.markers[i] = symbolSdf || null; + } + scatterOptions.borderSizes[i] = 0.5 * bw; + + var optColors = scatterOptions.colors; + var dim = isDimmed ? DESELECTDIM : 1; + if(!optColors[i]) optColors[i] = []; + if(isOpen || symbolNoFill) { + optColors[i][0] = TRANSPARENT[0]; + optColors[i][1] = TRANSPARENT[1]; + optColors[i][2] = TRANSPARENT[2]; + optColors[i][3] = TRANSPARENT[3]; + } else { + optColors[i][0] = _colors[4 * i + 0] * 255; + optColors[i][1] = _colors[4 * i + 1] * 255; + optColors[i][2] = _colors[4 * i + 2] * 255; + optColors[i][3] = dim * _colors[4 * i + 3] * 255; + } + if(!scatterOptions.borderColors[i]) scatterOptions.borderColors[i] = []; + scatterOptions.borderColors[i][0] = _borderColors[4 * i + 0] * 255; + scatterOptions.borderColors[i][1] = _borderColors[4 * i + 1] * 255; + scatterOptions.borderColors[i][2] = _borderColors[4 * i + 2] * 255; + scatterOptions.borderColors[i][3] = dim * _borderColors[4 * i + 3] * 255; } - if(!scatterOptions.borderColors[i]) scatterOptions.borderColors[i] = []; - scatterOptions.borderColors[i][0] = _borderColors[4 * i + 0] * 255; - scatterOptions.borderColors[i][1] = _borderColors[4 * i + 1] * 255; - scatterOptions.borderColors[i][2] = _borderColors[4 * i + 2] * 255; - scatterOptions.borderColors[i][3] = dim * _borderColors[4 * i + 3] * 255; + } + // shortcut update for single color + else { + scatterOptions.color = markerOpts.color; + scatterOptions.opacity = traceOpacity * markerOpacity; + scatterOptions.size = markerSizeFunc(markerOpts.size); + scatterOptions.borderSizes = 0; } scatterOptions.viewport = viewport; scatterOptions.range = range; } - return { scatter: scatterOptions, line: lineOptions, @@ -536,7 +521,6 @@ function plot(container, plotinfo, cdata) { convertNumber = convertArray.bind(null, function(x) { return +x; }); -convertColorBase = convertArray.bind(null, str2RGBArray); // handle the situation where values can be array-like or not array like function convertArray(convert, data, count) { From 8ae1d940450408fd6f14fdfb28e008832d78887e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 23 Oct 2017 13:07:00 -0400 Subject: [PATCH 067/151] Remove gl2d-related API --- src/plots/gl3d/index.js | 2 +- src/plots/plots.js | 40 ---------------------------------------- 2 files changed, 1 insertion(+), 41 deletions(-) diff --git a/src/plots/gl3d/index.js b/src/plots/gl3d/index.js index c30a7228dee..1e93bf9475c 100644 --- a/src/plots/gl3d/index.js +++ b/src/plots/gl3d/index.js @@ -37,7 +37,7 @@ exports.plot = function plotGl3d(gd) { for(var i = 0; i < sceneIds.length; i++) { var sceneId = sceneIds[i], - fullSceneData = Plots.getSubplotData(fullData, 'gl3d', sceneId), + fullSceneData = Plots.getSubplotCalcData(fullData, 'gl3d', sceneId), sceneLayout = fullLayout[sceneId], scene = sceneLayout._scene; diff --git a/src/plots/plots.js b/src/plots/plots.js index 46430fe737f..44f200e89e0 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -124,42 +124,6 @@ plots.getSubplotIds = function getSubplotIds(layout, type) { return subplotIds; }; -/** - * Get the data trace(s) associated with a given subplot. - * - * @param {array} data plotly full data array. - * @param {string} type subplot type to look for. - * @param {string} subplotId subplot id to look for. - * - * @return {array} list of trace objects. - * - */ -plots.getSubplotData = function getSubplotData(data, type, subplotId) { - if(!plots.subplotsRegistry[type]) return []; - - var attr = plots.subplotsRegistry[type].attr, - subplotData = [], - trace; - - for(var i = 0; i < data.length; i++) { - trace = data[i]; - - if(type === 'gl2d' && plots.traceIs(trace, 'gl2d')) { - var spmatch = Plotly.Axes.subplotMatch, - subplotX = 'x' + subplotId.match(spmatch)[1], - subplotY = 'y' + subplotId.match(spmatch)[2]; - - if(trace[attr[0]] === subplotX && trace[attr[1]] === subplotY) { - subplotData.push(trace); - } - } - else { - if(trace[attr] === subplotId) subplotData.push(trace); - } - } - - return subplotData; -}; /** * Get calcdata traces(s) associated with a given subplot @@ -666,10 +630,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(); From fce4cffd8f0a61c5bb14fc475798efc2353ec63d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 23 Oct 2017 16:08:30 -0400 Subject: [PATCH 068/151] Revert refactoring changes --- src/plots/gl3d/index.js | 2 +- src/plots/plots.js | 39 +- src/traces/parcoords/base_plot.js | 1 - src/traces/parcoords/index.js | 2 +- src/traces/parcoords/lines.js | 15 +- src/traces/parcoords/parcoords.js | 2 - src/traces/parcoords/plot.js | 1 - src/traces/scatterregl/index.js | 535 +++++++++++++++++++++++++--- src/traces/scatterregl/plot.js | 569 ------------------------------ 9 files changed, 531 insertions(+), 635 deletions(-) delete mode 100644 src/traces/scatterregl/plot.js diff --git a/src/plots/gl3d/index.js b/src/plots/gl3d/index.js index 1e93bf9475c..c30a7228dee 100644 --- a/src/plots/gl3d/index.js +++ b/src/plots/gl3d/index.js @@ -37,7 +37,7 @@ exports.plot = function plotGl3d(gd) { for(var i = 0; i < sceneIds.length; i++) { var sceneId = sceneIds[i], - fullSceneData = Plots.getSubplotCalcData(fullData, 'gl3d', sceneId), + fullSceneData = Plots.getSubplotData(fullData, 'gl3d', sceneId), sceneLayout = fullLayout[sceneId], scene = sceneLayout._scene; diff --git a/src/plots/plots.js b/src/plots/plots.js index 44f200e89e0..424face3f53 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -124,6 +124,43 @@ plots.getSubplotIds = function getSubplotIds(layout, type) { return subplotIds; }; +/** + * Get the data trace(s) associated with a given subplot. + * + * @param {array} data plotly full data array. + * @param {string} type subplot type to look for. + * @param {string} subplotId subplot id to look for. + * + * @return {array} list of trace objects. + * + */ +plots.getSubplotData = function getSubplotData(data, type, subplotId) { + if(!plots.subplotsRegistry[type]) return []; + + var attr = plots.subplotsRegistry[type].attr, + subplotData = [], + trace; + + for(var i = 0; i < data.length; i++) { + trace = data[i]; + + if(type === 'gl2d' && plots.traceIs(trace, 'gl2d')) { + var spmatch = Plotly.Axes.subplotMatch, + subplotX = 'x' + subplotId.match(spmatch)[1], + subplotY = 'y' + subplotId.match(spmatch)[2]; + + if(trace[attr[0]] === subplotX && trace[attr[1]] === subplotY) { + subplotData.push(trace); + } + } + else { + if(trace[attr] === subplotId) subplotData.push(trace); + } + } + + return subplotData; +}; + /** * Get calcdata traces(s) associated with a given subplot @@ -944,7 +981,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; diff --git a/src/traces/parcoords/base_plot.js b/src/traces/parcoords/base_plot.js index d34583c8be7..f8f8b2bb1a7 100644 --- a/src/traces/parcoords/base_plot.js +++ b/src/traces/parcoords/base_plot.js @@ -34,7 +34,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;}) diff --git a/src/traces/parcoords/index.js b/src/traces/parcoords/index.js index 920cec76473..fb71f73692d 100644 --- a/src/traces/parcoords/index.js +++ b/src/traces/parcoords/index.js @@ -19,7 +19,7 @@ Parcoords.colorbar = require('./colorbar'); Parcoords.moduleType = 'trace'; Parcoords.name = 'parcoords'; Parcoords.basePlotModule = require('./base_plot'); -Parcoords.categories = ['gl', 'noOpacity']; +Parcoords.categories = ['gl', 'regl', 'noOpacity']; Parcoords.meta = { description: [ 'Parallel coordinates for multidimensional exploratory data analysis.', diff --git a/src/traces/parcoords/lines.js b/src/traces/parcoords/lines.js index 98495e53ad8..4aef621fb49 100644 --- a/src/traces/parcoords/lines.js +++ b/src/traces/parcoords/lines.js @@ -8,7 +8,6 @@ 'use strict'; -var createREGL = require('regl'); var glslify = require('glslify'); var c = require('./constants'); var vertexShaderSource = glslify('./shaders/vertex.glsl'); @@ -202,13 +201,7 @@ module.exports = function(canvasGL, d, scatter) { var points = makePoints(sampleCount, dimensionCount, initialDims, color); var attributes = makeAttributes(sampleCount, points); - var regl = createREGL({ - canvas: canvasGL, - attributes: { - preserveDrawingBuffer: true, - antialias: !pick - } - }); + var regl = d.regl var paletteTexture = regl.texture({ shape: [256, 1], @@ -439,11 +432,15 @@ module.exports = function(canvasGL, d, scatter) { return pixelArray; } + function destroy() { + paletteTexture.destroy() + } + return { setColorDomain: setColorDomain, render: renderGLParcoords, readPixel: readPixel, readPixels: readPixels, - destroy: regl.destroy + destroy: destroy }; }; diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js index 33119b9fdb0..34204cf45a4 100644 --- a/src/traces/parcoords/parcoords.js +++ b/src/traces/parcoords/parcoords.js @@ -298,8 +298,6 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca var parcoordsLineLayer = parcoordsLineLayers.selectAll('.gl-canvas') .each(function(d) { var key = d.key; - d.context = key === 'contextLayer'; - d.pick = key === 'pickLayer'; // FIXME: figure out how to handle multiple instances d.viewModel = vm[0]; diff --git a/src/traces/parcoords/plot.js b/src/traces/parcoords/plot.js index 8abee427202..7a3a8f5677a 100644 --- a/src/traces/parcoords/plot.js +++ b/src/traces/parcoords/plot.js @@ -11,7 +11,6 @@ var parcoords = require('./parcoords'); module.exports = function plot(gd, cdparcoords) { - var fullLayout = gd._fullLayout; var svg = fullLayout._toppaper; var root = fullLayout._paperdiv; diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 96c9819d60f..3ad46190278 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -8,35 +8,71 @@ 'use strict'; +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 MAXDIST = Fx.constants.MAXDIST; -var subtypes = require('../scatter/subtypes'); +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 str2RGBArray = require('../../lib/str2rgbarray'); +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 Plots = require('../../plots/plots'); + +var MAXDIST = Fx.constants.MAXDIST; +var DESELECTDIM = 0.2; +var TRANSPARENT = [0, 0, 0, 0]; +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 Scatter = extend({}, require('../scatter/index')); -module.exports = Scatter; +var ScatterRegl = module.exports = extend({}, require('../scatter')); -Scatter.name = 'scatterregl'; -Scatter.categories = ['gl', 'gl2d', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; -Scatter.plot = require('./plot'); +ScatterRegl.name = 'scatterregl'; +ScatterRegl.categories = ['gl', 'gl2d', 'regl', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; -Scatter.calc = function calc(gd, trace) { +ScatterRegl.basePlotModule = require('./base'); + + +ScatterRegl.calc = function calc(container, trace) { + var layout = container._fullLayout; var positions; + var stash = {}; + var xaxis = Axes.getFromId(container, trace.xaxis || 'x'); + var yaxis = Axes.getFromId(container, trace.yaxis || 'y'); - var xaxis = Axes.getFromId(gd, trace.xaxis || 'x'); - var yaxis = Axes.getFromId(gd, trace.yaxis || 'y'); + //FIXME: find a better 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 serieslen = Math.max(x.length, y.length), - i, l; + var count = Math.max(x.length, y.length), i, l, xx, yy, ptrX = 0, ptrY = 0; + var lineOptions = stash.line = {}, + scatterOptions = stash.scatter = {}, + errorOptions = {}, + errorXOptions = stash.errorX = {}, + errorYOptions = stash.errorY = {}, + fillOptions = stash.fill = {}; + var selection = trace.selection; + var sizes, selIds; + var hasLines, hasErrorX, hasErrorY, hasMarkers, hasFill; + var linePositions; // calculate axes range // FIXME: probably we may want to have more complex ppad calculation @@ -56,32 +92,313 @@ Scatter.calc = function calc(gd, trace) { } } - positions = new Array(serieslen * 2); - - for(i = 0; i < serieslen; i++) { - positions[i * 2] = parseFloat(x[i]); - positions[i * 2 + 1] = parseFloat(y[i]); + // we need hi-precision for scatter2d + positions = new Array(count * 2); + for(i = 0; i < count; i++) { + positions[i * 2] = +x[i]; + positions[i * 2 + 1] = +y[i]; } - // TODO: delegate this to webworker if possible + calcColorscales(trace); + + // TODO: delegate this to webworker if possible (potential ) // FIXME: make sure it is a good place to store the tree - trace._tree = kdtree(positions, 512); + stash._tree = kdtree(positions, 512); - // stash data for performance - trace._x = x; - trace._y = y; - trace._positions = positions; + // stash data + stash._x = x; + stash._y = y; + stash._positions = positions; - calcColorscales(trace); + // get error values + var errorVals = (hasErrorX || hasErrorY) ? ErrorBars.calcFromTrace(trace, layout) : null; + + if(hasErrorX) { + var errorsX = new Float64Array(4 * count); + + for(i = 0; i < count; ++i) { + errorsX[ptrX++] = x[i] - errorVals[i].xs || 0; + errorsX[ptrX++] = errorVals[i].xh - x[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) { + var errorsY = new Float64Array(4 * count); + + for(i = 0; i < count; ++i) { + errorsY[ptrY++] = 0; + errorsY[ptrY++] = 0; + errorsY[ptrY++] = y[i] - errorVals[i].ys || 0; + errorsY[ptrY++] = errorVals[i].yh - y[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.thickness = trace.line.width; + lineOptions.color = trace.line.color; + lineOptions.opacity = trace.opacity; + lineOptions.join = trace.opacity === 1.0 ? 'rect' : 'round'; + 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; + } + lineOptions.positions = linePositions; + } + + if(hasFill) { + fillOptions.fill = trace.fillcolor; + fillOptions.thickness = 0; + fillOptions.closed = true; + + var pos = [], srcPos = linePositions || 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 { + var nextTrace = trace._nexttrace; + if(nextTrace && trace.fill === 'tonexty') { + pos = srcPos.slice(); + + // FIXME: overcalculation here + var nextOptions = getTraceOptions(nextTrace); + + if(nextOptions && nextOptions.line) { + var nextPos = nextOptions.line.positions; + + for(i = Math.floor(nextPos.length / 2); i--;) { + 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; + } + } + } + fillOptions.positions = pos; + } + + if(hasMarkers) { + scatterOptions.positions = positions; + + var markerSizeFunc = makeBubbleSizeFn(trace); + var markerOpts = trace.marker; + + //prepare colors + if (Array.isArray(markerOpts.color) || Array.isArray(markerOpts.line.color)) { + scatterOptions.colors = new Array(count); + scatterOptions.borderColors = new Array(count); + + var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, count); + var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, count); + var _colors, _borderColors, bw; + + for(i = 0; i < count; ++i) { + _colors = colors; + + if(isOpen) { + _borderColors = colors; + } else { + _borderColors = borderColors; + } + + var optColors = scatterOptions.colors; + if(!optColors[i]) optColors[i] = []; + if(isOpen || symbolNoFill) { + optColors[i][0] = TRANSPARENT[0]; + optColors[i][1] = TRANSPARENT[1]; + optColors[i][2] = TRANSPARENT[2]; + optColors[i][3] = TRANSPARENT[3]; + } else { + optColors[i][0] = _colors[4 * i + 0] * 255; + optColors[i][1] = _colors[4 * i + 1] * 255; + optColors[i][2] = _colors[4 * i + 2] * 255; + optColors[i][3] = _colors[4 * i + 3] * 255; + } + if(!scatterOptions.borderColors[i]) scatterOptions.borderColors[i] = []; + scatterOptions.borderColors[i][0] = _borderColors[4 * i + 0] * 255; + scatterOptions.borderColors[i][1] = _borderColors[4 * i + 1] * 255; + scatterOptions.borderColors[i][2] = _borderColors[4 * i + 2] * 255; + scatterOptions.borderColors[i][3] = dim * _borderColors[4 * i + 3] * 255; + } + + scatterOptions.opacity = trace.opacity; + } + else { + scatterOptions.color = markerOpts.color; + scatterOptions.borderColor = markerOpts.line; + scatterOptions.opacity = trace.opacity * markerOpts.opacity; + } + + //prepare markers + if (Array.isArray(markerOpts.symbol)) { + scatterOptions.markers = new Array(count); + for(i = 0; i < count; ++i) { + scatterOptions.markers[i] = getSymbolSdf(markerOpts.symbol[i]) + } + } + else { + scatterOptions.marker = getSymbolSdf(markerOpts.symbol) + } + + //prepare sizes + if(Array.isArray(markerOpts.size) || Array.isArray(markerOpts.line.width)) { + scatterOptions.sizes = new Array(count); + scatterOptions.borderSizes = new Array(count); + + var borderSizes = convertNumber(markerOpts.line.width, count); + var sizes = convertArray(markerSizeFunc, markerOpts.size, count); - return [{x: false, y: false, trace: trace}]; + for(i = 0; i < count; ++i) { + // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798 + scatterOptions.sizes[i] = sizes[i]; + scatterOptions.borderSizes[i] = 0.5 * borderSizes[i]; + } + } + else { + scatterOptions.size = markerSizeFunc(markerOpts.size); + scatterOptions.borderSizes = markerOpts.line.width * .5; + } + } + + return [{x: false, y: false, t: stash, trace: trace}]; +}; + + +//TODO: manages selection, range, viewport, that's it +ScatterRegl.plot = function plot(container, plotinfo, cdata) { + var layout = container._fullLayout; + var subplot = layout._plots[plotinfo.id]; + var scene = subplot._scene; + var vpSize = layout._size, width = layout.width, height = layout.height; + + // that is needed for fills + linkTraces(container, plotinfo, cdata); + + var count = 0, viewport; + + var batch = cdata.map(function(cdscatter, order) { + if(!cdscatter || !cdscatter[0] || !cdscatter[0].trace) return; + var cd = cdscatter[0]; + var trace = cd.trace; + var stash = cd.t; + var xaxis = Axes.getFromId(container, trace.xaxis || 'x'); + var yaxis = Axes.getFromId(container, trace.yaxis || 'y'); + + var range = [ + xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1] + ]; + + // update viewport & range + 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 + ]; + + stash.scatter.viewport = stash.line.viewport = stash.errorX.viewport = stash.errorY.viewport = stash.fill.viewport = viewport; + stash.scatter.range = stash.line.range = stash.errorX.range = stash.errorY.range = stash.fill.range = range; + + // TODO: update selection here + if(trace.selection && trace.selection.length) { + selIds = {}; + for(i = 0; i < trace.selection.length; i++) { + selIds[trace.selection[i].pointNumber] = true; + } + } + // TODO: recalculate fill area here since we can't calc connected traces beforehead + + return { + scatter: stash.scatter, + line: stash.line, + errorX: stash.errorX, + errorY: stash.errorY, + fill: stash.fill + }; + }); + + scene.update(batch); }; -Scatter.hoverPoints = function hover(pointData, xval, yval) { +ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { var cd = pointData.cd, trace = cd[0].trace, xa = pointData.xa, + ya = pointData.ya, positions = trace._positions, + x = trace._x, + y = trace._y, // hoveron = trace.hoveron || '', tree = trace._tree; @@ -105,37 +422,74 @@ Scatter.hoverPoints = function hover(pointData, xval, yval) { pointData.index = id; - if(id !== undefined) { - // FIXME: make proper hover eval here - // the closest data point - // var di = cd[pointData.index], - // xc = xa.c2p(di.x, true), - // yc = ya.c2p(di.y, true), - // rad = di.mrc || 1; + if (id === undefined) return [pointData] + + // the closest data point + var di = { + x: x[id], + y: y[id] + }; + + // that is single-item arrays_to_calcdata excerpt, bc we don't have to do it beforehead for 1e6 points + mergeProp(trace.text, 'tx'); + mergeProp(trace.hovertext, 'htx'); + mergeProp(trace.customdata, 'data'); + mergeProp(trace.textposition, 'tp'); + if(trace.textfont) { + mergeProp(trace.textfont.size, 'ts'); + mergeProp(trace.textfont.color, 'tc'); + mergeProp(trace.textfont.family, 'tf'); + } - // Lib.extendFlat(pointData, { - // color: getTraceColor(trace, di), + var marker = trace.marker; + if(marker) { + mergeProp(marker.size, 'ms'); + mergeProp(marker.opacity, 'mo'); + mergeProp(marker.symbol, 'mx'); + mergeProp(marker.color, 'mc'); + + var markerLine = marker.line; + if(marker.line) { + mergeProp(markerLine.color, 'mlc'); + mergeProp(markerLine.width, 'mlw'); + } + var markerGradient = marker.gradient; + if(markerGradient && markerGradient.type !== 'none') { + mergeProp(markerGradient.type, 'mgt'); + mergeProp(markerGradient.color, 'mgc'); + } + } + + function mergeProp(list, short) { + if (Array.isArray(list)) di[short] = list[id] + } - // x0: xc - rad, - // x1: xc + rad, - // xLabelVal: di.x, + var xc = xa.c2p(di.x, true), + yc = ya.c2p(di.y, true), + rad = di.mrc || 1; - // y0: yc - rad, - // y1: yc + rad, - // yLabelVal: di.y - // }); + Lib.extendFlat(pointData, { + color: getTraceColor(trace, di), - // if(di.htx) pointData.text = di.htx; - // else if(trace.hovertext) pointData.text = trace.hovertext; - // else if(di.tx) pointData.text = di.tx; - // else if(trace.text) pointData.text = trace.text; - // ErrorBars.hoverInfo(di, trace, pointData); - } + x0: xc - rad, + x1: xc + rad, + xLabelVal: di.x, + + y0: yc - rad, + y1: yc + rad, + yLabelVal: di.y + }); + + if(di.htx) pointData.text = di.htx; + else if(trace.hovertext) pointData.text = trace.hovertext; + else if(di.tx) pointData.text = di.tx; + else if(trace.text) pointData.text = trace.text; + ErrorBars.hoverInfo(di, trace, pointData); return [pointData]; }; -Scatter.selectPoints = function select(searchInfo, polygon) { +ScatterRegl.selectPoints = function select(searchInfo, polygon) { var cd = searchInfo.cd, xa = searchInfo.xaxis, ya = searchInfo.yaxis, @@ -150,7 +504,7 @@ Scatter.selectPoints = function select(searchInfo, polygon) { if(!scene) return; - var hasOnlyLines = (!subtypes.hasMarkers(trace) && !subtypes.hasText(trace)); + var hasOnlyLines = (!subTypes.hasMarkers(trace) && !subTypes.hasText(trace)); if(trace.visible !== true || hasOnlyLines) return; // filter out points by visible scatter ones @@ -180,3 +534,84 @@ Scatter.selectPoints = function select(searchInfo, polygon) { return selection; }; + + +function getSymbolSdf(symbol) { + if(symbol === 'circle') return null + + var symbolNumber = Drawing.symbolNumber(symbol); + var symbolFunc = Drawing.symbolFuncs[symbolNumber % 100]; + var symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100]; + var symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100]; + + var isOpen = /-open/.test(symbol); + var isDot = /-dot/.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; +} + +var convertNumber = convertArray.bind(null, function(x) { return +x; }); + +// 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; +} + +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; +} diff --git a/src/traces/scatterregl/plot.js b/src/traces/scatterregl/plot.js deleted file mode 100644 index f87e0ebb810..00000000000 --- a/src/traces/scatterregl/plot.js +++ /dev/null @@ -1,569 +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 Lib = require('../../lib'); -var Axes = require('../../plots/cartesian/axes'); -var ErrorBars = require('../../components/errorbars'); -var str2RGBArray = require('../../lib/str2rgbarray'); -var formatColor = require('../../lib/gl_format_color'); -var subTypes = require('../scatter/subtypes'); -var linkTraces = require('../scatter/link_traces'); -var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); -var DASHES = require('../../constants/gl2d_dashes'); -var createScatter = require('regl-scatter2d'); -var createLine = require('regl-line2d'); -var createError = require('regl-error2d'); -var Drawing = require('../../components/drawing'); -var svgSdf = require('svg-path-sdf'); -var createRegl = require('regl'); - -var DESELECTDIM = 0.2; -var TRANSPARENT = [0, 0, 0, 0]; - -// tables with symbol SDF values -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 convertNumber; - - -module.exports = plot; - - -function plot(container, plotinfo, cdata) { - var layout = container._fullLayout; - var subplotObj = layout._plots[plotinfo.id]; - var scene = subplotObj._scene ? subplotObj._scene : updateScene; - - // that is needed for fills - linkTraces(container, plotinfo, cdata); - - // put references to traces for selectPoints - cdata.forEach(function(cd) { - if(!cd[0] || !cd[0].trace) return; - cd[0].trace._scene = scene; - }); - - // avoid creating scene if it exists already - if(subplotObj._scene) { - scene(cdata); - return; - } - else { - subplotObj._scene = scene; - } - - var canvas = container.querySelector('.gl-canvas-focus'); - - var regl = layout._regl; - if(!regl) { - regl = layout._regl = createRegl({ - canvas: canvas, - attributes: { - preserveDrawingBuffer: false - }, - extensions: ['ANGLE_instanced_arrays', 'OES_element_index_uint'], - pixelRatio: container._context.plotGlPixelRatio || global.devicePixelRatio - }); - } - - // FIXME: provide defaults to lazy init - var scatter2d, line2d, errorX, errorY, fill2d; - scatter2d = createScatter({ - regl: regl, - size: 12, - color: [0, 0, 0, 1], - borderSize: 1, - borderColor: [0, 0, 0, 1] - }); - line2d = createLine({ - regl: regl, - color: [0, 0, 0, 1], - thickness: 1, - miterLimit: 2, - dashes: [1] - }); - errorX = createError(regl); - errorY = createError(regl); - fill2d = createLine(regl); - - var count = 0, viewport; - - function updateRange(range) { - var batch = []; - range = {range: range}; - for(var i = 0; i < count; i++) { - batch.push({ - line: range, - scatter: range, - errorX: range, - errorY: range, - fill: range - }); - } - - updateBatch(batch); - } - - // update multi-traces data and render in proper layers order - function updateBatch(batch) { - if(!batch.length) return; - - var lineBatch = [], - scatterBatch = [], - errorXBatch = [], - errorYBatch = [], - fillBatch = [], - i; - - for(i = 0; i < batch.length; i++) { - lineBatch.push(batch[i].line); - scatterBatch.push(batch[i].scatter); - errorXBatch.push(batch[i].errorX); - errorYBatch.push(batch[i].errorY); - fillBatch.push(batch[i].fill); - } - - line2d.update(lineBatch); - scatter2d.update(scatterBatch); - errorX.update(errorXBatch); - errorY.update(errorYBatch); - fill2d.update(fillBatch); - - count = batch.length; - - // rendering requires proper batch sequence - for(i = 0; i < count; i++) { - if(fillBatch[i]) fill2d.draw(i); - if(errorXBatch[i]) errorX.draw(i); - if(errorYBatch[i]) errorY.draw(i); - if(lineBatch[i]) line2d.draw(i); - if(scatterBatch[i]) scatter2d.draw(i); - } - } - - // update based on calc data - function updateScene(cdscatters) { - var batch = []; - - cdscatters.forEach(function(cdscatter, order) { - if(!cdscatter || !cdscatter[0] || !cdscatter[0].trace) return; - var trace = cdscatter[0].trace; - batch[order] = getTraceOptions(trace); - }); - - updateBatch(batch); - } - - function getTraceOptions(trace) { - var lineOptions, scatterOptions, errorXOptions, errorYOptions, fillOptions; - var xaxis = Axes.getFromId(container, trace.xaxis || 'x'); - var yaxis = Axes.getFromId(container, trace.yaxis || 'y'); - var selection = trace.selection; - var vpSize = layout._size, - width = layout.width, - height = layout.height; - var sizes, selIds; - var i, xx, yy, ptrX = 0, ptrY = 0, errorOptions; - var hasLines = false; - var hasErrorX = false; - var hasErrorY = false; - var hasMarkers = false; - var hasFill = false; - - var x = trace._x; - var y = trace._y; - var count = Math.max(x.length, y.length); - var positions = trace._positions; - - if(trace.visible !== true) { - hasLines = false; - hasErrorX = false; - hasErrorY = false; - hasMarkers = false; - hasFill = false; - } - else { - hasLines = subTypes.hasLines(trace) && x.length > 1 && trace.line; - hasErrorX = trace.error_x.visible === true; - hasErrorY = trace.error_y.visible === true; - hasMarkers = subTypes.hasMarkers(trace); - hasFill = trace.fill && trace.fill !== 'none'; - } - - // update viewport & range - 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 - ]; - - var range = [ - xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1] - ]; - - // get error values - var errorVals = (hasErrorX || hasErrorY) ? ErrorBars.calcFromTrace(trace, layout) : null; - - var errorsX = new Float64Array(4 * count), - errorsY = new Float64Array(4 * count), - linePositions; - - if(hasErrorX) { - for(i = 0; i < count; ++i) { - errorsX[ptrX++] = x[i] - errorVals[i].xs || 0; - errorsX[ptrX++] = errorVals[i].xh - x[i] || 0; - errorsX[ptrX++] = 0; - errorsX[ptrX++] = 0; - } - - errorOptions = trace.error_x; - if(errorOptions.copy_ystyle) { - errorOptions = trace.error_y; - } - errorXOptions = {}; - errorXOptions.positions = positions; - errorXOptions.errors = errorsX; - errorXOptions.capSize = errorOptions.width * 2; - errorXOptions.lineWidth = errorOptions.thickness; - errorXOptions.color = errorOptions.color; - errorXOptions.viewport = viewport; - errorXOptions.range = range; - } - - if(hasErrorY) { - for(i = 0; i < count; ++i) { - errorsY[ptrY++] = 0; - errorsY[ptrY++] = 0; - errorsY[ptrY++] = y[i] - errorVals[i].ys || 0; - errorsY[ptrY++] = errorVals[i].yh - y[i] || 0; - } - - errorOptions = trace.error_y; - errorYOptions = {}; - errorYOptions.positions = positions; - errorYOptions.errors = errorsY; - errorYOptions.capSize = errorOptions.width * 2; - errorYOptions.lineWidth = errorOptions.thickness; - errorYOptions.color = errorOptions.color; - errorYOptions.viewport = viewport; - errorYOptions.range = range; - } - - if(hasLines) { - lineOptions = {}; - lineOptions.thickness = trace.line.width; - lineOptions.color = trace.line.color; - lineOptions.opacity = trace.opacity; - lineOptions.join = trace.opacity === 1.0 ? 'rect' : 'round'; - lineOptions.overlay = true; - - var lineWidth = lineOptions.thickness, - dashes = (DASHES[trace.line.dash] || [1]).slice(); - for(i = 0; i < dashes.length; ++i) dashes[i] *= lineWidth; - 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; - } - lineOptions.positions = linePositions; - lineOptions.viewport = viewport; - lineOptions.range = range; - } - - if(hasFill) { - fillOptions = {}; - fillOptions.fill = trace.fillcolor; - fillOptions.thickness = 0; - fillOptions.viewport = viewport; - fillOptions.range = range; - fillOptions.closed = true; - - var pos = [], srcPos = linePositions || 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 { - var nextTrace = trace._nexttrace; - if(nextTrace && trace.fill === 'tonexty') { - pos = srcPos.slice(); - - // FIXME: overcalculation here - var nextOptions = getTraceOptions(nextTrace); - - if(nextOptions && nextOptions.line) { - var nextPos = nextOptions.line.positions; - - for(i = Math.floor(nextPos.length / 2); i--;) { - 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; - } - } - } - fillOptions.positions = pos; - } - - if(selection && selection.length) { - selIds = {}; - for(i = 0; i < selection.length; i++) { - selIds[selection[i].pointNumber] = true; - } - } - - if(hasMarkers) { - scatterOptions = {}; - scatterOptions.positions = positions; - - var markerSizeFunc = makeBubbleSizeFn(trace); - var markerOpts = trace.marker; - var markerOpacity = markerOpts.opacity; - var traceOpacity = trace.opacity; - var symbols = markerOpts.symbol; - - if(Array.isArray(markerOpts.color) || Array.isArray(markerOpts.size)) { - scatterOptions.sizes = new Array(count); - scatterOptions.markers = new Array(count); - scatterOptions.borderSizes = new Array(count); - scatterOptions.colors = new Array(count); - scatterOptions.borderColors = new Array(count); - - var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, count); - var borderSizes = convertNumber(markerOpts.line.width, count); - var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, count); - var size, symbol, symbolNumber, isOpen, isDimmed, _colors, _borderColors, bw, symbolFunc, symbolNoDot, symbolSdf, symbolNoFill, symbolPath, isDot; - - sizes = convertArray(markerSizeFunc, markerOpts.size, count); - - for(i = 0; i < count; ++i) { - symbol = Array.isArray(symbols) ? symbols[i] : symbols; - symbolNumber = Drawing.symbolNumber(symbol); - symbolFunc = Drawing.symbolFuncs[symbolNumber % 100]; - symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100]; - symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100]; - - isOpen = /-open/.test(symbol); - isDot = /-dot/.test(symbol); - isDimmed = selIds && !selIds[i]; - - _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[i]; - bw = borderSizes[i]; - - scatterOptions.sizes[i] = size; - - if(symbol === 'circle') { - scatterOptions.markers[i] = null; - } - else { - // get symbol sdf from cache or generate it - if(SYMBOL_SDF[symbol]) { - symbolSdf = SYMBOL_SDF[symbol]; - } else { - 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; - } - - scatterOptions.markers[i] = symbolSdf || null; - } - scatterOptions.borderSizes[i] = 0.5 * bw; - - var optColors = scatterOptions.colors; - var dim = isDimmed ? DESELECTDIM : 1; - if(!optColors[i]) optColors[i] = []; - if(isOpen || symbolNoFill) { - optColors[i][0] = TRANSPARENT[0]; - optColors[i][1] = TRANSPARENT[1]; - optColors[i][2] = TRANSPARENT[2]; - optColors[i][3] = TRANSPARENT[3]; - } else { - optColors[i][0] = _colors[4 * i + 0] * 255; - optColors[i][1] = _colors[4 * i + 1] * 255; - optColors[i][2] = _colors[4 * i + 2] * 255; - optColors[i][3] = dim * _colors[4 * i + 3] * 255; - } - if(!scatterOptions.borderColors[i]) scatterOptions.borderColors[i] = []; - scatterOptions.borderColors[i][0] = _borderColors[4 * i + 0] * 255; - scatterOptions.borderColors[i][1] = _borderColors[4 * i + 1] * 255; - scatterOptions.borderColors[i][2] = _borderColors[4 * i + 2] * 255; - scatterOptions.borderColors[i][3] = dim * _borderColors[4 * i + 3] * 255; - } - } - // shortcut update for single color - else { - scatterOptions.color = markerOpts.color; - scatterOptions.opacity = traceOpacity * markerOpacity; - scatterOptions.size = markerSizeFunc(markerOpts.size); - scatterOptions.borderSizes = 0; - } - - scatterOptions.viewport = viewport; - scatterOptions.range = range; - } - - return { - scatter: scatterOptions, - line: lineOptions, - errorX: errorXOptions, - errorY: errorYOptions, - fill: fillOptions - }; - } - - function clear() { - if(!viewport) return; - - // make sure no old graphics on the canvas - var gl = regl._gl; - gl.enable(gl.SCISSOR_TEST); - gl.scissor( - viewport[0] - 1, - viewport[1] - 1, - viewport[2] - viewport[0] + 2, - viewport[3] - viewport[1] + 2 - ); - gl.clearColor(0, 0, 0, 0); - gl.clear(gl.COLOR_BUFFER_BIT); - // FIXME: why ↓ does not suffice here? regl bug? - // regl.clear({color: [0, 0, 0, 0], depth: 1}); - } - - updateScene.range = updateRange; - updateScene.clear = clear; - - updateScene(cdata); - - return updateScene; -} - - -convertNumber = convertArray.bind(null, function(x) { return +x; }); - -// 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; -} - -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; -} From f0c42e07db274e5df6cc70cfdc5e3d2ce1fdaf17 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 23 Oct 2017 16:18:06 -0400 Subject: [PATCH 069/151] Make sure parcoords use global regl instance --- src/plot_api/plot_api.js | 24 +++++++++++++++++++++--- src/traces/scatterregl/index.js | 2 -- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 995461545e6..7492dedb247 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -13,6 +13,7 @@ var d3 = require('d3'); var isNumeric = require('fast-isnumeric'); var hasHover = require('has-hover'); +var createRegl = require('regl'); var Plotly = require('../plotly'); var Lib = require('../lib'); @@ -195,14 +196,31 @@ Plotly.plot = function(gd, data, layout, config) { } fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas').data(fullLayout._has('gl') ? [{ - key: 'contextLayer' + key: 'contextLayer', + context: true, + pick: false }, { - key: 'focusLayer' + key: 'focusLayer', + context: false, + pick: false }, { - key: 'pickLayer' + key: 'pickLayer', + context: false, + pick: true }] : []); fullLayout._glcanvas.enter().append('canvas') + .each(function (d) { + d.regl = createRegl({ + canvas: this, + attributes: { + antialias: !d.pick, + preserveDrawingBuffer: true + }, + extensions: ['ANGLE_instanced_arrays', 'OES_element_index_uint'], + pixelRatio: gd._context.plotGlPixelRatio || global.devicePixelRatio + }); + }) .attr('class', function(d) { return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); }) diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 3ad46190278..5088fd440ec 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -45,8 +45,6 @@ var ScatterRegl = module.exports = extend({}, require('../scatter')); ScatterRegl.name = 'scatterregl'; ScatterRegl.categories = ['gl', 'gl2d', 'regl', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; -ScatterRegl.basePlotModule = require('./base'); - ScatterRegl.calc = function calc(container, trace) { var layout = container._fullLayout; From 5ce9afc889122e62fe56d800ef3aa17d3fe74071 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 23 Oct 2017 17:38:00 -0400 Subject: [PATCH 070/151] Normalize initial options detection --- src/traces/scatterregl/index.js | 34 ++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 5088fd440ec..6f344585a81 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -69,7 +69,7 @@ ScatterRegl.calc = function calc(container, trace) { fillOptions = stash.fill = {}; var selection = trace.selection; var sizes, selIds; - var hasLines, hasErrorX, hasErrorY, hasMarkers, hasFill; + var isVisible, hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill; var linePositions; // calculate axes range @@ -108,8 +108,24 @@ ScatterRegl.calc = function calc(container, trace) { stash._y = y; stash._positions = positions; + if(trace.visible !== true) { + isVisible = false; + hasLines = false; + hasErrorX = false; + hasErrorY = false; + hasMarkers = false; + } + else { + isVisible = true; + hasLines = subTypes.hasLines(trace); + hasErrorX = trace.error_x.visible === true; + hasErrorY = trace.error_y.visible === true; + hasError = hasErrorX || hasErrorY; + hasMarkers = subTypes.hasMarkers(trace); + } + // get error values - var errorVals = (hasErrorX || hasErrorY) ? ErrorBars.calcFromTrace(trace, layout) : null; + var errorVals = hasError ? ErrorBars.calcFromTrace(trace, layout) : null; if(hasErrorX) { var errorsX = new Float64Array(4 * count); @@ -329,10 +345,21 @@ ScatterRegl.calc = function calc(container, trace) { } } + // make sure scene exists + var scene = subplot._scene; + if (!subplot._scene) { + scene = subplot._scene = {} + } + + // mark renderers required for the data + if (!scene.renderError && hasError) scene.renderError = true; + if (!scene.renderLine && hasLines) scene.renderLine = true; + if (!scene.renderMarkers && hasError) scene.renderMarkers = true; + if (!scene.renderFill && hasFill) scene.renderFill = true; + return [{x: false, y: false, t: stash, trace: trace}]; }; - //TODO: manages selection, range, viewport, that's it ScatterRegl.plot = function plot(container, plotinfo, cdata) { var layout = container._fullLayout; @@ -537,6 +564,7 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { 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]; From ea3eb4e8ce365838bd118e90f40d6c5cf9c169af Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 24 Oct 2017 12:20:32 -0400 Subject: [PATCH 071/151] Make proper range/viewport init --- src/plot_api/plot_api.js | 86 +++++++++++----------- src/plots/cartesian/index.js | 13 ++-- src/traces/scatterregl/index.js | 123 +++++++++++++++++++++++++------- 3 files changed, 149 insertions(+), 73 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 7492dedb247..a517e975675 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -195,48 +195,50 @@ Plotly.plot = function(gd, data, layout, config) { } } - fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas').data(fullLayout._has('gl') ? [{ - key: 'contextLayer', - context: true, - pick: false - }, { - key: 'focusLayer', - context: false, - pick: false - }, { - key: 'pickLayer', - context: false, - pick: true - }] : []); - - fullLayout._glcanvas.enter().append('canvas') - .each(function (d) { - d.regl = createRegl({ - canvas: this, - attributes: { - antialias: !d.pick, - preserveDrawingBuffer: true - }, - extensions: ['ANGLE_instanced_arrays', 'OES_element_index_uint'], - pixelRatio: gd._context.plotGlPixelRatio || global.devicePixelRatio - }); - }) - .attr('class', function(d) { - return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); - }) - .style({ - 'position': 'absolute', - 'top': 0, - 'left': 0, - 'width': '100%', - 'height': '100%', - 'pointer-events': 'none', - 'overflow': 'visible' - }) - .attr('width', fullLayout.width) - .attr('height', fullLayout.height); - - fullLayout._glcanvas.exit().remove(); + if (!fullLayout._glcanvas) { + fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas').data(fullLayout._has('gl') ? [{ + key: 'contextLayer', + context: true, + pick: false + }, { + key: 'focusLayer', + context: false, + pick: false + }, { + key: 'pickLayer', + context: false, + pick: true + }] : []); + + fullLayout._glcanvas.enter().append('canvas') + .each(function (d) { + d.regl = createRegl({ + canvas: this, + attributes: { + antialias: !d.pick, + preserveDrawingBuffer: true + }, + extensions: ['ANGLE_instanced_arrays', 'OES_element_index_uint'], + pixelRatio: gd._context.plotGlPixelRatio || global.devicePixelRatio + }); + }) + .attr('class', function(d) { + return 'gl-canvas gl-canvas-' + d.key.replace('Layer', ''); + }) + .style({ + 'position': 'absolute', + 'top': 0, + 'left': 0, + 'width': '100%', + 'height': '100%', + 'pointer-events': 'none', + 'overflow': 'visible' + }) + .attr('width', fullLayout.width) + .attr('height', fullLayout.height); + + fullLayout._glcanvas.exit().remove(); + } return Lib.syncOrAsync([ subroutines.layoutStyles diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index 9f68da3b0f9..b14ed2b71ec 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -48,12 +48,13 @@ exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) { } } - // clear gl frame, if any - for(var id in fullLayout._plots) { - var scatterScene = fullLayout._plots[id]._scene; - if(scatterScene && scatterScene.clear) { - scatterScene.clear(); - } + // clear gl frame, if any, since we preserve drawing buffer + if (fullLayout._glcanvas.size()) { + fullLayout._glcanvas.each(function (d) { + d.regl.clear({ + color: true + }); + }) } for(i = 0; i < subplots.length; i++) { diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 6f344585a81..bd9867b470c 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -61,12 +61,7 @@ ScatterRegl.calc = function calc(container, trace) { var y = yaxis.type === 'linear' ? trace.y : yaxis.makeCalcdata(trace, 'y'); var count = Math.max(x.length, y.length), i, l, xx, yy, ptrX = 0, ptrY = 0; - var lineOptions = stash.line = {}, - scatterOptions = stash.scatter = {}, - errorOptions = {}, - errorXOptions = stash.errorX = {}, - errorYOptions = stash.errorY = {}, - fillOptions = stash.fill = {}; + var lineOptions, scatterOptions, errorOptions, errorXOptions, errorYOptions, fillOptions; var selection = trace.selection; var sizes, selIds; var isVisible, hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill; @@ -128,6 +123,8 @@ ScatterRegl.calc = function calc(container, trace) { 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) { @@ -149,6 +146,8 @@ ScatterRegl.calc = function calc(container, trace) { } if(hasErrorY) { + errorYOptions = {} + errorYOptions.positions = positions; var errorsY = new Float64Array(4 * count); for(i = 0; i < count; ++i) { @@ -166,6 +165,7 @@ ScatterRegl.calc = function calc(container, trace) { } if(hasLines) { + lineOptions = {} lineOptions.thickness = trace.line.width; lineOptions.color = trace.line.color; lineOptions.opacity = trace.opacity; @@ -221,6 +221,7 @@ ScatterRegl.calc = function calc(container, trace) { } if(hasFill) { + fillOptions = {} fillOptions.fill = trace.fillcolor; fillOptions.thickness = 0; fillOptions.closed = true; @@ -263,6 +264,7 @@ ScatterRegl.calc = function calc(container, trace) { } if(hasMarkers) { + scatterOptions = {} scatterOptions.positions = positions; var markerSizeFunc = makeBubbleSizeFn(trace); @@ -310,7 +312,7 @@ ScatterRegl.calc = function calc(container, trace) { } else { scatterOptions.color = markerOpts.color; - scatterOptions.borderColor = markerOpts.line; + scatterOptions.borderColor = markerOpts.line.color; scatterOptions.opacity = trace.opacity * markerOpts.opacity; } @@ -348,14 +350,29 @@ ScatterRegl.calc = function calc(container, trace) { // make sure scene exists var scene = subplot._scene; if (!subplot._scene) { - scene = subplot._scene = {} + scene = subplot._scene = { + count: 0, + lineOptions: [], + fillOptions: [], + scatterOptions: [], + errorXOptions: [], + errorYOptions: [] + }; } // mark renderers required for the data - if (!scene.renderError && hasError) scene.renderError = true; - if (!scene.renderLine && hasLines) scene.renderLine = true; - if (!scene.renderMarkers && hasError) scene.renderMarkers = true; - if (!scene.renderFill && hasFill) scene.renderFill = true; + if (!scene.error2d && hasError) scene.error2d = true; + if (!scene.line2d && hasLines) scene.line2d = true; + if (!scene.scatter2d && hasMarkers) scene.scatter2d = true; + if (!scene.fill2d && hasFill) scene.fill2d = true; + + // save initial batch + scene.lineOptions.push(lineOptions); + scene.errorXOptions.push(errorXOptions); + scene.errorYOptions.push(errorYOptions); + scene.fillOptions.push(fillOptions); + scene.scatterOptions.push(scatterOptions); + scene.count++; return [{x: false, y: false, t: stash, trace: trace}]; }; @@ -366,13 +383,27 @@ ScatterRegl.plot = function plot(container, plotinfo, cdata) { var subplot = layout._plots[plotinfo.id]; var scene = subplot._scene; var vpSize = layout._size, width = layout.width, height = layout.height; + var regl = layout._glcanvas.data()[1].regl; // that is needed for fills linkTraces(container, plotinfo, cdata); - var count = 0, viewport; + // 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); + } - var batch = cdata.map(function(cdscatter, order) { + // 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; @@ -384,7 +415,6 @@ ScatterRegl.plot = function plot(container, plotinfo, cdata) { xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1] ]; - // update viewport & range var viewport = [ vpSize.l + xaxis.domain[0] * vpSize.w, vpSize.b + yaxis.domain[0] * vpSize.h, @@ -392,8 +422,59 @@ ScatterRegl.plot = function plot(container, plotinfo, cdata) { (height - vpSize.t) - (1 - yaxis.domain[1]) * vpSize.h ]; - stash.scatter.viewport = stash.line.viewport = stash.errorX.viewport = stash.errorY.viewport = stash.fill.viewport = viewport; - stash.scatter.range = stash.line.range = stash.errorX.range = stash.errorY.range = stash.fill.range = range; + return { + viewport: viewport, + range: range + }; + }); + + // uploat batch data to GPU + if (scene.fill2d) { + if (scene.fillOptions) { + scene.fill2d.update(scene.fillOptions); + scene.fillOptions = null; + } + scene.fill2d.update(vpRange); + } + if (scene.line2d) { + if (scene.lineOptions) { + scene.line2d.update(scene.lineOptions); + scene.lineOptions = null; + } + scene.line2d.update(vpRange); + } + if (scene.error2d) { + if (scene.errorXOptions || scene.errorYOptions) { + var errorBatch = (scene.errorXOptions || []).concat(scene.errorYOptions || []); + scene.error2d.update(errorBatch); + scene.errorXOptions = scene.errorYOptions = null; + } + scene.error2d.update(vpRange.concat(vpRange)); + } + if (scene.scatter2d) { + if (scene.scatterOptions) { + scene.scatter2d.update(scene.scatterOptions); + scene.scatterOptions = null; + } + scene.scatter2d.update(vpRange); + } + + // draw traces in proper order + for (var i = 0; i < scene.count; 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.scatter2d.draw(i); + if (scene.fill2d) scene.fill2d.draw(i); + } + + //reset batch + // batch.length = 0; + + return; + cdata.map(function(cdscatter, order) { // TODO: update selection here if(trace.selection && trace.selection.length) { @@ -403,14 +484,6 @@ ScatterRegl.plot = function plot(container, plotinfo, cdata) { } } // TODO: recalculate fill area here since we can't calc connected traces beforehead - - return { - scatter: stash.scatter, - line: stash.line, - errorX: stash.errorX, - errorY: stash.errorY, - fill: stash.fill - }; }); scene.update(batch); From 2a59bbfb71d05401d702bd3d82ce67850933544d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 24 Oct 2017 12:38:12 -0400 Subject: [PATCH 072/151] Make scattergl init test pass --- src/plots/cartesian/dragbox.js | 14 +++++++++++-- src/traces/scatterregl/index.js | 36 +++++++++++++++++++++------------ 2 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index e678596463d..02ae7ad3cb4 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -711,6 +711,16 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { return ax._length * (1 - scaleFactor) * FROM_TL[ax.constraintoward || 'middle']; } + // clear gl frame, if any, since we preserve drawing buffer + // FIXME: code duplication with cartesian.plot + if (fullLayout._glcanvas.size()) { + fullLayout._glcanvas.each(function (d) { + d.regl.clear({ + color: true + }); + }) + } + for(i = 0; i < subplots.length; i++) { var subplot = plotinfos[subplots[i]], xa2 = subplot.xaxis, @@ -719,11 +729,11 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { editY2 = editY && !ya2.fixedrange && (ya.indexOf(ya2) !== -1); // scattergl translate - if(subplot._scene && subplot._scene.range) { + if(subplot._scene && subplot._scene.updateRange) { // FIXME: possibly we could update axis internal _r and _rl here var xaRange = Lib.simpleMap(xa2.range, xa2.r2l), yaRange = Lib.simpleMap(ya2.range, ya2.r2l); - subplot._scene.range( + subplot._scene.updateRange( [xaRange[0], yaRange[0], xaRange[1], yaRange[1]] ); } diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index bd9867b470c..3710599a2f1 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -358,6 +358,28 @@ ScatterRegl.calc = function calc(container, trace) { errorXOptions: [], errorYOptions: [] }; + + scene.updateRange = function updateRange (range) { + var opts = Array(scene.count).fill({range: range}); + 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(opts.concat(opts)); + scene.draw(); + }; + + // draw traces in proper order + scene.draw = function draw () { + for (var i = 0; i < scene.count; 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.scatter2d.draw(i); + if (scene.fill2d) scene.fill2d.draw(i); + } + }; } // mark renderers required for the data @@ -459,19 +481,7 @@ ScatterRegl.plot = function plot(container, plotinfo, cdata) { scene.scatter2d.update(vpRange); } - // draw traces in proper order - for (var i = 0; i < scene.count; 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.scatter2d.draw(i); - if (scene.fill2d) scene.fill2d.draw(i); - } - - //reset batch - // batch.length = 0; + scene.draw(); return; cdata.map(function(cdscatter, order) { From dbafeb3975f5e7015903b0618c1df2bc63458bdd Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 25 Oct 2017 12:18:13 -0400 Subject: [PATCH 073/151] Make sure markers show proper colors --- src/lib/gl_format_color.js | 1 + src/traces/scatterregl/index.js | 104 ++++++++++++++------------------ 2 files changed, 47 insertions(+), 58 deletions(-) diff --git a/src/lib/gl_format_color.js b/src/lib/gl_format_color.js index 83052c63360..5635517bb44 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/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 3710599a2f1..50538adc2ff 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -31,7 +31,6 @@ var Plots = require('../../plots/plots'); var MAXDIST = Fx.constants.MAXDIST; var DESELECTDIM = 0.2; -var TRANSPARENT = [0, 0, 0, 0]; var SYMBOL_SDF_SIZE = 200; var SYMBOL_SIZE = 20; var SYMBOL_STROKE = SYMBOL_SIZE / 20; @@ -60,7 +59,7 @@ ScatterRegl.calc = function calc(container, trace) { var x = xaxis.type === 'linear' ? trace.x : xaxis.makeCalcdata(trace, 'x'); var y = yaxis.type === 'linear' ? trace.y : yaxis.makeCalcdata(trace, 'y'); - var count = Math.max(x.length, y.length), i, l, xx, yy, ptrX = 0, ptrY = 0; + var count = Math.max(x ? x.length : 0, y ? y.length: 0), i, l, xx, yy, ptrX = 0, ptrY = 0; var lineOptions, scatterOptions, errorOptions, errorXOptions, errorYOptions, fillOptions; var selection = trace.selection; var sizes, selIds; @@ -88,8 +87,9 @@ ScatterRegl.calc = function calc(container, trace) { // we need hi-precision for scatter2d positions = new Array(count * 2); for(i = 0; i < count; i++) { - positions[i * 2] = +x[i]; - positions[i * 2 + 1] = +y[i]; + // if no x defined, we are creating simple int sequence (API) + positions[i * 2] = x ? +x[i] : i; + positions[i * 2 + 1] = y ? +y[i] : i; } calcColorscales(trace); @@ -270,42 +270,50 @@ ScatterRegl.calc = function calc(container, trace) { var markerSizeFunc = makeBubbleSizeFn(trace); var markerOpts = trace.marker; + //get basic symbol info + var multiMarker = Array.isArray(markerOpts.symbol); + var symbolNumber, isOpen, symbol, noFill; + if (!multiMarker) { + symbolNumber = Drawing.symbolNumber(markerOpts.symbol); + isOpen = /-open/.test(markerOpts.symbol); + noFill = !!Drawing.symbolNoFill[symbolNumber % 100]; + } //prepare colors - if (Array.isArray(markerOpts.color) || Array.isArray(markerOpts.line.color)) { + if (multiMarker || Array.isArray(markerOpts.color) || Array.isArray(markerOpts.line.color) || Array.isArray(markerOpts.line)) { scatterOptions.colors = new Array(count); scatterOptions.borderColors = new Array(count); - var colors = convertColorScale(markerOpts, markerOpacity, traceOpacity, count); - var borderColors = convertColorScale(markerOpts.line, markerOpacity, traceOpacity, count); - var _colors, _borderColors, bw; - - for(i = 0; i < count; ++i) { - _colors = colors; + var colors = formatColor(markerOpts, markerOpts.opacity, count); + var borderColors = formatColor(markerOpts.line, markerOpts.opacity, count); - if(isOpen) { - _borderColors = colors; - } else { - _borderColors = borderColors; + 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; + } + } - var optColors = scatterOptions.colors; - if(!optColors[i]) optColors[i] = []; - if(isOpen || symbolNoFill) { - optColors[i][0] = TRANSPARENT[0]; - optColors[i][1] = TRANSPARENT[1]; - optColors[i][2] = TRANSPARENT[2]; - optColors[i][3] = TRANSPARENT[3]; - } else { - optColors[i][0] = _colors[4 * i + 0] * 255; - optColors[i][1] = _colors[4 * i + 1] * 255; - optColors[i][2] = _colors[4 * i + 2] * 255; - optColors[i][3] = _colors[4 * i + 3] * 255; + scatterOptions.colors = colors; + scatterOptions.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; } - if(!scatterOptions.borderColors[i]) scatterOptions.borderColors[i] = []; - scatterOptions.borderColors[i][0] = _borderColors[4 * i + 0] * 255; - scatterOptions.borderColors[i][1] = _borderColors[4 * i + 1] * 255; - scatterOptions.borderColors[i][2] = _borderColors[4 * i + 2] * 255; - scatterOptions.borderColors[i][3] = dim * _borderColors[4 * i + 3] * 255; } scatterOptions.opacity = trace.opacity; @@ -314,6 +322,12 @@ ScatterRegl.calc = function calc(container, trace) { scatterOptions.color = markerOpts.color; scatterOptions.borderColor = markerOpts.line.color; scatterOptions.opacity = trace.opacity * markerOpts.opacity; + + if (isOpen) { + scatterOptions.borderColor = scatterOptions.color.slice(); + scatterOptions.color = scatterOptions.color.slice(); + scatterOptions.color[3] = 0; + } } //prepare markers @@ -698,29 +712,3 @@ function _convertArray(convert, data, count) { return result; } - -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; -} From cb1b553d04d31481c69d68e29aea5db28fc26724 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 25 Oct 2017 12:59:38 -0400 Subject: [PATCH 074/151] Use fast scatter expand --- src/traces/scatterregl/index.js | 46 +++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 50538adc2ff..642c190fc63 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -49,8 +49,8 @@ ScatterRegl.calc = function calc(container, trace) { var layout = container._fullLayout; var positions; var stash = {}; - var xaxis = Axes.getFromId(container, trace.xaxis || 'x'); - var yaxis = Axes.getFromId(container, trace.yaxis || 'y'); + var xaxis = Axes.getFromId(container, trace.xaxis); + var yaxis = Axes.getFromId(container, trace.yaxis); //FIXME: find a better way to obtain subplot object from trace var subplot = layout._plots[trace.xaxis + trace.yaxis]; @@ -66,12 +66,6 @@ ScatterRegl.calc = function calc(container, trace) { var isVisible, hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill; var linePositions; - // calculate axes range - // FIXME: probably we may want to have more complex ppad calculation - // FIXME: that is pretty slow thing here @etpinard your assistance required - Axes.expand(xaxis, x, 0); - Axes.expand(yaxis, y, 0); - // convert log axes if(xaxis.type === 'log') { for(i = 0, l = x.length; i < l; i++) { @@ -86,16 +80,41 @@ ScatterRegl.calc = function calc(container, trace) { // we need hi-precision for scatter2d positions = new Array(count * 2); + var xbounds = [Infinity, -Infinity], ybounds = [Infinity, -Infinity] + for(i = 0; i < count; i++) { // if no x defined, we are creating simple int sequence (API) - positions[i * 2] = x ? +x[i] : i; - positions[i * 2 + 1] = y ? +y[i] : i; + // we use parseFloat because it gives NaN (we need that for empty values to avoid drawing lines) and it is incredibly fast + xx = x ? parseFloat(x[i]) : i; + yy = y ? parseFloat(y[i]) : 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; + + positions[i * 2] = xx; + positions[i * 2 + 1] = yy; + } + + // calculate axes range + var pad = 20; + 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 }); } calcColorscales(trace); // TODO: delegate this to webworker if possible (potential ) - // FIXME: make sure it is a good place to store the tree stash._tree = kdtree(positions, 512); // stash data @@ -414,15 +433,14 @@ ScatterRegl.calc = function calc(container, trace) { }; //TODO: manages selection, range, viewport, that's it -ScatterRegl.plot = function plot(container, plotinfo, cdata) { +ScatterRegl.plot = function plot(container, subplot, cdata) { var layout = container._fullLayout; - var subplot = layout._plots[plotinfo.id]; var scene = subplot._scene; var vpSize = layout._size, width = layout.width, height = layout.height; var regl = layout._glcanvas.data()[1].regl; // that is needed for fills - linkTraces(container, plotinfo, cdata); + linkTraces(container, subplot, cdata); // make sure scenes are created if (scene.error2d === true) { From c22d688b5836a2735ac7bea4ab18a591f20d4d39 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 25 Oct 2017 17:17:07 -0400 Subject: [PATCH 075/151] Resurrect fills --- src/traces/scatterregl/index.js | 190 +++++++++++++++++++------------- 1 file changed, 112 insertions(+), 78 deletions(-) diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 642c190fc63..4fe8f78f6c7 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -115,12 +115,12 @@ ScatterRegl.calc = function calc(container, trace) { calcColorscales(trace); // TODO: delegate this to webworker if possible (potential ) - stash._tree = kdtree(positions, 512); + stash.tree = kdtree(positions, 512); // stash data - stash._x = x; - stash._y = y; - stash._positions = positions; + stash.x = x; + stash.y = y; + stash.positions = positions; if(trace.visible !== true) { isVisible = false; @@ -128,14 +128,16 @@ ScatterRegl.calc = function calc(container, trace) { hasErrorX = false; hasErrorY = false; hasMarkers = false; + hasFill = false; } else { isVisible = true; - hasLines = subTypes.hasLines(trace); + 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 @@ -244,42 +246,6 @@ ScatterRegl.calc = function calc(container, trace) { fillOptions.fill = trace.fillcolor; fillOptions.thickness = 0; fillOptions.closed = true; - - var pos = [], srcPos = linePositions || 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 { - var nextTrace = trace._nexttrace; - if(nextTrace && trace.fill === 'tonexty') { - pos = srcPos.slice(); - - // FIXME: overcalculation here - var nextOptions = getTraceOptions(nextTrace); - - if(nextOptions && nextOptions.line) { - var nextPos = nextOptions.line.positions; - - for(i = Math.floor(nextPos.length / 2); i--;) { - 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; - } - } - } - fillOptions.positions = pos; } if(hasMarkers) { @@ -382,9 +348,11 @@ ScatterRegl.calc = function calc(container, trace) { // make sure scene exists var scene = subplot._scene; + if (!subplot._scene) { scene = subplot._scene = { count: 0, + dirty: true, lineOptions: [], fillOptions: [], scatterOptions: [], @@ -393,28 +361,46 @@ ScatterRegl.calc = function calc(container, trace) { }; scene.updateRange = function updateRange (range) { - var opts = Array(scene.count).fill({range: range}); + var opts = Array(scene.count); + var rangeOpts = {range: range}; + for (var i = 0; i < scene.count; i++) { + opts[i] = rangeOpts; + } 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(opts.concat(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.scatter2d.draw(i); - if (scene.fill2d) scene.fill2d.draw(i); } + + scene.dirty = false; }; } + // 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.scatterOptions = []; + scene.errorXOptions = []; + scene.errorYOptions = []; + } + // mark renderers required for the data if (!scene.error2d && hasError) scene.error2d = true; if (!scene.line2d && hasLines) scene.line2d = true; @@ -436,24 +422,91 @@ ScatterRegl.calc = function calc(container, trace) { ScatterRegl.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; var regl = layout._glcanvas.data()[1].regl; // that is needed for fills linkTraces(container, subplot, cdata); - // 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.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) { + scene.scatter2d.update(scene.scatterOptions); + } + // 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 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 { + var nextTrace = trace._nexttrace; + if(nextTrace && trace.fill === 'tonexty') { + pos = srcPos.slice(); + + // FIXME: overcalculation here + var nextOptions = scene.lineOptions[i + 1]; + + if(nextOptions) { + var nextPos = nextOptions.positions; + + 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; + } + } + } + fillOptions.positions = pos; + }); + + scene.fill2d.update(scene.fillOptions); + } } // provide viewport and range @@ -484,32 +537,15 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { // uploat batch data to GPU if (scene.fill2d) { - if (scene.fillOptions) { - scene.fill2d.update(scene.fillOptions); - scene.fillOptions = null; - } scene.fill2d.update(vpRange); } if (scene.line2d) { - if (scene.lineOptions) { - scene.line2d.update(scene.lineOptions); - scene.lineOptions = null; - } scene.line2d.update(vpRange); } if (scene.error2d) { - if (scene.errorXOptions || scene.errorYOptions) { - var errorBatch = (scene.errorXOptions || []).concat(scene.errorYOptions || []); - scene.error2d.update(errorBatch); - scene.errorXOptions = scene.errorYOptions = null; - } scene.error2d.update(vpRange.concat(vpRange)); } if (scene.scatter2d) { - if (scene.scatterOptions) { - scene.scatter2d.update(scene.scatterOptions); - scene.scatterOptions = null; - } scene.scatter2d.update(vpRange); } @@ -527,8 +563,6 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { } // TODO: recalculate fill area here since we can't calc connected traces beforehead }); - - scene.update(batch); }; ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { @@ -536,11 +570,11 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { trace = cd[0].trace, xa = pointData.xa, ya = pointData.ya, - positions = trace._positions, - x = trace._x, - y = trace._y, + positions = trace.positions, + x = trace.x, + y = trace.y, // hoveron = trace.hoveron || '', - tree = trace._tree; + tree = trace.tree; if(!tree) return [pointData]; From 2309d8ef3905de1dbf4c2d91c5802245845be111 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 25 Oct 2017 17:31:53 -0400 Subject: [PATCH 076/151] Make hover work --- src/traces/scatterregl/index.js | 62 ++++++++++++++++----------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 4fe8f78f6c7..e46fc65191f 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -567,14 +567,15 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { var cd = pointData.cd, + stash = cd[0].t, trace = cd[0].trace, xa = pointData.xa, ya = pointData.ya, - positions = trace.positions, - x = trace.x, - y = trace.y, - // hoveron = trace.hoveron || '', - tree = trace.tree; + positions = stash.positions, + x = stash.x, + y = stash.y, + // hoveron = stash.hoveron || '', + tree = stash.tree; if(!tree) return [pointData]; @@ -604,38 +605,37 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { y: y[id] }; - // that is single-item arrays_to_calcdata excerpt, bc we don't have to do it beforehead for 1e6 points - mergeProp(trace.text, 'tx'); - mergeProp(trace.hovertext, 'htx'); - mergeProp(trace.customdata, 'data'); - mergeProp(trace.textposition, 'tp'); - if(trace.textfont) { - mergeProp(trace.textfont.size, 'ts'); - mergeProp(trace.textfont.color, 'tc'); - mergeProp(trace.textfont.family, 'tf'); + // 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) { - mergeProp(marker.size, 'ms'); - mergeProp(marker.opacity, 'mo'); - mergeProp(marker.symbol, 'mx'); - mergeProp(marker.color, 'mc'); - - var markerLine = marker.line; - if(marker.line) { - mergeProp(markerLine.color, 'mlc'); - mergeProp(markerLine.width, 'mlw'); - } - var markerGradient = marker.gradient; - if(markerGradient && markerGradient.type !== 'none') { - mergeProp(markerGradient.type, 'mgt'); - mergeProp(markerGradient.color, 'mgc'); - } + 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; } - function mergeProp(list, short) { - if (Array.isArray(list)) di[short] = list[id] + 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), From 748cff8a31c400d0c211c286e8ee4277e977c339 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 25 Oct 2017 17:37:57 -0400 Subject: [PATCH 077/151] Optimize sizes processing, remove redundant fn --- src/traces/scatterregl/index.js | 55 +++++++++++++++------------------ 1 file changed, 25 insertions(+), 30 deletions(-) diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index e46fc65191f..c3c966c17f1 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -328,16 +328,33 @@ ScatterRegl.calc = function calc(container, trace) { //prepare sizes if(Array.isArray(markerOpts.size) || Array.isArray(markerOpts.line.width)) { - scatterOptions.sizes = new Array(count); - scatterOptions.borderSizes = new Array(count); + var size; + var sizes = scatterOptions.sizes = new Array(count); + var borderSizes = scatterOptions.borderSizes = new Array(count); - var borderSizes = convertNumber(markerOpts.line.width, count); - var sizes = convertArray(markerSizeFunc, markerOpts.size, 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; + } + } - for(i = 0; i < count; ++i) { - // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798 - scatterOptions.sizes[i] = sizes[i]; - scatterOptions.borderSizes[i] = 0.5 * borderSizes[i]; + // 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] * .5; + } + } + else { + size = markerSizeFunc(markerOpts.line.width) * .5; + for(i = 0; i < count; ++i) { + borderSizes[i] = size; + } } } else { @@ -742,25 +759,3 @@ function getSymbolSdf(symbol) { return symbolSdf || null; } - -var convertNumber = convertArray.bind(null, function(x) { return +x; }); - -// 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; -} From c63d09e7c8e2a7ededd460fcd7f12632e14531ec Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 25 Oct 2017 18:15:40 -0400 Subject: [PATCH 078/151] Lintify --- src/lib/gl_format_color.js | 2 +- src/plot_api/plot_api.js | 4 +- src/plots/cartesian/dragbox.js | 6 +- src/plots/cartesian/index.js | 6 +- src/traces/parcoords/lines.js | 4 +- src/traces/parcoords/parcoords.js | 2 - src/traces/scatterregl/index.js | 188 ++++++++++++++---------------- 7 files changed, 101 insertions(+), 111 deletions(-) diff --git a/src/lib/gl_format_color.js b/src/lib/gl_format_color.js index 5635517bb44..8d93b7e1f92 100644 --- a/src/lib/gl_format_color.js +++ b/src/lib/gl_format_color.js @@ -59,7 +59,7 @@ function formatColor(containerIn, opacityIn, len) { if(isArrayColorIn) { getColor = function(c, i) { - //FIXME: there is double work, considering that sclFunc does the opposite + // 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 a517e975675..7fe0ba207b2 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -195,7 +195,7 @@ Plotly.plot = function(gd, data, layout, config) { } } - if (!fullLayout._glcanvas) { + if(!fullLayout._glcanvas) { fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas').data(fullLayout._has('gl') ? [{ key: 'contextLayer', context: true, @@ -211,7 +211,7 @@ Plotly.plot = function(gd, data, layout, config) { }] : []); fullLayout._glcanvas.enter().append('canvas') - .each(function (d) { + .each(function(d) { d.regl = createRegl({ canvas: this, attributes: { diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 02ae7ad3cb4..69aae336e78 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -713,12 +713,12 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { // clear gl frame, if any, since we preserve drawing buffer // FIXME: code duplication with cartesian.plot - if (fullLayout._glcanvas.size()) { - fullLayout._glcanvas.each(function (d) { + if(fullLayout._glcanvas.size()) { + fullLayout._glcanvas.each(function(d) { d.regl.clear({ color: true }); - }) + }); } for(i = 0; i < subplots.length; i++) { diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index b14ed2b71ec..d4170ea439d 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -49,12 +49,12 @@ exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) { } // clear gl frame, if any, since we preserve drawing buffer - if (fullLayout._glcanvas.size()) { - fullLayout._glcanvas.each(function (d) { + if(fullLayout._glcanvas.size()) { + fullLayout._glcanvas.each(function(d) { d.regl.clear({ color: true }); - }) + }); } for(i = 0; i < subplots.length; i++) { diff --git a/src/traces/parcoords/lines.js b/src/traces/parcoords/lines.js index 4aef621fb49..2b7ec1c4623 100644 --- a/src/traces/parcoords/lines.js +++ b/src/traces/parcoords/lines.js @@ -201,7 +201,7 @@ module.exports = function(canvasGL, d, scatter) { var points = makePoints(sampleCount, dimensionCount, initialDims, color); var attributes = makeAttributes(sampleCount, points); - var regl = d.regl + var regl = d.regl; var paletteTexture = regl.texture({ shape: [256, 1], @@ -433,7 +433,7 @@ module.exports = function(canvasGL, d, scatter) { } function destroy() { - paletteTexture.destroy() + paletteTexture.destroy(); } return { diff --git a/src/traces/parcoords/parcoords.js b/src/traces/parcoords/parcoords.js index 34204cf45a4..ce7f7af82a1 100644 --- a/src/traces/parcoords/parcoords.js +++ b/src/traces/parcoords/parcoords.js @@ -297,8 +297,6 @@ module.exports = function(root, svg, parcoordsLineLayers, styledData, layout, ca var parcoordsLineLayer = parcoordsLineLayers.selectAll('.gl-canvas') .each(function(d) { - var key = d.key; - // FIXME: figure out how to handle multiple instances d.viewModel = vm[0]; d.model = vm[0].model; diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index c3c966c17f1..b1d2d50ea18 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -20,14 +20,12 @@ 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 str2RGBArray = require('../../lib/str2rgbarray'); 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 Plots = require('../../plots/plots'); var MAXDIST = Fx.constants.MAXDIST; var DESELECTDIM = 0.2; @@ -52,17 +50,16 @@ ScatterRegl.calc = function calc(container, trace) { var xaxis = Axes.getFromId(container, trace.xaxis); var yaxis = Axes.getFromId(container, trace.yaxis); - //FIXME: find a better way to obtain subplot object from trace + // FIXME: find a better 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 = Math.max(x ? x.length : 0, y ? y.length: 0), i, l, xx, yy, ptrX = 0, ptrY = 0; - var lineOptions, scatterOptions, errorOptions, errorXOptions, errorYOptions, fillOptions; + var count = Math.max(x ? x.length : 0, y ? y.length : 0), i, l, xx, yy, ptrX = 0, ptrY = 0; + var lineOptions, scatterOptions, errorXOptions, errorYOptions, fillOptions; var selection = trace.selection; - var sizes, selIds; var isVisible, hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill; var linePositions; @@ -80,7 +77,7 @@ ScatterRegl.calc = function calc(container, trace) { // we need hi-precision for scatter2d positions = new Array(count * 2); - var xbounds = [Infinity, -Infinity], ybounds = [Infinity, -Infinity] + var xbounds = [Infinity, -Infinity], ybounds = [Infinity, -Infinity]; for(i = 0; i < count; i++) { // if no x defined, we are creating simple int sequence (API) @@ -88,10 +85,10 @@ ScatterRegl.calc = function calc(container, trace) { xx = x ? parseFloat(x[i]) : i; yy = y ? parseFloat(y[i]) : 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; + 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; positions[i * 2] = xx; positions[i * 2 + 1] = yy; @@ -99,16 +96,16 @@ ScatterRegl.calc = function calc(container, trace) { // calculate axes range var pad = 20; - if (xaxis._min) { + if(xaxis._min) { xaxis._min.push({ val: xbounds[0], pad: pad }); } - if (xaxis._max) { + if(xaxis._max) { xaxis._max.push({ val: xbounds[1], pad: pad }); } - if (yaxis._min) { + if(yaxis._min) { yaxis._min.push({ val: ybounds[0], pad: pad }); } - if (yaxis._max) { + if(yaxis._max) { yaxis._max.push({ val: ybounds[1], pad: pad }); } @@ -137,7 +134,7 @@ ScatterRegl.calc = function calc(container, trace) { hasErrorY = trace.error_y.visible === true; hasError = hasErrorX || hasErrorY; hasMarkers = subTypes.hasMarkers(trace); - hasFill = !!trace.fill && trace.fill != 'none'; + hasFill = !!trace.fill && trace.fill !== 'none'; } // get error values @@ -167,7 +164,7 @@ ScatterRegl.calc = function calc(container, trace) { } if(hasErrorY) { - errorYOptions = {} + errorYOptions = {}; errorYOptions.positions = positions; var errorsY = new Float64Array(4 * count); @@ -186,7 +183,7 @@ ScatterRegl.calc = function calc(container, trace) { } if(hasLines) { - lineOptions = {} + lineOptions = {}; lineOptions.thickness = trace.line.width; lineOptions.color = trace.line.color; lineOptions.opacity = trace.opacity; @@ -242,46 +239,44 @@ ScatterRegl.calc = function calc(container, trace) { } if(hasFill) { - fillOptions = {} + fillOptions = {}; fillOptions.fill = trace.fillcolor; fillOptions.thickness = 0; fillOptions.closed = true; } if(hasMarkers) { - scatterOptions = {} + scatterOptions = {}; scatterOptions.positions = positions; var markerSizeFunc = makeBubbleSizeFn(trace); var markerOpts = trace.marker; - //get basic symbol info + // get basic symbol info var multiMarker = Array.isArray(markerOpts.symbol); - var symbolNumber, isOpen, symbol, noFill; - if (!multiMarker) { - symbolNumber = Drawing.symbolNumber(markerOpts.symbol); + var isOpen, symbol; + if(!multiMarker) { isOpen = /-open/.test(markerOpts.symbol); - noFill = !!Drawing.symbolNoFill[symbolNumber % 100]; } - //prepare colors - if (multiMarker || Array.isArray(markerOpts.color) || Array.isArray(markerOpts.line.color) || Array.isArray(markerOpts.line)) { + // prepare colors + if(multiMarker || Array.isArray(markerOpts.color) || Array.isArray(markerOpts.line.color) || Array.isArray(markerOpts.line)) { scatterOptions.colors = new Array(count); scatterOptions.borderColors = new Array(count); var colors = formatColor(markerOpts, markerOpts.opacity, count); var borderColors = formatColor(markerOpts.line, markerOpts.opacity, count); - if (!Array.isArray(borderColors[0])) { + if(!Array.isArray(borderColors[0])) { var borderColor = borderColors; borderColors = Array(count); - for (i = 0; i < count; i++) { + for(i = 0; i < count; i++) { borderColors[i] = borderColor; } } - if (!Array.isArray(colors[0])) { + if(!Array.isArray(colors[0])) { var color = colors; colors = Array(count); - for (i = 0; i < count; i++) { + for(i = 0; i < count; i++) { colors[i] = color; } } @@ -289,12 +284,12 @@ ScatterRegl.calc = function calc(container, trace) { scatterOptions.colors = colors; scatterOptions.borderColors = borderColors; - for (i = 0; i < count; i++) { - if (multiMarker) { + for(i = 0; i < count; i++) { + if(multiMarker) { symbol = markerOpts.symbol[i]; isOpen = /-open/.test(symbol); } - if (isOpen) { + if(isOpen) { borderColors[i] = colors[i].slice(); colors[i] = colors[i].slice(); colors[i][3] = 0; @@ -308,31 +303,31 @@ ScatterRegl.calc = function calc(container, trace) { scatterOptions.borderColor = markerOpts.line.color; scatterOptions.opacity = trace.opacity * markerOpts.opacity; - if (isOpen) { + if(isOpen) { scatterOptions.borderColor = scatterOptions.color.slice(); scatterOptions.color = scatterOptions.color.slice(); scatterOptions.color[3] = 0; } } - //prepare markers - if (Array.isArray(markerOpts.symbol)) { + // prepare markers + if(Array.isArray(markerOpts.symbol)) { scatterOptions.markers = new Array(count); for(i = 0; i < count; ++i) { - scatterOptions.markers[i] = getSymbolSdf(markerOpts.symbol[i]) + scatterOptions.markers[i] = getSymbolSdf(markerOpts.symbol[i]); } } else { - scatterOptions.marker = getSymbolSdf(markerOpts.symbol) + scatterOptions.marker = getSymbolSdf(markerOpts.symbol); } - //prepare sizes + // prepare sizes if(Array.isArray(markerOpts.size) || Array.isArray(markerOpts.line.width)) { var size; var sizes = scatterOptions.sizes = new Array(count); var borderSizes = scatterOptions.borderSizes = new Array(count); - if (Array.isArray(markerOpts.size)) { + if(Array.isArray(markerOpts.size)) { for(i = 0; i < count; ++i) { sizes[i] = markerSizeFunc(markerOpts.size[i]); } @@ -345,13 +340,13 @@ ScatterRegl.calc = function calc(container, trace) { } // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798 - if (Array.isArray(markerOpts.line.width)) { + if(Array.isArray(markerOpts.line.width)) { for(i = 0; i < count; ++i) { - borderSizes[i] = markerOpts.line.width[i] * .5; + borderSizes[i] = markerOpts.line.width[i] * 0.5; } } else { - size = markerSizeFunc(markerOpts.line.width) * .5; + size = markerSizeFunc(markerOpts.line.width) * 0.5; for(i = 0; i < count; ++i) { borderSizes[i] = size; } @@ -359,14 +354,14 @@ ScatterRegl.calc = function calc(container, trace) { } else { scatterOptions.size = markerSizeFunc(markerOpts.size); - scatterOptions.borderSizes = markerOpts.line.width * .5; + scatterOptions.borderSizes = markerOpts.line.width * 0.5; } } // make sure scene exists var scene = subplot._scene; - if (!subplot._scene) { + if(!subplot._scene) { scene = subplot._scene = { count: 0, dirty: true, @@ -377,30 +372,30 @@ ScatterRegl.calc = function calc(container, trace) { errorYOptions: [] }; - scene.updateRange = function updateRange (range) { + scene.updateRange = function updateRange(range) { var opts = Array(scene.count); var rangeOpts = {range: range}; - for (var i = 0; i < scene.count; i++) { + for(var i = 0; i < scene.count; i++) { opts[i] = rangeOpts; } - 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(opts.concat(opts)); + 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(opts.concat(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.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.scatter2d.draw(i); + if(scene.scatter2d) scene.scatter2d.draw(i); } scene.dirty = false; @@ -408,7 +403,7 @@ ScatterRegl.calc = function calc(container, trace) { } // In case if we have scene from the last calc - reset data - if (!scene.dirty) { + if(!scene.dirty) { scene.dirty = true; scene.count = 0; scene.lineOptions = []; @@ -419,10 +414,10 @@ ScatterRegl.calc = function calc(container, trace) { } // mark renderers required for the data - if (!scene.error2d && hasError) scene.error2d = true; - if (!scene.line2d && hasLines) scene.line2d = true; - if (!scene.scatter2d && hasMarkers) scene.scatter2d = true; - if (!scene.fill2d && hasFill) scene.fill2d = true; + if(!scene.error2d && hasError) scene.error2d = true; + if(!scene.line2d && hasLines) scene.line2d = true; + if(!scene.scatter2d && hasMarkers) scene.scatter2d = true; + if(!scene.fill2d && hasFill) scene.fill2d = true; // save initial batch scene.lineOptions.push(lineOptions); @@ -435,13 +430,13 @@ ScatterRegl.calc = function calc(container, trace) { return [{x: false, y: false, t: stash, trace: trace}]; }; -//TODO: manages selection, range, viewport, that's it +// TODO: manages selection, range, viewport, that's it ScatterRegl.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; + if(!scene) return; var vpSize = layout._size, width = layout.width, height = layout.height; var regl = layout._glcanvas.data()[1].regl; @@ -449,40 +444,40 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { // that is needed for fills linkTraces(container, subplot, cdata); - if (scene.dirty) { + if(scene.dirty) { // make sure scenes are created - if (scene.error2d === true) { + if(scene.error2d === true) { scene.error2d = createError(regl); } - if (scene.line2d === true) { + if(scene.line2d === true) { scene.line2d = createLine(regl); } - if (scene.scatter2d === true) { + if(scene.scatter2d === true) { scene.scatter2d = createScatter(regl); } - if (scene.fill2d === true) { + if(scene.fill2d === true) { scene.fill2d = createLine(regl); } - if (scene.line2d) { + if(scene.line2d) { scene.line2d.update(scene.lineOptions); } - if (scene.error2d) { + if(scene.error2d) { var errorBatch = (scene.errorXOptions || []).concat(scene.errorYOptions || []); scene.error2d.update(errorBatch); } - if (scene.scatter2d) { + if(scene.scatter2d) { scene.scatter2d.update(scene.scatterOptions); } // 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(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 lineOptions = scene.lineOptions[i]; var pos = [], srcPos = (lineOptions && lineOptions.positions) || stash.positions; @@ -527,11 +522,10 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { } // provide viewport and range - var vpRange = cdata.map(function (cdscatter) { + 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 xaxis = Axes.getFromId(container, trace.xaxis || 'x'); var yaxis = Axes.getFromId(container, trace.yaxis || 'y'); @@ -553,33 +547,32 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { }); // uploat batch data to GPU - if (scene.fill2d) { + if(scene.fill2d) { scene.fill2d.update(vpRange); } - if (scene.line2d) { + if(scene.line2d) { scene.line2d.update(vpRange); } - if (scene.error2d) { + if(scene.error2d) { scene.error2d.update(vpRange.concat(vpRange)); } - if (scene.scatter2d) { + if(scene.scatter2d) { scene.scatter2d.update(vpRange); } scene.draw(); return; - cdata.map(function(cdscatter, order) { - - // TODO: update selection here - if(trace.selection && trace.selection.length) { - selIds = {}; - for(i = 0; i < trace.selection.length; i++) { - selIds[trace.selection[i].pointNumber] = true; - } - } - // TODO: recalculate fill area here since we can't calc connected traces beforehead - }); + // cdata.map(function(cdscatter, order) { + + // // TODO: update selection here + // if(trace.selection && trace.selection.length) { + // selIds = {}; + // for(var i = 0; i < trace.selection.length; i++) { + // selIds[trace.selection[i].pointNumber] = true; + // } + // } + // }); }; ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { @@ -614,7 +607,7 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { pointData.index = id; - if (id === undefined) return [pointData] + if(id === undefined) return [pointData]; // the closest data point var di = { @@ -628,7 +621,7 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { 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 + 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; @@ -728,7 +721,7 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { function getSymbolSdf(symbol) { - if(symbol === 'circle') return null + if(symbol === 'circle') return null; var symbolPath, symbolSdf; var symbolNumber = Drawing.symbolNumber(symbol); @@ -736,7 +729,6 @@ function getSymbolSdf(symbol) { var symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100]; var symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100]; - var isOpen = /-open/.test(symbol); var isDot = /-dot/.test(symbol); // get symbol sdf from cache or generate it From 2db2e1f20aea8ee5e2aa5b84cbf1f587c1f74dfa Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 26 Oct 2017 16:15:47 -0400 Subject: [PATCH 079/151] Fix expanding axes and selection --- src/plots/cartesian/select.js | 13 +- src/traces/scatterregl/index.js | 291 ++++++++++++++++++++------------ 2 files changed, 193 insertions(+), 111 deletions(-) diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index d8542f5c090..ee2d969c20c 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -184,11 +184,18 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { } selection = []; + + var traceSelections = [], traceSelection; for(i = 0; i < searchTraces.length; i++) { searchInfo = searchTraces[i]; - [].push.apply(selection, fillSelectionItem( - searchInfo.selectPoints(searchInfo, poly), searchInfo - )); + traceSelection = searchInfo.selectPoints(searchInfo, poly); + traceSelections.push(traceSelection); + selection = selection.concat(fillSelectionItem(traceSelection, searchInfo)); + } + + // update scatterregl scene + if (plotinfo._scene) { + plotinfo._scene.select(traceSelections); } eventData = {points: selection}; diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index b1d2d50ea18..1cdf5c6f418 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -49,6 +49,7 @@ ScatterRegl.calc = function calc(container, trace) { var stash = {}; var xaxis = Axes.getFromId(container, trace.xaxis); var yaxis = Axes.getFromId(container, trace.yaxis); + var markerOpts = trace.marker; // FIXME: find a better way to obtain subplot object from trace var subplot = layout._plots[trace.xaxis + trace.yaxis]; @@ -59,7 +60,6 @@ ScatterRegl.calc = function calc(container, trace) { var count = Math.max(x ? x.length : 0, y ? y.length : 0), i, l, xx, yy, ptrX = 0, ptrY = 0; var lineOptions, scatterOptions, errorXOptions, errorYOptions, fillOptions; - var selection = trace.selection; var isVisible, hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill; var linePositions; @@ -77,7 +77,6 @@ ScatterRegl.calc = function calc(container, trace) { // we need hi-precision for scatter2d positions = new Array(count * 2); - var xbounds = [Infinity, -Infinity], ybounds = [Infinity, -Infinity]; for(i = 0; i < count; i++) { // if no x defined, we are creating simple int sequence (API) @@ -85,39 +84,30 @@ ScatterRegl.calc = function calc(container, trace) { xx = x ? parseFloat(x[i]) : i; yy = y ? parseFloat(y[i]) : 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; - positions[i * 2] = xx; positions[i * 2 + 1] = yy; } - // calculate axes range - var pad = 20; - 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 }); - } - calcColorscales(trace); - // TODO: delegate this to webworker if possible (potential ) - stash.tree = kdtree(positions, 512); + // we don't build a tree for log axes since it takes long to convert log2px + // and it is also + if (count > 1e4 && 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; + } + } // stash data stash.x = x; stash.y = y; stash.positions = positions; + stash.count = count; if(trace.visible !== true) { isVisible = false; @@ -249,9 +239,6 @@ ScatterRegl.calc = function calc(container, trace) { scatterOptions = {}; scatterOptions.positions = positions; - var markerSizeFunc = makeBubbleSizeFn(trace); - var markerOpts = trace.marker; - // get basic symbol info var multiMarker = Array.isArray(markerOpts.symbol); var isOpen, symbol; @@ -321,8 +308,12 @@ ScatterRegl.calc = function calc(container, trace) { scatterOptions.marker = getSymbolSdf(markerOpts.symbol); } - // prepare sizes - if(Array.isArray(markerOpts.size) || Array.isArray(markerOpts.line.width)) { + // prepare sizes and expand axes + var multiSize = markerOpts && (Array.isArray(markerOpts.size) || Array.isArray(markerOpts.line.width)); + var msx, msy, xbounds = [Infinity, -Infinity], ybounds = [Infinity, -Infinity]; + var markerSizeFunc = makeBubbleSizeFn(trace); + + if(multiSize) { var size; var sizes = scatterOptions.sizes = new Array(count); var borderSizes = scatterOptions.borderSizes = new Array(count); @@ -351,25 +342,66 @@ ScatterRegl.calc = function calc(container, trace) { borderSizes[i] = size; } } + + // FIXME: this slows down big number of points + Axes.expand(xaxis, trace.x, { padded: true, ppad: sizes }); + Axes.expand(yaxis, trace.y, { padded: true, ppad: sizes }); } else { - scatterOptions.size = markerSizeFunc(markerOpts.size); + scatterOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); scatterOptions.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; + } + + // update axes fast + var pad = scatterOptions.size; + if(xaxis._min && !xaxis._min.length) { + xaxis._min.push({ val: xbounds[0], pad: pad }); + } + if(xaxis._max && !xaxis._max.length) { + xaxis._max.push({ val: xbounds[1], pad: pad }); + } + if(yaxis._min && !yaxis._min.length) { + yaxis._min.push({ val: ybounds[0], pad: pad }); + } + if(yaxis._max && !yaxis._max.length) { + yaxis._max.push({ val: ybounds[1], pad: pad }); + } } } + // 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: [], scatterOptions: [], errorXOptions: [], - errorYOptions: [] + errorYOptions: [], + + // regl- component stubs, initialized in dirty plot call + fill2d: hasFill, + scatter2d: hasMarkers, + error2d: hasError, + line2d: hasLines, + select2d: null }; scene.updateRange = function updateRange(range) { @@ -400,6 +432,27 @@ ScatterRegl.calc = function calc(container, trace) { scene.dirty = false; }; + + // highlight selected points + scene.select = function select(selection) { + if (!scene.select2d) return; + + scene.select2d.regl.clear({color: true}); + + if (!selection.length) return; + + var options = selection.map(function (points) { + if (!points || !points.length) return null; + + var elements = Array(points.length); + for (var i = 0; i < points.length; i++) { + elements[i] = points[i].pointNumber; + } + return elements + }); + + scene.select2d.draw(options); + } } // In case if we have scene from the last calc - reset data @@ -413,12 +466,6 @@ ScatterRegl.calc = function calc(container, trace) { scene.errorYOptions = []; } - // mark renderers required for the data - if(!scene.error2d && hasError) scene.error2d = true; - if(!scene.line2d && hasLines) scene.line2d = true; - if(!scene.scatter2d && hasMarkers) scene.scatter2d = true; - if(!scene.fill2d && hasFill) scene.fill2d = true; - // save initial batch scene.lineOptions.push(lineOptions); scene.errorXOptions.push(errorXOptions); @@ -427,10 +474,46 @@ ScatterRegl.calc = function calc(container, trace) { scene.scatterOptions.push(scatterOptions); scene.count++; + //stash scene ref + stash.scene = scene; + return [{x: false, y: false, t: stash, trace: trace}]; }; -// TODO: manages selection, range, viewport, that's it + +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/.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; +} + + ScatterRegl.plot = function plot(container, subplot, cdata) { var layout = container._fullLayout; var scene = subplot._scene; @@ -521,11 +604,30 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { } } + // make sure selection layer is initialized if we require selection + var dragmode = layout.dragmode; + if (dragmode === 'lasso' || dragmode === 'select') { + if(!scene.select2d && scene.scatter2d) { + var selectRegl = layout._glcanvas.data()[0].regl; + + scene.select2d = createScatter(selectRegl); + scene.select2d.update(scene.scatterOptions); + } + + // adjust selection transparency via canvas opacity + scene.scatter2d.canvas.style.opacity = DESELECTDIM; + } + else { + if (scene.select2d) scene.select2d.regl.clear({color: true}); + scene.scatter2d.canvas.style.opacity = 1; + } + // 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 xaxis = Axes.getFromId(container, trace.xaxis || 'x'); var yaxis = Axes.getFromId(container, trace.yaxis || 'y'); @@ -540,6 +642,20 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { (height - vpSize.t) - (1 - yaxis.domain[1]) * vpSize.h ]; + if (dragmode === 'lasso' || dragmode === 'select') { + //precalculate px coords since we are not going to pan during select + var xpx = Array(stash.count), ypx = Array(stash.count); + for(var i = 0; i < stash.count; i++) { + xpx[i] = xaxis.c2p(trace.x[i]); + ypx[i] = yaxis.c2p(trace.y[i]); + } + stash.xpx = xpx; + stash.ypx = ypx; + } + else { + stash.xpx = stash.ypx = null; + } + return { viewport: viewport, range: range @@ -559,22 +675,16 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { if(scene.scatter2d) { scene.scatter2d.update(vpRange); } + if (scene.select2d) { + scene.select2d.update(vpRange); + } scene.draw(); return; - // cdata.map(function(cdscatter, order) { - - // // TODO: update selection here - // if(trace.selection && trace.selection.length) { - // selIds = {}; - // for(var i = 0; i < trace.selection.length; i++) { - // selIds[trace.selection[i].pointNumber] = true; - // } - // } - // }); }; + ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { var cd = pointData.cd, stash = cd[0].t, @@ -584,20 +694,27 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { positions = stash.positions, x = stash.x, y = stash.y, - // hoveron = stash.hoveron || '', - tree = stash.tree; - - if(!tree) return [pointData]; + xpx = xa.c2p(xval), + ypx = xa.c2p(yval), + ids; // FIXME: make sure this is a proper way to calc search radius - var ids = tree.within(xval, yval, MAXDIST / xa._m); + if (stash.tree) { + ids = stash.tree.within(xval, yval, MAXDIST / xa._m); + } + else if (stash.ids) { + ids = stash.ids; + } + else return [pointData]; // pick the id closest to the point var min = MAXDIST, id = ids[0], ptx, pty; + for(var i = 0; i < ids.length; i++) { - ptx = positions[ids[i] * 2]; - pty = positions[ids[i] * 2 + 1]; - var dx = ptx - xval, dy = pty - yval; + ptx = trace.x[ids[i]]; + pty = trace.y[ids[i]]; + var dx = xa.c2p(ptx) - xpx, dy = xa.c2p(pty) - ypx; + var dist = Math.sqrt(dx * dx + dy * dy); if(dist < min) { min = dist; @@ -673,81 +790,39 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { return [pointData]; }; + ScatterRegl.selectPoints = function select(searchInfo, polygon) { var cd = searchInfo.cd, xa = searchInfo.xaxis, ya = searchInfo.yaxis, selection = [], trace = cd[0].trace, - i, - di, - x, - y; + stash = cd[0].t; - var scene = cd[0] && cd[0].trace && cd[0].trace._scene; + var scene = stash.scene; - if(!scene) return; + if(!scene) return selection; var hasOnlyLines = (!subTypes.hasMarkers(trace) && !subTypes.hasText(trace)); - if(trace.visible !== true || hasOnlyLines) return; + if(trace.visible !== true || hasOnlyLines) return selection; // filter out points by visible scatter ones if(polygon === false) { // clear selection + console.log('clear') 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])) { + for(var i = 0; i < stash.count; i++) { + if(polygon.contains([stash.xpx[i], stash.ypx[i]])) { selection.push({ pointNumber: i, - x: di.x, - y: di.y + x: trace.x[i], + y: trace.y[i] }); - di.dim = 0; } - else di.dim = 1; } } - trace.selection = selection; - scene([cd]); - return selection; }; - - -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/.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; -} From 3d4a3f1fad5da1f2d6a9416306f22aa773138e84 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 26 Oct 2017 17:29:01 -0400 Subject: [PATCH 080/151] Merge multiple polygons --- package.json | 1 + src/lib/polygon.js | 39 ++++++++++++++++++ src/plots/cartesian/dragbox.js | 50 +++++++++++++++++++++-- src/plots/cartesian/select.js | 72 ++++++++++++++++++++++----------- src/traces/scatterregl/index.js | 14 ++++--- 5 files changed, 142 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 16213318a6e..e7e9d9419e1 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "ndarray-homography": "^1.0.0", "ndarray-ops": "^1.2.2", "object-assign": "^4.1.1", + "poly-bool": "^1.0.0", "regl": "^1.3.0", "regl-line2d": "^1.1.1", "regl-scatter2d": "^1.0.3", diff --git a/src/lib/polygon.js b/src/lib/polygon.js index d30d1fda104..f358e81dd66 100644 --- a/src/lib/polygon.js +++ b/src/lib/polygon.js @@ -31,6 +31,8 @@ var polygon = module.exports = {}; * returns boolean: is pt inside the polygon (including on its edges) */ polygon.tester = function tester(ptsIn) { + if(Array.isArray(ptsIn[0][0])) return polygon.multitester(ptsIn); + var pts = ptsIn.slice(), xmin = pts[0][0], xmax = xmin, @@ -160,6 +162,43 @@ polygon.tester = function tester(ptsIn) { }; }; +/** + * Test multiple polygons + */ +polygon.multitester = function multitester(list) { + var testers = [], + xmin = list[0][0][0], + xmax = xmin, + ymin = list[0][0][1], + ymax = ymin; + + for(var i = 0; i < list.length; i++) { + var tester = polygon.tester(list[i]); + testers.push(tester); + xmin = Math.min(xmin, tester.xmin); + xmax = Math.max(xmax, tester.xmax); + ymin = Math.min(ymin, tester.ymin); + ymax = Math.max(ymax, tester.ymax); + } + + function contains(pt, arg) { + for(var i = 0; i < testers.length; i++) { + if(testers[i].contains(pt, arg)) return true; + } + return false; + } + + return { + xmin: xmin, + xmax: xmax, + ymin: ymin, + ymax: ymax, + pts: [], + contains: contains, + isRect: false + }; +}; + /** * Test if a segment of a points array is bent or straight * diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 69aae336e78..0cbdc65131b 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -140,7 +140,10 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { // to pan (or to zoom if it already is pan) on shift if(e.shiftKey) { if(dragModeNow === 'pan') dragModeNow = 'zoom'; - else dragModeNow = 'pan'; + else if(!isSelectOrLasso(dragModeNow)) dragModeNow = 'pan'; + } + else if(e.ctrlKey) { + dragModeNow = 'pan'; } } // all other draggers just pan @@ -168,6 +171,19 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { else if(isSelectOrLasso(dragModeNow)) { dragOptions.xaxes = xa; dragOptions.yaxes = ya; + + // take over selection polygons from prev mode, if any + if(e.shiftKey && plotinfo.selection.polygons && !dragOptions.polygons) { + dragOptions.polygons = plotinfo.selection.polygons; + dragOptions.mergedPolygons = plotinfo.selection.mergedPolygons; + } + // create new polygons, if shift mode + else if(!e.shiftKey || (e.shiftKey && !plotinfo.selection.polygons)) { + plotinfo.selection = {}; + plotinfo.selection.polygons = dragOptions.polygons = []; + dragOptions.mergedPolygons = plotinfo.selection.mergedPolygons = []; + } + prepSelect(e, startX, startY, dragOptions, dragModeNow); } } @@ -175,6 +191,11 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { dragElement.init(dragOptions); + // FIXME: this hack highlights selection once we enter select/lasso mode + if(isSelectOrLasso(gd._fullLayout.dragmode) && plotinfo.selection) { + showSelect(zoomlayer, dragOptions); + } + var x0, y0, box, @@ -927,6 +948,29 @@ function clearSelect(zoomlayer) { zoomlayer.selectAll('.select-outline').remove(); } +function showSelect(zoomlayer, dragOptions) { + var outlines = zoomlayer.selectAll('path.select-outline').data([1, 2]), + plotinfo = dragOptions.plotinfo, + xaxis = plotinfo.xaxis, + yaxis = plotinfo.yaxis, + selection = plotinfo.selection, + polygons = selection.mergedPolygons, + xs = xaxis._offset, + ys = yaxis._offset, + paths = []; + + for(var i = 0; i < polygons.length; i++) { + var ppts = polygons[i]; + paths.push(ppts.join('L') + 'L' + ppts[0]); + } + + outlines.enter() + .append('path') + .attr('class', function(d) { return 'select-outline select-outline-' + d; }) + .attr('transform', 'translate(' + xs + ', ' + ys + ')') + .attr('d', 'M' + paths.join('M') + 'Z'); +} + function updateZoombox(zb, corners, box, path0, dimmed, lum) { zb.attr('d', path0 + 'M' + (box.l) + ',' + (box.t) + 'v' + (box.h) + @@ -949,9 +993,7 @@ function removeZoombox(gd) { } function isSelectOrLasso(dragmode) { - var modes = ['lasso', 'select']; - - return modes.indexOf(dragmode) !== -1; + return dragmode === 'lasso' || dragmode === 'select'; } function xCorners(box, y0) { diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index ee2d969c20c..85edfcbfe43 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -9,6 +9,7 @@ 'use strict'; +var polybool = require('poly-bool'); var polygon = require('../../lib/polygon'); var color = require('../../components/color'); var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue; @@ -18,6 +19,7 @@ var constants = require('./constants'); var filteredPolygon = polygon.filter; var polygonTester = polygon.tester; +var multipolygonTester = polygon.multitester; var MINSELECT = constants.MINSELECT; function getAxId(ax) { return ax._id; } @@ -38,10 +40,10 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { xAxisIds = dragOptions.xaxes.map(getAxId), yAxisIds = dragOptions.yaxes.map(getAxId), allAxes = dragOptions.xaxes.concat(dragOptions.yaxes), - pts; + filterPoly, testPoly, mergedPolygons, currentPolygon; if(mode === 'lasso') { - pts = filteredPolygon([[x0, y0]], constants.BENDPX); + filterPoly = filteredPolygon([[x0, y0]], constants.BENDPX); } var outlines = zoomLayer.selectAll('path.select-outline').data([1, 2]); @@ -115,34 +117,35 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { fillRangeItems = plotinfo.fillRangeItems; } else { if(mode === 'select') { - fillRangeItems = function(eventData, poly) { + //FIXME: this is regression + fillRangeItems = function(eventData, currentPolygon) { var ranges = eventData.range = {}; for(i = 0; i < allAxes.length; i++) { var ax = allAxes[i]; var axLetter = ax._id.charAt(0); + var x = axLetter === 'x'; + //FIXME: this should be fixed to read xmin/xmax, ymin/ymax ranges[ax._id] = [ - ax.p2d(poly[axLetter + 'min']), - ax.p2d(poly[axLetter + 'max']) + ax.p2d(currentPolygon[0][x ? 0 : 1]), + ax.p2d(currentPolygon[2][x ? 0 : 1]) ].sort(ascending); } }; } else { - fillRangeItems = function(eventData, poly, pts) { + fillRangeItems = function(eventData, currentPolygon, filterPoly) { var dataPts = eventData.lassoPoints = {}; for(i = 0; i < allAxes.length; i++) { var ax = allAxes[i]; - dataPts[ax._id] = pts.filtered.map(axValue(ax)); + dataPts[ax._id] = filterPoly.filtered.map(axValue(ax)); } }; } } dragOptions.moveFn = function(dx0, dy0) { - var poly; - x1 = Math.max(0, Math.min(pw, dx0 + x0)); y1 = Math.max(0, Math.min(ph, dy0 + y0)); @@ -152,43 +155,58 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { if(mode === 'select') { if(dy < Math.min(dx * 0.6, MINSELECT)) { // horizontal motion: make a vertical box - poly = polygonTester([[x0, 0], [x0, ph], [x1, ph], [x1, 0]]); + currentPolygon = [[x0, 0], [x0, ph], [x1, ph], [x1, 0]]; // extras to guide users in keeping a straight selection - corners.attr('d', 'M' + poly.xmin + ',' + (y0 - MINSELECT) + + corners.attr('d', 'M' + Math.min(x0, x1) + ',' + (y0 - MINSELECT) + 'h-4v' + (2 * MINSELECT) + 'h4Z' + - 'M' + (poly.xmax - 1) + ',' + (y0 - MINSELECT) + + 'M' + (Math.max(x0, x1) - 1) + ',' + (y0 - MINSELECT) + 'h4v' + (2 * MINSELECT) + 'h-4Z'); } else if(dx < Math.min(dy * 0.6, MINSELECT)) { // vertical motion: make a horizontal box - poly = polygonTester([[0, y0], [0, y1], [pw, y1], [pw, y0]]); - corners.attr('d', 'M' + (x0 - MINSELECT) + ',' + poly.ymin + + currentPolygon = [[0, y0], [0, y1], [pw, y1], [pw, y0]]; + corners.attr('d', 'M' + (x0 - MINSELECT) + ',' + Math.min(y0, y1) + 'v-4h' + (2 * MINSELECT) + 'v4Z' + - 'M' + (x0 - MINSELECT) + ',' + (poly.ymax - 1) + + 'M' + (x0 - MINSELECT) + ',' + (Math.max(y0, y1) - 1) + 'v4h' + (2 * MINSELECT) + 'v-4Z'); } else { // diagonal motion - poly = polygonTester([[x0, y0], [x0, y1], [x1, y1], [x1, y0]]); + currentPolygon = [[x0, y0], [x0, y1], [x1, y1], [x1, y0]]; corners.attr('d', 'M0,0Z'); } - outlines.attr('d', 'M' + poly.xmin + ',' + poly.ymin + - 'H' + (poly.xmax - 1) + 'V' + (poly.ymax - 1) + - 'H' + poly.xmin + 'Z'); } else if(mode === 'lasso') { - pts.addPt([x1, y1]); - poly = polygonTester(pts.filtered); - outlines.attr('d', 'M' + pts.filtered.join('L') + 'Z'); + filterPoly.addPt([x1, y1]); + currentPolygon = filterPoly.filtered; + } + + // create outline & tester + if(dragOptions.polygons.length) { + mergedPolygons = polybool(dragOptions.mergedPolygons, [currentPolygon], 'or'); + testPoly = multipolygonTester(dragOptions.polygons.concat([currentPolygon])); + } + else { + mergedPolygons = [currentPolygon]; + testPoly = polygonTester(currentPolygon); } + // draw selection + var paths = []; + for(i = 0; i < mergedPolygons.length; i++) { + var ppts = mergedPolygons[i]; + paths.push(ppts.join('L') + 'L' + ppts[0]); + } + outlines.attr('d', 'M' + paths.join('M') + 'Z'); + + // select points selection = []; var traceSelections = [], traceSelection; for(i = 0; i < searchTraces.length; i++) { searchInfo = searchTraces[i]; - traceSelection = searchInfo.selectPoints(searchInfo, poly); + traceSelection = searchInfo.selectPoints(searchInfo, testPoly); traceSelections.push(traceSelection); selection = selection.concat(fillSelectionItem(traceSelection, searchInfo)); } @@ -199,7 +217,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { } eventData = {points: selection}; - fillRangeItems(eventData, poly, pts); + fillRangeItems(eventData, currentPolygon, filterPoly); dragOptions.gd.emit('plotly_selecting', eventData); }; @@ -218,6 +236,12 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { else { dragOptions.gd.emit('plotly_selected', eventData); } + + // save last polygons + dragOptions.polygons.push(currentPolygon); + + // we have to keep reference to arrays, therefore just replace items + dragOptions.mergedPolygons.splice.apply(dragOptions.mergedPolygons, [0, dragOptions.mergedPolygons.length].concat(mergedPolygons)); }; }; diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 1cdf5c6f418..62bf3399880 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -26,9 +26,9 @@ var createScatter = require('regl-scatter2d'); var createLine = require('regl-line2d'); var createError = require('regl-error2d'); var svgSdf = require('svg-path-sdf'); +var DESELECTDIM = require('../../constants/interactions').DESELECTDIM; var MAXDIST = Fx.constants.MAXDIST; -var DESELECTDIM = 0.2; var SYMBOL_SDF_SIZE = 200; var SYMBOL_SIZE = 20; var SYMBOL_STROKE = SYMBOL_SIZE / 20; @@ -522,7 +522,7 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { if(!scene) return; var vpSize = layout._size, width = layout.width, height = layout.height; - var regl = layout._glcanvas.data()[1].regl; + var regl = layout._glcanvas.data()[0].regl; // that is needed for fills linkTraces(container, subplot, cdata); @@ -608,14 +608,13 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { var dragmode = layout.dragmode; if (dragmode === 'lasso' || dragmode === 'select') { if(!scene.select2d && scene.scatter2d) { - var selectRegl = layout._glcanvas.data()[0].regl; + var selectRegl = layout._glcanvas.data()[1].regl; scene.select2d = createScatter(selectRegl); + + //TODO: modify options here according to the proposed selection options scene.select2d.update(scene.scatterOptions); } - - // adjust selection transparency via canvas opacity - scene.scatter2d.canvas.style.opacity = DESELECTDIM; } else { if (scene.select2d) scene.select2d.regl.clear({color: true}); @@ -822,6 +821,9 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { }); } } + + // adjust selection transparency via canvas opacity + scene.scatter2d.canvas.style.opacity = DESELECTDIM; } return selection; From b9d6538714c5c1bd90cfc49f82e1eac5803d8fbb Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 26 Oct 2017 17:32:17 -0400 Subject: [PATCH 081/151] Fix undefined scatter2d --- src/traces/scatterregl/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 62bf3399880..648f6f43361 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -618,7 +618,7 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { } else { if (scene.select2d) scene.select2d.regl.clear({color: true}); - scene.scatter2d.canvas.style.opacity = 1; + if (scene.scatter2d) scene.scatter2d.canvas.style.opacity = 1; } // provide viewport and range @@ -823,7 +823,7 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { } // adjust selection transparency via canvas opacity - scene.scatter2d.canvas.style.opacity = DESELECTDIM; + if (scene.scatter2d) scene.scatter2d.canvas.style.opacity = DESELECTDIM; } return selection; From b870e7bfa0cfb25a57be84fa4b59b198f287346a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 26 Oct 2017 17:42:31 -0400 Subject: [PATCH 082/151] Detect degenerate polygon --- src/lib/polygon.js | 13 ++++++++++++- src/traces/scatterregl/index.js | 9 ++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/lib/polygon.js b/src/lib/polygon.js index f358e81dd66..a5f8e7fb789 100644 --- a/src/lib/polygon.js +++ b/src/lib/polygon.js @@ -151,6 +151,16 @@ polygon.tester = function tester(ptsIn) { return crossings % 2 === 1; } + // detect if poly is degenerate + var degenerate = true; + var lastPt = pts[0]; + for(var i = 1; i < pts.length; i++) { + if(lastPt[0] !== pts[i][0] || lastPt[1] !== pts[i][1]) { + degenerate = false; + break; + } + } + return { xmin: xmin, xmax: xmax, @@ -158,7 +168,8 @@ polygon.tester = function tester(ptsIn) { ymax: ymax, pts: pts, contains: isRect ? rectContains : contains, - isRect: isRect + isRect: isRect, + degenerate: degenerate }; }; diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 648f6f43361..1e46155d7c7 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -805,12 +805,11 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { var hasOnlyLines = (!subTypes.hasMarkers(trace) && !subTypes.hasText(trace)); if(trace.visible !== true || hasOnlyLines) return selection; - // filter out points by visible scatter ones - if(polygon === false) { - // clear selection - console.log('clear') - for(i = 0; i < cd.length; i++) cd[i].dim = 0; + // degenerate polygon does not enable selection + if(polygon === false || polygon.degenerate) { + if (scene.scatter2d) scene.scatter2d.canvas.style.opacity = 1; } + // filter out points by visible scatter ones else { for(var i = 0; i < stash.count; i++) { if(polygon.contains([stash.xpx[i], stash.ypx[i]])) { From ef6cfc4af13136c661f747d18ad0436f6a4ff38f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 26 Oct 2017 18:33:11 -0400 Subject: [PATCH 083/151] Fix tree picking --- src/traces/scatterregl/index.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index 1e46155d7c7..c1ecbd82efd 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -92,7 +92,7 @@ ScatterRegl.calc = function calc(container, trace) { // we don't build a tree for log axes since it takes long to convert log2px // and it is also - if (count > 1e4 && xaxis.type !== 'log' && yaxis.type !== 'log') { + if (xaxis.type !== 'log' && yaxis.type !== 'log') { // FIXME: delegate this to webworker stash.tree = kdtree(positions, 512); } @@ -694,11 +694,17 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { x = stash.x, y = stash.y, xpx = xa.c2p(xval), - ypx = xa.c2p(yval), + ypx = ya.c2p(yval), ids; // FIXME: make sure this is a proper way to calc search radius if (stash.tree) { + // ids = stash.tree.range( + // xval - MAXDIST / xa._m, yval - MAXDIST / ya._m, + // xval + MAXDIST / xa._m, yval + MAXDIST / ya._m + // ); + + //FIXME: this works only for the case of linear points ids = stash.tree.within(xval, yval, MAXDIST / xa._m); } else if (stash.ids) { @@ -707,12 +713,13 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { else return [pointData]; // pick the id closest to the point - var min = MAXDIST, id = ids[0], ptx, pty; + // note that point possibly may not be found + var min = MAXDIST, id, ptx, pty; for(var i = 0; i < ids.length; i++) { ptx = trace.x[ids[i]]; pty = trace.y[ids[i]]; - var dx = xa.c2p(ptx) - xpx, dy = xa.c2p(pty) - ypx; + var dx = xa.c2p(ptx) - xpx, dy = ya.c2p(pty) - ypx; var dist = Math.sqrt(dx * dx + dy * dy); if(dist < min) { @@ -727,8 +734,8 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { // the closest data point var di = { - x: x[id], - y: y[id] + x: trace.x[id], + y: trace.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 From e62ebb507c5cd8b28869bce783fc94b2384d50fb Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 26 Oct 2017 19:01:07 -0400 Subject: [PATCH 084/151] Fix overlapping deselected markers opacity --- src/plots/cartesian/dragbox.js | 6 +++--- src/traces/scatterregl/index.js | 30 ++++++++++++++++++++++++------ 2 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 0cbdc65131b..3975bd55d4e 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -750,12 +750,12 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { editY2 = editY && !ya2.fixedrange && (ya.indexOf(ya2) !== -1); // scattergl translate - if(subplot._scene && subplot._scene.updateRange) { + if(subplot._scene && subplot._scene.update) { // FIXME: possibly we could update axis internal _r and _rl here var xaRange = Lib.simpleMap(xa2.range, xa2.r2l), yaRange = Lib.simpleMap(ya2.range, ya2.r2l); - subplot._scene.updateRange( - [xaRange[0], yaRange[0], xaRange[1], yaRange[1]] + subplot._scene.update( + {range: [xaRange[0], yaRange[0], xaRange[1], yaRange[1]]} ); } diff --git a/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js index c1ecbd82efd..302b9a0deda 100644 --- a/src/traces/scatterregl/index.js +++ b/src/traces/scatterregl/index.js @@ -404,11 +404,11 @@ ScatterRegl.calc = function calc(container, trace) { select2d: null }; - scene.updateRange = function updateRange(range) { + // apply new option to all regl components + scene.update = function update(opt) { var opts = Array(scene.count); - var rangeOpts = {range: range}; for(var i = 0; i < scene.count; i++) { - opts[i] = rangeOpts; + opts[i] = opt; } if(scene.fill2d) scene.fill2d.update(opts); if(scene.scatter2d) scene.scatter2d.update(opts); @@ -452,6 +452,10 @@ ScatterRegl.calc = function calc(container, trace) { }); scene.select2d.draw(options); + + //FIXME: this can be a strong guess, possibly we can redraw scene once + scene.scatter2d.regl.clear({color: true}); + scene.draw(); } } @@ -618,7 +622,6 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { } else { if (scene.select2d) scene.select2d.regl.clear({color: true}); - if (scene.scatter2d) scene.scatter2d.canvas.style.opacity = 1; } // provide viewport and range @@ -653,6 +656,13 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { } else { stash.xpx = stash.ypx = null; + + //reset opacities + if (scene.scatter2d) { + scene.scatter2d.update(scene.scatterOptions.map(function (opt) { + return {opacity: opt.opacity} + })); + } } return { @@ -814,7 +824,11 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { // degenerate polygon does not enable selection if(polygon === false || polygon.degenerate) { - if (scene.scatter2d) scene.scatter2d.canvas.style.opacity = 1; + if (scene.scatter2d) { + scene.scatter2d.update(scene.scatterOptions.map(function (opt) { + return {opacity: opt.opacity} + })); + }; } // filter out points by visible scatter ones else { @@ -829,7 +843,11 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { } // adjust selection transparency via canvas opacity - if (scene.scatter2d) scene.scatter2d.canvas.style.opacity = DESELECTDIM; + if (scene.scatter2d) { + scene.scatter2d.update(scene.scatterOptions.map(function (opt) { + return {opacity: opt.opacity * DESELECTDIM} + })); + }; } return selection; From 6525be136042ff057b2ace1b9aa5f8eb0eb126d4 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 27 Oct 2017 11:58:21 -0400 Subject: [PATCH 085/151] Replace scattergl with scatterregl --- lib/index.js | 1 - lib/scatterregl.js | 11 - src/traces/pointcloud/attributes.js | 2 +- src/traces/scattergl/attributes.js | 80 --- src/traces/scattergl/calc.js | 43 -- src/traces/scattergl/convert.js | 768 ------------------------ src/traces/scattergl/defaults.js | 55 -- src/traces/scattergl/index.js | 866 +++++++++++++++++++++++++++- src/traces/scattergl/select.js | 60 -- src/traces/scatterregl/index.js | 854 --------------------------- 10 files changed, 843 insertions(+), 1897 deletions(-) delete mode 100644 lib/scatterregl.js delete mode 100644 src/traces/scattergl/attributes.js delete mode 100644 src/traces/scattergl/calc.js delete mode 100644 src/traces/scattergl/convert.js delete mode 100644 src/traces/scattergl/defaults.js delete mode 100644 src/traces/scattergl/select.js delete mode 100644 src/traces/scatterregl/index.js diff --git a/lib/index.js b/lib/index.js index 057e38fc444..140b8030885 100644 --- a/lib/index.js +++ b/lib/index.js @@ -31,7 +31,6 @@ Plotly.register([ require('./choropleth'), require('./scattergl'), - require('./scatterregl'), require('./pointcloud'), require('./heatmapgl'), require('./parcoords'), diff --git a/lib/scatterregl.js b/lib/scatterregl.js deleted file mode 100644 index 05a7a88ad7f..00000000000 --- a/lib/scatterregl.js +++ /dev/null @@ -1,11 +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'; - -module.exports = require('../src/traces/scatterregl'); diff --git a/src/traces/pointcloud/attributes.js b/src/traces/pointcloud/attributes.js index 3c3d76277c4..e4f78d7a48f 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 deleted file mode 100644 index a0e1347551d..00000000000 --- a/src/traces/scattergl/attributes.js +++ /dev/null @@ -1,80 +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 scatterAttrs = require('../scatter/attributes'); -var colorAttributes = require('../../components/colorscale/color_attributes'); - -var DASHES = require('../../constants/gl2d_dashes'); -var extendFlat = require('../../lib/extend').extendFlat; -var extendDeep = require('../../lib/extend').extendDeep; - -var scatterLineAttrs = scatterAttrs.line, - scatterMarkerAttrs = scatterAttrs.marker, - scatterMarkerLineAttrs = scatterMarkerAttrs.line; - -module.exports = { - x: scatterAttrs.x, - x0: scatterAttrs.x0, - dx: scatterAttrs.dx, - y: scatterAttrs.y, - y0: scatterAttrs.y0, - dy: scatterAttrs.dy, - - text: extendFlat({}, scatterAttrs.text, { - description: [ - 'Sets text elements associated with each (x,y) pair to appear on hover.', - 'If a single string, the same string appears over', - 'all the data points.', - 'If an array of string, the items are mapped in order to the', - 'this trace\'s (x,y) coordinates.' - ].join(' ') - }), - mode: { - valType: 'flaglist', - flags: ['lines', 'markers'], - extras: ['none'], - role: 'info', - description: [ - 'Determines the drawing mode for this scatter trace.' - ].join(' ') - }, - line: { - color: scatterLineAttrs.color, - width: scatterLineAttrs.width, - dash: { - valType: 'enumerated', - values: Object.keys(DASHES), - dflt: 'solid', - role: 'style', - description: 'Sets the style of the lines.' - } - }, - marker: extendDeep({}, colorAttributes('marker'), { - symbol: scatterMarkerAttrs.symbol, - size: scatterMarkerAttrs.size, - sizeref: scatterMarkerAttrs.sizeref, - sizemin: scatterMarkerAttrs.sizemin, - sizemode: scatterMarkerAttrs.sizemode, - opacity: scatterMarkerAttrs.opacity, - showscale: scatterMarkerAttrs.showscale, - colorbar: scatterMarkerAttrs.colorbar, - line: extendDeep({}, colorAttributes('marker.line'), { - width: scatterMarkerLineAttrs.width - }) - }), - connectgaps: scatterAttrs.connectgaps, - fill: extendFlat({}, scatterAttrs.fill, { - values: ['none', 'tozeroy', 'tozerox'] - }), - fillcolor: scatterAttrs.fillcolor, - - error_y: scatterAttrs.error_y, - error_x: scatterAttrs.error_x -}; 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 892b573960b..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 deleted file mode 100644 index 442363ae113..00000000000 --- a/src/traces/scattergl/defaults.js +++ /dev/null @@ -1,55 +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 Lib = require('../../lib'); - -var constants = require('../scatter/constants'); -var subTypes = require('../scatter/subtypes'); -var handleXYDefaults = require('../scatter/xy_defaults'); -var handleMarkerDefaults = require('../scatter/marker_defaults'); -var handleLineDefaults = require('../scatter/line_defaults'); -var handleFillColorDefaults = require('../scatter/fillcolor_defaults'); -var errorBarsSupplyDefaults = require('../../components/errorbars/defaults'); - -var attributes = require('./attributes'); - - -module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { - function coerce(attr, dflt) { - return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); - } - - var len = handleXYDefaults(traceIn, traceOut, layout, coerce); - if(!len) { - traceOut.visible = false; - return; - } - - coerce('text'); - coerce('mode', len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'); - - if(subTypes.hasLines(traceOut)) { - coerce('connectgaps'); - handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); - } - - if(subTypes.hasMarkers(traceOut)) { - handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce); - } - - coerce('fill'); - if(traceOut.fill !== 'none') { - handleFillColorDefaults(traceIn, traceOut, defaultColor, coerce); - } - - errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'y'}); - errorBarsSupplyDefaults(traceIn, traceOut, defaultColor, {axis: 'x', inherit: 'y'}); -}; diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 6ad0666d16a..302b9a0deda 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -8,29 +8,847 @@ 'use strict'; -var ScatterGl = {}; - -ScatterGl.attributes = require('./attributes'); -ScatterGl.supplyDefaults = require('./defaults'); -ScatterGl.colorbar = require('../scatter/colorbar'); -ScatterGl.hoverPoints = require('../scatter/hover'); - -// 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(' ') +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 DESELECTDIM = require('../../constants/interactions').DESELECTDIM; + +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 ScatterRegl = module.exports = extend({}, require('../scatter')); + + +ScatterRegl.name = 'scatterregl'; +ScatterRegl.categories = ['gl', 'gl2d', 'regl', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; + + +ScatterRegl.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: find a better 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 = Math.max(x ? x.length : 0, y ? y.length : 0), i, l, xx, yy, ptrX = 0, ptrY = 0; + var lineOptions, scatterOptions, errorXOptions, errorYOptions, fillOptions; + var isVisible, hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill; + var linePositions; + + // convert log axes + if(xaxis.type === 'log') { + for(i = 0, l = x.length; i < l; i++) { + x[i] = xaxis.d2l(x[i]); + } + } + if(yaxis.type === 'log') { + for(i = 0, l = y.length; i < l; i++) { + y[i] = yaxis.d2l(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 = x ? parseFloat(x[i]) : i; + yy = y ? parseFloat(y[i]) : i; + + 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; + } + } + + // stash data + stash.x = x; + stash.y = y; + stash.positions = positions; + stash.count = count; + + if(trace.visible !== true) { + isVisible = false; + hasLines = false; + hasErrorX = false; + hasErrorY = false; + hasMarkers = false; + hasFill = false; + } + else { + isVisible = true; + 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++] = x[i] - errorVals[i].xs || 0; + errorsX[ptrX++] = errorVals[i].xh - x[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++] = y[i] - errorVals[i].ys || 0; + errorsY[ptrY++] = errorVals[i].yh - y[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.join = trace.opacity === 1.0 ? 'rect' : 'round'; + 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; + } + lineOptions.positions = linePositions; + } + + if(hasFill) { + fillOptions = {}; + fillOptions.fill = trace.fillcolor; + fillOptions.thickness = 0; + fillOptions.closed = true; + } + + if(hasMarkers) { + scatterOptions = {}; + scatterOptions.positions = positions; + + // 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)) { + scatterOptions.colors = new Array(count); + scatterOptions.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; + } + } + + scatterOptions.colors = colors; + scatterOptions.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; + } + } + + scatterOptions.opacity = trace.opacity; + } + else { + scatterOptions.color = markerOpts.color; + scatterOptions.borderColor = markerOpts.line.color; + scatterOptions.opacity = trace.opacity * markerOpts.opacity; + + if(isOpen) { + scatterOptions.borderColor = scatterOptions.color.slice(); + scatterOptions.color = scatterOptions.color.slice(); + scatterOptions.color[3] = 0; + } + } + + // prepare markers + if(Array.isArray(markerOpts.symbol)) { + scatterOptions.markers = new Array(count); + for(i = 0; i < count; ++i) { + scatterOptions.markers[i] = getSymbolSdf(markerOpts.symbol[i]); + } + } + else { + scatterOptions.marker = getSymbolSdf(markerOpts.symbol); + } + + // prepare sizes and expand axes + var multiSize = markerOpts && (Array.isArray(markerOpts.size) || Array.isArray(markerOpts.line.width)); + var msx, msy, xbounds = [Infinity, -Infinity], ybounds = [Infinity, -Infinity]; + var markerSizeFunc = makeBubbleSizeFn(trace); + + if(multiSize) { + var size; + var sizes = scatterOptions.sizes = new Array(count); + var borderSizes = scatterOptions.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; + } + } + + // FIXME: this slows down big number of points + Axes.expand(xaxis, trace.x, { padded: true, ppad: sizes }); + Axes.expand(yaxis, trace.y, { padded: true, ppad: sizes }); + } + else { + scatterOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); + scatterOptions.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; + } + + // update axes fast + var pad = scatterOptions.size; + if(xaxis._min && !xaxis._min.length) { + xaxis._min.push({ val: xbounds[0], pad: pad }); + } + if(xaxis._max && !xaxis._max.length) { + xaxis._max.push({ val: xbounds[1], pad: pad }); + } + if(yaxis._min && !yaxis._min.length) { + yaxis._min.push({ val: ybounds[0], pad: pad }); + } + if(yaxis._max && !yaxis._max.length) { + yaxis._max.push({ val: ybounds[1], pad: pad }); + } + } + } + + + // 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: [], + scatterOptions: [], + errorXOptions: [], + errorYOptions: [], + + // 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(opts.concat(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.scatter2d.draw(i); + } + + scene.dirty = false; + }; + + // highlight selected points + scene.select = function select(selection) { + if (!scene.select2d) return; + + scene.select2d.regl.clear({color: true}); + + if (!selection.length) return; + + var options = selection.map(function (points) { + if (!points || !points.length) return null; + + var elements = Array(points.length); + for (var i = 0; i < points.length; i++) { + elements[i] = points[i].pointNumber; + } + return elements + }); + + scene.select2d.draw(options); + + //FIXME: this can be a strong guess, possibly we can redraw scene once + scene.scatter2d.regl.clear({color: true}); + scene.draw(); + } + } + + // 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.scatterOptions = []; + scene.errorXOptions = []; + scene.errorYOptions = []; + } + + // save initial batch + scene.lineOptions.push(lineOptions); + scene.errorXOptions.push(errorXOptions); + scene.errorYOptions.push(errorYOptions); + scene.fillOptions.push(fillOptions); + scene.scatterOptions.push(scatterOptions); + scene.count++; + + //stash scene ref + stash.scene = scene; + + 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/.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; +} + + +ScatterRegl.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; + 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) { + scene.scatter2d.update(scene.scatterOptions); + } + // 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 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 { + var nextTrace = trace._nexttrace; + if(nextTrace && trace.fill === 'tonexty') { + pos = srcPos.slice(); + + // FIXME: overcalculation here + var nextOptions = scene.lineOptions[i + 1]; + + if(nextOptions) { + var nextPos = nextOptions.positions; + + 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; + } + } + } + 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.scatter2d) { + var selectRegl = layout._glcanvas.data()[1].regl; + + scene.select2d = createScatter(selectRegl); + + //TODO: modify options here according to the proposed selection options + scene.select2d.update(scene.scatterOptions); + } + } + else { + if (scene.select2d) scene.select2d.regl.clear({color: true}); + } + + // 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 xaxis = Axes.getFromId(container, trace.xaxis || 'x'); + var yaxis = Axes.getFromId(container, trace.yaxis || 'y'); + + 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 (dragmode === 'lasso' || dragmode === 'select') { + //precalculate px coords since we are not going to pan during select + var xpx = Array(stash.count), ypx = Array(stash.count); + for(var i = 0; i < stash.count; i++) { + xpx[i] = xaxis.c2p(trace.x[i]); + ypx[i] = yaxis.c2p(trace.y[i]); + } + stash.xpx = xpx; + stash.ypx = ypx; + } + else { + stash.xpx = stash.ypx = null; + + //reset opacities + if (scene.scatter2d) { + scene.scatter2d.update(scene.scatterOptions.map(function (opt) { + return {opacity: opt.opacity} + })); + } + } + + return { + viewport: viewport, + range: range + }; + }); + + // 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; +}; + + +ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { + var cd = pointData.cd, + stash = cd[0].t, + trace = cd[0].trace, + xa = pointData.xa, + ya = pointData.ya, + positions = stash.positions, + x = stash.x, + y = stash.y, + xpx = xa.c2p(xval), + ypx = ya.c2p(yval), + ids; + + // FIXME: make sure this is a proper way to calc search radius + if (stash.tree) { + // ids = stash.tree.range( + // xval - MAXDIST / xa._m, yval - MAXDIST / ya._m, + // xval + MAXDIST / xa._m, yval + MAXDIST / ya._m + // ); + + //FIXME: this works only for the case of linear points + ids = stash.tree.within(xval, yval, MAXDIST / xa._m); + } + 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; + + for(var i = 0; i < ids.length; i++) { + ptx = trace.x[ids[i]]; + pty = trace.y[ids[i]]; + var dx = xa.c2p(ptx) - xpx, dy = ya.c2p(pty) - ypx; + + var 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 = { + x: trace.x[id], + y: trace.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; + + 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 + }); + + if(di.htx) pointData.text = di.htx; + else if(trace.hovertext) pointData.text = trace.hovertext; + else if(di.tx) pointData.text = di.tx; + else if(trace.text) pointData.text = trace.text; + ErrorBars.hoverInfo(di, trace, pointData); + + return [pointData]; +}; + + +ScatterRegl.selectPoints = function select(searchInfo, polygon) { + var cd = searchInfo.cd, + xa = searchInfo.xaxis, + ya = searchInfo.yaxis, + selection = [], + trace = cd[0].trace, + stash = cd[0].t; + + 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 + if(polygon === false || polygon.degenerate) { + if (scene.scatter2d) { + scene.scatter2d.update(scene.scatterOptions.map(function (opt) { + return {opacity: opt.opacity} + })); + }; + } + // filter out points by visible scatter ones + else { + for(var i = 0; i < stash.count; i++) { + if(polygon.contains([stash.xpx[i], stash.ypx[i]])) { + selection.push({ + pointNumber: i, + x: trace.x[i], + y: trace.y[i] + }); + } + } + + // adjust selection transparency via canvas opacity + if (scene.scatter2d) { + scene.scatter2d.update(scene.scatterOptions.map(function (opt) { + return {opacity: opt.opacity * DESELECTDIM} + })); + }; + } + + return selection; +}; diff --git a/src/traces/scattergl/select.js b/src/traces/scattergl/select.js deleted file mode 100644 index f594c69dd74..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(trace.visible !== true || 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: di.x, - y: 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/src/traces/scatterregl/index.js b/src/traces/scatterregl/index.js deleted file mode 100644 index 302b9a0deda..00000000000 --- a/src/traces/scatterregl/index.js +++ /dev/null @@ -1,854 +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 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 DESELECTDIM = require('../../constants/interactions').DESELECTDIM; - -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 ScatterRegl = module.exports = extend({}, require('../scatter')); - - -ScatterRegl.name = 'scatterregl'; -ScatterRegl.categories = ['gl', 'gl2d', 'regl', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; - - -ScatterRegl.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: find a better 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 = Math.max(x ? x.length : 0, y ? y.length : 0), i, l, xx, yy, ptrX = 0, ptrY = 0; - var lineOptions, scatterOptions, errorXOptions, errorYOptions, fillOptions; - var isVisible, hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill; - var linePositions; - - // convert log axes - if(xaxis.type === 'log') { - for(i = 0, l = x.length; i < l; i++) { - x[i] = xaxis.d2l(x[i]); - } - } - if(yaxis.type === 'log') { - for(i = 0, l = y.length; i < l; i++) { - y[i] = yaxis.d2l(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 = x ? parseFloat(x[i]) : i; - yy = y ? parseFloat(y[i]) : i; - - 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; - } - } - - // stash data - stash.x = x; - stash.y = y; - stash.positions = positions; - stash.count = count; - - if(trace.visible !== true) { - isVisible = false; - hasLines = false; - hasErrorX = false; - hasErrorY = false; - hasMarkers = false; - hasFill = false; - } - else { - isVisible = true; - 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++] = x[i] - errorVals[i].xs || 0; - errorsX[ptrX++] = errorVals[i].xh - x[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++] = y[i] - errorVals[i].ys || 0; - errorsY[ptrY++] = errorVals[i].yh - y[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.join = trace.opacity === 1.0 ? 'rect' : 'round'; - 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; - } - lineOptions.positions = linePositions; - } - - if(hasFill) { - fillOptions = {}; - fillOptions.fill = trace.fillcolor; - fillOptions.thickness = 0; - fillOptions.closed = true; - } - - if(hasMarkers) { - scatterOptions = {}; - scatterOptions.positions = positions; - - // 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)) { - scatterOptions.colors = new Array(count); - scatterOptions.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; - } - } - - scatterOptions.colors = colors; - scatterOptions.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; - } - } - - scatterOptions.opacity = trace.opacity; - } - else { - scatterOptions.color = markerOpts.color; - scatterOptions.borderColor = markerOpts.line.color; - scatterOptions.opacity = trace.opacity * markerOpts.opacity; - - if(isOpen) { - scatterOptions.borderColor = scatterOptions.color.slice(); - scatterOptions.color = scatterOptions.color.slice(); - scatterOptions.color[3] = 0; - } - } - - // prepare markers - if(Array.isArray(markerOpts.symbol)) { - scatterOptions.markers = new Array(count); - for(i = 0; i < count; ++i) { - scatterOptions.markers[i] = getSymbolSdf(markerOpts.symbol[i]); - } - } - else { - scatterOptions.marker = getSymbolSdf(markerOpts.symbol); - } - - // prepare sizes and expand axes - var multiSize = markerOpts && (Array.isArray(markerOpts.size) || Array.isArray(markerOpts.line.width)); - var msx, msy, xbounds = [Infinity, -Infinity], ybounds = [Infinity, -Infinity]; - var markerSizeFunc = makeBubbleSizeFn(trace); - - if(multiSize) { - var size; - var sizes = scatterOptions.sizes = new Array(count); - var borderSizes = scatterOptions.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; - } - } - - // FIXME: this slows down big number of points - Axes.expand(xaxis, trace.x, { padded: true, ppad: sizes }); - Axes.expand(yaxis, trace.y, { padded: true, ppad: sizes }); - } - else { - scatterOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); - scatterOptions.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; - } - - // update axes fast - var pad = scatterOptions.size; - if(xaxis._min && !xaxis._min.length) { - xaxis._min.push({ val: xbounds[0], pad: pad }); - } - if(xaxis._max && !xaxis._max.length) { - xaxis._max.push({ val: xbounds[1], pad: pad }); - } - if(yaxis._min && !yaxis._min.length) { - yaxis._min.push({ val: ybounds[0], pad: pad }); - } - if(yaxis._max && !yaxis._max.length) { - yaxis._max.push({ val: ybounds[1], pad: pad }); - } - } - } - - - // 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: [], - scatterOptions: [], - errorXOptions: [], - errorYOptions: [], - - // 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(opts.concat(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.scatter2d.draw(i); - } - - scene.dirty = false; - }; - - // highlight selected points - scene.select = function select(selection) { - if (!scene.select2d) return; - - scene.select2d.regl.clear({color: true}); - - if (!selection.length) return; - - var options = selection.map(function (points) { - if (!points || !points.length) return null; - - var elements = Array(points.length); - for (var i = 0; i < points.length; i++) { - elements[i] = points[i].pointNumber; - } - return elements - }); - - scene.select2d.draw(options); - - //FIXME: this can be a strong guess, possibly we can redraw scene once - scene.scatter2d.regl.clear({color: true}); - scene.draw(); - } - } - - // 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.scatterOptions = []; - scene.errorXOptions = []; - scene.errorYOptions = []; - } - - // save initial batch - scene.lineOptions.push(lineOptions); - scene.errorXOptions.push(errorXOptions); - scene.errorYOptions.push(errorYOptions); - scene.fillOptions.push(fillOptions); - scene.scatterOptions.push(scatterOptions); - scene.count++; - - //stash scene ref - stash.scene = scene; - - return [{x: false, y: false, t: stash, trace: trace}]; -}; - - -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/.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; -} - - -ScatterRegl.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; - 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) { - scene.scatter2d.update(scene.scatterOptions); - } - // 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 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 { - var nextTrace = trace._nexttrace; - if(nextTrace && trace.fill === 'tonexty') { - pos = srcPos.slice(); - - // FIXME: overcalculation here - var nextOptions = scene.lineOptions[i + 1]; - - if(nextOptions) { - var nextPos = nextOptions.positions; - - 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; - } - } - } - 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.scatter2d) { - var selectRegl = layout._glcanvas.data()[1].regl; - - scene.select2d = createScatter(selectRegl); - - //TODO: modify options here according to the proposed selection options - scene.select2d.update(scene.scatterOptions); - } - } - else { - if (scene.select2d) scene.select2d.regl.clear({color: true}); - } - - // 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 xaxis = Axes.getFromId(container, trace.xaxis || 'x'); - var yaxis = Axes.getFromId(container, trace.yaxis || 'y'); - - 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 (dragmode === 'lasso' || dragmode === 'select') { - //precalculate px coords since we are not going to pan during select - var xpx = Array(stash.count), ypx = Array(stash.count); - for(var i = 0; i < stash.count; i++) { - xpx[i] = xaxis.c2p(trace.x[i]); - ypx[i] = yaxis.c2p(trace.y[i]); - } - stash.xpx = xpx; - stash.ypx = ypx; - } - else { - stash.xpx = stash.ypx = null; - - //reset opacities - if (scene.scatter2d) { - scene.scatter2d.update(scene.scatterOptions.map(function (opt) { - return {opacity: opt.opacity} - })); - } - } - - return { - viewport: viewport, - range: range - }; - }); - - // 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; -}; - - -ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { - var cd = pointData.cd, - stash = cd[0].t, - trace = cd[0].trace, - xa = pointData.xa, - ya = pointData.ya, - positions = stash.positions, - x = stash.x, - y = stash.y, - xpx = xa.c2p(xval), - ypx = ya.c2p(yval), - ids; - - // FIXME: make sure this is a proper way to calc search radius - if (stash.tree) { - // ids = stash.tree.range( - // xval - MAXDIST / xa._m, yval - MAXDIST / ya._m, - // xval + MAXDIST / xa._m, yval + MAXDIST / ya._m - // ); - - //FIXME: this works only for the case of linear points - ids = stash.tree.within(xval, yval, MAXDIST / xa._m); - } - 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; - - for(var i = 0; i < ids.length; i++) { - ptx = trace.x[ids[i]]; - pty = trace.y[ids[i]]; - var dx = xa.c2p(ptx) - xpx, dy = ya.c2p(pty) - ypx; - - var 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 = { - x: trace.x[id], - y: trace.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; - - 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 - }); - - if(di.htx) pointData.text = di.htx; - else if(trace.hovertext) pointData.text = trace.hovertext; - else if(di.tx) pointData.text = di.tx; - else if(trace.text) pointData.text = trace.text; - ErrorBars.hoverInfo(di, trace, pointData); - - return [pointData]; -}; - - -ScatterRegl.selectPoints = function select(searchInfo, polygon) { - var cd = searchInfo.cd, - xa = searchInfo.xaxis, - ya = searchInfo.yaxis, - selection = [], - trace = cd[0].trace, - stash = cd[0].t; - - 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 - if(polygon === false || polygon.degenerate) { - if (scene.scatter2d) { - scene.scatter2d.update(scene.scatterOptions.map(function (opt) { - return {opacity: opt.opacity} - })); - }; - } - // filter out points by visible scatter ones - else { - for(var i = 0; i < stash.count; i++) { - if(polygon.contains([stash.xpx[i], stash.ypx[i]])) { - selection.push({ - pointNumber: i, - x: trace.x[i], - y: trace.y[i] - }); - } - } - - // adjust selection transparency via canvas opacity - if (scene.scatter2d) { - scene.scatter2d.update(scene.scatterOptions.map(function (opt) { - return {opacity: opt.opacity * DESELECTDIM} - })); - }; - } - - return selection; -}; From ebb0caca47940837ed8140c5f94ecbeb4604b9fe Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 27 Oct 2017 12:07:05 -0400 Subject: [PATCH 086/151] Rename main trace --- src/traces/scattergl/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 302b9a0deda..42e85780e94 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -39,7 +39,7 @@ var SYMBOL_SVG_CIRCLE = Drawing.symbolFuncs[0](SYMBOL_SIZE * 0.05); var ScatterRegl = module.exports = extend({}, require('../scatter')); -ScatterRegl.name = 'scatterregl'; +ScatterRegl.name = 'scattergl'; ScatterRegl.categories = ['gl', 'gl2d', 'regl', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; From fbaed641857dcf0100f83f23c56e506a9dc39834 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 27 Oct 2017 12:34:31 -0400 Subject: [PATCH 087/151] Add data for Russia --- test/image/mocks/gl2d_12.json | 3 +++ 1 file changed, 3 insertions(+) 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", From ef279e2fb515acf81d5511f14dccec37b422595e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 27 Oct 2017 13:15:03 -0400 Subject: [PATCH 088/151] Fix no scatter trace check --- src/traces/scattergl/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 42e85780e94..ef0253b856c 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -40,7 +40,7 @@ var ScatterRegl = module.exports = extend({}, require('../scatter')); ScatterRegl.name = 'scattergl'; -ScatterRegl.categories = ['gl', 'gl2d', 'regl', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; +ScatterRegl.categories = ['gl', 'regl', 'cartesian', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; ScatterRegl.calc = function calc(container, trace) { @@ -660,7 +660,7 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { //reset opacities if (scene.scatter2d) { scene.scatter2d.update(scene.scatterOptions.map(function (opt) { - return {opacity: opt.opacity} + return {opacity: opt ? opt.opacity : 1} })); } } From 8e1beb7c92d1cbfb8588e9dde8196e869b87d466 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 27 Oct 2017 16:54:48 -0400 Subject: [PATCH 089/151] Fix axes-related issues --- src/traces/scattergl/index.js | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index ef0253b856c..0af8857873a 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -57,8 +57,21 @@ ScatterRegl.calc = function calc(container, trace) { // 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 = Math.max(x ? x.length : 0, y ? y.length : 0), i, l, xx, yy, ptrX = 0, ptrY = 0; + + if (!x) { + x = Array(count) + for (let i = 0; i < count; i++) { + x[i] = i + } + } + if (!y) { + y = Array(count) + for (let i = 0; i < count; i++) { + y[i] = i + } + } + var lineOptions, scatterOptions, errorXOptions, errorYOptions, fillOptions; var isVisible, hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill; var linePositions; @@ -81,8 +94,8 @@ ScatterRegl.calc = function calc(container, trace) { 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 = x ? parseFloat(x[i]) : i; - yy = y ? parseFloat(y[i]) : i; + xx = parseFloat(x[i]); + yy = parseFloat(y[i]); positions[i * 2] = xx; positions[i * 2 + 1] = yy; @@ -362,16 +375,16 @@ ScatterRegl.calc = function calc(container, trace) { // update axes fast var pad = scatterOptions.size; - if(xaxis._min && !xaxis._min.length) { + if(xaxis._min) { xaxis._min.push({ val: xbounds[0], pad: pad }); } - if(xaxis._max && !xaxis._max.length) { + if(xaxis._max) { xaxis._max.push({ val: xbounds[1], pad: pad }); } - if(yaxis._min && !yaxis._min.length) { + if(yaxis._min) { yaxis._min.push({ val: ybounds[0], pad: pad }); } - if(yaxis._max && !yaxis._max.length) { + if(yaxis._max) { yaxis._max.push({ val: ybounds[1], pad: pad }); } } From 8b81ba13b4a841dd1c8b5b50dc155387e3feaea4 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 30 Oct 2017 12:58:52 -0400 Subject: [PATCH 090/151] Put axes expansion to the first plot call --- src/traces/scattergl/convert.js | 769 ++++++++++++++++++++++++++++++++ src/traces/scattergl/index.js | 53 ++- 2 files changed, 801 insertions(+), 21 deletions(-) create mode 100644 src/traces/scattergl/convert.js diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js new file mode 100644 index 00000000000..af7055ccf24 --- /dev/null +++ b/src/traces/scattergl/convert.js @@ -0,0 +1,769 @@ +/** +* 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 }); + // console.log(min, max) + } +}; + +// 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/index.js b/src/traces/scattergl/index.js index 0af8857873a..83f12fbabc5 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -322,7 +322,7 @@ ScatterRegl.calc = function calc(container, trace) { } // prepare sizes and expand axes - var multiSize = markerOpts && (Array.isArray(markerOpts.size) || Array.isArray(markerOpts.line.width)); + var multiSize = scatterOptions.multiSize = markerOpts && (Array.isArray(markerOpts.size) || Array.isArray(markerOpts.line.width)); var msx, msy, xbounds = [Infinity, -Infinity], ybounds = [Infinity, -Infinity]; var markerSizeFunc = makeBubbleSizeFn(trace); @@ -355,10 +355,6 @@ ScatterRegl.calc = function calc(container, trace) { borderSizes[i] = size; } } - - // FIXME: this slows down big number of points - Axes.expand(xaxis, trace.x, { padded: true, ppad: sizes }); - Axes.expand(yaxis, trace.y, { padded: true, ppad: sizes }); } else { scatterOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); @@ -372,21 +368,8 @@ ScatterRegl.calc = function calc(container, trace) { if(ybounds[0] > yy) ybounds[0] = yy; if(ybounds[1] < yy) ybounds[1] = yy; } - - // update axes fast - var pad = scatterOptions.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 }); - } + scatterOptions.xbounds = xbounds; + scatterOptions.ybounds = ybounds; } } @@ -638,7 +621,7 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { } // provide viewport and range - var vpRange = cdata.map(function(cdscatter) { + var vpRange = cdata.map(function(cdscatter, i) { if(!cdscatter || !cdscatter[0] || !cdscatter[0].trace) return; var cd = cdscatter[0]; var trace = cd.trace; @@ -657,6 +640,7 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { (height - vpSize.t) - (1 - yaxis.domain[1]) * vpSize.h ]; + // handle selection here if (dragmode === 'lasso' || dragmode === 'select') { //precalculate px coords since we are not going to pan during select var xpx = Array(stash.count), ypx = Array(stash.count); @@ -678,6 +662,33 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { } } + // axes expand does not trigger right at the calc stage, so we do it here + if (scene.scatter2d) { + var scatterOptions = scene.scatterOptions[i]; + if (scatterOptions.multiSize) { + // FIXME: this slows down big number of points + Axes.expand(xaxis, trace.x, { padded: true, ppad: scatterOptions.sizes }); + Axes.expand(yaxis, trace.y, { padded: true, ppad: scatterOptions.sizes }); + } + // update axes fast + else { + var pad = scatterOptions.sizes; + console.log(scatterOptions.xbounds) + if(xaxis._min) { + xaxis._min.push({ val: scatterOptions.xbounds[0], pad: pad }); + } + if(xaxis._max) { + xaxis._max.push({ val: scatterOptions.xbounds[1], pad: pad }); + } + if(yaxis._min) { + yaxis._min.push({ val: scatterOptions.ybounds[0], pad: pad }); + } + if(yaxis._max) { + yaxis._max.push({ val: scatterOptions.ybounds[1], pad: pad }); + } + } + } + return { viewport: viewport, range: range From ae6ebd35bf5225f0e347ae258828e8a6b0cc430a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 30 Oct 2017 14:17:01 -0400 Subject: [PATCH 091/151] Do not redefine autorange --- src/traces/scattergl/index.js | 65 ++++++++++++++++------------------- 1 file changed, 29 insertions(+), 36 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 83f12fbabc5..2d1c280098b 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -322,7 +322,7 @@ ScatterRegl.calc = function calc(container, trace) { } // prepare sizes and expand axes - var multiSize = scatterOptions.multiSize = markerOpts && (Array.isArray(markerOpts.size) || Array.isArray(markerOpts.line.width)); + var multiSize = markerOpts && (Array.isArray(markerOpts.size) || Array.isArray(markerOpts.line.width)); var msx, msy, xbounds = [Infinity, -Infinity], ybounds = [Infinity, -Infinity]; var markerSizeFunc = makeBubbleSizeFn(trace); @@ -355,6 +355,10 @@ ScatterRegl.calc = function calc(container, trace) { borderSizes[i] = size; } } + + // FIXME: this slows down big number of points + Axes.expand(xaxis, trace.x, { padded: true, ppad: sizes }); + Axes.expand(yaxis, trace.y, { padded: true, ppad: sizes }); } else { scatterOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); @@ -368,8 +372,25 @@ ScatterRegl.calc = function calc(container, trace) { if(ybounds[0] > yy) ybounds[0] = yy; if(ybounds[1] < yy) ybounds[1] = yy; } - scatterOptions.xbounds = xbounds; - scatterOptions.ybounds = ybounds; + + if (!xaxis.autorange) { + // update axes fast + var pad = scatterOptions.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.autorange) { + if(yaxis._min) { + yaxis._min.push({ val: ybounds[0], pad: pad }); + } + if(yaxis._max) { + yaxis._max.push({ val: ybounds[1], pad: pad }); + } + } } } @@ -621,7 +642,7 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { } // provide viewport and range - var vpRange = cdata.map(function(cdscatter, i) { + var vpRange = cdata.map(function(cdscatter) { if(!cdscatter || !cdscatter[0] || !cdscatter[0].trace) return; var cd = cdscatter[0]; var trace = cd.trace; @@ -640,7 +661,6 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { (height - vpSize.t) - (1 - yaxis.domain[1]) * vpSize.h ]; - // handle selection here if (dragmode === 'lasso' || dragmode === 'select') { //precalculate px coords since we are not going to pan during select var xpx = Array(stash.count), ypx = Array(stash.count); @@ -662,33 +682,6 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { } } - // axes expand does not trigger right at the calc stage, so we do it here - if (scene.scatter2d) { - var scatterOptions = scene.scatterOptions[i]; - if (scatterOptions.multiSize) { - // FIXME: this slows down big number of points - Axes.expand(xaxis, trace.x, { padded: true, ppad: scatterOptions.sizes }); - Axes.expand(yaxis, trace.y, { padded: true, ppad: scatterOptions.sizes }); - } - // update axes fast - else { - var pad = scatterOptions.sizes; - console.log(scatterOptions.xbounds) - if(xaxis._min) { - xaxis._min.push({ val: scatterOptions.xbounds[0], pad: pad }); - } - if(xaxis._max) { - xaxis._max.push({ val: scatterOptions.xbounds[1], pad: pad }); - } - if(yaxis._min) { - yaxis._min.push({ val: scatterOptions.ybounds[0], pad: pad }); - } - if(yaxis._max) { - yaxis._max.push({ val: scatterOptions.ybounds[1], pad: pad }); - } - } - } - return { viewport: viewport, range: range @@ -751,8 +744,8 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { var min = MAXDIST, id, ptx, pty; for(var i = 0; i < ids.length; i++) { - ptx = trace.x[ids[i]]; - pty = trace.y[ids[i]]; + ptx = x[ids[i]]; + pty = y[ids[i]]; var dx = xa.c2p(ptx) - xpx, dy = ya.c2p(pty) - ypx; var dist = Math.sqrt(dx * dx + dy * dy); @@ -768,8 +761,8 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { // the closest data point var di = { - x: trace.x[id], - y: trace.y[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 From f02f0eeffcca3065599d16fd15843c70ac29891e Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 30 Oct 2017 14:39:15 -0400 Subject: [PATCH 092/151] Fix axes expansion --- src/traces/scattergl/index.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 2d1c280098b..ef7eb5340f9 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -34,7 +34,7 @@ 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 ScatterRegl = module.exports = extend({}, require('../scatter')); @@ -356,12 +356,12 @@ ScatterRegl.calc = function calc(container, trace) { } } - // FIXME: this slows down big number of points - Axes.expand(xaxis, trace.x, { padded: true, ppad: sizes }); - Axes.expand(yaxis, trace.y, { padded: true, ppad: sizes }); + Axes.expand(xaxis, x, { padded: true, ppad: sizes }); + Axes.expand(yaxis, y, { padded: true, ppad: sizes }); } else { - scatterOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); + // console.log(x, xOptions, xa._min) + var size = scatterOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); scatterOptions.borderSizes = markerOpts.line.width * 0.5; // axes bounds @@ -373,8 +373,13 @@ ScatterRegl.calc = function calc(container, trace) { if(ybounds[1] < yy) ybounds[1] = yy; } - if (!xaxis.autorange) { - // update axes fast + // FIXME: is there a better way to separate expansion? + if (count < TOO_MANY_POINTS) { + Axes.expand(xaxis, x, { padded: true, ppad: size }); + Axes.expand(yaxis, y, { padded: true, ppad: size }); + } + // update axes fast for big number of points + else { var pad = scatterOptions.size; if(xaxis._min) { xaxis._min.push({ val: xbounds[0], pad: pad }); @@ -382,8 +387,7 @@ ScatterRegl.calc = function calc(container, trace) { if(xaxis._max) { xaxis._max.push({ val: xbounds[1], pad: pad }); } - } - if (!yaxis.autorange) { + if(yaxis._min) { yaxis._min.push({ val: ybounds[0], pad: pad }); } From 30bb703b1a46d3a204473144b3ccc4846a7e6ca6 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 31 Oct 2017 13:01:08 -0400 Subject: [PATCH 093/151] Fix line/test cases --- src/traces/scattergl/index.js | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index ef7eb5340f9..f044dbc0ba7 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -190,7 +190,6 @@ ScatterRegl.calc = function calc(container, trace) { lineOptions.thickness = trace.line.width; lineOptions.color = trace.line.color; lineOptions.opacity = trace.opacity; - lineOptions.join = trace.opacity === 1.0 ? 'rect' : 'round'; lineOptions.overlay = true; var dashes = (DASHES[trace.line.dash] || [1]).slice(); @@ -238,6 +237,33 @@ ScatterRegl.calc = function calc(container, trace) { else { linePositions = positions; } + + // If we have data with gaps, we ought to use rect joins + // FIXME: get rid of this + var hasNaNs = false; + for (i = 0; i < linePositions.length; i++) { + if (isNaN(linePositions[i])) { + hasNaNs = true; + break; + } + } + lineOptions.join = (hasNaNs || linePositions.length > TOO_MANY_POINTS) && !hasMarkers ? 'rect' : 'round' + + // fill gaps + if (hasNaNs && 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; } @@ -360,7 +386,6 @@ ScatterRegl.calc = function calc(container, trace) { Axes.expand(yaxis, y, { padded: true, ppad: sizes }); } else { - // console.log(x, xOptions, xa._min) var size = scatterOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); scatterOptions.borderSizes = markerOpts.line.width * 0.5; @@ -479,6 +504,12 @@ ScatterRegl.calc = function calc(container, trace) { scene.draw(); } } + else { + if (hasFill && !scene.fill2d) scene.fill2d = true; + if (hasMarkers && !scene.marker2d) scene.marker2d = 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) { From 3126005c076b30bde8599c481008d5c348ebd2a8 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 31 Oct 2017 14:33:51 -0400 Subject: [PATCH 094/151] Fix log ranges --- src/traces/scattergl/index.js | 37 ++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index f044dbc0ba7..f2fbd1258b4 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -58,7 +58,6 @@ ScatterRegl.calc = function calc(container, trace) { var x = xaxis.type === 'linear' ? trace.x : xaxis.makeCalcdata(trace, 'x'); var y = yaxis.type === 'linear' ? trace.y : yaxis.makeCalcdata(trace, 'y'); var count = Math.max(x ? x.length : 0, y ? y.length : 0), i, l, xx, yy, ptrX = 0, ptrY = 0; - if (!x) { x = Array(count) for (let i = 0; i < count; i++) { @@ -76,17 +75,28 @@ ScatterRegl.calc = function calc(container, trace) { var isVisible, hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill; var linePositions; - // convert log axes + // get log converted positions + var rawx, rawy; if(xaxis.type === 'log') { + var 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; + } if(yaxis.type === 'log') { + var 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; + } // we need hi-precision for scatter2d positions = new Array(count * 2); @@ -119,6 +129,8 @@ ScatterRegl.calc = function calc(container, trace) { // stash data stash.x = x; stash.y = y; + stash.rawx = rawx; + stash.rawy = rawy; stash.positions = positions; stash.count = count; @@ -240,17 +252,17 @@ ScatterRegl.calc = function calc(container, trace) { // If we have data with gaps, we ought to use rect joins // FIXME: get rid of this - var hasNaNs = false; + var hasNaN = false; for (i = 0; i < linePositions.length; i++) { if (isNaN(linePositions[i])) { - hasNaNs = true; + hasNaN = true; break; } } - lineOptions.join = (hasNaNs || linePositions.length > TOO_MANY_POINTS) && !hasMarkers ? 'rect' : 'round' + lineOptions.join = (hasNaN || linePositions.length > TOO_MANY_POINTS) ? 'rect' : hasMarkers ? 'rect' : 'round'; // fill gaps - if (hasNaNs && trace.connectgaps) { + 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])) { @@ -382,8 +394,8 @@ ScatterRegl.calc = function calc(container, trace) { } } - Axes.expand(xaxis, x, { padded: true, ppad: sizes }); - Axes.expand(yaxis, y, { padded: true, ppad: sizes }); + Axes.expand(xaxis, stash.rawx, { padded: true, ppad: sizes }); + Axes.expand(yaxis, stash.rawy, { padded: true, ppad: sizes }); } else { var size = scatterOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); @@ -400,8 +412,8 @@ ScatterRegl.calc = function calc(container, trace) { // FIXME: is there a better way to separate expansion? if (count < TOO_MANY_POINTS) { - Axes.expand(xaxis, x, { padded: true, ppad: size }); - Axes.expand(yaxis, y, { padded: true, ppad: size }); + Axes.expand(xaxis, stash.rawx, { padded: true, ppad: size }); + Axes.expand(yaxis, stash.rawy, { padded: true, ppad: size }); } // update axes fast for big number of points else { @@ -686,7 +698,10 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { var yaxis = Axes.getFromId(container, trace.yaxis || 'y'); var range = [ - xaxis._rl[0], yaxis._rl[0], xaxis._rl[1], yaxis._rl[1] + xaxis.range[0], + yaxis.range[0], + xaxis.range[1], + yaxis.range[1] ]; var viewport = [ From 530efee8a211af3c2a8549de9432336de55ec5fe Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 31 Oct 2017 16:26:19 -0400 Subject: [PATCH 095/151] Fix log axes ranges --- src/traces/scattergl/index.js | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index f2fbd1258b4..a16803d9ade 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -161,8 +161,8 @@ ScatterRegl.calc = function calc(container, trace) { var errorsX = new Float64Array(4 * count); for(i = 0; i < count; ++i) { - errorsX[ptrX++] = x[i] - errorVals[i].xs || 0; - errorsX[ptrX++] = errorVals[i].xh - x[i] || 0; + errorsX[ptrX++] = rawx[i] - errorVals[i].xs || 0; + errorsX[ptrX++] = errorVals[i].xh - rawx[i] || 0; errorsX[ptrX++] = 0; errorsX[ptrX++] = 0; } @@ -186,8 +186,8 @@ ScatterRegl.calc = function calc(container, trace) { for(i = 0; i < count; ++i) { errorsY[ptrY++] = 0; errorsY[ptrY++] = 0; - errorsY[ptrY++] = y[i] - errorVals[i].ys || 0; - errorsY[ptrY++] = errorVals[i].yh - y[i] || 0; + errorsY[ptrY++] = rawy[i] - errorVals[i].ys || 0; + errorsY[ptrY++] = errorVals[i].yh - rawy[i] || 0; } errorYOptions.positions = positions; @@ -471,8 +471,7 @@ ScatterRegl.calc = function calc(container, trace) { 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(opts.concat(opts)); - + if(scene.error2d) scene.error2d.update([].push.apply(opts, opts)); scene.draw(); }; @@ -768,8 +767,8 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { xa = pointData.xa, ya = pointData.ya, positions = stash.positions, - x = stash.x, - y = stash.y, + x = stash.rawx, + y = stash.rawy, xpx = xa.c2p(xval), ypx = ya.c2p(yval), ids; From f21920655706d4df18440b4204b184398f6ae0c7 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 31 Oct 2017 17:30:03 -0400 Subject: [PATCH 096/151] Fix raw data init --- src/traces/scattergl/index.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index a16803d9ade..9ce798a0e1a 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -80,12 +80,15 @@ ScatterRegl.calc = function calc(container, trace) { if(xaxis.type === 'log') { var rawx = Array(x.length); for(i = 0, l = x.length; i < l; i++) { - rawx[i] = x[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') { var rawy = Array(y.length); @@ -96,6 +99,9 @@ ScatterRegl.calc = function calc(container, trace) { } else { rawy = y; + for(i = 0, l = y.length; i < l; i++) { + y[i] = parseFloat(y[i]); + } } // we need hi-precision for scatter2d From ea123a6e01b6bb84d32f00e17fc4d53a46f70964 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 1 Nov 2017 13:02:06 -0400 Subject: [PATCH 097/151] Enable hovermode x --- src/traces/scattergl/index.js | 65 +++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 9ce798a0e1a..50484b4aa49 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -51,7 +51,7 @@ ScatterRegl.calc = function calc(container, trace) { var yaxis = Axes.getFromId(container, trace.yaxis); var markerOpts = trace.marker; - // FIXME: find a better way to obtain subplot object from trace + // 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 @@ -440,6 +440,11 @@ ScatterRegl.calc = function calc(container, trace) { } } } + //expand no-markers axes + else { + Axes.expand(xaxis, stash.rawx, { padded: true }); + Axes.expand(yaxis, stash.rawy, { padded: true }); + } // make sure scene exists @@ -703,10 +708,10 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { var yaxis = Axes.getFromId(container, trace.yaxis || 'y'); var range = [ - xaxis.range[0], - yaxis.range[0], - xaxis.range[1], - yaxis.range[1] + xaxis._rl[0], + yaxis._rl[0], + xaxis._rl[1], + yaxis._rl[1] ]; var viewport = [ @@ -766,7 +771,7 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { }; -ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { +ScatterRegl.hoverPoints = function hover(pointData, xval, yval, hovermode) { var cd = pointData.cd, stash = cd[0].t, trace = cd[0].trace, @@ -775,6 +780,7 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { positions = stash.positions, x = stash.rawx, y = stash.rawy, + scene = stash.scene, xpx = xa.c2p(xval), ypx = ya.c2p(yval), ids; @@ -787,7 +793,18 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { // ); //FIXME: this works only for the case of linear points - ids = stash.tree.within(xval, yval, MAXDIST / xa._m); + 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; @@ -796,17 +813,29 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval) { // pick the id closest to the point // note that point possibly may not be found - var min = MAXDIST, id, ptx, pty; - - for(var i = 0; i < ids.length; i++) { - ptx = x[ids[i]]; - pty = y[ids[i]]; - var dx = xa.c2p(ptx) - xpx, dy = ya.c2p(pty) - ypx; - - var dist = Math.sqrt(dx * dx + dy * dy); - if(dist < min) { - min = dist; - id = ids[i]; + var min = MAXDIST, id, ptx, pty, i; + + if (hovermode === 'x') { + for(i = 0; i < ids.length; i++) { + ptx = x[ids[i]]; + var 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]]; + var dx = xa.c2p(ptx) - xpx, dy = ya.c2p(pty) - ypx; + + var dist = Math.sqrt(dx * dx + dy * dy); + if(dist < min) { + min = dist; + id = ids[i]; + } } } From cc0fd0a117523ecc98322bb22f409ba5b7da8eea Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 1 Nov 2017 13:49:16 -0400 Subject: [PATCH 098/151] Fix selection --- src/plot_api/plot_api.js | 4 ++-- src/traces/scattergl/index.js | 14 +++++--------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 7fe0ba207b2..12fab04752d 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -2264,12 +2264,12 @@ function _relayout(gd, aobj) { ai.match(/^(bar|box|font)/)) { flags.docalc = true; } - else if(fullLayout._has('gl2d') && + else if((fullLayout._has('gl2d') || fullLayout._has('regl')) && (ai.indexOf('axis') !== -1 || ai === 'plot_bgcolor') ) { flags.doplot = true; } - else if(fullLayout._has('gl2d') && + else if((fullLayout._has('gl2d') || fullLayout._has('regl')) && (ai === 'dragmode' && (vi === 'lasso' || vi === 'select') && !(vOld === 'lasso' || vOld === 'select')) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 50484b4aa49..b368838eaa5 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -787,12 +787,6 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval, hovermode) { // FIXME: make sure this is a proper way to calc search radius if (stash.tree) { - // ids = stash.tree.range( - // xval - MAXDIST / xa._m, yval - MAXDIST / ya._m, - // xval + MAXDIST / xa._m, yval + MAXDIST / ya._m - // ); - - //FIXME: this works only for the case of linear points if (hovermode === 'x') { ids = stash.tree.range( xa.p2c(xpx - MAXDIST), ya._rl[0], @@ -914,7 +908,9 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { ya = searchInfo.yaxis, selection = [], trace = cd[0].trace, - stash = cd[0].t; + stash = cd[0].t, + x = stash.x, + y = stash.y; var scene = stash.scene; @@ -937,8 +933,8 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { if(polygon.contains([stash.xpx[i], stash.ypx[i]])) { selection.push({ pointNumber: i, - x: trace.x[i], - y: trace.y[i] + x: x[i], + y: y[i] }); } } From e36b0a14cf645160cccf48fdb6323e5dd51fb5e6 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 1 Nov 2017 16:36:59 -0400 Subject: [PATCH 099/151] Install deps --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e7e9d9419e1..f1429b9fa7c 100644 --- a/package.json +++ b/package.json @@ -98,8 +98,9 @@ "object-assign": "^4.1.1", "poly-bool": "^1.0.0", "regl": "^1.3.0", - "regl-line2d": "^1.1.1", - "regl-scatter2d": "^1.0.3", + "regl-error2d": "^2.0.0", + "regl-line2d": "^2.0.0", + "regl-scatter2d": "^2.0.0", "right-now": "^1.0.0", "robust-orientation": "^1.1.3", "sane-topojson": "^2.0.0", From fb37ffc0f2b542a41a539c14c99267cd065e2420 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 1 Nov 2017 16:42:12 -0400 Subject: [PATCH 100/151] Lintify --- src/lib/polygon.js | 7 +- src/plots/cartesian/select.js | 6 +- src/traces/scattergl/index.js | 140 ++++++++++++++++------------------ 3 files changed, 74 insertions(+), 79 deletions(-) diff --git a/src/lib/polygon.js b/src/lib/polygon.js index a5f8e7fb789..b37fce6e3fe 100644 --- a/src/lib/polygon.js +++ b/src/lib/polygon.js @@ -37,10 +37,11 @@ polygon.tester = function tester(ptsIn) { xmin = pts[0][0], xmax = xmin, ymin = pts[0][1], - ymax = ymin; + ymax = ymin, + i; pts.push(pts[0]); - for(var i = 1; i < pts.length; i++) { + for(i = 1; i < pts.length; i++) { xmin = Math.min(xmin, pts[i][0]); xmax = Math.max(xmax, pts[i][0]); ymin = Math.min(ymin, pts[i][1]); @@ -154,7 +155,7 @@ polygon.tester = function tester(ptsIn) { // detect if poly is degenerate var degenerate = true; var lastPt = pts[0]; - for(var i = 1; i < pts.length; i++) { + for(i = 1; i < pts.length; i++) { if(lastPt[0] !== pts[i][0] || lastPt[1] !== pts[i][1]) { degenerate = false; break; diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 85edfcbfe43..b695959417c 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -117,7 +117,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { fillRangeItems = plotinfo.fillRangeItems; } else { if(mode === 'select') { - //FIXME: this is regression + // FIXME: this is regression fillRangeItems = function(eventData, currentPolygon) { var ranges = eventData.range = {}; @@ -126,7 +126,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { var axLetter = ax._id.charAt(0); var x = axLetter === 'x'; - //FIXME: this should be fixed to read xmin/xmax, ymin/ymax + // FIXME: this should be fixed to read xmin/xmax, ymin/ymax ranges[ax._id] = [ ax.p2d(currentPolygon[0][x ? 0 : 1]), ax.p2d(currentPolygon[2][x ? 0 : 1]) @@ -212,7 +212,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { } // update scatterregl scene - if (plotinfo._scene) { + if(plotinfo._scene) { plotinfo._scene.select(traceSelections); } diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index b368838eaa5..5d13e073c7d 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -58,27 +58,27 @@ ScatterRegl.calc = function calc(container, trace) { var x = xaxis.type === 'linear' ? trace.x : xaxis.makeCalcdata(trace, 'x'); var y = yaxis.type === 'linear' ? trace.y : yaxis.makeCalcdata(trace, 'y'); var count = Math.max(x ? x.length : 0, y ? y.length : 0), i, l, xx, yy, ptrX = 0, ptrY = 0; - if (!x) { - x = Array(count) - for (let i = 0; i < count; i++) { - x[i] = i + if(!x) { + x = Array(count); + for(i = 0; i < count; i++) { + x[i] = i; } } - if (!y) { - y = Array(count) - for (let i = 0; i < count; i++) { - y[i] = i + if(!y) { + y = Array(count); + for(i = 0; i < count; i++) { + y[i] = i; } } var lineOptions, scatterOptions, errorXOptions, errorYOptions, fillOptions; - var isVisible, hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill; + var hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill; var linePositions; // get log converted positions var rawx, rawy; if(xaxis.type === 'log') { - var rawx = Array(x.length); + rawx = Array(x.length); for(i = 0, l = x.length; i < l; i++) { rawx[i] = x[i]; x[i] = xaxis.d2l(x[i]); @@ -91,9 +91,9 @@ ScatterRegl.calc = function calc(container, trace) { } } if(yaxis.type === 'log') { - var rawy = Array(y.length); + rawy = Array(y.length); for(i = 0, l = y.length; i < l; i++) { - rawy[i] = y[i] + rawy[i] = y[i]; y[i] = yaxis.d2l(y[i]); } } @@ -121,13 +121,13 @@ ScatterRegl.calc = function calc(container, 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') { + 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++) { + for(i = 0; i < count; i++) { ids[i] = i; } } @@ -141,7 +141,6 @@ ScatterRegl.calc = function calc(container, trace) { stash.count = count; if(trace.visible !== true) { - isVisible = false; hasLines = false; hasErrorX = false; hasErrorY = false; @@ -149,7 +148,6 @@ ScatterRegl.calc = function calc(container, trace) { hasFill = false; } else { - isVisible = true; hasLines = subTypes.hasLines(trace) && positions.length > 2; hasErrorX = trace.error_x.visible === true; hasErrorY = trace.error_y.visible === true; @@ -259,8 +257,8 @@ ScatterRegl.calc = function calc(container, trace) { // 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])) { + for(i = 0; i < linePositions.length; i++) { + if(isNaN(linePositions[i])) { hasNaN = true; break; } @@ -268,12 +266,12 @@ ScatterRegl.calc = function calc(container, trace) { lineOptions.join = (hasNaN || linePositions.length > TOO_MANY_POINTS) ? 'rect' : hasMarkers ? 'rect' : 'round'; // fill gaps - if (hasNaN && trace.connectgaps) { + 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 + 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]; @@ -367,12 +365,12 @@ ScatterRegl.calc = function calc(container, trace) { // prepare sizes and expand axes var multiSize = markerOpts && (Array.isArray(markerOpts.size) || Array.isArray(markerOpts.line.width)); - var msx, msy, xbounds = [Infinity, -Infinity], ybounds = [Infinity, -Infinity]; + var xbounds = [Infinity, -Infinity], ybounds = [Infinity, -Infinity]; var markerSizeFunc = makeBubbleSizeFn(trace); + var size, sizes; if(multiSize) { - var size; - var sizes = scatterOptions.sizes = new Array(count); + sizes = scatterOptions.sizes = new Array(count); var borderSizes = scatterOptions.borderSizes = new Array(count); if(Array.isArray(markerOpts.size)) { @@ -404,12 +402,12 @@ ScatterRegl.calc = function calc(container, trace) { Axes.expand(yaxis, stash.rawy, { padded: true, ppad: sizes }); } else { - var size = scatterOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); + size = scatterOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); scatterOptions.borderSizes = markerOpts.line.width * 0.5; // axes bounds for(i = 0; i < count; i++) { - xx = x[i], yy = y[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; @@ -417,7 +415,7 @@ ScatterRegl.calc = function calc(container, trace) { } // FIXME: is there a better way to separate expansion? - if (count < TOO_MANY_POINTS) { + if(count < TOO_MANY_POINTS) { Axes.expand(xaxis, stash.rawx, { padded: true, ppad: size }); Axes.expand(yaxis, stash.rawy, { padded: true, ppad: size }); } @@ -440,7 +438,7 @@ ScatterRegl.calc = function calc(container, trace) { } } } - //expand no-markers axes + // expand no-markers axes else { Axes.expand(xaxis, stash.rawx, { padded: true }); Axes.expand(yaxis, stash.rawy, { padded: true }); @@ -503,34 +501,34 @@ ScatterRegl.calc = function calc(container, trace) { // highlight selected points scene.select = function select(selection) { - if (!scene.select2d) return; + if(!scene.select2d) return; scene.select2d.regl.clear({color: true}); - if (!selection.length) return; + if(!selection.length) return; - var options = selection.map(function (points) { - if (!points || !points.length) return null; + var options = selection.map(function(points) { + if(!points || !points.length) return null; var elements = Array(points.length); - for (var i = 0; i < points.length; i++) { + for(var i = 0; i < points.length; i++) { elements[i] = points[i].pointNumber; } - return elements + return elements; }); scene.select2d.draw(options); - //FIXME: this can be a strong guess, possibly we can redraw scene once + // FIXME: this can be a strong guess, possibly we can redraw scene once scene.scatter2d.regl.clear({color: true}); scene.draw(); - } + }; } else { - if (hasFill && !scene.fill2d) scene.fill2d = true; - if (hasMarkers && !scene.marker2d) scene.marker2d = true; - if (hasLines && !scene.line2d) scene.line2d = true; - if (hasError && !scene.error2d) scene.error2d = true; + if(hasFill && !scene.fill2d) scene.fill2d = true; + if(hasMarkers && !scene.marker2d) scene.marker2d = 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 @@ -552,7 +550,7 @@ ScatterRegl.calc = function calc(container, trace) { scene.scatterOptions.push(scatterOptions); scene.count++; - //stash scene ref + // stash scene ref stash.scene = scene; return [{x: false, y: false, t: stash, trace: trace}]; @@ -684,18 +682,18 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { // make sure selection layer is initialized if we require selection var dragmode = layout.dragmode; - if (dragmode === 'lasso' || dragmode === 'select') { + if(dragmode === 'lasso' || dragmode === 'select') { if(!scene.select2d && scene.scatter2d) { var selectRegl = layout._glcanvas.data()[1].regl; scene.select2d = createScatter(selectRegl); - //TODO: modify options here according to the proposed selection options + // TODO: modify options here according to the proposed selection options scene.select2d.update(scene.scatterOptions); } } else { - if (scene.select2d) scene.select2d.regl.clear({color: true}); + if(scene.select2d) scene.select2d.regl.clear({color: true}); } // provide viewport and range @@ -721,8 +719,8 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { (height - vpSize.t) - (1 - yaxis.domain[1]) * vpSize.h ]; - if (dragmode === 'lasso' || dragmode === 'select') { - //precalculate px coords since we are not going to pan during select + if(dragmode === 'lasso' || dragmode === 'select') { + // precalculate px coords since we are not going to pan during select var xpx = Array(stash.count), ypx = Array(stash.count); for(var i = 0; i < stash.count; i++) { xpx[i] = xaxis.c2p(trace.x[i]); @@ -734,10 +732,10 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { else { stash.xpx = stash.ypx = null; - //reset opacities - if (scene.scatter2d) { - scene.scatter2d.update(scene.scatterOptions.map(function (opt) { - return {opacity: opt ? opt.opacity : 1} + // reset opacities + if(scene.scatter2d) { + scene.scatter2d.update(scene.scatterOptions.map(function(opt) { + return {opacity: opt ? opt.opacity : 1}; })); } } @@ -761,7 +759,7 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { if(scene.scatter2d) { scene.scatter2d.update(vpRange); } - if (scene.select2d) { + if(scene.select2d) { scene.select2d.update(vpRange); } @@ -777,17 +775,15 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval, hovermode) { trace = cd[0].trace, xa = pointData.xa, ya = pointData.ya, - positions = stash.positions, x = stash.rawx, y = stash.rawy, - scene = stash.scene, 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') { + if(stash.tree) { + if(hovermode === 'x') { ids = stash.tree.range( xa.p2c(xpx - MAXDIST), ya._rl[0], xa.p2c(xpx + MAXDIST), ya._rl[1] @@ -800,19 +796,19 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval, hovermode) { ); } } - else if (stash.ids) { + 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; + var min = MAXDIST, id, ptx, pty, i, dx, dy, dist; - if (hovermode === 'x') { + if(hovermode === 'x') { for(i = 0; i < ids.length; i++) { ptx = x[ids[i]]; - var dx = Math.abs(xa.c2p(ptx) - xpx); + dx = Math.abs(xa.c2p(ptx) - xpx); if(dx < min) { min = dx; id = ids[i]; @@ -823,9 +819,9 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval, hovermode) { for(i = 0; i < ids.length; i++) { ptx = x[ids[i]]; pty = y[ids[i]]; - var dx = xa.c2p(ptx) - xpx, dy = ya.c2p(pty) - ypx; + dx = xa.c2p(ptx) - xpx, dy = ya.c2p(pty) - ypx; - var dist = Math.sqrt(dx * dx + dy * dy); + dist = Math.sqrt(dx * dx + dy * dy); if(dist < min) { min = dist; id = ids[i]; @@ -904,8 +900,6 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval, hovermode) { ScatterRegl.selectPoints = function select(searchInfo, polygon) { var cd = searchInfo.cd, - xa = searchInfo.xaxis, - ya = searchInfo.yaxis, selection = [], trace = cd[0].trace, stash = cd[0].t, @@ -921,11 +915,11 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { // degenerate polygon does not enable selection if(polygon === false || polygon.degenerate) { - if (scene.scatter2d) { - scene.scatter2d.update(scene.scatterOptions.map(function (opt) { - return {opacity: opt.opacity} + if(scene.scatter2d) { + scene.scatter2d.update(scene.scatterOptions.map(function(opt) { + return {opacity: opt.opacity}; })); - }; + } } // filter out points by visible scatter ones else { @@ -940,11 +934,11 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { } // adjust selection transparency via canvas opacity - if (scene.scatter2d) { - scene.scatter2d.update(scene.scatterOptions.map(function (opt) { - return {opacity: opt.opacity * DESELECTDIM} + if(scene.scatter2d) { + scene.scatter2d.update(scene.scatterOptions.map(function(opt) { + return {opacity: opt.opacity * DESELECTDIM}; })); - }; + } } return selection; From be571af817bbd11655596708d1293ac8f78eb53a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 1 Nov 2017 16:44:24 -0400 Subject: [PATCH 101/151] Fix review comments --- src/traces/scatter/get_trace_color.js | 1 + src/traces/scatter/hover.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/scatter/get_trace_color.js b/src/traces/scatter/get_trace_color.js index bef495af0dd..cbf0708217c 100644 --- a/src/traces/scatter/get_trace_color.js +++ b/src/traces/scatter/get_trace_color.js @@ -17,6 +17,7 @@ module.exports = function getTraceColor(trace, di) { var lc, tc; // TODO: text modes + if(trace.mode === 'lines') { lc = trace.line.color; return (lc && Color.opacity(lc)) ? diff --git a/src/traces/scatter/hover.js b/src/traces/scatter/hover.js index 52579f45f5a..ee968926c64 100644 --- a/src/traces/scatter/hover.js +++ b/src/traces/scatter/hover.js @@ -52,7 +52,6 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) { Fx.getClosest(cd, distfn, pointData); - // skip the rest (for this trace) if we didn't find a close point if(pointData.index !== false) { From fe9935dd676248d9a9e5b8004d32d024207a02e2 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 1 Nov 2017 16:45:48 -0400 Subject: [PATCH 102/151] Kill old scattergl stuff --- src/traces/scattergl/convert.js | 769 -------------------------------- 1 file changed, 769 deletions(-) delete mode 100644 src/traces/scattergl/convert.js diff --git a/src/traces/scattergl/convert.js b/src/traces/scattergl/convert.js deleted file mode 100644 index af7055ccf24..00000000000 --- a/src/traces/scattergl/convert.js +++ /dev/null @@ -1,769 +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 }); - // console.log(min, max) - } -}; - -// 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; From 88e2014d2c61e614f810fd7a420452f121b7aa3c Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 2 Nov 2017 16:41:00 -0400 Subject: [PATCH 103/151] Fix merging artifacts --- src/plots/cartesian/select.js | 1 - src/traces/parcoords/attributes.js | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 01366ea1fbd..af1a52e7fe7 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -193,7 +193,6 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { filterPoly.addPt([x1, y1]); currentPolygon = filterPoly.filtered; } - outlines.attr('d', 'M' + paths.join('M') + 'Z'); // create outline & tester if(dragOptions.polygons && dragOptions.polygons.length) { diff --git a/src/traces/parcoords/attributes.js b/src/traces/parcoords/attributes.js index d143fb6f6ef..d7b34b20d9d 100644 --- a/src/traces/parcoords/attributes.js +++ b/src/traces/parcoords/attributes.js @@ -153,20 +153,6 @@ module.exports = { 'The default value is false, so that `parcoords` colorscale can default to `Viridis`.' ].join(' ') } - - colorscale: {dflt: colorscales.Viridis}, - autocolorscale: { - dflt: false, - description: [ - 'Has an effect only if line.color` is set to a numerical array.', - 'Determines whether the colorscale is a default palette (`autocolorscale: true`)', - 'or the palette determined by `line.colorscale`.', - 'In case `colorscale` is unspecified or `autocolorscale` is true, the default ', - 'palette will be chosen according to whether numbers in the `color` array are', - 'all positive, all negative or mixed.', - 'The default value is false, so that `parcoords` colorscale can default to `Viridis`.' - ].join(' ') - } } ), From 78d337239dc24faa75740561c1c66fbedfdb14dc Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 2 Nov 2017 16:46:30 -0400 Subject: [PATCH 104/151] Fix axes test case --- test/jasmine/tests/axes_test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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() { From 289e2c9f8d0439cd0eb022f7396f04cc0b5bfbf1 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 3 Nov 2017 17:50:24 -0400 Subject: [PATCH 105/151] Fix multiselect and interaction details --- src/plot_api/plot_api.js | 2 +- src/plots/cartesian/select.js | 25 ++++++++++++------------- src/traces/scatter/select.js | 2 +- src/traces/scattergl/index.js | 35 +++++++++++++++++++++-------------- 4 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index f25423c34d9..2fe1df2a284 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -2039,7 +2039,7 @@ function _relayout(gd, aobj) { else flags.plot = true; } else { - if(fullLayout._has('gl2d') && + if((fullLayout._has('gl2d') || fullLayout._has('regl')) && (ai === 'dragmode' && (vi === 'lasso' || vi === 'select') && !(vOld === 'lasso' || vOld === 'select')) diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index fb08ccfabf0..a1ca78bbb5c 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -118,24 +118,21 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { fillRangeItems = plotinfo.fillRangeItems; } else { if(mode === 'select') { - // FIXME: this is regression - fillRangeItems = function(eventData, currentPolygon) { + fillRangeItems = function(eventData, poly) { var ranges = eventData.range = {}; for(i = 0; i < allAxes.length; i++) { var ax = allAxes[i]; var axLetter = ax._id.charAt(0); - var x = axLetter === 'x'; - // FIXME: this should be fixed to read xmin/xmax, ymin/ymax ranges[ax._id] = [ - ax.p2d(currentPolygon[0][x ? 0 : 1]), - ax.p2d(currentPolygon[2][x ? 0 : 1]) + ax.p2d(poly[axLetter + 'min']), + ax.p2d(poly[axLetter + 'max']) ].sort(ascending); } }; } else { - fillRangeItems = function(eventData, currentPolygon, filterPoly) { + fillRangeItems = function(eventData, poly, filterPoly) { var dataPts = eventData.lassoPoints = {}; for(i = 0; i < allAxes.length; i++) { @@ -220,15 +217,12 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { function() { selection = []; - var traceSelections = [], traceSelection; + var thisSelection; 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( + searchInfo.selectPoints(searchInfo, testPoly), searchInfo ); if(selection.length) { for(var j = 0; j < thisSelection.length; j++) { @@ -238,6 +232,11 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { else selection = thisSelection; } + // update selection scene + if (plotinfo._scene) { + plotinfo._scene.select(selection) + } + eventData = {points: selection}; fillRangeItems(eventData, currentPolygon, filterPoly); dragOptions.gd.emit('plotly_selecting', eventData); diff --git a/src/traces/scatter/select.js b/src/traces/scatter/select.js index 7dd2b1cd1c4..ccff36175a6 100644 --- a/src/traces/scatter/select.js +++ b/src/traces/scatter/select.js @@ -30,7 +30,7 @@ module.exports = function selectPoints(searchInfo, polygon) { var opacity = Array.isArray(marker.opacity) ? 1 : marker.opacity; - if(polygon === false) { // clear selection + if(polygon === false || polygon.degenerate) { // clear selection for(i = 0; i < cd.length; i++) cd[i].dim = 0; } else { diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 5d13e073c7d..ef80cdaf2ab 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -502,24 +502,22 @@ ScatterRegl.calc = function calc(container, trace) { // highlight selected points scene.select = function select(selection) { if(!scene.select2d) return; + if(!selection.length) return; scene.select2d.regl.clear({color: true}); - if(!selection.length) return; - - var options = selection.map(function(points) { - if(!points || !points.length) return null; + var batch = Array(scene.count), i, traceId; + for (i = 0; i < scene.count; i++) { + batch[i] = [] + } - var elements = Array(points.length); - for(var i = 0; i < points.length; i++) { - elements[i] = points[i].pointNumber; - } - return elements; - }); + for(i = 0; i < selection.length; i++) { + var traceId = selection[i].curveNumber || 0; + batch[traceId].push(selection[i].pointNumber); + } - scene.select2d.draw(options); + scene.select2d.draw(batch); - // FIXME: this can be a strong guess, possibly we can redraw scene once scene.scatter2d.regl.clear({color: true}); scene.draw(); }; @@ -702,6 +700,8 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { 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'); @@ -723,8 +723,8 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { // precalculate px coords since we are not going to pan during select var xpx = Array(stash.count), ypx = Array(stash.count); for(var i = 0; i < stash.count; i++) { - xpx[i] = xaxis.c2p(trace.x[i]); - ypx[i] = yaxis.c2p(trace.y[i]); + xpx[i] = xaxis.c2p(x[i]); + ypx[i] = yaxis.c2p(y[i]); } stash.xpx = xpx; stash.ypx = ypx; @@ -923,8 +923,11 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { } // filter out points by visible scatter ones else { + let els = [] + for(var 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], @@ -939,7 +942,11 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { return {opacity: opt.opacity * DESELECTDIM}; })); } + + // update scattergl selection + scene.select(selection); } + return selection; }; From 5a9adf54f72bfc8afdaa8a93935d901ebe6b8b93 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 6 Nov 2017 16:40:58 -0500 Subject: [PATCH 106/151] Fix selection scattergl cases --- src/components/fx/hover.js | 5 +++-- src/traces/scattergl/index.js | 25 ++++++++++++++++++++- test/jasmine/tests/gl2d_click_test.js | 31 +++++++++------------------ 3 files changed, 37 insertions(+), 24 deletions(-) diff --git a/src/components/fx/hover.js b/src/components/fx/hover.js index 37d80fbc126..e1173a65387 100644 --- a/src/components/fx/hover.js +++ b/src/components/fx/hover.js @@ -538,7 +538,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; @@ -1096,8 +1096,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/traces/scattergl/index.js b/src/traces/scattergl/index.js index ef80cdaf2ab..49c12dbf94b 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -26,6 +26,7 @@ var createScatter = require('regl-scatter2d'); var createLine = require('regl-line2d'); var createError = require('regl-error2d'); var svgSdf = require('svg-path-sdf'); +var fillHoverText = require('../scatter/fill_hover_text'); var DESELECTDIM = require('../../constants/interactions').DESELECTDIM; var MAXDIST = Fx.constants.MAXDIST; @@ -876,6 +877,24 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval, hovermode) { 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), @@ -885,13 +904,17 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval, hovermode) { y0: yc - rad, y1: yc + rad, - yLabelVal: di.y + yLabelVal: di.y, + + cd: fakeCd }); if(di.htx) pointData.text = di.htx; else if(trace.hovertext) pointData.text = trace.hovertext; 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]; diff --git a/test/jasmine/tests/gl2d_click_test.js b/test/jasmine/tests/gl2d_click_test.js index 54dd23ee1aa..d8ba00c609e 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'; }) @@ -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); From cb355dd7b103bd7488d0c59775e77063ced886fa Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 6 Nov 2017 17:59:32 -0500 Subject: [PATCH 107/151] Fix one plot interaction case --- test/jasmine/tests/gl_plot_interact_test.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js index a2796146ac9..5faeff438ae 100644 --- a/test/jasmine/tests/gl_plot_interact_test.js +++ b/test/jasmine/tests/gl_plot_interact_test.js @@ -839,6 +839,7 @@ describe('Test gl2d plots', function() { beforeEach(function() { gd = createGraphDiv(); + jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000 }); afterEach(function() { @@ -855,12 +856,14 @@ describe('Test gl2d plots', function() { it('should respond to drag interactions', function(done) { var _mock = Lib.extendDeep({}, mock); + _mock.data[0].type = 'scatter' + 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 originalX = [-0.3169014084507042,5.316901408450704]; + var originalY = [-0.5806379476536665,6.218528262566369]; + var newX = [-0.5516431924882629,5.082159624413145]; + var newY = [-1.7947747709072441,5.004391439312791]; 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); From bedcdfa83af9d0a8941cea9effdd043f0b3f1f4c Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 8 Nov 2017 18:25:25 -0500 Subject: [PATCH 108/151] Fix gl_plot_interact visibility test --- src/plot_api/plot_api.js | 6 +- src/plots/cartesian/index.js | 2 +- src/plots/cartesian/select.js | 4 +- src/plots/plots.js | 2 - src/traces/scattergl/index.js | 88 +++++++++++---------- test/jasmine/assets/read_pixel.js | 13 +++ test/jasmine/tests/gl_plot_interact_test.js | 33 +++----- 7 files changed, 75 insertions(+), 73 deletions(-) create mode 100644 test/jasmine/assets/read_pixel.js diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 2fe1df2a284..712d400a0e8 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -196,8 +196,8 @@ Plotly.plot = function(gd, data, layout, config) { } } - if(!fullLayout._glcanvas) { - fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas').data(fullLayout._has('gl') ? [{ + if(!fullLayout._glcanvas && fullLayout._has('gl')) { + fullLayout._glcanvas = fullLayout._glcontainer.selectAll('.gl-canvas').data([{ key: 'contextLayer', context: true, pick: false @@ -209,7 +209,7 @@ Plotly.plot = function(gd, data, layout, config) { key: 'pickLayer', context: false, pick: true - }] : []); + }]); fullLayout._glcanvas.enter().append('canvas') .each(function(d) { diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index d4170ea439d..428cb89e25c 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -49,7 +49,7 @@ exports.plot = function(gd, traces, transitionOpts, makeOnCompleteCallback) { } // clear gl frame, if any, since we preserve drawing buffer - if(fullLayout._glcanvas.size()) { + if(fullLayout._glcanvas && fullLayout._glcanvas.size()) { fullLayout._glcanvas.each(function(d) { d.regl.clear({ color: true diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index a1ca78bbb5c..5e442b2ffa3 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -233,8 +233,8 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { } // update selection scene - if (plotinfo._scene) { - plotinfo._scene.select(selection) + if(plotinfo._scene) { + plotinfo._scene.select(selection); } eventData = {points: selection}; diff --git a/src/plots/plots.js b/src/plots/plots.js index fde654e646d..2fc5570d2b3 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -801,7 +801,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); @@ -847,7 +846,6 @@ plots.supplyDataDefaults = function(dataIn, dataOut, layout, fullLayout) { } } else { - // add identify refs for consistency with transformed traces fullTrace._fullInput = fullTrace; fullTrace._expandedInput = fullTrace; diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 49c12dbf94b..323760c09f6 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -72,7 +72,7 @@ ScatterRegl.calc = function calc(container, trace) { } } - var lineOptions, scatterOptions, errorXOptions, errorYOptions, fillOptions; + var lineOptions, markerOptions, errorXOptions, errorYOptions, fillOptions; var hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill; var linePositions; @@ -292,8 +292,8 @@ ScatterRegl.calc = function calc(container, trace) { } if(hasMarkers) { - scatterOptions = {}; - scatterOptions.positions = positions; + markerOptions = {}; + markerOptions.positions = positions; // get basic symbol info var multiMarker = Array.isArray(markerOpts.symbol); @@ -303,8 +303,8 @@ ScatterRegl.calc = function calc(container, trace) { } // prepare colors if(multiMarker || Array.isArray(markerOpts.color) || Array.isArray(markerOpts.line.color) || Array.isArray(markerOpts.line)) { - scatterOptions.colors = new Array(count); - scatterOptions.borderColors = new Array(count); + 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); @@ -324,8 +324,8 @@ ScatterRegl.calc = function calc(container, trace) { } } - scatterOptions.colors = colors; - scatterOptions.borderColors = borderColors; + markerOptions.colors = colors; + markerOptions.borderColors = borderColors; for(i = 0; i < count; i++) { if(multiMarker) { @@ -339,29 +339,29 @@ ScatterRegl.calc = function calc(container, trace) { } } - scatterOptions.opacity = trace.opacity; + markerOptions.opacity = trace.opacity; } else { - scatterOptions.color = markerOpts.color; - scatterOptions.borderColor = markerOpts.line.color; - scatterOptions.opacity = trace.opacity * markerOpts.opacity; + markerOptions.color = markerOpts.color; + markerOptions.borderColor = markerOpts.line.color; + markerOptions.opacity = trace.opacity * markerOpts.opacity; if(isOpen) { - scatterOptions.borderColor = scatterOptions.color.slice(); - scatterOptions.color = scatterOptions.color.slice(); - scatterOptions.color[3] = 0; + markerOptions.borderColor = markerOptions.color.slice(); + markerOptions.color = markerOptions.color.slice(); + markerOptions.color[3] = 0; } } // prepare markers if(Array.isArray(markerOpts.symbol)) { - scatterOptions.markers = new Array(count); + markerOptions.markers = new Array(count); for(i = 0; i < count; ++i) { - scatterOptions.markers[i] = getSymbolSdf(markerOpts.symbol[i]); + markerOptions.markers[i] = getSymbolSdf(markerOpts.symbol[i]); } } else { - scatterOptions.marker = getSymbolSdf(markerOpts.symbol); + markerOptions.marker = getSymbolSdf(markerOpts.symbol); } // prepare sizes and expand axes @@ -371,8 +371,8 @@ ScatterRegl.calc = function calc(container, trace) { var size, sizes; if(multiSize) { - sizes = scatterOptions.sizes = new Array(count); - var borderSizes = scatterOptions.borderSizes = new Array(count); + sizes = markerOptions.sizes = new Array(count); + var borderSizes = markerOptions.borderSizes = new Array(count); if(Array.isArray(markerOpts.size)) { for(i = 0; i < count; ++i) { @@ -403,8 +403,8 @@ ScatterRegl.calc = function calc(container, trace) { Axes.expand(yaxis, stash.rawy, { padded: true, ppad: sizes }); } else { - size = scatterOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); - scatterOptions.borderSizes = markerOpts.line.width * 0.5; + size = markerOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); + markerOptions.borderSizes = markerOpts.line.width * 0.5; // axes bounds for(i = 0; i < count; i++) { @@ -422,7 +422,7 @@ ScatterRegl.calc = function calc(container, trace) { } // update axes fast for big number of points else { - var pad = scatterOptions.size; + var pad = markerOptions.size; if(xaxis._min) { xaxis._min.push({ val: xbounds[0], pad: pad }); } @@ -460,7 +460,7 @@ ScatterRegl.calc = function calc(container, trace) { // last used options lineOptions: [], fillOptions: [], - scatterOptions: [], + markerOptions: [], errorXOptions: [], errorYOptions: [], @@ -508,12 +508,12 @@ ScatterRegl.calc = function calc(container, trace) { scene.select2d.regl.clear({color: true}); var batch = Array(scene.count), i, traceId; - for (i = 0; i < scene.count; i++) { - batch[i] = [] + for(i = 0; i < scene.count; i++) { + batch[i] = []; } for(i = 0; i < selection.length; i++) { - var traceId = selection[i].curveNumber || 0; + traceId = selection[i].curveNumber || 0; batch[traceId].push(selection[i].pointNumber); } @@ -536,17 +536,17 @@ ScatterRegl.calc = function calc(container, trace) { scene.count = 0; scene.lineOptions = []; scene.fillOptions = []; - scene.scatterOptions = []; + scene.markerOptions = []; scene.errorXOptions = []; scene.errorYOptions = []; } // save initial batch - scene.lineOptions.push(lineOptions); - scene.errorXOptions.push(errorXOptions); - scene.errorYOptions.push(errorYOptions); - scene.fillOptions.push(fillOptions); - scene.scatterOptions.push(scatterOptions); + 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.count++; // stash scene ref @@ -597,6 +597,7 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { if(!scene) return; var vpSize = layout._size, width = layout.width, height = layout.height; + var regl = layout._glcanvas.data()[0].regl; // that is needed for fills @@ -625,7 +626,7 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { scene.error2d.update(errorBatch); } if(scene.scatter2d) { - scene.scatter2d.update(scene.scatterOptions); + scene.scatter2d.update(scene.markerOptions); } // fill requires linked traces, so we generate it's positions here if(scene.fill2d) { @@ -681,6 +682,7 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { // make sure selection layer is initialized if we require selection var dragmode = layout.dragmode; + if(dragmode === 'lasso' || dragmode === 'select') { if(!scene.select2d && scene.scatter2d) { var selectRegl = layout._glcanvas.data()[1].regl; @@ -688,7 +690,7 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { scene.select2d = createScatter(selectRegl); // TODO: modify options here according to the proposed selection options - scene.select2d.update(scene.scatterOptions); + scene.select2d.update(scene.markerOptions); } } else { @@ -735,16 +737,16 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { // reset opacities if(scene.scatter2d) { - scene.scatter2d.update(scene.scatterOptions.map(function(opt) { + scene.scatter2d.update(scene.markerOptions.map(function(opt) { return {opacity: opt ? opt.opacity : 1}; })); } } - return { + return trace.visible ? { viewport: viewport, range: range - }; + } : null; }); // uploat batch data to GPU @@ -879,7 +881,7 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval, hovermode) { var hoverlabel = trace.hoverlabel; - if (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; @@ -888,7 +890,7 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval, hovermode) { di.hnl = Array.isArray(hoverlabel.namelength) ? hoverlabel.namelength[id] : hoverlabel.namelength; } var hoverinfo = trace.hoverinfo; - if (hoverinfo) { + if(hoverinfo) { di.hi = Array.isArray(hoverinfo) ? hoverinfo[id] : hoverinfo; } @@ -939,18 +941,18 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { // degenerate polygon does not enable selection if(polygon === false || polygon.degenerate) { if(scene.scatter2d) { - scene.scatter2d.update(scene.scatterOptions.map(function(opt) { + scene.scatter2d.update(scene.markerOptions.map(function(opt) { return {opacity: opt.opacity}; })); } } // filter out points by visible scatter ones else { - let els = [] + var els = []; for(var i = 0; i < stash.count; i++) { if(polygon.contains([stash.xpx[i], stash.ypx[i]])) { - els.push(i) + els.push(i); selection.push({ pointNumber: i, x: x[i], @@ -961,7 +963,7 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { // adjust selection transparency via canvas opacity if(scene.scatter2d) { - scene.scatter2d.update(scene.scatterOptions.map(function(opt) { + scene.scatter2d.update(scene.markerOptions.map(function(opt) { return {opacity: opt.opacity * DESELECTDIM}; })); } 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/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js index 5faeff438ae..a4d40466f22 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,7 +840,7 @@ describe('Test gl2d plots', function() { beforeEach(function() { gd = createGraphDiv(); - jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000 + jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000; }); afterEach(function() { @@ -856,14 +857,14 @@ describe('Test gl2d plots', function() { it('should respond to drag interactions', function(done) { var _mock = Lib.extendDeep({}, mock); - _mock.data[0].type = 'scatter' + _mock.data[0].type = 'scatter'; var relayoutCallback = jasmine.createSpy('relayoutCallback'); - var originalX = [-0.3169014084507042,5.316901408450704]; - var originalY = [-0.5806379476536665,6.218528262566369]; - var newX = [-0.5516431924882629,5.082159624413145]; - var newY = [-1.7947747709072441,5.004391439312791]; + var originalX = [-0.3169014084507042, 5.316901408450704]; + var originalY = [-0.5806379476536665, 6.218528262566369]; + var newX = [-0.5516431924882629, 5.082159624413145]; + var newY = [-1.7947747709072441, 5.004391439312791]; var precision = 5; Plotly.plot(gd, _mock) @@ -946,40 +947,28 @@ 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(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); }) .then(done); }); From 3e826eb59936c06d469730432b737b0c7144d1a1 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 8 Nov 2017 19:20:14 -0500 Subject: [PATCH 109/151] Make scattergl plot disposal --- src/plots/cartesian/dragbox.js | 2 +- src/plots/cartesian/index.js | 20 +++++++++ src/traces/scattergl/index.js | 15 +++++++ test/jasmine/tests/gl_plot_interact_test.js | 45 ++++++--------------- 4 files changed, 49 insertions(+), 33 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 76d8b55e91f..a3d3e4c288f 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -733,7 +733,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { // clear gl frame, if any, since we preserve drawing buffer // FIXME: code duplication with cartesian.plot - if(fullLayout._glcanvas.size()) { + if(fullLayout._glcanvas && fullLayout._glcanvas.size()) { fullLayout._glcanvas.each(function(d) { d.regl.clear({ color: true diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index 428cb89e25c..da4387e2c19 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -145,6 +145,26 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) var hadScatter, hasScatter, i; + // destruct scattergl + var oldSceneKeys = Plots.getSubplotIds(oldFullLayout, 'cartesian'); + + for(i = 0; i < oldSceneKeys.length; i++) { + var id = oldSceneKeys[i], + oldSubplot = oldFullLayout._plots[id]; + + // old subplot wasn't gl2d; nothing to do + if(!oldSubplot._scene) continue; + + // if no traces are present, delete gl2d subplot + var subplotData = Plots.getSubplotData(newFullData, 'gl', id); + + if(subplotData.length === 0) { + oldSubplot._scene.destroy(); + delete oldFullLayout._plots[id]; + } + } + + for(i = 0; i < oldModules.length; i++) { if(oldModules[i].name === 'scatter') { hadScatter = true; diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 323760c09f6..1bf5d64f68d 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -500,6 +500,21 @@ ScatterRegl.calc = function calc(container, trace) { scene.dirty = false; }; + // 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.errorXOptions = null; + scene.errorYOptions = null; + }; + // highlight selected points scene.select = function select(selection) { if(!scene.select2d) return; diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js index a4d40466f22..70736e256c4 100644 --- a/test/jasmine/tests/gl_plot_interact_test.js +++ b/test/jasmine/tests/gl_plot_interact_test.js @@ -857,14 +857,13 @@ describe('Test gl2d plots', function() { it('should respond to drag interactions', function(done) { var _mock = Lib.extendDeep({}, mock); - _mock.data[0].type = 'scatter'; var relayoutCallback = jasmine.createSpy('relayoutCallback'); - var originalX = [-0.3169014084507042, 5.316901408450704]; - var originalY = [-0.5806379476536665, 6.218528262566369]; - var newX = [-0.5516431924882629, 5.082159624413145]; - var newY = [-1.7947747709072441, 5.004391439312791]; + var originalX = [-0.3037383177570093, 5.303738317757009]; + var originalY = [-0.5532219548705213, 6.191112269783224]; + var newX = [-0.5373831775700935, 5.070093457943925]; + var newY = [-1.7575673521301185, 4.986766872523626]; var precision = 5; Plotly.plot(gd, _mock) @@ -973,25 +972,6 @@ describe('Test gl2d plots', function() { .then(done); }); - it('should clear orphan cartesian subplots on addTraces', function(done) { - Plotly.newPlot(gd, [], { - xaxis: { title: 'X' }, - yaxis: { title: 'Y' } - }) - .then(function() { - return Plotly.addTraces(gd, [{ - type: 'scattergl', - x: [1, 2, 3, 4, 5, 6, 7], - y: [0, 5, 8, 9, 8, 5, 0] - }]); - }) - .then(function() { - expect(d3.select('.xtitle').size()).toEqual(0); - expect(d3.select('.ytitle').size()).toEqual(0); - }) - .then(done); - }); - it('supports 1D and 2D Zoom', function(done) { var centerX, centerY; Plotly.newPlot(gd, @@ -1026,8 +1006,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); @@ -1073,8 +1053,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, @@ -1082,8 +1062,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); @@ -1136,9 +1116,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({}); }) .then(done); @@ -1188,7 +1169,7 @@ describe('Test removal of gl contexts', function() { .then(done); }); - it('Plotly.newPlot should remove gl context from the graph div of a gl2d plot', function(done) { + fit('Plotly.newPlot should remove gl context from the graph div of a gl2d plot', function(done) { var firstGlplotObject, firstGlContext, firstCanvas; Plotly.plot(gd, [{ From 18b9337a8488736f908a8353aab72300b1c1fae3 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 9 Nov 2017 14:56:17 -0500 Subject: [PATCH 110/151] Fix gl_plot_interact --- src/plots/cartesian/index.js | 28 +++++---------------- src/plots/plots.js | 4 ++- src/traces/scattergl/index.js | 3 ++- test/jasmine/tests/gl_plot_interact_test.js | 20 +++++++-------- 4 files changed, 21 insertions(+), 34 deletions(-) diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index da4387e2c19..6f611914d86 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -145,35 +145,16 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) var hadScatter, hasScatter, i; - // destruct scattergl - var oldSceneKeys = Plots.getSubplotIds(oldFullLayout, 'cartesian'); - - for(i = 0; i < oldSceneKeys.length; i++) { - var id = oldSceneKeys[i], - oldSubplot = oldFullLayout._plots[id]; - - // old subplot wasn't gl2d; nothing to do - if(!oldSubplot._scene) continue; - - // if no traces are present, delete gl2d subplot - var subplotData = Plots.getSubplotData(newFullData, 'gl', id); - - if(subplotData.length === 0) { - oldSubplot._scene.destroy(); - delete oldFullLayout._plots[id]; - } - } - for(i = 0; i < oldModules.length; i++) { - if(oldModules[i].name === 'scatter') { + if(oldModules[i].name === 'scatter' || oldModules[i].name === 'scattergl') { hadScatter = true; break; } } for(i = 0; i < newModules.length; i++) { - if(newModules[i].name === 'scatter') { + if(newModules[i].name === 'scatter' || newModules[i].name === 'scattergl') { hasScatter = true; break; } @@ -191,6 +172,10 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) .selectAll('g.trace') .remove(); } + + if(subplotInfo._scene) { + subplotInfo._scene.destroy(); + } } oldFullLayout._infolayer.selectAll('g.rangeslider-container') @@ -240,7 +225,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/plots.js b/src/plots/plots.js index 2fc5570d2b3..957c054d455 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1304,7 +1304,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. @@ -1327,6 +1326,9 @@ plots.purge = function(gd) { } } + // remove any planned throttles + Lib.clearThrottle(); + // data and layout delete gd.data; delete gd.layout; diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 1bf5d64f68d..ff75d9f8df1 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -54,7 +54,6 @@ ScatterRegl.calc = function calc(container, trace) { // 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'); @@ -513,6 +512,8 @@ ScatterRegl.calc = function calc(container, trace) { scene.markerOptions = null; scene.errorXOptions = null; scene.errorYOptions = null; + + delete subplot._scene; }; // highlight selected points diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js index 70736e256c4..6c926b01b16 100644 --- a/test/jasmine/tests/gl_plot_interact_test.js +++ b/test/jasmine/tests/gl_plot_interact_test.js @@ -1071,7 +1071,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(); @@ -1120,7 +1119,7 @@ describe('Test removal of gl contexts', function() { Plots.cleanPlot([], {}, gd._fullData, gd._fullLayout); - expect(gd._fullLayout._plots).toEqual({}); + expect(gd._fullLayout._plots.xy._scene).toBeUndefined(); }) .then(done); }); @@ -1169,7 +1168,7 @@ describe('Test removal of gl contexts', function() { .then(done); }); - fit('Plotly.newPlot should remove gl context from the graph div of a gl2d plot', function(done) { + it('Plotly.newPlot should remove gl context from the graph div of a gl2d plot', function(done) { var firstGlplotObject, firstGlContext, firstCanvas; Plotly.plot(gd, [{ @@ -1178,8 +1177,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(); @@ -1193,8 +1192,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); @@ -1276,7 +1275,8 @@ describe('Test gl plot side effects', function() { return Plotly.deleteTraces(gd, [0]); }).then(function() { - countCanvases(0); + // deleteTraces should not delete canvases since we may reuse them + countCanvases(3); return Plotly.purge(gd); }).then(done); @@ -1324,10 +1324,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], From 3d924b1cf72a9d5275e53d839ca5a7965c6b8416 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 9 Nov 2017 17:20:02 -0500 Subject: [PATCH 111/151] Fix gl2d_date_axis_render test --- .../tests/gl2d_date_axis_render_test.js | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) 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(); }); }); From 218cb83c32435ef4b90b45d25d0f084ffb40e5ee Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 10 Nov 2017 17:53:47 -0500 Subject: [PATCH 112/151] Bump deps --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 530d0a548d8..446ed5ff15f 100644 --- a/package.json +++ b/package.json @@ -97,9 +97,9 @@ "ndarray-ops": "^1.2.2", "polybooljs": "^1.2.0", "regl": "^1.3.0", - "regl-error2d": "^2.0.0", - "regl-line2d": "^2.0.0", - "regl-scatter2d": "^2.0.0", + "regl-error2d": "^2.0.1", + "regl-line2d": "^2.0.2", + "regl-scatter2d": "^2.0.4", "right-now": "^1.0.0", "robust-orientation": "^1.1.3", "sane-topojson": "^2.0.0", From 40e72f3b30c4770504c59e41c9d0de8c655249b5 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 13 Nov 2017 18:47:04 -0500 Subject: [PATCH 113/151] Handle multimarkers --- src/traces/scattergl/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index ff75d9f8df1..c0de332112b 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -301,10 +301,9 @@ ScatterRegl.calc = function calc(container, trace) { isOpen = /-open/.test(markerOpts.symbol); } // prepare colors - if(multiMarker || Array.isArray(markerOpts.color) || Array.isArray(markerOpts.line.color) || Array.isArray(markerOpts.line)) { + 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); From 97ff74132edf890240a2b817c1c9e25c3cf68f05 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 13 Nov 2017 18:48:14 -0500 Subject: [PATCH 114/151] Bump scatter2d dep --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 446ed5ff15f..a5047f51105 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,7 @@ "regl": "^1.3.0", "regl-error2d": "^2.0.1", "regl-line2d": "^2.0.2", - "regl-scatter2d": "^2.0.4", + "regl-scatter2d": "^2.0.5", "right-now": "^1.0.0", "robust-orientation": "^1.1.3", "sane-topojson": "^2.0.0", From b915f4d51d5ccfefd5b796379789a21843a28917 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 13 Nov 2017 19:37:08 -0500 Subject: [PATCH 115/151] Fix double click selection behaviour --- src/traces/scattergl/index.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index c0de332112b..2c5018086d3 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -518,7 +518,7 @@ ScatterRegl.calc = function calc(container, trace) { // highlight selected points scene.select = function select(selection) { if(!scene.select2d) return; - if(!selection.length) return; + // if(!selection.length) return; scene.select2d.regl.clear({color: true}); @@ -959,6 +959,13 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { scene.scatter2d.update(scene.markerOptions.map(function(opt) { return {opacity: opt.opacity}; })); + + scene.scatter2d.regl.clear({color: true}); + scene.draw(); + + if(scene.select2d) { + scene.select2d.regl.clear({color: true}); + } } } // filter out points by visible scatter ones @@ -987,6 +994,5 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { scene.select(selection); } - return selection; }; From 4156d83ddce1202065126392a41474f9106a993d Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 24 Nov 2017 14:38:55 -0500 Subject: [PATCH 116/151] Rearrange selection --- src/plots/cartesian/select.js | 2 +- src/traces/scattergl/index.js | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index f13e400d3ea..d5b21d77c74 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -233,7 +233,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { function() { selection = []; - var thisSelection; + var thisSelection, traceSelections = [], traceSelection; for(i = 0; i < searchTraces.length; i++) { searchInfo = searchTraces[i]; diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 2c5018086d3..c8f77421ee8 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -518,9 +518,12 @@ ScatterRegl.calc = function calc(container, trace) { // highlight selected points scene.select = function select(selection) { if(!scene.select2d) return; - // if(!selection.length) return; scene.select2d.regl.clear({color: true}); + scene.scatter2d.regl.clear({color: true}); + scene.draw(); + + if(!selection.length) return; var batch = Array(scene.count), i, traceId; for(i = 0; i < scene.count; i++) { @@ -531,11 +534,7 @@ ScatterRegl.calc = function calc(container, trace) { traceId = selection[i].curveNumber || 0; batch[traceId].push(selection[i].pointNumber); } - scene.select2d.draw(batch); - - scene.scatter2d.regl.clear({color: true}); - scene.draw(); }; } else { From e50b4a4286f3ea024949f3205e2d09edcca9afeb Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 24 Nov 2017 17:07:27 -0500 Subject: [PATCH 117/151] Start merging persistent selection --- src/plots/plots.js | 2 +- src/traces/scattergl/index.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/plots/plots.js b/src/plots/plots.js index d6eaeabccab..2177cd16f73 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1040,7 +1040,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'); } diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 28d8869954e..379d5d8c0c3 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -1009,3 +1009,8 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { return selection; }; + + +ScatterRegl.style = function style(container, cdata) { + // TODO +}; From 5425531d9068c7a2f8f34f16e5f1a57829a1d86f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 28 Nov 2017 13:30:13 -0500 Subject: [PATCH 118/151] Fix merged global-contexts issues --- src/plot_api/plot_api.js | 10 ++-------- src/plots/cartesian/select.js | 2 +- src/traces/parcoords/lines.js | 1 + src/traces/parcoords/parcoords.js | 1 + src/traces/scattergl/index.js | 7 ++++--- 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index c23fccd3f11..e40fb0263af 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -13,7 +13,6 @@ var d3 = require('d3'); var isNumeric = require('fast-isnumeric'); var hasHover = require('has-hover'); -var createRegl = require('regl'); var Plotly = require('../plotly'); var Lib = require('../lib'); @@ -221,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([ diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 3b3e7a03b17..37d911edc90 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -240,7 +240,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { 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++) { diff --git a/src/traces/parcoords/lines.js b/src/traces/parcoords/lines.js index 2b7ec1c4623..31d861eb99e 100644 --- a/src/traces/parcoords/lines.js +++ b/src/traces/parcoords/lines.js @@ -433,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/scattergl/index.js b/src/traces/scattergl/index.js index 379d5d8c0c3..70f2c2bf442 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -615,13 +615,14 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { // make sure proper regl instances are created layout._glcanvas.each(function(d) { - if(d.regl) return; + 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 }); }); @@ -1011,6 +1012,6 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { }; -ScatterRegl.style = function style(container, cdata) { +/*ScatterRegl.style = function style(container, cdata) { // TODO -}; +};*/ From a91530ca9892ec07320aa8d10028290acddf7533 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 28 Nov 2017 18:01:10 -0500 Subject: [PATCH 119/151] Upgrade bundling to uglify-es --- package.json | 3 ++- tasks/bundle.js | 2 ++ tasks/util/browserify_wrapper.js | 40 ++++++++++++++++---------------- tasks/util/constants.js | 21 +++++++++++++---- tasks/util/patch_minified.js | 22 ------------------ 5 files changed, 41 insertions(+), 47 deletions(-) delete mode 100644 tasks/util/patch_minified.js diff --git a/package.json b/package.json index a5047f51105..b361158c93b 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "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", @@ -88,6 +89,7 @@ "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", @@ -146,7 +148,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/tasks/bundle.js b/tasks/bundle.js index b1a175d4654..f3062c1d115 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' @@ -53,3 +54,4 @@ constants.partialBundlePaths.forEach(function(pathObj) { pathToMinBundle: pathObj.distMin }); }); + diff --git a/tasks/util/browserify_wrapper.js b/tasks/util/browserify_wrapper.js index fa57092764e..3fd477a584f 100644 --- a/tasks/util/browserify_wrapper.js +++ b/tasks/util/browserify_wrapper.js @@ -2,11 +2,11 @@ var fs = require('fs'); var path = require('path'); var browserify = require('browserify'); -var UglifyJS = require('uglify-js'); +var bubleify = require('bubleify'); +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 +46,30 @@ 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.transform(bubleify, constants.bubleifyOptions); - b.bundle(function(err, buf) { + var bundleStream = b.bundle(function(err, buf) { 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..3f65761597a 100644 --- a/tasks/util/constants.js +++ b/tasks/util/constants.js @@ -84,16 +84,29 @@ 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 + }, + + bubleifyOptions: { + target: { + chrome: 48, + firefox: 44, + edge: 12 + }, + transforms: { + arrow: true, + defaultParameter: false, + dangerousForOf: 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); -}; From 328af5fdd5442bc4fcb8fb3ac699a6bce0ec86c8 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 28 Nov 2017 18:01:10 -0500 Subject: [PATCH 120/151] Upgrade bundling to uglify-es Conflicts: package.json --- package.json | 4 +++- tasks/bundle.js | 2 ++ tasks/util/browserify_wrapper.js | 40 ++++++++++++++++---------------- tasks/util/constants.js | 21 +++++++++++++---- tasks/util/patch_minified.js | 22 ------------------ 5 files changed, 42 insertions(+), 47 deletions(-) delete mode 100644 tasks/util/patch_minified.js diff --git a/package.json b/package.json index 303f6c4993e..527c436dba1 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", @@ -86,6 +88,7 @@ "has-hover": "^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", @@ -140,7 +143,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/tasks/bundle.js b/tasks/bundle.js index b1a175d4654..f3062c1d115 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' @@ -53,3 +54,4 @@ constants.partialBundlePaths.forEach(function(pathObj) { pathToMinBundle: pathObj.distMin }); }); + diff --git a/tasks/util/browserify_wrapper.js b/tasks/util/browserify_wrapper.js index fa57092764e..3fd477a584f 100644 --- a/tasks/util/browserify_wrapper.js +++ b/tasks/util/browserify_wrapper.js @@ -2,11 +2,11 @@ var fs = require('fs'); var path = require('path'); var browserify = require('browserify'); -var UglifyJS = require('uglify-js'); +var bubleify = require('bubleify'); +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 +46,30 @@ 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.transform(bubleify, constants.bubleifyOptions); - b.bundle(function(err, buf) { + var bundleStream = b.bundle(function(err, buf) { 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..3f65761597a 100644 --- a/tasks/util/constants.js +++ b/tasks/util/constants.js @@ -84,16 +84,29 @@ 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 + }, + + bubleifyOptions: { + target: { + chrome: 48, + firefox: 44, + edge: 12 + }, + transforms: { + arrow: true, + defaultParameter: false, + dangerousForOf: 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); -}; From 41be06ff506856732bc336ec6ef074baa05b07e6 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 28 Nov 2017 18:07:33 -0500 Subject: [PATCH 121/151] Remove unused dep --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 527c436dba1..fa8620db081 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,6 @@ "@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", From a399671f69d5ebf3b87770c468d0cb701bc59a73 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 28 Nov 2017 18:25:10 -0500 Subject: [PATCH 122/151] Lintify --- tasks/bundle.js | 1 - tasks/util/browserify_wrapper.js | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/tasks/bundle.js b/tasks/bundle.js index f3062c1d115..dd767f018ff 100644 --- a/tasks/bundle.js +++ b/tasks/bundle.js @@ -54,4 +54,3 @@ constants.partialBundlePaths.forEach(function(pathObj) { pathToMinBundle: pathObj.distMin }); }); - diff --git a/tasks/util/browserify_wrapper.js b/tasks/util/browserify_wrapper.js index 3fd477a584f..e4803d94e8c 100644 --- a/tasks/util/browserify_wrapper.js +++ b/tasks/util/browserify_wrapper.js @@ -49,11 +49,11 @@ module.exports = function _bundle(pathToIndex, pathToBundle, opts) { b.transform(bubleify, constants.bubleifyOptions); - var bundleStream = b.bundle(function(err, buf) { + var bundleStream = b.bundle(function(err) { if(err) throw err; - }) + }); - if (outputMinified) { + if(outputMinified) { bundleStream .pipe(minify(constants.uglifyOptions)) .pipe(fs.createWriteStream(pathToMinBundle)) From a573fdf0ee4771b887bc334b467a5b87d5c130d0 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 28 Nov 2017 18:31:50 -0500 Subject: [PATCH 123/151] Fix bundling --- src/traces/scattergl/index.js | 2 +- tasks/bundle.js | 1 - tasks/util/browserify_wrapper.js | 6 +++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 70f2c2bf442..c3292558878 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -1012,6 +1012,6 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { }; -/*ScatterRegl.style = function style(container, cdata) { +/* ScatterRegl.style = function style(container, cdata) { // TODO };*/ diff --git a/tasks/bundle.js b/tasks/bundle.js index f3062c1d115..dd767f018ff 100644 --- a/tasks/bundle.js +++ b/tasks/bundle.js @@ -54,4 +54,3 @@ constants.partialBundlePaths.forEach(function(pathObj) { pathToMinBundle: pathObj.distMin }); }); - diff --git a/tasks/util/browserify_wrapper.js b/tasks/util/browserify_wrapper.js index 3fd477a584f..e4803d94e8c 100644 --- a/tasks/util/browserify_wrapper.js +++ b/tasks/util/browserify_wrapper.js @@ -49,11 +49,11 @@ module.exports = function _bundle(pathToIndex, pathToBundle, opts) { b.transform(bubleify, constants.bubleifyOptions); - var bundleStream = b.bundle(function(err, buf) { + var bundleStream = b.bundle(function(err) { if(err) throw err; - }) + }); - if (outputMinified) { + if(outputMinified) { bundleStream .pipe(minify(constants.uglifyOptions)) .pipe(fs.createWriteStream(pathToMinBundle)) From a053c2668734e740b2fd45e83acd530b241fff49 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 28 Nov 2017 18:54:41 -0500 Subject: [PATCH 124/151] Use cross-spawn for windows stats calc --- package.json | 1 + tasks/stats.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index fa8620db081..842125b5353 100644 --- a/package.json +++ b/package.json @@ -111,6 +111,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", 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'); From 160871e0059a9dd5df8d5446b933c939bd2cc0e2 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 28 Nov 2017 20:59:56 -0500 Subject: [PATCH 125/151] Introduce simple selected/unselected options --- src/plots/cartesian/select.js | 9 +-- src/traces/scattergl/index.js | 115 +++++++++++++++++----------------- 2 files changed, 58 insertions(+), 66 deletions(-) diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 37d911edc90..43d41885f07 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -250,11 +250,6 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { else selection = thisSelection; } - // update selection scene - if(plotinfo._scene) { - plotinfo._scene.select(selection); - } - eventData = {points: selection}; updateSelectedState(gd, searchTraces, eventData); fillRangeItems(eventData, currentPolygon, filterPoly); @@ -314,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); diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index c3292558878..29278809637 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -72,7 +72,7 @@ ScatterRegl.calc = function calc(container, trace) { } } - var lineOptions, markerOptions, errorXOptions, errorYOptions, fillOptions; + var lineOptions, markerOptions, errorXOptions, errorYOptions, fillOptions, selectedOptions, unselectedOptions; var hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill; var linePositions; @@ -292,7 +292,19 @@ ScatterRegl.calc = function calc(container, trace) { } if(hasMarkers) { - markerOptions = {}; + markerOptions = makeMarkerOptions(markerOpts); + selectedOptions = trace.selected ? makeMarkerOptions(extend({}, markerOpts, trace.selected.marker)) : markerOptions; + unselectedOptions = trace.unselected ? makeMarkerOptions(extend({}, markerOpts, trace.unselected.marker)) : markerOptions; + } + // expand no-markers axes + else { + Axes.expand(xaxis, stash.rawx, { padded: true }); + Axes.expand(yaxis, stash.rawy, { padded: true }); + } + + function makeMarkerOptions(markerOpts) { + var markerOptions = {}; + markerOptions.positions = positions; // get basic symbol info @@ -437,11 +449,8 @@ ScatterRegl.calc = function calc(container, trace) { } } } - } - // expand no-markers axes - else { - Axes.expand(xaxis, stash.rawx, { padded: true }); - Axes.expand(yaxis, stash.rawy, { padded: true }); + + return markerOptions; } @@ -460,6 +469,8 @@ ScatterRegl.calc = function calc(container, trace) { lineOptions: [], fillOptions: [], markerOptions: [], + selectedOptions: [], + unselectedOptions: [], errorXOptions: [], errorYOptions: [], @@ -510,33 +521,13 @@ ScatterRegl.calc = function calc(container, trace) { scene.lineOptions = null; scene.fillOptions = null; scene.markerOptions = null; + scene.selectedOptions = null; + scene.unselectedOptions = null; scene.errorXOptions = null; scene.errorYOptions = null; delete subplot._scene; }; - - // highlight selected points - scene.select = function select(selection) { - if(!scene.select2d) return; - - scene.select2d.regl.clear({color: true}); - scene.scatter2d.regl.clear({color: true}); - scene.draw(); - - if(!selection.length) return; - - var batch = Array(scene.count), i, traceId; - for(i = 0; i < scene.count; i++) { - batch[i] = []; - } - - for(i = 0; i < selection.length; i++) { - traceId = selection[i].curveNumber || 0; - batch[traceId].push(selection[i].pointNumber); - } - scene.select2d.draw(batch); - }; } else { if(hasFill && !scene.fill2d) scene.fill2d = true; @@ -552,6 +543,8 @@ ScatterRegl.calc = function calc(container, trace) { scene.lineOptions = []; scene.fillOptions = []; scene.markerOptions = []; + scene.selectedOptions = []; + scene.unselectedOptions = []; scene.errorXOptions = []; scene.errorYOptions = []; } @@ -562,6 +555,8 @@ ScatterRegl.calc = function calc(container, trace) { 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 @@ -717,13 +712,14 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { var selectRegl = layout._glcanvas.data()[1].regl; scene.select2d = createScatter(selectRegl); - - // TODO: modify options here according to the proposed selection options - scene.select2d.update(scene.markerOptions); } + + scene.select2d.update(scene.selectedOptions); + scene.scatter2d.update(scene.unselectedOptions); } else { if(scene.select2d) scene.select2d.regl.clear({color: true}); + scene.scatter2d.update(scene.markerOptions); } // provide viewport and range @@ -968,22 +964,8 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { if(trace.visible !== true || hasOnlyLines) return selection; // degenerate polygon does not enable selection - if(polygon === false || polygon.degenerate) { - if(scene.scatter2d) { - scene.scatter2d.update(scene.markerOptions.map(function(opt) { - return {opacity: opt.opacity}; - })); - - scene.scatter2d.regl.clear({color: true}); - scene.draw(); - - if(scene.select2d) { - scene.select2d.regl.clear({color: true}); - } - } - } // filter out points by visible scatter ones - else { + if(polygon !== false && !polygon.degenerate) { var els = []; for(var i = 0; i < stash.count; i++) { @@ -996,22 +978,37 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { }); } } + } - // adjust selection transparency via canvas opacity - if(scene.scatter2d) { - scene.scatter2d.update(scene.markerOptions.map(function(opt) { - return {opacity: opt.opacity * DESELECTDIM}; - })); - } + // clear scene ready for selection + scene.select2d.regl.clear({color: true}); + scene.scatter2d.regl.clear({color: true}); - // update scattergl selection - scene.select(selection); - } + scene.scatter2d.update(scene.markerOptions.map(function(opt) { + return {opacity: opt.opacity * DESELECTDIM}; + })); + + scene.draw(); return selection; }; -/* ScatterRegl.style = function style(container, cdata) { - // TODO -};*/ +ScatterRegl.style = function style(container, cdata) { + if (!cdata) return; + + var trace = cdata[0].trace; + var pts = trace.selectedpoints; + var stash = cdata[0].t; + var scene = stash.scene; + + if(!scene.select2d) return; + + + // if(!pts.length) return; + + var batch = Array(scene.count); + batch[trace.index || 0] = pts; + + scene.select2d.draw(batch); +}; From 850595e47847214c02391901bf9bb57579efde09 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 29 Nov 2017 15:26:02 -0500 Subject: [PATCH 126/151] Fix scattergl persistent selection --- src/plots/cartesian/select.js | 5 ++ src/traces/scattergl/index.js | 93 +++++++++++++++++++++-------------- 2 files changed, 61 insertions(+), 37 deletions(-) diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 43d41885f07..a6e77c301b1 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -322,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/traces/scattergl/index.js b/src/traces/scattergl/index.js index 29278809637..0770b0c8810 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -28,7 +28,6 @@ var createError = require('regl-error2d'); var svgSdf = require('svg-path-sdf'); var createRegl = require('regl'); var fillHoverText = require('../scatter/fill_hover_text'); -var DESELECTDIM = require('../../constants/interactions').DESELECTDIM; var MAXDIST = Fx.constants.MAXDIST; var SYMBOL_SDF_SIZE = 200; @@ -473,6 +472,7 @@ ScatterRegl.calc = function calc(container, trace) { unselectedOptions: [], errorXOptions: [], errorYOptions: [], + selectBatch: null, // regl- component stubs, initialized in dirty plot call fill2d: hasFill, @@ -492,6 +492,8 @@ ScatterRegl.calc = function calc(container, trace) { 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(); }; @@ -507,9 +509,29 @@ ScatterRegl.calc = function calc(container, trace) { if(scene.scatter2d) scene.scatter2d.draw(i); } + // persistent selection draw + if(scene.select2d && scene.selectBatch) { + scene.select2d.draw(scene.selectBatch); + } + scene.dirty = false; }; + // make sure canvas is clear + scene.clear = function clear() { + scene.select2d.regl.clear({color: true}); + scene.scatter2d.regl.clear({color: true}); + }; + + // remove selection + scene.clearSelect = function clearSelect() { + if(!scene.selectBatch) return; + scene.selectBatch = null; + scene.scatter2d.update(scene.markerOptions); + scene.clear(); + scene.draw(); + }; + // remove scene resources scene.destroy = function destroy() { if(scene.fill2d) scene.fill2d.destroy(); @@ -525,13 +547,14 @@ ScatterRegl.calc = function calc(container, trace) { scene.unselectedOptions = null; scene.errorXOptions = null; scene.errorYOptions = null; + scene.selectBatch = null; delete subplot._scene; }; } else { if(hasFill && !scene.fill2d) scene.fill2d = true; - if(hasMarkers && !scene.marker2d) scene.marker2d = true; + if(hasMarkers && !scene.scatter2d) scene.scatter2d = true; if(hasLines && !scene.line2d) scene.line2d = true; if(hasError && !scene.error2d) scene.error2d = true; } @@ -650,7 +673,13 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { scene.error2d.update(errorBatch); } if(scene.scatter2d) { - scene.scatter2d.update(scene.markerOptions); + 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) { @@ -710,16 +739,13 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { if(dragmode === 'lasso' || dragmode === 'select') { if(!scene.select2d && scene.scatter2d) { var selectRegl = layout._glcanvas.data()[1].regl; - scene.select2d = createScatter(selectRegl); + scene.select2d.update(scene.selectedOptions); + } + // in case if we keep selection + else if(scene.selectBatch) { + scene.scatter2d.update(scene.unselectedOptions); } - - scene.select2d.update(scene.selectedOptions); - scene.scatter2d.update(scene.unselectedOptions); - } - else { - if(scene.select2d) scene.select2d.regl.clear({color: true}); - scene.scatter2d.update(scene.markerOptions); } // provide viewport and range @@ -759,13 +785,6 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { } else { stash.xpx = stash.ypx = null; - - // reset opacities - if(scene.scatter2d) { - scene.scatter2d.update(scene.markerOptions.map(function(opt) { - return {opacity: opt ? opt.opacity : 1}; - })); - } } return trace.visible ? { @@ -965,9 +984,9 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { // degenerate polygon does not enable selection // filter out points by visible scatter ones + var els = null; if(polygon !== false && !polygon.degenerate) { - var els = []; - + els = []; for(var i = 0; i < stash.count; i++) { if(polygon.contains([stash.xpx[i], stash.ypx[i]])) { els.push(i); @@ -980,14 +999,14 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { } } - // clear scene ready for selection - scene.select2d.regl.clear({color: true}); - scene.scatter2d.regl.clear({color: true}); - - scene.scatter2d.update(scene.markerOptions.map(function(opt) { - return {opacity: opt.opacity * DESELECTDIM}; - })); + // create selection style once we have something selected + if(!scene.selectBatch) { + scene.selectBatch = Array(scene.count); + scene.scatter2d.update(scene.unselectedOptions); + } + scene.selectBatch[trace.index || 0] = els; + scene.clear(); scene.draw(); return selection; @@ -995,20 +1014,20 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { ScatterRegl.style = function style(container, cdata) { - if (!cdata) return; - - var trace = cdata[0].trace; - var pts = trace.selectedpoints; - var stash = cdata[0].t; - var scene = stash.scene; + if(!cdata) return; - if(!scene.select2d) return; + // var trace = cdata[0].trace; + // var pts = trace.selectedpoints; + // var stash = cdata[0].t; + // var scene = stash.scene; + // if(!scene.select2d) return; // if(!pts.length) return; - var batch = Array(scene.count); - batch[trace.index || 0] = pts; + // var selectBatch = Array(scene.count); + // selectBatch[trace.index || 0] = pts; - scene.select2d.draw(batch); + // scene.select2d.draw(selectBatch); + // scene.scatter2d.draw(); }; From 02fac50bd1af98b3d85e139bfe9e2517fb0e5fd9 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 29 Nov 2017 15:44:26 -0500 Subject: [PATCH 127/151] Hide unselected points --- src/traces/scattergl/index.js | 42 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 0770b0c8810..14c4501292a 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -473,6 +473,7 @@ ScatterRegl.calc = function calc(container, trace) { errorXOptions: [], errorYOptions: [], selectBatch: null, + unselectBatch: null, // regl- component stubs, initialized in dirty plot call fill2d: hasFill, @@ -506,12 +507,13 @@ ScatterRegl.calc = function calc(container, trace) { scene.error2d.draw(i); scene.error2d.draw(i + scene.count); } - if(scene.scatter2d) scene.scatter2d.draw(i); + 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; @@ -527,6 +529,7 @@ ScatterRegl.calc = function calc(container, trace) { scene.clearSelect = function clearSelect() { if(!scene.selectBatch) return; scene.selectBatch = null; + scene.unselectBatch = null; scene.scatter2d.update(scene.markerOptions); scene.clear(); scene.draw(); @@ -548,6 +551,7 @@ ScatterRegl.calc = function calc(container, trace) { scene.errorXOptions = null; scene.errorYOptions = null; scene.selectBatch = null; + scene.unselectBatch = null; delete subplot._scene; }; @@ -984,10 +988,10 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { // degenerate polygon does not enable selection // filter out points by visible scatter ones - var els = null; + var els = null, unels = null, i; if(polygon !== false && !polygon.degenerate) { - els = []; - for(var i = 0; i < stash.count; i++) { + els = [], unels = []; + for(i = 0; i < stash.count; i++) { if(polygon.contains([stash.xpx[i], stash.ypx[i]])) { els.push(i); selection.push({ @@ -996,15 +1000,26 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { 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[trace.index || 0] = els; + scene.unselectBatch[trace.index || 0] = unels; scene.clear(); scene.draw(); @@ -1013,21 +1028,4 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { }; -ScatterRegl.style = function style(container, cdata) { - if(!cdata) return; - - // var trace = cdata[0].trace; - // var pts = trace.selectedpoints; - // var stash = cdata[0].t; - // var scene = stash.scene; - - // if(!scene.select2d) return; - - // if(!pts.length) return; - - // var selectBatch = Array(scene.count); - // selectBatch[trace.index || 0] = pts; - - // scene.select2d.draw(selectBatch); - // scene.scatter2d.draw(); -}; +ScatterRegl.style = function style() {}; From 25fd2e4dde786b9524d371507e8438d1e893c322 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 29 Nov 2017 17:43:09 -0500 Subject: [PATCH 128/151] Remove bubleify --- package.json | 1 - tasks/util/browserify_wrapper.js | 3 --- tasks/util/constants.js | 14 -------------- 3 files changed, 18 deletions(-) diff --git a/package.json b/package.json index 842125b5353..1f15d00c564 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,6 @@ "3d-view": "^2.0.0", "@plotly/d3-sankey": "^0.5.0", "alpha-shape": "^1.0.0", - "bubleify": "^1.0.0", "color-rgba": "^1.1.1", "convex-hull": "^1.0.3", "country-regex": "^1.1.0", diff --git a/tasks/util/browserify_wrapper.js b/tasks/util/browserify_wrapper.js index e4803d94e8c..65a8de6c07b 100644 --- a/tasks/util/browserify_wrapper.js +++ b/tasks/util/browserify_wrapper.js @@ -2,7 +2,6 @@ var fs = require('fs'); var path = require('path'); var browserify = require('browserify'); -var bubleify = require('bubleify'); var minify = require('minify-stream'); var constants = require('./constants'); @@ -47,8 +46,6 @@ module.exports = function _bundle(pathToIndex, pathToBundle, opts) { var b = browserify(pathToIndex, browserifyOpts); - b.transform(bubleify, constants.bubleifyOptions); - var bundleStream = b.bundle(function(err) { if(err) throw err; }); diff --git a/tasks/util/constants.js b/tasks/util/constants.js index 3f65761597a..46f0ba357d4 100644 --- a/tasks/util/constants.js +++ b/tasks/util/constants.js @@ -95,20 +95,6 @@ module.exports = { sourceMap: false }, - bubleifyOptions: { - target: { - chrome: 48, - firefox: 44, - edge: 12 - }, - transforms: { - arrow: true, - defaultParameter: false, - dangerousForOf: true, - }, - sourceMap: false - }, - licenseDist: [ '/**', '* plotly.js v' + pkg.version, From 9039ca3056fdc30cbff83386b8ae91ca262785ef Mon Sep 17 00:00:00 2001 From: Dmitry Date: Thu, 30 Nov 2017 17:31:20 -0500 Subject: [PATCH 129/151] Fix browserify bundle --- tasks/util/browserify_wrapper.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tasks/util/browserify_wrapper.js b/tasks/util/browserify_wrapper.js index 5a4bed4156b..65a8de6c07b 100644 --- a/tasks/util/browserify_wrapper.js +++ b/tasks/util/browserify_wrapper.js @@ -2,10 +2,6 @@ var fs = require('fs'); var path = require('path'); var browserify = require('browserify'); -<<<<<<< HEAD -var bubleify = require('bubleify'); -======= ->>>>>>> bundle-up var minify = require('minify-stream'); var constants = require('./constants'); From 7197b1fcdc4350d9420b2e422c68b0d5ee43bfa2 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 1 Dec 2017 16:15:22 -0500 Subject: [PATCH 130/151] Fix positions --- src/traces/scattergl/index.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 14c4501292a..e4f15e2d963 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -294,6 +294,8 @@ ScatterRegl.calc = function calc(container, trace) { 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 { @@ -304,8 +306,6 @@ ScatterRegl.calc = function calc(container, trace) { function makeMarkerOptions(markerOpts) { var markerOptions = {}; - markerOptions.positions = positions; - // get basic symbol info var multiMarker = Array.isArray(markerOpts.symbol); var isOpen, symbol; @@ -743,7 +743,9 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { if(dragmode === 'lasso' || dragmode === 'select') { if(!scene.select2d && scene.scatter2d) { var selectRegl = layout._glcanvas.data()[1].regl; - scene.select2d = createScatter(selectRegl); + + // smol hack to create scatter instance by cloning scatter2d + scene.select2d = createScatter(selectRegl, {clone: scene.scatter2d}); scene.select2d.update(scene.selectedOptions); } // in case if we keep selection @@ -1007,7 +1009,7 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { } else { unels = Array(stash.count); - for (i = 0; i < stash.count; i++) { + for(i = 0; i < stash.count; i++) { unels[i] = i; } } From 8aaed6cef328ea4e404b48edece25fe9e5492d33 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 4 Dec 2017 13:56:19 -0500 Subject: [PATCH 131/151] Add selection transform for multiple subplots --- src/plots/cartesian/select.js | 6 +++++- src/traces/scattergl/index.js | 9 ++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index a6e77c301b1..8ccf5a69ae1 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -220,12 +220,16 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { } // draw selection + var xs = dragOptions.plotinfo.xaxis._offset; + var ys = dragOptions.plotinfo.yaxis._offset; var paths = []; for(i = 0; i < mergedPolygons.length; i++) { var ppts = mergedPolygons[i]; paths.push(ppts.join('L') + 'L' + ppts[0]); } - outlines.attr('d', 'M' + paths.join('M') + 'Z'); + outlines + .attr('transform', 'translate(' + xs + ', ' + ys + ')') + .attr('d', 'M' + paths.join('M') + 'Z'); throttle.throttle( throttleID, diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index e4f15e2d963..f369dd89d1b 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -714,7 +714,6 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { if(nextTrace && trace.fill === 'tonexty') { pos = srcPos.slice(); - // FIXME: overcalculation here var nextOptions = scene.lineOptions[i + 1]; if(nextOptions) { @@ -962,7 +961,6 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval, hovermode) { }); if(di.htx) pointData.text = di.htx; - else if(trace.hovertext) pointData.text = trace.hovertext; else if(di.tx) pointData.text = di.tx; else if(trace.text) pointData.text = trace.text; @@ -1030,4 +1028,9 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { }; -ScatterRegl.style = function style() {}; +ScatterRegl.style = function style(gd, cd) { + if(cd) { + // var trace = cd[0].trace; + // trace._glTrace.update(cd); + } +}; From 4d7c4ce3a98f377dad6f2b65f8726ef719602fa1 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 4 Dec 2017 14:05:03 -0500 Subject: [PATCH 132/151] Scope outlines by subplots --- src/plots/cartesian/select.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 8ccf5a69ae1..69011aaa03e 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'); @@ -228,7 +227,6 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { paths.push(ppts.join('L') + 'L' + ppts[0]); } outlines - .attr('transform', 'translate(' + xs + ', ' + ys + ')') .attr('d', 'M' + paths.join('M') + 'Z'); throttle.throttle( From a0917c789a9391f7f24fb5ceba67201161c7fd6a Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 4 Dec 2017 14:57:40 -0500 Subject: [PATCH 133/151] Fix multiplot selection --- src/traces/scattergl/index.js | 37 +++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index f369dd89d1b..c35e083d8b9 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -521,8 +521,29 @@ ScatterRegl.calc = function calc(container, trace) { // make sure canvas is clear scene.clear = function clear() { - scene.select2d.regl.clear({color: true}); - scene.scatter2d.regl.clear({color: true}); + 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 @@ -1018,11 +1039,9 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { scene.unselectBatch = Array(scene.count); scene.scatter2d.update(scene.unselectedOptions); } - scene.selectBatch[trace.index || 0] = els; - scene.unselectBatch[trace.index || 0] = unels; + scene.selectBatch[0] = els; + scene.unselectBatch[0] = unels; - scene.clear(); - scene.draw(); return selection; }; @@ -1030,7 +1049,9 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { ScatterRegl.style = function style(gd, cd) { if(cd) { - // var trace = cd[0].trace; - // trace._glTrace.update(cd); + var stash = cd[0].t; + var scene = stash.scene; + stash.scene.clear(); + stash.scene.draw(); } }; From 70e442761c4ea41c106f52514ad3c85ac7cb1e71 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 4 Dec 2017 16:38:24 -0500 Subject: [PATCH 134/151] Fix subplot selection indexing --- src/traces/scattergl/index.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index c35e083d8b9..a22d041c006 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -132,13 +132,6 @@ ScatterRegl.calc = function calc(container, trace) { } } - // stash data - stash.x = x; - stash.y = y; - stash.rawx = rawx; - stash.rawy = rawy; - stash.positions = positions; - stash.count = count; if(trace.visible !== true) { hasLines = false; @@ -299,8 +292,8 @@ ScatterRegl.calc = function calc(container, trace) { } // expand no-markers axes else { - Axes.expand(xaxis, stash.rawx, { padded: true }); - Axes.expand(yaxis, stash.rawy, { padded: true }); + Axes.expand(xaxis, rawx, { padded: true }); + Axes.expand(yaxis, rawy, { padded: true }); } function makeMarkerOptions(markerOpts) { @@ -409,8 +402,8 @@ ScatterRegl.calc = function calc(container, trace) { } } - Axes.expand(xaxis, stash.rawx, { padded: true, ppad: sizes }); - Axes.expand(yaxis, stash.rawy, { padded: true, ppad: sizes }); + 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); @@ -427,8 +420,8 @@ ScatterRegl.calc = function calc(container, trace) { // FIXME: is there a better way to separate expansion? if(count < TOO_MANY_POINTS) { - Axes.expand(xaxis, stash.rawx, { padded: true, ppad: size }); - Axes.expand(yaxis, stash.rawy, { padded: true, ppad: size }); + 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 { @@ -609,6 +602,13 @@ ScatterRegl.calc = function calc(container, trace) { // 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}]; }; @@ -1039,8 +1039,8 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { scene.unselectBatch = Array(scene.count); scene.scatter2d.update(scene.unselectedOptions); } - scene.selectBatch[0] = els; - scene.unselectBatch[0] = unels; + scene.selectBatch[stash.index] = els; + scene.unselectBatch[stash.index] = unels; return selection; From 9763096b52398f11e7329d664bb2944d96b37ca0 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 4 Dec 2017 16:42:12 -0500 Subject: [PATCH 135/151] Hoist dot re --- src/traces/scattergl/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index a22d041c006..e10a5b2fede 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -36,6 +36,7 @@ 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 ScatterRegl = module.exports = extend({}, require('../scatter')); @@ -623,7 +624,7 @@ function getSymbolSdf(symbol) { var symbolNoDot = !!Drawing.symbolNoDot[symbolNumber % 100]; var symbolNoFill = !!Drawing.symbolNoFill[symbolNumber % 100]; - var isDot = /-dot/.test(symbol); + var isDot = DOT_RE.test(symbol); // get symbol sdf from cache or generate it if(SYMBOL_SDF[symbol]) return SYMBOL_SDF[symbol]; From 8b9652e5b43c03cf93d281e6aa5a9f0fae129dd6 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 4 Dec 2017 18:14:33 -0500 Subject: [PATCH 136/151] Init passed selectedpoints --- src/plots/cartesian/select.js | 2 -- src/traces/scattergl/index.js | 53 +++++++++++++++++++++++++---------- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 69011aaa03e..9fdd82faa53 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -219,8 +219,6 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { } // draw selection - var xs = dragOptions.plotinfo.xaxis._offset; - var ys = dragOptions.plotinfo.yaxis._offset; var paths = []; for(i = 0; i < mergedPolygons.length; i++) { var ppts = mergedPolygons[i]; diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index e10a5b2fede..bf9e3c4389d 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -521,7 +521,7 @@ ScatterRegl.calc = function calc(container, trace) { 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; @@ -762,15 +762,7 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { var dragmode = layout.dragmode; if(dragmode === 'lasso' || dragmode === 'select') { - 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); - } - // in case if we keep selection - else if(scene.selectBatch) { + if(scene.select2d && scene.selectBatch) { scene.scatter2d.update(scene.unselectedOptions); } } @@ -785,6 +777,7 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { 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], @@ -800,10 +793,41 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { (height - vpSize.t) - (1 - yaxis.domain[1]) * vpSize.h ]; - if(dragmode === 'lasso' || dragmode === 'select') { + 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(var i = 0; i < stash.count; i++) { + for(i = 0; i < stash.count; i++) { xpx[i] = xaxis.c2p(x[i]); ypx[i] = yaxis.c2p(y[i]); } @@ -1043,7 +1067,6 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { scene.selectBatch[stash.index] = els; scene.unselectBatch[stash.index] = unels; - return selection; }; @@ -1052,7 +1075,7 @@ ScatterRegl.style = function style(gd, cd) { if(cd) { var stash = cd[0].t; var scene = stash.scene; - stash.scene.clear(); - stash.scene.draw(); + scene.clear(); + scene.draw(); } }; From 9457ac5c11233c8da36d69740195be45e2d4bf06 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 4 Dec 2017 18:59:18 -0500 Subject: [PATCH 137/151] Fix number-related issues --- src/traces/scattergl/index.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index bf9e3c4389d..69ba3afa10c 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -28,6 +28,7 @@ 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 MAXDIST = Fx.constants.MAXDIST; var SYMBOL_SDF_SIZE = 200; @@ -58,7 +59,8 @@ ScatterRegl.calc = function calc(container, trace) { // 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 = Math.max(x ? x.length : 0, y ? y.length : 0), i, l, xx, yy, ptrX = 0, ptrY = 0; + var count = x.length, i, l, xx, yy, ptrX = 0, ptrY = 0; + if(!x) { x = Array(count); for(i = 0; i < count; i++) { @@ -111,8 +113,8 @@ ScatterRegl.calc = function calc(container, trace) { 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 = parseFloat(x[i]); - yy = parseFloat(y[i]); + xx = isNumeric(x[i]) ? +x[i] : NaN; + yy = isNumeric(y[i]) ? +y[i] : NaN; positions[i * 2] = xx; positions[i * 2 + 1] = yy; From fd6db00d5f048fa1b4d8348a1a9bdc18dc54cb78 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 5 Dec 2017 17:59:00 -0500 Subject: [PATCH 138/151] Explicitly define attributes --- src/traces/scattergl/index.js | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 69ba3afa10c..5699eccbf8a 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -29,6 +29,7 @@ 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; @@ -39,14 +40,27 @@ var SYMBOL_SVG_CIRCLE = Drawing.symbolFuncs[0](SYMBOL_SIZE * 0.05); var TOO_MANY_POINTS = 1e5; var DOT_RE = /-dot/; -var ScatterRegl = module.exports = extend({}, require('../scatter')); +var ScatterGl = module.exports = {}; -ScatterRegl.name = 'scattergl'; -ScatterRegl.categories = ['gl', 'regl', 'cartesian', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; +ScatterGl.name = 'scattergl'; +ScatterGl.categories = ['gl', 'regl', 'cartesian', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; +ScatterGl.attributes = Scatter.attributes; +ScatterGl.supplyDefaults = Scatter.supplyDefaults; +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'); -ScatterRegl.calc = function calc(container, trace) { +ScatterGl.calc = function calc(container, trace) { var layout = container._fullLayout; var positions; var stash = {}; @@ -650,7 +664,7 @@ function getSymbolSdf(symbol) { } -ScatterRegl.plot = function plot(container, subplot, cdata) { +ScatterGl.plot = function plot(container, subplot, cdata) { var layout = container._fullLayout; var scene = subplot._scene; @@ -869,7 +883,7 @@ ScatterRegl.plot = function plot(container, subplot, cdata) { }; -ScatterRegl.hoverPoints = function hover(pointData, xval, yval, hovermode) { +ScatterGl.hoverPoints = function hover(pointData, xval, yval, hovermode) { var cd = pointData.cd, stash = cd[0].t, trace = cd[0].trace, @@ -935,6 +949,7 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval, hovermode) { // the closest data point var di = { + pointNumber: id, x: x[id], y: y[id] }; @@ -1019,7 +1034,7 @@ ScatterRegl.hoverPoints = function hover(pointData, xval, yval, hovermode) { }; -ScatterRegl.selectPoints = function select(searchInfo, polygon) { +ScatterGl.selectPoints = function select(searchInfo, polygon) { var cd = searchInfo.cd, selection = [], trace = cd[0].trace, @@ -1073,7 +1088,7 @@ ScatterRegl.selectPoints = function select(searchInfo, polygon) { }; -ScatterRegl.style = function style(gd, cd) { +ScatterGl.style = function style(gd, cd) { if(cd) { var stash = cd[0].t; var scene = stash.scene; From 689f262d37422a1fdde8f09f556bdd0aca7b3125 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Dec 2017 18:01:55 -0500 Subject: [PATCH 139/151] Add basic toself/tonext modes --- src/traces/scattergl/index.js | 38 ++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 5699eccbf8a..6251ade7ce3 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -747,23 +747,41 @@ ScatterGl.plot = function plot(container, subplot, cdata) { pos.push(0); pos.push(srcPos[srcPos.length - 1]); } + else if(trace.fill === 'toself') { + pos = srcPos.slice(); + } + else if(trace.fill === 'tonext') { + pos = []; + var last = 0; + for(i = 0; i < srcPos.length; i += 2) { + if(isNaN(srcPos[i]) || isNaN(srcPos[i + 1])) { + pos = pos.concat(srcPos.slice(last, i)); + pos.push(srcPos[last], srcPos[last + 1]); + last = i; + } + } + pos = pos.concat(srcPos.slice(last)); + pos.push(srcPos[last + 2], srcPos[last + 3]); + } else { var nextTrace = trace._nexttrace; - if(nextTrace && trace.fill === 'tonexty') { - pos = srcPos.slice(); + if(nextTrace) { var nextOptions = scene.lineOptions[i + 1]; + var nextPos = nextOptions.positions; if(nextOptions) { - var nextPos = nextOptions.positions; - - 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); + 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; } - fillOptions.fill = nextTrace.fillcolor; } } } From c556a85eee0515d7ab5a755bbfbc7678c75c8f48 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Dec 2017 18:09:50 -0500 Subject: [PATCH 140/151] Fix toself opacity --- src/traces/scattergl/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 6251ade7ce3..0ce005f3225 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -747,10 +747,7 @@ ScatterGl.plot = function plot(container, subplot, cdata) { pos.push(0); pos.push(srcPos[srcPos.length - 1]); } - else if(trace.fill === 'toself') { - pos = srcPos.slice(); - } - else if(trace.fill === 'tonext') { + else if(trace.fill === 'toself' || trace.fill === 'tonext') { pos = []; var last = 0; for(i = 0; i < srcPos.length; i += 2) { @@ -761,7 +758,9 @@ ScatterGl.plot = function plot(container, subplot, cdata) { } } pos = pos.concat(srcPos.slice(last)); - pos.push(srcPos[last + 2], srcPos[last + 3]); + if(last) { + pos.push(srcPos[last + 2], srcPos[last + 3]); + } } else { var nextTrace = trace._nexttrace; @@ -785,6 +784,7 @@ ScatterGl.plot = function plot(container, subplot, cdata) { } } } + fillOptions.opacity = trace.opacity; fillOptions.positions = pos; }); From 23b364c9df89d492431f0809458bafd877212bf0 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 6 Dec 2017 19:43:49 -0500 Subject: [PATCH 141/151] Enhance toself processing --- src/traces/scattergl/index.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 0ce005f3225..6c3fa23f53b 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -750,16 +750,16 @@ ScatterGl.plot = function plot(container, subplot, cdata) { else if(trace.fill === 'toself' || trace.fill === 'tonext') { pos = []; var last = 0; - for(i = 0; i < srcPos.length; i += 2) { - if(isNaN(srcPos[i]) || isNaN(srcPos[i + 1])) { - pos = pos.concat(srcPos.slice(last, i)); + for(var 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 = i; + last = j + 2; } } pos = pos.concat(srcPos.slice(last)); if(last) { - pos.push(srcPos[last + 2], srcPos[last + 3]); + pos.push(srcPos[last], srcPos[last + 1]); } } else { @@ -767,9 +767,9 @@ ScatterGl.plot = function plot(container, subplot, cdata) { if(nextTrace) { var nextOptions = scene.lineOptions[i + 1]; - var nextPos = nextOptions.positions; if(nextOptions) { + var nextPos = nextOptions.positions; if(trace.fill === 'tonexty') { pos = srcPos.slice(); @@ -784,6 +784,7 @@ ScatterGl.plot = function plot(container, subplot, cdata) { } } } + fillOptions.opacity = trace.opacity; fillOptions.positions = pos; }); From 9cfe84a095efa90ef87519a2c6c8b153693a0eb8 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 8 Dec 2017 13:57:55 -0500 Subject: [PATCH 142/151] Make hollow tonext shapes --- src/traces/scattergl/index.js | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 6c3fa23f53b..3dda15a30ca 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -149,7 +149,6 @@ ScatterGl.calc = function calc(container, trace) { } } - if(trace.visible !== true) { hasLines = false; hasErrorX = false; @@ -732,6 +731,7 @@ ScatterGl.plot = function plot(container, subplot, cdata) { var trace = cd.trace; var stash = cd.t; var lineOptions = scene.lineOptions[i]; + var last, j; var pos = [], srcPos = (lineOptions && lineOptions.positions) || stash.positions; @@ -749,8 +749,8 @@ ScatterGl.plot = function plot(container, subplot, cdata) { } else if(trace.fill === 'toself' || trace.fill === 'tonext') { pos = []; - var last = 0; - for(var j = 0; j < srcPos.length; j += 2) { + 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]); @@ -785,6 +785,25 @@ ScatterGl.plot = function plot(container, subplot, cdata) { } } + // 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; }); From 8334e7d8d7ae2e0b5997f3d6566a9103837dd70f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Fri, 8 Dec 2017 14:44:44 -0500 Subject: [PATCH 143/151] Fix length detection --- src/traces/scattergl/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 3dda15a30ca..6fd188d0f2a 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -73,7 +73,7 @@ ScatterGl.calc = function calc(container, trace) { // 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.length, i, l, xx, yy, ptrX = 0, ptrY = 0; + var count = (x || y).length, i, l, xx, yy, ptrX = 0, ptrY = 0; if(!x) { x = Array(count); From ffe37205710745feded3e678e6945671ca59e200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etienne=20T=C3=A9treault-Pinard?= Date: Fri, 8 Dec 2017 15:35:27 -0500 Subject: [PATCH 144/151] bump regl-scatter2d --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cd6c2b80c10..e8363e5b2ac 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "regl": "^1.3.0", "regl-error2d": "^2.0.1", "regl-line2d": "^2.0.2", - "regl-scatter2d": "^2.0.5", + "regl-scatter2d": "^2.1.5", "right-now": "^1.0.0", "robust-orientation": "^1.1.3", "sane-topojson": "^2.0.0", From 3dd25c76f6fb41ea3f6fc70d612e89d45eb604a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etienne=20T=C3=A9treault-Pinard?= Date: Fri, 8 Dec 2017 15:39:43 -0500 Subject: [PATCH 145/151] bump re line/error 2d --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e8363e5b2ac..2f664bfeab9 100644 --- a/package.json +++ b/package.json @@ -99,8 +99,8 @@ "ndarray-ops": "^1.2.2", "polybooljs": "^1.2.0", "regl": "^1.3.0", - "regl-error2d": "^2.0.1", - "regl-line2d": "^2.0.2", + "regl-error2d": "^2.0.3", + "regl-line2d": "^2.1.0", "regl-scatter2d": "^2.1.5", "right-now": "^1.0.0", "robust-orientation": "^1.1.3", From 79955f6264e4d1aef3831d687dcae672879f9a72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etienne=20T=C3=A9treault-Pinard?= Date: Fri, 8 Dec 2017 15:06:47 -0500 Subject: [PATCH 146/151] bring back scattergl-specific attrs & dflts --- src/traces/scattergl/attributes.js | 89 ++++++++++++++++++++++++++++++ src/traces/scattergl/defaults.js | 66 ++++++++++++++++++++++ src/traces/scattergl/index.js | 4 +- 3 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 src/traces/scattergl/attributes.js create mode 100644 src/traces/scattergl/defaults.js diff --git a/src/traces/scattergl/attributes.js b/src/traces/scattergl/attributes.js new file mode 100644 index 00000000000..437c266c3ac --- /dev/null +++ b/src/traces/scattergl/attributes.js @@ -0,0 +1,89 @@ +/** +* 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 scatterAttrs = require('../scatter/attributes'); +var colorAttributes = require('../../components/colorscale/color_attributes'); + +var DASHES = require('../../constants/gl2d_dashes'); +var extendFlat = require('../../lib/extend').extendFlat; +var overrideAll = require('../../plot_api/edit_types').overrideAll; + +var scatterLineAttrs = scatterAttrs.line, + scatterMarkerAttrs = scatterAttrs.marker, + scatterMarkerLineAttrs = scatterMarkerAttrs.line; + +var attrs = module.exports = overrideAll({ + x: scatterAttrs.x, + x0: scatterAttrs.x0, + dx: scatterAttrs.dx, + y: scatterAttrs.y, + y0: scatterAttrs.y0, + dy: scatterAttrs.dy, + + text: extendFlat({}, scatterAttrs.text, { + description: [ + 'Sets text elements associated with each (x,y) pair to appear on hover.', + 'If a single string, the same string appears over', + 'all the data points.', + 'If an array of string, the items are mapped in order to the', + 'this trace\'s (x,y) coordinates.' + ].join(' ') + }), + mode: { + valType: 'flaglist', + flags: ['lines', 'markers'], + extras: ['none'], + role: 'info', + description: [ + 'Determines the drawing mode for this scatter trace.' + ].join(' ') + }, + line: { + color: scatterLineAttrs.color, + width: scatterLineAttrs.width, + dash: { + valType: 'enumerated', + values: Object.keys(DASHES), + dflt: 'solid', + role: 'style', + description: 'Sets the style of the lines.' + } + }, + marker: extendFlat({}, colorAttributes('marker'), { + symbol: scatterMarkerAttrs.symbol, + size: scatterMarkerAttrs.size, + sizeref: scatterMarkerAttrs.sizeref, + sizemin: scatterMarkerAttrs.sizemin, + sizemode: scatterMarkerAttrs.sizemode, + opacity: scatterMarkerAttrs.opacity, + showscale: scatterMarkerAttrs.showscale, + colorbar: scatterMarkerAttrs.colorbar, + line: extendFlat({}, colorAttributes('marker.line'), { + width: scatterMarkerLineAttrs.width + }) + }), + connectgaps: scatterAttrs.connectgaps, + 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'); + +attrs.x.editType = attrs.y.editType = attrs.x0.editType = attrs.y0.editType = 'calc+clearAxisTypes'; diff --git a/src/traces/scattergl/defaults.js b/src/traces/scattergl/defaults.js new file mode 100644 index 00000000000..e3c2e80f730 --- /dev/null +++ b/src/traces/scattergl/defaults.js @@ -0,0 +1,66 @@ +/** +* 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 Lib = require('../../lib'); + +var constants = require('../scatter/constants'); +var subTypes = require('../scatter/subtypes'); +var handleXYDefaults = require('../scatter/xy_defaults'); +var handleMarkerDefaults = require('../scatter/marker_defaults'); +var handleLineDefaults = require('../scatter/line_defaults'); +var handleFillColorDefaults = require('../scatter/fillcolor_defaults'); +var errorBarsSupplyDefaults = require('../../components/errorbars/defaults'); + +var attributes = require('./attributes'); + + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor, layout) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + var len = handleXYDefaults(traceIn, traceOut, layout, coerce); + if(!len) { + traceOut.visible = false; + return; + } + + coerce('text'); + coerce('mode', len < constants.PTS_LINESONLY ? 'lines+markers' : 'lines'); + + if(subTypes.hasLines(traceOut)) { + coerce('connectgaps'); + handleLineDefaults(traceIn, traceOut, defaultColor, layout, coerce); + } + + var dfltHoverOn = []; + + if(subTypes.hasMarkers(traceOut)) { + handleMarkerDefaults(traceIn, traceOut, defaultColor, layout, coerce); + dfltHoverOn.push('points'); + } + + coerce('fill'); + if(traceOut.fill !== 'none') { + 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 6fd188d0f2a..204b4b11155 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -45,8 +45,8 @@ var ScatterGl = module.exports = {}; ScatterGl.name = 'scattergl'; ScatterGl.categories = ['gl', 'regl', 'cartesian', 'symbols', 'errorBarsOK', 'markerColorscale', 'showLegend', 'scatter-like']; -ScatterGl.attributes = Scatter.attributes; -ScatterGl.supplyDefaults = Scatter.supplyDefaults; +ScatterGl.attributes = require('./attributes'); +ScatterGl.supplyDefaults = require('./defaults'); ScatterGl.cleanData = Scatter.cleanData; ScatterGl.arraysToCalcdata = Scatter.arraysToCalcdata; ScatterGl.colorbar = Scatter.colorbar; From ba75823c5d6e52e09067498cc2b27299dc499205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Etienne=20T=C3=A9treault-Pinard?= Date: Fri, 8 Dec 2017 16:27:39 -0500 Subject: [PATCH 147/151] svg -> gl -> svg + comment on failing test --- src/plots/cartesian/index.js | 4 ++-- test/jasmine/tests/gl_plot_interact_test.js | 25 +++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index b958a6408a7..289dc9c2ade 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -149,14 +149,14 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout) for(i = 0; i < oldModules.length; i++) { - if(oldModules[i].name === 'scatter' || oldModules[i].name === 'scattergl') { + if(oldModules[i].name === 'scatter') { hadScatter = true; break; } } for(i = 0; i < newModules.length; i++) { - if(newModules[i].name === 'scatter' || newModules[i].name === 'scattergl') { + if(newModules[i].name === 'scatter') { hasScatter = true; break; } diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js index f3b8a33e497..2adfd5d3518 100644 --- a/test/jasmine/tests/gl_plot_interact_test.js +++ b/test/jasmine/tests/gl_plot_interact_test.js @@ -949,6 +949,7 @@ describe('Test gl2d plots', function() { Plotly.plot(gd, _mock) .then(delay(20)) .then(function() { + // TODO this fails to remove marker points return Plotly.restyle(gd, 'visible', 'legendonly'); }) .then(function() { @@ -972,6 +973,30 @@ describe('Test gl2d plots', function() { .then(done); }); + 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() { + expect(countCanvases()).toBe(3); + expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(0); + + return Plotly.restyle(gd, 'type', 'scatter'); + }) + .then(function() { + expect(countCanvases()).toBe(0); + expect(d3.selectAll('.scatterlayer > .trace').size()).toBe(1); + }) + .catch(fail) + .then(done); + }); + it('supports 1D and 2D Zoom', function(done) { var centerX, centerY; Plotly.newPlot(gd, From a02ed20026a80c530f3f40d66dd079ec024bba96 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 11 Dec 2017 20:52:27 -0500 Subject: [PATCH 148/151] Fix scattergl interaction tests --- package.json | 2 +- src/plots/cartesian/index.js | 41 ++++++++++++++++----- src/plots/plots.js | 7 ++-- src/traces/scattergl/index.js | 8 +++- test/jasmine/tests/gl_plot_interact_test.js | 26 +++++++------ 5 files changed, 58 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 2f664bfeab9..f073cd1194a 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "regl": "^1.3.0", "regl-error2d": "^2.0.3", "regl-line2d": "^2.1.0", - "regl-scatter2d": "^2.1.5", + "regl-scatter2d": "^2.1.6", "right-now": "^1.0.0", "robust-orientation": "^1.1.3", "sane-topojson": "^2.0.0", diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index 289dc9c2ade..8752fd92bab 100644 --- a/src/plots/cartesian/index.js +++ b/src/plots/cartesian/index.js @@ -145,14 +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++) { @@ -162,22 +162,32 @@ 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') .selectAll('g.trace') .remove(); } - - if(subplotInfo._scene) { - subplotInfo._scene.destroy(); - } } oldFullLayout._infolayer.selectAll('g.rangeslider-container') @@ -186,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')); diff --git a/src/plots/plots.js b/src/plots/plots.js index 9a16d97ab1b..aabf96f2609 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -1002,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'); diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index 204b4b11155..5e09b2bfbcf 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -511,12 +511,16 @@ ScatterGl.calc = function calc(container, trace) { 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.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); + if(scene.scatter2d && !scene.selectBatch) { + scene.scatter2d.draw(i); + } } // persistent selection draw diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js index 2adfd5d3518..6d38838d3ca 100644 --- a/test/jasmine/tests/gl_plot_interact_test.js +++ b/test/jasmine/tests/gl_plot_interact_test.js @@ -949,7 +949,6 @@ describe('Test gl2d plots', function() { Plotly.plot(gd, _mock) .then(delay(20)) .then(function() { - // TODO this fails to remove marker points return Plotly.restyle(gd, 'visible', 'legendonly'); }) .then(function() { @@ -963,7 +962,7 @@ describe('Test gl2d plots', function() { return Plotly.restyle(gd, 'visible', false); }) .then(function() { - expect(readPixel(gd.querySelector('.gl-canvas-context'), 108, 100)[0]).toBe(0); + expect(gd.querySelector('.gl-canvas-context')).not.toBe(null); return Plotly.restyle(gd, 'visible', true); }) @@ -1141,7 +1140,7 @@ describe('Test removal of gl contexts', function() { }]) .then(function() { expect(gd._fullLayout._plots.xy._scene).toBeDefined(); - + console.log(1); Plots.cleanPlot([], {}, gd._fullData, gd._fullLayout); expect(gd._fullLayout._plots.xy._scene).toBeUndefined(); @@ -1283,28 +1282,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() { - // deleteTraces should not delete canvases since we may reuse them - countCanvases(3); + }) + .then(function() { + countCanvases(0); return Plotly.purge(gd); - }).then(done); + }) + .then(done); }); it('should be able to switch trace type', function(done) { From 9bd0e94521133dd20e18310e62594e90975270e7 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 12 Dec 2017 13:42:09 -0500 Subject: [PATCH 149/151] Fix gl2d_click_test --- test/jasmine/tests/gl2d_click_test.js | 12 ++++++------ test/jasmine/tests/gl_plot_interact_test.js | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/jasmine/tests/gl2d_click_test.js b/test/jasmine/tests/gl2d_click_test.js index f1b001456de..593d896d03a 100644 --- a/test/jasmine/tests/gl2d_click_test.js +++ b/test/jasmine/tests/gl2d_click_test.js @@ -358,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' }); @@ -406,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' }); diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js index 6d38838d3ca..21b8bbc66e6 100644 --- a/test/jasmine/tests/gl_plot_interact_test.js +++ b/test/jasmine/tests/gl_plot_interact_test.js @@ -1360,8 +1360,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, [{ From 7183ba0cfe312e014bd9aaf7b51e33bdf66e0b9f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 12 Dec 2017 14:33:16 -0500 Subject: [PATCH 150/151] Fix finance charts test --- src/plots/cartesian/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index 8752fd92bab..280faab9199 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); } } From 90ce57547bc91bf050cfeba6263e094b5e73c067 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 12 Dec 2017 14:38:55 -0500 Subject: [PATCH 151/151] Lintify --- src/plots/cartesian/index.js | 2 +- test/jasmine/tests/gl_plot_interact_test.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/plots/cartesian/index.js b/src/plots/cartesian/index.js index 280faab9199..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 } } - if (_module.plot) _module.plot(gd, plotinfo, cdModule, transitionOpts, makeOnCompleteCallback); + if(_module.plot) _module.plot(gd, plotinfo, cdModule, transitionOpts, makeOnCompleteCallback); } } diff --git a/test/jasmine/tests/gl_plot_interact_test.js b/test/jasmine/tests/gl_plot_interact_test.js index 21b8bbc66e6..1c76f573f9c 100644 --- a/test/jasmine/tests/gl_plot_interact_test.js +++ b/test/jasmine/tests/gl_plot_interact_test.js @@ -1140,7 +1140,6 @@ describe('Test removal of gl contexts', function() { }]) .then(function() { expect(gd._fullLayout._plots.xy._scene).toBeDefined(); - console.log(1); Plots.cleanPlot([], {}, gd._fullData, gd._fullLayout); expect(gd._fullLayout._plots.xy._scene).toBeUndefined();