add graph_view
This commit is contained in:
328
lib/widgets/chart_line_widget.dart
Normal file
328
lib/widgets/chart_line_widget.dart
Normal file
@@ -0,0 +1,328 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../models/chart_model.dart';
|
||||
|
||||
class ChartLineWidget extends StatelessWidget {
|
||||
final ChartData chartData;
|
||||
|
||||
const ChartLineWidget({super.key, required this.chartData});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (chartData.dataPoints.isEmpty) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blueGrey[50],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.blueGrey[200]!),
|
||||
),
|
||||
child: const Center(child: Text('Keine Daten verfügbar')),
|
||||
);
|
||||
}
|
||||
|
||||
final maxLiters = chartData.getMaxLiters();
|
||||
final maxPrice = chartData.getMaxPrice();
|
||||
final maxPricePerLiter = chartData.getMaxPricePerLiter();
|
||||
|
||||
final maxY = [
|
||||
maxLiters,
|
||||
maxPrice,
|
||||
maxPricePerLiter,
|
||||
].reduce((a, b) => a > b ? a : b);
|
||||
final padding = maxY * 0.1;
|
||||
final yAxisMax = maxY + padding;
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.blueGrey[50],
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.blueGrey[200]!),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 300,
|
||||
child: CustomPaint(
|
||||
painter: LineChartPainter(
|
||||
chartData: chartData,
|
||||
yAxisMax: yAxisMax,
|
||||
),
|
||||
size: Size.infinite,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Wrap(
|
||||
spacing: 20,
|
||||
runSpacing: 10,
|
||||
children: [
|
||||
_LegendItem(color: Colors.red, label: 'Liter pro Stop'),
|
||||
_LegendItem(color: Colors.green, label: 'Preis pro Stop (€)'),
|
||||
_LegendItem(
|
||||
color: Colors.amber,
|
||||
label: 'Preis pro Liter (€)',
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _LegendItem extends StatelessWidget {
|
||||
final Color color;
|
||||
final String label;
|
||||
|
||||
const _LegendItem({required this.color, required this.label});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Container(
|
||||
width: 16,
|
||||
height: 16,
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
label,
|
||||
style: const TextStyle(fontSize: 12, color: Colors.black87),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LineChartPainter extends CustomPainter {
|
||||
final ChartData chartData;
|
||||
final double yAxisMax;
|
||||
|
||||
LineChartPainter({required this.chartData, required this.yAxisMax});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
if (chartData.dataPoints.isEmpty) return;
|
||||
|
||||
final paint = Paint()
|
||||
..color = Colors.blueGrey[300]!
|
||||
..strokeWidth = 1;
|
||||
|
||||
final axisPaint = Paint()
|
||||
..color = Colors.blueGrey[600]!
|
||||
..strokeWidth = 2;
|
||||
|
||||
final padding = EdgeInsets.fromLTRB(40, 20, 20, 40);
|
||||
final graphWidth = size.width - padding.left - padding.right;
|
||||
final graphHeight = size.height - padding.top - padding.bottom;
|
||||
|
||||
// Draw grid and axes
|
||||
_drawAxes(canvas, size, padding, axisPaint, paint);
|
||||
|
||||
// Draw lines
|
||||
_drawLine(
|
||||
canvas,
|
||||
chartData,
|
||||
padding,
|
||||
graphWidth,
|
||||
graphHeight,
|
||||
Colors.red,
|
||||
(point) => point.litersPerStop,
|
||||
);
|
||||
|
||||
_drawLine(
|
||||
canvas,
|
||||
chartData,
|
||||
padding,
|
||||
graphWidth,
|
||||
graphHeight,
|
||||
Colors.green,
|
||||
(point) => point.pricePerStop,
|
||||
);
|
||||
|
||||
_drawLine(
|
||||
canvas,
|
||||
chartData,
|
||||
padding,
|
||||
graphWidth,
|
||||
graphHeight,
|
||||
Colors.amber,
|
||||
(point) => point.pricePerLiter,
|
||||
);
|
||||
|
||||
// Draw points
|
||||
final pointPaint = Paint()
|
||||
..strokeWidth = 3
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
||||
final pointsSize = 4.0;
|
||||
|
||||
for (var point in chartData.dataPoints) {
|
||||
final xPos =
|
||||
padding.left +
|
||||
(point.stopNumber - 1) *
|
||||
(graphWidth /
|
||||
(chartData.dataPoints.length - 1 > 0
|
||||
? chartData.dataPoints.length - 1
|
||||
: 1));
|
||||
|
||||
// Red points (Liters)
|
||||
final yLiters =
|
||||
padding.top +
|
||||
graphHeight -
|
||||
(point.litersPerStop / yAxisMax) * graphHeight;
|
||||
pointPaint.color = Colors.red;
|
||||
canvas.drawCircle(Offset(xPos, yLiters), pointsSize, pointPaint);
|
||||
|
||||
// Green points (Price)
|
||||
final yPrice =
|
||||
padding.top +
|
||||
graphHeight -
|
||||
(point.pricePerStop / yAxisMax) * graphHeight;
|
||||
pointPaint.color = Colors.green;
|
||||
canvas.drawCircle(Offset(xPos, yPrice), pointsSize, pointPaint);
|
||||
|
||||
// Amber points (Price per Liter)
|
||||
final yPricePerLiter =
|
||||
padding.top +
|
||||
graphHeight -
|
||||
(point.pricePerLiter / yAxisMax) * graphHeight;
|
||||
pointPaint.color = Colors.amber;
|
||||
canvas.drawCircle(Offset(xPos, yPricePerLiter), pointsSize, pointPaint);
|
||||
}
|
||||
}
|
||||
|
||||
void _drawAxes(
|
||||
Canvas canvas,
|
||||
Size size,
|
||||
EdgeInsets padding,
|
||||
Paint axisPaint,
|
||||
Paint gridPaint,
|
||||
) {
|
||||
// Y-axis
|
||||
canvas.drawLine(
|
||||
Offset(padding.left, padding.top),
|
||||
Offset(padding.left, size.height - padding.bottom),
|
||||
axisPaint,
|
||||
);
|
||||
|
||||
// X-axis
|
||||
canvas.drawLine(
|
||||
Offset(padding.left, size.height - padding.bottom),
|
||||
Offset(size.width - padding.right, size.height - padding.bottom),
|
||||
axisPaint,
|
||||
);
|
||||
|
||||
final graphHeight = size.height - padding.top - padding.bottom;
|
||||
final graphWidth = size.width - padding.left - padding.right;
|
||||
|
||||
// Y-axis labels
|
||||
final steps = 5;
|
||||
for (int i = 0; i <= steps; i++) {
|
||||
final yPos = padding.top + (graphHeight / steps) * i;
|
||||
final value = yAxisMax - (yAxisMax / steps) * i;
|
||||
|
||||
// Grid line
|
||||
canvas.drawLine(
|
||||
Offset(padding.left, yPos),
|
||||
Offset(size.width - padding.right, yPos),
|
||||
gridPaint..strokeWidth = 0.5,
|
||||
);
|
||||
|
||||
// Label
|
||||
final textPainter = TextPainter(
|
||||
text: TextSpan(
|
||||
text: value.toStringAsFixed(1),
|
||||
style: const TextStyle(color: Colors.black54, fontSize: 10),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
textPainter.layout();
|
||||
textPainter.paint(
|
||||
canvas,
|
||||
Offset(
|
||||
padding.left - textPainter.width - 8,
|
||||
yPos - textPainter.height / 2,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// X-axis labels
|
||||
for (int i = 0; i < chartData.dataPoints.length; i++) {
|
||||
final xPos =
|
||||
padding.left +
|
||||
i *
|
||||
(graphWidth /
|
||||
(chartData.dataPoints.length - 1 > 0
|
||||
? chartData.dataPoints.length - 1
|
||||
: 1));
|
||||
|
||||
final textPainter = TextPainter(
|
||||
text: TextSpan(
|
||||
text: (i + 1).toString(),
|
||||
style: const TextStyle(color: Colors.black54, fontSize: 10),
|
||||
),
|
||||
textDirection: TextDirection.ltr,
|
||||
);
|
||||
textPainter.layout();
|
||||
textPainter.paint(
|
||||
canvas,
|
||||
Offset(xPos - textPainter.width / 2, size.height - padding.bottom + 8),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void _drawLine(
|
||||
Canvas canvas,
|
||||
ChartData chartData,
|
||||
EdgeInsets padding,
|
||||
double graphWidth,
|
||||
double graphHeight,
|
||||
Color color,
|
||||
double Function(ChartDataPoint) getValue,
|
||||
) {
|
||||
final paint = Paint()
|
||||
..color = color
|
||||
..strokeWidth = 2
|
||||
..strokeCap = StrokeCap.round
|
||||
..strokeJoin = StrokeJoin.round
|
||||
..style = PaintingStyle.stroke;
|
||||
|
||||
final points = chartData.dataPoints;
|
||||
if (points.length < 2) return;
|
||||
|
||||
final path = Path();
|
||||
|
||||
for (int i = 0; i < points.length; i++) {
|
||||
final x =
|
||||
padding.left +
|
||||
i * (graphWidth / (points.length - 1 > 0 ? points.length - 1 : 1));
|
||||
final y =
|
||||
padding.top +
|
||||
graphHeight -
|
||||
(getValue(points[i]) / yAxisMax) * graphHeight;
|
||||
|
||||
if (i == 0) {
|
||||
path.moveTo(x, y);
|
||||
} else {
|
||||
path.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(LineChartPainter oldDelegate) {
|
||||
return oldDelegate.chartData != chartData ||
|
||||
oldDelegate.yAxisMax != yAxisMax;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user