From b044b06cb9fddb0e200c42dede98da0de6871c42 Mon Sep 17 00:00:00 2001 From: Sarah Zakarias Date: Fri, 28 Feb 2025 13:00:36 +0000 Subject: [PATCH 1/5] Downloads chart: Add helper function for determining point in polygon --- .../widget/downloads_chart/computations.dart | 43 +++++++++++++++++++ .../downloads_chart/downloads_chart_test.dart | 38 ++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/pkg/web_app/lib/src/widget/downloads_chart/computations.dart b/pkg/web_app/lib/src/widget/downloads_chart/computations.dart index 2dfe7ae95b..013d359bc0 100644 --- a/pkg/web_app/lib/src/widget/downloads_chart/computations.dart +++ b/pkg/web_app/lib/src/widget/downloads_chart/computations.dart @@ -125,3 +125,46 @@ bool isPointOnPathWithTolerance( } return false; } + +/// Determines if a point is inside a polygon. +/// +/// Uses the ray casting algorithm to determine if a given point lies inside +/// a polygon defined by a list of vertices. The polygon is assumed to be +/// closed and non-self-intersecting. +/// +/// Returns `true` if the point is inside the polygon or exactly on a vertex and +/// `false` otherwise. +bool isPointInPolygon(List<(double, double)> polygon, (double, double) point) { + if (polygon.length < 3) { + return false; + } + + int intersections = 0; + final (px, py) = point; + + for (int i = 0; i < polygon.length; i++) { + final (x1, y1) = polygon[i]; + final (x2, y2) = polygon[(i + 1) % polygon.length]; + + // Check if the point is on a vertex + if ((px == x1 && py == y1) || (px == x2 && py == y2)) { + return true; + } + + if (py > min(y1, y2) && py <= max(y1, y2)) { + double intersectX; + // vertical edge + if (x1 == x2) { + intersectX = x1; + } else { + intersectX = x1 + (py - y1) * (x2 - x1) / (y2 - y1); + } + + if (px < intersectX) { + intersections++; + } + } + } + + return intersections % 2 == 1; +} diff --git a/pkg/web_app/test/widget/downloads_chart/downloads_chart_test.dart b/pkg/web_app/test/widget/downloads_chart/downloads_chart_test.dart index a9ac960280..30dbd3736f 100644 --- a/pkg/web_app/test/widget/downloads_chart/downloads_chart_test.dart +++ b/pkg/web_app/test/widget/downloads_chart/downloads_chart_test.dart @@ -182,4 +182,42 @@ void main() { expect(isPointOnPathWithTolerance(chart, point, 0.001), isTrue); }); }); + + group('isPointInPolygon', () { + test('Basic inside/outside, vertex, edge, and invalid input', () { + final square = [(0.0, 0.0), (0.0, 2.0), (2.0, 2.0), (2.0, 0.0)]; + final invalidPolygon = [(0.0, 0.0)]; + + final insidePoint = (1.0, 1.0); + final outsidePoint = (3.0, 1.0); + final vertexPoint = (0.0, 0.0); + final edgePoint = (1.0, 0.0); + + expect(isPointInPolygon(square, insidePoint), isTrue); + expect(isPointInPolygon(square, outsidePoint), isFalse); + expect(isPointInPolygon(square, vertexPoint), isTrue); + expect(isPointInPolygon(square, edgePoint), isFalse); + expect(isPointInPolygon(invalidPolygon, insidePoint), isFalse); + }); + + test('Complex polygon', () { + final complexPolygon = [ + (0.0, 0.0), + (0.0, 4.0), + (2.0, 2.0), + (4.0, 4.0), + (4.0, 0.0) + ]; + final insidePoint = (1.0, 1.0); + final outsidePoint = (3.0, 3.0); + final vertexPoint = (1.0, 1.0); + final outsidePointOnEdgeExtension = (3.0, 4.0); + + expect(isPointInPolygon(complexPolygon, insidePoint), isTrue); + expect(isPointInPolygon(complexPolygon, outsidePoint), isFalse); + expect(isPointInPolygon(complexPolygon, vertexPoint), isTrue); + expect(isPointInPolygon(complexPolygon, outsidePointOnEdgeExtension), + isFalse); + }); + }); } From 81b1f2611d96ce1634f2990c4f78fc744e108382 Mon Sep 17 00:00:00 2001 From: Sarah Zakarias Date: Fri, 28 Feb 2025 13:29:05 +0000 Subject: [PATCH 2/5] update test --- .../test/widget/downloads_chart/downloads_chart_test.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/web_app/test/widget/downloads_chart/downloads_chart_test.dart b/pkg/web_app/test/widget/downloads_chart/downloads_chart_test.dart index 30dbd3736f..cb9bf93d32 100644 --- a/pkg/web_app/test/widget/downloads_chart/downloads_chart_test.dart +++ b/pkg/web_app/test/widget/downloads_chart/downloads_chart_test.dart @@ -209,13 +209,15 @@ void main() { (4.0, 0.0) ]; final insidePoint = (1.0, 1.0); - final outsidePoint = (3.0, 3.0); + final outsidePoint = (2.0, 3.0); final vertexPoint = (1.0, 1.0); + final edgePoint = (1.0, 3.0); final outsidePointOnEdgeExtension = (3.0, 4.0); expect(isPointInPolygon(complexPolygon, insidePoint), isTrue); expect(isPointInPolygon(complexPolygon, outsidePoint), isFalse); expect(isPointInPolygon(complexPolygon, vertexPoint), isTrue); + expect(isPointInPolygon(complexPolygon, edgePoint), isFalse); expect(isPointInPolygon(complexPolygon, outsidePointOnEdgeExtension), isFalse); }); From e0f7dd04904722305f20387daf4d76e7ab76e1a7 Mon Sep 17 00:00:00 2001 From: Sarah Zakarias Date: Mon, 3 Mar 2025 08:53:14 +0000 Subject: [PATCH 3/5] Explicit edge check --- .../lib/src/widget/downloads_chart/computations.dart | 9 +++++++-- .../widget/downloads_chart/downloads_chart_test.dart | 4 +++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pkg/web_app/lib/src/widget/downloads_chart/computations.dart b/pkg/web_app/lib/src/widget/downloads_chart/computations.dart index 013d359bc0..4f8b91308d 100644 --- a/pkg/web_app/lib/src/widget/downloads_chart/computations.dart +++ b/pkg/web_app/lib/src/widget/downloads_chart/computations.dart @@ -132,8 +132,8 @@ bool isPointOnPathWithTolerance( /// a polygon defined by a list of vertices. The polygon is assumed to be /// closed and non-self-intersecting. /// -/// Returns `true` if the point is inside the polygon or exactly on a vertex and -/// `false` otherwise. +/// Returns `true` if the point is inside the polygon or exactly on a vertex or +/// on edge, and `false` otherwise. bool isPointInPolygon(List<(double, double)> polygon, (double, double) point) { if (polygon.length < 3) { return false; @@ -142,6 +142,11 @@ bool isPointInPolygon(List<(double, double)> polygon, (double, double) point) { int intersections = 0; final (px, py) = point; + // Check if the point is on an edge + if (isPointOnPathWithTolerance(polygon, point, 0.001)) { + return true; + } + for (int i = 0; i < polygon.length; i++) { final (x1, y1) = polygon[i]; final (x2, y2) = polygon[(i + 1) % polygon.length]; diff --git a/pkg/web_app/test/widget/downloads_chart/downloads_chart_test.dart b/pkg/web_app/test/widget/downloads_chart/downloads_chart_test.dart index cb9bf93d32..0240f0d3d9 100644 --- a/pkg/web_app/test/widget/downloads_chart/downloads_chart_test.dart +++ b/pkg/web_app/test/widget/downloads_chart/downloads_chart_test.dart @@ -212,12 +212,14 @@ void main() { final outsidePoint = (2.0, 3.0); final vertexPoint = (1.0, 1.0); final edgePoint = (1.0, 3.0); + final edgePoint2 = (3.0, 3.0); final outsidePointOnEdgeExtension = (3.0, 4.0); expect(isPointInPolygon(complexPolygon, insidePoint), isTrue); expect(isPointInPolygon(complexPolygon, outsidePoint), isFalse); expect(isPointInPolygon(complexPolygon, vertexPoint), isTrue); - expect(isPointInPolygon(complexPolygon, edgePoint), isFalse); + expect(isPointInPolygon(complexPolygon, edgePoint), isTrue); + expect(isPointInPolygon(complexPolygon, edgePoint2), isTrue); expect(isPointInPolygon(complexPolygon, outsidePointOnEdgeExtension), isFalse); }); From c3795f35efe3df9b1f3c64be0f564b96e1ce3e77 Mon Sep 17 00:00:00 2001 From: Sarah Zakarias Date: Mon, 3 Mar 2025 10:08:41 +0000 Subject: [PATCH 4/5] handle horizontal edge --- .../lib/src/widget/downloads_chart/computations.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/web_app/lib/src/widget/downloads_chart/computations.dart b/pkg/web_app/lib/src/widget/downloads_chart/computations.dart index 4f8b91308d..7742e64dd5 100644 --- a/pkg/web_app/lib/src/widget/downloads_chart/computations.dart +++ b/pkg/web_app/lib/src/widget/downloads_chart/computations.dart @@ -158,9 +158,9 @@ bool isPointInPolygon(List<(double, double)> polygon, (double, double) point) { if (py > min(y1, y2) && py <= max(y1, y2)) { double intersectX; - // vertical edge - if (x1 == x2) { - intersectX = x1; + if (y1 == y2) { + // horizontal edge + continue; } else { intersectX = x1 + (py - y1) * (x2 - x1) / (y2 - y1); } From 3155ae4bcf882615e39a6058aef68c2a17a54297 Mon Sep 17 00:00:00 2001 From: Sarah Zakarias Date: Mon, 3 Mar 2025 13:02:25 +0000 Subject: [PATCH 5/5] typo --- pkg/web_app/lib/src/widget/downloads_chart/computations.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/web_app/lib/src/widget/downloads_chart/computations.dart b/pkg/web_app/lib/src/widget/downloads_chart/computations.dart index 7742e64dd5..60b3450308 100644 --- a/pkg/web_app/lib/src/widget/downloads_chart/computations.dart +++ b/pkg/web_app/lib/src/widget/downloads_chart/computations.dart @@ -133,7 +133,7 @@ bool isPointOnPathWithTolerance( /// closed and non-self-intersecting. /// /// Returns `true` if the point is inside the polygon or exactly on a vertex or -/// on edge, and `false` otherwise. +/// an edge, and `false` otherwise. bool isPointInPolygon(List<(double, double)> polygon, (double, double) point) { if (polygon.length < 3) { return false;