preface

This article explains how to use Flutter, Google’s open source UI toolkit, to help developers efficiently build beautiful multi-platform applications with a code base that includes mobile, Web, desktop, and embedded platforms, to draw an animated bar chart. The result is shown below.

CustomPaint and CustomPainter are similar to the

element on the Web. CustomPaint provides a drawing area. CustomPainter has the specific drawing methods.

CustomPaint and CustomPainter

CustomPaint is the control that provides the canvas. It uses the incoming brush Painter to draw behind the Child control, and the foregroundPainter brush to draw before the Child control. The size property controls the size of the canvas. If a child control is defined, the size of the canvas is determined by the size of the child control. The size property is ignored.

class CustomPaint extends SingleChildRenderObjectWidget {
  const CustomPaint({
    Key key,
    this.painter,
    this.foregroundPainter,
 this.size = Size.zero,  this.isComplex = false. this.willChange = false. Widget child,  }) } Copy the code

CustomPainter is an abstract class that implements the control that draws graphics. To draw graphics on a canvas, you need to implement its Paint method. The paint method takes two arguments, Canvas Canvas and Size Size. The Size object represents the Size of the Canvas, and on the Canvas is the specific method of drawing the graph.

abstract class CustomPainter extends Listenable {
  void paint(Canvas canvas, Size size);

  bool shouldRepaint(covariant CustomPainter oldDelegate);
}
Copy the code

Canvas The main drawing methods of Canvas are as follows

The method name parameter The effect
drawColor Color color.BlendMode blendMode Paint color to canvas
drawLine Offset p1.Offset p2.Paint paint Draw a line between two points
drawPaint Paint paint Fill the canvas with [Paint]
drawRect Rect rect.Paint paint Draw a rectangle
drawRRect RRect rrect.Paint paint Draw a rectangle with rounded corners
drawOval Rect rect.Paint paint Draw the ellipse
drawCircle Offset c.double radius.Paint paint Draw a circular
drawArc Rect rect.double startAngle.double sweepAngle.bool useCenter.Paint paint Draw the arc
drawPath Path path.Paint paint Draw the path
drawImage Image image.Offset p.Paint paint The plot
drawPoints PointMode pointMode.List<Offset> points.Paint paint Draw multiple points

To draw a graph to a canvas you first create a custom brush inherited from CustomPainter. For example, to draw a rectangle you need to implement a RectanglePainter, and then apply it to the canvas CustomPaint.

class RectanglePainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // Define a rectangle
    final Rect rect = Rect.fromLTWH(50.0.50.0.100.0.100.0);
 // Specify the style to draw  final Paint paint = Paint()  ..color = Colors.orange  ..strokeWidth = 4.0  ..style = PaintingStyle.stroke  ..isAntiAlias = true;   // Use drawRect to draw a rectangle  canvas.drawRect(rect, paint);  }   @override  bool shouldRepaint(RectanglePainter oldDelegate) => false; }  class Rectangle extends StatelessWidget {  @override  Widget build(BuildContext context) {  return Center(  child: CustomPaint(  // Use RectanglePainter on canvas  painter: RectanglePainter(),  child: Container(  width: 300. height: 300. decoration: BoxDecoration(  border: Border.all(  width: 1.0. color: Colors.grey[300]. ),  ),  ),  ),  );  } } Copy the code

The effect is shown in figure

Draw a bar chart

The first step is to create a BarChart control to represent the BarChart. It has two construction parameters: one is data to receive chart data, and the other is xAxis to represent the horizontal axis of the chart.

class BarChart extends StatefulWidget {
  final List<double> data;
  final List<String> xAxis;

  const BarChart({
 @required this.data,  @required this.xAxis,  });   @override  _BarChartState createState() => _BarChartState(); }  class _BarChartState extends State<BarChart> with TickerProviderStateMixin {  @override  Widget build(BuildContext context) {  return Column(  mainAxisAlignment: MainAxisAlignment.center,  children: [  CustomPaint(  painter: BarChartPainter(  datas: widget.data,  xAxis: widget.xAxis,  ),  child: Container(width: 300, height: 300),  ), ]. );  } } Copy the code

