Skip to content

Commit a227ae1

Browse files
authored
Merge pull request #1657 from dfcreative/scattergl-lasso
Scattergl lasso
2 parents 1c38597 + 9d5d411 commit a227ae1

File tree

19 files changed

+557
-88
lines changed

19 files changed

+557
-88
lines changed

package.json

+4-3
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@
5656
"3d-view": "^2.0.0",
5757
"@plotly/d3-sankey": "^0.5.0",
5858
"alpha-shape": "^1.0.0",
59-
"color-rgba": "^1.0.4",
59+
"color-rgba": "^1.1.0",
6060
"convex-hull": "^1.0.3",
6161
"country-regex": "^1.1.0",
6262
"d3": "^3.5.12",
@@ -76,8 +76,8 @@
7676
"gl-plot2d": "^1.2.0",
7777
"gl-plot3d": "^1.5.4",
7878
"gl-pointcloud2d": "^1.0.0",
79-
"gl-scatter2d": "^1.2.2",
80-
"gl-scatter2d-sdf": "^1.3.10",
79+
"gl-scatter2d": "^1.3.1",
80+
"gl-scatter2d-sdf": "^1.3.11",
8181
"gl-scatter3d": "^1.0.4",
8282
"gl-select-box": "^1.0.1",
8383
"gl-shader": "4.2.0",
@@ -86,6 +86,7 @@
8686
"mapbox-gl": "^0.22.0",
8787
"matrix-camera-controller": "^2.1.3",
8888
"mouse-change": "^1.4.0",
89+
"mouse-event-offset": "^3.0.2",
8990
"mouse-wheel": "^1.0.2",
9091
"ndarray": "^1.0.18",
9192
"ndarray-fill": "^1.0.2",

