// 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; // } 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; final bool showRangeBackground; // 新增参数,控制是否显示区间背景 const HorizontalBarChart({ super.key, required this.showLabel, this.showPercent = true, this.showRangeBackground = false, // 默认为false }); @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, showRangeBackground: showRangeBackground, ), ), // 柱状图列表 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) { if (oldDelegate is SingleBarPainter) { return oldDelegate.value != value || oldDelegate.color != color; } return true; } } class GridPainter extends CustomPainter { final double totalHeight; final int gridCount = 5; final double maxValue = 100; final double bottomPadding = 30.0.rpx; final bool showRangeBackground; // 新增参数 GridPainter({ required this.totalHeight, this.showRangeBackground = false, }); @override void paint(Canvas canvas, Size size) { final double rightPadding = 20.0.rpx; final double chartWidth = size.width - rightPadding; final double chartHeight = totalHeight - bottomPadding; // 如果需要显示区间背景 if (showRangeBackground) { final double segmentWidth = chartWidth / gridCount; // 计算0-60对应的网格区间(60对应3格,因为每格20) // 0-60: 第0-3格(0,20,40,60) for (int i = 0; i < 3; i++) { final rect = Rect.fromLTWH( i * segmentWidth, 0, segmentWidth, chartHeight, ); final paint = Paint() ..color = themeController.currentColor.sc1.withOpacity(0.2) ..style = PaintingStyle.fill; canvas.drawRect(rect, paint); } // 60-100: 第3-5格(60,80,100) for (int i = 3; i < 5; i++) { final rect = Rect.fromLTWH( i * segmentWidth, 0, segmentWidth, chartHeight, ); final paint = Paint() ..color = themeController.currentColor.sc9.withOpacity(0.2) ..style = PaintingStyle.fill; canvas.drawRect(rect, paint); } } // 绘制网格线 final Paint gridPaint = Paint() ..color = Colors.grey.withOpacity(0.3) ..strokeWidth = 1.0.rpx; 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) { if (oldDelegate is GridPainter) { return oldDelegate.showRangeBackground != showRangeBackground; } return true; } }