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; } }