src/components/modebar/manage.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) {
121121
if(((hasCartesian || hasGL2D) && !allAxesFixed) || hasTernary) {
122122
dragModeGroup = ['zoom2d', 'pan2d'];
123123
}
124-
if((hasCartesian || hasTernary) && isSelectable(fullData)) {
124+
if((hasCartesian || hasTernary || hasGL2D) && isSelectable(fullData)) {
125125
dragModeGroup.push('select2d');
126126
dragModeGroup.push('lasso2d');
127127
}
@@ -173,7 +173,7 @@ function isSelectable(fullData) {
173173

174174
if(!trace._module || !trace._module.selectPoints) continue;
175175

176-
if(trace.type === 'scatter' || trace.type === 'scatterternary') {
176+
if(trace.type === 'scatter' || trace.type === 'scatterternary' || trace.type === 'scattergl') {
177177
if(scatterSubTypes.hasMarkers(trace) || scatterSubTypes.hasText(trace)) {
178178
selectable = true;
179179
}

src/lib/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,7 @@ lib.minExtend = function(obj1, obj2) {
451451
for(i = 0; i < keys.length; i++) {
452452
k = keys[i];
453453
v = obj1[k];
454-
if(k.charAt(0) === '_' || typeof v === 'function') continue;
454+
if(k.charAt(0) === '_' || typeof v === 'function' || k === 'glTrace') continue;
455455
else if(k === 'module') objOut[k] = v;
456456
else if(Array.isArray(v)) objOut[k] = v.slice(0, arrayLen);
457457
else if(v && (typeof v === 'object')) objOut[k] = lib.minExtend(obj1[k], obj2[k]);

src/plot_api/plot_api.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -1948,15 +1948,16 @@ function _relayout(gd, aobj) {
19481948
// trunk nodes (everything except the leaf)
19491949
ptrunk = p.parts.slice(0, pend).join('.'),
19501950
parentIn = Lib.nestedProperty(gd.layout, ptrunk).get(),
1951-
parentFull = Lib.nestedProperty(fullLayout, ptrunk).get();
1951+
parentFull = Lib.nestedProperty(fullLayout, ptrunk).get(),
1952+
vOld = p.get();
19521953

19531954
if(vi === undefined) continue;
19541955

19551956
redoit[ai] = vi;
19561957

19571958
// axis reverse is special - it is its own inverse
19581959
// op and has no flag.
1959-
undoit[ai] = (pleaf === 'reverse') ? vi : p.get();
1960+
undoit[ai] = (pleaf === 'reverse') ? vi : vOld;
19601961

19611962
// Setting width or height to null must reset the graph's width / height
19621963
// back to its initial value as computed during the first pass in Plots.plotAutoSize.
@@ -2166,7 +2167,16 @@ function _relayout(gd, aobj) {
21662167
}
21672168
else if(fullLayout._has('gl2d') &&
21682169
(ai.indexOf('axis') !== -1 || ai === 'plot_bgcolor')
2169-
) flags.doplot = true;
2170+
) {
2171+
flags.doplot = true;
2172+
}
2173+
else if(fullLayout._has('gl2d') &&
2174+
(ai === 'dragmode' &&
2175+
(vi === 'lasso' || vi === 'select') &&
2176+
!(vOld === 'lasso' || vOld === 'select'))
2177+
) {
2178+
flags.docalc = true;
2179+
}
21702180
else if(ai === 'hiddenlabels') flags.docalc = true;
21712181
else if(proot.indexOf('legend') !== -1) flags.dolegend = true;
21722182
else if(ai.indexOf('title') !== -1) flags.doticks = true;

src/plot_api/subroutines.js

+34-28
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,16 @@ exports.lsInner = function(gd) {
126126

127127
var freefinished = [];
128128
subplotSelection.each(function(subplot) {
129-
var plotinfo = fullLayout._plots[subplot],
130-
xa = Plotly.Axes.getFromId(gd, subplot, 'x'),
129+
var plotinfo = fullLayout._plots[subplot];
130+
131+
var xa = Plotly.Axes.getFromId(gd, subplot, 'x'),
131132
ya = Plotly.Axes.getFromId(gd, subplot, 'y');
132133

133134
// reset scale in case the margins have changed
134135
xa.setScale();
135136
ya.setScale();
136137

137-
if(plotinfo.bg) {
138+
if(plotinfo.bg && fullLayout._has('cartesian')) {
138139
plotinfo.bg
139140
.call(Drawing.setRect,
140141
xa._offset - gs.p, ya._offset - gs.p,
@@ -254,27 +255,29 @@ exports.lsInner = function(gd) {
254255
rightpos += xa._offset - gs.l;
255256
}
256257

257-
plotinfo.xlines
258-
.attr('transform', originx)
259-
.attr('d', (
260-
(showbottom ? (xpathPrefix + bottompos + xpathSuffix) : '') +
261-
(showtop ? (xpathPrefix + toppos + xpathSuffix) : '') +
262-
(showfreex ? (xpathPrefix + freeposx + xpathSuffix) : '')) ||
263-
// so it doesn't barf with no lines shown
264-
'M0,0')
265-
.style('stroke-width', xlw + 'px')
266-
.call(Color.stroke, xa.showline ?
267-
xa.linecolor : 'rgba(0,0,0,0)');
268-
plotinfo.ylines
269-
.attr('transform', originy)
270-
.attr('d', (
271-
(showleft ? ('M' + leftpos + ypathSuffix) : '') +
272-
(showright ? ('M' + rightpos + ypathSuffix) : '') +
273-
(showfreey ? ('M' + freeposy + ypathSuffix) : '')) ||
274-
'M0,0')
275-
.attr('stroke-width', ylw + 'px')
276-
.call(Color.stroke, ya.showline ?
277-
ya.linecolor : 'rgba(0,0,0,0)');
258+
if(fullLayout._has('cartesian')) {
259+
plotinfo.xlines
260+
.attr('transform', originx)
261+
.attr('d', (
262+
(showbottom ? (xpathPrefix + bottompos + xpathSuffix) : '') +
263+
(showtop ? (xpathPrefix + toppos + xpathSuffix) : '') +
264+
(showfreex ? (xpathPrefix + freeposx + xpathSuffix) : '')) ||
265+
// so it doesn't barf with no lines shown
266+
'M0,0')
267+
.style('stroke-width', xlw + 'px')
268+
.call(Color.stroke, xa.showline ?
269+
xa.linecolor : 'rgba(0,0,0,0)');
270+
plotinfo.ylines
271+
.attr('transform', originy)
272+
.attr('d', (
273+
(showleft ? ('M' + leftpos + ypathSuffix) : '') +
274+
(showright ? ('M' + rightpos + ypathSuffix) : '') +
275+
(showfreey ? ('M' + freeposy + ypathSuffix) : '')) ||
276+
'M0,0')
277+
.attr('stroke-width', ylw + 'px')
278+
.call(Color.stroke, ya.showline ?
279+
ya.linecolor : 'rgba(0,0,0,0)');
280+
}
278281

279282
plotinfo.xaxislayer.attr('transform', originx);
280283
plotinfo.yaxislayer.attr('transform', originy);
@@ -375,19 +378,22 @@ exports.doTicksRelayout = function(gd) {
375378

376379
exports.doModeBar = function(gd) {
377380
var fullLayout = gd._fullLayout;
378-
var subplotIds, i;
381+
var subplotIds, scene, i;
379382

380383
ModeBar.manage(gd);
381384
initInteractions(gd);
382385

383386
subplotIds = Plots.getSubplotIds(fullLayout, 'gl3d');
384387
for(i = 0; i < subplotIds.length; i++) {
385-
var scene = fullLayout[subplotIds[i]]._scene;
388+
scene = fullLayout[subplotIds[i]]._scene;
386389
scene.updateFx(fullLayout.dragmode, fullLayout.hovermode);
387390
}
388391

389-
// no need to do this for gl2d subplots,
390-
// Plots.linkSubplots takes care of it all.
392+
subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d');
393+
for(i = 0; i < subplotIds.length; i++) {
394+
scene = fullLayout._plots[subplotIds[i]]._scene2d;
395+
scene.updateFx(fullLayout.dragmode);
396+
}
391397

392398
return Plots.previousPromises(gd);
393399
};

src/plots/cartesian/axes.js

+3
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,9 @@ axes.doAutoRange = function(ax) {
360360
if(ax.autorange && hasDeps) {
361361
ax.range = axes.getAutoRange(ax);
362362

363+
ax._r = ax.range.slice();
364+
ax._rl = Lib.simpleMap(ax._r, ax.r2l);
365+
363366
// doAutoRange will get called on fullLayout,
364367
// but we want to report its results back to layout
365368

src/plots/cartesian/graph_interact.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ var dragBox = require('./dragbox');
2020
module.exports = function initInteractions(gd) {
2121
var fullLayout = gd._fullLayout;
2222

23-
if(!fullLayout._has('cartesian') || gd._context.staticPlot) return;
23+
if((!fullLayout._has('cartesian') && !fullLayout._has('gl2d')) || gd._context.staticPlot) return;
2424

2525
var subplots = Object.keys(fullLayout._plots || {}).sort(function(a, b) {
2626
// sort overlays last, then by x axis number, then y axis number
@@ -38,8 +38,6 @@ module.exports = function initInteractions(gd) {
3838
subplots.forEach(function(subplot) {
3939
var plotinfo = fullLayout._plots[subplot];
4040

41-
if(!fullLayout._has('cartesian')) return;
42-
4341
var xa = plotinfo.xaxis,
4442
ya = plotinfo.yaxis,
4543

src/plots/cartesian/index.js

+5
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,11 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
184184
oldFullLayout._infolayer.select('.' + axIds[i] + 'title').remove();
185185
}
186186
}
187+
188+
// clean selection
189+
if(oldFullLayout._zoomlayer) {
190+
oldFullLayout._zoomlayer.selectAll('.select-outline').remove();
191+
}
187192
};
188193

189194
exports.drawFramework = function(gd) {

src/plots/gl2d/index.js

+13-9
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,18 @@
1212
var Scene2D = require('./scene2d');
1313
var Plots = require('../plots');
1414
var xmlnsNamespaces = require('../../constants/xmlns_namespaces');
15-
15+
var constants = require('../cartesian/constants');
16+
var Cartesian = require('../cartesian');
1617

1718
exports.name = 'gl2d';
1819

1920
exports.attr = ['xaxis', 'yaxis'];
2021

2122
exports.idRoot = ['x', 'y'];
2223

23-
exports.idRegex = {
24-
x: /^x([2-9]|[1-9][0-9]+)?$/,
25-
y: /^y([2-9]|[1-9][0-9]+)?$/
26-
};
24+
exports.idRegex = constants.idRegex;
2725

28-
exports.attrRegex = {
29-
x: /^xaxis([2-9]|[1-9][0-9]+)?$/,
30-
y: /^yaxis([2-9]|[1-9][0-9]+)?$/
31-
};
26+
exports.attrRegex = constants.attrRegex;
3227

3328
exports.attributes = require('../cartesian/attributes');
3429

@@ -82,6 +77,15 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
8277
delete oldFullLayout._plots[id];
8378
}
8479
}
80+
81+
// since we use cartesian interactions, do cartesian clean
82+
Cartesian.clean.apply(this, arguments);
83+
};
84+
85+
exports.drawFramework = function(gd) {
86+
if(!gd._context.staticPlot) {
87+
Cartesian.drawFramework(gd);
88+
}
8589
};
8690

8791
exports.toSVG = function(gd) {

src/plots/gl2d/scene2d.js

+9
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ proto.makeFramework = function() {
154154
// create div to catch the mouse event
155155
var mouseContainer = this.mouseContainer = document.createElement('div');
156156
mouseContainer.style.position = 'absolute';
157+
mouseContainer.style['pointer-events'] = 'auto';
157158

158159
// append canvas, hover svg and mouse div to container
159160
var container = this.container;
@@ -380,6 +381,7 @@ proto.plot = function(fullData, calcData, fullLayout) {
380381

381382
this.updateRefs(fullLayout);
382383
this.updateTraces(fullData, calcData);
384+
this.updateFx(fullLayout.dragmode);
383385

384386
var width = fullLayout.width,
385387
height = fullLayout.height;
@@ -520,7 +522,14 @@ proto.updateTraces = function(fullData, calcData) {
520522
this.glplot.objects.sort(function(a, b) {
521523
return a._trace.index - b._trace.index;
522524
});
525+
};
523526

527+
proto.updateFx = function(dragmode) {
528+
if(dragmode === 'lasso' || dragmode === 'select') {
529+
this.mouseContainer.style['pointer-events'] = 'none';
530+
} else {
531+
this.mouseContainer.style['pointer-events'] = 'auto';
532+
}
524533
};
525534

526535
proto.emitPointAction = function(nextSelection, eventType) {

src/plots/gl3d/camera.js

+21-3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ var now = require('right-now');
1414
var createView = require('3d-view');
1515
var mouseChange = require('mouse-change');
1616
var mouseWheel = require('mouse-wheel');
17+
var mouseOffset = require('mouse-event-offset');
1718

1819
function createCamera(element, options) {
1920
element = element || document.body;
@@ -179,8 +180,24 @@ function createCamera(element, options) {
179180
return false;
180181
});
181182

182-
var lastX = 0, lastY = 0;
183-
mouseChange(element, function(buttons, x, y, mods) {
183+
var lastX = 0, lastY = 0, lastMods = {shift: false, control: false, alt: false, meta: false};
184+
mouseChange(element, handleInteraction);
185+
186+
// enable simple touch interactions
187+
element.addEventListener('touchstart', function(ev) {
188+
var xy = mouseOffset(ev.changedTouches[0], element);
189+
handleInteraction(0, xy[0], xy[1], lastMods);
190+
handleInteraction(1, xy[0], xy[1], lastMods);
191+
});
192+
element.addEventListener('touchmove', function(ev) {
193+
var xy = mouseOffset(ev.changedTouches[0], element);
194+
handleInteraction(1, xy[0], xy[1], lastMods);
195+
});
196+
element.addEventListener('touchend', function() {
197+
handleInteraction(0, lastX, lastY, lastMods);
198+
});
199+
200+
function handleInteraction(buttons, x, y, mods) {
184201
var keyBindingMode = camera.keyBindingMode;
185202

186203
if(keyBindingMode === false) return;
@@ -225,9 +242,10 @@ function createCamera(element, options) {
225242

226243
lastX = x;
227244
lastY = y;
245+
lastMods = mods;
228246

229247
return true;
230-
});
248+
}
231249

232250
mouseWheel(element, function(dx, dy) {
233251
if(camera.keyBindingMode === false) return;

src/traces/scatter/hover.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,12 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
2424
ya = pointData.ya,
2525
xpx = xa.c2p(xval),
2626
ypx = ya.c2p(yval),
27-
pt = [xpx, ypx];
27+
pt = [xpx, ypx],
28+
hoveron = trace.hoveron || '';
2829

2930
// look for points to hover on first, then take fills only if we
3031
// didn't find a point
31-
if(trace.hoveron.indexOf('points') !== -1) {
32+
if(hoveron.indexOf('points') !== -1) {
3233
var dx = function(di) {
3334
// scatter points: d.mrc is the calculated marker radius
3435
// adjust the distance so if you're inside the marker it
@@ -84,7 +85,7 @@ module.exports = function hoverPoints(pointData, xval, yval, hovermode) {
8485
}
8586

8687
// even if hoveron is 'fills', only use it if we have polygons too
87-
if(trace.hoveron.indexOf('fills') !== -1 && trace._polygons) {
88+
if(hoveron.indexOf('fills') !== -1 && trace._polygons) {
8889
var polygons = trace._polygons,
8990
polygonsIn = [],
9091
inside = false,

0 commit comments

Comments
 (0)