Allow PieChartView to hide labels for tiny slices in Swift - swift

I'm making a chart by using chart iOS framework. but the value will overlay when the slice is tiny. How can I hide it? This question is similar to this GitHub link, But I don't understand how it works. Do I just need to add the code in my View Controller or drag the PieChartRenderer.swift file to my project?
Can someone explain to me how to use the pull request or some open public function...
Sorry I'm new in iOS framework.
This is my code.
#IBOutlet weak var myChart: PieChartView!
var valueColors = [UIColor]()
var dataEntries = [PieChartDataEntry]()
var record = [Record]()
var category = [String]()
var categoryTotal : [Double] = []
var categoryArray : [String] = []
func setDataCount() {
valueAndColor()
let set = PieChartDataSet(values: dataEntries, label: nil)
set.colors = valueColors
set.valueLinePart1OffsetPercentage = 0.8
set.valueLinePart1Length = 0.2
set.valueLinePart2Length = 0.4
set.xValuePosition = .outsideSlice
set.yValuePosition = .outsideSlice
set.selectionShift = 0.0
let data = PieChartData(dataSet: set)
let Formatter:ChartFormatter = ChartFormatter()
data.setValueFormatter(Formatter)
data.setValueFont(.systemFont(ofSize: 11, weight: .light))
data.setValueTextColor(.black)
myChart.data = data
myChart.highlightValues(nil)
}
func setup(pieChartView chartView: PieChartView) {
chartView.usePercentValuesEnabled = true
chartView.drawSlicesUnderHoleEnabled = true
chartView.holeRadiusPercent = 0.58
chartView.chartDescription?.enabled = false
chartView.drawCenterTextEnabled = true
chartView.centerAttributedText = attributedString;
chartView.drawHoleEnabled = true
chartView.rotationAngle = 0
chartView.rotationEnabled = true
chartView.highlightPerTapEnabled = true
}
func valueAndColor(){
for i in 0..<categoryArray.count{
let dataEntry = PieChartDataEntry(value: categoryTotal[i], label: categoryArray[i % categoryArray.count])
dataEntries.append(dataEntry)
if categoryArray[i] == "吃喝" {
valueColors.append(UIColor.yellow)
}else if categoryArray[i] == "δΊ€ι€š"{
valueColors.append(UIColor.red)
}...
}

Create a custom formatter, I set the minNumber as 10.0 and the empty string is returned when a value is less than the minNumber, otherwise the value is returned.
public class ChartFormatter: NSObject, IValueFormatter{
public func stringForValue(_ value: Double, entry: ChartDataEntry, dataSetIndex: Int, viewPortHandler: ViewPortHandler?) -> String {
let total = UserDefaults.standard.double(forKey: "totalValue")
var valueToUse = value/total * 100
valueToUse = Double(round(10*valueToUse)/10)
print("valueToUse: \(valueToUse)")
let minNumber = 10.0
if(valueToUse<minNumber) {
return ""
}
else {
return String(valueToUse) + "%"
}
}
}
Then make sure you set the totalValue variable, store it in UserDefaults (to make it possible to access it in the formatter) and set the formatter for your graph
var totalValue = 0.0
let units = [10.0, 4.0, 6.0, 3.0, 12.0, 16.0]
for a in units {
totalValue += a
}
UserDefaults.standard.set(totalValue, forKey: "totalValue")
let formatter:ChartFormatter = ChartFormatter()
data.setValueFormatter(formatter)
Result:

Newer versions of the Charts library have added this feature and made it a simple property to set on the instance of the PieChartView:
pieChart.sliceTextDrawingThreshold = 20
The sliceTextDrawingThreshold property sets the minimum angle that is required for a label to be drawn.

Related

iOS Charts Radar Chart size

I'm using the Charts library and am trying to replicate this design:
I'm sort of getting there, but the chart is rendering itself way too small:
I'm expecting the chart to fill the entire width of the screen, and use all the vertical space. To be clear: the RadarChartView is the width of the entire black area, and the entire vertical space right up to the legend (which is not part of the chart view itself).
Any ideas?
This is the table cell code that shows the chart:
import Charts
import UIKit
final class ReportSpiderChart: UITableViewCell {
private let labels = ["ARTISTS", "TRACKS", "ALBUMS"]
#IBOutlet private var chartView: RadarChartView!
override func awakeFromNib() {
super.awakeFromNib()
chartView.webLineWidth = 1
chartView.innerWebLineWidth = 1
chartView.webColor = .init(hex: "28282A")
chartView.innerWebColor = .init(hex: "28282A")
chartView.legend.enabled = false
let xAxis = chartView.xAxis
xAxis.labelFont = .systemFont(ofSize: 11, weight: .semibold)
xAxis.xOffset = 0
xAxis.yOffset = 0
xAxis.labelTextColor = .init(hex: "919198")
xAxis.valueFormatter = self
let yAxis = chartView.yAxis
yAxis.labelCount = 3
yAxis.labelFont = .systemFont(ofSize: 11, weight: .semibold)
yAxis.labelTextColor = .init(hex: "919198")
yAxis.axisMinimum = 0
yAxis.drawLabelsEnabled = false
}
func configure(data: ReportData) {
let entries: [RadarChartDataEntry] = [
.init(value: Double(data.artists)),
.init(value: Double(data.tracks)),
.init(value: Double(data.albums)),
]
chartView.yAxis.axisMaximum = Double(max(max(data.artists, data.tracks), data.albums))
let dataSet = RadarChartDataSet(entries: entries)
dataSet.fillColor = UIColor(hex: "FA4B4B").withAlphaComponent(0.75)
dataSet.fillAlpha = 0.75
dataSet.drawFilledEnabled = true
dataSet.lineWidth = 0
dataSet.drawHighlightCircleEnabled = false
dataSet.setDrawHighlightIndicators(false)
let data = RadarChartData(dataSets: [dataSet])
data.setDrawValues(false)
chartView.data = data
}
}
extension ReportSpiderChart: IAxisValueFormatter {
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
return labels[Int(value) % labels.count]
}
}
It seems that their is a spaceTop and spaceBottom property on axis, did you try to set them to 0 on both axis ?
https://github.com/danielgindi/Charts/blob/1bbec78109c7842d130d53ff8811bb6dbe865ba4/Source/Charts/Components/YAxis.swift#L72

