Skip to content

Commit f0e6cfb

Browse files
committed
polygon filtering algorithm
1 parent 78d2867 commit f0e6cfb

File tree

2 files changed

+89
-10
lines changed

2 files changed

+89
-10
lines changed

src/lib/polygon.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99

1010
'use strict';
11+
var dot = require('./matrix').dot;
1112

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

122+
/**
123+
* Test if a segment of a points array is bent or straight
124+
*
125+
* @param pts Array of [x, y] pairs
126+
* @param start the index of the proposed start of the straight section
127+
* @param end the index of the proposed end point
128+
* @param tolerance the max distance off the line connecting start and end
129+
* before the line counts as bent
130+
* @returns boolean: true means this segment is bent, false means straight
131+
*/
132+
var isBent = polygon.isSegmentBent = function isBent(pts, start, end, tolerance) {
133+
var startPt = pts[start],
134+
segment = [pts[end][0] - startPt[0], pts[end][1] - startPt[1]],
135+
segmentSquared = dot(segment, segment),
136+
segmentLen = Math.sqrt(segmentSquared),
137+
unitPerp = [-segment[1] / segmentLen, segment[0] / segmentLen],
138+
i,
139+
part,
140+
partParallel;
141+
142+
for(i = start + 1; i < end; i++) {
143+
part = [pts[i][0] - startPt[0], pts[i][1] - startPt[1]];
144+
partParallel = dot(part, segment);
145+
146+
if(partParallel < 0 || partParallel > segmentSquared ||
147+
Math.abs(dot(part, unitPerp)) > tolerance) return true;
148+
}
149+
return false;
150+
};
151+
152+
/**
153+
* Make a filtering polygon, to minimize the number of segments
154+
*
155+
* @param pts Array of [x, y] pairs (must start with at least 1 pair)
156+
* @param tolerance the maximum deviation from straight allowed for
157+
* removing points to simplify the polygon
158+
*
159+
* @returns Object {addPt, raw, filtered}
160+
* addPt is a function(pt: [x, y] pair) to add a raw point and
161+
* continue filtering
162+
* raw is all the input points
163+
* filtered is the resulting filtered Array of [x, y] pairs
164+
*/
165+
polygon.filter = function filter(pts, tolerance) {
166+
var ptsFiltered = [pts[0]],
167+
doneRawIndex = 0,
168+
doneFilteredIndex = 0;
169+
170+
function addPt(pt) {
171+
pts.push(pt);
172+
var prevFilterLen = ptsFiltered.length,
173+
iLast = doneRawIndex;
174+
ptsFiltered.splice(doneFilteredIndex + 1);
175+
176+
for(var i = iLast + 1; i < pts.length; i++) {
177+
if(i === pts.length - 1 || isBent(pts, iLast, i + 1, tolerance)) {
178+
ptsFiltered.push(pts[i]);
179+
if(ptsFiltered.length < prevFilterLen - 2) {
180+
doneRawIndex = i;
181+
doneFilteredIndex = ptsFiltered.length - 1;
182+
}
183+
iLast = i;
184+
}
185+
}
186+
}
121187

188+
if(pts.length > 1) {
189+
var lastPt = pts.pop();
190+
addPt(lastPt);
191+
}
192+
193+
return {
194+
addPt: addPt,
195+
raw: pts,
196+
filtered: ptsFiltered
197+
};
198+
};

src/plots/cartesian/select.js

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@
88

99

1010
'use strict';
11+
var polygon = require('../../lib/polygon');
12+
var filteredPolygon = polygon.filter;
13+
var BENDPX = 1.5; // max pixels off straight before a line counts as bent
1114

1215
module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
13-
console.log('select start', e, startX, startY, dragOptions, mode);
1416
var plot = dragOptions.plotinfo.plot,
1517
dragBBox = dragOptions.element.getBoundingClientRect(),
1618
x0 = startX - dragBBox.left,
@@ -19,35 +21,34 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
1921
y1 = y0,
2022
path0 = 'M' + x0 + ',' + y0,
2123
pw = dragOptions.xaxes[0]._length,
22-
ph = dragOptions.yaxes[0]._length,
23-
pts = [[x0, y0]],
24-
outlines = plot.selectAll('path.select-outline').data([1,2]);
24+
ph = dragOptions.yaxes[0]._length;
25+
if(mode === 'lasso') {
26+
var pts = filteredPolygon([[x0, y0]], BENDPX);
27+
}
2528

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

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

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

3840
if(mode === 'select') {
3941
outlines.attr('d', path0 + 'H' + x1 + 'V' + y1 + 'H' + x0 + 'Z');
4042
}
41-
else {
42-
pts.push([x1, y1]); // TODO: filter this down to something reasonable
43-
outlines.attr('d', 'M' + pts.join('L'));
43+
else if(mode === 'lasso') {
44+
pts.addPt([x1, y1]);
45+
outlines.attr('d', 'M' + pts.filtered.join('L'));
4446
}
4547

4648
// TODO - actual selection and dimming!
4749
};
4850

4951
dragOptions.doneFn = function(dragged, numclicks) {
50-
console.log('select done', dragged, numclicks);
5152
if(!dragged && numclicks === 2) dragOptions.doubleclick();
5253
else {
5354
// TODO - select event
@@ -56,3 +57,4 @@ module.exports = function prepSelect(e, startX, startY, dragOptions, mode) {
5657
// TODO - remove dimming
5758
};
5859
};
60+

0 commit comments

Comments
 (0)