Skip to content

Commit 6dd0534

Browse files
szakariasisoos
authored andcommitted
Downloads chart: Add helper function for computing closest point on line (dart-lang#8584)
1 parent 840e863 commit 6dd0534

File tree

2 files changed

+112
-0
lines changed

2 files changed

+112
-0
lines changed

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

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,41 @@ Iterable<String> prepareRanges(List<VersionRangeCount> rangeDownloads) {
4141

4242
return (ranges: ranges, weekLists: result.reversed.toList());
4343
}
44+
45+
/// Calculates the closest point on the line segment between [startPoint]
46+
/// and [endPoint] to a given [point].
47+
///
48+
/// If [startPoint] and [endPoint] are the same, [startPoint] is returned.
49+
///
50+
/// If [point] is outside the line segment, that is the closest point would not
51+
/// be within the thwo endpoints, `(double.maxFinite, double.maxFinite)`
52+
/// is returned.
53+
(num, num) closestPointOnLine(
54+
(num, num) startPoint, (num, num) endPoint, (num, num) point) {
55+
final directionVector =
56+
(endPoint.$1 - startPoint.$1, endPoint.$2 - startPoint.$2);
57+
58+
if (directionVector.$1 == 0 && directionVector.$2 == 0) {
59+
return startPoint;
60+
}
61+
62+
final v = (point.$1 - startPoint.$1, point.$2 - startPoint.$2);
63+
64+
// The dot product ((v · d) / (d · d))
65+
final t = ((v.$1 * directionVector.$1 + v.$2 * directionVector.$2) /
66+
(directionVector.$1 * directionVector.$1 +
67+
directionVector.$2 * directionVector.$2));
68+
69+
if (t < 0 || t > 1) {
70+
// Closest point is before or after the line.
71+
return (double.maxFinite, double.maxFinite);
72+
}
73+
74+
// t * d
75+
final projectionVOntoD = (t * directionVector.$1, t * directionVector.$2);
76+
final closestPoint = (
77+
startPoint.$1 + projectionVOntoD.$1,
78+
startPoint.$2 + projectionVOntoD.$2
79+
);
80+
return (closestPoint.$1, closestPoint.$2);
81+
}

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

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,78 @@ void main() {
7878
expect(w3[i], [0, 0, 0, 0, 0, 0]);
7979
}
8080
});
81+
82+
group('closestPointOnLine tests', () {
83+
test('point on the line', () {
84+
final lineStart = (0, 0);
85+
final lineEnd = (10, 10);
86+
final point = (5, 5);
87+
final closest = closestPointOnLine(lineStart, lineEnd, point);
88+
expect(closest, (5.0, 5.0));
89+
});
90+
91+
test('point before the line', () {
92+
final lineStart = (0, 0);
93+
final lineEnd = (10, 10);
94+
final point = (-2, -5);
95+
final closest = closestPointOnLine(lineStart, lineEnd, point);
96+
expect(closest, (double.maxFinite, double.maxFinite));
97+
});
98+
99+
test('point after the line', () {
100+
final lineStart = (0, 0);
101+
final lineEnd = (10, 10);
102+
final point = (15, 15);
103+
final closest = closestPointOnLine(lineStart, lineEnd, point);
104+
expect(closest, (double.maxFinite, double.maxFinite));
105+
});
106+
107+
test('point off the line', () {
108+
final lineStart = (0, 0);
109+
final lineEnd = (10, 10);
110+
final point = (5, 3);
111+
final closest = closestPointOnLine(lineStart, lineEnd, point);
112+
expect(closest, (4.0, 4.0));
113+
});
114+
115+
test('vertical line', () {
116+
final lineStart = (1, 2);
117+
final lineEnd = (1, 10);
118+
final point = (5, 5);
119+
final closest = closestPointOnLine(lineStart, lineEnd, point);
120+
expect(closest, (1.0, 5.0));
121+
});
122+
123+
test('horizontal line', () {
124+
final lineStart = (2, 1);
125+
final lineEnd = (10, 1);
126+
final point = (5, 5);
127+
final closest = closestPointOnLine(lineStart, lineEnd, point);
128+
expect(closest, (5.0, 1.0));
129+
});
130+
131+
test('same start and end points', () {
132+
final lineStart = (5, 5);
133+
final lineEnd = (5, 5);
134+
final point = (10, 10);
135+
final closest = closestPointOnLine(lineStart, lineEnd, point);
136+
expect(closest, (5.0, 5.0));
137+
});
138+
139+
test('line with negative coordinates', () {
140+
final lineStart = (-5, -5);
141+
final lineEnd = (5, 5);
142+
final point = (0, 10);
143+
final closest = closestPointOnLine(lineStart, lineEnd, point);
144+
expect(closest, (5.0, 5.0));
145+
});
146+
147+
test('line with negative and positive coordinates', () {
148+
final lineStart = (-5, 5);
149+
final lineEnd = (5, -5);
150+
final point = (0, 0);
151+
final closest = closestPointOnLine(lineStart, lineEnd, point);
152+
expect(closest, (0.0, 0.0));
153+
});
154+
});
81155
}

0 commit comments

Comments
 (0)