簡體   English   中英

是否可以使用charts_flutter包旋轉餅圖?

[英]Is it possible to rotate pie charts with the charts_flutter package?

是否可以使用包chart_flutter獲得相同的效果? 在這種情況下,用戶可以旋轉餅圖。

用戶旋轉餅圖

除非您使用其代碼並進行更改,否則您使用的圖表庫的當前實現是不可能的。 可以通過連接手勢檢測代碼並為startAngle動畫值來使其與顫動的圓形圖表插件一起使用,但我不確定它是否會完全符合您的要求(或者可能會嘗試重繪整個每次都沒有過分表現的東西)。

我有一些舊的代碼可以實現你想要的大部分內容,所以我把它修改了一下 - 這是一個只寫自己的餅圖的例子。 您可以將其復制/粘貼到文件中並按原樣運行。

你的里程可能因此而有所不同 - 我沒有對它進行過廣泛的測試或任何測試,但我們歡迎你至少使用它作為一個起點 - 它具有繪制餅圖和至少根據手勢旋轉的代碼。

這里有很多東西,所以我鼓勵你仔細閱讀,看看我到底在做什么。 我現在沒有時間添加文檔,但如果您有任何問題,請隨時提出。

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;
  }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM