From 0cdb3db45ffa2e7b2b85890d32df5f331fb67b63 Mon Sep 17 00:00:00 2001 From: TAJAOUART Mounir Date: Thu, 9 Feb 2023 17:51:47 +0100 Subject: [PATCH] make graphs scalable to handle values greater than the heigh of the canvas --- example/lib/main.dart | 50 +++++++++++------------ lib/src/live_chart.dart | 87 +++++++++++++++++++++++++++-------------- 2 files changed, 84 insertions(+), 53 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index abf96c7..84a75f5 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -34,38 +34,40 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { @override Widget build(BuildContext context) { + final stream = getDataStream().asBroadcastStream(); + return Scaffold( backgroundColor: Colors.black, appBar: AppBar( title: Text(widget.title), ), - body: Column( - children: [ - Flexible( - child: Container( - color: Colors.red, - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.width, - child: RealTimeGraph( - stream: getDataStream(), + body: SizedBox( + width: double.maxFinite, + child: Column( + children: [ + Expanded( + child: Container( + color: Colors.green, + child: RealTimeGraph( + stream: stream, + ), ), ), - ), - const SizedBox( - height: 32, - ), - Flexible( - child: Container( - color: Colors.red, - width: MediaQuery.of(context).size.width, - height: MediaQuery.of(context).size.width, - child: RealTimeGraph( - stream: getDataStream(), - displayMode: ChartDisplay.points, + const SizedBox( + height: 32, + child: Divider(color: Colors.grey,), + ), + Expanded( + child: Container( + color: Colors.green, + child: RealTimeGraph( + stream: stream, + displayMode: ChartDisplay.points, + ), ), ), - ), - ], + ], + ), ), ); } @@ -75,7 +77,7 @@ class _MyHomePageState extends State { Stream getDataStream() { return Stream.periodic(const Duration(milliseconds: 500), (_) { - return Random().nextInt(300).toDouble(); + return Random().nextInt(500).toDouble(); }); } } diff --git a/lib/src/live_chart.dart b/lib/src/live_chart.dart index bf6050d..2864d00 100644 --- a/lib/src/live_chart.dart +++ b/lib/src/live_chart.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import 'dart:ui'; +import 'dart:math'; import 'package:flutter/material.dart'; @@ -73,9 +73,9 @@ class RealTimeGraphState extends State width: constraints.maxHeight, child: CustomPaint( painter: widget.displayMode == ChartDisplay.points - ? _PointsGraphPainter( + ? _PointGraphPainter( data: _data, - pointsSpacing: widget.pointsSpacing, + pointSpacing: 3, ) : _LineGraphPainter( data: _data, @@ -100,47 +100,64 @@ class RealTimeGraphState extends State } } -class _PointsGraphPainter extends CustomPainter { +class _PointGraphPainter extends CustomPainter { final List> data; + final double pointSpacing; - _PointsGraphPainter({ - required this.data, - required this.pointsSpacing, - }); - - final double pointsSpacing; + _PointGraphPainter({required this.data, required this.pointSpacing}); @override void paint(Canvas canvas, Size size) { final paint = Paint() - ..style = PaintingStyle.stroke + ..style = PaintingStyle.fill ..strokeWidth = 1.5 ..color = Colors.white; + // Find the maximum y value in the data + double maxY = 0; + + // Calculate the scaling factor for the y values + double yScale = 1; + // Iterate over the data points and add intermediate points if necessary + if (data.isNotEmpty) { + maxY = data.map((point) => point.y).reduce(max); + yScale = (maxY > size.height) ? (size.height / maxY) : 1; + } + for (int i = 0; i < data.length - 1; i++) { - final y1 = size.height - data[i].y; - final x1 = data[i].x + size.width; - final y2 = size.height - data[i + 1].y; - final x2 = data[i + 1].x + size.width; - final yDiff = (y2 - y1).abs(); + double y1 = data[i].y * yScale; + double x1 = data[i].x + size.width; + double y2 = data[i + 1].y * yScale; + double x2 = data[i + 1].x + size.width; + double yDiff = (y2 - y1).abs(); // If the difference in y values is small, add intermediate points - if (yDiff >= pointsSpacing) { - int numOfIntermediatePoints = (yDiff / pointsSpacing).round(); - final yInterval = (y2 - y1) / numOfIntermediatePoints; - final xInterval = (x2 - x1) / numOfIntermediatePoints; + if (yDiff >= pointSpacing) { + int numOfIntermediatePoints = (yDiff / pointSpacing).round(); + double yInterval = (y2 - y1) / numOfIntermediatePoints; + double xInterval = (x2 - x1) / numOfIntermediatePoints; for (int j = 0; j <= numOfIntermediatePoints; j++) { - final intermediateY = y1 + yInterval * j; - final intermediateX = x1 + xInterval * j; - canvas.drawPoints( - PointMode.points, - [Offset(intermediateX, intermediateY)], + double intermediateY = y1 + yInterval * j; + double intermediateX = x1 + xInterval * j; + canvas.drawCircle( + Offset(intermediateX, size.height - intermediateY), + 1, paint, ); } } + canvas.drawCircle( + Offset(x1, size.height - y1), + 1, + paint, + ); } + canvas.drawCircle( + Offset(data.last.x + size.width, size.height - data.last.y * yScale), + 1, + paint, + ); } @override @@ -159,16 +176,28 @@ class _LineGraphPainter extends CustomPainter { ..strokeWidth = 1.5 ..color = Colors.white; Path path = Path(); + // Find the maximum y value in the data + double maxY = 0; + + // Calculate the scaling factor for the y values + double yScale = 1; // Iterate over the data points and add intermediate points if necessary if (data.isNotEmpty) { - path.moveTo(data.first.x + size.width, size.height - data.first.y); + maxY = data.map((point) => point.y).reduce(max); + yScale = (maxY > size.height) ? (size.height / maxY) : 1; + path.moveTo( + data.first.x + size.width, + (size.height - data.first.y * yScale), + ); } for (int i = 0; i < data.length - 1; i++) { - double y = size.height - data[i].y; - double x = data[i].x + size.width; - path.lineTo(x, y); + double y = data[i + 1].y * yScale; + double x = data[i + 1].x + size.width; + + path.lineTo(x, size.height - y); } + canvas.drawPath(path, paint); }