import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:vbvs_app/common/color/appConstants.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:vbvs_app/component/tool/ClickableContainer.dart'; import 'package:vbvs_app/enum/APPPackageType.dart'; import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart'; class HorizontalBarChart extends StatelessWidget { final List> showLabel; final bool showPercent; const HorizontalBarChart({ super.key, required this.showLabel, this.showPercent = true, }); @override Widget build(BuildContext context) { final data = showLabel.map((item) { return BarData( label: item['name'], value: (item['percent'] ?? 0).toDouble(), color: item['color'] ?? Colors.grey, explain: item['explain'] ?? '', ); }).toList(); final double labelWidth = (MediaQuery.of(context).size.width * 0.5).clamp( MediaQuery.of(context).size.width * 0.08, MediaQuery.of(context).size.width * 0.22); final double barHeight = 24.0.rpx; final double barSpacing = 40.0.rpx; final totalHeight = data.length * (barHeight + barSpacing) + 30.rpx; return SizedBox( height: totalHeight, child: Row( children: [ // 左侧标签列 SizedBox( width: labelWidth, child: ListView.builder( physics: const NeverScrollableScrollPhysics(), itemCount: data.length, itemBuilder: (context, index) { final bar = data[index]; return Container( height: barHeight + barSpacing, alignment: Alignment.centerRight, child: LabelWithSvg( label: bar.label, explain: bar.explain, ), ); }, ), ), SizedBox( width: 16.rpx, ), // 右侧柱状图区 Expanded( child: Stack( children: [ // 网格线背景层 CustomPaint( size: Size(double.infinity, totalHeight), painter: GridPainter(totalHeight: totalHeight), ), // 柱状图列表 ListView.builder( physics: const NeverScrollableScrollPhysics(), itemCount: data.length, itemBuilder: (context, index) { final bar = data[index]; return SizedBox( height: barHeight + barSpacing, child: CustomPaint( painter: SingleBarPainter( value: bar.value, color: bar.color, showPercent: showPercent, ), ), ); }, ) ], ), ), ], ), ); } } class BarData { final String label; final double value; final Color color; final String explain; BarData({ required this.label, required this.value, required this.color, required this.explain, }); } class LabelWithSvg extends StatelessWidget { final String label; final String explain; const LabelWithSvg({ super.key, required this.label, required this.explain, }); @override Widget build(BuildContext context) { final textStyle = TextStyle(color: themeController.currentColor.sc3, fontSize: 26.rpx); return Row( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.center, children: [ Flexible( child: Text( label, style: textStyle, overflow: TextOverflow.ellipsis, ), ), // SizedBox(width: 8.rpx), ClickableContainer( backgroundColor: Colors.transparent, highlightColor: Colors.white, padding: EdgeInsetsDirectional.fromSTEB(14.rpx, 14.rpx, 14.rpx, 14.rpx), borderRadius: 0.rpx, onTap: () { // Get.toNamed("/deviceShareListPage", arguments: explain); if (AppConstants().ent_type == APPPackageType.MHT.code) { showTipDialog( context, Container( child: Text( explain, style: TextStyle(fontSize: 26.rpx, color: Colors.black), ), ), backgroundColor: Color(0xFFFFFFFF), colors: [ Color(0XFF1592AA), Color(0xFF0C83A7), Color(0xFF006FA3) ], ); } else { showTipDialog( context, Container( child: Text( explain, style: TextStyle( fontSize: 26.rpx, color: themeController.currentColor.sc3), ), ), backgroundColor: themeController.currentColor.sc17, colors: AppConstants().thNormalButton, ); } }, child: SizedBox( width: 17.rpx, height: 17.rpx, child: SvgPicture.asset( 'assets/img/icon/explain.svg', fit: BoxFit.cover, color: Colors.white, ), ), ), ], ); } } class SingleBarPainter extends CustomPainter { final double value; final Color color; final bool showPercent; final double maxValue = 100; SingleBarPainter({ required this.value, required this.color, required this.showPercent, }); @override void paint(Canvas canvas, Size size) { final barHeight = 24.0.rpx; final rightPadding = 20.0.rpx; final chartWidth = size.width - rightPadding; final left = 0.0; final right = left + (value / maxValue) * chartWidth; final rect = Rect.fromLTWH( left, (size.height - barHeight) / 2, right - left, barHeight); final paint = Paint()..color = color; canvas.drawRect(rect, paint); if (showPercent) { final textStyle = TextStyle(color: themeController.currentColor.sc3, fontSize: 26.rpx); final textPainter = TextPainter(textDirection: TextDirection.ltr); textPainter.text = TextSpan(text: '${value.toStringAsFixed(0)}', style: textStyle); textPainter.layout(); canvas.save(); canvas.clipRect(Rect.fromLTWH( left, (size.height - barHeight) / 2, chartWidth, barHeight)); textPainter.paint( canvas, Offset(right + 4.0.rpx, (size.height - textPainter.height) / 2), ); canvas.restore(); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; } class GridPainter extends CustomPainter { final double totalHeight; final int gridCount = 5; final double maxValue = 100; final double bottomPadding = 30.0.rpx; GridPainter({required this.totalHeight}); @override void paint(Canvas canvas, Size size) { final Paint gridPaint = Paint() ..color = Colors.grey.withOpacity(0.3) ..strokeWidth = 1.0.rpx; final double rightPadding = 20.0.rpx; final double chartWidth = size.width - rightPadding; final double chartHeight = totalHeight - bottomPadding; for (int i = 0; i <= gridCount; i++) { double dx = (i / gridCount) * chartWidth; _drawDashedLine( canvas, Offset(dx, 0), Offset(dx, chartHeight), gridPaint); final percent = (i / gridCount) * maxValue; final TextPainter textPainter = TextPainter( textDirection: TextDirection.ltr, text: TextSpan( text: '${percent.toInt()}', style: TextStyle(color: Colors.grey, fontSize: 18.rpx), ), ); textPainter.layout(); textPainter.paint( canvas, Offset(dx - textPainter.width / 2, chartHeight + 4.0.rpx)); } } void _drawDashedLine(Canvas canvas, Offset start, Offset end, Paint paint) { const double dashWidth = 6.0; const double dashSpace = 4.0; final double totalHeight = (end.dy - start.dy).abs(); double currentY = start.dy; while (currentY < end.dy) { final double nextY = currentY + dashWidth; canvas.drawLine( Offset(start.dx, currentY), Offset(start.dx, nextY > end.dy ? end.dy : nextY), paint, ); currentY = nextY + dashSpace; } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; }