Set labels at RadarChartData

I am using Charts v3.2.2 framework by danielgindi for iOS and macOS to draw a RadarChartView. There is a github repository that provides an example xcode project including Playgrounds. One is for RadarChartView.
Following the example I can set RadarChartData with two different RadarChartDataSets
var chartView = RadarChartView(frame: rect)
let data = RadarChartData(dataSets: [set1, set2])
chartView.data = data
It shows a chart like this:
The green labels set to x-axis are numbers from 0.0 to 4.0, but they should be string labels.
I cannot figure out how to set these labels that should be drawn around the RadarChart at the end of each web line. I guess it should be something like this:
data.setLabels("London", "Paris", "Berlin", "New York", "Tokio")
But this isn't working although it is a feature of class RadarChartData to set the desired labels.
Can somebody help me with that issue?
EDIT: complete code example
import Cocoa
import Charts
import PlaygroundSupport
let r = CGRect(x: 0, y: 0, width: 400, height: 400)
var chartView = RadarChartView(frame: r)
// General settings
chartView.webColor = NSUIColor.lightGray
chartView.innerWebColor = NSUIColor.lightGray
chartView.webAlpha = 1.0
// xAxis settings
let xAxis = chartView.xAxis
xAxis.xOffset = 0.0
xAxis.yOffset = 0.0
xAxis.labelTextColor = NSUIColor.green
xAxis.drawLabelsEnabled = true
// yAxis settings
let yAxis = chartView.yAxis
yAxis.labelCount = 5
yAxis.axisMinimum = 0.0
yAxis.axisMaximum = 80.0
yAxis.drawLabelsEnabled = true
// Legend settings
let legend = chartView.legend
// ... (irrelevant)
// Description
chartView.chartDescription?.enabled = true
chartView.chartDescription?.text = "Radar demo"
chartView.chartDescription?.textColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
// RadarChartDataEntry
let mult = 80.0
let min = 20.0
let cnt = 5
var entries1 = [RadarChartDataEntry]()
var entries2 = [RadarChartDataEntry]()
for i in 1...cnt
{
let values1 = (Double(arc4random_uniform(UInt32(mult))) + min)
entries1.append(RadarChartDataEntry(value: values1, data: "a" as AnyObject))
let values2 = (Double(arc4random_uniform(UInt32(mult))) + min)
entries2.append(RadarChartDataEntry(value: values2, data: "b" as AnyObject))
}
// RadarChartDataSet
let set1 = RadarChartDataSet(entries: entries1, label: "Last Week")
set1.drawFilledEnabled = true
set1.fillAlpha = 0.7
set1.lineWidth = 2.0
set1.drawHighlightCircleEnabled = true
set1.setDrawHighlightIndicators(false)
let set2 = RadarChartDataSet(entries: entries2, label: "This Week")
set2.drawFilledEnabled = true
set2.fillAlpha = 0.7
set2.lineWidth = 2.0
set2.drawHighlightCircleEnabled = true
set2.setDrawHighlightIndicators(false)
// RadarChartData
let data = RadarChartData(dataSets: [set1, set2])
data.setLabels("London", "Paris", "Berlin", "New York", "Tokio")
data.setDrawValues ( true )
data.setValueTextColor( NSUIColor.white )
chartView.data = data
chartView.animate(xAxisDuration: 2.0, yAxisDuration: 2.0, easingOption: .easeInBounce)
// show chartView
PlaygroundPage.current.liveView = chartView
You need to override IAxisValueFormatter func stringForValue(_ value: Double, axis: AxisBase?) -> String {} function.
Like below:
Step1: Customize your xAxis with custom formatter.
let xValues = ["X1", "X2", "X3", "X4", "X5", "X6", "X7", "X8", "X9", "X10"]
let chartFormatter = RadarChartFormatter(labels: xValues)
let xAxis = XAxis()
xAxis.valueFormatter = chartFormatter
self.xAxis.valueFormatter = xAxis.valueFormatter
Step2: Implement Custom formatter with below method.
private class RadarChartFormatter: NSObject, IAxisValueFormatter {
var labels: [String] = []
func stringForValue(_ value: Double, axis: AxisBase?) -> String {
if Int(value) < labels.count {
return labels[Int(value)]
}else{
return String("")
}
}
init(labels: [String]) {
super.init()
self.labels = labels
}
}
You will get below output in your RadarCharView:
Hope this will help you to get your custom labels on Radar chart!

