support negative values

This commit is contained in:
TAJAOUART Mounir 2023-02-10 17:16:00 +01:00
parent 73f91e110e
commit bf8c339b86
2 changed files with 163 additions and 57 deletions

View File

@ -34,7 +34,7 @@ class MyHomePage extends StatefulWidget {
class _MyHomePageState extends State<MyHomePage> { class _MyHomePageState extends State<MyHomePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final stream = getDataStream().asBroadcastStream(); final stream = positiveDataStream();
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
@ -42,9 +42,12 @@ class _MyHomePageState extends State<MyHomePage> {
), ),
body: SizedBox( body: SizedBox(
width: double.maxFinite, width: double.maxFinite,
child: SingleChildScrollView(
child: Column( child: Column(
children: [ children: [
Expanded( SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.width * 0.8,
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: RealTimeGraph( child: RealTimeGraph(
@ -53,7 +56,9 @@ class _MyHomePageState extends State<MyHomePage> {
), ),
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
Expanded( SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.width * 0.8,
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: RealTimeGraph( child: RealTimeGraph(
@ -62,18 +67,41 @@ class _MyHomePageState extends State<MyHomePage> {
), ),
), ),
), ),
const SizedBox(height: 32),
SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.width * 0.8,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: RealTimeGraph(
stream: stream.map((value) => value - 150),
supportNegativeValuesDisplay: true,
),
),
),
const SizedBox(height: 32),
SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.width * 0.8,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: RealTimeGraph(
stream: stream.map((value) => value - 150),
supportNegativeValuesDisplay: true,
displayMode: ChartDisplay.points,
),
),
),
], ],
), ),
), ),
),
); );
} }
int count = 0; Stream<double> positiveDataStream() {
bool up = true;
Stream<double> getDataStream() {
return Stream.periodic(const Duration(milliseconds: 500), (_) { return Stream.periodic(const Duration(milliseconds: 500), (_) {
return Random().nextInt(500).toDouble(); return Random().nextInt(300).toDouble();
}); }).asBroadcastStream();
} }
} }

View File

