更新快检功能

This commit is contained in:
wyf
2026-03-10 12:01:00 +08:00
parent d0ae2a9f76
commit 6472baf993
48 changed files with 8046 additions and 120 deletions

View File

@@ -0,0 +1,977 @@
// import 'package:ef/ef.dart';
// import 'package:fl_chart/fl_chart.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter_svg/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';
// class QcTimeSeriesPoint {
// final double value;
// QcTimeSeriesPoint(this.value);
// }
// class QcTimeSeriesChart extends StatelessWidget {
// final List<QcTimeSeriesPoint> dataPoints;
// final double yMin;
// final double yMax;
// final int xSegmentCount;
// final double? baseValue; // 基准值,可选
// final String? baseLabel; // 基准值标签,可选
// const QcTimeSeriesChart({
// Key? key,
// required this.yMin,
// required this.yMax,
// required this.dataPoints,
// this.xSegmentCount = 11,
// this.baseValue,
// this.baseLabel,
// }) : super(key: key);
// int get _dataPointCount => dataPoints.length;
// List<double> _generateYAxisTicks() {
// if (yMin >= yMax) {
// return [0, 20, 40, 60, 80, 100];
// }
// double step = (yMax - yMin) / 5;
// List<double> ticks = [];
// for (int i = 0; i <= 5; i++) {
// ticks.add(yMin + (step * i));
// }
// return ticks;
// }
// // 计算所有刻度位置对应的索引
// List<int> _getTickIndices() {
// List<int> tickIndices = [];
// // 确保至少显示两个刻度(起点和终点)
// if (_dataPointCount <= 1) {
// return [0];
// }
// // 计算合理的刻度间隔
// double step = _dataPointCount / (xSegmentCount - 1);
// for (int i = 0; i < xSegmentCount; i++) {
// int index = (i * step).round();
// // 确保索引在有效范围内
// index = index.clamp(0, _dataPointCount - 1);
// // 避免重复的索引
// if (tickIndices.isEmpty || index != tickIndices.last) {
// tickIndices.add(index);
// }
// }
// // 确保最后一个点是最后一个数据点
// if (tickIndices.last != _dataPointCount - 1) {
// tickIndices.add(_dataPointCount - 1);
// }
// return tickIndices;
// }
// // 查找最大值和最小值的索引和值
// Map<String, dynamic> _findMinMax() {
// if (dataPoints.isEmpty)
// return {'minIndex': -1, 'maxIndex': -1, 'minValue': 0, 'maxValue': 0};
// int minIndex = -1;
// int maxIndex = -1;
// double minValue = double.infinity;
// double maxValue = -double.infinity;
// for (int i = 0; i < dataPoints.length; i++) {
// var point = dataPoints[i];
// if (point.value != -1) {
// // 跳过无效数据
// if (point.value < minValue) {
// minValue = point.value;
// minIndex = i;
// }
// if (point.value > maxValue) {
// maxValue = point.value;
// maxIndex = i;
// }
// }
// }
// return {
// 'minIndex': minIndex,
// 'maxIndex': maxIndex,
// 'minValue': minValue,
// 'maxValue': maxValue
// };
// }
// @override
// Widget build(BuildContext context) {
// // final yTicks = _generateYAxisTicks();
// final double xMax = _dataPointCount.toDouble();
// final double yRange = yMax - yMin;
// final double yInterval = yRange > 0 ? yRange / 5 : 20.0;
// final tickIndices = _getTickIndices();
// final minMaxData = _findMinMax();
// // 将数据点分割成多个连续段
// List<List<FlSpot>> lineSegments = [];
// List<FlSpot> currentSegment = [];
// for (int i = 0; i < dataPoints.length; i++) {
// var point = dataPoints[i];
// if (point.value != -1) {
// currentSegment.add(FlSpot(
// (i + 1).toDouble(),
// point.value,
// ));
// } else if (currentSegment.isNotEmpty) {
// lineSegments.add(currentSegment);
// currentSegment = [];
// }
// }
// if (currentSegment.isNotEmpty) {
// lineSegments.add(currentSegment);
// }
// // 创建渐变填充的线图数据
// List<LineChartBarData> lineBarsData = [];
// for (var segment in lineSegments) {
// if (segment.isEmpty) continue;
// lineBarsData.add(
// LineChartBarData(
// spots: segment,
// isCurved: false,
// color: themeController.currentColor.sc2,
// barWidth: 2,
// dotData: FlDotData(show: false),
// preventCurveOverShooting: true,
// belowBarData: BarAreaData(
// show: true,
// gradient: LinearGradient(
// begin: Alignment.topCenter,
// end: Alignment.bottomCenter,
// colors: [
// themeController.currentColor.sc2.withOpacity(0.3),
// themeController.currentColor.sc2.withOpacity(0.1),
// Colors.transparent,
// ],
// stops: const [0.0, 1, 1.0],
// ),
// applyCutOffY: true,
// cutOffY: 0,
// ),
// ),
// );
// }
// // 创建刻度点的数据(绿色小球)
// List<FlSpot> tickSpots = [];
// for (int index in tickIndices) {
// if (index >= 0 && index < dataPoints.length) {
// var point = dataPoints[index];
// if (point.value != -1) {
// tickSpots.add(FlSpot(
// (index + 1).toDouble(),
// point.value,
// ));
// }
// }
// }
// // 准备水平线列表
// List<HorizontalLine> horizontalLines = [
// HorizontalLine(
// y: 0,
// color: themeController.currentColor.sc4,
// strokeWidth: 1.rpx,
// ),
// ];
// // 如果有基准值,添加基准线
// if (baseValue != null) {
// horizontalLines.add(
// HorizontalLine(
// y: baseValue!,
// color: themeController.currentColor.sc9,
// strokeWidth: 2.rpx,
// dashArray: [8, 4], // 虚线样式
// label: HorizontalLineLabel(
// show: true,
// alignment: Alignment.topRight,
// labelResolver: (line) =>
// "基准".tr + '${baseValue!.toStringAsFixed(0)}',
// style: TextStyle(
// color: themeController.currentColor.sc9,
// fontSize: 18.rpx,
// fontWeight: FontWeight.w500,
// ),
// ),
// ),
// );
// }
// return AspectRatio(
// aspectRatio: 2,
// child: LayoutBuilder(
// builder: (context, constraints) {
// return Stack(
// children: [
// LineChart(
// LineChartData(
// minX: 1,
// maxX: xMax + 0.5,
// minY: yMin - 2,
// maxY: yMax + 2,
// gridData: FlGridData(show: false),
// extraLinesData: ExtraLinesData(
// horizontalLines: horizontalLines,
// ),
// titlesData: FlTitlesData(
// bottomTitles: AxisTitles(
// sideTitles: SideTitles(
// showTitles: true,
// reservedSize: 30,
// interval: 1, // 让 fl_chart 自动计算合适的间隔
// getTitlesWidget: (value, meta) {
// // 只显示我们在 tickIndices 中定义的刻度
// int index = value.round() - 1;
// if (index >= 0 &&
// index < _dataPointCount &&
// tickIndices.contains(index)) {
// String label;
// if (index == 0) {
// label = '0';
// } else if (index == _dataPointCount - 1) {
// label = '${_dataPointCount}';
// } else {
// label = '${index + 1}';
// }
// return Padding(
// padding: const EdgeInsets.only(top: 8.0),
// child: Text(
// label,
// style: TextStyle(
// color: themeController.currentColor.sc4,
// fontSize: 14.rpx,
// ),
// ),
// );
// }
// return const SizedBox.shrink();
// },
// ),
// ),
// leftTitles: AxisTitles(
// sideTitles: SideTitles(
// showTitles: true,
// reservedSize: 60.rpx,
// interval: yInterval,
// getTitlesWidget: (value, meta) {
// return Padding(
// padding: const EdgeInsets.only(right: 8.0),
// child: Text(
// value.toStringAsFixed(0),
// style: TextStyle(
// color: themeController.currentColor.sc4,
// fontSize: 16.rpx,
// ),
// textAlign: TextAlign.right,
// ),
// );
// },
// ),
// ),
// rightTitles:
// AxisTitles(sideTitles: SideTitles(showTitles: false)),
// topTitles:
// AxisTitles(sideTitles: SideTitles(showTitles: false)),
// ),
// borderData: FlBorderData(
// show: true,
// border: Border(
// bottom: BorderSide(color: Colors.grey.withOpacity(0.3)),
// left: BorderSide(color: Colors.grey.withOpacity(0.3)),
// right: BorderSide.none,
// top: BorderSide.none,
// ),
// ),
// lineBarsData: [
// ...lineBarsData,
// // 添加绿色小球的线图数据
// LineChartBarData(
// spots: tickSpots,
// isCurved: false,
// color: Colors.transparent,
// barWidth: 0,
// dotData: FlDotData(
// show: true,
// getDotPainter: (spot, percent, barData, index) {
// return FlDotCirclePainter(
// radius: 4.rpx,
// color: stringToColor("#5DD8C9"),
// strokeWidth: 1,
// strokeColor: stringToColor("#00C1AA"),
// );
// },
// ),
// preventCurveOverShooting: true,
// ),
// ],
// ),
// ),
// // 使用 Positioned 来绘制最大值和最小值的标签
// if (minMaxData['minIndex'] != -1 || minMaxData['maxIndex'] != -1)
// _buildMinMaxLabels(
// context, constraints, minMaxData, xMax, yMin, yMax),
// ],
// );
// },
// ),
// );
// }
// Widget _buildMinMaxLabels(
// BuildContext context,
// BoxConstraints constraints,
// Map<String, dynamic> minMaxData,
// double xMax,
// double yMin,
// double yMax,
// ) {
// // 获取图表区域的实际绘制区域
// // fl_chart 默认会有一些内边距,我们需要估算这些内边距
// double leftPadding = 60.rpx; // 左侧留白用于Y轴标签
// double rightPadding = 10.rpx; // 右侧留白
// double topPadding = 10.rpx; // 顶部留白
// double bottomPadding = 30.rpx; // 底部留白用于X轴标签
// double chartWidth = constraints.maxWidth - leftPadding - rightPadding;
// double chartHeight = constraints.maxHeight - topPadding - bottomPadding;
// // X轴范围1 到 xMax
// // Y轴范围yMin - 2 到 yMax + 2
// double xMin = 1;
// double xMaxValue = xMax;
// double yMinValue = yMin - 2;
// double yMaxValue = yMax + 2;
// double xRange = xMaxValue - xMin;
// double yRange = yMaxValue - yMinValue;
// List<Widget> labels = [];
// // 添加最小值标签
// if (minMaxData['minIndex'] != -1) {
// double minX = (minMaxData['minIndex'] + 1).toDouble();
// double minY = minMaxData['minValue'];
// // 计算在图表中的相对位置 (0-1)
// double relativeX = (minX - xMin) / xRange;
// double relativeY = (minY - yMinValue) / yRange;
// // 转换为像素位置
// double left = leftPadding + (relativeX * chartWidth);
// double top = topPadding + ((1 - relativeY) * chartHeight);
// // 标签尺寸
// double labelWidth = 38.rpx;
// double labelHeight = 50.rpx;
// labels.add(
// Positioned(
// left: left - labelWidth / 2, // 水平居中
// top: top - labelHeight - 8.rpx, // 显示在点的正上方留出8rpx间距
// child: SizedBox(
// width: labelWidth,
// height: labelHeight,
// child: Stack(
// alignment: Alignment.center,
// children: [
// // SVG图片作为背景
// SvgPicture.asset(
// 'assets/img/icon/location.svg', // 替换为你的SVG图片路径
// // width: 28.rpx,
// // height: 40.rpx,
// fit: BoxFit.contain,
// color: stringToColor("#d69dd2"),
// ),
// Padding(
// padding: EdgeInsets.only(bottom: 12.rpx), // 调整这个值来控制向上移动的距离
// child: Text(
// '${minMaxData['minValue'].toStringAsFixed(0)}',
// style: TextStyle(
// color: Colors.white,
// fontSize: AppConstants().smaller_text_fontSize,
// ),
// ),
// ),
// ],
// ),
// ),
// ),
// );
// }
// // 添加最大值标签
// if (minMaxData['maxIndex'] != -1 &&
// minMaxData['maxIndex'] != minMaxData['minIndex']) {
// double maxX = (minMaxData['maxIndex'] + 1).toDouble();
// double maxY = minMaxData['maxValue'];
// // 计算在图表中的相对位置 (0-1)
// double relativeX = (maxX - xMin) / xRange;
// double relativeY = (maxY - yMinValue) / yRange;
// // 转换为像素位置
// double left = leftPadding + (relativeX * chartWidth);
// double top = topPadding + ((1 - relativeY) * chartHeight);
// // 标签尺寸
// double labelWidth = 38.rpx;
// double labelHeight = 50.rpx;
// labels.add(
// Positioned(
// left: left - labelWidth / 2, // 水平居中
// top: top - labelHeight - 8.rpx, // 显示在点的正上方留出8rpx间距
// child: SizedBox(
// width: labelWidth,
// height: labelHeight,
// child: Stack(
// alignment: Alignment.center,
// children: [
// // SVG图片作为背景
// SvgPicture.asset(
// 'assets/img/icon/location.svg', // 替换为你的SVG图片路径
// // width: 28.rpx,
// // height: 40.rpx,
// fit: BoxFit.contain,
// color: stringToColor("#FF9F66"),
// ),
// // 文字覆盖在SVG上
// // Text(
// // '${minMaxData['maxValue'].toStringAsFixed(0)}',
// // style: TextStyle(
// // color: Colors.white,
// // fontSize: AppConstants().smaller_text_fontSize,
// // // fontWeight: FontWeight.bold,
// // ),
// // ),
// Padding(
// padding: EdgeInsets.only(bottom: 12.rpx), // 调整这个值来控制向上移动的距离
// child: Text(
// '${minMaxData['maxValue'].toStringAsFixed(0)}',
// style: TextStyle(
// color: Colors.white,
// fontSize: AppConstants().smaller_text_fontSize,
// ),
// ),
// ),
// ],
// ),
// ),
// ),
// );
// }
// return Stack(
// children: labels,
// );
// }
// }
import 'package:ef/ef.dart';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/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';
class QcTimeSeriesPoint {
final double value;
QcTimeSeriesPoint(this.value);
}
class QcTimeSeriesChart extends StatelessWidget {
final List<QcTimeSeriesPoint> dataPoints;
final double yMin;
final double yMax; // 注意:这个值在使用时会自动加 padding
final int xSegmentCount;
final double? baseValue; // 基准值,可选
final String? baseLabel; // 基准值标签,可选
final double yAxisPadding; // Y轴顶部留白默认为20
const QcTimeSeriesChart({
Key? key,
required this.yMin,
required this.yMax,
required this.dataPoints,
this.xSegmentCount = 11,
this.baseValue,
this.baseLabel,
this.yAxisPadding = 20.0, // 默认为20
}) : super(key: key);
// 添加一个 getter 来获取实际使用的 yMax自动加 padding
double get _actualYMax => yMax + yAxisPadding;
int get _dataPointCount => dataPoints.length;
List<double> _generateYAxisTicks() {
if (yMin >= _actualYMax) {
return [0, 20, 40, 60, 80, 100];
}
double step = (_actualYMax - yMin) / 5;
List<double> ticks = [];
for (int i = 0; i <= 5; i++) {
ticks.add(yMin + (step * i));
}
return ticks;
}
// 计算所有刻度位置对应的索引
List<int> _getTickIndices() {
List<int> tickIndices = [];
// 确保至少显示两个刻度(起点和终点)
if (_dataPointCount <= 1) {
return [0];
}
// 计算合理的刻度间隔
double step = _dataPointCount / (xSegmentCount - 1);
for (int i = 0; i < xSegmentCount; i++) {
int index = (i * step).round();
// 确保索引在有效范围内
index = index.clamp(0, _dataPointCount - 1);
// 避免重复的索引
if (tickIndices.isEmpty || index != tickIndices.last) {
tickIndices.add(index);
}
}
// 确保最后一个点是最后一个数据点
if (tickIndices.last != _dataPointCount - 1) {
tickIndices.add(_dataPointCount - 1);
}
return tickIndices;
}
// 查找最大值和最小值的索引和值
Map<String, dynamic> _findMinMax() {
if (dataPoints.isEmpty)
return {'minIndex': -1, 'maxIndex': -1, 'minValue': 0, 'maxValue': 0};
int minIndex = -1;
int maxIndex = -1;
double minValue = double.infinity;
double maxValue = -double.infinity;
for (int i = 0; i < dataPoints.length; i++) {
var point = dataPoints[i];
if (point.value != -1) {
// 跳过无效数据
if (point.value < minValue) {
minValue = point.value;
minIndex = i;
}
if (point.value > maxValue) {
maxValue = point.value;
maxIndex = i;
}
}
}
return {
'minIndex': minIndex,
'maxIndex': maxIndex,
'minValue': minValue,
'maxValue': maxValue
};
}
@override
Widget build(BuildContext context) {
// final yTicks = _generateYAxisTicks();
final double xMax = _dataPointCount.toDouble();
final double yRange = _actualYMax - yMin;
final double yInterval = yRange > 0 ? yRange / 5 : 20.0;
final tickIndices = _getTickIndices();
final minMaxData = _findMinMax();
// 将数据点分割成多个连续段
List<List<FlSpot>> lineSegments = [];
List<FlSpot> currentSegment = [];
for (int i = 0; i < dataPoints.length; i++) {
var point = dataPoints[i];
if (point.value != -1) {
currentSegment.add(FlSpot(
(i + 1).toDouble(),
point.value,
));
} else if (currentSegment.isNotEmpty) {
lineSegments.add(currentSegment);
currentSegment = [];
}
}
if (currentSegment.isNotEmpty) {
lineSegments.add(currentSegment);
}
// 创建渐变填充的线图数据
List<LineChartBarData> lineBarsData = [];
for (var segment in lineSegments) {
if (segment.isEmpty) continue;
lineBarsData.add(
LineChartBarData(
spots: segment,
isCurved: false,
color: themeController.currentColor.sc2,
barWidth: 2,
dotData: FlDotData(show: false),
preventCurveOverShooting: true,
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
themeController.currentColor.sc2.withOpacity(0.3),
themeController.currentColor.sc2.withOpacity(0.1),
Colors.transparent,
],
stops: const [0.0, 1, 1.0],
),
applyCutOffY: true,
cutOffY: 0,
),
),
);
}
// 创建刻度点的数据(绿色小球)
List<FlSpot> tickSpots = [];
for (int index in tickIndices) {
if (index >= 0 && index < dataPoints.length) {
var point = dataPoints[index];
if (point.value != -1) {
tickSpots.add(FlSpot(
(index + 1).toDouble(),
point.value,
));
}
}
}
// 准备水平线列表
List<HorizontalLine> horizontalLines = [
HorizontalLine(
y: 0,
color: themeController.currentColor.sc4,
strokeWidth: 1.rpx,
),
];
// 如果有基准值,添加基准线
if (baseValue != null) {
horizontalLines.add(
HorizontalLine(
y: baseValue!,
color: themeController.currentColor.sc9,
strokeWidth: 2.rpx,
dashArray: [8, 4], // 虚线样式
label: HorizontalLineLabel(
show: true,
alignment: Alignment.topRight,
labelResolver: (line) =>
"基准".tr + '${baseValue!.toStringAsFixed(0)}',
style: TextStyle(
color: themeController.currentColor.sc9,
fontSize: 18.rpx,
fontWeight: FontWeight.w500,
),
),
),
);
}
return AspectRatio(
aspectRatio: 2,
child: LayoutBuilder(
builder: (context, constraints) {
return Stack(
children: [
LineChart(
LineChartData(
minX: 1,
maxX: xMax + 0.5,
minY: yMin - 2,
maxY: _actualYMax + 2,
gridData: FlGridData(show: false),
extraLinesData: ExtraLinesData(
horizontalLines: horizontalLines,
),
titlesData: FlTitlesData(
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 30,
interval: 1, // 让 fl_chart 自动计算合适的间隔
getTitlesWidget: (value, meta) {
// 只显示我们在 tickIndices 中定义的刻度
int index = value.round() - 1;
if (index >= 0 &&
index < _dataPointCount &&
tickIndices.contains(index)) {
String label;
if (index == 0) {
label = '0';
} else if (index == _dataPointCount - 1) {
label = '${_dataPointCount}';
} else {
label = '${index + 1}';
}
return Padding(
padding: const EdgeInsets.only(top: 8.0),
child: Text(
label,
style: TextStyle(
color: themeController.currentColor.sc4,
fontSize: 14.rpx,
),
),
);
}
return const SizedBox.shrink();
},
),
),
leftTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
reservedSize: 60.rpx,
interval: yInterval,
getTitlesWidget: (value, meta) {
return Padding(
padding: const EdgeInsets.only(right: 8.0),
child: Text(
value.toStringAsFixed(0),
style: TextStyle(
color: themeController.currentColor.sc4,
fontSize: 16.rpx,
),
textAlign: TextAlign.right,
),
);
},
),
),
rightTitles:
AxisTitles(sideTitles: SideTitles(showTitles: false)),
topTitles:
AxisTitles(sideTitles: SideTitles(showTitles: false)),
),
borderData: FlBorderData(
show: true,
border: Border(
bottom: BorderSide(color: Colors.grey.withOpacity(0.3)),
left: BorderSide(color: Colors.grey.withOpacity(0.3)),
right: BorderSide.none,
top: BorderSide.none,
),
),
lineBarsData: [
...lineBarsData,
// 添加绿色小球的线图数据
LineChartBarData(
spots: tickSpots,
isCurved: false,
color: Colors.transparent,
barWidth: 0,
dotData: FlDotData(
show: true,
getDotPainter: (spot, percent, barData, index) {
return FlDotCirclePainter(
radius: 4.rpx,
color: stringToColor("#5DD8C9"),
strokeWidth: 1,
strokeColor: stringToColor("#00C1AA"),
);
},
),
preventCurveOverShooting: true,
),
],
),
),
// 使用 Positioned 来绘制最大值和最小值的标签
if (minMaxData['minIndex'] != -1 || minMaxData['maxIndex'] != -1)
_buildMinMaxLabels(
context, constraints, minMaxData, xMax, yMin, _actualYMax),
],
);
},
),
);
}
Widget _buildMinMaxLabels(
BuildContext context,
BoxConstraints constraints,
Map<String, dynamic> minMaxData,
double xMax,
double yMin,
double yMax,
) {
// 获取图表区域的实际绘制区域
// fl_chart 默认会有一些内边距,我们需要估算这些内边距
double leftPadding = 60.rpx; // 左侧留白用于Y轴标签
double rightPadding = 10.rpx; // 右侧留白
double topPadding = 10.rpx; // 顶部留白
double bottomPadding = 30.rpx; // 底部留白用于X轴标签
double chartWidth = constraints.maxWidth - leftPadding - rightPadding;
double chartHeight = constraints.maxHeight - topPadding - bottomPadding;
// X轴范围1 到 xMax
// Y轴范围yMin - 2 到 yMax + 2
double xMin = 1;
double xMaxValue = xMax;
double yMinValue = yMin - 2;
double yMaxValue = yMax + 2;
double xRange = xMaxValue - xMin;
double yRange = yMaxValue - yMinValue;
List<Widget> labels = [];
// 添加最小值标签
if (minMaxData['minIndex'] != -1) {
double minX = (minMaxData['minIndex'] + 1).toDouble();
double minY = minMaxData['minValue'];
// 计算在图表中的相对位置 (0-1)
double relativeX = (minX - xMin) / xRange;
double relativeY = (minY - yMinValue) / yRange;
// 转换为像素位置
double left = leftPadding + (relativeX * chartWidth);
double top = topPadding + ((1 - relativeY) * chartHeight);
// 标签尺寸
double labelWidth = 38.rpx;
double labelHeight = 50.rpx;
labels.add(
Positioned(
left: left - labelWidth / 2, // 水平居中
top: top - labelHeight - 8.rpx, // 显示在点的正上方留出8rpx间距
child: SizedBox(
width: labelWidth,
height: labelHeight,
child: Stack(
alignment: Alignment.center,
children: [
// SVG图片作为背景
SvgPicture.asset(
'assets/img/icon/location.svg',
fit: BoxFit.contain,
color: stringToColor("#d69dd2"),
),
Padding(
padding: EdgeInsets.only(bottom: 12.rpx),
child: Text(
'${minMaxData['minValue'].toStringAsFixed(0)}',
style: TextStyle(
color: Colors.white,
fontSize: AppConstants().smaller_text_fontSize,
),
),
),
],
),
),
),
);
}
// 添加最大值标签
if (minMaxData['maxIndex'] != -1 &&
minMaxData['maxIndex'] != minMaxData['minIndex']) {
double maxX = (minMaxData['maxIndex'] + 1).toDouble();
double maxY = minMaxData['maxValue'];
// 计算在图表中的相对位置 (0-1)
double relativeX = (maxX - xMin) / xRange;
double relativeY = (maxY - yMinValue) / yRange;
// 转换为像素位置
double left = leftPadding + (relativeX * chartWidth);
double top = topPadding + ((1 - relativeY) * chartHeight);
// 标签尺寸
double labelWidth = 38.rpx;
double labelHeight = 50.rpx;
labels.add(
Positioned(
left: left - labelWidth / 2, // 水平居中
top: top - labelHeight - 8.rpx, // 显示在点的正上方留出8rpx间距
child: SizedBox(
width: labelWidth,
height: labelHeight,
child: Stack(
alignment: Alignment.center,
children: [
// SVG图片作为背景
SvgPicture.asset(
'assets/img/icon/location.svg',
fit: BoxFit.contain,
color: stringToColor("#FF9F66"),
),
Padding(
padding: EdgeInsets.only(bottom: 12.rpx),
child: Text(
'${minMaxData['maxValue'].toStringAsFixed(0)}',
style: TextStyle(
color: Colors.white,
fontSize: AppConstants().smaller_text_fontSize,
),
),
),
],
),
),
),
);
}
return Stack(
children: labels,
);
}
}