Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Spinning Donut Chart #4

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,4 @@ DerivedData
#
#Pods/

xcshareddata
Expand Down
9 changes: 8 additions & 1 deletion Example/YOGraphImageKit Demo App (Common)/YOChart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import UIKit
import YOChartImageKit

enum YOChart {
case SolidLineChart, SmoothLineChart, VerticalBarChart, HorizontalBarChart, DonutChart
case SolidLineChart, SmoothLineChart, VerticalBarChart, HorizontalBarChart, DonutChart, SpinningDonutChart

func drawImage(frame: CGRect, scale: CGFloat) -> UIImage {
switch self {
Expand Down Expand Up @@ -39,6 +39,13 @@ enum YOChart {
image.values = [10.0, 20.0, 70.0]
image.colors = (0..<3).map { _ in randomColor() }
return image.drawImage(frame, scale: scale)
case .SpinningDonutChart:
let image = YOSpinningDonutChartImage()
image.duration = 4
image.value = 10
image.donutWidth = 16.0
image.color = randomColor()
return image.drawImage(frame, scale: scale)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class ViewController: UIViewController, UIPageViewControllerDataSource {

private weak var pageViewController: UIPageViewController!

let charts: [YOChart] = [.SolidLineChart, .SmoothLineChart, .VerticalBarChart, .HorizontalBarChart, .DonutChart]
let charts: [YOChart] = [.SpinningDonutChart, .SolidLineChart, .SmoothLineChart, .VerticalBarChart, .HorizontalBarChart, .DonutChart]

override func viewDidLoad() {
super.viewDidLoad()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import WatchKit


class SpinningDonutChartInterfaceController: BaseInterfaceController {

override func willActivate() {
super.willActivate()

let chart = YOChart.SpinningDonutChart
let frame = CGRectMake(0, 0, contentFrame.width, contentFrame.height / 1.5)
let image = chart.drawImage(frame, scale: WKInterfaceDevice.currentDevice().screenScale)
self.imageView.setImage(image)
self.imageView.startAnimating()
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="8164.2" systemVersion="14E46" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="AgC-eL-Hgc">
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="3BN-5p-JBx">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8135.1"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="8083.2"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="9515"/>
</dependencies>
<scenes>
<!--Solid Line Chart Interface Controller-->
Expand Down Expand Up @@ -79,5 +79,20 @@
</objects>
<point key="canvasLocation" x="693" y="331"/>
</scene>
<!--Spinning Donut Chart Interface Controller-->
<scene sceneID="Ohk-cz-L5B">
<objects>
<controller id="3BN-5p-JBx" customClass="SpinningDonutChartInterfaceController" customModule="YOChartImageKit_Demo">
<items>
<imageView alignment="center" verticalAlignment="center" id="Yt6-Jy-hoG"/>
</items>
<connections>
<outlet property="imageView" destination="Yt6-Jy-hoG" id="Dm9-eJ-pMW"/>
<segue destination="AgC-eL-Hgc" kind="relationship" relationship="nextPage" id="o1K-mV-iv1"/>
</connections>
</controller>
</objects>
<point key="canvasLocation" x="60" y="331"/>
</scene>
</scenes>
</document>
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,19 @@ image.colors = (0..<3).map { _ in randomColor() } // colors of pieces
image.drawImage(frame, scale: scale) // draw an image
```

### Spinning chart (Animated)

```swift
let image = YOSpinningDonutChartImage()
image.duration = 4 // duration — not supported on watchOS yet.
// Always 1 second on watchOS
image.donutWidth = 16.0 // width of donut
image.values = [70.0, 30.0] // 70% turning, 30% clear colog
image.colors = [aColor(), UIColor.clearColor()] // Define the color + add a clearColor
view.image = image.drawImage(frame, scale: scale) // set imageView
view.startAnimating() // Animate it!
```

## Framework Requirements

- watchOS ~> 2.0
Expand Down
1 change: 1 addition & 0 deletions Source/YOChartImageKit/YOChartImageKit.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ FOUNDATION_EXPORT double YOChartImageKitVersionNumber;
FOUNDATION_EXPORT const unsigned char YOChartImageKitVersionString[];

#import "YODonutChartImage.h"
#import "YOSpinningDonutChartImage.h"
#import "YOBarChartImage.h"
#import "YOLineChartImage.h"
6 changes: 6 additions & 0 deletions Source/YOChartImageKit/YODonutChartImage.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
*/
@property CGFloat donutWidth;

/**
* The default `lineCapStyle` of the internal `UIBezierPath`.
* The default width is `kCGLineCapSquare` for historical reasons but `kCGLineCapSquare` is way cooler!
*/
@property CGLineCap lineCapStyle;

/**
* The text of center label in donut chart.
* The default text is `nil`.
Expand Down
22 changes: 12 additions & 10 deletions Source/YOChartImageKit/YODonutChartImage.m
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,19 @@ - (UIImage *)drawImage:(CGRect)frame scale:(CGFloat)scale {
}
[_values enumerateObjectsUsingBlock:^(NSNumber *number, NSUInteger idx, BOOL *_) {
CGFloat normalizedValue = number.floatValue / totalValue;
UIColor *strokeColor = _colors[idx];

CGFloat endAngle = _startAngle + 2.0 * M_PI * normalizedValue;
UIBezierPath *donutPath = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:_startAngle
endAngle:endAngle
clockwise:YES];
donutPath.lineWidth = _donutWidth;
[strokeColor setStroke];
[donutPath stroke];
UIColor *strokeColor = _colors[idx];
if (strokeColor != [UIColor clearColor]) {
UIBezierPath *donutPath = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:_startAngle
endAngle:endAngle
clockwise:YES];
donutPath.lineWidth = _donutWidth;
donutPath.lineCapStyle = _lineCapStyle;
[strokeColor setStroke];
[donutPath stroke];
}
_startAngle = endAngle;
}];

Expand Down
33 changes: 33 additions & 0 deletions Source/YOChartImageKit/YOSpinningDonutChartImage.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#import "YODonutChartImage.h"
@import UIKit;

/**
* A _spinning_ variant of `YODonutChartImage`.
*/
@interface YOSpinningDonutChartImage : YODonutChartImage

/**
* The spinner will automatically create a gradient, based on the last color
* Default is `true`
*/
@property BOOL useGradient;

/**
* The array of colors for the donut chart. `colors` should be an array of UIColor.
* You must provide `colors`, otherwise raises an exception.
*/
@property (nonatomic) UIColor *color;

/**
* One value is enough — the `values` will fill the rest.
* The default is 70
*/
@property (assign, nonatomic) CGFloat value;

/**
* The duration of the spinning animation
* The default value is 2 seconds.
*/
@property CGFloat duration;

@end
84 changes: 84 additions & 0 deletions Source/YOChartImageKit/YOSpinningDonutChartImage.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#import "YOSpinningDonutChartImage.h"

@implementation YOSpinningDonutChartImage {
CGFloat startAngle;
NSMutableArray *images;
NSInteger index;
}

- (instancetype)init {
self = [super init];
if (self) {
index = 0;
startAngle = self.startAngle;
_duration = 2;
_useGradient = YES;
self.value = 40; // Need to call `self` to invoke the setter
self.lineCapStyle = kCGLineCapRound;
}
return self;
}

- (void)setColor:(UIColor *)color {
self.colors = @[color, [UIColor clearColor]];
}

- (void)setValue:(CGFloat)value {
NSNumber *number = [NSNumber numberWithFloat:value];
if (value < 100) {
self.values = @[number, [NSNumber numberWithFloat:100-value]];
} else {
self.values = @[number];
}
}

- (void)makeGradient {
// Getting all the colors and values for the last one
NSMutableArray *values = [NSMutableArray arrayWithArray:self.values];
NSMutableArray *colors = [NSMutableArray arrayWithArray:self.colors];

// Getting the amount of the last value and computing the alpha decrement
CGFloat last = [(NSNumber*)values.lastObject floatValue];
CGFloat alpha = 1 / last;

// Because we're about to replace it with a gradient
[values removeLastObject];
[colors removeLastObject];
UIColor *gradientColor = [colors lastObject];
const CGFloat multiplicator = (self.lineCapStyle == kCGLineCapRound) ? 1.2 : 1;

for (NSUInteger ai = 0; ai < last; ai++) {
[values addObject:@1];
const CGFloat currentAlpha = 1 - (ai * alpha * multiplicator);
[colors addObject:[gradientColor colorWithAlphaComponent:currentAlpha]];
}
self.values = values;
self.colors = colors;
}

- (UIImage *)drawImage:(CGRect)frame scale:(CGFloat)scale {
if (_useGradient) {
[self makeGradient];
}
// Create the animations
while (index++ < 32) {
// TODO: Probablement make this `32` variable, depending on the `duration` (but
self.startAngle = startAngle;
CGFloat chunk = M_PI_4 / 4;
if (startAngle > (M_PI + M_PI_2 - chunk)) {
startAngle = -M_PI_2;
} else {
startAngle += chunk;
}
UIImage *image = [super drawImage:frame scale:scale];
if (!images) {
images = [NSMutableArray arrayWithObject:image];
} else {
[images addObject:image];
}
}
UIImage *image = [UIImage animatedImageWithImages:images duration:_duration];
return image;
}

@end
16 changes: 16 additions & 0 deletions YOChartImageKit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
objects = {

/* Begin PBXBuildFile section */
4C847B521C60B60F008EF00A /* SpinningDonutChartInterfaceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C847B4F1C60B60F008EF00A /* SpinningDonutChartInterfaceController.swift */; };
4C847B561C60BB06008EF00A /* YOSpinningDonutChartImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C847B541C60BB06008EF00A /* YOSpinningDonutChartImage.m */; };
4C847B581C60BB06008EF00A /* YOSpinningDonutChartImage.m in Sources */ = {isa = PBXBuildFile; fileRef = 4C847B541C60BB06008EF00A /* YOSpinningDonutChartImage.m */; };
4C847B5A1C60BB0F008EF00A /* YOSpinningDonutChartImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C847B591C60BB0F008EF00A /* YOSpinningDonutChartImage.h */; settings = {ATTRIBUTES = (Public, ); }; };
4C847B5B1C60BB0F008EF00A /* YOSpinningDonutChartImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 4C847B591C60BB0F008EF00A /* YOSpinningDonutChartImage.h */; settings = {ATTRIBUTES = (Public, ); }; };
A8154AB81B2A594B001B3024 /* YODonutChartImage.h in Headers */ = {isa = PBXBuildFile; fileRef = A8C0FF6E1B29831E00BA4E81 /* YODonutChartImage.h */; settings = {ATTRIBUTES = (Public, ); }; };
A8154AB91B2A594B001B3024 /* YODonutChartImage.m in Sources */ = {isa = PBXBuildFile; fileRef = A8C0FF6F1B29831E00BA4E81 /* YODonutChartImage.m */; };
A8154ABA1B2A594B001B3024 /* YOBarChartImage.h in Headers */ = {isa = PBXBuildFile; fileRef = A8C0FF731B29888E00BA4E81 /* YOBarChartImage.h */; settings = {ATTRIBUTES = (Public, ); }; };
Expand Down Expand Up @@ -126,6 +131,9 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
4C847B4F1C60B60F008EF00A /* SpinningDonutChartInterfaceController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpinningDonutChartInterfaceController.swift; sourceTree = "<group>"; };
4C847B541C60BB06008EF00A /* YOSpinningDonutChartImage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = YOSpinningDonutChartImage.m; sourceTree = "<group>"; };
4C847B591C60BB0F008EF00A /* YOSpinningDonutChartImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = YOSpinningDonutChartImage.h; sourceTree = "<group>"; };
A8154AB01B2A5917001B3024 /* YOChartImageKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = YOChartImageKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A81AB56C1B2B074000FE8B93 /* YOChartImageKit Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "YOChartImageKit Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; };
A81AB56F1B2B074000FE8B93 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Interface.storyboard; sourceTree = "<group>"; };
Expand Down Expand Up @@ -225,6 +233,7 @@
A872CC211B328A900086554F /* VerticalBarChartInterfaceController.swift */,
A8B509651B688E6700F596ED /* HorizontalBarChartInterfaceController.swift */,
A872CC231B328A9F0086554F /* DonutChartInterfaceController.swift */,
4C847B4F1C60B60F008EF00A /* SpinningDonutChartInterfaceController.swift */,
A81AB57F1B2B074100FE8B93 /* ExtensionDelegate.swift */,
A81AB5811B2B074100FE8B93 /* Assets.xcassets */,
A81AB5831B2B074100FE8B93 /* Info.plist */,
Expand Down Expand Up @@ -272,6 +281,8 @@
A8C0FF741B29888E00BA4E81 /* YOBarChartImage.m */,
A8C0FF6E1B29831E00BA4E81 /* YODonutChartImage.h */,
A8C0FF6F1B29831E00BA4E81 /* YODonutChartImage.m */,
4C847B591C60BB0F008EF00A /* YOSpinningDonutChartImage.h */,
4C847B541C60BB06008EF00A /* YOSpinningDonutChartImage.m */,
);
name = "YOChartImageKit (Common)";
path = Source/YOChartImageKit;
Expand Down Expand Up @@ -331,6 +342,7 @@
files = (
A836D41A1BB244ED00169EC7 /* YOChartImageKit.h in Headers */,
A8154ABC1B2A594B001B3024 /* YOLineChartImage.h in Headers */,
4C847B5B1C60BB0F008EF00A /* YOSpinningDonutChartImage.h in Headers */,
A8154AB81B2A594B001B3024 /* YODonutChartImage.h in Headers */,
A8154ABA1B2A594B001B3024 /* YOBarChartImage.h in Headers */,
);
Expand All @@ -342,6 +354,7 @@
files = (
A851576C1B99CF0600CA58EF /* YOChartImageKit.h in Headers */,
A8C876B41B2AAA5F0092E5FF /* YOLineChartImage.h in Headers */,
4C847B5A1C60BB0F008EF00A /* YOSpinningDonutChartImage.h in Headers */,
A8C876B21B2AAA5F0092E5FF /* YOBarChartImage.h in Headers */,
A8C876B01B2AAA5F0092E5FF /* YODonutChartImage.h in Headers */,
);
Expand Down Expand Up @@ -544,6 +557,7 @@
A8154ABB1B2A594B001B3024 /* YOBarChartImage.m in Sources */,
A8154ABD1B2A594B001B3024 /* YOLineChartImage.m in Sources */,
A8154AB91B2A594B001B3024 /* YODonutChartImage.m in Sources */,
4C847B581C60BB06008EF00A /* YOSpinningDonutChartImage.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand All @@ -555,6 +569,7 @@
A872CC221B328A900086554F /* VerticalBarChartInterfaceController.swift in Sources */,
A86251451B4532C60036BC13 /* YOChart.swift in Sources */,
A872CC241B328A9F0086554F /* DonutChartInterfaceController.swift in Sources */,
4C847B521C60B60F008EF00A /* SpinningDonutChartInterfaceController.swift in Sources */,
A8B509661B688E6700F596ED /* HorizontalBarChartInterfaceController.swift in Sources */,
A81AB5801B2B074100FE8B93 /* ExtensionDelegate.swift in Sources */,
A81AB57E1B2B074100FE8B93 /* BaseInterfaceController.swift in Sources */,
Expand All @@ -569,6 +584,7 @@
A8C876B11B2AAA5F0092E5FF /* YODonutChartImage.m in Sources */,
A8C876B51B2AAA5F0092E5FF /* YOLineChartImage.m in Sources */,
A8C876B31B2AAA5F0092E5FF /* YOBarChartImage.m in Sources */,
4C847B561C60BB06008EF00A /* YOSpinningDonutChartImage.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down