// usage_line_chart.dart — 最近两周(14 天)按天流量折线图。 // CustomPainter 手绘:渐变面积 + accent 折线 + 末点强调,无第三方图表依赖。 import 'package:flutter/material.dart'; /// values: 每日流量(GB,旧→新)。accent/grid 由调用方传 token 色(零硬编码)。 class UsageLineChart extends StatelessWidget { const UsageLineChart({super.key, required this.values, required this.accent, required this.grid}); final List values; final Color accent, grid; @override Widget build(BuildContext context) { return SizedBox( height: 116, child: CustomPaint(painter: _LinePainter(values, accent, grid), size: Size.infinite), ); } } class _LinePainter extends CustomPainter { _LinePainter(this.values, this.accent, this.grid); final List values; final Color accent, grid; @override void paint(Canvas canvas, Size size) { final gp = Paint() ..color = grid ..strokeWidth = 1; for (var i = 1; i <= 3; i++) { final y = size.height * i / 4; canvas.drawLine(Offset(0, y), Offset(size.width, y), gp); } if (values.isEmpty) return; final maxV = values.reduce((a, b) => a > b ? a : b); final denom = maxV <= 0 ? 1.0 : maxV; final n = values.length; final dx = n > 1 ? size.width / (n - 1) : 0.0; const topPad = 10.0, botPad = 6.0; final h = size.height - topPad - botPad; Offset pt(int i) => Offset(i * dx, topPad + h * (1 - values[i] / denom)); final area = Path()..moveTo(0, size.height); for (var i = 0; i < n; i++) { area.lineTo(pt(i).dx, pt(i).dy); } area ..lineTo(size.width, size.height) ..close(); canvas.drawPath( area, Paint() ..shader = LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [accent.withOpacity(0.26), accent.withOpacity(0)], ).createShader(Rect.fromLTWH(0, 0, size.width, size.height)), ); final line = Path()..moveTo(pt(0).dx, pt(0).dy); for (var i = 1; i < n; i++) { line.lineTo(pt(i).dx, pt(i).dy); } canvas.drawPath( line, Paint() ..color = accent ..style = PaintingStyle.stroke ..strokeWidth = 2 ..strokeJoin = StrokeJoin.round ..strokeCap = StrokeCap.round, ); canvas.drawCircle(pt(n - 1), 3.2, Paint()..color = accent); } @override bool shouldRepaint(covariant _LinePainter old) => old.values != values || old.accent != accent || old.grid != grid; }