Multiple scatterplots using Core Plot and Swift

I'm trying to find a way to add two different scatterplots to a single graph but i wasn't able so far. I've found some examples in Objective-C but nothing in Swift, just the Scatterplot example in the CorePlot 2.1 release, but it plots the same data in two different line colors.
This is what i have so far (only one scatter plot is plotted):
import UIKit
import CorePlot
class ViewController : UIViewController, CPTScatterPlotDataSource {
private var scatterGraph : CPTXYGraph? = nil
typealias plotDataType = [CPTScatterPlotField : Double]
private var dataForPlot = [plotDataType]()
#IBOutlet var graphView: UIView!
// MARK: Initialization
override func viewDidAppear(animated : Bool)
{
super.viewDidAppear(animated)
// Create graph from theme
let newGraph = CPTXYGraph(frame: CGRectZero)
newGraph.applyTheme(CPTTheme(named: kCPTDarkGradientTheme))
let hostingView = graphView as! CPTGraphHostingView
hostingView.hostedGraph = newGraph
// Paddings
newGraph.paddingLeft = 10.0
newGraph.paddingRight = 10.0
newGraph.paddingTop = 10.0
newGraph.paddingBottom = 10.0
// Plot space
let plotSpace = newGraph.defaultPlotSpace as! CPTXYPlotSpace
//plotSpace.allowsUserInteraction = true
plotSpace.yRange = CPTPlotRange(location:0, length:10)
plotSpace.xRange = CPTPlotRange(location:0, length:10)
// Axes
let axisSet = newGraph.axisSet as! CPTXYAxisSet
if let x = axisSet.xAxis {
x.majorIntervalLength = 2
x.orthogonalPosition = 2.0
x.minorTicksPerInterval = 2
}
if let y = axisSet.xAxis {
y.majorIntervalLength = 2
y.minorTicksPerInterval = 5
y.orthogonalPosition = 2.0
y.delegate = self
}
// Create a blue plot area
let boundLinePlot = CPTScatterPlot(frame: CGRectZero)
let blueLineStyle = CPTMutableLineStyle()
blueLineStyle.miterLimit = 1.0
blueLineStyle.lineWidth = 3.0
blueLineStyle.lineColor = CPTColor.blueColor()
boundLinePlot.dataLineStyle = blueLineStyle
boundLinePlot.identifier = "Blue Plot"
boundLinePlot.dataSource = self
newGraph.addPlot(boundLinePlot)
// Add plot symbols
let symbolLineStyle = CPTMutableLineStyle()
symbolLineStyle.lineColor = CPTColor.blackColor()
let plotSymbol = CPTPlotSymbol.ellipsePlotSymbol()
plotSymbol.fill = CPTFill(color: CPTColor.blueColor())
plotSymbol.lineStyle = symbolLineStyle
plotSymbol.size = CGSize(width: 10.0, height: 10.0)
// Put an area gradient under the plot above
let areaColor = CPTColor(componentRed: 0.3, green: 1.0, blue: 0.3, alpha: 0.8)
let areaGradient = CPTGradient(beginningColor: areaColor, endingColor: CPTColor.clearColor())
areaGradient.angle = -90.0
let areaGradientFill = CPTFill(gradient: areaGradient)
// Add some initial data
var contentArray = [plotDataType]()
let plotData1: plotDataType = [.X: 0, .Y: 5]
let plotData2: plotDataType = [.X: 5, .Y: 0]
contentArray.append(plotData1)
contentArray.append(plotData2)
self.dataForPlot = contentArray
self.scatterGraph = newGraph
}
// MARK: - Plot Data Source Methods
func numberOfRecordsForPlot(plot: CPTPlot) -> UInt
{
return UInt(self.dataForPlot.count)
}
func numberForPlot(plot: CPTPlot, field: UInt, recordIndex: UInt) -> AnyObject?
{
let plotField = CPTScatterPlotField(rawValue: Int(field))
if let num = self.dataForPlot[Int(recordIndex)][plotField!] {
return num as NSNumber
}
else {
return nil
}
}
// MARK: - Axis Delegate Methods
func axis(axis: CPTAxis, shouldUpdateAxisLabelsAtLocations locations: NSSet!) -> Bool
{
if let formatter = axis.labelFormatter {
let labelOffset = axis.labelOffset
var newLabels = Set<CPTAxisLabel>()
for tickLocation in locations {
if let labelTextStyle = axis.labelTextStyle?.mutableCopy() as? CPTMutableTextStyle {
if tickLocation.doubleValue >= 0.0 {
labelTextStyle.color = CPTColor.greenColor()
}
else {
labelTextStyle.color = CPTColor.redColor()
}
let labelString = formatter.stringForObjectValue(tickLocation)
let newLabelLayer = CPTTextLayer(text: labelString, style: labelTextStyle)
let newLabel = CPTAxisLabel(contentLayer: newLabelLayer)
newLabel.tickLocation = tickLocation as! NSNumber
newLabel.offset = labelOffset
newLabels.insert(newLabel)
}
axis.axisLabels = newLabels
}
}
return false
}
}
This gives me a single line, but i want to add an additional line with a different data.
Any suggestions?
For a starter, create two CPTScatterPlots (e.g. boundLinePlot1 & boundLinePlot2)and configure them with different colors and different identifier then add them
boundLinePlot1.identifier = "Blue Plot"
boundLinePlot2.identifier = "Green Plot"
newGraph.addPlot(boundLinePlot1)
newGraph.addPlot(boundLinePlot2)
Now in the Plot Data Source Methods (numberOfRecordsForPlot & numberForPlot) calculate return value based on plot.identifier
if plot.identifier == "Blue Plot" {
return dataForPlot1[Int(recordIndex)][plotField!]
} else {
return dataForPlot2[Int(recordIndex)][plotField!]
}

