662 lines
20 KiB
Dart
662 lines
20 KiB
Dart
// 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<Map<String, dynamic>> 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<Map<String, dynamic>> 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;
|
||
}
|
||
}
|