Skip to content

Commit 97dea71

Browse files
committed
Downloads chart: Add helper function for computing closest point on line
1 parent b5f7154 commit 97dea71

File tree

2 files changed

+111
-0
lines changed

2 files changed

+111
-0
lines changed

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

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

4242
return (ranges: ranges, weekLists: result.reversed.toList());
4343
}
44+
45+
/// Calculates closest point from [point] to line defined by [startPoint] and
46+
/// [endPoint].
47+
///
48+
/// This assumes that the closest point is on the line, that is within the
49+
/// two endpoints. Returns `(double.maxFinite, double.maxFinite)` if this is
50+
/// not the case.
51+
(num, num) closestPointOnLine(
52+
(num, num) startPoint, (num, num) endPoint, (num, num) point) {
53+
final directionVector =
54+
(endPoint.$1 - startPoint.$1, endPoint.$2 - startPoint.$2);
55+
56+
if (directionVector.$1 == 0 && directionVector.$2 == 0) {
57+
return startPoint;
58+
}
59+
60+
final vector = (point.$1 - startPoint.$1, point.$2 - startPoint.$2);
61+
62+
// The dot product ((v · d) / (d · d)) where v = vector and d = directionVector
63+
final t = ((vector.$1 * directionVector.$1 + vector.$2 * directionVector.$2) /
64+
(directionVector.$1 * directionVector.$1 +
65+
directionVector.$2 * directionVector.$2));
66+
67+
if (t < 0 || t > 1) {
68+
// Closest point is before or after the line. This should not happen in
69+
// our use case.
70+
return (double.maxFinite, double.maxFinite);
71+
}
72+
73+
// t * d
74+
final projectionVOntoD = (t * directionVector.$1, t * directionVector.$2);
75+
final closestPoint = (
76+
startPoint.$1 + projectionVOntoD.$1,
77+
startPoint.$2 + projectionVOntoD.$2
78+
);
79+
return (closestPoint.$1, closestPoint.$2);
80+
}

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)