diff --git a/lib/Controller/gnss_controller.dart b/lib/Controller/gnss_controller.dart index 8144f31..da9eeb9 100644 --- a/lib/Controller/gnss_controller.dart +++ b/lib/Controller/gnss_controller.dart @@ -18,13 +18,14 @@ class GnssController extends GetxController { // "QZSS": true, // "SBAS": true, // }.obs; - final selectedSignal = [true, true, true, true, true, true].obs; + final selectedSignal = [false, false, false, false, false].obs; //天空图 + final QselectedSignal = [false, false, false, false, false].obs; //信号质量 @override void onInit() async { super.onInit(); - // gnss = Gnss(port: "/dev/ttysWK2", baudrate: 115200); - gnss = Gnss(port: "COM1", baudrate: 115200); + gnss = Gnss(port: "/dev/ttysWK2", baudrate: 115200); + // gnss = Gnss(port: "COM1", baudrate: 115200); gnss.start(); gnss.locationStream.listen((location) { locationData = location; diff --git a/lib/main.dart b/lib/main.dart index 8a83730..9ea2f5e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -6,42 +6,6 @@ import 'sky/sky_plot.dart'; import 'sky/sky_info.dart'; import 'Controller/gnss_controller.dart'; // Import the correct file location for GnssController -// void main() { -// Get.lazyPut(() => GnssController(), tag: 'gnss'); -// runApp(MyApp()); -// } - -// Gnss gnss = Gnss(port: "/dev/ttysWK2", baudrate: 115200); - -// class MyApp extends StatefulWidget { -// @override -// _MyAppState createState() => _MyAppState(); -// } - -// class _MyAppState extends State { -// @override -// void initState() { -// super.initState(); -// // 初始化每个组的颜色 -// gnss.start(); -// gnss.locationStream.listen((location) { -// log(location.toString() as num); -// }); -// gnss.signalStream.listen((location) { -// log(location.toString() as num); -// }); -// } - -// @override -// Widget build(BuildContext context) { -// return Scaffold( -// appBar: AppBar( -// title: Text('天空图'), -// ), -// body: SkyPlotPage(controller: Get.find(tag: 'gnss'))); -// } -// } - void main() { Get.put(GnssController()); runApp(MyApp()); @@ -76,7 +40,7 @@ class _MyAppState extends State { appBar: AppBar( title: Text('天空图'), ), - body: SkyInfo(), + body: SkyInfoPlotPage(), )); } } diff --git a/lib/quality/chartpart.dart b/lib/quality/chartpart.dart index 00f70bf..2f07392 100644 --- a/lib/quality/chartpart.dart +++ b/lib/quality/chartpart.dart @@ -1,89 +1,322 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; +import 'package:get/get.dart'; +import 'package:gnss/gnss.dart'; + +import '../Controller/gnss_controller.dart'; + +const signalNameList = ["GPS", "GLONASS", "GALILEO", "BEIDOU", "QZSS"]; +const signalPrefixList = ["G", "R", "E", "B", "Q"]; +const List signalColorList = [ + Color.fromARGB(255, 255, 0, 0), + Color.fromARGB(255, 0, 255, 0), + Color.fromARGB(255, 0, 0, 255), + Color.fromARGB(255, 255, 255, 0), + Color.fromARGB(255, 0, 255, 255) +]; class ChartPart extends StatelessWidget { - final List> groupColors; // 接收每个组的颜色 - - ChartPart({required this.groupColors}); // 构造函数 - + late final GnssController controller; + final List signalColorList; // 接收每个组的颜色 +final List QselectedSignal; // 新增变量,用于跟踪选中的信号 + ChartPart({super.key}) { + controller = Get.find(); + } + List checkSVData = []; + + final signalGNSS = [ + controller.signalData?.GPS, + controller.signalData?.GLO, + controller.signalData?.GAL, + controller.signalData?.BDS, + controller.signalData?.QZSS, + ]; + double maxY = 0; + int maxX = 10; + double xLength = 0; + int? i; // 新增变量,用于跟踪选中的按钮索引 @override - Widget build(BuildContext context) { - return OrientationBuilder(builder: (context, orientation) { - final size = MediaQuery.of(context).size; +void drawBarChart( + Canvas canvas, + Size size, - // 使用静态示例数据来绘制图表框架 - List sampleData = []; - for (int i = 0; i < groupColors.length; i++) { - // 确保每个组至少有两个颜色 - if (groupColors[i].isNotEmpty && - groupColors[i][0] != Colors.transparent) { - sampleData.add( - BarChartGroupData( - x: i, - barRods: [ - BarChartRodData( - toY: (4 + i).toDouble(), color: groupColors[i][0]), - ], - ), - ); - } + int index, + Paint paint, + ) { + if(QselectedSignal[index]){ + paint.color = signalColorList[index]; + for (final signal in signalGNSS[index]) { + for (int i = 0; i < signals.length; i++) { + SvItem item = svData[i]; + if (item.l1 == 0 && + item.l2 == 0 && + item.l3 == 0 && + item.l4 == 0 && + item.l5 == 0) { + continue; } - return OrientationBuilder( - builder: (context, orientation) { - return SizedBox( - width: size.width, - child: Padding( - padding: const EdgeInsets.all(10), - child: BarChart( - BarChartData( - maxY: 10, - barTouchData: BarTouchData(enabled: false), - titlesData: FlTitlesData( - show: true, - bottomTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - reservedSize: 42, - getTitlesWidget: (value, meta) { - // 根据方向设置标题 - return Text(orientation == Orientation.portrait - ? 'Group $value' - : 'Group $value'); // 可以根据需要调整 - }, - ), - ), - topTitles: const AxisTitles( - sideTitles: SideTitles( - showTitles: false, - ), - ), - rightTitles: const AxisTitles( - sideTitles: SideTitles( - showTitles: false, - ), - ), - leftTitles: AxisTitles( - sideTitles: SideTitles( - showTitles: true, - reservedSize: 40, - getTitlesWidget: (value, meta) { - return RotatedBox( - quarterTurns: 0, // 旋转 270 度 - child: Text(value.toString()), - ); - }, - ), - ), + maxX = max(maxX, item.sn); + List listy = [item.l1, item.l2, item.l3, item.l4, item.l5]; + int maxItem = listy.reduce(max); + maxY = max(maxY, maxItem.toDouble()); + checkSVData.add(makeGroupData(i + 1, listy, signalQualityItem.color)); + xLength++;} + +// svData = List.from(controller.svData) +// ..sort((a, b) => a.sn.compareTo(b.sn)); + +// for (int i = 0; i < svData.length; i++) { +// SvItem item = svData[i]; +// if (item.l1 == 0 && +// item.l2 == 0 && +// item.l3 == 0 && +// item.l4 == 0 && +// item.l5 == 0) { +// continue; +// } +// if (signalQualityItem.show && +// (selectedIndex == null || +// selectedIndex == +// controller.signalQuality.keys.toList().indexOf(sys))) { +// maxX = max(maxX, item.sn); +// List listy = [item.l1, item.l2, item.l3, item.l4, item.l5]; +// int maxItem = listy.reduce(max); +// maxY = max(maxY, maxItem.toDouble()); +// checkSVData.add(makeGroupData(i + 1, listy, signalQualityItem.color)); +// xLength++; +// } +// } +// // print('CheckSVData: $checkSVData'); // 调试输出 +// if (checkSVData.isNotEmpty) { +// if (controller.startIndex.value < checkSVData.length - 8) { +// checkSVData = checkSVData.sublist( +// controller.startIndex.value, controller.startIndex.value + 8); +// } else { +// checkSVData = checkSVData.sublist( +// controller.startIndex.value, checkSVData.length - 1); +// } +// } +// } + +// @override +// Widget build(BuildContext context) { +// // 只有当选中状态不为空时才更新数据 +// if (selectedIndex != null) { +// updateSVdata(); +// } + +// ever(controller.startIndex, (callback) { +// if (svData.length - callback < 8) { +// controller.startIndex.value = max(0, svData.length - 8); // 确保不小于 0 +// return; +// } +// updateSVdata(); +// }); + + final size = MediaQuery.of(context).size; + final orientation = + MediaQuery.of(context).orientation == Orientation.portrait; + return SizedBox( + width: size.width, + // decoration: const BoxDecoration(color: Colors.white), + child: AspectRatio( + aspectRatio: 1, + child: Padding( + padding: const EdgeInsets.all(10), + child: Stack( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: Obx(() { + controller.startIndex.value; + return BarChart( + swapAnimationDuration: const Duration(milliseconds: 0), + BarChartData( + maxY: maxY, // 提示需要+ 20 因为 提示是根据柱状图位置进行计算的 + barTouchData: BarTouchData( + enabled: false, + touchTooltipData: BarTouchTooltipData( + // tooltipBgColor: Colors.blueGrey, + getTooltipItem: ( + BarChartGroupData group, + int groupIndex, + BarChartRodData rod, + int rodIndex, + ) { + return BarTooltipItem( + 'L1:${group.barRods[0].toY}\n', + const TextStyle( + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + color: Colors.black, + fontSize: 18, + shadows: [ + Shadow( + color: Colors.black26, + blurRadius: 12, + ) + ], + ), + ); + }, + ), + touchCallback: (event, response) { + if (event.isInterestedForInteractions && + response != null && + response.spot != null) { + // setState(() { + // touchedGroupIndex = + // response.spot!.touchedBarGroupIndex; + // }); + } else { + // setState(() { + // touchedGroupIndex = -100; + // }); + } + }, + ), + titlesData: FlTitlesData( + show: true, + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: bottomTitles, + reservedSize: 42, + ), + ), + leftTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + reservedSize: 40, + interval: 1, + getTitlesWidget: leftTitles, + ), + ), + ), + borderData: FlBorderData( + show: false, + ), + barGroups: checkSVData, + gridData: const FlGridData(show: true), + ), + ); + }), + ), + orientation + ? const SizedBox( + height: 50, + ) + : const Text(""), + ], + ), + Positioned( + bottom: 0, + left: 0, + right: 0, + height: size.height, + child: Opacity( + opacity: 0, // 设置 Slider 组件的透明度为 0,使其不可见 + child: Slider( + value: controller.startIndex.toDouble(), + onChanged: (newvalue) { + controller.updateSlider(newvalue); + }, + min: 0, + max: xLength <= 5 ? 0 : xLength - 5, ), - borderData: FlBorderData(show: false), - barGroups: sampleData, - alignment: BarChartAlignment.spaceAround, ), ), - ), - ); - }, - ); - }); + ], + ), + ), + ), + ); + } + + Widget leftTitles(double value, TitleMeta meta) { + const style = TextStyle( + color: Color(0xff7589a2), + fontWeight: FontWeight.bold, + fontSize: 14, + decoration: TextDecoration.none); + String text; + int intValue = (value).ceil(); + if (intValue % 10 == 0 && intValue <= maxY) { + text = intValue.toString(); + } else { + return Container(); + } + return SideTitleWidget( + axisSide: meta.axisSide, + space: 0, + child: Padding( + padding: const EdgeInsets.only(right: 20), + child: Text(text, style: style)), + ); + } + + Map svMap = { + "BEIDOU": "BSV", + "GLONASS": "ESV", + "GPS": "GSV", + "GALILEO": "RSV", + }; + Widget bottomTitles(double index, TitleMeta meta) { + String text = ""; + int intValue = (index - 1).ceil(); + if (svData.isNotEmpty) { + var data = svData[intValue]; + String? name = svMap[data.name]; + if (name != null) { + text = "${name.substring(0, 1)}${data.sn}"; + } + } + + return SideTitleWidget( + axisSide: meta.axisSide, + space: 10, //margin top + + child: Text( + text, + style: const TextStyle( + color: Color(0xff7589a2), + fontWeight: FontWeight.bold, + decoration: TextDecoration.none, + fontSize: 14, + ), + textAlign: TextAlign.left, + ), + ); + } + + final double width = 15; + final Color leftBarColor = const Color.fromARGB(95, 101, 98, 98); + final Color rightBarColor = const Color.fromRGBO(33, 150, 243, 1); + + BarChartGroupData makeGroupData(int x, List listy, + [Color color = const Color.fromRGBO(33, 150, 243, 1)]) { + List list = []; + for (var i = 0; i < listy.length; i++) { + list.add(BarChartRodData( + toY: listy[i].toDouble(), + color: color.withOpacity((i + 1) * .2), + borderRadius: const BorderRadius.all( + Radius.circular(2), + ), + width: width, + )); + } + return BarChartGroupData( + barsSpace: 0, x: x, showingTooltipIndicators: [], barRods: list); } } diff --git a/lib/quality/signalQuality_page.dart b/lib/quality/signalQuality_page.dart index ce9ff4e..e5b1e8c 100644 --- a/lib/quality/signalQuality_page.dart +++ b/lib/quality/signalQuality_page.dart @@ -63,7 +63,7 @@ class _SignalQualityState extends State { ), Expanded( child: Center( - child: ChartPart(groupColors: groupColors), // 将组颜色传递给 ChartPart + child: ChartPart(: groupColors), // 将组颜色传递给 ChartPart ), ), ], diff --git a/lib/quality/singlebutton.dart b/lib/quality/singlebutton.dart index 295e661..d738b49 100644 --- a/lib/quality/singlebutton.dart +++ b/lib/quality/singlebutton.dart @@ -3,11 +3,11 @@ import 'package:get/get.dart'; class SingleButton extends StatefulWidget { // final controller = Get.find(tag: 'gnss'); - final List colors; + final List signalColorList; final Function(Color) onSelectionChanged; SingleButton({ - required this.colors, + required this.signalColorList, required this.onSelectionChanged, }); @@ -16,17 +16,17 @@ class SingleButton extends StatefulWidget { } class _SingleButtonState extends State { - late Color selectedColor; + late Color QselectedSignal; @override void initState() { super.initState(); - selectedColor = widget.colors[0]; // 默认选中第一个颜色 + QselectedSignal = widget.signalColorList[0]; // 默认选中第一个颜色 } void updateSelection(Color color) { setState(() { - selectedColor = color; + QselectedSignal = color; widget.onSelectionChanged(color); // 通知父组件 }); } @@ -35,23 +35,23 @@ class _SingleButtonState extends State { Widget build(BuildContext context) { return Row( mainAxisAlignment: MainAxisAlignment.center, - children: List.generate(widget.colors.length, (index) { + children: List.generate(widget.signalColorList.length, (index) { return Row( mainAxisSize: MainAxisSize.min, children: [ Radio( - value: widget.colors[index], - groupValue: selectedColor, + value: widget.signalColorList[index], + groupValue: QselectedSignal, onChanged: (Color? value) { if (value != null) { updateSelection(value); } }, - activeColor: widget.colors[index], + activeColor: widget.signalColorList[index], ), Text( - colorToString(widget.colors[index]), - style: TextStyle(color: widget.colors[index]), + colorToString(widget.signalColorList[index]), + style: TextStyle(color: widget.signalColorList[index]), ), ], ); @@ -60,10 +60,11 @@ class _SingleButtonState extends State { } String colorToString(Color color) { - if (color == Colors.blue) return 'GPS'; - if (color == Colors.red) return 'BDS'; - if (color == Colors.green) return 'GLO'; - if (color == Colors.orange) return 'ALS'; + if (color == Color.fromARGB(255, 255, 0, 0)) return 'GPS(G)'; + if (color == Color.fromARGB(255, 0, 255, 0)) return 'GLONASS(R)'; + if (color == Color.fromARGB(255, 0, 0, 255)) return 'GALILEO(E)'; + if (color == Color.fromARGB(255, 255, 255, 0)) return 'BEIDOU(B)'; + if (color == Color.fromARGB(255, 0, 255, 255)) return 'QZSS(Q)'; return 'Unknown Color'; } } diff --git a/lib/sky/mulbutton.dart b/lib/sky/mulbutton.dart index 5ff7de3..936235a 100644 --- a/lib/sky/mulbutton.dart +++ b/lib/sky/mulbutton.dart @@ -2,12 +2,11 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; class MulButton extends StatefulWidget { - // final controller = Get.find(tag: 'gnss'); - final List colors; + final List signalColorList; final Function(int, bool) onSelectionChanged; MulButton({ - required this.colors, + required this.signalColorList, required this.onSelectionChanged, }); @@ -16,17 +15,18 @@ class MulButton extends StatefulWidget { } class _MulButtonState extends State { - late List selectedColors; + late List selectedSignal; @override void initState() { super.initState(); - selectedColors = List.generate(widget.colors.length, (index) => false); + selectedSignal = + List.generate(widget.signalColorList.length, (index) => false); } void updateSelection(int index, bool value) { setState(() { - selectedColors[index] = value; + selectedSignal[index] = value; // List selected = []; // for (int i = 0; i < selectedColors.length; i++) { // if (selectedColors[i]) { @@ -43,25 +43,25 @@ class _MulButtonState extends State { spacing: 8.0, // 水平间距 runSpacing: 4.0, // 垂直间距 alignment: WrapAlignment.start, - children: List.generate(widget.colors.length, (index) { + children: List.generate(widget.signalColorList.length, (index) { return Row( mainAxisSize: MainAxisSize.min, children: [ Checkbox( - value: selectedColors[index], + value: selectedSignal[index], onChanged: (bool? value) { if (value != null) { updateSelection(index, value); } }, - activeColor: widget.colors[index], + activeColor: widget.signalColorList[index], shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(4), // 设置边角 ), ), Text( - colorToString(widget.colors[index]), - style: TextStyle(color: widget.colors[index]), + colorToString(widget.signalColorList[index]), + style: TextStyle(color: widget.signalColorList[index]), ), ], ); @@ -70,10 +70,11 @@ class _MulButtonState extends State { } String colorToString(Color color) { - if (color == Colors.blue) return 'GPS'; - if (color == Colors.red) return 'BDS'; - if (color == Colors.green) return 'GLO'; - if (color == Colors.orange) return 'ALS'; + if (color == Color.fromARGB(255, 255, 0, 0)) return 'GPS(G)'; + if (color == Color.fromARGB(255, 0, 255, 0)) return 'GLONASS(R)'; + if (color == Color.fromARGB(255, 0, 0, 255)) return 'GALILEO(E)'; + if (color == Color.fromARGB(255, 255, 255, 0)) return 'BEIDOU(B)'; + if (color == Color.fromARGB(255, 0, 255, 255)) return 'QZSS(Q)'; return 'Unknown Color'; } } diff --git a/lib/sky/sky_info.dart b/lib/sky/sky_info.dart index 9edbc28..dd8be90 100644 --- a/lib/sky/sky_info.dart +++ b/lib/sky/sky_info.dart @@ -4,6 +4,7 @@ import 'package:intl/intl.dart'; import 'package:gnssview/sky/device_type.dart'; import '../Controller/gnss_controller.dart'; import 'mulbutton.dart'; +import 'sky_plot.dart'; class SkyInfo extends StatelessWidget { late final GnssController controller; @@ -56,11 +57,12 @@ class SkyInfo extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ MulButton( - colors: const [ - Colors.blue, - Colors.red, - Colors.green, - Colors.orange + signalColorList: const [ + Color.fromARGB(255, 255, 0, 0), + Color.fromARGB(255, 0, 255, 0), + Color.fromARGB(255, 0, 0, 255), + Color.fromARGB(255, 255, 255, 0), + Color.fromARGB(255, 0, 255, 255) ], onSelectionChanged: (int index, bool value) { controller.selectedSignal[index] = value; @@ -120,3 +122,35 @@ class FixedWidthText extends StatelessWidget { ); } } + +class SkyInfoPlotPage extends StatelessWidget { + + @override + Widget build(BuildContext context) { + final orientation = MediaQuery.of(context).orientation; + bool isPortrait = orientation == Orientation.portrait; + return isPortrait + ? Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: SkyInfo(), + ), + Expanded( + child: SkyPlotPage(), + ), + ], + ) + : Row( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: SkyInfo(), + ), + Expanded( + child: SkyPlotPage(), + ), + ], + ); + } +} diff --git a/lib/sky/sky_plot.dart b/lib/sky/sky_plot.dart index eec7008..3199795 100644 --- a/lib/sky/sky_plot.dart +++ b/lib/sky/sky_plot.dart @@ -11,6 +11,7 @@ import 'package:gnss/gnss.dart'; import '../Controller/gnss_controller.dart'; const signalNameList = ["GPS", "GLONASS", "GALILEO", "BEIDOU", "QZSS"]; +const signalPrefixList = ["G", "R", "E", "B", "Q"]; const List signalColorList = [ Color.fromARGB(255, 255, 0, 0), Color.fromARGB(255, 0, 255, 0), @@ -18,7 +19,6 @@ const List signalColorList = [ Color.fromARGB(255, 255, 255, 0), Color.fromARGB(255, 0, 255, 255) ]; - // class DrawSkyPlot { // double width = 500; // double height = 500; @@ -209,9 +209,9 @@ class SkyPlotPage extends StatelessWidget { @override Widget build(BuildContext context) { bool isDarkMode = Theme.of(context).brightness == Brightness.dark; - + final size = MediaQuery.of(context).size; return Obx(() { - controller.singnalUpdate; + controller.singnalUpdate.value; final signalGNSS = [ controller.signalData?.GPS, controller.signalData?.GLO, @@ -219,8 +219,12 @@ class SkyPlotPage extends StatelessWidget { controller.signalData?.BDS, controller.signalData?.QZSS, ]; - return CustomPaint( - painter: SkyPlotPainter(signalGNSS, controller.selectedSignal), + return SizedBox( + width: size.width, + height: size.height, + child: CustomPaint( + painter: SkyPlotPainter(signalGNSS, controller.selectedSignal), + ), ); }); } @@ -271,13 +275,13 @@ class SkyPlotPage extends StatelessWidget { class SkyPlotPainter extends CustomPainter { final List?> signalGNSS; final List selectedSignal; - final satelliteRadius = 20.0; + final satelliteRadius = 12.0; SkyPlotPainter(this.signalGNSS, this.selectedSignal); @override void paint(Canvas canvas, Size size) { final paint = Paint() - ..color = Colors.blue + ..color = const ui.Color.fromARGB(255, 0, 0, 0) ..style = PaintingStyle.stroke; final double radius = min(size.width / 2, size.height / 2); @@ -306,7 +310,7 @@ class SkyPlotPainter extends CustomPainter { continue; } satellitePaint.color = signalColorList[i]; - drawGnssSignal(canvas, center, radius, signalData, satellitePaint); + drawGnssSignal(canvas, center, radius, signalData, satellitePaint, i); } } @@ -316,21 +320,32 @@ class SkyPlotPainter extends CustomPainter { double radius, List signals, Paint paint, + int index, ) { for (final signal in signals) { final int el = signal.elevation; - final double az = signal.azimuth * pi / 180; + + final double az = (360 - signal.azimuth + 90) * pi / 180; final double r = radius * (90 - el) / 90; final double x = center.dx + r * cos(az); final double y = center.dy + r * sin(az); canvas.drawCircle(Offset(x, y), satelliteRadius, paint); + TextPainter( + text: TextSpan( + text: signalPrefixList[index] + signal.prn.toString().padLeft(2, '0'), + style: TextStyle(color: Colors.white, fontSize: 12), + ), + textDirection: TextDirection.ltr, + ) + ..layout() + ..paint(canvas, Offset(x - 9, y - 7)); } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) { - return false; + return true; } }