From be47021bd705201245906d1e814f5470e4dd2d54 Mon Sep 17 00:00:00 2001 From: archmoj Date: Fri, 22 Jan 2021 13:54:21 -0500 Subject: [PATCH 1/2] hasHeatmapgl > hasGL2D --- src/components/modebar/manage.js | 10 +++++----- src/plots/plots.js | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/modebar/manage.js b/src/components/modebar/manage.js index 264e8f0062b..6743ba5aae5 100644 --- a/src/components/modebar/manage.js +++ b/src/components/modebar/manage.js @@ -89,7 +89,7 @@ function getButtonGroups(gd) { var hasGeo = fullLayout._has('geo'); var hasPie = fullLayout._has('pie'); var hasFunnelarea = fullLayout._has('funnelarea'); - var hasHeatmapgl = fullLayout._has('gl2d'); + var hasGL2D = fullLayout._has('gl2d'); var hasTernary = fullLayout._has('ternary'); var hasMapbox = fullLayout._has('mapbox'); var hasPolar = fullLayout._has('polar'); @@ -124,7 +124,7 @@ function getButtonGroups(gd) { var resetGroup = []; var dragModeGroup = []; - if((hasCartesian || hasHeatmapgl || hasPie || hasFunnelarea || hasTernary) + hasGeo + hasGL3D + hasMapbox + hasPolar > 1) { + if((hasCartesian || hasGL2D || hasPie || hasFunnelarea || hasTernary) + hasGeo + hasGL3D + hasMapbox + hasPolar > 1) { // graphs with more than one plot types get 'union buttons' // which reset the view or toggle hover labels across all subplots. hoverGroup = ['toggleHover']; @@ -140,7 +140,7 @@ function getButtonGroups(gd) { zoomGroup = ['zoomInMapbox', 'zoomOutMapbox']; hoverGroup = ['toggleHover']; resetGroup = ['resetViewMapbox']; - } else if(hasHeatmapgl) { + } else if(hasGL2D) { hoverGroup = ['hoverClosestGl2d']; } else if(hasPie) { hoverGroup = ['hoverClosestPie']; @@ -161,14 +161,14 @@ function getButtonGroups(gd) { hoverGroup = []; } - if((hasCartesian || hasHeatmapgl) && !allAxesFixed) { + if((hasCartesian || hasGL2D) && !allAxesFixed) { zoomGroup = ['zoomIn2d', 'zoomOut2d', 'autoScale2d']; if(resetGroup[0] !== 'resetViews') resetGroup = ['resetScale2d']; } if(hasGL3D) { dragModeGroup = ['zoom3d', 'pan3d', 'orbitRotation', 'tableRotation']; - } else if(((hasCartesian || hasHeatmapgl) && !allAxesFixed) || hasTernary) { + } else if(((hasCartesian || hasGL2D) && !allAxesFixed) || hasTernary) { dragModeGroup = ['zoom2d', 'pan2d']; } else if(hasMapbox || hasGeo) { dragModeGroup = ['pan2d']; diff --git a/src/plots/plots.js b/src/plots/plots.js index e8f1f1af56e..a43cd2a6905 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -468,11 +468,11 @@ plots.supplyDefaults = function(gd, opts) { plots.cleanPlot(newFullData, newFullLayout, oldFullData, oldFullLayout); var hadHeatmapgl = !!(oldFullLayout._has && oldFullLayout._has('gl2d')); - var hasHeatmapgl = !!(newFullLayout._has && newFullLayout._has('gl2d')); + var hasGL2D = !!(newFullLayout._has && newFullLayout._has('gl2d')); var hadCartesian = !!(oldFullLayout._has && oldFullLayout._has('cartesian')); var hasCartesian = !!(newFullLayout._has && newFullLayout._has('cartesian')); var hadBgLayer = hadCartesian || hadHeatmapgl; - var hasBgLayer = hasCartesian || hasHeatmapgl; + var hasBgLayer = hasCartesian || hasGL2D; if(hadBgLayer && !hasBgLayer) { // remove bgLayer oldFullLayout._bgLayer.remove(); From 74055575a0beb75110d087a63511e5491426eab2 Mon Sep 17 00:00:00 2001 From: archmoj Date: Fri, 22 Jan 2021 14:09:00 -0500 Subject: [PATCH 2/2] reimplement pointcloud trace type --- lib/index-gl2d.js | 1 + lib/index.js | 1 + lib/pointcloud.js | 11 + package-lock.json | 11 + package.json | 1 + src/components/images/draw.js | 2 +- src/plot_api/plot_schema.js | 4 +- src/plot_api/subroutines.js | 2 +- src/plots/cartesian/axes.js | 2 +- src/plots/cartesian/include_components.js | 4 +- src/plots/plots.js | 4 +- src/traces/heatmap/make_bound_array.js | 6 +- src/traces/pointcloud/attributes.js | 146 ++++++++++ src/traces/pointcloud/convert.js | 200 ++++++++++++++ src/traces/pointcloud/defaults.js | 46 ++++ src/traces/pointcloud/index.js | 29 ++ .../image/baselines/gl2d_pointcloud-basic.png | Bin 0 -> 30504 bytes test/image/compare_pixels_test.js | 8 +- test/image/mocks/gl2d_pointcloud-basic.json | 72 +++++ test/jasmine/assets/mock_lists.js | 1 + test/jasmine/bundle_tests/no_webgl_test.js | 4 +- test/jasmine/tests/gl2d_click_test.js | 27 ++ test/jasmine/tests/mock_test.js | 2 + test/jasmine/tests/modebar_test.js | 2 +- test/jasmine/tests/pointcloud_test.js | 255 ++++++++++++++++++ 25 files changed, 822 insertions(+), 19 deletions(-) create mode 100644 lib/pointcloud.js create mode 100644 src/traces/pointcloud/attributes.js create mode 100644 src/traces/pointcloud/convert.js create mode 100644 src/traces/pointcloud/defaults.js create mode 100644 src/traces/pointcloud/index.js create mode 100644 test/image/baselines/gl2d_pointcloud-basic.png create mode 100644 test/image/mocks/gl2d_pointcloud-basic.json create mode 100644 test/jasmine/tests/pointcloud_test.js diff --git a/lib/index-gl2d.js b/lib/index-gl2d.js index ad78095ed80..5d8e572c0d1 100644 --- a/lib/index-gl2d.js +++ b/lib/index-gl2d.js @@ -13,6 +13,7 @@ var Plotly = require('./core'); Plotly.register([ require('./scattergl'), require('./splom'), + require('./pointcloud'), require('./heatmapgl'), require('./parcoords') ]); diff --git a/lib/index.js b/lib/index.js index 72930ceb362..30dbb1ac4bf 100644 --- a/lib/index.js +++ b/lib/index.js @@ -44,6 +44,7 @@ Plotly.register([ require('./scattergl'), require('./splom'), + require('./pointcloud'), require('./heatmapgl'), require('./parcoords'), diff --git a/lib/pointcloud.js b/lib/pointcloud.js new file mode 100644 index 00000000000..15d851007e8 --- /dev/null +++ b/lib/pointcloud.js @@ -0,0 +1,11 @@ +/** +* Copyright 2012-2021, 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/pointcloud'); diff --git a/package-lock.json b/package-lock.json index cd2a3337465..5fbd8bd9d2c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5586,6 +5586,17 @@ } } }, + "gl-pointcloud2d": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/gl-pointcloud2d/-/gl-pointcloud2d-1.0.3.tgz", + "integrity": "sha512-OS2e1irvJXVRpg/GziXj10xrFJm9kkRfFoB6BLUvkjCQV7ZRNNcs2CD+YSK1r0gvMwTg2T3lfLM3UPwNtz+4Xw==", + "requires": { + "gl-buffer": "^2.1.2", + "gl-shader": "^4.2.1", + "glslify": "^7.0.0", + "typedarray-pool": "^1.1.0" + } + }, "gl-quat": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gl-quat/-/gl-quat-1.0.0.tgz", diff --git a/package.json b/package.json index 63bbe4b860d..9d70870d3bd 100644 --- a/package.json +++ b/package.json @@ -87,6 +87,7 @@ "gl-mesh3d": "^2.3.1", "gl-plot2d": "^1.4.5", "gl-plot3d": "^2.4.7", + "gl-pointcloud2d": "^1.0.3", "gl-scatter3d": "^1.2.3", "gl-select-box": "^1.0.4", "gl-spikes2d": "^1.0.2", diff --git a/src/components/images/draw.js b/src/components/images/draw.js index 61dcfe5757e..17c17382e99 100644 --- a/src/components/images/draw.js +++ b/src/components/images/draw.js @@ -237,7 +237,7 @@ module.exports = function draw(gd) { var subplotObj = fullLayout._plots[subplot]; // filter out overlaid plots (which have their images on the main plot) - // and heatmapgl plots (which don't support below images, at least not yet) + // and gl2d plots (which don't support below images, at least not yet) if(!subplotObj.imagelayer) continue; var imagesOnSubplot = subplotObj.imagelayer.selectAll('image') diff --git a/src/plot_api/plot_schema.js b/src/plot_api/plot_schema.js index 73aca869bc8..cfdaa67c823 100644 --- a/src/plot_api/plot_schema.js +++ b/src/plot_api/plot_schema.js @@ -332,7 +332,7 @@ function layoutHeadAttr(fullLayout, head) { _module = basePlotModules[i]; if(_module.attrRegex && _module.attrRegex.test(head)) { // if a module defines overrides, these take precedence - // initially this was to allow heatmapgl different editTypes from svg cartesian + // initially this is to allow gl2d different editTypes from svg cartesian if(_module.layoutAttrOverrides) return _module.layoutAttrOverrides; // otherwise take the first attributes we find @@ -340,7 +340,7 @@ function layoutHeadAttr(fullLayout, head) { } // a module can also override the behavior of base (and component) module layout attrs - // again see heatmapgl for initial use case + // again see gl2d for initial use case var baseOverrides = _module.baseLayoutAttrOverrides; if(baseOverrides && head in baseOverrides) return baseOverrides[head]; } diff --git a/src/plot_api/subroutines.js b/src/plot_api/subroutines.js index ab9b0a59ade..7c78b2026cc 100644 --- a/src/plot_api/subroutines.js +++ b/src/plot_api/subroutines.js @@ -68,7 +68,7 @@ function lsInner(gd) { exports.drawMainTitle(gd); ModeBar.manage(gd); - // _has('cartesian') means SVG specifically, not heatmapgl - but heatmapgl + // _has('cartesian') means SVG specifically, not GL2D - but GL2D // can still get here because it makes some of the SVG structure // for shared features like selections. if(!fullLayout._has('cartesian')) { diff --git a/src/plots/cartesian/axes.js b/src/plots/cartesian/axes.js index 0f7da3b3716..bd53e4364ff 100644 --- a/src/plots/cartesian/axes.js +++ b/src/plots/cartesian/axes.js @@ -102,7 +102,7 @@ axes.coerceRef = function(containerIn, containerOut, gd, attr, dflt, extraOption if(!extraOption) extraOption = dflt; axlist = axlist.concat(axlist.map(function(x) { return x + ' domain'; })); - // data-ref annotations are not supported in heatmapgl yet + // data-ref annotations are not supported in gl2d yet attrDef[refAttr] = { valType: 'enumerated', diff --git a/src/plots/cartesian/include_components.js b/src/plots/cartesian/include_components.js index 2ca206406ac..c699716b5ff 100644 --- a/src/plots/cartesian/include_components.js +++ b/src/plots/cartesian/include_components.js @@ -35,7 +35,7 @@ module.exports = function makeIncludeComponents(containerArrayName) { var xaList = subplots.xaxis; var yaList = subplots.yaxis; var cartesianList = subplots.cartesian; - var hasCartesianOrHeatmapgl = layoutOut._has('cartesian') || layoutOut._has('gl2d'); + var hasCartesianOrGL2D = layoutOut._has('cartesian') || layoutOut._has('gl2d'); for(var i = 0; i < array.length; i++) { var itemi = array[i]; @@ -49,7 +49,7 @@ module.exports = function makeIncludeComponents(containerArrayName) { var hasXref = idRegex.x.test(xref); var hasYref = idRegex.y.test(yref); if(hasXref || hasYref) { - if(!hasCartesianOrHeatmapgl) Lib.pushUnique(layoutOut._basePlotModules, Cartesian); + if(!hasCartesianOrGL2D) Lib.pushUnique(layoutOut._basePlotModules, Cartesian); var newAxis = false; if(hasXref && xaList.indexOf(xref) === -1) { diff --git a/src/plots/plots.js b/src/plots/plots.js index a43cd2a6905..2174bbc756c 100644 --- a/src/plots/plots.js +++ b/src/plots/plots.js @@ -467,11 +467,11 @@ plots.supplyDefaults = function(gd, opts) { // clean subplots and other artifacts from previous plot calls plots.cleanPlot(newFullData, newFullLayout, oldFullData, oldFullLayout); - var hadHeatmapgl = !!(oldFullLayout._has && oldFullLayout._has('gl2d')); + var hadGL2D = !!(oldFullLayout._has && oldFullLayout._has('gl2d')); var hasGL2D = !!(newFullLayout._has && newFullLayout._has('gl2d')); var hadCartesian = !!(oldFullLayout._has && oldFullLayout._has('cartesian')); var hasCartesian = !!(newFullLayout._has && newFullLayout._has('cartesian')); - var hadBgLayer = hadCartesian || hadHeatmapgl; + var hadBgLayer = hadCartesian || hadGL2D; var hasBgLayer = hasCartesian || hasGL2D; if(hadBgLayer && !hasBgLayer) { // remove bgLayer diff --git a/src/traces/heatmap/make_bound_array.js b/src/traces/heatmap/make_bound_array.js index 4356e8c9d80..aad299e565d 100644 --- a/src/traces/heatmap/make_bound_array.js +++ b/src/traces/heatmap/make_bound_array.js @@ -15,7 +15,7 @@ module.exports = function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, var arrayOut = []; var isContour = Registry.traceIs(trace, 'contour'); var isHist = Registry.traceIs(trace, 'histogram'); - var isHeatmapgl = Registry.traceIs(trace, 'gl2d'); + var isGL2D = Registry.traceIs(trace, 'gl2d'); var v0; var dv; var i; @@ -30,7 +30,7 @@ module.exports = function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, // and extend it linearly based on the last two points if(len <= numbricks) { // contour plots only want the centers - if(isContour || isHeatmapgl) arrayOut = arrayIn.slice(0, numbricks); + if(isContour || isGL2D) arrayOut = arrayIn.slice(0, numbricks); else if(numbricks === 1) { arrayOut = [arrayIn[0] - 0.5, arrayIn[0] + 0.5]; } else { @@ -77,7 +77,7 @@ module.exports = function makeBoundArray(trace, arrayIn, v0In, dvIn, numbricks, dv = dvIn || 1; - for(i = (isContour || isHeatmapgl) ? 0 : -0.5; i < numbricks; i++) { + for(i = (isContour || isGL2D) ? 0 : -0.5; i < numbricks; i++) { arrayOut.push(v0 + dv * i); } } diff --git a/src/traces/pointcloud/attributes.js b/src/traces/pointcloud/attributes.js new file mode 100644 index 00000000000..15ac4d51d98 --- /dev/null +++ b/src/traces/pointcloud/attributes.js @@ -0,0 +1,146 @@ +/** +* Copyright 2012-2021, 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 scatterglAttrs = require('../scatter/attributes'); + +module.exports = { + x: scatterglAttrs.x, + y: scatterglAttrs.y, + xy: { + valType: 'data_array', + editType: 'calc', + description: [ + 'Faster alternative to specifying `x` and `y` separately.', + 'If supplied, it must be a typed `Float32Array` array that', + 'represents points such that `xy[i * 2] = x[i]` and `xy[i * 2 + 1] = y[i]`' + ].join(' ') + }, + indices: { + valType: 'data_array', + editType: 'calc', + description: [ + 'A sequential value, 0..n, supply it to avoid creating this array inside plotting.', + 'If specified, it must be a typed `Int32Array` array.', + 'Its length must be equal to or greater than the number of points.', + 'For the best performance and memory use, create one large `indices` typed array', + 'that is guaranteed to be at least as long as the largest number of points during', + 'use, and reuse it on each `Plotly.restyle()` call.' + ].join(' ') + }, + xbounds: { + valType: 'data_array', + editType: 'calc', + description: [ + 'Specify `xbounds` in the shape of `[xMin, xMax] to avoid looping through', + 'the `xy` typed array. Use it in conjunction with `xy` and `ybounds` for the performance benefits.' + ].join(' ') + }, + ybounds: { + valType: 'data_array', + editType: 'calc', + description: [ + 'Specify `ybounds` in the shape of `[yMin, yMax] to avoid looping through', + 'the `xy` typed array. Use it in conjunction with `xy` and `xbounds` for the performance benefits.' + ].join(' ') + }, + text: scatterglAttrs.text, + marker: { + color: { + valType: 'color', + arrayOk: false, + + editType: 'calc', + description: [ + 'Sets the marker fill color. It accepts a specific color.', + 'If the color is not fully opaque and there are hundreds of thousands', + 'of points, it may cause slower zooming and panning.' + ].join('') + }, + opacity: { + valType: 'number', + min: 0, + max: 1, + dflt: 1, + arrayOk: false, + + editType: 'calc', + description: [ + 'Sets the marker opacity. The default value is `1` (fully opaque).', + 'If the markers are not fully opaque and there are hundreds of thousands', + 'of points, it may cause slower zooming and panning.', + 'Opacity fades the color even if `blend` is left on `false` even if there', + 'is no translucency effect in that case.' + ].join(' ') + }, + blend: { + valType: 'boolean', + dflt: null, + + editType: 'calc', + description: [ + 'Determines if colors are blended together for a translucency effect', + 'in case `opacity` is specified as a value less then `1`.', + 'Setting `blend` to `true` reduces zoom/pan', + 'speed if used with large numbers of points.' + ].join(' ') + }, + sizemin: { + valType: 'number', + min: 0.1, + max: 2, + dflt: 0.5, + + editType: 'calc', + description: [ + 'Sets the minimum size (in px) of the rendered marker points, effective when', + 'the `pointcloud` shows a million or more points.' + ].join(' ') + }, + sizemax: { + valType: 'number', + min: 0.1, + dflt: 20, + + editType: 'calc', + description: [ + 'Sets the maximum size (in px) of the rendered marker points.', + 'Effective when the `pointcloud` shows only few points.' + ].join(' ') + }, + border: { + color: { + valType: 'color', + arrayOk: false, + + editType: 'calc', + description: [ + 'Sets the stroke color. It accepts a specific color.', + 'If the color is not fully opaque and there are hundreds of thousands', + 'of points, it may cause slower zooming and panning.' + ].join(' ') + }, + arearatio: { + valType: 'number', + min: 0, + max: 1, + dflt: 0, + + editType: 'calc', + description: [ + 'Specifies what fraction of the marker area is covered with the', + 'border.' + ].join(' ') + }, + editType: 'calc' + }, + editType: 'calc' + }, + transforms: undefined +}; diff --git a/src/traces/pointcloud/convert.js b/src/traces/pointcloud/convert.js new file mode 100644 index 00000000000..9735071ba54 --- /dev/null +++ b/src/traces/pointcloud/convert.js @@ -0,0 +1,200 @@ +/** +* Copyright 2012-2021, 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 createPointCloudRenderer = require('gl-pointcloud2d'); + +var str2RGBArray = require('../../lib/str2rgbarray'); +var findExtremes = require('../../plots/cartesian/autorange').findExtremes; +var getTraceColor = require('../scatter/get_trace_color'); + +function Pointcloud(scene, uid) { + this.scene = scene; + this.uid = uid; + this.type = 'pointcloud'; + + this.pickXData = []; + this.pickYData = []; + this.xData = []; + this.yData = []; + this.textLabels = []; + this.color = 'rgb(0, 0, 0)'; + this.name = ''; + this.hoverinfo = 'all'; + + this.idToIndex = new Int32Array(0); + this.bounds = [0, 0, 0, 0]; + + this.pointcloudOptions = { + positions: new Float32Array(0), + idToIndex: this.idToIndex, + sizemin: 0.5, + sizemax: 12, + color: [0, 0, 0, 1], + areaRatio: 1, + borderColor: [0, 0, 0, 1] + }; + this.pointcloud = createPointCloudRenderer(scene.glplot, this.pointcloudOptions); + this.pointcloud._trace = this; // scene2d requires this prop +} + +var proto = Pointcloud.prototype; + +proto.handlePick = function(pickResult) { + var index = this.idToIndex[pickResult.pointId]; + + // prefer the readout from XY, if present + return { + trace: this, + dataCoord: pickResult.dataCoord, + traceCoord: this.pickXYData ? + [this.pickXYData[index * 2], this.pickXYData[index * 2 + 1]] : + [this.pickXData[index], this.pickYData[index]], + textLabel: Array.isArray(this.textLabels) ? + this.textLabels[index] : + this.textLabels, + color: this.color, + name: this.name, + pointIndex: index, + hoverinfo: this.hoverinfo + }; +}; + +proto.update = function(options) { + this.index = options.index; + this.textLabels = options.text; + this.name = options.name; + this.hoverinfo = options.hoverinfo; + this.bounds = [Infinity, Infinity, -Infinity, -Infinity]; + + this.updateFast(options); + + this.color = getTraceColor(options, {}); +}; + +proto.updateFast = function(options) { + var x = this.xData = this.pickXData = options.x; + var y = this.yData = this.pickYData = options.y; + var xy = this.pickXYData = options.xy; + + var userBounds = options.xbounds && options.ybounds; + var index = options.indices; + + var len; + var idToIndex; + var positions; + var bounds = this.bounds; + + var xx, yy, i; + + if(xy) { + positions = xy; + + // dividing xy.length by 2 and truncating to integer if xy.length was not even + len = xy.length >>> 1; + + if(userBounds) { + bounds[0] = options.xbounds[0]; + bounds[2] = options.xbounds[1]; + bounds[1] = options.ybounds[0]; + bounds[3] = options.ybounds[1]; + } else { + for(i = 0; i < len; i++) { + xx = positions[i * 2]; + yy = positions[i * 2 + 1]; + + if(xx < bounds[0]) bounds[0] = xx; + if(xx > bounds[2]) bounds[2] = xx; + if(yy < bounds[1]) bounds[1] = yy; + if(yy > bounds[3]) bounds[3] = yy; + } + } + + if(index) { + idToIndex = index; + } else { + idToIndex = new Int32Array(len); + + for(i = 0; i < len; i++) { + idToIndex[i] = i; + } + } + } else { + len = x.length; + + positions = new Float32Array(2 * len); + idToIndex = new Int32Array(len); + + for(i = 0; i < len; i++) { + xx = x[i]; + yy = y[i]; + + idToIndex[i] = i; + + positions[i * 2] = xx; + positions[i * 2 + 1] = yy; + + if(xx < bounds[0]) bounds[0] = xx; + if(xx > bounds[2]) bounds[2] = xx; + if(yy < bounds[1]) bounds[1] = yy; + if(yy > bounds[3]) bounds[3] = yy; + } + } + + this.idToIndex = idToIndex; + this.pointcloudOptions.idToIndex = idToIndex; + + this.pointcloudOptions.positions = positions; + + var markerColor = str2RGBArray(options.marker.color); + var borderColor = str2RGBArray(options.marker.border.color); + var opacity = options.opacity * options.marker.opacity; + + markerColor[3] *= opacity; + this.pointcloudOptions.color = markerColor; + + // detect blending from the number of points, if undefined + // because large data with blending hits performance + var blend = options.marker.blend; + if(blend === null) { + var maxPoints = 100; + blend = x.length < maxPoints || y.length < maxPoints; + } + this.pointcloudOptions.blend = blend; + + borderColor[3] *= opacity; + this.pointcloudOptions.borderColor = borderColor; + + var markerSizeMin = options.marker.sizemin; + var markerSizeMax = Math.max(options.marker.sizemax, options.marker.sizemin); + this.pointcloudOptions.sizeMin = markerSizeMin; + this.pointcloudOptions.sizeMax = markerSizeMax; + this.pointcloudOptions.areaRatio = options.marker.border.arearatio; + + this.pointcloud.update(this.pointcloudOptions); + + // add item for autorange routine + var xa = this.scene.xaxis; + var ya = this.scene.yaxis; + var pad = markerSizeMax / 2 || 0.5; + options._extremes[xa._id] = findExtremes(xa, [bounds[0], bounds[2]], {ppad: pad}); + options._extremes[ya._id] = findExtremes(ya, [bounds[1], bounds[3]], {ppad: pad}); +}; + +proto.dispose = function() { + this.pointcloud.dispose(); +}; + +function createPointcloud(scene, data) { + var plot = new Pointcloud(scene, data.uid); + plot.update(data); + return plot; +} + +module.exports = createPointcloud; diff --git a/src/traces/pointcloud/defaults.js b/src/traces/pointcloud/defaults.js new file mode 100644 index 00000000000..0aedf577504 --- /dev/null +++ b/src/traces/pointcloud/defaults.js @@ -0,0 +1,46 @@ +/** +* Copyright 2012-2021, 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 attributes = require('./attributes'); + +module.exports = function supplyDefaults(traceIn, traceOut, defaultColor) { + function coerce(attr, dflt) { + return Lib.coerce(traceIn, traceOut, attributes, attr, dflt); + } + + coerce('x'); + coerce('y'); + + coerce('xbounds'); + coerce('ybounds'); + + if(traceIn.xy && traceIn.xy instanceof Float32Array) { + traceOut.xy = traceIn.xy; + } + + if(traceIn.indices && traceIn.indices instanceof Int32Array) { + traceOut.indices = traceIn.indices; + } + + coerce('text'); + coerce('marker.color', defaultColor); + coerce('marker.opacity'); + coerce('marker.blend'); + coerce('marker.sizemin'); + coerce('marker.sizemax'); + coerce('marker.border.color', defaultColor); + coerce('marker.border.arearatio'); + + // disable 1D transforms - that would defeat the purpose of this trace type, performance! + traceOut._length = null; +}; diff --git a/src/traces/pointcloud/index.js b/src/traces/pointcloud/index.js new file mode 100644 index 00000000000..868e1cce512 --- /dev/null +++ b/src/traces/pointcloud/index.js @@ -0,0 +1,29 @@ +/** +* Copyright 2012-2021, 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 = { + attributes: require('./attributes'), + supplyDefaults: require('./defaults'), + + // reuse the Scatter3D 'dummy' calc step so that legends know what to do + calc: require('../scatter3d/calc'), + plot: require('./convert'), + + moduleType: 'trace', + name: 'pointcloud', + basePlotModule: require('../../plots/gl2d'), + categories: ['gl', 'gl2d', 'showLegend'], + meta: { + description: [ + 'The data visualized as a point cloud set in `x` and `y`', + 'using the WebGl plotting engine.' + ].join(' ') + } +}; diff --git a/test/image/baselines/gl2d_pointcloud-basic.png b/test/image/baselines/gl2d_pointcloud-basic.png new file mode 100644 index 0000000000000000000000000000000000000000..13e73650e515ae041cd0a6c4cc93a57e73ab4bb8 GIT binary patch literal 30504 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 LEyaQx_a6TrG&>aS literal 0 HcmV?d00001 diff --git a/test/image/compare_pixels_test.js b/test/image/compare_pixels_test.js index 46361b59818..9ec29d774ae 100644 --- a/test/image/compare_pixels_test.js +++ b/test/image/compare_pixels_test.js @@ -121,7 +121,7 @@ if(argv['skip-flaky']) { }); } -/* non-regl mock(s) i.e. heatmapgl +/* gl2d pointcloud and other non-regl gl2d mock(s) * must be tested first on in order to work; * sort them here. * @@ -136,8 +136,8 @@ if(argv['skip-flaky']) { * More info here: * https://github.com/plotly/plotly.js/pull/1037 */ -function sortHeatmapglMockList(mockList) { - var mockNames = ['gl2d_heatmapgl']; +function sortGl2dMockList(mockList) { + var mockNames = ['gl2d_pointcloud-basic', 'gl2d_heatmapgl']; var pos = 0; mockNames.forEach(function(m) { @@ -283,7 +283,7 @@ function comparePixels(mockName, cb) { .on('close', checkImage); } -sortHeatmapglMockList(allMockList); +sortGl2dMockList(allMockList); console.log(''); // main diff --git a/test/image/mocks/gl2d_pointcloud-basic.json b/test/image/mocks/gl2d_pointcloud-basic.json new file mode 100644 index 00000000000..bdab3ae5510 --- /dev/null +++ b/test/image/mocks/gl2d_pointcloud-basic.json @@ -0,0 +1,72 @@ +{ + "data": [ + { + "type": "pointcloud", + "mode": "markers", + "marker": { + "sizemin": 0.5, + "sizemax": 100, + "arearatio": 0, + "color": "rgba(255, 0, 0, 0.6)" + }, + "x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + "y": [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + }, + { + "type": "pointcloud", + "mode": "markers", + "marker": { + "sizemin": 0.5, + "sizemax": 100, + "arearatio": 0, + "color": "rgba(0, 0, 255, 0.9)", + "opacity": 0.8, + "blend": true + }, + "opacity": 0.7, + "x": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + "y": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }, + { + "type": "pointcloud", + "mode": "markers", + "marker": { + "sizemin": 0.5, + "sizemax": 100, + "border": { + "color": "rgb(0, 0, 0)", + "arearatio": 0.7071 + }, + "color": "green", + "opacity": 0.8, + "blend": true + }, + "opacity": 0.7, + "x": [3, 4.5, 6], + "y": [9, 9, 9] + } + ], + "layout": { + "title": {"text": "Point Cloud - basic"}, + "xaxis": { + "type": "linear", + "range": [ + -2.501411175139456, + 43.340777299865266 + ], + "autorange": true + }, + "yaxis": { + "type": "linear", + "range": [ + 4, + 6 + ], + "autorange": true + }, + "height": 598, + "width": 1080, + "autosize": true, + "showlegend": false + } +} diff --git a/test/jasmine/assets/mock_lists.js b/test/jasmine/assets/mock_lists.js index 221ae703166..59af1a73109 100644 --- a/test/jasmine/assets/mock_lists.js +++ b/test/jasmine/assets/mock_lists.js @@ -58,6 +58,7 @@ var glMockList = [ ['gl2d_heatmapgl', require('@mocks/gl2d_heatmapgl.json')], ['gl2d_line_dash', require('@mocks/gl2d_line_dash.json')], ['gl2d_parcoords_2', require('@mocks/gl2d_parcoords_2.json')], + ['gl2d_pointcloud-basic', require('@mocks/gl2d_pointcloud-basic.json')], ['gl3d_annotations', require('@mocks/gl3d_annotations.json')], ['gl3d_set-ranges', require('@mocks/gl3d_set-ranges.json')], ['gl3d_world-cals', require('@mocks/gl3d_world-cals.json')], diff --git a/test/jasmine/bundle_tests/no_webgl_test.js b/test/jasmine/bundle_tests/no_webgl_test.js index 7d33d980c43..10a22e858c5 100644 --- a/test/jasmine/bundle_tests/no_webgl_test.js +++ b/test/jasmine/bundle_tests/no_webgl_test.js @@ -37,8 +37,8 @@ describe('Plotly w/o WebGL support:', function() { .then(done, done.fail); }); - it('heatmapgl subplots', function(done) { - Plotly.react(gd, require('@mocks/gl2d_heatmapgl.json')) + it('gl2d subplots', function(done) { + Plotly.react(gd, require('@mocks/gl2d_pointcloud-basic.json')) .then(function() { checkNoWebGLMsg(true); return Plotly.react(gd, require('@mocks/10.json')); diff --git a/test/jasmine/tests/gl2d_click_test.js b/test/jasmine/tests/gl2d_click_test.js index 69fb784fec7..c615e191e41 100644 --- a/test/jasmine/tests/gl2d_click_test.js +++ b/test/jasmine/tests/gl2d_click_test.js @@ -387,6 +387,33 @@ describe('Test hover and click interactions', function() { .then(done, done.fail); }); + it('@gl should output correct event data for pointcloud', function(done) { + var _mock = Lib.extendDeep({}, require('@mocks/gl2d_pointcloud-basic.json')); + + _mock.layout.hoverlabel = { font: {size: 8} }; + _mock.data[2].hoverlabel = { + bgcolor: ['red', 'green', 'blue'] + }; + + var run = makeRunner([540, 150], { + x: 4.5, + y: 9, + curveNumber: 2, + pointNumber: 1, + bgcolor: 'rgb(0, 128, 0)', + bordercolor: 'rgb(255, 255, 255)', + fontSize: 8, + fontFamily: 'Arial', + fontColor: 'rgb(255, 255, 255)' + }, { + msg: 'pointcloud' + }); + + Plotly.newPlot(gd, _mock) + .then(run) + .then(done, done.fail); + }); + it('@gl scattergl should propagate marker colors to hover labels', function(done) { var _mock = Lib.extendDeep({}, mock0); _mock.layout.width = 800; diff --git a/test/jasmine/tests/mock_test.js b/test/jasmine/tests/mock_test.js index cf9b0ac63ee..44811150551 100644 --- a/test/jasmine/tests/mock_test.js +++ b/test/jasmine/tests/mock_test.js @@ -426,6 +426,7 @@ var list = [ 'gl2d_parcoords_tick_format', 'gl2d_period_positioning', 'gl2d_point-selection', + 'gl2d_pointcloud-basic', 'gl2d_rgb_dont_accept_alpha_scattergl', 'gl2d_scatter_fill_self_next', 'gl2d_scatter_fill_self_next_vs_nogl', @@ -1513,6 +1514,7 @@ figs['gl2d_parcoords_style_labels'] = require('@mocks/gl2d_parcoords_style_label figs['gl2d_parcoords_tick_format'] = require('@mocks/gl2d_parcoords_tick_format'); figs['gl2d_period_positioning'] = require('@mocks/gl2d_period_positioning'); figs['gl2d_point-selection'] = require('@mocks/gl2d_point-selection'); +// figs['gl2d_pointcloud-basic'] = require('@mocks/gl2d_pointcloud-basic'); // figs['gl2d_rgb_dont_accept_alpha_scattergl'] = require('@mocks/gl2d_rgb_dont_accept_alpha_scattergl'); figs['gl2d_scatter_fill_self_next'] = require('@mocks/gl2d_scatter_fill_self_next'); figs['gl2d_scatter_fill_self_next_vs_nogl'] = require('@mocks/gl2d_scatter_fill_self_next_vs_nogl'); diff --git a/test/jasmine/tests/modebar_test.js b/test/jasmine/tests/modebar_test.js index 83b89734e08..a6b52bd45b1 100644 --- a/test/jasmine/tests/modebar_test.js +++ b/test/jasmine/tests/modebar_test.js @@ -521,7 +521,7 @@ describe('ModeBar', function() { checkButtons(modeBar, buttons, 1); }); - it('creates mode bar (heatmapgl version)', function() { + it('creates mode bar (gl2d version)', function() { var buttons = getButtons([ ['toImage'], ['zoom2d', 'pan2d'], diff --git a/test/jasmine/tests/pointcloud_test.js b/test/jasmine/tests/pointcloud_test.js new file mode 100644 index 00000000000..3fba5e52381 --- /dev/null +++ b/test/jasmine/tests/pointcloud_test.js @@ -0,0 +1,255 @@ +'use strict'; + +var Plotly = require('@lib/index'); +var Lib = require('@src/lib'); +var d3Select = require('../../strict-d3').select; + +// Test utilities +var createGraphDiv = require('../assets/create_graph_div'); +var destroyGraphDiv = require('../assets/destroy_graph_div'); + +var delay = require('../assets/delay'); +var mouseEvent = require('../assets/mouse_event'); +var readPixel = require('../assets/read_pixel'); + +var multipleScatter2dMock = require('@mocks/gl2d_scatter2d-multiple-colors.json'); + +var plotData = { + 'data': [ + { + 'type': 'pointcloud', + 'mode': 'markers', + 'marker': { + 'sizemin': 0.5, + 'sizemax': 100, + 'arearatio': 0, + 'color': 'rgba(255, 0, 0, 0.6)' + }, + 'x': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + 'y': [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] + }, + { + 'type': 'pointcloud', + 'mode': 'markers', + 'marker': { + 'sizemin': 0.5, + 'sizemax': 100, + 'arearatio': 0, + 'color': 'rgba(0, 0, 255, 0.9)', + 'opacity': 0.8, + 'blend': true + }, + 'opacity': 0.7, + 'x': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + 'y': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + }, + { + 'type': 'pointcloud', + 'mode': 'markers', + 'marker': { + 'sizemin': 0.5, + 'sizemax': 100, + 'border': { + 'color': 'rgb(0, 0, 0)', + 'arearatio': 0.7071 + }, + 'color': 'green', + 'opacity': 0.8, + 'blend': true + }, + 'opacity': 0.7, + 'x': [3, 4.5, 6], + 'y': [9, 9, 9] + }, + { + 'type': 'pointcloud', + 'mode': 'markers', + 'marker': { + 'sizemin': 0.5, + 'sizemax': 100, + 'color': 'yellow', + 'opacity': 0.8, + 'blend': true + }, + 'opacity': 0.7, + 'xy': new Float32Array([1, 3, 9, 3]), + 'indices': new Int32Array([0, 1]), + 'xbounds': [1, 9], + 'ybounds': [3, 3] + }, + { + 'type': 'pointcloud', + 'mode': 'markers', + 'marker': { + 'sizemin': 0.5, + 'sizemax': 100, + 'color': 'orange', + 'opacity': 0.8, + 'blend': true + }, + 'opacity': 0.7, + 'xy': new Float32Array([1, 4, 9, 4]), + 'indices': new Int32Array([0, 1]) + }, + { + 'type': 'pointcloud', + 'mode': 'markers', + 'marker': { + 'sizemin': 0.5, + 'sizemax': 100, + 'color': 'darkorange', + 'opacity': 0.8, + 'blend': true + }, + 'opacity': 0.7, + 'xy': new Float32Array([1, 5, 9, 5]), + 'xbounds': [1, 9], + 'ybounds': [5, 5] + }, + { + 'type': 'pointcloud', + 'mode': 'markers', + 'marker': { + 'sizemin': 0.5, + 'sizemax': 100, + 'color': 'red', + 'opacity': 0.8, + 'blend': true + }, + 'opacity': 0.7, + 'xy': new Float32Array([1, 6, 9, 6]) + } + ], + 'layout': { + 'title': 'Point Cloud - basic', + 'xaxis': { + 'type': 'linear', + 'range': [ + -2.501411175139456, + 43.340777299865266 + ], + 'autorange': true + }, + 'yaxis': { + 'type': 'linear', + 'range': [ + 4, + 6 + ], + 'autorange': true + }, + 'height': 598, + 'width': 1080, + 'autosize': true, + 'showlegend': false + } +}; + +describe('pointcloud traces', function() { + var gd; + + beforeEach(function() { + gd = createGraphDiv(); + }); + + afterEach(function() { + Plotly.purge(gd); + destroyGraphDiv(); + }); + + it('@gl renders without raising an error', function(done) { + Plotly.newPlot(gd, Lib.extendDeep({}, plotData)) + .then(done, done.fail); + }); + + it('@gl should update properly', function(done) { + var scene2d; + + Plotly.newPlot(gd, Lib.extendDeep({}, plotData)) + .then(function() { + scene2d = gd._fullLayout._plots.xy._scene2d; + expect(scene2d.traces[gd._fullData[0].uid].type).toBe('pointcloud'); + + return Plotly.relayout(gd, 'xaxis.range', [3, 6]); + }).then(function() { + expect(scene2d.xaxis.range).toEqual([3, 6]); + expect(scene2d.yaxis.range).toBeCloseToArray([-1.415, 10.415], 2); + return Plotly.relayout(gd, 'xaxis.autorange', true); + }).then(function() { + expect(scene2d.xaxis.range).toBeCloseToArray([-0.548, 9.548], 2); + expect(scene2d.yaxis.range).toBeCloseToArray([-1.415, 10.415], 2); + return Plotly.relayout(gd, 'yaxis.range', [8, 20]); + }).then(function() { + expect(scene2d.xaxis.range).toBeCloseToArray([-0.548, 9.548], 2); + expect(scene2d.yaxis.range).toEqual([8, 20]); + return Plotly.relayout(gd, 'yaxis.autorange', true); + }).then(function() { + expect(scene2d.xaxis.range).toBeCloseToArray([-0.548, 9.548], 2); + expect(scene2d.yaxis.range).toBeCloseToArray([-1.415, 10.415], 2); + }) + .then(done, done.fail); + }); + + it('@gl should not change other traces colors', function(done) { + var _mock = Lib.extendDeep({}, multipleScatter2dMock); + Plotly.newPlot(gd, _mock) + .then(delay(20)) + .then(function() { + var canvas = d3Select('.gl-canvas-context').node(); + + var RGBA = readPixel(canvas, canvas.width / 2 - 1, canvas.height / 2 - 1, 1, 1); + + expect(RGBA[0] === 255).toBe(true, 'be red'); + expect(RGBA[1] === 0).toBe(true, 'no green'); + expect(RGBA[2] === 0).toBe(true, 'no blue'); + expect(RGBA[3] === 255).toBe(true, 'no transparent'); + }) + .then(done, done.fail); + }); + + it('@gl should respond to drag', function(done) { + function _drag(p0, p1) { + mouseEvent('mousemove', p0[0], p0[1], {buttons: 1}); + mouseEvent('mousedown', p0[0], p0[1], {buttons: 1}); + mouseEvent('mousemove', (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2, {buttons: 1}); + mouseEvent('mousemove', p1[0], p1[1], {buttons: 0}); + mouseEvent('mouseup', p1[0], p1[1], {buttons: 0}); + } + + function _assertRange(msg, xrng, yrng) { + expect(gd._fullLayout.xaxis.range).toBeCloseToArray(xrng, 2, msg); + expect(gd._fullLayout.yaxis.range).toBeCloseToArray(yrng, 2, msg); + } + + Plotly.newPlot(gd, Lib.extendDeep({}, plotData)) + .then(delay(20)) + .then(function() { + _assertRange('base', [-0.548, 9.548], [-1.415, 10.415]); + }) + .then(delay(20)) + .then(function() { _drag([200, 200], [350, 350]); }) + .then(delay(20)) + .then(function() { + _assertRange('after zoombox drag', [0.768, 1.591], [5.462, 7.584]); + }) + .then(function() { + return Plotly.relayout(gd, { + 'xaxis.autorange': true, + 'yaxis.autorange': true + }); + }) + .then(function() { + _assertRange('back to base', [-0.548, 9.548], [-1.415, 10.415]); + }) + .then(function() { + return Plotly.relayout(gd, 'dragmode', 'pan'); + }) + .then(delay(20)) + .then(function() { _drag([200, 200], [350, 350]); }) + .then(delay(20)) + .then(function() { + _assertRange('after pan drag', [0.2743, 10.3719], [-3.537, 8.292]); + }) + .then(done, done.fail); + }); +});