@ -22,6 +22,7 @@ class RealTimeGraph extends StatefulWidget {
this.minValue = 0, this.minValue = 0,
this.speed = 1, this.speed = 1,
Key? key, Key? key,
this.supportNegativeValuesDisplay = false,
}) : super(key: key); }) : super(key: key);
// Callback to build custom Y-axis text. // Callback to build custom Y-axis text.
@ -36,6 +37,10 @@ class RealTimeGraph extends StatefulWidget {
// Flag to display the X-Y-axis lines or not. // Flag to display the X-Y-axis lines or not.
final bool displayYAxisLines; final bool displayYAxisLines;
// Flag to display the x-Axis in the middle of the chart
// And support the display of negative values < 0
final bool supportNegativeValuesDisplay;
// The stream to listen to for new data. // The stream to listen to for new data.
final Stream<double> stream; final Stream<double> stream;
@ -110,11 +115,43 @@ class RealTimeGraphState extends State<RealTimeGraph>
// Maximum value of the y-axis of the graph // Maximum value of the y-axis of the graph
double get maxValue { double get maxValue {
return _data.isEmpty ? 0 : _data.map((point) => point.y).reduce(max); if (_data.isEmpty) {
return 0;
}
final maxValue = _data.map((point) => point.y).reduce(max);
final minValue = _data.map((point) => point.y).reduce(min);
if (widget.supportNegativeValuesDisplay) {
if (maxValue > minValue.abs()) {
return maxValue;
} else {
return minValue.abs();
}
}
return maxValue;
} }
// Minimum value of the y-axis of the graph // Minimum value of the y-axis of the graph
double get minValue => widget.minValue; double get minValue {
if (_data.isEmpty) {
return 0;
}
final maxValue = _data.map((point) => point.y).reduce(max);
final minValue = _data.map((point) => point.y).reduce(min);
if (widget.supportNegativeValuesDisplay) {
if (maxValue > minValue.abs()) {
return maxValue * -1;
} else {
return minValue;
}
}
return widget.minValue;
}
// Median value of the y-axis of the graph // Median value of the y-axis of the graph
double get medianValue => (maxValue + minValue) / 2; double get medianValue => (maxValue + minValue) / 2;
@ -145,10 +182,9 @@ class RealTimeGraphState extends State<RealTimeGraph>
height: double.maxFinite, height: double.maxFinite,
), ),
Expanded( Expanded(
child: Column( child: Stack(
mainAxisSize: MainAxisSize.max,
children: [ children: [
Expanded( Positioned.fill(
child: LayoutBuilder( child: LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
if (!constraints.maxWidth.isFinite || if (!constraints.maxWidth.isFinite ||
@ -176,11 +212,15 @@ class RealTimeGraphState extends State<RealTimeGraph>
pointsSpacing: widget.pointsSpacing, pointsSpacing: widget.pointsSpacing,
graphStroke: widget.graphStroke, graphStroke: widget.graphStroke,
color: widget.graphColor, color: widget.graphColor,
supportNegativeValuesDisplay:
widget.supportNegativeValuesDisplay,
) )
: _LineGraphPainter( : _LineGraphPainter(
data: _data, data: _data,
graphStroke: widget.graphStroke, graphStroke: widget.graphStroke,
color: widget.graphColor, color: widget.graphColor,
supportNegativeValuesDisplay:
widget.supportNegativeValuesDisplay,
), ),
), ),
), ),
@ -190,10 +230,15 @@ class RealTimeGraphState extends State<RealTimeGraph>
), ),
), ),
if (widget.displayYAxisLines) if (widget.displayYAxisLines)
Container( Align(
alignment: widget.supportNegativeValuesDisplay
? Alignment.center
: Alignment.bottomCenter,
child: Container(
color: widget.axisColor, color: widget.axisColor,
height: widget.axisStroke, height: widget.axisStroke,
width: double.maxFinite, width: double.maxFinite,
),
) )
], ],
), ),
@ -229,6 +274,7 @@ class _PointGraphPainter extends CustomPainter {
required this.pointsSpacing, required this.pointsSpacing,
required this.graphStroke, required this.graphStroke,
required this.color, required this.color,
required this.supportNegativeValuesDisplay,
}); });
// List of data points to be plotted on the graph // List of data points to be plotted on the graph
@ -243,6 +289,9 @@ class _PointGraphPainter extends CustomPainter {
// Color of the graph // Color of the graph
final Color color; final Color color;
// Whether to support display of negative values on the graph
final bool supportNegativeValuesDisplay;
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
// Paint object used to draw the graph // Paint object used to draw the graph
@ -253,8 +302,24 @@ class _PointGraphPainter extends CustomPainter {
// If the data is not empty, calculate the maximum y value and the y scaling factor // If the data is not empty, calculate the maximum y value and the y scaling factor
if (data.isNotEmpty) { if (data.isNotEmpty) {
double maxY = data.map((point) => point.y).reduce(max); // Calculate the maximum and minimum y values in the data
double yScale = (maxY > size.height) ? (size.height / maxY) : 1; final maxY = data.map((point) => point.y).reduce(max);
final minY = data.map((point) => point.y).reduce(min);
// Calculate the scaling factor for the y values
double yScale = 1;
final yTranslation =
supportNegativeValuesDisplay ? size.height / 2 : size.height;
if (maxY.abs() > yTranslation) {
yScale = yTranslation / maxY.abs();
}
if (minY.abs() > yTranslation) {
final scale = yTranslation / minY.abs();
if (maxY.abs() < minY.abs()) {
yScale = scale;
}
}
// Iterate over the data points and draw them on the canvas // Iterate over the data points and draw them on the canvas
for (int i = 0; i < data.length - 1; i++) { for (int i = 0; i < data.length - 1; i++) {
@ -265,11 +330,10 @@ class _PointGraphPainter extends CustomPainter {
double yDiff = (y2 - y1).abs(); double yDiff = (y2 - y1).abs();
double xDiff = (x2 - x1).abs(); double xDiff = (x2 - x1).abs();
final distance = sqrt(pow(xDiff, 2) + pow(yDiff, 2));
// If the difference in y values or x values is large, add intermediate points // If the difference in y values or x values is large, add intermediate points
if (yDiff >= pointsSpacing || xDiff >= pointsSpacing) { if (distance >= pointsSpacing) {
int numOfIntermediatePoints = yDiff >= pointsSpacing int numOfIntermediatePoints = (distance / pointsSpacing).round();
? (yDiff / pointsSpacing).round()
: (xDiff / pointsSpacing).round();
double yInterval = (y2 - y1) / numOfIntermediatePoints; double yInterval = (y2 - y1) / numOfIntermediatePoints;
double xInterval = (x2 - x1) / numOfIntermediatePoints; double xInterval = (x2 - x1) / numOfIntermediatePoints;
for (int j = 0; j <= numOfIntermediatePoints; j++) { for (int j = 0; j <= numOfIntermediatePoints; j++) {
@ -278,7 +342,7 @@ class _PointGraphPainter extends CustomPainter {
if (intermediateX.isFinite && intermediateY.isFinite) { if (intermediateX.isFinite && intermediateY.isFinite) {
// Draw an intermediate point if it is within the canvas bounds // Draw an intermediate point if it is within the canvas bounds
canvas.drawCircle( canvas.drawCircle(
Offset(intermediateX, size.height - intermediateY), Offset(intermediateX, yTranslation - intermediateY),
sqrt(graphStroke), sqrt(graphStroke),
paint, paint,
); );
@ -287,7 +351,7 @@ class _PointGraphPainter extends CustomPainter {
} }
// Draw the data point // Draw the data point
canvas.drawCircle( canvas.drawCircle(
Offset(x1, size.height - y1), Offset(x1, yTranslation - y1),
sqrt(graphStroke), sqrt(graphStroke),
paint, paint,
); );
@ -304,6 +368,7 @@ class _LineGraphPainter extends CustomPainter {
required this.data, required this.data,
required this.graphStroke, required this.graphStroke,
required this.color, required this.color,
this.supportNegativeValuesDisplay = false,
}); });
// The data to be plotted in the graph // The data to be plotted in the graph
@ -315,6 +380,9 @@ class _LineGraphPainter extends CustomPainter {
// The color of the graph // The color of the graph
final Color color; final Color color;
// Whether to support display of negative values on the graph
final bool supportNegativeValuesDisplay;
@override @override
void paint(Canvas canvas, Size size) { void paint(Canvas canvas, Size size) {
final paint = Paint() final paint = Paint()
@ -325,28 +393,38 @@ class _LineGraphPainter extends CustomPainter {
// A path object to store the graph's lines // A path object to store the graph's lines
Path path = Path(); Path path = Path();
// Find the maximum y value in the data if (data.isNotEmpty) {
double maxY = 0; // Calculate the maximum and minimum y values in the data
final maxY = data.map((point) => point.y).reduce(max);
final minY = data.map((point) => point.y).reduce(min);
// Calculate the scaling factor for the y values // Calculate the scaling factor for the y values
double yScale = 1; double yScale = 1;
final yTranslation =
supportNegativeValuesDisplay ? size.height / 2 : size.height;
if (maxY.abs() > yTranslation) {
yScale = yTranslation / maxY.abs();
}
if (minY.abs() > yTranslation) {
final scale = yTranslation / minY.abs();
if (maxY.abs() < minY.abs()) {
yScale = scale;
}
}
// 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;
// Start the path at the first data point // Start the path at the first data point
path.moveTo( path.moveTo(
data.first.x + size.width, data.first.x + size.width,
(size.height - data.first.y * yScale), yTranslation - (data.first.y * yScale),
); );
}
// Plot the lines between each subsequent data point // Plot the lines between each subsequent data point
for (int i = 0; i < data.length - 1; i++) { for (int i = 0; i < data.length - 1; i++) {
double y = data[i + 1].y * yScale; final y = data[i + 1].y * yScale;
double x = data[i + 1].x + size.width; final x = data[i + 1].x + size.width;
path.lineTo(x, size.height - y); path.lineTo(x, yTranslation - y);
}
} }
// Draw the path on the canvas // Draw the path on the canvas