Skip to content

Commit 95c88e6

Browse files
authored
Merge pull request #2705 from plotly/decimation-fix
fix line decimation for segments crossing the viewport
2 parents b88abdb + 99a550a commit 95c88e6

File tree

2 files changed

+82
-7
lines changed

2 files changed

+82
-7
lines changed

src/traces/scatter/line_points.js

+37-5
Original file line numberDiff line numberDiff line change
@@ -61,17 +61,44 @@ module.exports = function linePoints(d, opts) {
6161
// turn one calcdata point into pixel coordinates
6262
function getPt(index) {
6363
var di = d[index];
64+
if(!di) return false;
6465
var x = xa.c2p(di.x);
6566
var y = ya.c2p(di.y);
6667
if(x === BADNUM || y === BADNUM) return di.intoCenter || false;
6768
return [x, y];
6869
}
6970

71+
function crossesViewport(xFrac0, yFrac0, xFrac1, yFrac1) {
72+
var dx = xFrac1 - xFrac0;
73+
var dy = yFrac1 - yFrac0;
74+
var dx0 = 0.5 - xFrac0;
75+
var dy0 = 0.5 - yFrac0;
76+
var norm2 = dx * dx + dy * dy;
77+
var dot = dx * dx0 + dy * dy0;
78+
if(dot > 0 && dot < norm2) {
79+
var cross = dx0 * dy - dy0 * dx;
80+
if(cross * cross < norm2) return true;
81+
}
82+
}
83+
84+
var latestXFrac, latestYFrac;
7085
// if we're off-screen, increase tolerance over baseTolerance
71-
function getTolerance(pt) {
86+
function getTolerance(pt, nextPt) {
7287
var xFrac = pt[0] / xa._length;
7388
var yFrac = pt[1] / ya._length;
74-
return (1 + constants.toleranceGrowth * Math.max(0, -xFrac, xFrac - 1, -yFrac, yFrac - 1)) * baseTolerance;
89+
var offScreenFraction = Math.max(0, -xFrac, xFrac - 1, -yFrac, yFrac - 1);
90+
if(offScreenFraction && (latestXFrac !== undefined) &&
91+
crossesViewport(xFrac, yFrac, latestXFrac, latestYFrac)
92+
) {
93+
offScreenFraction = 0;
94+
}
95+
if(offScreenFraction && nextPt &&
96+
crossesViewport(xFrac, yFrac, nextPt[0] / xa._length, nextPt[1] / ya._length)
97+
) {
98+
offScreenFraction = 0;
99+
}
100+
101+
return (1 + constants.toleranceGrowth * offScreenFraction) * baseTolerance;
75102
}
76103

77104
function ptDist(pt1, pt2) {
@@ -239,6 +266,8 @@ module.exports = function linePoints(d, opts) {
239266
}
240267

241268
function addPt(pt) {
269+
latestXFrac = pt[0] / xa._length;
270+
latestYFrac = pt[1] / ya._length;
242271
// Are we more than maxScreensAway off-screen any direction?
243272
// if so, clip to this box, but in such a way that on-screen
244273
// drawing is unchanged
@@ -333,9 +362,11 @@ module.exports = function linePoints(d, opts) {
333362
continue;
334363
}
335364

365+
var nextPt = getPt(i + 1);
366+
336367
clusterRefDist = ptDist(clusterHighPt, clusterStartPt);
337368

338-
if(clusterRefDist < getTolerance(clusterHighPt) * minTolerance) continue;
369+
if(clusterRefDist < getTolerance(clusterHighPt, nextPt) * minTolerance) continue;
339370

340371
clusterUnitVector = [
341372
(clusterHighPt[0] - clusterStartPt[0]) / clusterRefDist,
@@ -350,7 +381,8 @@ module.exports = function linePoints(d, opts) {
350381

351382
// loop over one cluster of points that collapse onto one line
352383
for(i++; i < d.length; i++) {
353-
thisPt = getPt(i);
384+
thisPt = nextPt;
385+
nextPt = getPt(i + 1);
354386
if(!thisPt) {
355387
if(connectGaps) continue;
356388
else break;
@@ -364,7 +396,7 @@ module.exports = function linePoints(d, opts) {
364396
clusterMinDeviation = Math.min(clusterMinDeviation, thisDeviation);
365397
clusterMaxDeviation = Math.max(clusterMaxDeviation, thisDeviation);
366398

367-
if(clusterMaxDeviation - clusterMinDeviation > getTolerance(thisPt)) break;
399+
if(clusterMaxDeviation - clusterMinDeviation > getTolerance(thisPt, nextPt)) break;
368400

369401
clusterEndPt = thisPt;
370402
thisVal = thisVector[0] * clusterUnitVector[0] + thisVector[1] * clusterUnitVector[1];

test/jasmine/tests/scatter_test.js

+45-2
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,10 @@ describe('Test scatter', function() {
405405

406406
// TODO: test coarser decimation outside plot, and removing very near duplicates from the four of a cluster
407407

408+
function reverseXY(v) { return [v[1], v[0]]; }
409+
410+
function reverseXY2(v) { return v.map(reverseXY); }
411+
408412
it('should clip extreme points without changing on-screen paths', function() {
409413
var ptsIn = [
410414
// first bunch: rays going in/out in many directions
@@ -458,8 +462,6 @@ describe('Test scatter', function() {
458462
[[2100, 2100], [-2000, 2100], [-2000, -2000], [2100, -2000], [2100, 2100]]
459463
];
460464

461-
function reverseXY(v) { return [v[1], v[0]]; }
462-
463465
ptsIn.forEach(function(ptsIni, i) {
464466
// disable clustering for these tests
465467
var ptsOut = callLinePoints(ptsIni, {simplify: false});
@@ -472,6 +474,47 @@ describe('Test scatter', function() {
472474
expect(ptsOut2[0]).toBeCloseTo2DArray(ptsExpected[i].map(reverseXY), 1, i);
473475
});
474476
});
477+
478+
it('works when far off-screen points cross the viewport', function() {
479+
function _check(ptsIn, ptsExpected) {
480+
var ptsOut = callLinePoints(ptsIn);
481+
expect(JSON.stringify(ptsOut)).toEqual(JSON.stringify([ptsExpected]));
482+
483+
var ptsOut2 = callLinePoints(ptsIn.map(reverseXY)).map(reverseXY2);
484+
expect(JSON.stringify(ptsOut2)).toEqual(JSON.stringify([ptsExpected]));
485+
}
486+
487+
// first cross the viewport horizontally/vertically
488+
_check([
489+
[-822, 20], [-802, 2], [-801.5, 1.1], [-800, 0],
490+
[900, 0], [901.5, 1.1], [902, 2], [922, 20]
491+
], [
492+
// all that's really important here (and the next check) is that
493+
// the points [-800, 0] and [900, 0] are connected. What we do
494+
// with other points beyond those doesn't matter too much.
495+
[-822, 20], [-800, 0],
496+
[900, 0], [922, 20]
497+
]);
498+
499+
_check([
500+
[-804, 4], [-802, 2], [-801.5, 1.1], [-800, 0],
501+
[900, 0], [901.5, 1.1], [902, 2], [904, 4]
502+
], [
503+
[-804, 4], [-800, 0],
504+
[900, 0]
505+
]);
506+
507+
// now cross the viewport diagonally
508+
_check([
509+
[-801, 925], [-800, 902], [-800.5, 901.1], [-800, 900],
510+
[900, -800], [900.5, -801.1], [900, -802], [901, -825]
511+
], [
512+
// similarly here, we just care that
513+
// [-800, 900] connects to [900, -800]
514+
[-801, 925], [-800, 900],
515+
[900, -800], [901, -825]
516+
]);
517+
});
475518
});
476519

477520
});

0 commit comments

Comments
 (0)