8000 Lasso & rectangular selections by alexcjohnson · Pull Request #154 · plotly/plotly.js · GitHub
[go: up one dir, main page]

Skip to content

Lasso & rectangular selections #154

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 35 commits into from
Jan 6, 2016
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d6f983e
agignore dist
alexcjohnson Dec 27, 2015
e536a91
point in polygon routine, with tests
alexcjohnson Dec 27, 2015
5cad4ff
lasso and selectbox skeleton
alexcjohnson Dec 28, 2015
ca60e63
propagate dragbox cursor to coverSlip while dragging
alexcjohnson Dec 29, 2015
76926d7
fix modebar logic and tests for select dragmodes
alexcjohnson Dec 29, 2015
78d2867
change polygon and its tests to multi-exports form
alexcjohnson Dec 29, 2015
f0e6cfb
polygon filtering algorithm
alexcjohnson Dec 29, 2015
c0a94f7
polygon filtering test
alexcjohnson Dec 29, 2015
12b43c6
special case of polygon for rectangles
alexcjohnson Dec 30, 2015
07e5cef
selection on scatter points
alexcjohnson Dec 31, 2015
b1a1c24
clear hover when drag starts
alexcjohnson Dec 31, 2015
1958475
didn't end up using a separate lasso handler
alexcjohnson Dec 31, 2015
3c40c41
crosshair it is
alexcjohnson Jan 4, 2016
188a0db
inline rectFirstEdgeTest
alexcjohnson Jan 4, 2016
8d0afb5
Merge branch 'master' into lasso
alexcjohnson Jan 4, 2016
1c5f325
lasso like it's 2016 baby!
alexcjohnson Jan 4, 2016
d0203c8
scatter.selectPoints uses scatter.hasMarkers
alexcjohnson Jan 4, 2016
5be72de
:cow2:
alexcjohnson Jan 4, 2016
057e4ac
prep for adding selection bounds to event data
alexcjohnson Jan 5, 2016
556cb83
split out scatter.selectPoints to a new file
alexcjohnson Jan 5, 2016
7b07a25
support selecting scatter text
alexcjohnson Jan 5, 2016
2a970fc
fx constants file
alexcjohnson Jan 5, 2016
d7abd18
more fx constants
alexcjohnson Jan 6, 2016
7addcec
BENDPX into constants
alexcjohnson Jan 6, 2016
d02d612
horizontal and vertical select boxes
alexcjohnson Jan 6, 2016
698376e
off-by-one error in select outline
alexcjohnson Jan 6, 2016
89c1655
clear selection on zoom/pan
alexcjohnson Jan 6, 2016
d8ed7a7
selection range event data
alexcjohnson Jan 6, 2016
7d897a4
show select icons only when they apply
alexcjohnson Jan 6, 2016
96e01b6
update modebar tests for new select icon logic
alexcjohnson Jan 6, 2016
bc04a15
desciption attribute value delimiters
alexcjohnson Jan 6, 2016
710791d
fix nonlinear axes in select
alexcjohnson Jan 6, 2016
342f991
:cow2:
alexcjohnson Jan 6, 2016
b5c090f
MINSELECT bigger than MINDRAG
alexcjohnson Jan 6, 2016
1d7745f
add lasso and selectbox icon
etpinard Jan 6, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
polygon filtering algorithm
  • Loading branch information
alexcjohnson committed Dec 29, 2015
commit f0e6cfb68e6c4f1122a6998ad9146200f657ad0a
77 changes: 77 additions & 0 deletions src/lib/polygon.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@


'use strict';
var dot = require('./matrix').dot;

/**
* Turn an array of [x, y] pairs into a polygon object
Expand Down Expand Up @@ -118,4 +119,80 @@ polygon.tester = function tester(ptsIn) {
};
};

/**
* Test if a segment of a points array is bent or straight
*
* @param pts Array of [x, y] pairs
* @param start the index of the proposed start of the straight section
* @param end the index of the proposed end point
* @param tolerance the max distance off the line connecting start and end
* before the line counts as bent
* @returns boolean: true means this segment is bent, false means straight
*/
var isBent = polygon.isSegmentBent = function isBent(pts, start, end, tolerance) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Used to simplify the lasso polygon. This is pretty similar to the scatter line decimation algorithm, so not super 🌴 but line decimation has a few other quirks that made it seem annoying to combine.

Copy link
Contributor

Choose a reason for hiding this comment

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

polygon.js is ~150 lines of code. Nothing to worry here.

var startPt = pts[start],
segment = [pts[end][0] - startPt[0], pts[end][1] - startPt[1]],
segmentSquared = dot(segment, segment),
segmentLen = Math.sqrt(segmentSquared),
unitPerp = [-segment[1] / segmentLen, segment[0] / segmentLen],
i,
part,
partParallel;

