282 lines
7.9 KiB
Dart
282 lines
7.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_svg/flutter_svg.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/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);
|
|
showTipDialog(
|
|
context,
|
|
Container(
|
|
child: Text(
|
|
explain,
|
|
style: TextStyle(
|
|
fontSize: 26.rpx,
|
|
color: themeController.currentColor.sc3,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
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;
|
|
}
|