Change yValue label vertical spacing in combined chart (ios-charts)

I have a combined chart that uses a line and bar chart to show yValues.
In some instances the line and bar chart values will overlap, is there a way to set the vertical spacing of the labels for the yValues so that they're not on top of each other (like Jan to Oct in the image)?
Combined Chart Image
I'm using the Charts framework (formerly ios-charts), here is the code to setup the CombineChartView:
let xValues = getXAxisLabelsForYear(year)
let runningTotalsByMonth = getRunningTotalByMonthForYear(year)!
var yValsBar = [BarChartDataEntry]()
var yValsLine = [ChartDataEntry]()
for i in 0 ..< xValues.count {
let yBarDataEntry = BarChartDataEntry(value: monthlyWinnings[i], xIndex: i)
yValsBar.append(yBarDataEntry)
let yLineDataEntry = ChartDataEntry(value: runningTotalsByMonth[i], xIndex: i)
yValsLine.append(yLineDataEntry)
}
let barChartDataSet = BarChartDataSet(yVals: yValsBar, label: "Monthly Winnings")
//setup bar chart
var barChartColors = [UIColor]()
for i in monthlyWinnings {
if i >= 0.0 {
barChartColors.append(myGreen)
} else {
barChartColors.append(UIColor.redColor())
}
}
barChartDataSet.colors = barChartColors
barChartDataSet.barShadowColor = UIColor.clearColor()
barChartDataSet.valueFont = UIFont.systemFontOfSize(10.0)
//setup line chart
let lineChartDataSet = LineChartDataSet(yVals: yValsLine, label: "Cumulative Winnings")
var lineChartColors = [UIColor]()
for i in runningTotalsByMonth {
if i >= 0.0 {
lineChartColors.append(myGreen)
} else {
lineChartColors.append(UIColor.redColor())
}
}
lineChartDataSet.colors = lineChartColors
lineChartDataSet.circleColors = [UIColor.blueColor()]
lineChartDataSet.drawCircleHoleEnabled = false
lineChartDataSet.circleRadius = 5
lineChartDataSet.lineWidth = 2
lineChartDataSet.valueFont = UIFont.systemFontOfSize(10.0)
//combine data
let data = CombinedChartData(xVals: xValues)
data.barData = BarChartData(xVals: xValues, dataSet: barChartDataSet)
data.lineData = LineChartData(xVals: xValues, dataSet: lineChartDataSet)
combinedChartView.data = data
//format the chart
combinedChartView.xAxis.setLabelsToSkip(0)
combinedChartView.xAxis.labelPosition = .Bottom
combinedChartView.descriptionText = ""
combinedChartView.rightAxis.drawLabelsEnabled = false
combinedChartView.rightAxis.drawGridLinesEnabled = false
combinedChartView.drawGridBackgroundEnabled = false
combinedChartView.leftAxis.drawZeroLineEnabled = true
combinedChartView.xAxis.drawGridLinesEnabled = false
combinedChartView.xAxis.wordWrapEnabled = true
You can draw bar chart values below the top of the bar using
chartView.drawValueAboveBarEnabled = false
and setting some color
barChartDataSet.valueTextColor = UIColor.someColor()
Will look like this:
See my comment above, but something like this may work if you're not using auto layout:
let labelA = UILabel()
let labelB = UILabel()
let padding: CGFloat = 5.0 // or whatever
if CGRectIntersectsRect(labelA.frame, labelB.frame) {
// If the minY of labelA is <= labelB's that means labelA is ABOVE labelB
if labelA.frame.minY <= labelB.frame.minY {
// Set it above, with some (optional) padding
labelA.frame.origin.y = labelB.frame.origin.y - padding - labelA.frame.height
} else {
labelB.frame.origin.y = labelA.frame.origin.y - padding - labelB.frame.height
}
}
Of course you'll need additional code for checking if it's too high and other edge cases.

