I've been playing around with the CatmullRomSpline class from Flutter the past few days. Drawing points with is trivial, however, I've been looking for information on interpolating between two sets of values and haven't had much luck.
to give a better overview here is the first plot of offsets:
This is the next plot of offsets that I would like to morph/animate to:
Here is the code I have so far:
import 'dart:ui';
import 'package:curves/data.dart';
import 'package:flutter/material.dart';
import 'dart:math' as math;
import 'models/price_plot.dart';
void main() {
runApp(const App());
}
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Home(),
);
}
}
class Home extends StatefulWidget {
const Home({
Key? key,
}) : super(key: key);
@override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin {
late Animation<List<Offset>> animation;
late AnimationController controller;
List<Offset> controlPoints = [];
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
controlPoints = buildControlPoints(hourItems);
setState(() {});
});
}
List<Offset> buildControlPoints(List<List<double>> items) {
final max = items.map((x) => x.last).fold<double>(
items.first.last,
math.max,
);
final min = items.map((x) => x.last).fold<double>(
items.first.last,
math.min,
);
final range = max - min;
final priceMap = {
for (var v in items)
v.last: ((max - v.last) / range) * MediaQuery.of(context).size.height,
};
final pricePlots =
priceMap.entries.map((e) => PricePlot(e.value.toInt(), e.key)).toList();
return controlPoints = List.generate(
pricePlots.length,
(index) {
return Offset(
calculateXOffset(
MediaQuery.of(context).size.width, pricePlots, index),
pricePlots[index].yAxis.toDouble(),
);
},
).toList();
}
double calculateXOffset(double width, List<PricePlot> list, int index) {
if (index == (list.length - 1)) {
return width;
}
return index == 0 ? 0 : width ~/ list.length * (index + 1);
}
void _startAnimation() {
controller.stop();
controller.reset();
controller.forward();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("App"),
),
body: Stack(
children: [
CustomPaint(
painter: SplinePainter(controlPoints),
size: Size(MediaQuery.of(context).size.width,
MediaQuery.of(context).size.height),
),
FloatingActionButton(onPressed: () {
_startAnimation();
})
],
),
);
}
}
class SplinePainter extends CustomPainter {
final List<Offset> items;
SplinePainter(this.items);
@override
void paint(Canvas canvas, Size size) {
canvas.drawPaint(Paint()..color = Colors.white);
if (items.isEmpty) {
return;
}
final spline = CatmullRomSpline(items);
final bezierPaint = Paint()
..strokeCap = StrokeCap.round
..strokeWidth = 2
..color = Colors.blue;
canvas.drawPoints(
PointMode.points,
spline.generateSamples(tolerance: 1e-17).map((e) => e.value).toList(),
bezierPaint,
);
}
@override
bool shouldRepaint(SplinePainter oldDelegate) => true;
}