Можно ли добиться того же эффекта с помощью пакета charts_flutter? В этом случае пользователь может повернуть круговую диаграмму.
Можно ли вращать круговые диаграммы с помощью пакета charts_flutter?
Ответы (1)
Это невозможно с текущей реализацией библиотеки диаграмм, которую вы используете, если вы не использовали их код и не изменили. Вы можете заставить его работать с помощью плагина флаттер-круговой диаграммы подключив код обнаружения жестов и анимировав значение для 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;
}
}