是否可以使用Charts_flutter包旋转饼图?

时间:2018-08-15 13:02:21

标签: dart flutter flutter-animation

使用Charts_flutter包是否可能具有相同的效果?在这种情况下,用户可以旋转饼图。

User rotating pie chart

1 个答案:

答案 0 :(得分:1)

除非您使用了它们的代码并进行了更改,否则您正在使用的图表库的当前实现是不可能的。您可以通过连接手势检测代码并为startAngle设置值动画来使其与flutter circular chart plugin配合使用,但我不确定它是否可以确切地是您想要的(或者可能会尝试每次重新绘制不太好的性能的整个对象)。

我周围有一些旧代码,可以实现您想要的大部分内容,因此我对其进行了一些修复-这是一个仅编写自己的饼图的示例。您可以将其复制/粘贴到文件中,然后按原样运行。

您的行驶里程可能会有所不同-我尚未对其进行广泛的测试或其他任何测试,但是至少欢迎您使用它作为起点-它具有绘制饼图并根据手势进行旋转的代码最少。

这里有很多东西,所以我鼓励您深入阅读它,以准确了解我在做什么。我现在没有时间添加文档,但是如果您有任何疑问,请随时提出。

import 'dart:math';

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: SafeArea(
        child: Material(
          child: RotatingPieChart(
            items: [
              PieChartItem(30, "one", Colors.red),
              PieChartItem(210, "two", Colors.green),
              PieChartItem(60, "three", Colors.blue),
              PieChartItem(35, "four", Colors.teal),
              PieChartItem(25, "five", Colors.orange)
            ],
            toText: (item, _) => TextPainter(
                textAlign: TextAlign.center,
                text: TextSpan(
                  style: TextStyle(color: Colors.black, fontSize: 8.0),
                  text: "${item.name}\n${item.val}",
                ),
                textDirection: TextDirection.ltr),
          ),
        ),
      ),
    );
  }
}

class PieChartItem {
  final num val;
  final String name;
  final Color color;

  PieChartItem(this.val, this.name, this.color) : assert(val != 0);
}

typedef TextPainter PieChartItemToText(PieChartItem item, double total);

class RotatingPieChart extends StatelessWidget {
  final double accellerationFactor;
  final List<PieChartItem> items;
  final PieChartItemToText toText;

  const RotatingPieChart({Key key, this.accellerationFactor = 1.0, @required this.items, @required this.toText})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: AspectRatio(
        aspectRatio: 1.0,
        child: _RotatingPieChartInternal(
          items: items,
          toText: toText,
          accellerationFactor: accellerationFactor,
        ),
      ),
    );
  }
}

class _RotationEndSimulation extends Simulation {
  final double initialVelocity;
  final double initialPosition;
  final double accelleration;

  _RotationEndSimulation({
    @required this.initialVelocity,
    @required double decelleration,
    @required this.initialPosition,
  }) : accelleration = decelleration * -1.0;

  @override
  double dx(double time) => initialVelocity + (accelleration * time);

  @override
  bool isDone(double time) => initialVelocity > 0 ? dx(time) < 0.001 : dx(time) > -0.001;

  @override
  double x(double time) => (initialPosition + (initialVelocity * time) + (accelleration * time * time / 2)) % 1.0;
}

class _RotatingPieChartInternal extends StatefulWidget {
  final double accellerationFactor;
  final List<PieChartItem> items;
  final PieChartItemToText toText;

  const _RotatingPieChartInternal(
      {Key key, this.accellerationFactor = 1.0, @required this.items, @required this.toText})
      : super(key: key);

  @override
  _RotatingPieChartInternalState createState() => _RotatingPieChartInternalState();
}

class _RotatingPieChartInternalState extends State<_RotatingPieChartInternal> with SingleTickerProviderStateMixin {
  AnimationController _controller;
  Animation<double> _animation;

