Skip to content

Commit f44e403

Browse files
szakariasisoos
authored andcommitted
Downloads chart: add helper for calculating closest point on path (dart-lang#8600)
1 parent 7daffd0 commit f44e403

File tree

2 files changed

+76
-0
lines changed

2 files changed

+76
-0
lines changed

pkg/web_app/lib/src/widget/downloads_chart/computations.dart

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:math';
6+
57
import 'package:_pub_shared/data/download_counts_data.dart';
68

79
Iterable<String> prepareRanges(List<VersionRangeCount> rangeDownloads) {
@@ -79,3 +81,47 @@ Iterable<String> prepareRanges(List<VersionRangeCount> rangeDownloads) {
7981
);
8082
return (closestPoint.$1, closestPoint.$2);
8183
}
84+
85+
/// Calculates the Euclidean distance between two points.
86+
double distance((num, num) point, (double, double) point2) {
87+
final dx = point.$1 - point2.$1;
88+
final dy = point.$2 - point2.$2;
89+
return sqrt(dx * dx + dy * dy);
90+
}
91+
92+
/// Finds the closest point on [path] (a series of points defining the line
93+
/// segments) to a given [point].
94+
(num, num) closestPointOnPath(
95+
List<(double, double)> path, (double, double) point) {
96+
if (path.length < 2) {
97+
return (double.maxFinite, double.maxFinite);
98+
}
99+
(num, num) closestPoint = (double.maxFinite, double.maxFinite);
100+
var minDistance = double.infinity;
101+
for (int i = 0; i < path.length - 1; i++) {
102+
final p = closestPointOnLine(path[i], path[i + 1], point);
103+
final dist = distance(p, point);
104+
if (dist < minDistance) {
105+
minDistance = dist;
106+
closestPoint = p;
107+
}
108+
}
109+
return closestPoint;
110+
}
111+
112+
/// Determines if a given [point] is within a specified [tolerance] distance of
113+
/// a [path] defined by a series of points.
114+
bool isPointOnPathWithTolerance(
115+
List<(double, double)> path, (double, double) point, double tolerance) {
116+
if (path.length < 2) {
117+
// Not enough points to define a line segment.
118+
return false;
119+
}
120+
121+
final closestPoint = closestPointOnPath(path, point);
122+
final dist = distance(closestPoint, point);
123+
if (dist < tolerance) {
124+
return true;
125+
}
126+
return false;
127+
}

pkg/web_app/test/widget/downloads_chart/downloads_chart_test.dart

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,4 +152,34 @@ void main() {
152152
expect(closest, (0.0, 0.0));
153153
});
154154
});
155+
156+
group('isPointOnPathWithTolerance', () {
157+
test('Points on and off the line segment', () {
158+
final path = [(0.0, 0.0), (2.0, 2.0), (4.0, 0.0)];
159+
160+
final pointOnLine = (1.0, 1.0);
161+
expect(isPointOnPathWithTolerance(path, pointOnLine, 0.001), isTrue);
162+
163+
final pointCloseToLine = (1.0, 1.1);
164+
expect(isPointOnPathWithTolerance(path, pointCloseToLine, 0.2), isTrue);
165+
expect(
166+
isPointOnPathWithTolerance(path, pointCloseToLine, 0.001), isFalse);
167+
168+
final pointFurtherFromLine = (1.0, 1.5);
169+
expect(
170+
isPointOnPathWithTolerance(path, pointFurtherFromLine, 0.1), isFalse);
171+
});
172+
173+
test('Path with fewer than 2 points', () {
174+
final path = [(1.0, 1.0)];
175+
final point = (1.0, 1.0);
176+
expect(isPointOnPathWithTolerance(path, point, 0.001), isFalse);
177+
});
178+
179+
test('Point on zero length segment', () {
180+
final chart = [(1.0, 1.0), (1.0, 1.0), (5.0, 1.0)];
181+
final point = (1.0, 1.0);
182+
expect(isPointOnPathWithTolerance(chart, point, 0.001), isTrue);
183+
});
184+
});
155185
}

0 commit comments

Comments
 (0)