flutterchartsflutter-custompainter

how to make a curved chart with a gradient under highest value in flutter?


I want to make a chart with a gradient under the highest value, exactly like this :

enter image description here

how can I do that?


Solution

  • You should use CustomPaint.

    1. Create a custom painter that draws the chart line based on chart data:
    class CurvedChartPainter extends CustomPainter {
      final List<double> xValues;
      final List<double> yValues;
      final Color color;
      final double strokeWidth;
    
      CurvedChartPainter({
        @required this.xValues,
        @required this.yValues,
        @required this.strokeWidth,
        this.color,
      });
    
      @override
      void paint(Canvas canvas, Size size) {
        var paint = Paint();
        paint.color = color ??  Color(0xFFF63E02);
        paint.style = PaintingStyle.stroke;
        paint.strokeWidth = strokeWidth;
    
        var path = Path();
        if (xValues.length > 1 && yValues.isNotEmpty) {
          final maxValue = yValues.last;
          final firstValueHeight = size.height * (xValues.first / maxValue);
          path.moveTo(0.0, size.height - firstValueHeight);
    
          final itemXDistance = size.width / (xValues.length - 1);
          for (var i = 1; i < xValues.length; i++) {
            final x = itemXDistance * i;
            final valueHeight = size.height -
                strokeWidth -
                ((size.height -  strokeWidth) * (xValues[i].value / maxValue));
            final previousValueHeight = size.height -
                strokeWidth -
                ((size.height -  strokeWidth) *
                    (xValues[i - 1].value / maxValue));
            
            path.quadraticBezierTo(
              x - (itemXDistance / 2) - (itemXDistance / 8),
              previousValueHeight,
              x - (itemXDistance / 2),
              valueHeight + ((previousValueHeight - valueHeight) / 2),
            );
            path.quadraticBezierTo(
              x - (itemXDistance / 2) + (itemXDistance / 8),
              valueHeight,
              x,
              valueHeight,
            );
          }
        }
    
        canvas.drawPath(path, paint);
      }
    
      @override
      bool shouldRepaint(CustomPainter oldDelegate) => oldDelegate != this;
    }
    
    
    1. Create a Container that renders the gradient:
    class MyCurvedChart extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Container(
          decoration: BoxDecoration(
            gradient: LinearGradient(
              begin: Alignment.topCenter,
              end: Alignment.bottomCenter,
              colors: [
                Color(0xFFF63E02).withOpacity(0.45),
                Colors.white.withOpacity(0.0),
              ],
            ),
          ),
          width: 200,
          height: 150,
          child: CustomPaint(
            painter: CurvedChartPainter(
              xValues: [
                0.0,
                1.0,
                0.0,
                2.0,
                3.0,
                1.0,
                1.5,
              ],
              yValues: [
                0.0,
                1.0,
                2.0,
                3.0,
                4.0,
              ],
              strokeWidth: 3.0,
            ),
          ),
        );
      }
    }
    

    now our output would look like something like this:
    enter image description here

    1. Now we should make a CostumClipper that clips the Container exactly like the chart:
    class CurvedChartClipper extends CustomClipper<Path> {
      final List<double> xValues;
      final List<double> yValues;
      final double strokeWidth;
    
      CurvedChartClipper({
        @required this.xValues,
        @required this.yValues,
        @required this.strokeWidth,
      });
    
      @override
      Path getClip(Size size) {
        var path = Path();
        if (xValues.length > 1 && yValues.isNotEmpty) {
          final maxValue = yValues.last;
          final firstValueHeight = size.height * (xValues.first / maxValue);
          path.moveTo(0.0, size.height - firstValueHeight);
    
          final itemXDistance = size.width / (xValues.length - 1);
          for (var i = 1; i < xValues.length; i++) {
            final x = itemXDistance * i;
            final valueHeight = size.height -
                strokeWidth -
                ((size.height -  strokeWidth) * (xValues[i].value / maxValue));
            final previousValueHeight = size.height -
                strokeWidth -
                ((size.height -  strokeWidth) *
                    (xValues[i - 1].value / maxValue));
    
            path.quadraticBezierTo(
              x - (itemXDistance / 2) - (itemXDistance / 8),
              previousValueHeight,
              x - (itemXDistance / 2),
              valueHeight + ((previousValueHeight - valueHeight) / 2),
            );
            path.quadraticBezierTo(
              x - (itemXDistance / 2) + (itemXDistance / 8),
              valueHeight,
              x,
              valueHeight,
            );
          }
    
          path.lineTo(size.width, size.height);
          path.lineTo(0, size.height);
          path.lineTo(0, 0);
        }
    
        return path;
      }
    
      @override
      bool shouldReclip(covariant CustomClipper<Path> oldClipper) =>
          oldClipper != this;
    }
    
    1. use ClipPath widget to clip the Container that has the gradient:
    class MyCurvedChart extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        final xValues = [
          0.0,
          1.0,
          0.0,
          2.0,
          3.0,
          1.0,
          1.5,
        ];
        final yValues = [
          0.0,
          1.0,
          2.0,
          3.0,
          4.0,
        ];
        final stroke = 3.0;
    
        return ClipPath(
          clipper: CurvedChartClipper(
            xValues: xValues,
            yValues: yValues,
            strokeWidth: stroke,
          ),
          child: Container(
            decoration: BoxDecoration(
              gradient: LinearGradient(
                begin: Alignment.topCenter,
                end: Alignment.bottomCenter,
                colors: [
                  Color(0xFFF63E02).withOpacity(0.45),
                  Colors.white.withOpacity(0.0),
                ],
              ),
            ),
            width: 200,
            height: 150,
            child: CustomPaint(
              painter: CurvedChartPainter(
                xValues: xValues,
                yValues: yValues,
                strokeWidth: stroke,
              ),
            ),
          ),
        );
      }
    }
    

    Now our output will be something like this:
    enter image description here