  @override
  void initState() {
    _controller = AnimationController(vsync: this);
    _animation = new Tween(begin: 0.0, end: 2.0 * pi).animate(_controller);
    _controller.animateTo(2 * pi, duration: Duration(seconds: 10));
    super.initState();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  Offset lastDirection;

  Offset getDirection(Offset globalPosition) {
    RenderBox box = context.findRenderObject();
    Offset offset = box.globalToLocal(globalPosition);
    Offset center = Offset(context.size.width / 2.0, context.size.height / 2.0);
    return offset - center;
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanDown: (details) {
        lastDirection = getDirection(details.globalPosition);
      },
      onPanUpdate: (details) {
        Offset newDirection = getDirection(details.globalPosition);
        double diff = newDirection.direction - lastDirection.direction;

        var value = _controller.value + (diff / pi / 2);
        _controller.value = value % 1.0;
        lastDirection = newDirection;
      },
      onPanEnd: (details) {
        // non-angular velocity
        Offset velocity = details.velocity.pixelsPerSecond;

        var top = (lastDirection.dx * velocity.dy) - (lastDirection.dy * velocity.dx);
        var bottom = (lastDirection.dx * lastDirection.dx) + (lastDirection.dy * lastDirection.dy);

        var angularVelocity = top / bottom;
        var angularRotation = angularVelocity / pi / 2;
        var decelleration = angularRotation * widget.accellerationFactor;
        _controller.animateWith(
          _RotationEndSimulation(
            decelleration: decelleration,
            initialPosition: _controller.value,
            initialVelocity: angularRotation,
          ),
        );
      },
      child: AnimatedBuilder(
        animation: _animation,
        builder: (context, widget) {
          return Stack(
            fit: StackFit.passthrough,
            children: [
              Transform.rotate(
                angle: _animation.value,
                child: widget,
              ),
              CustomPaint(
                painter:
                    _PieTextPainter(items: this.widget.items, rotation: _animation.value, toText: this.widget.toText),
              )
            ],
          );
        },
        child: CustomPaint(
          painter: _PieChartPainter(
            items: widget.items,
          ),
        ),
      ),
    );
  }
}

abstract class _AlignedCustomPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // for convenience I'm doing all the drawing in a 100x100 square then moving it rather than worrying
    // about the actual size.
    // Also, using a 100x100 square for convenience so we can hardcode values.
    FittedSizes fittedSizes = applyBoxFit(BoxFit.contain, Size(100.0, 100.0), size);
    var dest = fittedSizes.destination;
    canvas.translate((size.width - dest.width) / 2 + 1, (size.height - dest.height) / 2 + 1);
    canvas.scale((dest.width - 2) / 100.0);
    alignedPaint(canvas, Size(100.0, 100.0));
  }

  void alignedPaint(Canvas canvas, Size size);
}

class _PieChartPainter extends _AlignedCustomPainter {
  final List<PieChartItem> items;
  final double total;
  final double rotation;

  _PieChartPainter({this.rotation = 0.0, @required this.items})
      : total = items.fold(0.0, (total, el) => total + el.val);

  @override
  void alignedPaint(Canvas canvas, Size size) {
    Rect rect = Offset.zero & size;

    double soFar = rotation;
    Paint outlinePaint = Paint()
      ..color = Colors.white
      ..style = PaintingStyle.stroke;

    for (int i = 0; i < items.length; ++i) {
      PieChartItem item = items[i];
      double arcRad = item.val / total * 2 * pi;
      canvas.drawArc(rect, soFar, arcRad, true, Paint()..color = item.color);
      canvas.drawArc(rect, soFar, arcRad, true, outlinePaint);
      soFar += arcRad;
    }
  }

  @override
  bool shouldRepaint(_PieChartPainter oldDelegate) {
    return oldDelegate.rotation != rotation || oldDelegate.items != items;
  }
}

class _PieTextPainter extends _AlignedCustomPainter {
  final List<PieChartItem> items;
  final double total;
  final double rotation;
  final List<double> middles;
  final PieChartItemToText toText;
  static final double textDisplayCenter = 0.7;

  _PieTextPainter._(this.items, this.total, this.rotation, this.middles, this.toText);

  factory _PieTextPainter(
      {double rotation = 0.0, @required List<PieChartItem> items, @required PieChartItemToText toText}) {
    double total = items.fold(0.0, (prev, el) => prev + el.val);
    var middles = (() {
      double soFar = rotation;
      return items.map((item) {
        double arcRad = item.val / total * 2 * pi;
        double middleRad = (soFar) + (arcRad / 2);
        soFar += arcRad;
        return middleRad;
      }).toList(growable: false);
    })();
    return _PieTextPainter._(items, total, rotation, middles, toText);
  }

  @override
  void alignedPaint(Canvas canvas, Size size) {
    for (int i = 0; i < items.length; ++i) {
      var middleRad = middles[i];
      var item = items[i];
      var rad = size.width / 2;

      var middleX = rad + rad * textDisplayCenter * cos(middleRad);
      var middleY = rad + rad * textDisplayCenter * sin(middleRad);

      TextPainter textPainter = toText(item, total)..layout();
      textPainter.paint(canvas, Offset(middleX - (textPainter.width / 2), middleY - (textPainter.height / 2)));
    }
  }

  @override
  bool shouldRepaint(_PieTextPainter oldDelegate) {
    // note that just checking items != items might not be enough.
    return oldDelegate.rotation != rotation || oldDelegate.items != items || oldDelegate.toText != toText;
  }
}