Initial commit
This commit is contained in:
commit
2e558d8596
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.idea
|
||||||
|
.packages
|
||||||
|
.pub/
|
||||||
|
packages
|
||||||
|
pubspec.lock
|
3
CHANGELOG.md
Normal file
3
CHANGELOG.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
## [0.0.1] - TODO: Add release date.
|
||||||
|
|
||||||
|
* TODO: Describe initial release.
|
9
README.md
Normal file
9
README.md
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# flutter_circular_chart
|
||||||
|
|
||||||
|
Animated radial and pie charts for Flutter
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
For help getting started with Flutter, view our online [documentation](http://flutter.io/).
|
||||||
|
|
||||||
|
For help on editing package code, view the [documentation](https://flutter.io/developing-packages/).
|
13
flutter_circular_chart.iml
Normal file
13
flutter_circular_chart.iml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.pub" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
<orderEntry type="library" name="Dart SDK" level="project" />
|
||||||
|
<orderEntry type="library" name="Dart Packages" level="project" />
|
||||||
|
</component>
|
||||||
|
</module>
|
5
lib/flutter_circular_chart.dart
Normal file
5
lib/flutter_circular_chart.dart
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
library flutter_circular_chart;
|
||||||
|
|
||||||
|
export 'src/circular_chart.dart';
|
||||||
|
export 'src/animated_circular_chart.dart';
|
||||||
|
export 'src/entry.dart';
|
201
lib/src/animated_circular_chart.dart
Normal file
201
lib/src/animated_circular_chart.dart
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_circular_chart/src/circular_chart.dart';
|
||||||
|
import 'package:flutter_circular_chart/src/entry.dart';
|
||||||
|
import 'package:flutter_circular_chart/src/painter.dart';
|
||||||
|
|
||||||
|
// The default chart tween animation duration.
|
||||||
|
const Duration _kDuration = const Duration(milliseconds: 300);
|
||||||
|
// The default angle the chart is oriented at.
|
||||||
|
const double _kStartAngle = -90.0;
|
||||||
|
|
||||||
|
enum CircularChartType {
|
||||||
|
Pie,
|
||||||
|
Radial,
|
||||||
|
}
|
||||||
|
|
||||||
|
class AnimatedCircularChart extends StatefulWidget {
|
||||||
|
AnimatedCircularChart({
|
||||||
|
Key key,
|
||||||
|
@required this.size,
|
||||||
|
@required this.initialChartData,
|
||||||
|
this.chartType = CircularChartType.Radial,
|
||||||
|
this.duration = _kDuration,
|
||||||
|
this.percentageValues = false,
|
||||||
|
this.holeRadius,
|
||||||
|
this.startAngle = _kStartAngle,
|
||||||
|
})
|
||||||
|
: assert(size != null),
|
||||||
|
super(key: key);
|
||||||
|
|
||||||
|
/// The size of the bounding box this chart will be constrained to.
|
||||||
|
final Size size;
|
||||||
|
|
||||||
|
/// The data used to build the chart displayed when the widget is first placed.
|
||||||
|
/// Each [CircularStackEntry] in the list defines an individual stack of data:
|
||||||
|
/// For a Pie chart that corresponds to individual slices in the chart.
|
||||||
|
/// For a Radial chart it corresponds to individual segments on the same arc.
|
||||||
|
///
|
||||||
|
/// If length > 1 and [chartType] is [CircularChartType.Radial] then the stacks
|
||||||
|
/// will be grouped together as concentric circles.
|
||||||
|
///
|
||||||
|
/// If [chartType] is [CircularChartType.Pie] then length cannot be > 1.
|
||||||
|
final List<CircularStackEntry> initialChartData;
|
||||||
|
|
||||||
|
/// The type of chart to be rendered.
|
||||||
|
/// Use [CircularChartType.Pie] for a circle divided into slices for each entry.
|
||||||
|
/// Use [CircularChartType.Radial] for one or more arcs with a hole in the center.
|
||||||
|
final CircularChartType chartType;
|
||||||
|
|
||||||
|
/// The duration of the chart animation when [AnimatedCircularChartState.updateData]
|
||||||
|
/// is called.
|
||||||
|
final Duration duration;
|
||||||
|
|
||||||
|
/// If true then the data values provided will determine what percentage of the circle
|
||||||
|
/// this segment occupies [i.e: a value of 100 is the full circle].
|
||||||
|
///
|
||||||
|
/// Otherwise the data is normalized such that the sum of all values in each stack
|
||||||
|
/// is considered to encompass 100% of the circle.
|
||||||
|
///
|
||||||
|
/// defaults to false.
|
||||||
|
final bool percentageValues;
|
||||||
|
|
||||||
|
/// For [CircularChartType.Radial] charts this defines the circle in the center
|
||||||
|
/// of the canvas, around which the chart is drawn. If not provided then it will
|
||||||
|
/// be automatically calculated to accommodate all the data.
|
||||||
|
///
|
||||||
|
/// Has no effect in [CircularChartType.Pie] charts.
|
||||||
|
final double holeRadius;
|
||||||
|
|
||||||
|
/// The chart gets drawn and animates clockwise from [startAngle], defaulting to the
|
||||||
|
/// top/center point or -90.0. In terms of a clock face these would be:
|
||||||
|
/// - -90.0: 12 o'clock
|
||||||
|
/// - 0.0: 3 o'clock
|
||||||
|
/// - 90.0: 6 o'clock
|
||||||
|
/// - 180.0: 9 o'clock
|
||||||
|
final double startAngle;
|
||||||
|
|
||||||
|
/// The state from the closest instance of this class that encloses the given context.
|
||||||
|
///
|
||||||
|
/// This method is typically used by [AnimatedCircularChart] item widgets that insert or
|
||||||
|
/// remove items in response to user input.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// AnimatedCircularChartState animatedCircularChart = AnimatedCircularChart.of(context);
|
||||||
|
/// ```
|
||||||
|
static AnimatedCircularChartState of(BuildContext context,
|
||||||
|
{bool nullOk: false}) {
|
||||||
|
assert(context != null);
|
||||||
|
assert(nullOk != null);
|
||||||
|
|
||||||
|
final AnimatedCircularChartState result = context
|
||||||
|
.ancestorStateOfType(const TypeMatcher<AnimatedCircularChartState>());
|
||||||
|
|
||||||
|
if (nullOk || result != null) return result;
|
||||||
|
|
||||||
|
throw new FlutterError(
|
||||||
|
'AnimatedCircularChart.of() called with a context that does not contain a AnimatedCircularChart.\n'
|
||||||
|
'No AnimatedCircularChart ancestor could be found starting from the context that was passed to AnimatedCircularChart.of(). '
|
||||||
|
'This can happen when the context provided is from the same StatefulWidget that '
|
||||||
|
'built the AnimatedCircularChart.\n'
|
||||||
|
'The context used was:\n'
|
||||||
|
' $context');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AnimatedCircularChartState createState() => new AnimatedCircularChartState();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The state for a circular chart that animates when its data is updated.
|
||||||
|
///
|
||||||
|
/// When the chart data changes with [updateData] an animation begins running.
|
||||||
|
///
|
||||||
|
/// An app that needs to update its data in response to an event
|
||||||
|
/// can refer to the [AnimatedCircularChart]'s state with a global key:
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// GlobalKey<AnimatedCircularChartState> chartKey = new GlobalKey<AnimatedCircularChartState>();
|
||||||
|
/// ...
|
||||||
|
/// new AnimatedCircularChart(key: chartKey, ...);
|
||||||
|
/// ...
|
||||||
|
/// chartKey.currentState.updateData(newData);
|
||||||
|
/// ```
|
||||||
|
class AnimatedCircularChartState extends State<AnimatedCircularChart>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
CircularChartTween _tween;
|
||||||
|
AnimationController _animation;
|
||||||
|
final Map<String, int> _stackRanks = <String, int>{};
|
||||||
|
final Map<String, int> _entryRanks = <String, int>{};
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_animation = new AnimationController(
|
||||||
|
duration: widget.duration,
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
|
||||||
|
_assignRanks(widget.initialChartData);
|
||||||
|
|
||||||
|
_tween = new CircularChartTween(
|
||||||
|
new CircularChart.empty(chartType: widget.chartType),
|
||||||
|
new CircularChart.fromData(
|
||||||
|
size: widget.size,
|
||||||
|
data: widget.initialChartData,
|
||||||
|
chartType: widget.chartType,
|
||||||
|
stackRanks: _stackRanks,
|
||||||
|
entryRanks: _entryRanks,
|
||||||
|
percentageValues: widget.percentageValues,
|
||||||
|
holeRadius: widget.holeRadius,
|
||||||
|
startAngle: widget.startAngle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_animation.forward();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_animation.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _assignRanks(List<CircularStackEntry> data) {
|
||||||
|
for (CircularStackEntry stackEntry in data) {
|
||||||
|
_stackRanks.putIfAbsent(stackEntry.rankKey, () => _stackRanks.length);
|
||||||
|
for (CircularSegmentEntry entry in stackEntry.entries) {
|
||||||
|
_entryRanks.putIfAbsent(entry.rankKey, () => _entryRanks.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Update the data this chart represents and start an animation that will tween
|
||||||
|
/// between the old data and this one.
|
||||||
|
void updateData(List<CircularStackEntry> data) {
|
||||||
|
_assignRanks(data);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_tween = new CircularChartTween(
|
||||||
|
_tween.evaluate(_animation),
|
||||||
|
new CircularChart.fromData(
|
||||||
|
size: widget.size,
|
||||||
|
data: data,
|
||||||
|
chartType: widget.chartType,
|
||||||
|
stackRanks: _stackRanks,
|
||||||
|
entryRanks: _entryRanks,
|
||||||
|
percentageValues: widget.percentageValues,
|
||||||
|
holeRadius: widget.holeRadius,
|
||||||
|
startAngle: widget.startAngle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_animation.forward(from: 0.0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return new CustomPaint(
|
||||||
|
size: widget.size,
|
||||||
|
painter: new AnimatedCircularChartPainter(_tween.animate(_animation)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
65
lib/src/circular_chart.dart
Normal file
65
lib/src/circular_chart.dart
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import 'package:flutter/animation.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_circular_chart/src/animated_circular_chart.dart';
|
||||||
|
import 'package:flutter_circular_chart/src/entry.dart';
|
||||||
|
import 'package:flutter_circular_chart/src/stack.dart';
|
||||||
|
import 'package:flutter_circular_chart/src/tween.dart';
|
||||||
|
|
||||||
|
class CircularChart {
|
||||||
|
static const double _kStackWidthFraction = 0.75;
|
||||||
|
|
||||||
|
CircularChart(this.stacks, this.chartType);
|
||||||
|
|
||||||
|
factory CircularChart.empty({@required CircularChartType chartType}) {
|
||||||
|
return new CircularChart(<CircularChartStack>[], chartType);
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<CircularChartStack> stacks;
|
||||||
|
final CircularChartType chartType;
|
||||||
|
|
||||||
|
factory CircularChart.fromData({
|
||||||
|
@required Size size,
|
||||||
|
@required List<CircularStackEntry> data,
|
||||||
|
@required CircularChartType chartType,
|
||||||
|
@required bool percentageValues,
|
||||||
|
@required double startAngle,
|
||||||
|
Map<String, int> stackRanks,
|
||||||
|
Map<String, int> entryRanks,
|
||||||
|
double holeRadius,
|
||||||
|
}) {
|
||||||
|
final double _holeRadius = holeRadius ?? size.width / (2 + data.length);
|
||||||
|
final double stackDistance =
|
||||||
|
(size.width / 2 - _holeRadius) / (2 + data.length);
|
||||||
|
final double stackWidth = stackDistance * _kStackWidthFraction;
|
||||||
|
final double startRadius = stackDistance + _holeRadius;
|
||||||
|
|
||||||
|
List<CircularChartStack> stacks = new List<CircularChartStack>.generate(
|
||||||
|
data.length,
|
||||||
|
(i) => new CircularChartStack.fromData(
|
||||||
|
stackRanks[data[i].rankKey] ?? i,
|
||||||
|
data[i].entries,
|
||||||
|
entryRanks,
|
||||||
|
percentageValues,
|
||||||
|
startRadius + i * stackDistance,
|
||||||
|
stackWidth,
|
||||||
|
startAngle,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
return new CircularChart(stacks, chartType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CircularChartTween extends Tween<CircularChart> {
|
||||||
|
CircularChartTween(CircularChart begin, CircularChart end)
|
||||||
|
: _stacksTween =
|
||||||
|
new MergeTween<CircularChartStack>(begin.stacks, end.stacks),
|
||||||
|
super(begin: begin, end: end);
|
||||||
|
|
||||||
|
final MergeTween<CircularChartStack> _stacksTween;
|
||||||
|
|
||||||
|
@override
|
||||||
|
CircularChart lerp(double t) =>
|
||||||
|
new CircularChart(_stacksTween.lerp(t), begin.chartType);
|
||||||
|
}
|
20
lib/src/entry.dart
Normal file
20
lib/src/entry.dart
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
class CircularSegmentEntry {
|
||||||
|
const CircularSegmentEntry(this.value, this.color, {this.rankKey});
|
||||||
|
|
||||||
|
final double value;
|
||||||
|
final Color color;
|
||||||
|
final String rankKey;
|
||||||
|
|
||||||
|
String toString() {
|
||||||
|
return '$rankKey: $value $color';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CircularStackEntry {
|
||||||
|
const CircularStackEntry(this.entries, {this.rankKey});
|
||||||
|
|
||||||
|
final List<CircularSegmentEntry> entries;
|
||||||
|
final String rankKey;
|
||||||
|
}
|
60
lib/src/painter.dart
Normal file
60
lib/src/painter.dart
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import 'dart:math' as Math;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_circular_chart/src/animated_circular_chart.dart';
|
||||||
|
import 'package:flutter_circular_chart/src/circular_chart.dart';
|
||||||
|
import 'package:flutter_circular_chart/src/stack.dart';
|
||||||
|
|
||||||
|
class AnimatedCircularChartPainter extends CustomPainter {
|
||||||
|
AnimatedCircularChartPainter(this.animation) : super(repaint: animation);
|
||||||
|
|
||||||
|
final Animation<CircularChart> animation;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
_paintChart(canvas, size, animation.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(AnimatedCircularChartPainter old) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CircularChartPainter extends CustomPainter {
|
||||||
|
CircularChartPainter(this.chart);
|
||||||
|
|
||||||
|
final CircularChart chart;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
_paintChart(canvas, size, chart);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(CircularChartPainter old) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const double _kRadiansPerDegree = Math.PI / 180;
|
||||||
|
|
||||||
|
void _paintChart(Canvas canvas, Size size, CircularChart chart) {
|
||||||
|
final Paint segmentPaint = new Paint()
|
||||||
|
..style = chart.chartType == CircularChartType.Radial
|
||||||
|
? PaintingStyle.stroke
|
||||||
|
: PaintingStyle.fill;
|
||||||
|
|
||||||
|
for (final CircularChartStack stack in chart.stacks) {
|
||||||
|
for (final segment in stack.segments) {
|
||||||
|
segmentPaint.color = segment.color;
|
||||||
|
segmentPaint.strokeWidth = stack.width;
|
||||||
|
canvas.drawArc(
|
||||||
|
new Rect.fromCircle(
|
||||||
|
center: new Offset(size.width / 2, size.height / 2),
|
||||||
|
radius: stack.radius,
|
||||||
|
),
|
||||||
|
stack.startAngle * _kRadiansPerDegree,
|
||||||
|
segment.sweepAngle * _kRadiansPerDegree,
|
||||||
|
chart.chartType == CircularChartType.Pie,
|
||||||
|
segmentPaint,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
45
lib/src/segment.dart
Normal file
45
lib/src/segment.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_circular_chart/src/tween.dart';
|
||||||
|
|
||||||
|
class CircularChartSegment extends MergeTweenable<CircularChartSegment> {
|
||||||
|
CircularChartSegment(this.rank, this.sweepAngle, this.color);
|
||||||
|
|
||||||
|
final int rank;
|
||||||
|
final double sweepAngle;
|
||||||
|
final Color color;
|
||||||
|
|
||||||
|
@override
|
||||||
|
CircularChartSegment get empty => new CircularChartSegment(rank, 0.0, color);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator <(CircularChartSegment other) => rank < other.rank;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Tween<CircularChartSegment> tweenTo(CircularChartSegment other) =>
|
||||||
|
new CircularChartSegmentTween(this, other);
|
||||||
|
|
||||||
|
static CircularChartSegment lerp(
|
||||||
|
CircularChartSegment begin, CircularChartSegment end, double t) {
|
||||||
|
assert(begin.rank == end.rank);
|
||||||
|
|
||||||
|
return new CircularChartSegment(
|
||||||
|
begin.rank,
|
||||||
|
lerpDouble(begin.sweepAngle, end.sweepAngle, t),
|
||||||
|
Color.lerp(begin.color, end.color, t),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CircularChartSegmentTween extends Tween<CircularChartSegment> {
|
||||||
|
CircularChartSegmentTween(
|
||||||
|
CircularChartSegment begin, CircularChartSegment end)
|
||||||
|
: super(begin: begin, end: end) {
|
||||||
|
assert(begin.rank == end.rank);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
CircularChartSegment lerp(double t) =>
|
||||||
|
CircularChartSegment.lerp(begin, end, t);
|
||||||
|
}
|
85
lib/src/stack.dart
Normal file
85
lib/src/stack.dart
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import 'dart:ui' show lerpDouble;
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_circular_chart/src/entry.dart';
|
||||||
|
import 'package:flutter_circular_chart/src/segment.dart';
|
||||||
|
import 'package:flutter_circular_chart/src/tween.dart';
|
||||||
|
|
||||||
|
const double _kMaxAngle = 360.0;
|
||||||
|
|
||||||
|
class CircularChartStack implements MergeTweenable<CircularChartStack> {
|
||||||
|
CircularChartStack(
|
||||||
|
this.rank, this.radius, this.width, this.startAngle, this.segments);
|
||||||
|
|
||||||
|
final int rank;
|
||||||
|
final double radius;
|
||||||
|
final double width;
|
||||||
|
final double startAngle;
|
||||||
|
final List<CircularChartSegment> segments;
|
||||||
|
|
||||||
|
factory CircularChartStack.fromData(
|
||||||
|
int stackRank,
|
||||||
|
List<CircularSegmentEntry> entries,
|
||||||
|
Map<String, int> entryRanks,
|
||||||
|
bool percentageValues,
|
||||||
|
double startRadius,
|
||||||
|
double stackWidth,
|
||||||
|
double startAngle,
|
||||||
|
) {
|
||||||
|
final double valueSum = percentageValues
|
||||||
|
? 100.0
|
||||||
|
: entries.fold(
|
||||||
|
0.0,
|
||||||
|
(double prev, CircularSegmentEntry element) =>
|
||||||
|
prev + element.value);
|
||||||
|
|
||||||
|
double previousSweepAngle = 0.0;
|
||||||
|
List<CircularChartSegment> segments =
|
||||||
|
new List<CircularChartSegment>.generate(entries.length, (i) {
|
||||||
|
double sweepAngle =
|
||||||
|
(entries[i].value / valueSum * _kMaxAngle) + previousSweepAngle;
|
||||||
|
previousSweepAngle = sweepAngle;
|
||||||
|
int rank = entryRanks[entries[i].rankKey] ?? i;
|
||||||
|
return new CircularChartSegment(rank, sweepAngle, entries[i].color);
|
||||||
|
});
|
||||||
|
|
||||||
|
return new CircularChartStack(
|
||||||
|
stackRank,
|
||||||
|
startRadius,
|
||||||
|
stackWidth,
|
||||||
|
startAngle,
|
||||||
|
segments.reversed.toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
CircularChartStack get empty => new CircularChartStack(
|
||||||
|
rank, radius, 0.0, startAngle, <CircularChartSegment>[]);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator <(CircularChartStack other) => rank < other.rank;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Tween<CircularChartStack> tweenTo(CircularChartStack other) =>
|
||||||
|
new CircularChartStackTween(this, other);
|
||||||
|
}
|
||||||
|
|
||||||
|
class CircularChartStackTween extends Tween<CircularChartStack> {
|
||||||
|
CircularChartStackTween(CircularChartStack begin, CircularChartStack end)
|
||||||
|
: _circularSegmentsTween =
|
||||||
|
new MergeTween<CircularChartSegment>(begin.segments, end.segments),
|
||||||
|
super(begin: begin, end: end) {
|
||||||
|
assert(begin.rank == end.rank);
|
||||||
|
}
|
||||||
|
|
||||||
|
final MergeTween<CircularChartSegment> _circularSegmentsTween;
|
||||||
|
|
||||||
|
@override
|
||||||
|
CircularChartStack lerp(double t) => new CircularChartStack(
|
||||||
|
begin.rank,
|
||||||
|
lerpDouble(begin.radius, end.radius, t),
|
||||||
|
lerpDouble(begin.width, end.width, t),
|
||||||
|
lerpDouble(begin.startAngle, end.startAngle, t),
|
||||||
|
_circularSegmentsTween.lerp(t),
|
||||||
|
);
|
||||||
|
}
|
39
lib/src/tween.dart
Normal file
39
lib/src/tween.dart
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import 'package:flutter/animation.dart';
|
||||||
|
|
||||||
|
abstract class MergeTweenable<T> {
|
||||||
|
T get empty;
|
||||||
|
|
||||||
|
Tween<T> tweenTo(T other);
|
||||||
|
|
||||||
|
bool operator <(T other);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MergeTween<T extends MergeTweenable<T>> extends Tween<List<T>> {
|
||||||
|
MergeTween(List<T> begin, List<T> end) : super(begin: begin, end: end) {
|
||||||
|
final bMax = begin.length;
|
||||||
|
final eMax = end.length;
|
||||||
|
var b = 0;
|
||||||
|
var e = 0;
|
||||||
|
while (b + e < bMax + eMax) {
|
||||||
|
if (b < bMax && (e == eMax || begin[b] < end[e])) {
|
||||||
|
_tweens.add(begin[b].tweenTo(begin[b].empty));
|
||||||
|
b++;
|
||||||
|
} else if (e < eMax && (b == bMax || end[e] < begin[b])) {
|
||||||
|
_tweens.add(end[e].empty.tweenTo(end[e]));
|
||||||
|
e++;
|
||||||
|
} else {
|
||||||
|
_tweens.add(begin[b].tweenTo(end[e]));
|
||||||
|
b++;
|
||||||
|
e++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final _tweens = <Tween<T>>[];
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<T> lerp(double t) => new List.generate(
|
||||||
|
_tweens.length,
|
||||||
|
(i) => _tweens[i].lerp(t),
|
||||||
|
);
|
||||||
|
}
|
49
pubspec.yaml
Normal file
49
pubspec.yaml
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
name: flutter_circular_chart
|
||||||
|
description: Animated radial and pie charts for Flutter
|
||||||
|
version: 0.0.1
|
||||||
|
author:
|
||||||
|
homepage:
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
test: ^0.12.0
|
||||||
|
|
||||||
|
# For information on the generic Dart part of this file, see the
|
||||||
|
# following page: https://www.dartlang.org/tools/pub/pubspec
|
||||||
|
|
||||||
|
# The following section is specific to Flutter.
|
||||||
|
flutter:
|
||||||
|
|
||||||
|
# To add assets to your package, add an assets section, like this:
|
||||||
|
# assets:
|
||||||
|
# - images/a_dot_burr.jpeg
|
||||||
|
# - images/a_dot_ham.jpeg
|
||||||
|
#
|
||||||
|
# For details regarding assets in packages, see
|
||||||
|
# https://flutter.io/assets-and-images/#from-packages
|
||||||
|
#
|
||||||
|
# An image asset can refer to one or more resolution-specific "variants", see
|
||||||
|
# https://flutter.io/assets-and-images/#resolution-aware.
|
||||||
|
|
||||||
|
# To add custom fonts to your package, add a fonts section here,
|
||||||
|
# in this "flutter" section. Each entry in this list should have a
|
||||||
|
# "family" key with the font family name, and a "fonts" key with a
|
||||||
|
# list giving the asset and other descriptors for the font. For
|
||||||
|
# example:
|
||||||
|
# fonts:
|
||||||
|
# - family: Schyler
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/Schyler-Regular.ttf
|
||||||
|
# - asset: fonts/Schyler-Italic.ttf
|
||||||
|
# style: italic
|
||||||
|
# - family: Trajan Pro
|
||||||
|
# fonts:
|
||||||
|
# - asset: fonts/TrajanPro.ttf
|
||||||
|
# - asset: fonts/TrajanPro_Bold.ttf
|
||||||
|
# weight: 700
|
||||||
|
#
|
||||||
|
# For details regarding fonts in packages, see
|
||||||
|
# https://flutter.io/custom-fonts/#from-packages
|
13
test/flutter_circular_chart_test.dart
Normal file
13
test/flutter_circular_chart_test.dart
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_circular_chart/flutter_circular_chart.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('adds one to input values', () {
|
||||||
|
final calculator = new Calculator();
|
||||||
|
expect(calculator.addOne(2), 3);
|
||||||
|
expect(calculator.addOne(-7), -6);
|
||||||
|
expect(calculator.addOne(0), 1);
|
||||||
|
expect(() => calculator.addOne(null), throwsNoSuchMethodError);
|
||||||
|
});
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user