diff --git a/src/plot_api/plot_api.js b/src/plot_api/plot_api.js index 9aa3f9712ce..838968e2ca5 100644 --- a/src/plot_api/plot_api.js +++ b/src/plot_api/plot_api.js @@ -2008,12 +2008,12 @@ function _relayout(gd, aobj) { else flags.plot = true; } else { - if((fullLayout._has('gl2d') || fullLayout._has('regl')) && + if((fullLayout._has('scatter-like') && fullLayout._has('regl')) && (ai === 'dragmode' && (vi === 'lasso' || vi === 'select') && !(vOld === 'lasso' || vOld === 'select')) ) { - flags.calc = true; + flags.plot = true; } else if(valObject) editTypes.update(flags, valObject); else flags.calc = true; diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 01f892f748f..34e49b56959 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -423,6 +423,13 @@ axes.saveShowSpikeInitial = function(gd, overwrite) { return hasOneAxisChanged; }; +axes.doesAxisNeedAutoRange = function(ax) { + return ( + ax.autorange || + !!Lib.nestedProperty(ax, 'rangeslider.autorange').get() + ); +}; + // axes.expand: if autoranging, include new data in the outer limits // for this axis // data is an array of numbers (ie already run through ax.d2c) @@ -436,12 +443,7 @@ axes.saveShowSpikeInitial = function(gd, overwrite) { // tozero: (boolean) make sure to include zero if axis is linear, // and make it a tight bound if possible axes.expand = function(ax, data, options) { - var needsAutorange = ( - ax.autorange || - !!Lib.nestedProperty(ax, 'rangeslider.autorange').get() - ); - - if(!needsAutorange || !data) return; + if(!axes.doesAxisNeedAutoRange(ax) || !data) return; if(!ax._min) ax._min = []; if(!ax._max) ax._max = []; diff --git a/src/traces/scatter/calc.js b/src/traces/scatter/calc.js index d3c6d2e4360..547c4a49603 100644 --- a/src/traces/scatter/calc.js +++ b/src/traces/scatter/calc.js @@ -25,6 +25,31 @@ function calc(gd, trace) { var x = xa.makeCalcdata(trace, 'x'); var y = ya.makeCalcdata(trace, 'y'); var serieslen = trace._length; + var cd = new Array(serieslen); + + var ppad = calcMarkerSize(trace, serieslen); + calcAxisExpansion(gd, trace, xa, ya, x, y, ppad); + + for(var 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); + calcColorscale(trace); + calcSelection(cd, trace); + + gd.firstscatter = false; + return cd; +} + +function calcAxisExpansion(gd, trace, xa, ya, x, y, ppad) { + var serieslen = trace._length; // cancel minimum tick spacings (only applies to bars and boxes) xa._minDtick = 0; @@ -35,8 +60,9 @@ function calc(gd, trace) { var xOptions = {padded: true}; var yOptions = {padded: true}; - var ppad = calcMarkerSize(trace, serieslen); - if(ppad) xOptions.ppad = yOptions.ppad = ppad; + if(ppad) { + xOptions.ppad = yOptions.ppad = ppad; + } // TODO: text size @@ -72,24 +98,6 @@ function calc(gd, trace) { Axes.expand(xa, x, xOptions); Axes.expand(ya, y, yOptions); - - // create the "calculated data" to plot - var cd = new Array(serieslen); - for(var 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); - calcColorscale(trace); - calcSelection(cd, trace); - - gd.firstscatter = false; - return cd; } function calcMarkerSize(trace, serieslen) { @@ -131,5 +139,6 @@ function calcMarkerSize(trace, serieslen) { module.exports = { calc: calc, - calcMarkerSize: calcMarkerSize + calcMarkerSize: calcMarkerSize, + calcAxisExpansion: calcAxisExpansion }; diff --git a/src/traces/scattergl/index.js b/src/traces/scattergl/index.js index dc8921aa753..80af61197b3 100644 --- a/src/traces/scattergl/index.js +++ b/src/traces/scattergl/index.js @@ -8,29 +8,32 @@ '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 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 createRegl = require('regl'); var createScatter = require('regl-scatter2d'); var createLine = require('regl-line2d'); var createError = require('regl-error2d'); +var kdtree = require('kdgrass'); var rgba = require('color-normalize'); var svgSdf = require('svg-path-sdf'); -var createRegl = require('regl'); var arrayRange = require('array-range'); + +var Lib = require('../../lib'); +var Axes = require('../../plots/cartesian/axes'); +var Drawing = require('../../components/drawing'); +var ErrorBars = require('../../components/errorbars'); +var formatColor = require('../../lib/gl_format_color'); + +var subTypes = require('../scatter/subtypes'); +var calcMarkerSize = require('../scatter/calc').calcMarkerSize; +var calcAxisExpansion = require('../scatter/calc').calcAxisExpansion; +var calcColorscales = require('../scatter/colorscale_calc'); +var makeBubbleSizeFn = require('../scatter/make_bubble_size_func'); +var linkTraces = require('../scatter/link_traces'); +var getTraceColor = require('../scatter/get_trace_color'); var fillHoverText = require('../scatter/fill_hover_text'); -var isNumeric = require('fast-isnumeric'); +var DASHES = require('../../constants/gl2d_dashes'); +var BADNUM = require('../../constants/numerical').BADNUM; var SYMBOL_SDF_SIZE = 200; var SYMBOL_SIZE = 20; var SYMBOL_STROKE = SYMBOL_SIZE / 20; @@ -38,123 +41,77 @@ var SYMBOL_SDF = {}; var SYMBOL_SVG_CIRCLE = Drawing.symbolFuncs[0](SYMBOL_SIZE * 0.05); var TOO_MANY_POINTS = 1e5; var DOT_RE = /-dot/; - -function calc(container, trace) { - var layout = container._fullLayout; - var positions; +var OPEN_RE = /-open/; + +function calc(gd, trace) { + var fullLayout = gd._fullLayout; + var xa = Axes.getFromId(gd, trace.xaxis); + var ya = Axes.getFromId(gd, trace.yaxis); + var subplot = fullLayout._plots[trace.xaxis + trace.yaxis]; + var count = trace._length; + var count2 = count * 2; var stash = {}; - var xaxis = Axes.getFromId(container, trace.xaxis); - var yaxis = Axes.getFromId(container, trace.yaxis); + var i, xx, yy; - var subplot = layout._plots[trace.xaxis + trace.yaxis]; + 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 count = trace._length, i, xx, yy; + // we need hi-precision for scatter2d, + // regl-scatter2d uses NaNs for bad/missing values + var positions = new Array(count2); + for(i = 0; i < count; i++) { + xx = x[i]; + yy = y[i]; + positions[i * 2] = xx === BADNUM ? NaN : xx; + positions[i * 2 + 1] = yy === BADNUM ? NaN : yy; + } - if(!x) { - x = Array(count); - for(i = 0; i < count; i++) { - x[i] = i; + if(xa.type === 'log') { + for(i = 0; i < count2; i += 2) { + positions[i] = xa.c2l(positions[i]); } } - if(!y) { - y = Array(count); - for(i = 0; i < count; i++) { - y[i] = i; + if(ya.type === 'log') { + for(i = 1; i < count2; i += 2) { + positions[i] = ya.c2l(positions[i]); } } - // get log converted positions - var rawx = (xaxis.type === 'log' || x.length > count) ? x.slice(0, count) : x; - var rawy = (yaxis.type === 'log' || y.length > count) ? y.slice(0, count) : y; - - var convertX = (xaxis.type === 'log') ? xaxis.d2l : parseFloat; - var convertY = (yaxis.type === 'log') ? yaxis.d2l : parseFloat; - - // we need hi-precision for scatter2d - positions = new Array(count * 2); - - for(i = 0; i < count; i++) { - x[i] = convertX(x[i]); - y[i] = convertY(y[i]); - - // if no x defined, we are creating simple int sequence (API) - // we use parseFloat because it gives NaN (we need that for empty values to avoid drawing lines) and it is incredibly fast - xx = isNumeric(x[i]) ? +x[i] : NaN; - yy = isNumeric(y[i]) ? +y[i] : NaN; - - positions[i * 2] = xx; - positions[i * 2 + 1] = yy; - } - // 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(xa.type !== 'log' && ya.type !== 'log') { // FIXME: delegate this to webworker stash.tree = kdtree(positions, 512); - } - else { - var ids = stash.ids = Array(count); + } else { + var ids = stash.ids = new Array(count); for(i = 0; i < count; i++) { ids[i] = i; } } + // create scene options and scene calcColorscales(trace); - - var options = sceneOptions(container, subplot, trace, positions); - - // expanding axes is separate from options - if(!options.markers) { - Axes.expand(xaxis, rawx, { padded: true }); - Axes.expand(yaxis, rawy, { padded: true }); - } - else if(Lib.isArrayOrTypedArray(options.markers.sizes)) { - var sizes = options.markers.sizes; - Axes.expand(xaxis, rawx, { padded: true, ppad: sizes }); - Axes.expand(yaxis, rawy, { padded: true, ppad: sizes }); - } - else { - var xbounds = [Infinity, -Infinity], ybounds = [Infinity, -Infinity]; - var size = options.markers.size; - - // axes bounds - for(i = 0; i < count; i++) { - xx = x[i], yy = y[i]; - if(xbounds[0] > xx) xbounds[0] = xx; - if(xbounds[1] < xx) xbounds[1] = xx; - if(ybounds[0] > yy) ybounds[0] = yy; - if(ybounds[1] < yy) ybounds[1] = yy; - } - - // FIXME: is there a better way to separate expansion? - if(count < TOO_MANY_POINTS) { - Axes.expand(xaxis, rawx, { padded: true, ppad: size }); - Axes.expand(yaxis, rawy, { padded: true, ppad: size }); - } - // update axes fast for big number of points - else { - if(xaxis._min) { - xaxis._min.push({ val: xbounds[0], pad: size }); - } - if(xaxis._max) { - xaxis._max.push({ val: xbounds[1], pad: size }); - } - - if(yaxis._min) { - yaxis._min.push({ val: ybounds[0], pad: size }); - } - if(yaxis._max) { - yaxis._max.push({ val: ybounds[1], pad: size }); - } + var options = sceneOptions(gd, subplot, trace, positions); + var markerOptions = options.marker; + var scene = sceneUpdate(gd, subplot); + var ppad; + + // Re-use SVG scatter axis expansion routine except + // for graph with very large number of points where it + // performs poorly. + // In big data case, fake Axes.expand outputs with data bounds, + // and an average size for array marker.size inputs. + if(count < TOO_MANY_POINTS) { + ppad = calcMarkerSize(trace, count); + calcAxisExpansion(gd, trace, xa, ya, x, y, ppad); + } else { + if(markerOptions) { + ppad = 2 * (markerOptions.sizeAvg || Math.max(markerOptions.size, 3)); } + fastAxisExpand(xa, x, ppad); + fastAxisExpand(ya, y, ppad); } - // create scene - var scene = sceneUpdate(container, subplot); - // set flags to create scene renderers if(options.fill && !scene.fill2d) scene.fill2d = true; if(options.marker && !scene.scatter2d) scene.scatter2d = true; @@ -176,22 +133,43 @@ function calc(container, trace) { stash.index = scene.count - 1; stash.x = x; stash.y = y; - stash.rawx = rawx; - stash.rawy = rawy; stash.positions = positions; stash.count = count; + gd.firstscatter = false; return [{x: false, y: false, t: stash, trace: trace}]; } +// Approximate Axes.expand results with speed +function fastAxisExpand(ax, vals, ppad) { + if(!Axes.doesAxisNeedAutoRange(ax) || !vals) return; + + var b0 = Infinity; + var b1 = -Infinity; + + for(var i = 0; i < vals.length; i += 2) { + var v = vals[i]; + if(v < b0) b0 = v; + if(v > b1) b1 = v; + } + + if(ax._min) ax._min = []; + ax._min.push({val: b0, pad: ppad}); + + if(ax._max) ax._max = []; + ax._max.push({val: b1, pad: ppad}); +} + // create scene options -function sceneOptions(container, subplot, trace, positions) { - var layout = container._fullLayout; +function sceneOptions(gd, subplot, trace, positions) { + var fullLayout = gd._fullLayout; var count = positions.length / 2; var markerOpts = trace.marker; - var i, ptrX = 0, ptrY = 0; - var xaxis = Axes.getFromId(container, trace.xaxis); - var yaxis = Axes.getFromId(container, trace.yaxis); + var xaxis = Axes.getFromId(gd, trace.xaxis); + var yaxis = Axes.getFromId(gd, trace.yaxis); + var ptrX = 0; + var ptrY = 0; + var i; var hasLines, hasErrorX, hasErrorY, hasError, hasMarkers, hasFill; @@ -201,8 +179,7 @@ function sceneOptions(container, subplot, trace, positions) { hasErrorY = false; hasMarkers = false; hasFill = false; - } - else { + } else { hasLines = subTypes.hasLines(trace) && positions.length > 1; hasErrorX = trace.error_x && trace.error_x.visible === true; hasErrorY = trace.error_y && trace.error_y.visible === true; @@ -211,11 +188,13 @@ function sceneOptions(container, subplot, trace, positions) { hasFill = !!trace.fill && trace.fill !== 'none'; } - var lineOptions, markerOptions, errorXOptions, errorYOptions, fillOptions, selectedOptions, unselectedOptions; + var lineOptions, markerOptions, fillOptions; + var errorXOptions, errorYOptions; + var selectedOptions, unselectedOptions; var linePositions; // get error values - var errorVals = hasError ? ErrorBars.calcFromTrace(trace, layout) : null; + var errorVals = hasError ? ErrorBars.calcFromTrace(trace, fullLayout) : null; if(hasErrorX) { errorXOptions = {}; @@ -337,7 +316,9 @@ function sceneOptions(container, subplot, trace, positions) { break; } } - lineOptions.join = (hasNaN || linePositions.length > TOO_MANY_POINTS) ? 'rect' : hasMarkers ? 'rect' : 'round'; + + lineOptions.join = (hasNaN || linePositions.length > TOO_MANY_POINTS) ? 'rect' : + hasMarkers ? 'rect' : 'round'; // fill gaps if(hasNaN && trace.connectgaps) { @@ -368,7 +349,6 @@ function sceneOptions(container, subplot, trace, positions) { markerOptions = makeMarkerOptions(markerOpts); selectedOptions = makeSelectedOptions(trace.selected, markerOpts); unselectedOptions = makeSelectedOptions(trace.unselected, markerOpts); - markerOptions.positions = positions; } @@ -378,7 +358,7 @@ function sceneOptions(container, subplot, trace, positions) { if(!selected) return options; if(selected.marker && selected.marker.symbol) { - options = makeMarkerOptions(extend({}, markerOpts, selected.marker)); + options = makeMarkerOptions(Lib.extendFlat({}, markerOpts, selected.marker)); } // shortcut simple selection logic @@ -393,17 +373,21 @@ function sceneOptions(container, subplot, trace, positions) { } function makeMarkerOptions(markerOpts) { - var markerOptions = {}, i; + var markerOptions = {}; + var i; - // get basic symbol info - var multiMarker = Array.isArray(markerOpts.symbol); - var isOpen, symbol; - if(!multiMarker) { - isOpen = /-open/.test(markerOpts.symbol); - } + var multiSymbol = Array.isArray(markerOpts.symbol); + var multiColor = Lib.isArrayOrTypedArray(markerOpts.color); + var multiLineColor = Lib.isArrayOrTypedArray(markerOpts.line.color); + var multiOpacity = Lib.isArrayOrTypedArray(markerOpts.opacity); + var multiSize = Lib.isArrayOrTypedArray(markerOpts.size); + var multiLineWidth = Lib.isArrayOrTypedArray(markerOpts.line.width); + + var isOpen; + if(!multiSymbol) isOpen = OPEN_RE.test(markerOpts.symbol); // prepare colors - if(multiMarker || Array.isArray(markerOpts.color) || Array.isArray(markerOpts.line.color) || Array.isArray(markerOpts.line) || Array.isArray(markerOpts.opacity)) { + if(multiSymbol || multiColor || multiLineColor || multiOpacity) { markerOptions.colors = new Array(count); markerOptions.borderColors = new Array(count); var colors = formatColor(markerOpts, markerOpts.opacity, count); @@ -428,9 +412,9 @@ function sceneOptions(container, subplot, trace, positions) { markerOptions.borderColors = borderColors; for(i = 0; i < count; i++) { - if(multiMarker) { - symbol = markerOpts.symbol[i]; - isOpen = /-open/.test(symbol); + if(multiSymbol) { + var symbol = markerOpts.symbol[i]; + isOpen = OPEN_RE.test(symbol); } if(isOpen) { borderColors[i] = colors[i].slice(); @@ -446,8 +430,7 @@ function sceneOptions(container, subplot, trace, positions) { markerOptions.color = rgba(markerOpts.color, 'uint8'); markerOptions.color[3] = 0; markerOptions.borderColor = rgba(markerOpts.color, 'uint8'); - } - else { + } else { markerOptions.color = rgba(markerOpts.color, 'uint8'); markerOptions.borderColor = rgba(markerOpts.line.color, 'uint8'); } @@ -455,53 +438,54 @@ function sceneOptions(container, subplot, trace, positions) { markerOptions.opacity = trace.opacity * markerOpts.opacity; } - // prepare markers - if(Array.isArray(markerOpts.symbol)) { + // prepare symbols + if(multiSymbol) { markerOptions.markers = new Array(count); - for(i = 0; i < count; ++i) { + for(i = 0; i < count; i++) { markerOptions.markers[i] = getSymbolSdf(markerOpts.symbol[i]); } - } - else { + } else { markerOptions.marker = getSymbolSdf(markerOpts.symbol); } - // prepare sizes and expand axes - var multiSize = markerOpts && (Array.isArray(markerOpts.size) || Array.isArray(markerOpts.line.width)); + // prepare sizes var markerSizeFunc = makeBubbleSizeFn(trace); - var size, sizes; + var s; - if(multiSize) { - sizes = markerOptions.sizes = new Array(count); + if(multiSize || multiLineWidth) { + var sizes = markerOptions.sizes = new Array(count); var borderSizes = markerOptions.borderSizes = new Array(count); + var sizeTotal = 0; + var sizeAvg; - if(Array.isArray(markerOpts.size)) { - for(i = 0; i < count; ++i) { + if(multiSize) { + for(i = 0; i < count; i++) { sizes[i] = markerSizeFunc(markerOpts.size[i]); + sizeTotal += sizes[i]; } - } - else { - size = markerSizeFunc(markerOpts.size); - for(i = 0; i < count; ++i) { - sizes[i] = size; + sizeAvg = sizeTotal / count; + } else { + s = markerSizeFunc(markerOpts.size); + for(i = 0; i < count; i++) { + sizes[i] = s; } } // See https://github.com/plotly/plotly.js/pull/1781#discussion_r121820798 - if(Array.isArray(markerOpts.line.width)) { - for(i = 0; i < count; ++i) { + if(multiLineWidth) { + for(i = 0; i < count; i++) { borderSizes[i] = markerSizeFunc(markerOpts.line.width[i]); } - } - else { - size = markerSizeFunc(markerOpts.line.width); - for(i = 0; i < count; ++i) { - borderSizes[i] = size; + } else { + s = markerSizeFunc(markerOpts.line.width); + for(i = 0; i < count; i++) { + borderSizes[i] = s; } } - } - else { - size = markerOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); + + markerOptions.sizeAvg = sizeAvg; + } else { + markerOptions.size = markerSizeFunc(markerOpts && markerOpts.size || 10); markerOptions.borderSizes = markerSizeFunc(markerOpts.line.width); } @@ -520,9 +504,9 @@ function sceneOptions(container, subplot, trace, positions) { } // make sure scene exists on subplot, return it -function sceneUpdate(container, subplot) { +function sceneUpdate(gd, subplot) { var scene = subplot._scene; - var layout = container._fullLayout; + var fullLayout = gd._fullLayout; if(!subplot._scene) { scene = subplot._scene = { @@ -599,9 +583,12 @@ function sceneUpdate(container, subplot) { // make sure canvas is clear scene.clear = function clear() { - var vpSize = layout._size, width = layout.width, height = layout.height, vp, gl, regl; + var vpSize = fullLayout._size; + var width = fullLayout.width; + var height = fullLayout.height; var xaxis = subplot.xaxis; var yaxis = subplot.yaxis; + var vp, gl, regl; // multisubplot case if(xaxis && xaxis.domain && yaxis && yaxis.domain) { @@ -720,20 +707,22 @@ function getSymbolSdf(symbol) { return symbolSdf || null; } -function plot(container, subplot, cdata) { +function plot(gd, subplot, cdata) { if(!cdata.length) return; - var layout = container._fullLayout; + var fullLayout = gd._fullLayout; var scene = cdata[0][0].t.scene; - var dragmode = layout.dragmode; + var dragmode = fullLayout.dragmode; // 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 vpSize = fullLayout._size; + var width = fullLayout.width; + var height = fullLayout.height; // make sure proper regl instances are created - layout._glcanvas.each(function(d) { + fullLayout._glcanvas.each(function(d) { if(d.regl || d.pick) return; d.regl = createRegl({ canvas: this, @@ -742,14 +731,14 @@ function plot(container, subplot, cdata) { preserveDrawingBuffer: true }, extensions: ['ANGLE_instanced_arrays', 'OES_element_index_uint'], - pixelRatio: container._context.plotGlPixelRatio || global.devicePixelRatio + pixelRatio: gd._context.plotGlPixelRatio || global.devicePixelRatio }); }); - var regl = layout._glcanvas.data()[0].regl; + var regl = fullLayout._glcanvas.data()[0].regl; // that is needed for fills - linkTraces(container, subplot, cdata); + linkTraces(gd, subplot, cdata); if(scene.dirty) { // make sure scenes are created @@ -878,11 +867,11 @@ function plot(container, subplot, cdata) { var trace = cd.trace; var stash = cd.t; var id = stash.index; - var x = stash.rawx, - y = stash.rawy; + var x = stash.x; + var y = stash.y; - var xaxis = subplot.xaxis || Axes.getFromId(container, trace.xaxis || 'x'); - var yaxis = subplot.yaxis || Axes.getFromId(container, trace.yaxis || 'y'); + var xaxis = subplot.xaxis || Axes.getFromId(gd, trace.xaxis || 'x'); + var yaxis = subplot.yaxis || Axes.getFromId(gd, trace.yaxis || 'y'); var i; var range = [ @@ -922,7 +911,8 @@ 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); + var xpx = new Array(stash.count); + var ypx = new Array(stash.count); for(i = 0; i < stash.count; i++) { xpx[i] = xaxis.c2p(x[i]); ypx[i] = yaxis.c2p(y[i]); @@ -944,7 +934,7 @@ function plot(container, subplot, cdata) { // create select2d if(!scene.select2d) { // create scatter instance by cloning scatter2d - scene.select2d = createScatter(layout._glcanvas.data()[1].regl, {clone: scene.scatter2d}); + scene.select2d = createScatter(fullLayout._glcanvas.data()[1].regl, {clone: scene.scatter2d}); } if(scene.scatter2d && scene.selectBatch && scene.selectBatch.length) { @@ -988,8 +978,8 @@ function hoverPoints(pointData, xval, yval, hovermode) { var trace = cd[0].trace; var xa = pointData.xa; var ya = pointData.ya; - var x = stash.rawx; - var y = stash.rawy; + var x = stash.x; + var y = stash.y; var xpx = xa.c2p(xval); var ypx = ya.c2p(yval); var maxDistance = pointData.distance; @@ -1096,9 +1086,9 @@ function hoverPoints(pointData, xval, yval, hovermode) { di.mgc = Array.isArray(grad.color) ? grad.color[id] : grad.color; } - var xc = xa.c2p(di.x, true), - yc = ya.c2p(di.y, true), - rad = di.mrc || 1; + var xp = xa.c2p(di.x, true); + var yp = ya.c2p(di.y, true); + var rad = di.mrc || 1; var hoverlabel = trace.hoverlabel; @@ -1121,12 +1111,12 @@ function hoverPoints(pointData, xval, yval, hovermode) { Lib.extendFlat(pointData, { color: getTraceColor(trace, di), - x0: xc - rad, - x1: xc + rad, + x0: xp - rad, + x1: xp + rad, xLabelVal: di.x, - y0: yc - rad, - y1: yc + rad, + y0: yp - rad, + y1: yp + rad, yLabelVal: di.y, cd: fakeCd, @@ -1145,15 +1135,12 @@ function hoverPoints(pointData, xval, yval, hovermode) { } function selectPoints(searchInfo, polygon) { - var cd = searchInfo.cd, - selection = [], - trace = cd[0].trace, - stash = cd[0].t, - x = stash.x, - y = stash.y, - rawx = stash.rawx, - rawy = stash.rawy; - + var cd = searchInfo.cd; + var selection = []; + var trace = cd[0].trace; + var stash = cd[0].t; + var x = stash.x; + var y = stash.y; var scene = stash.scene; if(!scene) return selection; @@ -1163,7 +1150,9 @@ function selectPoints(searchInfo, polygon) { // degenerate polygon does not enable selection // filter out points by visible scatter ones - var els = null, unels = null, i; + var els = null; + var unels = null; + var i; if(polygon !== false && !polygon.degenerate) { els = [], unels = []; for(i = 0; i < stash.count; i++) { @@ -1171,16 +1160,15 @@ function selectPoints(searchInfo, polygon) { els.push(i); selection.push({ pointNumber: i, - x: rawx ? rawx[i] : x[i], - y: rawy ? rawy[i] : y[i] + x: x[i], + y: y[i] }); } else { unels.push(i); } } - } - else { + } else { unels = arrayRange(stash.count); } diff --git a/test/image/baselines/gl2d_10.png b/test/image/baselines/gl2d_10.png index b3d8ce18148..85bcc6a1a3a 100644 Binary files a/test/image/baselines/gl2d_10.png and b/test/image/baselines/gl2d_10.png differ diff --git a/test/image/baselines/gl2d_12.png b/test/image/baselines/gl2d_12.png index 9f35f0bb4fe..e0e6c389512 100644 Binary files a/test/image/baselines/gl2d_12.png and b/test/image/baselines/gl2d_12.png differ diff --git a/test/image/baselines/gl2d_14.png b/test/image/baselines/gl2d_14.png index 9a4ae8542e8..d90ff1e2468 100644 Binary files a/test/image/baselines/gl2d_14.png and b/test/image/baselines/gl2d_14.png differ diff --git a/test/image/baselines/gl2d_17.png b/test/image/baselines/gl2d_17.png index 85a564fd72c..7eeb7dea3a8 100644 Binary files a/test/image/baselines/gl2d_17.png and b/test/image/baselines/gl2d_17.png differ diff --git a/test/image/baselines/gl2d_axes_booleans.png b/test/image/baselines/gl2d_axes_booleans.png index e0350f5c8de..7e523efca3b 100644 Binary files a/test/image/baselines/gl2d_axes_booleans.png and b/test/image/baselines/gl2d_axes_booleans.png differ diff --git a/test/image/baselines/gl2d_axes_labels.png b/test/image/baselines/gl2d_axes_labels.png index 5cf474017f0..decf839b993 100644 Binary files a/test/image/baselines/gl2d_axes_labels.png and b/test/image/baselines/gl2d_axes_labels.png differ diff --git a/test/image/baselines/gl2d_axes_labels2.png b/test/image/baselines/gl2d_axes_labels2.png index 07d41bf98a3..0df05344ea9 100644 Binary files a/test/image/baselines/gl2d_axes_labels2.png and b/test/image/baselines/gl2d_axes_labels2.png differ diff --git a/test/image/baselines/gl2d_axes_lines.png b/test/image/baselines/gl2d_axes_lines.png index 6d2711ff435..de668a0dc13 100644 Binary files a/test/image/baselines/gl2d_axes_lines.png and b/test/image/baselines/gl2d_axes_lines.png differ diff --git a/test/image/baselines/gl2d_axes_range_mode.png b/test/image/baselines/gl2d_axes_range_mode.png index 22f6212f49c..98eca06f700 100644 Binary files a/test/image/baselines/gl2d_axes_range_mode.png and b/test/image/baselines/gl2d_axes_range_mode.png differ diff --git a/test/image/baselines/gl2d_axes_range_type.png b/test/image/baselines/gl2d_axes_range_type.png index ba6739c52cc..5e02124eb9a 100644 Binary files a/test/image/baselines/gl2d_axes_range_type.png and b/test/image/baselines/gl2d_axes_range_type.png differ diff --git a/test/image/baselines/gl2d_clean-number.png b/test/image/baselines/gl2d_clean-number.png new file mode 100644 index 00000000000..4143c7c7dce Binary files /dev/null and b/test/image/baselines/gl2d_clean-number.png differ diff --git a/test/image/baselines/gl2d_connect_gaps.png b/test/image/baselines/gl2d_connect_gaps.png index 69d5a638652..dba70d4dc06 100644 Binary files a/test/image/baselines/gl2d_connect_gaps.png and b/test/image/baselines/gl2d_connect_gaps.png differ diff --git a/test/image/baselines/gl2d_date_axes.png b/test/image/baselines/gl2d_date_axes.png index 309ec31a081..35a016d6f19 100644 Binary files a/test/image/baselines/gl2d_date_axes.png and b/test/image/baselines/gl2d_date_axes.png differ diff --git a/test/image/baselines/gl2d_error_bars.png b/test/image/baselines/gl2d_error_bars.png index 7c2ef3fe710..0cd99081555 100644 Binary files a/test/image/baselines/gl2d_error_bars.png and b/test/image/baselines/gl2d_error_bars.png differ diff --git a/test/image/baselines/gl2d_error_bars_log.png b/test/image/baselines/gl2d_error_bars_log.png index d8b75b45d95..26cc4c4c472 100644 Binary files a/test/image/baselines/gl2d_error_bars_log.png and b/test/image/baselines/gl2d_error_bars_log.png differ diff --git a/test/image/baselines/gl2d_fonts.png b/test/image/baselines/gl2d_fonts.png index 038bcef6f67..6465d9087c6 100644 Binary files a/test/image/baselines/gl2d_fonts.png and b/test/image/baselines/gl2d_fonts.png differ diff --git a/test/image/baselines/gl2d_layout_image.png b/test/image/baselines/gl2d_layout_image.png index 89a268319f9..fa45cb7793a 100644 Binary files a/test/image/baselines/gl2d_layout_image.png and b/test/image/baselines/gl2d_layout_image.png differ diff --git a/test/image/baselines/gl2d_line_aligned.png b/test/image/baselines/gl2d_line_aligned.png index 2d3752f23c6..84913b81e0f 100644 Binary files a/test/image/baselines/gl2d_line_aligned.png and b/test/image/baselines/gl2d_line_aligned.png differ diff --git a/test/image/baselines/gl2d_line_dash.png b/test/image/baselines/gl2d_line_dash.png index af3a62f6466..1b73c3f0907 100644 Binary files a/test/image/baselines/gl2d_line_dash.png and b/test/image/baselines/gl2d_line_dash.png differ diff --git a/test/image/baselines/gl2d_line_select.png b/test/image/baselines/gl2d_line_select.png index acdf282ae05..150c00b55a2 100644 Binary files a/test/image/baselines/gl2d_line_select.png and b/test/image/baselines/gl2d_line_select.png differ diff --git a/test/image/baselines/gl2d_marker_size.png b/test/image/baselines/gl2d_marker_size.png index 2aaffd39d60..563f0272edd 100644 Binary files a/test/image/baselines/gl2d_marker_size.png and b/test/image/baselines/gl2d_marker_size.png differ diff --git a/test/image/baselines/gl2d_marker_symbols.png b/test/image/baselines/gl2d_marker_symbols.png index f8b4b8eba58..954265506ed 100644 Binary files a/test/image/baselines/gl2d_marker_symbols.png and b/test/image/baselines/gl2d_marker_symbols.png differ diff --git a/test/image/baselines/gl2d_multiple-traces-axes-labels.png b/test/image/baselines/gl2d_multiple-traces-axes-labels.png index c38de480a7f..deff530274c 100644 Binary files a/test/image/baselines/gl2d_multiple-traces-axes-labels.png and b/test/image/baselines/gl2d_multiple-traces-axes-labels.png differ diff --git a/test/image/baselines/gl2d_multiple-traces-axes.png b/test/image/baselines/gl2d_multiple-traces-axes.png index 024f7b7578b..5c44cfc442d 100644 Binary files a/test/image/baselines/gl2d_multiple-traces-axes.png and b/test/image/baselines/gl2d_multiple-traces-axes.png differ diff --git a/test/image/baselines/gl2d_multiple_subplots.png b/test/image/baselines/gl2d_multiple_subplots.png index 6335bb92491..f0a814a8a6b 100644 Binary files a/test/image/baselines/gl2d_multiple_subplots.png and b/test/image/baselines/gl2d_multiple_subplots.png differ diff --git a/test/image/baselines/gl2d_open_marker_line_width.png b/test/image/baselines/gl2d_open_marker_line_width.png index 389948b230f..b8519424f4d 100644 Binary files a/test/image/baselines/gl2d_open_marker_line_width.png and b/test/image/baselines/gl2d_open_marker_line_width.png differ diff --git a/test/image/baselines/gl2d_scatter-color-clustering.png b/test/image/baselines/gl2d_scatter-color-clustering.png index 228aa2179ff..98e1e811832 100644 Binary files a/test/image/baselines/gl2d_scatter-color-clustering.png and b/test/image/baselines/gl2d_scatter-color-clustering.png differ diff --git a/test/image/baselines/gl2d_scatter-colorscale-colorbar.png b/test/image/baselines/gl2d_scatter-colorscale-colorbar.png index 87f05bdc541..e2ebc54e4a8 100644 Binary files a/test/image/baselines/gl2d_scatter-colorscale-colorbar.png and b/test/image/baselines/gl2d_scatter-colorscale-colorbar.png differ diff --git a/test/image/baselines/gl2d_scatter-colorscale-points.png b/test/image/baselines/gl2d_scatter-colorscale-points.png index 0242f8616aa..2aea7fb1d79 100644 Binary files a/test/image/baselines/gl2d_scatter-colorscale-points.png and b/test/image/baselines/gl2d_scatter-colorscale-points.png differ diff --git a/test/image/baselines/gl2d_scatter-marker-line-colorscales.png b/test/image/baselines/gl2d_scatter-marker-line-colorscales.png index 0de117bf3d0..ca93022e7c3 100644 Binary files a/test/image/baselines/gl2d_scatter-marker-line-colorscales.png and b/test/image/baselines/gl2d_scatter-marker-line-colorscales.png differ diff --git a/test/image/baselines/gl2d_scatter_fill_self_next.png b/test/image/baselines/gl2d_scatter_fill_self_next.png index 42cd181a489..41bfa5171f6 100644 Binary files a/test/image/baselines/gl2d_scatter_fill_self_next.png and b/test/image/baselines/gl2d_scatter_fill_self_next.png differ diff --git a/test/image/baselines/gl2d_shapes_below_traces.png b/test/image/baselines/gl2d_shapes_below_traces.png index 7eae642d04a..8c8a17c86c8 100644 Binary files a/test/image/baselines/gl2d_shapes_below_traces.png and b/test/image/baselines/gl2d_shapes_below_traces.png differ diff --git a/test/image/baselines/gl2d_simple_inset.png b/test/image/baselines/gl2d_simple_inset.png index b87b1d68337..c0005789be9 100644 Binary files a/test/image/baselines/gl2d_simple_inset.png and b/test/image/baselines/gl2d_simple_inset.png differ diff --git a/test/image/baselines/gl2d_size_margins.png b/test/image/baselines/gl2d_size_margins.png index 1cea44693ad..20d53998065 100644 Binary files a/test/image/baselines/gl2d_size_margins.png and b/test/image/baselines/gl2d_size_margins.png differ diff --git a/test/image/baselines/gl2d_stacked_coupled_subplots.png b/test/image/baselines/gl2d_stacked_coupled_subplots.png index 534e823812d..c67933ea7e3 100644 Binary files a/test/image/baselines/gl2d_stacked_coupled_subplots.png and b/test/image/baselines/gl2d_stacked_coupled_subplots.png differ diff --git a/test/image/baselines/gl2d_stacked_subplots.png b/test/image/baselines/gl2d_stacked_subplots.png index 79cef6fec93..c7d26103984 100644 Binary files a/test/image/baselines/gl2d_stacked_subplots.png and b/test/image/baselines/gl2d_stacked_subplots.png differ diff --git a/test/image/baselines/gl2d_subplots_anchor.png b/test/image/baselines/gl2d_subplots_anchor.png index 078e27ed403..341f440ab3b 100644 Binary files a/test/image/baselines/gl2d_subplots_anchor.png and b/test/image/baselines/gl2d_subplots_anchor.png differ diff --git a/test/image/baselines/gl2d_tick-labels.png b/test/image/baselines/gl2d_tick-labels.png index 437905db4ca..759baf31cfa 100644 Binary files a/test/image/baselines/gl2d_tick-labels.png and b/test/image/baselines/gl2d_tick-labels.png differ diff --git a/test/image/mocks/gl2d_clean-number.json b/test/image/mocks/gl2d_clean-number.json new file mode 100644 index 00000000000..6969e2d58fd --- /dev/null +++ b/test/image/mocks/gl2d_clean-number.json @@ -0,0 +1,7 @@ +{ + "data": [{ + "type": "scattergl", + "x": ["1", " 2 ", "3$", "4%"], + "y": ["1", " 2 ", "$3", "%4"] + }] +} diff --git a/test/jasmine/tests/gl2d_click_test.js b/test/jasmine/tests/gl2d_click_test.js index 852c543f44f..23512108888 100644 --- a/test/jasmine/tests/gl2d_click_test.js +++ b/test/jasmine/tests/gl2d_click_test.js @@ -534,11 +534,22 @@ describe('@gl Test hover and click interactions', function() { describe('@noCI @gl Test gl2d lasso/select:', function() { var mockFancy = require('@mocks/gl2d_14.json'); + delete mockFancy.layout.xaxis.autorange; + delete mockFancy.layout.yaxis.autorange; + mockFancy.layout.xaxis.range = [-2.951309064136961, 2.0954721318818916]; + mockFancy.layout.yaxis.range = [-0.9248866483012275, 1.3232607344525835]; + var mockFast = Lib.extendDeep({}, mockFancy, { data: [{mode: 'markers'}], layout: { - xaxis: {type: 'linear'}, - yaxis: {type: 'linear'} + xaxis: { + type: 'linear', + range: [-3.869222222222223, 73.55522222222223] + }, + yaxis: { + type: 'linear', + range: [-0.7402222222222222, 17.144222222222222] + } } }); diff --git a/test/jasmine/tests/gl2d_plot_interact_test.js b/test/jasmine/tests/gl2d_plot_interact_test.js index 0a219692b1c..7ebdd5c2756 100644 --- a/test/jasmine/tests/gl2d_plot_interact_test.js +++ b/test/jasmine/tests/gl2d_plot_interact_test.js @@ -209,7 +209,6 @@ describe('@gl Test gl plot side effects', function() { describe('@gl Test gl2d plots', function() { var gd; - var mock = require('@mocks/gl2d_10.json'); beforeEach(function() { @@ -255,9 +254,9 @@ describe('@gl Test gl2d plots', function() { var relayoutCallback = jasmine.createSpy('relayoutCallback'); var originalX = [-0.3037383177570093, 5.303738317757009]; - var originalY = [-0.5, 6.1]; - var newX = [-0.5, 5]; - var newY = [-1.7, 4.95]; + var originalY = [-0.5806379476536665, 6.218528262566369]; + var newX = [-0.5516431924882629, 5.082159624413145]; + var newY = [-1.7947747709072441, 5.004391439312791]; var precision = 1; Plotly.newPlot(gd, _mock) @@ -584,8 +583,8 @@ describe('@gl Test gl2d plots', function() { }); }) .then(function() { - expect(gd.layout.xaxis.range).toBeCloseToArray([-7.6, 23.6], 1); - expect(gd.layout.yaxis.range).toBeCloseToArray([0.2, 15.8], 1); + expect(gd.layout.xaxis.range).toBeCloseToArray([-8.2, 24.2], 1); + expect(gd.layout.yaxis.range).toBeCloseToArray([-0.12, 16.1], 1); }) .catch(fail) .then(done); @@ -834,3 +833,103 @@ describe('@gl Test gl2d plots', function() { .then(done); }); }); + +describe('Test scattergl autorange:', function() { + afterEach(destroyGraphDiv); + + describe('should return the same value as SVG scatter for ~small~ data', function() { + var specs = [ + {name: 'lines+markers', fig: require('@mocks/gl2d_10.json')}, + {name: 'bubbles', fig: require('@mocks/gl2d_12.json')}, + {name: 'line on log axes', fig: require('@mocks/gl2d_14.json')}, + {name: 'fill to zero', fig: require('@mocks/gl2d_axes_labels2.json')}, + {name: 'annotations', fig: require('@mocks/gl2d_annotations.json')} + ]; + + specs.forEach(function(s) { + it('- case ' + s.name, function(done) { + var gd = createGraphDiv(); + var glRangeX; + var glRangeY; + + // ensure the mocks have auto-range turned on + var glFig = Lib.extendDeep({}, s.fig); + Lib.extendDeep(glFig.layout, {xaxis: {autorange: true}}); + Lib.extendDeep(glFig.layout, {yaxis: {autorange: true}}); + + var svgFig = Lib.extendDeep({}, glFig); + svgFig.data.forEach(function(t) { t.type = 'scatter'; }); + + Plotly.newPlot(gd, glFig).then(function() { + glRangeX = gd._fullLayout.xaxis.range; + glRangeY = gd._fullLayout.yaxis.range; + }) + .then(function() { + return Plotly.newPlot(gd, svgFig); + }) + .then(function() { + expect(gd._fullLayout.xaxis.range).toBeCloseToArray(glRangeX, 'x range'); + expect(gd._fullLayout.yaxis.range).toBeCloseToArray(glRangeY, 'y range'); + }) + .catch(fail) + .then(done); + }); + }); + }); + + describe('should return the approximative values for ~big~ data', function() { + beforeEach(function() { + spyOn(ScatterGl, 'plot'); + }); + + // threshold for 'fast' axis expansion routine + var N = 1e5; + var x = new Array(N); + var y = new Array(N); + var ms = new Array(N); + + Lib.seedPseudoRandom(); + + for(var i = 0; i < N; i++) { + x[i] = Lib.pseudoRandom(); + y[i] = Lib.pseudoRandom(); + ms[i] = 10 * Lib.pseudoRandom() + 20; + } + + it('- case scalar marker.size', function(done) { + var gd = createGraphDiv(); + + Plotly.newPlot(gd, [{ + type: 'scattergl', + mode: 'markers', + x: x, + y: y, + marker: {size: 10} + }]) + .then(function() { + expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.02, 1.02], 'x range'); + expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.04, 1.04], 'y range'); + }) + .catch(fail) + .then(done); + }); + + it('- case array marker.size', function(done) { + var gd = createGraphDiv(); + + Plotly.newPlot(gd, [{ + type: 'scattergl', + mode: 'markers', + x: x, + y: y, + marker: {size: ms} + }]) + .then(function() { + expect(gd._fullLayout.xaxis.range).toBeCloseToArray([-0.05, 1.05], 'x range'); + expect(gd._fullLayout.yaxis.range).toBeCloseToArray([-0.11, 1.11], 'y range'); + }) + .catch(fail) + .then(done); + }); + }); +}); diff --git a/test/jasmine/tests/plot_api_test.js b/test/jasmine/tests/plot_api_test.js index 0d52ac76d28..14f61d65a60 100644 --- a/test/jasmine/tests/plot_api_test.js +++ b/test/jasmine/tests/plot_api_test.js @@ -523,7 +523,7 @@ describe('Test plot api', function() { return gd; } - it('should trigger recalc when switching into select or lasso dragmode', function() { + it('should trigger replot (but not recalc) when switching into select or lasso dragmode for scattergl traces', function() { var gd = mock({ data: [{ type: 'scattergl', @@ -541,8 +541,8 @@ describe('Test plot api', function() { expect(subroutines.layoutReplot).not.toHaveBeenCalled(); } - function expectRecalc() { - expect(gd.calcdata).toBeUndefined(); + function expectReplot() { + expect(gd.calcdata).toBeDefined(); expect(subroutines.doModeBar).not.toHaveBeenCalled(); expect(subroutines.layoutReplot).toHaveBeenCalled(); } @@ -551,7 +551,7 @@ describe('Test plot api', function() { expectModeBarOnly(); Plotly.relayout(mock(gd), 'dragmode', 'lasso'); - expectRecalc(); + expectReplot(); Plotly.relayout(mock(gd), 'dragmode', 'select'); expectModeBarOnly(); @@ -563,7 +563,7 @@ describe('Test plot api', function() { expectModeBarOnly(); Plotly.relayout(mock(gd), 'dragmode', 'select'); - expectRecalc(); + expectReplot(); }); });