feat: serpentine map layout function
This commit is contained in:
@@ -0,0 +1,29 @@
|
|||||||
|
import 'dart:math' as math;
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
/// Deterministic serpentine layout for the journey map. Stage 0 is at the
|
||||||
|
/// bottom; the path snakes upward. Works for any stage count.
|
||||||
|
class MapLayout {
|
||||||
|
const MapLayout({
|
||||||
|
required this.width,
|
||||||
|
this.nodeSpacing = 108,
|
||||||
|
this.topPadding = 140,
|
||||||
|
this.bottomPadding = 150,
|
||||||
|
});
|
||||||
|
|
||||||
|
final double width;
|
||||||
|
final double nodeSpacing;
|
||||||
|
final double topPadding;
|
||||||
|
final double bottomPadding;
|
||||||
|
|
||||||
|
double get amplitude => width * 0.26;
|
||||||
|
|
||||||
|
double heightFor(int count) =>
|
||||||
|
topPadding + bottomPadding + (count - 1) * nodeSpacing;
|
||||||
|
|
||||||
|
Offset nodeCenter(int index, int count) {
|
||||||
|
final y = heightFor(count) - bottomPadding - index * nodeSpacing;
|
||||||
|
final x = width / 2 + amplitude * math.sin(index * 1.05);
|
||||||
|
return Offset(x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import 'package:block_seasons/ui/widgets/map_layout.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
const layout = MapLayout(width: 400);
|
||||||
|
|
||||||
|
test('node 0 sits near the bottom, later nodes climb', () {
|
||||||
|
final h = layout.heightFor(60);
|
||||||
|
final first = layout.nodeCenter(0, 60);
|
||||||
|
final last = layout.nodeCenter(59, 60);
|
||||||
|
expect(first.dy, greaterThan(h - 200));
|
||||||
|
expect(last.dy, lessThan(200));
|
||||||
|
for (var i = 1; i < 60; i++) {
|
||||||
|
expect(layout.nodeCenter(i, 60).dy,
|
||||||
|
lessThan(layout.nodeCenter(i - 1, 60).dy));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('x stays within horizontal margins', () {
|
||||||
|
for (var i = 0; i < 60; i++) {
|
||||||
|
final x = layout.nodeCenter(i, 60).dx;
|
||||||
|
expect(x, greaterThanOrEqualTo(400 * 0.12));
|
||||||
|
expect(x, lessThanOrEqualTo(400 * 0.88));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('vertical spacing is uniform', () {
|
||||||
|
final a = layout.nodeCenter(3, 60).dy - layout.nodeCenter(4, 60).dy;
|
||||||
|
final b = layout.nodeCenter(40, 60).dy - layout.nodeCenter(41, 60).dy;
|
||||||
|
expect(a, closeTo(b, 0.001));
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user