草庐IT

dart - 是否可以使用 charts_flutter 包旋转饼图?

coder 2023-07-21 原文

使用包 charts_flutter 是否可以达到同样的效果?在这种情况下,用户可以旋转饼图。

User rotating pie chart

最佳答案

除非您使用他们的代码并进行更改,否则您正在使用的图表库的当前实现是不可能的。您可能能够让它与 flutter circular chart plugin 一起工作通过连接你的手势检测代码并为 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;
  }
}

关于dart - 是否可以使用 charts_flutter 包旋转饼图?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51859260/

有关dart - 是否可以使用 charts_flutter 包旋转饼图?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  9. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

随机推荐