Skip to content

Commit edf52ca

Browse files
authoredJul 10, 2017
Merge pull request #1836 from plotly/scattermapbox-lasso
Add scattermapbox select/lasso drag modes
2 parents 17b0d38 + 882e8e3 commit edf52ca

File tree

27 files changed

+550
-138
lines changed

27 files changed

+550
-138
lines changed
 

‎src/components/fx/layout_defaults.js

+8
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
2828
else hovermodeDflt = 'closest';
2929

3030
coerce('hovermode', hovermodeDflt);
31+
32+
// if only mapbox subplots is present on graph,
33+
// reset 'zoom' dragmode to 'pan' until 'zoom' is implemented,
34+
// so that the correct modebar button is active
35+
if(layoutOut._has('mapbox') && layoutOut._basePlotModules.length === 1 &&
36+
layoutOut.dragmode === 'zoom') {
37+
layoutOut.dragmode = 'pan';
38+
}
3139
};
3240

3341
function isHoriz(fullData) {

‎src/components/modebar/manage.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
var Axes = require('../../plots/cartesian/axes');
1313
var scatterSubTypes = require('../../traces/scatter/subtypes');
14+
var Registry = require('../../registry');
1415

1516
var createModeBar = require('./modebar');
1617
var modeBarButtons = require('./buttons');
@@ -78,7 +79,8 @@ function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) {
7879
hasGeo = fullLayout._has('geo'),
7980
hasPie = fullLayout._has('pie'),
8081
hasGL2D = fullLayout._has('gl2d'),
81-
hasTernary = fullLayout._has('ternary');
82+
hasTernary = fullLayout._has('ternary'),
83+
hasMapbox = fullLayout._has('mapbox');
8284

8385
var groups = [];
8486

@@ -121,7 +123,10 @@ function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) {
121123
if(((hasCartesian || hasGL2D) && !allAxesFixed) || hasTernary) {
122124
dragModeGroup = ['zoom2d', 'pan2d'];
123125
}
124-
if((hasCartesian || hasTernary || hasGL2D) && isSelectable(fullData)) {
126+
if(hasMapbox) {
127+
dragModeGroup = ['pan2d'];
128+
}
129+
if(isSelectable(fullData)) {
125130
dragModeGroup.push('select2d');
126131
dragModeGroup.push('lasso2d');
127132
}
@@ -173,7 +178,7 @@ function isSelectable(fullData) {
173178

174179
if(!trace._module || !trace._module.selectPoints) continue;
175180

176-
if(trace.type === 'scatter' || trace.type === 'scatterternary' || trace.type === 'scattergl') {
181+
if(Registry.traceIs(trace, 'scatter-like')) {
177182
if(scatterSubTypes.hasMarkers(trace) || scatterSubTypes.hasText(trace)) {
178183
selectable = true;
179184
}

‎src/constants/interactions.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,8 @@ module.exports = {
1818

1919
// ms between first mousedown and 2nd mouseup to constitute dblclick...
2020
// we don't seem to have access to the system setting
21-
DBLCLICKDELAY: 300
21+
DBLCLICKDELAY: 300,
22+
23+
// opacity dimming fraction for points that are not in selection
24+
DESELECTDIM: 0.2
2225
};

‎src/lib/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ lib.minExtend = function(obj1, obj2) {
458458
for(i = 0; i < keys.length; i++) {
459459
k = keys[i];
460460
v = obj1[k];
461-
if(k.charAt(0) === '_' || typeof v === 'function' || k === 'glTrace') continue;
461+
if(k.charAt(0) === '_' || typeof v === 'function') continue;
462462
else if(k === 'module') objOut[k] = v;
463463
else if(Array.isArray(v)) objOut[k] = v.slice(0, arrayLen);
464464
else if(v && (typeof v === 'object')) objOut[k] = lib.minExtend(obj1[k], obj2[k]);

‎src/plot_api/subroutines.js

+11-5
Original file line numberDiff line numberDiff line change
@@ -378,21 +378,27 @@ exports.doTicksRelayout = function(gd) {
378378

379379
exports.doModeBar = function(gd) {
380380
var fullLayout = gd._fullLayout;
381-
var subplotIds, scene, i;
381+
var subplotIds, subplotObj, i;
382382

383383
ModeBar.manage(gd);
384384
initInteractions(gd);
385385

386386
subplotIds = Plots.getSubplotIds(fullLayout, 'gl3d');
387387
for(i = 0; i < subplotIds.length; i++) {
388-
scene = fullLayout[subplotIds[i]]._scene;
389-
scene.updateFx(fullLayout.dragmode, fullLayout.hovermode);
388+
subplotObj = fullLayout[subplotIds[i]]._scene;
389+
subplotObj.updateFx(fullLayout.dragmode, fullLayout.hovermode);
390390
}
391391

392392
subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d');
393393
for(i = 0; i < subplotIds.length; i++) {
394-
scene = fullLayout._plots[subplotIds[i]]._scene2d;
395-
scene.updateFx(fullLayout.dragmode);
394+
subplotObj = fullLayout._plots[subplotIds[i]]._scene2d;
395+
subplotObj.updateFx(fullLayout.dragmode);
396+
}
397+
398+
subplotIds = Plots.getSubplotIds(fullLayout, 'mapbox');
399+
for(i = 0; i < subplotIds.length; i++) {
400+
subplotObj = fullLayout[subplotIds[i]]._subplot;
401+
subplotObj.updateFx(fullLayout);
396402
}
397403

398404
return Plots.previousPromises(gd);

‎src/plots/cartesian/index.js

-5
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,6 @@ 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-
}
192187
};
193188

194189
exports.drawFramework = function(gd) {

‎src/plots/cartesian/select.js

+39-25
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ function getAxId(ax) { return ax._id; }
2525
module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
2626
var zoomLayer = dragOptions.gd._fullLayout._zoomlayer,
2727
dragBBox = dragOptions.element.getBoundingClientRect(),
28-
xs = dragOptions.plotinfo.xaxis._offset,
29-
ys = dragOptions.plotinfo.yaxis._offset,
28+
plotinfo = dragOptions.plotinfo,
29+
xs = plotinfo.xaxis._offset,
30+
ys = plotinfo.yaxis._offset,
3031
x0 = startX - dragBBox.left,
3132
y0 = startY - dragBBox.top,
3233
x1 = x0,
@@ -71,6 +72,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
7172
searchInfo,
7273
selection = [],
7374
eventData;
75+
7476
for(i = 0; i < gd.calcdata.length; i++) {
7577
cd = gd.calcdata[i];
7678
trace = cd[0].trace;
@@ -106,9 +108,41 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
106108

107109
function ascending(a, b) { return a - b; }
108110

111+
// allow subplots to override fillRangeItems routine
112+
var fillRangeItems;
113+
114+
if(plotinfo.fillRangeItems) {
115+
fillRangeItems = plotinfo.fillRangeItems;
116+
} else {
117+
if(mode === 'select') {
118+
fillRangeItems = function(eventData, poly) {
119+
var ranges = eventData.range = {};
120+
121+
for(i = 0; i < allAxes.length; i++) {
122+
var ax = allAxes[i];
123+
var axLetter = ax._id.charAt(0);
124+
125+
ranges[ax._id] = [
126+
ax.p2d(poly[axLetter + 'min']),
127+
ax.p2d(poly[axLetter + 'max'])
128+
].sort(ascending);
129+
}
130+
};
131+
} else {
132+
fillRangeItems = function(eventData, poly, pts) {
133+
var dataPts = eventData.lassoPoints = {};
134+
135+
for(i = 0; i < allAxes.length; i++) {
136+
var ax = allAxes[i];
137+
dataPts[ax._id] = pts.filtered.map(axValue(ax));
138+
}
139+
};
140+
}
141+
}
142+
109143
dragOptions.moveFn = function(dx0, dy0) {
110-
var poly,
111-
ax;
144+
var poly;
145+
112146
x1 = Math.max(0, Math.min(pw, dx0 + x0));
113147
y1 = Math.max(0, Math.min(ph, dy0 + y0));
114148

@@ -158,27 +192,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
158192
}
159193

160194
eventData = {points: selection};
161-
162-
if(mode === 'select') {
163-
var ranges = eventData.range = {},
164-
axLetter;
165-
166-
for(i = 0; i < allAxes.length; i++) {
167-
ax = allAxes[i];
168-
axLetter = ax._id.charAt(0);
169-
ranges[ax._id] = [
170-
ax.p2d(poly[axLetter + 'min']),
171-
ax.p2d(poly[axLetter + 'max'])].sort(ascending);
172-
}
173-
}
174-
else {
175-
var dataPts = eventData.lassoPoints = {};
176-
177-
for(i = 0; i < allAxes.length; i++) {
178-
ax = allAxes[i];
179-
dataPts[ax._id] = pts.filtered.map(axValue(ax));
180-
}
181-
}
195+
fillRangeItems(eventData, poly, pts);
182196
dragOptions.gd.emit('plotly_selecting', eventData);
183197
};
184198

‎src/plots/mapbox/mapbox.js

+74-4
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ var mapboxgl = require('mapbox-gl');
1313

1414
var Fx = require('../../components/fx');
1515
var Lib = require('../../lib');
16+
var dragElement = require('../../components/dragelement');
17+
var prepSelect = require('../cartesian/select');
1618
var constants = require('./constants');
1719
var layoutAttributes = require('./layout_attributes');
1820
var createMapboxLayer = require('./layers');
@@ -86,9 +88,9 @@ proto.plot = function(calcData, fullLayout, promises) {
8688
};
8789

8890
proto.createMap = function(calcData, fullLayout, resolve, reject) {
89-
var self = this,
90-
gd = self.gd,
91-
opts = self.opts;
91+
var self = this;
92+
var gd = self.gd;
93+
var opts = self.opts;
9294

9395
// store style id and URL or object
9496
var styleObj = self.styleObj = getStyleObj(opts.style);
@@ -107,7 +109,9 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) {
107109
pitch: opts.pitch,
108110

109111
interactive: !self.isStatic,
110-
preserveDrawingBuffer: self.isStatic
112+
preserveDrawingBuffer: self.isStatic,
113+
114+
boxZoom: false
111115
});
112116

113117
// clear navigation container
@@ -128,6 +132,8 @@ proto.createMap = function(calcData, fullLayout, resolve, reject) {
128132
self.resolveOnRender(resolve);
129133
});
130134

135+
if(self.isStatic) return;
136+
131137
// keep track of pan / zoom in user layout and emit relayout event
132138
map.on('moveend', function(eventData) {
133139
if(!self.map) return;
@@ -261,6 +267,7 @@ proto.updateLayout = function(fullLayout) {
261267

262268
this.updateLayers();
263269
this.updateFramework(fullLayout);
270+
this.updateFx(fullLayout);
264271
this.map.resize();
265272
};
266273

@@ -314,6 +321,69 @@ proto.createFramework = function(fullLayout) {
314321
self.updateFramework(fullLayout);
315322
};
316323

324+
proto.updateFx = function(fullLayout) {
325+
var self = this;
326+
var map = self.map;
327+
var gd = self.gd;
328+
329+
if(self.isStatic) return;
330+
331+
function invert(pxpy) {
332+
var obj = self.map.unproject(pxpy);
333+
return [obj.lng, obj.lat];
334+
}
335+
336+
var dragMode = fullLayout.dragmode;
337+
var fillRangeItems;
338+
339+
if(dragMode === 'select') {
340+
fillRangeItems = function(eventData, poly) {
341+
var ranges = eventData.range = {};
342+
ranges[self.id] = [
343+
invert([poly.xmin, poly.ymin]),
344+
invert([poly.xmax, poly.ymax])
345+
];
346+
};
347+
} else {
348+
fillRangeItems = function(eventData, poly, pts) {
349+
var dataPts = eventData.lassoPoints = {};
350+
dataPts[self.id] = pts.filtered.map(invert);
351+
};
352+
}
353+
354+
if(dragMode === 'select' || dragMode === 'lasso') {
355+
map.dragPan.disable();
356+
357+
var dragOptions = {
358+
element: self.div,
359+
gd: gd,
360+
plotinfo: {
361+
xaxis: self.xaxis,
362+
yaxis: self.yaxis,
363+
fillRangeItems: fillRangeItems
364+
},
365+
xaxes: [self.xaxis],
366+
yaxes: [self.yaxis],
367+
subplot: self.id
368+
};
369+
370+
dragOptions.prepFn = function(e, startX, startY) {
371+
prepSelect(e, startX, startY, dragOptions, dragMode);
372+
};
373+
374+
dragOptions.doneFn = function(dragged, numClicks) {
375+
if(numClicks === 2) {
376+
fullLayout._zoomlayer.selectAll('.select-outline').remove();
377+
}
378+
};
379+
380+
dragElement.init(dragOptions);
381+
} else {
382+
map.dragPan.enable();
383+
self.div.onmousedown = null;
384+
}
385+
};
386+
317387
proto.updateFramework = function(fullLayout) {
318388
var domain = fullLayout[this.id].domain,
319389
size = fullLayout._size;

‎src/plots/plots.js

+4
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,10 @@ plots.cleanPlot = function(newFullData, newFullLayout, oldFullData, oldFullLayou
624624
.selectAll(query).remove();
625625
}
626626
}
627+
628+
if(oldFullLayout._zoomlayer) {
629+
oldFullLayout._zoomlayer.selectAll('.select-outline').remove();
630+
}
627631
};
628632

629633
plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {

‎src/plots/ternary/index.js

-4
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,4 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
6969
oldTernary.clipDef.remove();
7070
}
7171
}
72-
73-
if(oldFullLayout._zoomlayer) {
74-
oldFullLayout._zoomlayer.selectAll('.select-outline').remove();
75-
}
7672
};

‎src/traces/scatter/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Scatter.animatable = true;
3434
Scatter.moduleType = 'trace';
3535
Scatter.name = 'scatter';
3636
Scatter.basePlotModule = require('../../plots/cartesian');
37-
Scatter.categories = ['cartesian', 'symbols', 'markerColorscale', 'errorBarsOK', 'showLegend'];
37+
Scatter.categories = ['cartesian', 'symbols', 'markerColorscale', 'errorBarsOK', 'showLegend', 'scatter-like'];
3838
Scatter.meta = {
3939
description: [
4040
'The scatter trace type encompasses line charts, scatter charts, text charts, and bubble charts.',

‎src/traces/scatter/select.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@
1010
'use strict';
1111

1212
var subtypes = require('./subtypes');
13-
14-
var DESELECTDIM = 0.2;
13+
var DESELECTDIM = require('../../constants/interactions').DESELECTDIM;
1514

1615
module.exports = function selectPoints(searchInfo, polygon) {
1716
var cd = searchInfo.cd,

‎src/traces/scattergl/convert.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ var makeBubbleSizeFn = require('../scatter/make_bubble_size_func');
2727
var getTraceColor = require('../scatter/get_trace_color');
2828
var MARKER_SYMBOLS = require('../../constants/gl2d_markers');
2929
var DASHES = require('../../constants/gl2d_dashes');
30+
var DESELECTDIM = require('../../constants/interactions').DESELECTDIM;
3031

3132
var AXES = ['xaxis', 'yaxis'];
32-
var DESELECTDIM = 0.2;
3333
var TRANSPARENT = [0, 0, 0, 0];
3434

3535
function LineWithMarkers(scene, uid) {
@@ -322,8 +322,8 @@ proto.update = function(options, cdscatter) {
322322
this.color = getTraceColor(options, {});
323323

324324
// provide reference for selecting points
325-
if(cdscatter && cdscatter[0] && !cdscatter[0].glTrace) {
326-
cdscatter[0].glTrace = this;
325+
if(cdscatter && cdscatter[0] && !cdscatter[0]._glTrace) {
326+
cdscatter[0]._glTrace = this;
327327
}
328328
};
329329

0 commit comments

Comments
 (0)
Please sign in to comment.