I am currently working on a flutter app that will plot data from a BLE device in real-time on a line chart (where the data will be displayed in an oscilloscope style format). As a place holder for the live BLE data, I am using a timer callback to generate and plot a sine wave with the "charts_flutter" package. So far everything seems to be working. However, it seems like the chart rendering struggles to keep up when the update period is less than 500ms (for example, setting it to 200ms causes everything to freeze up). Before I start experimenting with different packages, I'd like to see if anyone has tips or suggestions to try to speed things up. It is possible that I'm just doing something wrong with state management etc. since I'm not that experienced with flutter. The code is pretty short so I've included both project files below. Thanks.
main.dart
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart';
import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart';
import 'models/graph_data_model.dart';
void main() => runApp(new GraphDataApp());
class GraphDataApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<GraphDataModel>(
create: (context) => GraphDataModel()),
],
child: MaterialApp(
title: 'Data Plotter',
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: '/',
routes: {
'/': (context) => MyApp(),
},
),
);
}
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Data Plotter")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GraphDataWidget(),
],
),
),
);
}
}
class GraphDataWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.all(5.0),
child: SizedBox(
height: 400.0,
child: Selector<GraphDataModel, Tuple2<GraphDataModel, double>>(
selector: (_, graphDataModel) {
return Tuple2(graphDataModel, graphDataModel.elapsedTime);
},
builder: (context, selectorTuple, child) {
GraphDataModel graphDataModel = selectorTuple.item1;
return LineChart(
graphDataModel.series.toList(),
animate: false,
domainAxis: NumericAxisSpec(
viewport: NumericExtents(graphDataModel.domainStart, graphDataModel.domainEnd))
);
},
),
),
);
}
}
graph_data_model.dart
import 'dart:async';
import 'dart:math';
import 'package:charts_flutter/flutter.dart';
import 'package:flutter/material.dart';
const UPDATE_INTERVAL = 500;
const MAX_TIME_INTERVAL = 10.0;
const MAX_DATA_POINTS = ((MAX_TIME_INTERVAL * 1000) / UPDATE_INTERVAL);
class GraphDataModel extends ChangeNotifier {
bool _running = false;
int _startTime;
double elapsedTime;
List<Series<PlotPoint, double>> series;
GraphDataModel() {
// initialize the data series
series = [
Series<PlotPoint, double>(
id: 'Sine',
colorFn: (_, __) => MaterialPalette.blue.shadeDefault,
domainFn: (PlotPoint point, _) => point.time,
measureFn: (PlotPoint point, _) => point.value,
data: <PlotPoint>[PlotPoint(0, 0)],
),
];
// initialize the start time and start the timer
_startTime = DateTime.now().millisecondsSinceEpoch;
Timer.periodic(Duration(milliseconds: UPDATE_INTERVAL), _timerCallback);
}
// getters
num get domainStart => _running ? (series.first.data.first.time) : 0;
num get domainEnd => _running ? (series.first.data.last.time) : 1;
void _timerCallback(Timer t) {
final currentTime = DateTime.now().millisecondsSinceEpoch;
// find the elapsed time in seconds
elapsedTime = (currentTime - _startTime).toDouble() / 1000.0;
// find the sine value based on the elapsed time
final yVal = sin((2.0 * pi * elapsedTime) / 10.0);
// add the latest data point to the series
series.first.data.add(PlotPoint(elapsedTime, yVal));
// remove the first element in the list to create oscilloscope scrolling effect
if(series.first.data.length > MAX_DATA_POINTS.toInt() || !_running) {
series.first.data.removeAt(0);
}
_running = true;
notifyListeners();
}
}
class PlotPoint {
final double time;
final double value;
PlotPoint(this.time, this.value);
}