Swift does not draw chart (XYPieChart)

I am using the XYPieChart library in order to draw a pie chart in my project but it doesn't draw the chart when I run it in the simulator. I tried it in a UITableViewController class but did not get result switched to UIViewController but got same results.
What am I doing wrong in here ?
import Foundation
import XYPieChart
class MainVC:UIViewController,XYPieChartDelegate,XYPieChartDataSource{
let z = Share.sharedInstance
let dbm = DatabaseManager()
var chart_dNameArr = [""]
var chart_dAmountArr = [0.0]
override func viewDidLoad() {
self.view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
makeChart()
}
func makeChart(){
let pieChart = XYPieChart()
let viewWidth: Float = Float(pieChart.bounds.size.width / 2)
let viewHeight: Float = Float(pieChart.bounds.size.height / 2)
pieChart.delegate = self
pieChart.dataSource = self
pieChart.startPieAngle = CGFloat(M_PI_2)
pieChart.animationSpeed = 1.5
pieChart.labelColor = UIColor.whiteColor()
pieChart.labelShadowColor = UIColor.blackColor()
pieChart.showPercentage = true
pieChart.backgroundColor = UIColor.whiteColor()
//To make the chart at the center of view
pieChart.pieCenter = CGPointMake(pieChart.bounds.origin.x + CGFloat(viewWidth), pieChart.bounds.origin.y + CGFloat(viewHeight))
//Method to display the pie chart with values.
pieChart.reloadData()
print("made a chart")
}
func numberOfSlicesInPieChart(pieChart: XYPieChart!) -> UInt {
return 2
}
func pieChart(pieChart: XYPieChart!, valueForSliceAtIndex index: UInt) -> CGFloat {
var value: CGFloat = 0.0
if index % 2 == 0 {
value = 25
}
else {
value = 75
}
return value
}
func pieChart(pieChart: XYPieChart!, colorForSliceAtIndex index: UInt) -> UIColor! {
var color: UIColor
if index % 2 == 0 {
color = UIColor.redColor()
}
else {
color = UIColor.greenColor()
}
return color
}
}
I think you did not add the piechart to the view
func makeChart(){
//....
pieChart.reloadData()
addSubView(pieChart) //<- Add this line
}