Then create a custom brush barart Painter for drawing.

class BarChartPainter extends CustomPainter {
  final List<double> datas;
  final List<String> xAxis;

  BarChartPainter({
 @required this.xAxis,  @required this.datas,  });   @override  void paint(Canvas canvas, Size size) {  // TODO  }   @override  bool shouldRepaint(BarChartPainter oldDelegate) => true;   @override  bool shouldRebuildSemantics(BarChartPainter oldDelegate) => false; }  Copy the code

Draw coordinate axes

Define a _drawAxis method on BarChartPainter to draw the horizontal axis using a Path controlled by the top left, bottom left, and bottom right points.

void _drawAxis(Canvas canvas, Size size) {
  final double sw = size.width;
  final double sh = size.height;

  Use Paint to define the style of the path
 final Paint paint = Paint()  ..color = Colors.black87  ..style = PaintingStyle.stroke  ..strokeWidth = 1.0;   // Use Path to define the Path to draw from the top left to the bottom left of the canvas to the bottom right  final Path path = Path()  ..moveTo(0.0)  ..lineTo(0, sh)  ..lineTo(sw, sh);   // Use the drawPath method to draw a path  canvas.drawPath(path, paint); }  @override void paint(Canvas canvas, Size size) {  _drawAxis(canvas, size); } Copy the code

Results the following

Draw the logo

Define a _drawLabels method on BarChartPainter to draw the vertical labels.

void _drawLabels(Canvas canvas, Size size) {
  final double gap = 50.0;
  final double sh = size.height;
  final List<double> yAxisLabels = [];

 Paint paint = Paint()  ..color = Colors.black87  ..strokeWidth = 2.0;   // Draw one more identifier than the incoming data with 50.0 as the interval  for (int i = 0; i <= datas.length; i++) {  yAxisLabels.add(gap * i);  }   yAxisLabels.asMap().forEach(  (index, label) {  // The height of the logo is the height of the canvas minus the value of the logo  final double top = sh - label;  final rect = Rect.fromLTWH(0, top, 4.1);  final Offset textOffset = Offset(  0 - labelFontSize * 3. top - labelFontSize / 2. );   // Draw the line to the right of the Y-axis  canvas.drawRect(rect, paint);   // TextPainter is used to draw text, and finally paint is called  TextPainter(  text: TextSpan(  text: label.toStringAsFixed(0),  style: TextStyle(fontSize: labelFontSize, color: Colors.black87),  ),  textAlign: TextAlign.right,  textDirection: TextDirection.ltr,  textWidthBasis: TextWidthBasis.longestLine,  )  ..layout(minWidth: 0, maxWidth: 24)  ..paint(canvas, textOffset);  },  ); }  @override void paint(Canvas canvas, Size size) {  _drawAxis(canvas, size);  _drawLabels(canvas, size); }  Copy the code

Results the following

Draw data rectangle

Then define a _darwBars method to draw the specific rectangle and horizontal identifier.

List<Color> colors = [
  Color(0xff8e43e7),
  Color(0xffff4f81),
  Color(0xff1cc7d0),
  Color(0xff00aeff),
 Color(0xff3369e7),  Color(0xffb84592),  Color(0xff2dde98),  Color(0xffff6c5f),  Color(0xff003666),  Color(0xffffc168),  Color(0xff050f2c), ];  void _darwBars(Canvas canvas, Size size) {  final sh = size.height;  finalpaint = Paint().. style = PaintingStyle.fill;  for (int i = 0; i < datas.length; i++) {  // Each rectangle uses the colors in the preset colors array  paint.color = colors[i];  final double textFontSize = 14.0;  final double data = datas[i];  // The top edge of the rectangle is the height of the canvas minus the data value  final double top = sh - data;  // The left edge of the rectangle is the current index multiplied by the width of the rectangle plus the spacing between the rectangles  final double left = i * _barWidth + (i * _barGap) + _barGap;   // Use the rect.fromltwh method to create the rectangle to draw  final rect = Rect.fromLTWH(left, top, _barWidth, data);  // Use the drawRect method to draw a rectangle  canvas.drawRect(rect, paint);   final offset = Offset(  left + _barWidth / 2 - textFontSize * 1.2. top - textFontSize * 2. );  // Use TextPainter to draw the values on the rectangle  TextPainter(  text: TextSpan(  text: data.toStringAsFixed(1),  style: TextStyle(fontSize: textFontSize, color: paint.color),  ),  textAlign: TextAlign.center,  textDirection: TextDirection.ltr,  )  ..layout(  minWidth: 0. maxWidth: textFontSize * data.toString().length,  )  ..paint(canvas, offset);   final xData = xAxis[i];  final xOffset = Offset(left + _barWidth / 2 - textFontSize, sh + 12);  // Draw the horizontal axis identifier  TextPainter(  textAlign: TextAlign.center,  text: TextSpan(  text: '$xData'. style: TextStyle(fontSize: 12, color: Colors.black87),  ),  textDirection: TextDirection.ltr,  )  ..layout(  minWidth: 0. maxWidth: size.width,  )  ..paint(canvas, xOffset);  } }  @override void paint(Canvas canvas, Size size) {  _drawAxis(canvas, size);  _drawLabels(canvas, size);  _darwBars(canvas, size); } Copy the code

Results the following

Add motion animation

Finally, use an AnimationController in _BarChartState to create an animation of bar chart motion. For animation, see the article Flutter Animation from scratch.

class _BarChartState extends State<BarChart> with TickerProviderStateMixin {
  AnimationController _controller;
  final _animations = <double> [];
  @override
 void initState() {  super.initState();  double begin = 0.0;  List<double> datas = widget.data;  // Initialize the animation controller and call the forward method to start the animation  _controller = AnimationController(  vsync: this. duration: Duration(milliseconds: 3000), ).. forward();  for (int i = 0; i < datas.length; i++) {  final double end = datas[i];  // Use a Tween to create animated values for each rectangle  final Tween<double> tween = Tween(begin: begin, end: end);  // Initializes the values in the array  _animations.add(begin);   // Create a tween animation  Animation<double> animation = tween.animate(  CurvedAnimation(  parent: _controller,  curve: Curves.ease,  ),  );  _controller.addListener(() {  Update the values in the _animations array with setState  setState(() {  _animations[i] = animation.value;  });  });  }  }   @override  Widget build(BuildContext context) {  return Column(  mainAxisAlignment: MainAxisAlignment.center,  children: [  CustomPaint(  // Finally, pass the _animations array to BarChartPainter to animate  painter: BarChartPainter(  datas: _animations,  xAxis: widget.xAxis,  animation: _controller,  ),  child: Container(width: 300, height: 300),  ), ]. );  } }  Copy the code

At this point the entire histogram drawing is complete, the incoming data can be used πŸŽ‰πŸŽ‰πŸŽ‰

BarChart(
  data: [180.0.98.0.126.0.64.0.118.0].  xAxis: ['δΈ€ζœˆ'.'二月'.'march'.'in April'.'may'].);
Copy the code

Full code address: bar_chart.dart

conclusion

This article explains what CustomPaint and CustomPainter are. And how to use them to draw a bar chart with animation.

P.S.

To share this knowledge, I am going to write a series of articles about drawing diagrams with Flutter. This article will be the first of six articles in this series.

  1. Plotting with Flutter (I) Bar chart πŸ“Š
  2. Chart (ii) pie chart using Flutter πŸͺ
  3. Chart (3) line chart using Flutter πŸ“ˆ
  4. Plotting with Flutter (IV) radar charts 🎯
  5. Use Flutter to chart (v) loops 🍩
  6. Chart using Flutter (vi) bar chart πŸ“

This article is formatted using MDNICE