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

Viewed 230

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?

1 Answers

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

Related