Skip to content

Commit 52c9b82

Browse files
update test for LineChartCubicMonotoneCurve.monotone
1 parent af2b7a8 commit 52c9b82

File tree

1 file changed

+137
-6
lines changed

1 file changed

+137
-6
lines changed

test/chart/line_chart/line_chart_curve_test.dart

Lines changed: 137 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import 'package:fl_chart/fl_chart.dart';
44
import 'package:fl_chart/src/chart/line_chart/line_chart_curve.dart';
55
import 'package:flutter_test/flutter_test.dart';
66

7-
const samplePoints1 = [
7+
const testPoints1 = [
88
Offset(10, 10),
99
Offset(20, 20),
1010
Offset(30, 40),
@@ -157,29 +157,29 @@ void main() {
157157

158158
test('draw curved line if distance > tinyThreshold', () {
159159
final path = _buildPathWithCurve(
160-
samplePoints1,
160+
testPoints1,
161161
const LineChartCubicMonotoneCurve(tinyThresholdSquared: 0),
162162
);
163163

164164
final metrics = path.computeMetrics().toList();
165165

166166
expect(
167167
metrics.single.length,
168-
greaterThan(samplePoints1.straightDistance),
168+
greaterThan(testPoints1.straightDistance),
169169
);
170170
});
171171
});
172172

173173
group('smooth parameter behavior', () {
174174
test('the effect of smooth increases monotonically', () {
175175
var smoothCount = 1;
176-
var lastCurveLength = samplePoints1.straightDistance;
176+
var lastCurveLength = testPoints1.straightDistance;
177177

178178
while (smoothCount < 11) {
179179
final smooth = 0.1 * smoothCount;
180180

181181
final path = _buildPathWithCurve(
182-
samplePoints1,
182+
testPoints1,
183183
LineChartCubicMonotoneCurve(
184184
smooth: smooth,
185185
tinyThresholdSquared: 0,
@@ -192,12 +192,143 @@ void main() {
192192
}
193193
});
194194
});
195+
196+
group('monotone constraint behavior', () {
197+
test('SmoothMonotone.x prevents Y-direction overshoot with zigzag data',
198+
() {
199+
final points = [
200+
const Offset(0, 15),
201+
const Offset(10, -50),
202+
const Offset(20, -56.5),
203+
const Offset(30, -46.5),
204+
const Offset(40, -22.1),
205+
const Offset(50, -2.5),
206+
];
207+
208+
const curve = LineChartCubicMonotoneCurve(
209+
monotone: SmoothMonotone.x,
210+
smooth: 0.3,
211+
tinyThresholdSquared: 0,
212+
);
213+
214+
final path = _buildPathWithCurve(points, curve);
215+
final samples = _samplePath(path, 100);
216+
217+
// Verify that Y coordinates of each curve segment stay within
218+
// the Y range of its two endpoints
219+
for (var i = 0; i < points.length - 1; i++) {
220+
final minY =
221+
points[i].dy < points[i + 1].dy ? points[i].dy : points[i + 1].dy;
222+
final maxY =
223+
points[i].dy > points[i + 1].dy ? points[i].dy : points[i + 1].dy;
224+
225+
final segmentSamples = samples
226+
.where((s) => s.dx >= points[i].dx && s.dx <= points[i + 1].dx);
227+
228+
for (final sample in segmentSamples) {
229+
expect(
230+
sample.dy,
231+
inRange(minY - 1.0, maxY + 1.0),
232+
reason: 'Segment $i: Y=${sample.dy} out of range [$minY, $maxY]',
233+
);
234+
}
235+
}
236+
});
237+
238+
test(
239+
'SmoothMonotone.y prevents X-direction overshoot with horizontal zigzag',
240+
() {
241+
final points = [
242+
const Offset(50, 0),
243+
const Offset(10, 10),
244+
const Offset(90, 20),
245+
const Offset(20, 30),
246+
const Offset(80, 40),
247+
];
248+
249+
const curve = LineChartCubicMonotoneCurve(
250+
monotone: SmoothMonotone.y,
251+
smooth: 0.3,
252+
tinyThresholdSquared: 0,
253+
);
254+
255+
final path = _buildPathWithCurve(points, curve);
256+
final samples = _samplePath(path, 100);
257+
258+
// Verify that X coordinates of each curve segment stay within
259+
// the X range of its two endpoints
260+
for (var i = 0; i < points.length - 1; i++) {
261+
final minX =
262+
points[i].dx < points[i + 1].dx ? points[i].dx : points[i + 1].dx;
263+
final maxX =
264+
points[i].dx > points[i + 1].dx ? points[i].dx : points[i + 1].dx;
265+
266+
final segmentSamples = samples
267+
.where((s) => s.dy >= points[i].dy && s.dy <= points[i + 1].dy);
268+
269+
for (final sample in segmentSamples) {
270+
expect(
271+
sample.dx,
272+
inRange(minX - 1.0, maxX + 1.0),
273+
reason: 'Segment $i: X=${sample.dx} out of range [$minX, $maxX]',
274+
);
275+
}
276+
}
277+
});
278+
});
195279
});
196280
}
197281

282+
/// Sample points uniformly along the path
283+
List<Offset> _samplePath(Path path, int sampleCount) {
284+
final samples = <Offset>[];
285+
final metrics = path.computeMetrics().first;
286+
287+
for (var i = 0; i <= sampleCount; i++) {
288+
final distance = metrics.length * i / sampleCount;
289+
final tangent = metrics.getTangentForOffset(distance);
290+
if (tangent != null) {
291+
samples.add(tangent.position);
292+
}
293+
}
294+
295+
return samples;
296+
}
297+
298+
/// Custom range matcher
299+
Matcher inRange(num min, num max) => _InRangeMatcher(min, max);
300+
301+
class _InRangeMatcher extends Matcher {
302+
const _InRangeMatcher(this.min, this.max);
303+
304+
final num min;
305+
final num max;
306+
307+
@override
308+
bool matches(dynamic item, Map<dynamic, dynamic> matchState) {
309+
if (item is! num) return false;
310+
return item >= min && item <= max;
311+
}
312+
313+
@override
314+
Description describe(Description description) {
315+
return description.add('in range [$min, $max]');
316+
}
317+
318+
@override
319+
Description describeMismatch(
320+
dynamic item,
321+
Description mismatchDescription,
322+
Map<dynamic, dynamic> matchState,
323+
bool verbose,
324+
) {
325+
return mismatchDescription.add('was $item, outside range [$min, $max]');
326+
}
327+
}
328+
198329
void expectLikeStraightLine(
199330
LineChartCurve curve, {
200-
List<Offset> points = samplePoints1,
331+
List<Offset> points = testPoints1,
201332
}) {
202333
final path = _buildPathWithCurve(points, curve);
203334

0 commit comments

Comments
 (0)