support negative values
This commit is contained in:
parent
73f91e110e
commit
bf8c339b86
@ -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,38 +42,66 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
),
|
),
|
||||||
body: SizedBox(
|
body: SizedBox(
|
||||||
width: double.maxFinite,
|
width: double.maxFinite,
|
||||||
child: Column(
|
child: SingleChildScrollView(
|
||||||
children: [
|
child: Column(
|
||||||
Expanded(
|
children: [
|
||||||
child: Padding(
|
SizedBox(
|
||||||
padding: const EdgeInsets.all(16.0),
|
width: MediaQuery.of(context).size.width,
|
||||||
child: RealTimeGraph(
|
height: MediaQuery.of(context).size.width * 0.8,
|
||||||
stream: stream,
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: RealTimeGraph(
|
||||||
|
stream: stream,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
const SizedBox(height: 32),
|
||||||
const SizedBox(height: 32),
|
SizedBox(
|
||||||
Expanded(
|
width: MediaQuery.of(context).size.width,
|
||||||
child: Padding(
|
height: MediaQuery.of(context).size.width * 0.8,
|
||||||
padding: const EdgeInsets.all(16.0),
|
child: Padding(
|
||||||
child: RealTimeGraph(
|
padding: const EdgeInsets.all(16.0),
|
||||||
stream: stream,
|
child: RealTimeGraph(
|
||||||
displayMode: ChartDisplay.points,
|
stream: stream,
|
||||||
|
displayMode: ChartDisplay.points,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
color: widget.axisColor,
|
alignment: widget.supportNegativeValuesDisplay
|
||||||
height: widget.axisStroke,
|
? Alignment.center
|
||||||
width: double.maxFinite,
|
: Alignment.bottomCenter,
|
||||||
|
child: Container(
|
||||||
|
color: widget.axisColor,
|
||||||
|
height: widget.axisStroke,
|
||||||
|
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
|
|
||||||
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) {
|
if (data.isNotEmpty) {
|
||||||
maxY = data.map((point) => point.y).reduce(max);
|
// Calculate the maximum and minimum y values in the data
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user