1- import 'dart:math' ;
2-
31import 'package:flutter/material.dart' ;
2+ import 'package:intl/intl.dart' ;
43import 'package:log_file_client/components/chart_series_selector.dart' ;
54import 'package:log_file_client/components/time_range_selector.dart' ;
65import 'package:log_file_client/utils/http_client.dart' ;
@@ -9,10 +8,12 @@ import 'package:syncfusion_flutter_charts/charts.dart';
98class GraphView extends StatefulWidget {
109 const GraphView ({
1110 required this .logData,
11+ this .now,
1212 super .key,
1313 });
1414
1515 final List <LogDataLine > logData;
16+ final DateTime ? now;
1617
1718 @override
1819 State <GraphView > createState () => _GraphViewState ();
@@ -21,19 +22,24 @@ class GraphView extends StatefulWidget {
2122class _GraphViewState extends State <GraphView > {
2223 bool _showPH = true ;
2324 bool _showTemp = true ;
25+ late final DateTime now;
2426 late final DateTimeRange avaliableTimeRange;
25- late List <int > _displayedTimeRange = [
26- max (widget.logData.length - 1400 , 0 ),
27- widget.logData.length,
28- ]; // 1 day
27+ late DateTimeRange displayedTimeRange;
28+ late List <int > _displayedTimeRangeIndices;
29+ late DateTimeIntervalType timeIntervalType;
30+ late double timeInterval;
31+ late DateFormat timeFormat;
2932
3033 @override
3134 void initState () {
3235 super .initState ();
36+ now = widget.now ?? DateTime .now ();
3337 avaliableTimeRange = DateTimeRange (
3438 start: widget.logData.first.time,
3539 end: widget.logData.last.time,
3640 );
41+ toggleTimeRange (2 , null );
42+ calculateTimeAxisFormat ();
3743 }
3844
3945 void toggleSeriesView (int index) {
@@ -47,32 +53,69 @@ class _GraphViewState extends State<GraphView> {
4753 }
4854
4955 void toggleTimeRange (int index, DateTimeRange ? customRange) {
50- final ranges = [
51- 360 , // 6 hours
52- 720 , // 12 hours
53- 1440 , // 1 day
54- 4320 , // 3 days
55- 10080 , // 7 days
56- 43200 , // 30 days
57- 9999999 , // Max
56+ // index: 0-5 are predefined ranges, 6 is max, 7 is custom
57+ final List < Duration > durations = [
58+ Duration ( hours: 6 ),
59+ Duration (hours : 12 ),
60+ Duration ( days: 1 ),
61+ Duration ( days: 3 ),
62+ Duration ( days: 7 ),
63+ Duration (days : 30 ),
5864 ];
5965
66+ late DateTimeRange timeRange;
67+ if (index < 6 ) {
68+ timeRange = DateTimeRange (
69+ start: now.subtract (durations[index]),
70+ end: now,
71+ );
72+ } else if (index == 6 ) {
73+ timeRange = avaliableTimeRange;
74+ } else {
75+ timeRange = customRange! ;
76+ }
77+
6078 setState (() {
61- if (index < 7 ) {
62- _displayedTimeRange = [
63- max (widget.logData.length - ranges[index], 0 ),
64- widget.logData.length,
65- ];
66- } else if (index == 7 ) {
67- final int endOffset =
68- avaliableTimeRange.end.difference (customRange! .end).inMinutes;
69- final int endIndex = widget.logData.length - endOffset;
70- final int startIndex = endIndex - customRange.duration.inMinutes;
71- _displayedTimeRange = [startIndex, endIndex];
72- }
79+ displayedTimeRange = timeRange;
80+ _displayedTimeRangeIndices = calculateTimeRange (timeRange);
81+ calculateTimeAxisFormat ();
7382 });
7483 }
7584
85+ List <int > calculateTimeRange (DateTimeRange range) {
86+ int startIndex = 0 ;
87+ int endIndex = widget.logData.length;
88+ for (int i = widget.logData.length - 1 ; i >= 0 ; i-- ) {
89+ if (widget.logData[i].time.compareTo (range.start) < 0 ) {
90+ startIndex = i + 1 ;
91+ break ;
92+ }
93+ }
94+ for (int i = widget.logData.length - 1 ; i >= 0 ; i-- ) {
95+ if (widget.logData[i].time.compareTo (range.end) > 0 ) {
96+ endIndex = i + 1 ;
97+ break ;
98+ }
99+ }
100+ return [startIndex, endIndex];
101+ }
102+
103+ void calculateTimeAxisFormat () {
104+ timeInterval = 1 ;
105+
106+ if (displayedTimeRange.duration <= Duration (hours: 24 )) {
107+ timeIntervalType = DateTimeIntervalType .hours;
108+ timeFormat = DateFormat ('h a' );
109+ } else if (displayedTimeRange.duration <= Duration (days: 3 )) {
110+ timeIntervalType = DateTimeIntervalType .days;
111+ timeFormat = DateFormat ('h a\n MMM d' );
112+ timeInterval = 0.5 ;
113+ } else {
114+ timeIntervalType = DateTimeIntervalType .days;
115+ timeFormat = DateFormat ('MMM d' );
116+ }
117+ }
118+
76119 @override
77120 Widget build (BuildContext context) {
78121 return Scaffold (
@@ -83,8 +126,8 @@ class _GraphViewState extends State<GraphView> {
83126 _topRow (widget.logData),
84127 _graph (
85128 widget.logData.sublist (
86- _displayedTimeRange [0 ],
87- _displayedTimeRange [1 ],
129+ _displayedTimeRangeIndices [0 ],
130+ _displayedTimeRangeIndices [1 ],
88131 ),
89132 ),
90133 ],
@@ -122,29 +165,30 @@ class _GraphViewState extends State<GraphView> {
122165 }
123166
124167 Widget _graph (List <LogDataLine > logData) {
168+ final List <Map <String , dynamic >> trackballData = [];
125169 final trackballBehavior = TrackballBehavior (
126170 enable: true ,
127171 tooltipDisplayMode: TrackballDisplayMode .groupAllPoints,
172+ activationMode: ActivationMode .singleTap,
128173 markerSettings: TrackballMarkerSettings (
129174 markerVisibility: TrackballVisibilityMode .visible,
130175 ),
131- tooltipSettings: InteractiveTooltip (
132- enable: true ,
133- format: 'series.name : point.y' ,
134- ),
135- activationMode: ActivationMode .singleTap,
136176 lineColor: Colors .grey.shade600,
137177 lineWidth: 1.5 ,
138178 lineDashArray: [2 , 2 ],
179+ // builder: _trackballBuilder,
139180 );
140181
141182 return Expanded (
142183 child: SfCartesianChart (
143184 backgroundColor: Colors .white,
144185 primaryXAxis: DateTimeAxis (
145186 title: AxisTitle (text: 'Time' ),
146- intervalType: DateTimeIntervalType .hours,
147- interval: 1 ,
187+ intervalType: timeIntervalType,
188+ interval: timeInterval,
189+ dateFormat: timeFormat,
190+ minimum: displayedTimeRange.start,
191+ maximum: displayedTimeRange.end,
148192 ),
149193 primaryYAxis: NumericAxis (
150194 name: 'pHAxis' ,
@@ -160,14 +204,52 @@ class _GraphViewState extends State<GraphView> {
160204 ),
161205 ],
162206 trackballBehavior: trackballBehavior,
207+ onTrackballPositionChanging: (TrackballArgs details) {
208+ // Clear previously stored data on each update.
209+ trackballData.clear ();
210+
211+ // Store the details for the current trackball position.
212+ if (details.chartPointInfo.series != null &&
213+ details.chartPointInfo.chartPoint != null ) {
214+ final String seriesName = details.chartPointInfo.series! .name;
215+ final DateTime date =
216+ details.chartPointInfo.chartPoint! .x as DateTime ;
217+ final double yValue =
218+ details.chartPointInfo.chartPoint! .y as double ;
219+
220+ // Add this series' details to the trackballData list.
221+ trackballData.add ({
222+ 'seriesName' : seriesName,
223+ 'yValue' : yValue,
224+ 'date' : date,
225+ });
226+
227+ // Construct the tooltip dynamically from the stored data.
228+ String tooltipText = '' ;
229+ String seriesText = '' ;
230+ for (final data in trackballData) {
231+ seriesText += '${data ['seriesName' ]} : ${data ['yValue' ]}' ;
232+ }
233+
234+ // Combine the series data into a compact format
235+ tooltipText += seriesText;
236+
237+ // Update the trackball tooltip information
238+ details.chartPointInfo.label = tooltipText;
239+ details.chartPointInfo.header =
240+ DateFormat ('MMM d hh:mm a' ).format (date);
241+ }
242+ },
163243 series: _chartSeries (logData),
164244 ),
165245 );
166246 }
167247
168248 List <CartesianSeries > _chartSeries (List <LogDataLine > logData) {
249+ final MarkerSettings markerSettings = MarkerSettings (height: 2 , width: 2 );
250+
169251 return < CartesianSeries > [
170- LineSeries <LogDataLine , DateTime >(
252+ ScatterSeries <LogDataLine , DateTime >(
171253 legendItemText: 'pH' ,
172254 name: 'pH' ,
173255 dataSource: logData,
@@ -176,8 +258,10 @@ class _GraphViewState extends State<GraphView> {
176258 color: _showPH ? Colors .green : Colors .transparent,
177259 yAxisName: 'pHAxis' ,
178260 animationDuration: 0 ,
261+ markerSettings: markerSettings,
262+ enableTrackball: _showPH,
179263 ),
180- LineSeries <LogDataLine , DateTime >(
264+ ScatterSeries <LogDataLine , DateTime >(
181265 legendItemText: 'pH setpoint' ,
182266 name: 'pH setpoint' ,
183267 dataSource: logData,
@@ -186,8 +270,10 @@ class _GraphViewState extends State<GraphView> {
186270 color: _showPH ? Colors .green.shade800 : Colors .transparent,
187271 yAxisName: 'pHAxis' ,
188272 animationDuration: 0 ,
273+ markerSettings: markerSettings,
274+ enableTrackball: _showPH,
189275 ),
190- LineSeries <LogDataLine , DateTime >(
276+ ScatterSeries <LogDataLine , DateTime >(
191277 legendItemText: 'temp' ,
192278 name: 'temp' ,
193279 dataSource: logData,
@@ -196,8 +282,10 @@ class _GraphViewState extends State<GraphView> {
196282 color: _showTemp ? Colors .blue : Colors .transparent,
197283 yAxisName: 'TemperatureAxis' ,
198284 animationDuration: 0 ,
285+ markerSettings: markerSettings,
286+ enableTrackball: _showTemp,
199287 ),
200- LineSeries <LogDataLine , DateTime >(
288+ ScatterSeries <LogDataLine , DateTime >(
201289 legendItemText: 'temp setpoint' ,
202290 name: 'temp setpoint' ,
203291 dataSource: logData,
@@ -206,6 +294,8 @@ class _GraphViewState extends State<GraphView> {
206294 color: _showTemp ? Colors .blue.shade800 : Colors .transparent,
207295 yAxisName: 'TemperatureAxis' ,
208296 animationDuration: 0 ,
297+ markerSettings: markerSettings,
298+ enableTrackball: _showTemp,
209299 ),
210300 ];
211301 }
0 commit comments