for(i = start + 1; i < end; i++) {
part = [pts[i][0] - startPt[0], pts[i][1] - startPt[1]];
partParallel = dot(part, segment);

if(partParallel < 0 || partParallel > segmentSquared ||
Math.abs(dot(part, unitPerp)) > tolerance) return true;
}
return false;
};

/**
* Make a filtering polygon, to minimize the number of segments
*
* @param pts Array of [x, y] pairs (must start with at least 1 pair)
* @param tolerance the maximum deviation from straight allowed for
* removing points to simplify the polygon
*
* @returns Object {addPt, raw, filtered}
* addPt is a function(pt: [x, y] pair) to add a raw point and
* continue filtering
* raw is all the input points
* filtered is the resulting filtered Array of [x, y] pairs
*/
polygon.filter = function filter(pts, tolerance) {
var ptsFiltered = [pts[0]],
doneRawIndex = 0,
doneFilteredIndex = 0;

function addPt(pt) {
pts.push(pt);
var prevFilterLen = ptsFiltered.length,
iLast = doneRawIndex;
ptsFiltered.splice(doneFilteredIndex + 1);

for(var i = iLast + 1; i < pts.length; i++) {
if(i === pts.length - 1 || isBent(pts, iLast, i + 1, tolerance)) {
ptsFiltered.push(pts[i]);
if(ptsFiltered.length < prevFilterLen - 2) {
doneRawIndex = i;
doneFilteredIndex = ptsFiltered.length - 1;
}
iLast = i;
}
}
}

if(pts.length > 1) {
var lastPt = pts.pop();
addPt(lastPt);
}

return {
addPt: addPt,
raw: pts,
filtered: ptsFiltered
};
};
22 changes: 12 additions & 10 deletions src/plots/cartesian/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@


'use strict';
var polygon = require('../../lib/polygon');
Copy link
Contributor

Choose a reason for hiding this comment

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

@alexcjohnson a couple (still un-documented) 🐄s

  • 'use strict'; should have a blank line below it
  • top-level require statements should be in groups separated by blank line in order:
    1. node builtin modules
    2. npm public modules
    3. project files
    4. files within the same directory
    5. other variable definitions

i.e. in increasing locality.

So, the select.js require blocks would be:

'use strict';

var polygon = require('../../lib/polygon');

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

var filteredPolygon = polygon.filter;
var polygonTester = polygon.tester;
var BENDPX = 1.5; // max pixels off straight before a line counts as bent

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

roger doger.

var filteredPolygon = polygon.filter;
var BENDPX = 1.5; // max pixels off straight before a line counts as bent

module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice.

How hard would it be the split up the dragBox definition out of graph_interact.js and into a separate file?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Probably pretty easy, but I'll leave that as an exercise to the reader 😄

Copy link
Contributor

Choose a reason for hiding this comment

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

but I'll leave that as an exercise to the reader

Haha. Hopefully this comes with bonus points ;)

console.log('select start', e, startX, startY, dragOptions, mode);
var plot = dragOptions.plotinfo.plot,
dragBBox = dragOptions.element.getBoundingClientRect(),
x0 = startX - dragBBox.left,
Expand All @@ -19,35 +21,34 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
y1 = y0,
path0 = 'M' + x0 + ',' + y0,
pw = dragOptions.xaxes[0]._length,
ph = dragOptions.yaxes[0]._length,
pts = [[x0, y0]],
outlines = plot.selectAll('path.select-outline').data([1,2]);
ph = dragOptions.yaxes[0]._length;
if(mode === 'lasso') {
var pts = filteredPolygon([[x0, y0]], BENDPX);
}

// TODO initial dimming of selectable points
var outlines = plot.selectAll('path.select-outline').data([1,2]);

outlines.enter()
.append('path')
.attr('class', function(d) { return 'select-outline select-outline-' + d; })
.attr('d', path0 + 'Z');

dragOptions.moveFn = function(dx0, dy0) {
console.log('select move', dx0, dy0);
x1 = Math.max(0, Math.min(pw, dx0 + x0));
y1 = Math.max(0, Math.min(ph, dy0 + y0));

if(mode === 'select') {
outlines.attr('d', path0 + 'H' + x1 + 'V' + y1 + 'H' + x0 + 'Z');
}
else {
pts.push([x1, y1]); // TODO: filter this down to something reasonable
outlines.attr('d', 'M' + pts.join('L'));
else if(mode === 'lasso') {
pts.addPt([x1, y1]);
outlines.attr('d', 'M' + pts.filtered.join('L'));
}

// TODO - actual selection and dimming!
};

dragOptions.doneFn = function(dragged, numclicks) {
console 52B4 .log('select done', dragged, numclicks);
if(!dragged && numclicks === 2) dragOptions.doubleclick();
else {
// TODO - select event
Expand All @@ -56,3 +57,4 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
// TODO - remove dimming
};
};

0