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

Prevent labels overlapping #62

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [0.0.8] - 2020-04-02
- Swift 5 support
- Allow to prevent slice label overlaps

## [0.0.7] - 2018-10-03
- Swift 4.2 support

Expand Down
2 changes: 1 addition & 1 deletion PieCharts.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "PieCharts"
s.version = "0.0.7"
s.version = "0.0.8"
s.summary = "Easy to use and highly customizable pie charts library for iOS"
s.homepage = "https://github.com/i-schuetz/PieCharts"
s.license = { :type => "Apache License, Version 2.0", :file => "LICENSE" }
Expand Down
28 changes: 24 additions & 4 deletions PieCharts.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@

/* Begin PBXBuildFile section */
4170AA701E35FFD4008A9629 /* ProgrammaticalDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4170AA6F1E35FFD4008A9629 /* ProgrammaticalDemo.swift */; };
68BB077F24365D0800697753 /* DoughnutDemoEdgeCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68BB077E24365D0800697753 /* DoughnutDemoEdgeCase.swift */; };
68BB078124365D1F00697753 /* CustomViewsDemoEdgeCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 68BB078024365D1F00697753 /* CustomViewsDemoEdgeCase.swift */; };
68BB078324365E8500697753 /* CustomViewsDemoEdgeCase.xib in Resources */ = {isa = PBXBuildFile; fileRef = 68BB078224365E8500697753 /* CustomViewsDemoEdgeCase.xib */; };
68BB078524365E9D00697753 /* DoughnutDemoEdgeCase.xib in Resources */ = {isa = PBXBuildFile; fileRef = 68BB078424365E9D00697753 /* DoughnutDemoEdgeCase.xib */; };
6A85B42C1E1DDB25007CA217 /* Int.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A85B42B1E1DDB25007CA217 /* Int.swift */; };
6A85B42E1E1DDB44007CA217 /* FloatingPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A85B42D1E1DDB44007CA217 /* FloatingPoint.swift */; };
6AAD967D1E1C030E00BABC91 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AAD96701E1C030E00BABC91 /* AppDelegate.swift */; };
Expand Down Expand Up @@ -70,6 +74,10 @@

