Skip to content

Instantly share code, notes, and snippets.

@Barttje
Last active October 15, 2020 18:47
Show Gist options
  • Save Barttje/0fa8454652d70ad9d41e7dcd779e53c1 to your computer and use it in GitHub Desktop.
Save Barttje/0fa8454652d70ad9d41e7dcd779e53c1 to your computer and use it in GitHub Desktop.
line chart
import 'package:flutter/material.dart';
void main() {
runApp(LineChartDemo());
}
class LineChartDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.green,
title: Text("Line chart animation demo"),
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: LineChart(),
),
),
);
}
}
class LineData {
Map<String, double> data = new Map();
String category;
Color color;
LineData(String category, Color color, List<double> values) {
this.data = createCumulativeData(values);
this.category = category;
this.color = color;
}
Map<String, double> createCumulativeData(List<double> values) {
Map<String, double> data = new Map();
int c = 0;
double sum = 0;
values.forEach((element) {
sum += element;
data.putIfAbsent(c.toString(), () => sum);
c++;
});
return data;
}
}
class LineChart extends StatelessWidget {
static List<double> hamilton = [0, 12, 25, 26, 25, 19, 25, 25, 7, 26, 15, 25];
static List<double> bottas = [0, 25, 18, 15, 0, 15, 16, 18, 10, 18, 26, 0];
static List<double> max = [0, 0, 15, 18, 19, 25, 18, 15, 0, 0, 18, 19];
final List<LineData> data = [
LineData("Hamilton", Colors.teal[700], hamilton),
LineData("Bottas", Colors.teal[300], bottas),
LineData("Verstappen", Colors.blue[900], max)
];
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: LineChartPainter(data, "Top Three Formula One"),
child: Container(width: double.infinity, height: 340),
);
}
}
class LineChartPainter extends CustomPainter {
final String title;
final List<LineData> lineData;
List<String> categories;
double marginLeft;
double marginTop;
double marginBottom;
double marginRight;
double maxValue = 250;
static double emptySpace = 5;
// title
static double titleTextScale = 1.5;
// axis
static double axisWidth = 2;
static double axisTextScale = 1;
// two main lines
Paint axis = Paint()
..strokeWidth = axisWidth
..color = Colors.grey;
// horizontal lines through the chart
Paint axisLight = Paint()
..strokeWidth = axisWidth
..color = Colors.grey.withOpacity(0.5);
// legend
static double legendSquareWidth = 10;
static double legendTextScale = 1;
LineChartPainter(this.lineData, this.title) {
// margin left side is based on largest value on the axis
marginLeft = createText(maxValue.toString(), 1).width + emptySpace;
// determine where to begin with , based on height of the title
marginTop = createText(title, titleTextScale).height + emptySpace;
// determine marginBottom on default text
marginBottom = createText("1", axisTextScale).height * 2 + emptySpace;
// determine marginRight based on the largest category name
marginRight = 0;
lineData.forEach((element) {
var width = createText(element.category, legendTextScale).width +
legendSquareWidth +
emptySpace;
if (width > marginRight) {
marginRight = width;
}
});
// set the categories, based on the entries of the first data
categories = lineData[0].data.keys.toList();
}
@override
void paint(Canvas canvas, Size size) {
drawTitle(canvas, size);
drawAxes(canvas, size);
drawLegend(canvas, size);
drawLines(size, canvas);
}
void drawLines(Size size, Canvas canvas) {
lineData.forEach((element) {
var points = element.data.entries.toList();
for (int i = 0; i < (points.length - 1); i++) {
var firstPoint = entryToPoint(points[i], size);
var goalPoint = entryToPoint(points[i + 1], size);
canvas.drawLine(firstPoint, goalPoint, getLinePaint(element));
canvas.drawCircle(firstPoint, 5, getLineDataColorPaint(element));
}
canvas.drawCircle(entryToPoint(points[points.length - 1], size), 5,
getLineDataColorPaint(element));
});
}
void drawLegend(Canvas canvas, Size size) {
double i = 0;
lineData.forEach((element) {
TextPainter tp = createText(element.category, legendTextScale);
tp.paint(
canvas,
new Offset(
size.width - marginRight + legendSquareWidth + 2 * emptySpace,
(i * tp.height + marginTop - tp.height / 2)));
var center = new Offset(
size.width - marginRight + legendSquareWidth + emptySpace,
(i * tp.height + marginTop));
canvas.drawRect(
Rect.fromCenter(
center: center,
width: legendSquareWidth,
height: legendSquareWidth),
getLineDataColorPaint(element));
i++;
});
}
void drawTitle(Canvas canvas, Size size) {
TextPainter tp = createText(title, titleTextScale);
tp.paint(canvas, new Offset(size.width / 2 - tp.width / 2, 0));
}
Paint getLinePaint(LineData lineData) {
return new Paint()
..strokeWidth = 4
..color = lineData.color.withOpacity(0.5);
}
Paint getLineDataColorPaint(LineData lineData) {
return new Paint()
..strokeWidth = 4
..color = lineData.color;
}
void drawAxes(Canvas canvas, Size size) {
// draw the horizontal line
canvas.drawLine(
Offset(marginLeft, size.height - marginTop),
Offset(size.width - marginRight, size.height - marginTop),
axis,
);
// draw the vertical line
canvas.drawLine(
Offset(marginLeft, size.height - marginTop),
Offset(marginLeft, marginTop),
axis,
);
// draw the categories on the horizontal axis
addCategoriesAsTextToHorizontalAxis(size, canvas);
// draw five sizes on the vertical axis and draw lighter vertical lines
addHorizontalLinesAndSizes(size, canvas);
}
void addHorizontalLinesAndSizes(Size size, Canvas canvas) {
for (int i = 1; i <= 5; i++) {
double y = chartHeight(size) - chartHeight(size) * (i / 5) + marginTop;
TextPainter tp = createText((maxValue / 5 * i).round().toString(), 1);
tp.paint(canvas,
new Offset(marginLeft - tp.width - emptySpace, y - emptySpace));
canvas.drawLine(
Offset(marginLeft, y),
Offset(size.width - marginRight, y),
axisLight,
);
}
}
void addCategoriesAsTextToHorizontalAxis(Size size, Canvas canvas) {
categories.forEach((entry) {
TextPainter tp = createText(entry, 1);
var x = chartWidth(size) *
categories.indexOf(entry) /
(categories.length - 1) +
marginLeft -
tp.width / 2;
tp.paint(
canvas, new Offset(x, chartHeight(size) + marginTop + tp.height / 2));
});
}
TextPainter createText(String key, double scale) {
TextSpan span =
new TextSpan(style: new TextStyle(color: Colors.grey[600]), text: key);
TextPainter tp = new TextPainter(
text: span,
textAlign: TextAlign.start,
textScaleFactor: scale,
textDirection: TextDirection.ltr);
tp.layout();
return tp;
}
Offset entryToPoint(MapEntry<String, double> entry, Size size) {
double x = chartWidth(size) *
categories.indexOf(entry.key) /
(categories.length - 1) +
marginLeft;
double y = chartHeight(size) -
chartHeight(size) * (entry.value / maxValue) +
marginTop;
return new Offset(x, y);
}
double chartHeight(Size size) => size.height - marginTop - marginBottom;
double chartWidth(Size size) => size.width - marginRight - marginLeft;
@override
bool shouldRepaint(LineChartPainter oldDelegate) => false;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment