Skip to content

Add scattermapbox select/lasso drag modes #1836

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Jul 10, 2017
8 changes: 8 additions & 0 deletions src/components/fx/layout_defaults.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ module.exports = function supplyLayoutDefaults(layoutIn, layoutOut, fullData) {
else hovermodeDflt = 'closest';

coerce('hovermode', hovermodeDflt);

// if only mapbox subplots is present on graph,
// reset 'zoom' dragmode to 'pan' until 'zoom' is implemented,
// so that the correct modebar button is active
if(layoutOut._has('mapbox') && layoutOut._basePlotModules.length === 1 &&
layoutOut.dragmode === 'zoom') {
layoutOut.dragmode = 'pan';
}
};

function isHoriz(fullData) {
Expand Down
11 changes: 8 additions & 3 deletions src/components/modebar/manage.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

var Axes = require('../../plots/cartesian/axes');
var scatterSubTypes = require('../../traces/scatter/subtypes');
var Registry = require('../../registry');

var createModeBar = require('./modebar');
var modeBarButtons = require('./buttons');
Expand Down Expand Up @@ -78,7 +79,8 @@ function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) {
hasGeo = fullLayout._has('geo'),
hasPie = fullLayout._has('pie'),
hasGL2D = fullLayout._has('gl2d'),
hasTernary = fullLayout._has('ternary');
hasTernary = fullLayout._has('ternary'),
hasMapbox = fullLayout._has('mapbox');

var groups = [];

Expand Down Expand Up @@ -121,7 +123,10 @@ function getButtonGroups(gd, buttonsToRemove, buttonsToAdd) {
if(((hasCartesian || hasGL2D) && !allAxesFixed) || hasTernary) {
dragModeGroup = ['zoom2d', 'pan2d'];
}
if((hasCartesian || hasTernary || hasGL2D) && isSelectable(fullData)) {
if(hasMapbox) {
dragModeGroup = ['pan2d'];
}
if(isSelectable(fullData)) {
dragModeGroup.push('select2d');
dragModeGroup.push('lasso2d');
}
Expand Down Expand Up @@ -173,7 +178,7 @@ function isSelectable(fullData) {

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

if(trace.type === 'scatter' || trace.type === 'scatterternary' || trace.type === 'scattergl') {
if(Registry.traceIs(trace, 'scatter-like')) {
if(scatterSubTypes.hasMarkers(trace) || scatterSubTypes.hasText(trace)) {
selectable = true;
}
Expand Down
5 changes: 4 additions & 1 deletion src/constants/interactions.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,8 @@ module.exports = {

// ms between first mousedown and 2nd mouseup to constitute dblclick...
// we don't seem to have access to the system setting
DBLCLICKDELAY: 300
DBLCLICKDELAY: 300,

// opacity dimming fraction for points that are not in selection
DESELECTDIM: 0.2
};
2 changes: 1 addition & 1 deletion src/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ lib.minExtend = function(obj1, obj2) {
for(i = 0; i < keys.length; i++) {
k = keys[i];
v = obj1[k];
if(k.charAt(0) === '_' || typeof v === 'function' || k === 'glTrace') continue;
if(k.charAt(0) === '_' || typeof v === 'function') continue;
else if(k === 'module') objOut[k] = v;
else if(Array.isArray(v)) objOut[k] = v.slice(0, arrayLen);
else if(v && (typeof v === 'object')) objOut[k] = lib.minExtend(obj1[k], obj2[k]);
Expand Down
16 changes: 11 additions & 5 deletions src/plot_api/subroutines.js
Original file line number Diff line number Diff line change
Expand Up @@ -378,21 +378,27 @@ exports.doTicksRelayout = function(gd) {

exports.doModeBar = function(gd) {
var fullLayout = gd._fullLayout;
var subplotIds, scene, i;
var subplotIds, subplotObj, i;

ModeBar.manage(gd);
initInteractions(gd);

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

subplotIds = Plots.getSubplotIds(fullLayout, 'gl2d');
for(i = 0; i < subplotIds.length; i++) {
scene = fullLayout._plots[subplotIds[i]]._scene2d;
scene.updateFx(fullLayout.dragmode);
subplotObj = fullLayout._plots[subplotIds[i]]._scene2d;
subplotObj.updateFx(fullLayout.dragmode);
}

subplotIds = Plots.getSubplotIds(fullLayout, 'mapbox');
for(i = 0; i < subplotIds.length; i++) {
subplotObj = fullLayout[subplotIds[i]]._subplot;
subplotObj.updateFx(fullLayout);
}

return Plots.previousPromises(gd);
Expand Down
5 changes: 0 additions & 5 deletions src/plots/cartesian/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,6 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
oldFullLayout._infolayer.select('.' + axIds[i] + 'title').remove();
}
}

// clean selection
if(oldFullLayout._zoomlayer) {
oldFullLayout._zoomlayer.selectAll('.select-outline').remove();
}
};

exports.drawFramework = function(gd) {
Expand Down
64 changes: 39 additions & 25 deletions src/plots/cartesian/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ function getAxId(ax) { return ax._id; }
module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
var zoomLayer = dragOptions.gd._fullLayout._zoomlayer,
dragBBox = dragOptions.element.getBoundingClientRect(),
xs = dragOptions.plotinfo.xaxis._offset,
ys = dragOptions.plotinfo.yaxis._offset,
plotinfo = dragOptions.plotinfo,
xs = plotinfo.xaxis._offset,
ys = plotinfo.yaxis._offset,
x0 = startX - dragBBox.left,
y0 = startY - dragBBox.top,
x1 = x0,
Expand Down Expand Up @@ -71,6 +72,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
searchInfo,
selection = [],
eventData;

for(i = 0; i < gd.calcdata.length; i++) {
cd = gd.calcdata[i];
trace = cd[0].trace;
Expand Down Expand Up @@ -106,9 +108,41 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {

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

// allow subplots to override fillRangeItems routine
var fillRangeItems;

if(plotinfo.fillRangeItems) {
fillRangeItems = plotinfo.fillRangeItems;
} else {
if(mode === 'select') {
fillRangeItems = function(eventData, poly) {
var ranges = eventData.range = {};

for(i = 0; i < allAxes.length; i++) {
var ax = allAxes[i];
var axLetter = ax._id.charAt(0);

ranges[ax._id] = [
ax.p2d(poly[axLetter + 'min']),
ax.p2d(poly[axLetter + 'max'])
].sort(ascending);
}
};
} else {
fillRangeItems = function(eventData, poly, pts) {
var dataPts = eventData.lassoPoints = {};

for(i = 0; i < allAxes.length; i++) {
var ax = allAxes[i];
dataPts[ax._id] = pts.filtered.map(axValue(ax));
}
};
}
}

dragOptions.moveFn = function(dx0, dy0) {
var poly,
ax;
var poly;

x1 = Math.max(0, Math.min(pw, dx0 + x0));
y1 = Math.max(0, Math.min(ph, dy0 + y0));

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

eventData = {points: selection};

if(mode === 'select') {
var ranges = eventData.range = {},
axLetter;

for(i = 0; i < allAxes.length; i++) {
ax = allAxes[i];
axLetter = ax._id.charAt(0);
ranges[ax._id] = [
ax.p2d(poly[axLetter + 'min']),
ax.p2d(poly[axLetter + 'max'])].sort(ascending);
}
}
else {
var dataPts = eventData.lassoPoints = {};

for(i = 0; i < allAxes.length; i++) {
ax = allAxes[i];
dataPts[ax._id] = pts.filtered.map(axValue(ax));
}
}
fillRangeItems(eventData, poly, pts);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, probably we have to merge this before I continue multiselect, because I depend on this part of code a lot.

dragOptions.gd.emit('plotly_selecting', eventData);
};

Expand Down
78 changes: 74 additions & 4 deletions src/plots/mapbox/mapbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ var mapboxgl = require('mapbox-gl');

var Fx = require('../../components/fx');
var Lib = require('../../lib');
var dragElement = require('../../components/dragelement');
var prepSelect = require('../cartesian/select');
var constants = require('./constants');
var layoutAttributes = require('./layout_attributes');
var createMapboxLayer = require('./layers');
Expand Down Expand Up @@ -86,9 +88,9 @@ proto.plot = function(calcData, fullLayout, promises) {
};

proto.createMap = function(calcData, fullLayout, resolve, reject) {
var self = this,
gd = self.gd,
opts = self.opts;
var self = this;
var gd = self.gd;
var opts = self.opts;

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

interactive: !self.isStatic,
preserveDrawingBuffer: self.isStatic
preserveDrawingBuffer: self.isStatic,

boxZoom: false
});

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

if(self.isStatic) return;

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

this.updateLayers();
this.updateFramework(fullLayout);
this.updateFx(fullLayout);
this.map.resize();
};

Expand Down Expand Up @@ -314,6 +321,69 @@ proto.createFramework = function(fullLayout) {
self.updateFramework(fullLayout);
};

proto.updateFx = function(fullLayout) {
var self = this;
var map = self.map;
var gd = self.gd;

if(self.isStatic) return;

function invert(pxpy) {
var obj = self.map.unproject(pxpy);
return [obj.lng, obj.lat];
}

var dragMode = fullLayout.dragmode;
var fillRangeItems;

if(dragMode === 'select') {
fillRangeItems = function(eventData, poly) {
var ranges = eventData.range = {};
ranges[self.id] = [
invert([poly.xmin, poly.ymin]),
invert([poly.xmax, poly.ymax])
];
};
} else {
fillRangeItems = function(eventData, poly, pts) {
var dataPts = eventData.lassoPoints = {};
dataPts[self.id] = pts.filtered.map(invert);
};
}

if(dragMode === 'select' || dragMode === 'lasso') {
map.dragPan.disable();

var dragOptions = {
element: self.div,
gd: gd,
plotinfo: {
xaxis: self.xaxis,
yaxis: self.yaxis,
fillRangeItems: fillRangeItems
},
xaxes: [self.xaxis],
yaxes: [self.yaxis],
subplot: self.id
};

dragOptions.prepFn = function(e, startX, startY) {
prepSelect(e, startX, startY, dragOptions, dragMode);
};

dragOptions.doneFn = function(dragged, numClicks) {
if(numClicks === 2) {
fullLayout._zoomlayer.selectAll('.select-outline').remove();
}
};

dragElement.init(dragOptions);
} else {
map.dragPan.enable();
self.div.onmousedown = null;
}
};

proto.updateFramework = function(fullLayout) {
var domain = fullLayout[this.id].domain,
size = fullLayout._size;
Expand Down
4 changes: 4 additions & 0 deletions src/plots/plots.js
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,10 @@ plots.cleanPlot = function(newFullData, newFullLayout, oldFullData, oldFullLayou
.selectAll(query).remove();
}
}

if(oldFullLayout._zoomlayer) {
oldFullLayout._zoomlayer.selectAll('.select-outline').remove();
}
};

plots.linkSubplots = function(newFullData, newFullLayout, oldFullData, oldFullLayout) {
Expand Down
4 changes: 0 additions & 4 deletions src/plots/ternary/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,4 @@ exports.clean = function(newFullData, newFullLayout, oldFullData, oldFullLayout)
oldTernary.clipDef.remove();
}
}

if(oldFullLayout._zoomlayer) {
oldFullLayout._zoomlayer.selectAll('.select-outline').remove();
}
};
2 changes: 1 addition & 1 deletion src/traces/scatter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Scatter.animatable = true;
Scatter.moduleType = 'trace';
Scatter.name = 'scatter';
Scatter.basePlotModule = require('../../plots/cartesian');
Scatter.categories = ['cartesian', 'symbols', 'markerColorscale', 'errorBarsOK', 'showLegend'];
Scatter.categories = ['cartesian', 'symbols', 'markerColorscale', 'errorBarsOK', 'showLegend', 'scatter-like'];
Scatter.meta = {
description: [
'The scatter trace type encompasses line charts, scatter charts, text charts, and bubble charts.',
Expand Down
3 changes: 1 addition & 2 deletions src/traces/scatter/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
'use strict';

var subtypes = require('./subtypes');

var DESELECTDIM = 0.2;
var DESELECTDIM = require('../../constants/interactions').DESELECTDIM;

module.exports = function selectPoints(searchInfo, polygon) {
var cd = searchInfo.cd,
Expand Down
6 changes: 3 additions & 3 deletions src/traces/scattergl/convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ var makeBubbleSizeFn = require('../scatter/make_bubble_size_func');
var getTraceColor = require('../scatter/get_trace_color');
var MARKER_SYMBOLS = require('../../constants/gl2d_markers');
var DASHES = require('../../constants/gl2d_dashes');
var DESELECTDIM = require('../../constants/interactions').DESELECTDIM;

var AXES = ['xaxis', 'yaxis'];
var DESELECTDIM = 0.2;
var TRANSPARENT = [0, 0, 0, 0];

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

// provide reference for selecting points
if(cdscatter && cdscatter[0] && !cdscatter[0].glTrace) {
cdscatter[0].glTrace = this;
if(cdscatter && cdscatter[0] && !cdscatter[0]._glTrace) {
cdscatter[0]._glTrace = this;
}
};

Expand Down
Loading