364 lines
10 KiB
Dart
364 lines
10 KiB
Dart
import 'dart:math';
|
||
import 'dart:developer' as dev;
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/scheduler.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:fl_chart/fl_chart.dart';
|
||
import 'package:intl/intl.dart';
|
||
|
||
import '../../../service/base.dart';
|
||
import '../process.dart';
|
||
|
||
|
||
class ProcessChart extends StatefulWidget {
|
||
final int pileId;
|
||
const ProcessChart({super.key, required this.pileId});
|
||
|
||
@override
|
||
State<ProcessChart> createState() => _ProcessChartState();
|
||
}
|
||
|
||
class _ProcessChartState extends State<ProcessChart> {
|
||
List<ProcessEntity> processList = [];
|
||
List<Widget> chartTitleWidget = [];
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
fetchData();
|
||
}
|
||
|
||
int maxX = 5;
|
||
int maxYL = 14; //深度
|
||
int maxYR = 0; //10cm
|
||
ChartData chartData = ChartData([]);
|
||
void fetchData() async {
|
||
try {
|
||
SchedulerBinding.instance.addPostFrameCallback((_) async {
|
||
//获取点的数据
|
||
List detailCdate = await GetServices().getProcessData(widget.pileId);
|
||
|
||
setState(() {
|
||
double ten1Max = 0;
|
||
double ten2Max = 0;
|
||
for (var i = 0; i < detailCdate.length; i++) {
|
||
ProcessEntity process = ProcessEntity.fromJson(detailCdate[i]);
|
||
processList.add(process);
|
||
// 左(深度)右(10cm流量)标题栏最大值计算
|
||
maxYL = max(maxYL, process.depth.ceil());
|
||
ten1Max = max(ten1Max, process.subtotalFlow1);
|
||
ten2Max = max(ten2Max, process.subtotalFlow2);
|
||
maxYR = max(ten2Max.ceil(), ten1Max.ceil());
|
||
}
|
||
int radtio = (maxYR / maxYL).ceil();
|
||
chartData = ChartData(processList, maxX, maxYL, maxYR, radtio);
|
||
chartTitleWidget = chartData.chartTitleWeidget();
|
||
});
|
||
});
|
||
} catch (e) {
|
||
dev.log("错误");
|
||
setState(() {
|
||
processList = [];
|
||
});
|
||
}
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
super.dispose();
|
||
SystemChrome.setPreferredOrientations([]);
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return Stack(
|
||
children: [
|
||
Positioned(
|
||
child: Row(
|
||
mainAxisAlignment: MainAxisAlignment.center,
|
||
children: chartTitleWidget)),
|
||
LineChart(chartData.lineChart)
|
||
],
|
||
);
|
||
}
|
||
}
|
||
|
||
class ChartTileModel {
|
||
String title;
|
||
Color colors;
|
||
ChartTileModel(
|
||
this.title,
|
||
this.colors,
|
||
);
|
||
}
|
||
|
||
class ChartData {
|
||
List<ProcessEntity> processList;
|
||
int maxX;
|
||
// 这3个值变化
|
||
int maxYL;
|
||
int maxYR;
|
||
int radtio;
|
||
List<ChartTileModel> chartTitle = [
|
||
ChartTileModel("深度:", Colors.green),
|
||
ChartTileModel("10cm流量1:", Colors.pink),
|
||
ChartTileModel("10cm流量2:", Colors.cyan),
|
||
];
|
||
ChartData(this.processList,
|
||
[this.maxX = 5, this.maxYL = 14, this.maxYR = 100, this.radtio = 8]);
|
||
|
||
List<Widget> chartTitleWeidget() {
|
||
List<Widget> chartTitleWidget = [];
|
||
for (var i = 0; i < chartTitle.length; i++) {
|
||
ChartTileModel tileModel = chartTitle[i];
|
||
List<Widget> item = [
|
||
Text(tileModel.title),
|
||
Container(
|
||
width: 13,
|
||
height: 8,
|
||
margin: const EdgeInsets.only(right: 5),
|
||
decoration: BoxDecoration(color: tileModel.colors),
|
||
),
|
||
const SizedBox(
|
||
width: 10,
|
||
height: 10,
|
||
),
|
||
];
|
||
chartTitleWidget.addAll(item);
|
||
}
|
||
return chartTitleWidget;
|
||
}
|
||
|
||
LineChartData get lineChart => LineChartData(
|
||
lineTouchData: lineTouchData,
|
||
gridData: gridData,
|
||
titlesData: titlesData,
|
||
borderData: borderData,
|
||
lineBarsData: lineBarsData,
|
||
minX: 0,
|
||
maxX: maxX.toDouble(),
|
||
maxY: maxYR.toDouble(),
|
||
minY: 0,
|
||
);
|
||
|
||
LineTouchData get lineTouchData => LineTouchData(
|
||
handleBuiltInTouches: true,
|
||
touchTooltipData: LineTouchTooltipData(
|
||
fitInsideHorizontally: true,
|
||
fitInsideVertically: true,
|
||
// tooltipBgColor: Colors.black38.withOpacity(0.8),
|
||
getTooltipItems: (List<LineBarSpot> touchedSpots) {
|
||
// 自定义提示框内容
|
||
return touchedSpots.map((LineBarSpot touchedSpot) {
|
||
// 找到对应的数据点的索引
|
||
final x = touchedSpot.x;
|
||
int index = (x / maxX * processList.length).round();
|
||
ProcessEntity process = processList[index];
|
||
String lineToolValue = "";
|
||
TextStyle lineToolStyle = const TextStyle(
|
||
color: Colors.green,
|
||
);
|
||
|
||
if (touchedSpot.barIndex == 0) {
|
||
lineToolValue =
|
||
"时间:${DateFormat('HH:mm:ss').format(DateTime.parse(process.recvTime)).toString()}\n深度:${process.depth}\n累计流量:${process.toatalFlow1}";
|
||
lineToolStyle = const TextStyle(color: Colors.green);
|
||
} else if (touchedSpot.barIndex == 1) {
|
||
lineToolValue = "10cm流量1:${process.subtotalFlow1}";
|
||
lineToolStyle = const TextStyle(color: Colors.pink);
|
||
} else if (touchedSpot.barIndex == 2) {
|
||
lineToolValue = "10cm流量2:${process.subtotalFlow2}";
|
||
lineToolStyle = const TextStyle(color: Colors.cyan);
|
||
}
|
||
|
||
return LineTooltipItem(lineToolValue, lineToolStyle,
|
||
textAlign: TextAlign.left);
|
||
}).toList();
|
||
},
|
||
),
|
||
);
|
||
// FlGridData get gridData => const FlGridData(show: false);
|
||
//网格
|
||
FlGridData get gridData => const FlGridData(drawHorizontalLine: true);
|
||
//设置标题
|
||
FlTitlesData get titlesData => FlTitlesData(
|
||
bottomTitles: AxisTitles(
|
||
sideTitles: bottomTitles,
|
||
),
|
||
rightTitles: AxisTitles(
|
||
sideTitles: rightTitles(),
|
||
),
|
||
topTitles: const AxisTitles(
|
||
sideTitles: SideTitles(showTitles: false),
|
||
),
|
||
leftTitles: AxisTitles(
|
||
sideTitles: leftTitles(),
|
||
),
|
||
);
|
||
|
||
SideTitles get bottomTitles => SideTitles(
|
||
showTitles: true,
|
||
reservedSize: 32,
|
||
interval: 1,
|
||
getTitlesWidget: bottomTitleWidgets,
|
||
);
|
||
SideTitles leftTitles() => SideTitles(
|
||
getTitlesWidget: leftTitleWidgets,
|
||
showTitles: true,
|
||
interval: 1,
|
||
reservedSize: 40,
|
||
);
|
||
SideTitles rightTitles() => SideTitles(
|
||
getTitlesWidget: rightTitleWidgets,
|
||
showTitles: true,
|
||
interval: 1,
|
||
reservedSize: 40,
|
||
);
|
||
|
||
//x轴
|
||
Widget bottomTitleWidgets(double value, TitleMeta meta) {
|
||
const style = TextStyle(
|
||
fontWeight: FontWeight.bold,
|
||
fontSize: 14,
|
||
);
|
||
|
||
String getTimeFromIndex(int index) {
|
||
if (processList.isEmpty || index < 0 || index >= processList.length) {
|
||
return '';
|
||
}
|
||
return processList[index].recvTime.toString().split(" ")[1];
|
||
}
|
||
|
||
int index;
|
||
|
||
switch (value.toInt()) {
|
||
case 0:
|
||
index = 0;
|
||
break;
|
||
case 1:
|
||
index = (processList.length / 5).round();
|
||
break;
|
||
case 2:
|
||
index = (processList.length * 2 / 5).round();
|
||
break;
|
||
case 3:
|
||
index = (processList.length * 3 / 5).round();
|
||
break;
|
||
case 4:
|
||
index = (processList.length * 4 / 5).round();
|
||
break;
|
||
case 5:
|
||
index = processList.length - 1;
|
||
break;
|
||
default:
|
||
return SideTitleWidget(
|
||
axisSide: meta.axisSide,
|
||
space: 10,
|
||
child: const Text(''),
|
||
);
|
||
}
|
||
|
||
return SideTitleWidget(
|
||
axisSide: meta.axisSide,
|
||
space: 10,
|
||
child: Text(
|
||
getTimeFromIndex(index),
|
||
style: style,
|
||
),
|
||
);
|
||
}
|
||
|
||
final style = const TextStyle(
|
||
fontWeight: FontWeight.bold,
|
||
fontSize: 14,
|
||
);
|
||
//左y轴
|
||
Widget leftTitleWidgets(double value, TitleMeta meta) {
|
||
String text;
|
||
int intValue = (value).ceil();
|
||
if (intValue % (2 * radtio) == 0 && intValue <= maxYR) {
|
||
text = (intValue / radtio).floor().toString();
|
||
if (maxYR - intValue < 2 * radtio) {
|
||
text = "深度(m)";
|
||
}
|
||
} else {
|
||
return const Text("");
|
||
}
|
||
|
||
return Text(text, style: style, textAlign: TextAlign.center);
|
||
}
|
||
|
||
//右y轴
|
||
Widget rightTitleWidgets(double value, TitleMeta meta) {
|
||
String text;
|
||
int intValue = value.toInt();
|
||
|
||
if (intValue >= 0 && intValue % (2 * radtio) == 0 && intValue <= maxYR) {
|
||
text = (intValue.ceil()).toString();
|
||
if (maxYR - intValue < 2 * radtio) {
|
||
text = "流量(L)";
|
||
}
|
||
} else {
|
||
return Container();
|
||
}
|
||
|
||
return Text(text, style: style, textAlign: TextAlign.center);
|
||
}
|
||
|
||
//边界数据
|
||
FlBorderData get borderData => FlBorderData(
|
||
show: true,
|
||
border: const Border(
|
||
bottom: BorderSide(color: Colors.grey, width: 4),
|
||
left: BorderSide(color: Colors.transparent),
|
||
right: BorderSide(color: Colors.transparent),
|
||
top: BorderSide(color: Colors.transparent),
|
||
),
|
||
);
|
||
//数据
|
||
List<LineChartBarData> get lineBarsData => [
|
||
lineChartBarDataTen1,
|
||
lineChartBarDataTen2,
|
||
lineChartBarDataDepth,
|
||
];
|
||
LineChartBarData get lineChartBarDataDepth => LineChartBarData(
|
||
isCurved: false,
|
||
color: Colors.green,
|
||
barWidth: 3,
|
||
isStrokeCapRound: true,
|
||
dotData: const FlDotData(show: false),
|
||
belowBarData: BarAreaData(show: false),
|
||
spots: processList.map((item) {
|
||
int index = processList.indexOf(item);
|
||
return FlSpot(
|
||
(maxX / processList.length) * index, (item.depth * radtio));
|
||
}).toList());
|
||
|
||
LineChartBarData get lineChartBarDataTen1 => LineChartBarData(
|
||
isCurved: false,
|
||
color: Colors.pink,
|
||
barWidth: 3,
|
||
isStrokeCapRound: true,
|
||
dotData: const FlDotData(show: false),
|
||
belowBarData: BarAreaData(
|
||
show: false,
|
||
color: Colors.pink,
|
||
),
|
||
spots: processList.map((item) {
|
||
int index = processList.indexOf(item);
|
||
return FlSpot((maxX / processList.length) * index, item.subtotalFlow1);
|
||
}).toList());
|
||
|
||
LineChartBarData get lineChartBarDataTen2 => LineChartBarData(
|
||
isCurved: false,
|
||
color: Colors.cyan,
|
||
barWidth: 3,
|
||
isStrokeCapRound: true,
|
||
dotData: const FlDotData(show: false),
|
||
belowBarData: BarAreaData(show: false),
|
||
spots: processList.map((item) {
|
||
int index = processList.indexOf(item);
|
||
return FlSpot((maxX / processList.length) * index, item.subtotalFlow2);
|
||
}).toList());
|
||
}
|