From 842adabc00b72a526dff226ebf38af65d6486b7f Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 26 Jun 2017 14:51:46 -0400 Subject: [PATCH 01/11] Switch lasso-drag to ctrl --- src/plots/cartesian/dragbox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 603e88d2f78..bd1b0b4c357 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -138,7 +138,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { if(isMainDrag) { // main dragger handles all drag modes, and changes // to pan (or to zoom if it already is pan) on shift - if(e.shiftKey) { + if(e.ctrlKey) { if(dragModeNow === 'pan') dragModeNow = 'zoom'; else dragModeNow = 'pan'; } From b4a9d3559d6fe13517df8d6089b9b51e5a9ea0ea Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 26 Jun 2017 16:20:58 -0400 Subject: [PATCH 02/11] Outline principle --- src/plots/cartesian/dragbox.js | 13 +++++++++---- src/plots/cartesian/select.js | 29 +++++++++++++++++++++++++++-- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index bd1b0b4c357..de833719da9 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -134,14 +134,21 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { plotinfo: plotinfo, prepFn: function(e, startX, startY) { var dragModeNow = gd._fullLayout.dragmode; + var isShift = false; if(isMainDrag) { // main dragger handles all drag modes, and changes // to pan (or to zoom if it already is pan) on shift - if(e.ctrlKey) { + if(e.shiftKey) { if(dragModeNow === 'pan') dragModeNow = 'zoom'; + else if(isSelectOrLasso(dragModeNow)) { + isShift = true; + } else dragModeNow = 'pan'; } + else if(e.ctrlKey) { + dragModeNow = 'pan'; + } } // all other draggers just pan else dragModeNow = 'pan'; @@ -921,9 +928,7 @@ function removeZoombox(gd) { } function isSelectOrLasso(dragmode) { - var modes = ['lasso', 'select']; - - return modes.indexOf(dragmode) !== -1; + return dragmode === 'lasso' || dragmode === 'select' } function xCorners(box, y0) { diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index cba5decac75..4328a1b49a4 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -22,6 +22,9 @@ var MINSELECT = constants.MINSELECT; function getAxId(ax) { return ax._id; } + +var polygons = [] + module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { var plot = dragOptions.gd._fullLayout._zoomlayer, dragBBox = dragOptions.element.getBoundingClientRect(), @@ -31,6 +34,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { y0 = startY - dragBBox.top, x1 = x0, y1 = y0, + //FIXME: replace path with prev polygon path path0 = 'M' + x0 + ',' + y0, pw = dragOptions.xaxes[0]._length, ph = dragOptions.yaxes[0]._length, @@ -41,6 +45,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { if(mode === 'lasso') { pts = filteredPolygon([[x0, y0]], constants.BENDPX); + polygons.unshift(pts) } var outlines = plot.selectAll('path.select-outline').data([1, 2]); @@ -144,9 +149,29 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { 'H' + poly.xmin + 'Z'); } else if(mode === 'lasso') { + // FIXME: merge polygons here pts.addPt([x1, y1]); - poly = polygonTester(pts.filtered); - outlines.attr('d', 'M' + pts.filtered.join('L') + 'Z'); + + var vertices = [] + var roots = [] + for (var i = 0; i < polygons.length; i++) { + var ppts = polygons[i].filtered + vertices = vertices.concat(ppts) + roots.push(ppts[0]) + vertices.push(ppts[0]) + } + while (roots.length) { + vertices.push(roots.pop()) + } + poly = polygonTester(vertices); + + // poly = polygonTester(pts.filtered); + + var paths = [] + for (var i = 0 ;i < polygons.length; i++) { + paths.push(polygons[i].filtered.join('L')) + } + outlines.attr('d', 'M' + paths.join('ZM') + 'Z'); } selection = []; From 1c615b5ded86bb12eaa71255400396203ab12f74 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 28 Jun 2017 18:16:49 -0400 Subject: [PATCH 03/11] Merge multipoly --- package.json | 1 + src/plots/cartesian/select.js | 48 ++++++++++++++++++++++++----------- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index a7186b1db76..ed1a21f4a02 100644 --- a/package.json +++ b/package.json @@ -92,6 +92,7 @@ "ndarray-fill": "^1.0.2", "ndarray-homography": "^1.0.0", "ndarray-ops": "^1.2.2", + "poly-bool": "^1.0.0", "regl": "^1.3.0", "right-now": "^1.0.0", "robust-orientation": "^1.1.3", diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 4328a1b49a4..35ab4a4bb4a 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -9,6 +9,7 @@ 'use strict'; +var polybool = require('poly-bool') var polygon = require('../../lib/polygon'); var color = require('../../components/color'); var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue; @@ -152,26 +153,32 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { // FIXME: merge polygons here pts.addPt([x1, y1]); - var vertices = [] - var roots = [] + var ppts = [] for (var i = 0; i < polygons.length; i++) { - var ppts = polygons[i].filtered - vertices = vertices.concat(ppts) - roots.push(ppts[0]) - vertices.push(ppts[0]) + ppts.push(polygons[i].filtered) } - while (roots.length) { - vertices.push(roots.pop()) - } - poly = polygonTester(vertices); + var mergedpoly = polymerge(ppts) + + // var vertices = [] + // var roots = [] + // for (var i = 0; i < polygons.length; i++) { + // var ppts = polygons[i].filtered + // vertices = vertices.concat(ppts) + // roots.push(ppts[0]) + // vertices.push(ppts[0]) + // } + // while (roots.length) { + // vertices.push(roots.pop()) + // } + poly = polygonTester(mergedpoly); // poly = polygonTester(pts.filtered); - var paths = [] - for (var i = 0 ;i < polygons.length; i++) { - paths.push(polygons[i].filtered.join('L')) - } - outlines.attr('d', 'M' + paths.join('ZM') + 'Z'); + // var paths = [] + // for (var i = 0 ;i < polygons.length; i++) { + // paths.push(polygons[i].filtered.join('L')) + // } + outlines.attr('d', 'M' + poly.pts.join('L') + 'Z'); } selection = []; @@ -241,3 +248,14 @@ function fillSelectionItem(selection, searchInfo) { return selection; } + + +function polymerge (list) { + let result = [list[0]] + + for (var i = 1; i < list.length; i++) { + result = polybool([list[i]], result, 'or') + } + + return result[0] +} From bcaacfd98380ab0910282e150f77d7b506c07ecd Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 3 Jul 2017 16:17:42 -0400 Subject: [PATCH 04/11] Make multiselect work properly --- src/lib/polygon.js | 35 +++++++++++++++ src/plots/cartesian/dragbox.js | 6 +-- src/plots/cartesian/select.js | 81 +++++++++++++--------------------- 3 files changed, 66 insertions(+), 56 deletions(-) diff --git a/src/lib/polygon.js b/src/lib/polygon.js index d30d1fda104..6ed34713a1e 100644 --- a/src/lib/polygon.js +++ b/src/lib/polygon.js @@ -31,6 +31,8 @@ var polygon = module.exports = {}; * returns boolean: is pt inside the polygon (including on its edges) */ polygon.tester = function tester(ptsIn) { + if(Array.isArray(ptsIn[0][0])) return polygon.multitester(ptsIn); + var pts = ptsIn.slice(), xmin = pts[0][0], xmax = xmin, @@ -160,6 +162,39 @@ polygon.tester = function tester(ptsIn) { }; }; +/** + * Test multiple polygons + */ +polygon.multitester = function multitester(list) { + var testers = [], xmin, xmax, ymin, ymax; + + for(var i = 0; i < list.length; i++) { + var tester = polygon.tester(list[i]); + testers.push(tester); + xmin = Math.min(xmin, tester.xmin); + xmax = Math.max(xmax, tester.xmax); + ymin = Math.min(ymin, tester.ymin); + ymax = Math.max(ymax, tester.ymax); + } + + function contains(pt, arg) { + for(var i = 0; i < testers.length; i++) { + if(testers[i].contains(pt, arg)) return true; + } + return false; + } + + return { + xmin: xmin, + xmax: xmax, + ymin: ymin, + ymax: ymax, + pts: [], + contains: contains, + isRect: false + }; +}; + /** * Test if a segment of a points array is bent or straight * diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index de833719da9..3c2112c0f38 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -134,16 +134,12 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { plotinfo: plotinfo, prepFn: function(e, startX, startY) { var dragModeNow = gd._fullLayout.dragmode; - var isShift = false; if(isMainDrag) { // main dragger handles all drag modes, and changes // to pan (or to zoom if it already is pan) on shift if(e.shiftKey) { if(dragModeNow === 'pan') dragModeNow = 'zoom'; - else if(isSelectOrLasso(dragModeNow)) { - isShift = true; - } else dragModeNow = 'pan'; } else if(e.ctrlKey) { @@ -928,7 +924,7 @@ function removeZoombox(gd) { } function isSelectOrLasso(dragmode) { - return dragmode === 'lasso' || dragmode === 'select' + return dragmode === 'lasso' || dragmode === 'select'; } function xCorners(box, y0) { diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 35ab4a4bb4a..ee23d1b30e3 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -9,7 +9,7 @@ 'use strict'; -var polybool = require('poly-bool') +var polybool = require('poly-bool'); var polygon = require('../../lib/polygon'); var color = require('../../components/color'); var appendArrayPointValue = require('../../components/fx/helpers').appendArrayPointValue; @@ -19,12 +19,13 @@ var constants = require('./constants'); var filteredPolygon = polygon.filter; var polygonTester = polygon.tester; +var multipolygonTester = polygon.multitester; var MINSELECT = constants.MINSELECT; function getAxId(ax) { return ax._id; } -var polygons = [] +var selectedPolyPts = [], lastMergedPolyPts; module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { var plot = dragOptions.gd._fullLayout._zoomlayer, @@ -35,18 +36,16 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { y0 = startY - dragBBox.top, x1 = x0, y1 = y0, - //FIXME: replace path with prev polygon path path0 = 'M' + x0 + ',' + y0, pw = dragOptions.xaxes[0]._length, ph = dragOptions.yaxes[0]._length, xAxisIds = dragOptions.xaxes.map(getAxId), yAxisIds = dragOptions.yaxes.map(getAxId), allAxes = dragOptions.xaxes.concat(dragOptions.yaxes), - pts; + currentPoly, mergedTester, mergedPolyPts, poly; if(mode === 'lasso') { - pts = filteredPolygon([[x0, y0]], constants.BENDPX); - polygons.unshift(pts) + currentPoly = filteredPolygon([[x0, y0]], constants.BENDPX); } var outlines = plot.selectAll('path.select-outline').data([1, 2]); @@ -113,8 +112,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { function ascending(a, b) { return a - b; } dragOptions.moveFn = function(dx0, dy0) { - var poly, - ax; + var ax; x1 = Math.max(0, Math.min(pw, dx0 + x0)); y1 = Math.max(0, Math.min(ph, dy0 + y0)); @@ -150,42 +148,31 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { 'H' + poly.xmin + 'Z'); } else if(mode === 'lasso') { - // FIXME: merge polygons here - pts.addPt([x1, y1]); - - var ppts = [] - for (var i = 0; i < polygons.length; i++) { - ppts.push(polygons[i].filtered) + currentPoly.addPt([x1, y1]); + + if(selectedPolyPts.length) { + mergedPolyPts = polybool(lastMergedPolyPts, [currentPoly.filtered], 'or'); + + var mergedPaths = []; + for(i = 0; i < mergedPolyPts.length; i++) { + var ppts = mergedPolyPts[i]; + mergedPaths.push(ppts.join('L') + 'L' + ppts[0]); + } + mergedTester = multipolygonTester(selectedPolyPts.concat([currentPoly.filtered])); + outlines.attr('d', 'M' + mergedPaths.join('M') + 'Z'); + } + else { + mergedPolyPts = [currentPoly.filtered]; + mergedTester = polygonTester(currentPoly.filtered); + outlines.attr('d', 'M' + mergedTester.pts.join('L') + 'Z'); } - var mergedpoly = polymerge(ppts) - - // var vertices = [] - // var roots = [] - // for (var i = 0; i < polygons.length; i++) { - // var ppts = polygons[i].filtered - // vertices = vertices.concat(ppts) - // roots.push(ppts[0]) - // vertices.push(ppts[0]) - // } - // while (roots.length) { - // vertices.push(roots.pop()) - // } - poly = polygonTester(mergedpoly); - - // poly = polygonTester(pts.filtered); - - // var paths = [] - // for (var i = 0 ;i < polygons.length; i++) { - // paths.push(polygons[i].filtered.join('L')) - // } - outlines.attr('d', 'M' + poly.pts.join('L') + 'Z'); } selection = []; for(i = 0; i < searchTraces.length; i++) { searchInfo = searchTraces[i]; [].push.apply(selection, fillSelectionItem( - searchInfo.selectPoints(searchInfo, poly), searchInfo + searchInfo.selectPoints(searchInfo, mergedTester), searchInfo )); } @@ -199,8 +186,8 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { ax = allAxes[i]; axLetter = ax._id.charAt(0); ranges[ax._id] = [ - ax.p2d(poly[axLetter + 'min']), - ax.p2d(poly[axLetter + 'max'])].sort(ascending); + ax.p2d(mergedTester[axLetter + 'min']), + ax.p2d(mergedTester[axLetter + 'max'])].sort(ascending); } } else { @@ -208,7 +195,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { for(i = 0; i < allAxes.length; i++) { ax = allAxes[i]; - dataPts[ax._id] = pts.filtered.map(axValue(ax)); + dataPts[ax._id] = currentPoly.filtered.map(axValue(ax)); } } dragOptions.gd.emit('plotly_selecting', eventData); @@ -229,6 +216,9 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { else { dragOptions.gd.emit('plotly_selected', eventData); } + + lastMergedPolyPts = mergedPolyPts; + selectedPolyPts.push(currentPoly.filtered); }; }; @@ -248,14 +238,3 @@ function fillSelectionItem(selection, searchInfo) { return selection; } - - -function polymerge (list) { - let result = [list[0]] - - for (var i = 1; i < list.length; i++) { - result = polybool([list[i]], result, 'or') - } - - return result[0] -} From c046a47f7a4504e7854ce4579f9dd057f7d68193 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Mon, 3 Jul 2017 18:16:44 -0400 Subject: [PATCH 05/11] Enable multiselect by shift --- src/plots/cartesian/dragbox.js | 7 ++++++- src/plots/cartesian/select.js | 12 +++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 3c2112c0f38..41200a1ad54 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -140,7 +140,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { // to pan (or to zoom if it already is pan) on shift if(e.shiftKey) { if(dragModeNow === 'pan') dragModeNow = 'zoom'; - else dragModeNow = 'pan'; + else if(!isSelectOrLasso(dragModeNow)) dragModeNow = 'pan'; } else if(e.ctrlKey) { dragModeNow = 'pan'; @@ -171,6 +171,11 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { else if(isSelectOrLasso(dragModeNow)) { dragOptions.xaxes = xa; dragOptions.yaxes = ya; + if(!e.shiftKey) { + // list of all prev polygons and their merge + dragOptions.polygons = []; + dragOptions.mergedPolygons = null; + } prepSelect(e, startX, startY, dragOptions, dragModeNow); } } diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index ee23d1b30e3..10133ea3cfb 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -25,8 +25,6 @@ var MINSELECT = constants.MINSELECT; function getAxId(ax) { return ax._id; } -var selectedPolyPts = [], lastMergedPolyPts; - module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { var plot = dragOptions.gd._fullLayout._zoomlayer, dragBBox = dragOptions.element.getBoundingClientRect(), @@ -150,15 +148,15 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { else if(mode === 'lasso') { currentPoly.addPt([x1, y1]); - if(selectedPolyPts.length) { - mergedPolyPts = polybool(lastMergedPolyPts, [currentPoly.filtered], 'or'); + if(dragOptions.polygons.length) { + mergedPolyPts = polybool(dragOptions.mergedPolygons, [currentPoly.filtered], 'or'); var mergedPaths = []; for(i = 0; i < mergedPolyPts.length; i++) { var ppts = mergedPolyPts[i]; mergedPaths.push(ppts.join('L') + 'L' + ppts[0]); } - mergedTester = multipolygonTester(selectedPolyPts.concat([currentPoly.filtered])); + mergedTester = multipolygonTester(dragOptions.polygons.concat([currentPoly.filtered])); outlines.attr('d', 'M' + mergedPaths.join('M') + 'Z'); } else { @@ -217,8 +215,8 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { dragOptions.gd.emit('plotly_selected', eventData); } - lastMergedPolyPts = mergedPolyPts; - selectedPolyPts.push(currentPoly.filtered); + dragOptions.mergedPolygons = mergedPolyPts; + dragOptions.polygons.push(currentPoly.filtered); }; }; From 329e0a887b8a431002f5dd203e65612544ea5661 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 4 Jul 2017 12:05:19 -0400 Subject: [PATCH 06/11] Enable selection for boxes --- src/lib/polygon.js | 6 +++- src/plots/cartesian/dragbox.js | 4 +-- src/plots/cartesian/select.js | 59 +++++++++++++++++----------------- 3 files changed, 36 insertions(+), 33 deletions(-) diff --git a/src/lib/polygon.js b/src/lib/polygon.js index 6ed34713a1e..f358e81dd66 100644 --- a/src/lib/polygon.js +++ b/src/lib/polygon.js @@ -166,7 +166,11 @@ polygon.tester = function tester(ptsIn) { * Test multiple polygons */ polygon.multitester = function multitester(list) { - var testers = [], xmin, xmax, ymin, ymax; + var testers = [], + xmin = list[0][0][0], + xmax = xmin, + ymin = list[0][0][1], + ymax = ymin; for(var i = 0; i < list.length; i++) { var tester = polygon.tester(list[i]); diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 41200a1ad54..7f338ba9894 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -171,10 +171,10 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { else if(isSelectOrLasso(dragModeNow)) { dragOptions.xaxes = xa; dragOptions.yaxes = ya; - if(!e.shiftKey) { + if(!e.shiftKey || !dragOptions.polygons) { // list of all prev polygons and their merge dragOptions.polygons = []; - dragOptions.mergedPolygons = null; + dragOptions.mergedPolygons = []; } prepSelect(e, startX, startY, dragOptions, dragModeNow); } diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 10133ea3cfb..2671379f329 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -40,7 +40,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { xAxisIds = dragOptions.xaxes.map(getAxId), yAxisIds = dragOptions.yaxes.map(getAxId), allAxes = dragOptions.xaxes.concat(dragOptions.yaxes), - currentPoly, mergedTester, mergedPolyPts, poly; + currentPoly, mergedPolyPts, poly, currentPts; if(mode === 'lasso') { currentPoly = filteredPolygon([[x0, y0]], constants.BENDPX); @@ -120,57 +120,55 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { if(mode === 'select') { if(dy < Math.min(dx * 0.6, MINSELECT)) { // horizontal motion: make a vertical box - poly = polygonTester([[x0, 0], [x0, ph], [x1, ph], [x1, 0]]); + currentPts = [[x0, 0], [x0, ph], [x1, ph], [x1, 0]]; // extras to guide users in keeping a straight selection - corners.attr('d', 'M' + poly.xmin + ',' + (y0 - MINSELECT) + + corners.attr('d', 'M' + Math.min(x0, x1) + ',' + (y0 - MINSELECT) + 'h-4v' + (2 * MINSELECT) + 'h4Z' + - 'M' + (poly.xmax - 1) + ',' + (y0 - MINSELECT) + + 'M' + (Math.max(x0, x1) - 1) + ',' + (y0 - MINSELECT) + 'h4v' + (2 * MINSELECT) + 'h-4Z'); } else if(dx < Math.min(dy * 0.6, MINSELECT)) { // vertical motion: make a horizontal box - poly = polygonTester([[0, y0], [0, y1], [pw, y1], [pw, y0]]); - corners.attr('d', 'M' + (x0 - MINSELECT) + ',' + poly.ymin + + currentPts = [[0, y0], [0, y1], [pw, y1], [pw, y0]]; + corners.attr('d', 'M' + (x0 - MINSELECT) + ',' + Math.min(y0, y1) + 'v-4h' + (2 * MINSELECT) + 'v4Z' + - 'M' + (x0 - MINSELECT) + ',' + (poly.ymax - 1) + + 'M' + (x0 - MINSELECT) + ',' + (Math.max(y0, y1) - 1) + 'v4h' + (2 * MINSELECT) + 'v-4Z'); } else { // diagonal motion - poly = polygonTester([[x0, y0], [x0, y1], [x1, y1], [x1, y0]]); + currentPts = [[x0, y0], [x0, y1], [x1, y1], [x1, y0]]; corners.attr('d', 'M0,0Z'); } - outlines.attr('d', 'M' + poly.xmin + ',' + poly.ymin + - 'H' + (poly.xmax - 1) + 'V' + (poly.ymax - 1) + - 'H' + poly.xmin + 'Z'); } else if(mode === 'lasso') { currentPoly.addPt([x1, y1]); + currentPts = currentPoly.filtered; + } - if(dragOptions.polygons.length) { - mergedPolyPts = polybool(dragOptions.mergedPolygons, [currentPoly.filtered], 'or'); - - var mergedPaths = []; - for(i = 0; i < mergedPolyPts.length; i++) { - var ppts = mergedPolyPts[i]; - mergedPaths.push(ppts.join('L') + 'L' + ppts[0]); - } - mergedTester = multipolygonTester(dragOptions.polygons.concat([currentPoly.filtered])); - outlines.attr('d', 'M' + mergedPaths.join('M') + 'Z'); - } - else { - mergedPolyPts = [currentPoly.filtered]; - mergedTester = polygonTester(currentPoly.filtered); - outlines.attr('d', 'M' + mergedTester.pts.join('L') + 'Z'); + // create outline & tester + if(dragOptions.polygons.length) { + mergedPolyPts = polybool(dragOptions.mergedPolygons, [currentPts], 'or'); + poly = multipolygonTester(dragOptions.polygons.concat([currentPts])); + var mergedPaths = []; + for(i = 0; i < mergedPolyPts.length; i++) { + var ppts = mergedPolyPts[i]; + mergedPaths.push(ppts.join('L') + 'L' + ppts[0]); } + outlines.attr('d', 'M' + mergedPaths.join('M') + 'Z'); + } + else { + mergedPolyPts = [currentPts]; + poly = polygonTester(currentPts); + outlines.attr('d', 'M' + mergedPolyPts[0].join('L') + 'Z'); } selection = []; for(i = 0; i < searchTraces.length; i++) { searchInfo = searchTraces[i]; [].push.apply(selection, fillSelectionItem( - searchInfo.selectPoints(searchInfo, mergedTester), searchInfo + searchInfo.selectPoints(searchInfo, poly), searchInfo )); } @@ -184,8 +182,8 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { ax = allAxes[i]; axLetter = ax._id.charAt(0); ranges[ax._id] = [ - ax.p2d(mergedTester[axLetter + 'min']), - ax.p2d(mergedTester[axLetter + 'max'])].sort(ascending); + ax.p2d(poly[axLetter + 'min']), + ax.p2d(poly[axLetter + 'max'])].sort(ascending); } } else { @@ -215,8 +213,9 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { dragOptions.gd.emit('plotly_selected', eventData); } + // save last polygons + dragOptions.polygons.push(currentPts); dragOptions.mergedPolygons = mergedPolyPts; - dragOptions.polygons.push(currentPoly.filtered); }; }; From 05d1fd2c20406ad647a563c2eeabee0066459722 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 4 Jul 2017 12:11:30 -0400 Subject: [PATCH 07/11] Add clear names --- src/plots/cartesian/select.js | 40 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 2671379f329..6879ffa541e 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -40,10 +40,10 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { xAxisIds = dragOptions.xaxes.map(getAxId), yAxisIds = dragOptions.yaxes.map(getAxId), allAxes = dragOptions.xaxes.concat(dragOptions.yaxes), - currentPoly, mergedPolyPts, poly, currentPts; + filterPoly, testPoly, mergedPolygons, currentPolygon; if(mode === 'lasso') { - currentPoly = filteredPolygon([[x0, y0]], constants.BENDPX); + filterPoly = filteredPolygon([[x0, y0]], constants.BENDPX); } var outlines = plot.selectAll('path.select-outline').data([1, 2]); @@ -120,7 +120,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { if(mode === 'select') { if(dy < Math.min(dx * 0.6, MINSELECT)) { // horizontal motion: make a vertical box - currentPts = [[x0, 0], [x0, ph], [x1, ph], [x1, 0]]; + currentPolygon = [[x0, 0], [x0, ph], [x1, ph], [x1, 0]]; // extras to guide users in keeping a straight selection corners.attr('d', 'M' + Math.min(x0, x1) + ',' + (y0 - MINSELECT) + 'h-4v' + (2 * MINSELECT) + 'h4Z' + @@ -130,7 +130,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { } else if(dx < Math.min(dy * 0.6, MINSELECT)) { // vertical motion: make a horizontal box - currentPts = [[0, y0], [0, y1], [pw, y1], [pw, y0]]; + currentPolygon = [[0, y0], [0, y1], [pw, y1], [pw, y0]]; corners.attr('d', 'M' + (x0 - MINSELECT) + ',' + Math.min(y0, y1) + 'v-4h' + (2 * MINSELECT) + 'v4Z' + 'M' + (x0 - MINSELECT) + ',' + (Math.max(y0, y1) - 1) + @@ -138,37 +138,37 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { } else { // diagonal motion - currentPts = [[x0, y0], [x0, y1], [x1, y1], [x1, y0]]; + currentPolygon = [[x0, y0], [x0, y1], [x1, y1], [x1, y0]]; corners.attr('d', 'M0,0Z'); } } else if(mode === 'lasso') { - currentPoly.addPt([x1, y1]); - currentPts = currentPoly.filtered; + filterPoly.addPt([x1, y1]); + currentPolygon = filterPoly.filtered; } // create outline & tester if(dragOptions.polygons.length) { - mergedPolyPts = polybool(dragOptions.mergedPolygons, [currentPts], 'or'); - poly = multipolygonTester(dragOptions.polygons.concat([currentPts])); + mergedPolygons = polybool(dragOptions.mergedPolygons, [currentPolygon], 'or'); + testPoly = multipolygonTester(dragOptions.polygons.concat([currentPolygon])); var mergedPaths = []; - for(i = 0; i < mergedPolyPts.length; i++) { - var ppts = mergedPolyPts[i]; + for(i = 0; i < mergedPolygons.length; i++) { + var ppts = mergedPolygons[i]; mergedPaths.push(ppts.join('L') + 'L' + ppts[0]); } outlines.attr('d', 'M' + mergedPaths.join('M') + 'Z'); } else { - mergedPolyPts = [currentPts]; - poly = polygonTester(currentPts); - outlines.attr('d', 'M' + mergedPolyPts[0].join('L') + 'Z'); + mergedPolygons = [currentPolygon]; + testPoly = polygonTester(currentPolygon); + outlines.attr('d', 'M' + mergedPolygons[0].join('L') + 'Z'); } selection = []; for(i = 0; i < searchTraces.length; i++) { searchInfo = searchTraces[i]; [].push.apply(selection, fillSelectionItem( - searchInfo.selectPoints(searchInfo, poly), searchInfo + searchInfo.selectPoints(searchInfo, testPoly), searchInfo )); } @@ -182,8 +182,8 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { ax = allAxes[i]; axLetter = ax._id.charAt(0); ranges[ax._id] = [ - ax.p2d(poly[axLetter + 'min']), - ax.p2d(poly[axLetter + 'max'])].sort(ascending); + ax.p2d(testPoly[axLetter + 'min']), + ax.p2d(testPoly[axLetter + 'max'])].sort(ascending); } } else { @@ -191,7 +191,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { for(i = 0; i < allAxes.length; i++) { ax = allAxes[i]; - dataPts[ax._id] = currentPoly.filtered.map(axValue(ax)); + dataPts[ax._id] = filterPoly.filtered.map(axValue(ax)); } } dragOptions.gd.emit('plotly_selecting', eventData); @@ -214,8 +214,8 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { } // save last polygons - dragOptions.polygons.push(currentPts); - dragOptions.mergedPolygons = mergedPolyPts; + dragOptions.polygons.push(currentPolygon); + dragOptions.mergedPolygons = mergedPolygons; }; }; From f32a48fbaac53696a4950095c06d8d46c82190dd Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 4 Jul 2017 12:52:53 -0400 Subject: [PATCH 08/11] Make intermode selection --- src/plots/cartesian/dragbox.js | 13 ++++++++++--- src/plots/cartesian/select.js | 4 +++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 7f338ba9894..0ada45f4da8 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -171,10 +171,17 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { else if(isSelectOrLasso(dragModeNow)) { dragOptions.xaxes = xa; dragOptions.yaxes = ya; - if(!e.shiftKey || !dragOptions.polygons) { - // list of all prev polygons and their merge + // take over selection polygons from prev mode, if any + if(e.shiftKey && dragOptions.plotinfo.selectionPolygons && !dragOptions.polygons) { + dragOptions.polygons = dragOptions.plotinfo.selectionPolygons; + dragOptions.mergedPolygons = dragOptions.plotinfo.selectionMergedPolygons; + } + // create new polygons, if shift mode + else if(!e.shiftKey || (e.shiftKey && !dragOptions.plotinfo.selectionPolygons)) { + dragOptions.plotinfo.selectionPolygons = dragOptions.polygons = []; - dragOptions.mergedPolygons = []; + dragOptions.mergedPolygons = + dragOptions.plotinfo.selectionMergedPolygons = []; } prepSelect(e, startX, startY, dragOptions, dragModeNow); } diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 6879ffa541e..69b9011885f 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -215,7 +215,9 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { // save last polygons dragOptions.polygons.push(currentPolygon); - dragOptions.mergedPolygons = mergedPolygons; + + // we have to keep reference to arrays, therefore just replace items + dragOptions.mergedPolygons.splice.apply(dragOptions.mergedPolygons, [0, dragOptions.mergedPolygons].concat(mergedPolygons)); }; }; From 66e480ee28fe3656a83b4f44f33e90db941c180b Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 4 Jul 2017 14:21:34 -0400 Subject: [PATCH 09/11] Fix merged polygons tracking --- src/plots/cartesian/select.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 69b9011885f..1537655928e 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -25,7 +25,9 @@ var MINSELECT = constants.MINSELECT; function getAxId(ax) { return ax._id; } -module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { +module.exports = prepSelect; + +function prepSelect(e, startX, startY, dragOptions, mode) { var plot = dragOptions.gd._fullLayout._zoomlayer, dragBBox = dragOptions.element.getBoundingClientRect(), xs = dragOptions.plotinfo.xaxis._offset, @@ -151,19 +153,21 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { if(dragOptions.polygons.length) { mergedPolygons = polybool(dragOptions.mergedPolygons, [currentPolygon], 'or'); testPoly = multipolygonTester(dragOptions.polygons.concat([currentPolygon])); - var mergedPaths = []; - for(i = 0; i < mergedPolygons.length; i++) { - var ppts = mergedPolygons[i]; - mergedPaths.push(ppts.join('L') + 'L' + ppts[0]); - } - outlines.attr('d', 'M' + mergedPaths.join('M') + 'Z'); } else { mergedPolygons = [currentPolygon]; testPoly = polygonTester(currentPolygon); - outlines.attr('d', 'M' + mergedPolygons[0].join('L') + 'Z'); } + // draw selection + var paths = []; + for(var i = 0; i < mergedPolygons.length; i++) { + var ppts = mergedPolygons[i]; + paths.push(ppts.join('L') + 'L' + ppts[0]); + } + outlines.attr('d', 'M' + paths.join('M') + 'Z'); + + // select points selection = []; for(i = 0; i < searchTraces.length; i++) { searchInfo = searchTraces[i]; @@ -217,7 +221,7 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) { dragOptions.polygons.push(currentPolygon); // we have to keep reference to arrays, therefore just replace items - dragOptions.mergedPolygons.splice.apply(dragOptions.mergedPolygons, [0, dragOptions.mergedPolygons].concat(mergedPolygons)); + dragOptions.mergedPolygons.splice.apply(dragOptions.mergedPolygons, [0, dragOptions.mergedPolygons.length].concat(mergedPolygons)); }; }; From aabb27fea908b3dcc4dbc9d4d79c31aa09e2d924 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 4 Jul 2017 14:45:11 -0400 Subject: [PATCH 10/11] Show selection before interaction --- src/plots/cartesian/dragbox.js | 24 ++++++++++++++++++++++++ src/plots/cartesian/select.js | 12 ++++++------ 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 0ada45f4da8..7645699cdf1 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -190,6 +190,11 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { dragElement.init(dragOptions); + // FIXME: this hack highlights selection once we enter select/lasso mode + if(isSelectOrLasso(gd._fullLayout.dragmode) && dragOptions.plotinfo.selectionPolygons) { + showSelect(zoomlayer, dragOptions); + } + var x0, y0, box, @@ -914,6 +919,25 @@ function clearSelect(zoomlayer) { zoomlayer.selectAll('.select-outline').remove(); } +function showSelect(zoomlayer, dragOptions) { + var outlines = zoomlayer.selectAll('path.select-outline').data([1, 2]), + xs = dragOptions.plotinfo.xaxis._offset, + ys = dragOptions.plotinfo.yaxis._offset, + paths = [], + polygons = dragOptions.plotinfo.selectionMergedPolygons; + + for(var i = 0; i < polygons.length; i++) { + var ppts = polygons[i]; + paths.push(ppts.join('L') + 'L' + ppts[0]); + } + + outlines.enter() + .append('path') + .attr('class', function(d) { return 'select-outline select-outline-' + d; }) + .attr('transform', 'translate(' + xs + ', ' + ys + ')') + .attr('d', 'M' + paths.join('M') + 'Z'); +} + function updateZoombox(zb, corners, box, path0, dimmed, lum) { zb.attr('d', path0 + 'M' + (box.l) + ',' + (box.t) + 'v' + (box.h) + diff --git a/src/plots/cartesian/select.js b/src/plots/cartesian/select.js index 1537655928e..c39ff9e05f9 100644 --- a/src/plots/cartesian/select.js +++ b/src/plots/cartesian/select.js @@ -28,7 +28,7 @@ function getAxId(ax) { return ax._id; } module.exports = prepSelect; function prepSelect(e, startX, startY, dragOptions, mode) { - var plot = dragOptions.gd._fullLayout._zoomlayer, + var zoomlayer = dragOptions.gd._fullLayout._zoomlayer, dragBBox = dragOptions.element.getBoundingClientRect(), xs = dragOptions.plotinfo.xaxis._offset, ys = dragOptions.plotinfo.yaxis._offset, @@ -48,7 +48,7 @@ function prepSelect(e, startX, startY, dragOptions, mode) { filterPoly = filteredPolygon([[x0, y0]], constants.BENDPX); } - var outlines = plot.selectAll('path.select-outline').data([1, 2]); + var outlines = zoomlayer.selectAll('path.select-outline').data([1, 2]); outlines.enter() .append('path') @@ -56,7 +56,7 @@ function prepSelect(e, startX, startY, dragOptions, mode) { .attr('transform', 'translate(' + xs + ', ' + ys + ')') .attr('d', path0 + 'Z'); - var corners = plot.append('path') + var corners = zoomlayer.append('path') .attr('class', 'zoombox-corners') .style({ fill: color.background, @@ -112,7 +112,7 @@ function prepSelect(e, startX, startY, dragOptions, mode) { function ascending(a, b) { return a - b; } dragOptions.moveFn = function(dx0, dy0) { - var ax; + var ax, i; x1 = Math.max(0, Math.min(pw, dx0 + x0)); y1 = Math.max(0, Math.min(ph, dy0 + y0)); @@ -161,7 +161,7 @@ function prepSelect(e, startX, startY, dragOptions, mode) { // draw selection var paths = []; - for(var i = 0; i < mergedPolygons.length; i++) { + for(i = 0; i < mergedPolygons.length; i++) { var ppts = mergedPolygons[i]; paths.push(ppts.join('L') + 'L' + ppts[0]); } @@ -223,7 +223,7 @@ function prepSelect(e, startX, startY, dragOptions, mode) { // we have to keep reference to arrays, therefore just replace items dragOptions.mergedPolygons.splice.apply(dragOptions.mergedPolygons, [0, dragOptions.mergedPolygons.length].concat(mergedPolygons)); }; -}; +} function fillSelectionItem(selection, searchInfo) { if(Array.isArray(selection)) { From f8a03b3cdce94aa62b38a4756d512c012ddf9046 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Tue, 4 Jul 2017 16:15:46 -0400 Subject: [PATCH 11/11] Make selection an object --- src/plots/cartesian/dragbox.js | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/plots/cartesian/dragbox.js b/src/plots/cartesian/dragbox.js index 7645699cdf1..ab27daacbb0 100644 --- a/src/plots/cartesian/dragbox.js +++ b/src/plots/cartesian/dragbox.js @@ -172,16 +172,15 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { dragOptions.xaxes = xa; dragOptions.yaxes = ya; // take over selection polygons from prev mode, if any - if(e.shiftKey && dragOptions.plotinfo.selectionPolygons && !dragOptions.polygons) { - dragOptions.polygons = dragOptions.plotinfo.selectionPolygons; - dragOptions.mergedPolygons = dragOptions.plotinfo.selectionMergedPolygons; + if(e.shiftKey && plotinfo.selection.polygons && !dragOptions.polygons) { + dragOptions.polygons = plotinfo.selection.polygons; + dragOptions.mergedPolygons = plotinfo.selection.mergedPolygons; } // create new polygons, if shift mode - else if(!e.shiftKey || (e.shiftKey && !dragOptions.plotinfo.selectionPolygons)) { - dragOptions.plotinfo.selectionPolygons = - dragOptions.polygons = []; - dragOptions.mergedPolygons = - dragOptions.plotinfo.selectionMergedPolygons = []; + else if(!e.shiftKey || (e.shiftKey && !plotinfo.selection.polygons)) { + plotinfo.selection = {}; + plotinfo.selection.polygons = dragOptions.polygons = []; + dragOptions.mergedPolygons = plotinfo.selection.mergedPolygons = []; } prepSelect(e, startX, startY, dragOptions, dragModeNow); } @@ -191,7 +190,7 @@ module.exports = function dragBox(gd, plotinfo, x, y, w, h, ns, ew) { dragElement.init(dragOptions); // FIXME: this hack highlights selection once we enter select/lasso mode - if(isSelectOrLasso(gd._fullLayout.dragmode) && dragOptions.plotinfo.selectionPolygons) { + if(isSelectOrLasso(gd._fullLayout.dragmode) && plotinfo.selection) { showSelect(zoomlayer, dragOptions); } @@ -921,10 +920,14 @@ function clearSelect(zoomlayer) { function showSelect(zoomlayer, dragOptions) { var outlines = zoomlayer.selectAll('path.select-outline').data([1, 2]), - xs = dragOptions.plotinfo.xaxis._offset, - ys = dragOptions.plotinfo.yaxis._offset, - paths = [], - polygons = dragOptions.plotinfo.selectionMergedPolygons; + plotinfo = dragOptions.plotinfo, + xaxis = plotinfo.xaxis, + yaxis = plotinfo.yaxis, + selection = plotinfo.selection, + polygons = selection.mergedPolygons, + xs = xaxis._offset, + ys = yaxis._offset, + paths = []; for(var i = 0; i < polygons.length; i++) { var ppts = polygons[i];