/* Begin PBXFileReference section */
4170AA6F1E35FFD4008A9629 /* ProgrammaticalDemo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgrammaticalDemo.swift; sourceTree = "<group>"; };
68BB077E24365D0800697753 /* DoughnutDemoEdgeCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DoughnutDemoEdgeCase.swift; sourceTree = "<group>"; };
68BB078024365D1F00697753 /* CustomViewsDemoEdgeCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomViewsDemoEdgeCase.swift; sourceTree = "<group>"; };
68BB078224365E8500697753 /* CustomViewsDemoEdgeCase.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CustomViewsDemoEdgeCase.xib; sourceTree = "<group>"; };
68BB078424365E9D00697753 /* DoughnutDemoEdgeCase.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = DoughnutDemoEdgeCase.xib; sourceTree = "<group>"; };
6A85B42B1E1DDB25007CA217 /* Int.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Int.swift; sourceTree = "<group>"; };
6A85B42D1E1DDB44007CA217 /* FloatingPoint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FloatingPoint.swift; sourceTree = "<group>"; };
6AAD96701E1C030E00BABC91 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -137,9 +145,13 @@
6AAD96741E1C030E00BABC91 /* Main.storyboard */,
6AAD96761E1C030E00BABC91 /* CustomViewsDemo.swift */,
6AAD96771E1C030E00BABC91 /* CustomViewsDemo.xib */,
68BB078024365D1F00697753 /* CustomViewsDemoEdgeCase.swift */,
68BB078224365E8500697753 /* CustomViewsDemoEdgeCase.xib */,
6AAD96781E1C030E00BABC91 /* DetailViewController.swift */,
6AAD96791E1C030E00BABC91 /* DoughnutDemo.swift */,
6AAD967A1E1C030E00BABC91 /* DoughnutDemo.xib */,
68BB077E24365D0800697753 /* DoughnutDemoEdgeCase.swift */,
68BB078424365E9D00697753 /* DoughnutDemoEdgeCase.xib */,
6AAD967C1E1C030E00BABC91 /* MasterViewController.swift */,
4170AA6F1E35FFD4008A9629 /* ProgrammaticalDemo.swift */,
6AAD96881E1C053100BABC91 /* Supporting files */,
Expand Down Expand Up @@ -381,26 +393,26 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0820;
LastUpgradeCheck = 1000;
LastUpgradeCheck = 1140;
ORGANIZATIONNAME = "Ivan Schuetz";
TargetAttributes = {
6AC1CD421E1C013900120720 = {
CreatedOnToolsVersion = 8.2.1;
DevelopmentTeam = 37QAPDY2PR;
LastSwiftMigration = 1000;
LastSwiftMigration = 1140;
ProvisioningStyle = Automatic;
};
6AFF22B61E16DF520001B0A4 = {
CreatedOnToolsVersion = 8.2.1;
DevelopmentTeam = 37QAPDY2PR;
LastSwiftMigration = 1000;
LastSwiftMigration = 1140;
ProvisioningStyle = Automatic;
};
};
};
buildConfigurationList = 6AFF22B21E16DF520001B0A4 /* Build configuration list for PBXProject "PieCharts" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Expand Down Expand Up @@ -431,7 +443,9 @@
files = (
6AAD96851E1C030E00BABC91 /* DoughnutDemo.xib in Resources */,
6AAD96801E1C030E00BABC91 /* Main.storyboard in Resources */,
68BB078524365E9D00697753 /* DoughnutDemoEdgeCase.xib in Resources */,
6AAD967E1E1C030E00BABC91 /* Assets.xcassets in Resources */,
68BB078324365E8500697753 /* CustomViewsDemoEdgeCase.xib in Resources */,
6AAD96821E1C030E00BABC91 /* CustomViewsDemo.xib in Resources */,
6AAD967F1E1C030E00BABC91 /* LaunchScreen.storyboard in Resources */,
);
Expand Down Expand Up @@ -478,7 +492,9 @@
6AAD96831E1C030E00BABC91 /* DetailViewController.swift in Sources */,
4170AA701E35FFD4008A9629 /* ProgrammaticalDemo.swift in Sources */,
6AAD96811E1C030E00BABC91 /* CustomViewsDemo.swift in Sources */,
68BB077F24365D0800697753 /* DoughnutDemoEdgeCase.swift in Sources */,
6AAD967D1E1C030E00BABC91 /* AppDelegate.swift in Sources */,
68BB078124365D1F00697753 /* CustomViewsDemoEdgeCase.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -562,6 +578,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
Expand Down Expand Up @@ -620,6 +637,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
Expand Down Expand Up @@ -672,6 +690,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 37QAPDY2PR;
INFOPLIST_FILE = "$(SRCROOT)/PieChartsDemo/Supporting files/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
Expand All @@ -687,6 +706,7 @@
buildSettings = {
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 37QAPDY2PR;
INFOPLIST_FILE = "$(SRCROOT)/PieChartsDemo/Supporting files/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
LastUpgradeVersion = "1140"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down Expand Up @@ -29,8 +29,6 @@
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
Expand All @@ -51,8 +49,6 @@
ReferencedContainer = "container:PieCharts.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
Expand Down
5 changes: 4 additions & 1 deletion PieCharts/Core/Chart/PieChart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ import UIKit
}
}
}


/// minimum required angle to prevent slice labels intersection
public var labelOverlapAngleConst: CGFloat?

var animated: Bool {
return animDuration > 0
}
Expand Down
14 changes: 14 additions & 0 deletions PieCharts/Core/Slice/PieSliceLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ open class PieSliceLayer: CALayer, CAAnimationDelegate {

fileprivate(set) var startAngle: CGFloat = 0
fileprivate(set) var endAngle: CGFloat = 0
fileprivate(set) var labelExtraAngle: CGFloat = 0

var angles: (CGFloat, CGFloat) = (0, 0) {
didSet {
Expand Down Expand Up @@ -251,6 +252,12 @@ open class PieSliceLayer: CALayer, CAAnimationDelegate {
return CGPoint(x: x + center.x, y: y + center.y)
}

public func midPointForLabel(radius: CGFloat) -> CGPoint {
let x = cos(midAngleForLabel()) * radius
let y = sin(midAngleForLabel()) * radius
return CGPoint(x: x + center.x, y: y + center.y)
}

public func maxRectWidth(center: CGPoint, height: CGFloat) -> CGFloat {

let startAngleCC = CGFloat.pi * 2 - startAngle + referenceAngle
Expand All @@ -276,6 +283,13 @@ open class PieSliceLayer: CALayer, CAAnimationDelegate {
return min(smallestStartAngleDistance, smallestEndAngleDistance) * 2
}

func setExtraAngleForLabel(_ angle: CGFloat) {
labelExtraAngle = angle
}

func midAngleForLabel() -> CGFloat {
return midAngle + labelExtraAngle
}

open override var debugDescription: String {
return "{data: \(String(describing: sliceData)), start: \(startAngle.radiansToDegrees), end: \(endAngle.radiansToDegrees)}"
Expand Down
31 changes: 27 additions & 4 deletions PieCharts/Layer/Impl/LineText/PieLineTextLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public struct PieLineTextLayerSettings {
open class PieLineTextLayer: PieChartLayer {

public weak var chart: PieChart?

public var settings: PieLineTextLayerSettings = PieLineTextLayerSettings()

fileprivate var sliceViews = [PieSlice: (CALayer, UILabel)]()
Expand All @@ -40,14 +40,16 @@ open class PieLineTextLayer: PieChartLayer {
public func addItems(slice: PieSlice) {
guard sliceViews[slice] == nil else {return}

calculateLabelExtraAngles()

let p1 = slice.view.calculatePosition(angle: slice.view.midAngle, p: slice.view.center, offset: slice.view.outerRadius + settings.chartOffset)
let p2 = slice.view.calculatePosition(angle: slice.view.midAngle, p: slice.view.center, offset: slice.view.outerRadius + settings.segment1Length)
let p2 = slice.view.calculatePosition(angle: slice.view.midAngleForLabel(), p: slice.view.center, offset: slice.view.outerRadius + settings.segment1Length)

let angle = slice.view.midAngle.truncatingRemainder(dividingBy: (CGFloat.pi * 2))
let isRightSide = angle >= 0 && angle <= (CGFloat.pi / 2) || (angle > (CGFloat.pi * 3 / 2) && angle <= CGFloat.pi * 2)

let p3 = CGPoint(x: p2.x + (isRightSide ? settings.segment2Length : -settings.segment2Length), y: p2.y)

let lineLayer = createLine(p1: p1, p2: p2, p3: p3)
let label = createLabel(slice: slice, isRightSide: isRightSide, referencePoint: p3)

Expand All @@ -60,6 +62,27 @@ open class PieLineTextLayer: PieChartLayer {
sliceViews[slice] = (lineLayer, label)
}

public func calculateLabelExtraAngles() {
chart?.slices.forEach { calculateLabelExtraAngle(for: $0) }
}

public func calculateLabelExtraAngle(for slice: PieSlice) {
guard let chart = chart else { return }
guard let labelOverlapAngleConst = chart.labelOverlapAngleConst else { return }
let sliceIndex = slice.data.id
guard sliceIndex > 0 else { return }
guard let previousSlice = chart.slices.filter({ $0.data.id == (sliceIndex - 1) }).first else { return }

let diffBetweenSlices = slice.view.midAngleForLabel() - previousSlice.view.midAngleForLabel()
if diffBetweenSlices > labelOverlapAngleConst {
// np wont intersect
return
}

let newExtraAngleForSlice = previousSlice.view.midAngleForLabel() + labelOverlapAngleConst - slice.view.midAngle
slice.view.setExtraAngleForLabel( newExtraAngleForSlice )
}

public func createLine(p1: CGPoint, p2: CGPoint, p3: CGPoint) -> CALayer {
let path = UIBezierPath()
path.move(to: p1)
Expand Down Expand Up @@ -97,7 +120,7 @@ open class PieLineTextLayer: PieChartLayer {

let offset = selected ? slice.view.selectedOffset : -slice.view.selectedOffset
UIView.animate(withDuration: 0.15) {
label.center = slice.view.calculatePosition(angle: slice.view.midAngle, p: label.center, offset: offset)
label.center = slice.view.calculatePosition(angle: slice.view.midAngleForLabel(), p: label.center, offset: offset)
}

layer.position = slice.view.calculatePosition(angle: slice.view.midAngle, p: layer.position, offset: offset)
Expand Down
30 changes: 29 additions & 1 deletion PieCharts/Layer/Impl/View/PieCustomViewsLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,15 @@ open class PieCustomViewsLayer: PieChartLayer {
public func addItems(slice: PieSlice) {
guard sliceViews[slice] == nil else {return}

let center = settings.viewRadius.map{slice.view.midPoint(radius: $0)} ?? slice.view.arcCenter
calculateLabelExtraAngles()

let center: CGPoint
if let viewRadius = settings.viewRadius {
center = slice.view.midPointForLabel(radius: viewRadius)
} else {
center = slice.view.arcCenter
}

guard let view = viewGenerator?(slice, center) else {print("Need a view generator to create views!"); return}

let size = view.frame.size
Expand All @@ -63,6 +70,27 @@ open class PieCustomViewsLayer: PieChartLayer {
sliceViews[slice] = view
}

public func calculateLabelExtraAngles() {
chart?.slices.forEach { calculateLabelExtraAngle(for: $0) }
}

public func calculateLabelExtraAngle(for slice: PieSlice) {
guard let chart = chart else { return }
guard let labelOverlapAngleConst = chart.labelOverlapAngleConst else { return }
let sliceIndex = slice.data.id
guard sliceIndex > 0 else { return }
guard let previousSlice = chart.slices.filter({ $0.data.id == (sliceIndex - 1) }).first else { return }

let diffBetweenSlices = slice.view.midAngleForLabel() - previousSlice.view.midAngleForLabel()
if diffBetweenSlices > labelOverlapAngleConst {
// np wont intersect
return
}

let newExtraAngleForSlice = previousSlice.view.midAngleForLabel() + labelOverlapAngleConst - slice.view.midAngle
slice.view.setExtraAngleForLabel( newExtraAngleForSlice )
}

public func onSelected(slice: PieSlice, selected: Bool) {
guard let label = sliceViews[slice] else {print("Invalid state: slice not in dictionary"); return}

Expand Down
Loading