blob: f0f49b83e68cdf49fd7a7c6a60761321dbc22621 [file] [log] [blame]
// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math';
import 'dart:ui';
import 'package:flutter/material.dart';
// Builds graph to visualize a list/stream of data.
class StatusGraphVisualizer extends StatelessWidget {
// Determines style of text in visualiation.
final TextStyle textStyle;
// Determines alignment of text/graph in visualization.
final MainAxisAlignment axisAlignment;
// Determines paint style used to draw graph.
final Paint drawStyle;
// Model to manage data for StatusGraphVisualizer.
final StatusGraphVisualizerModel model;
const StatusGraphVisualizer({
@required this.model,
@required this.textStyle,
@required this.axisAlignment,
@required this.drawStyle,
});
@override
Widget build(BuildContext context) {
if (model.graphFirst)
return AnimatedBuilder(
animation: model,
builder: (BuildContext context, Widget child) {
return _buildGraphLeft(context);
});
return AnimatedBuilder(
animation: model,
builder: (BuildContext context, Widget child) {
return _buildGraphRight(context);
});
}
Widget _buildGraphLeft(BuildContext context) {
return Row(
mainAxisAlignment: axisAlignment,
children: [
SizedBox(
height: model.graphHeight,
width: model.graphWidth,
child: _buildGraph(context)),
Text(
model.graphValue,
style: textStyle,
),
],
);
}
Widget _buildGraphRight(BuildContext context) {
return Row(
mainAxisAlignment: axisAlignment,
children: [
Text(
model.graphValue,
style: textStyle,
),
SizedBox(
height: model.graphHeight,
width: model.graphWidth,
child: _buildGraph(context)),
],
);
}
Widget _buildGraph(BuildContext context) {
return CustomPaint(
painter: _StatusGraphPainter(
data: model._graphDataList,
height: model.graphHeight,
width: model.graphWidth,
min: model.graphMin,
max: model.graphMax,
drawStyle: drawStyle,
borderActive: model.borderActive,
fillActive: model.fillActive),
);
}
}
class _StatusGraphPainter extends CustomPainter {
List<double> data;
double height;
double width;
double borderHeight;
double borderWidth;
double min;
double max;
double xFactor;
double yFactor;
bool borderActive;
bool fillActive;
Path path = Path();
Offset start;
List<Offset> points = <Offset>[];
List<Offset> border = <Offset>[];
Paint drawStyle;
_StatusGraphPainter({
this.data,
this.height,
this.width,
this.min,
this.max,
this.drawStyle,
this.borderActive,
this.fillActive,
});
@override
void paint(Canvas canvas, Size size) {
xFactor = _getXFactor(data.length.toDouble(), width);
yFactor = _getYFactor(min, max, height);
borderWidth = width;
borderHeight = height;
start = Offset(0, height);
for (int index = 0; index < data.length; index++) {
double x = index.toDouble() * xFactor;
double y = height - data[index] * yFactor;
points.add(Offset(x, y));
}
if (borderActive) {
border
..add(Offset(0, 0))
..add(Offset(0, borderHeight))
..add(Offset(borderWidth, borderHeight))
..add(Offset(borderWidth, 0))
..add(Offset(0, 0));
canvas.drawPoints(PointMode.polygon, border, drawStyle);
}
if (fillActive) {
Path fillPath = Path()
..addPath(path, Offset.zero)
..moveTo(start.dx, start.dy);
for (int index = 0; index < data.length; index++) {
double x = index.toDouble() * xFactor;
double y = height - data[index] * yFactor;
fillPath.lineTo(x, y);
}
fillPath
..lineTo(width, height)
..close();
canvas.drawPath(fillPath, drawStyle);
}
canvas.drawPoints(PointMode.polygon, points, drawStyle);
}
@override
bool shouldRepaint(_StatusGraphPainter prev) {
return data != prev.data ||
height != prev.height ||
width != prev.width ||
min != prev.min ||
max != prev.max ||
drawStyle != prev.drawStyle ||
borderActive != borderActive ||
fillActive != prev.fillActive;
}
double _getXFactor(double totalDataPoints, double graphWidth) =>
(graphWidth / (totalDataPoints - 1));
double _getYFactor(double min, double max, double height) =>
(height / (max - min));
}
class StatusGraphVisualizerModel extends ChangeNotifier {
// Descriptive text displayed on the side of the graph visualization.
String _graphValue;
// Data to be represented in graph.
double _graphData;
// Height of graph widget.
final double _graphHeight;
// Width of graph widget.
final double _graphWidth;
// Min data value found within data being plotted.
final double _graphMin;
// Max data value found within data being plotted.
final double _graphMax;
// Determines if graph visualization is first in order in row.
final bool _graphFirst;
// If true, draws border around graph.
final bool _borderActive;
// Determines if graph is filled underneath.
final bool _fillActive;
final List<double> _graphDataList = List.filled(50, 0);
StatusGraphVisualizerModel({
String graphValue = 'loading...',
double graphData = 1,
double graphHeight = 14,
double graphWidth = 60,
double graphMin = 0,
double graphMax = 2,
bool graphFirst = true,
bool borderActive = true,
bool fillActive = true,
}) : _graphValue = graphValue,
_graphData = graphData,
_graphHeight = graphHeight,
_graphWidth = graphWidth,
_graphMin = graphMin,
_graphMax = graphMax,
_graphFirst = graphFirst,
_borderActive = borderActive,
_fillActive = fillActive;
set graphData(double updatedGraphData) {
_graphData = updatedGraphData;
_updateGraph(_graphData);
notifyListeners();
}
set graphValue(String updatedGraphValue) {
_graphValue = updatedGraphValue;
notifyListeners();
}
String get graphValue => _graphValue;
double get graphData => _graphData;
double get graphHeight => _graphHeight;
double get graphWidth => _graphWidth;
double get graphMin => _graphMin;
double get graphMax => _graphMax;
bool get graphFirst => _graphFirst;
bool get borderActive => _borderActive;
bool get fillActive => _fillActive;
void _updateGraph(double newDataPoint) {
double newElem = Random().nextDouble() * 100;
for (int a = 0; a < _graphDataList.length - 1; a++) {
_graphDataList[a] = _graphDataList[a + 1];
}
_graphDataList[_graphDataList.length - 1] = newElem;
}
}