Swift does not draw chart (XYPieChart) - swift

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
}

Related

adjusting width of bar in bar chart for swift charts(not new swift charts lib)

I am using the swift charts library found here. When I call our api, we have anywhere from 1 data point up to 24 data points for 24 hrs in a day. When I have a lot of data our width of bars are great. When I have 1 or or up to 5 the widths are very wide and dont look good. I assume its because its trying to fill up the space. does anyone know how I could adjust it to be skinny and not take up the space.
In update view I tried to do various different zooms based on the amount of data but it seems as if there is a min zoom or something in the library.
//code below
import Charts
import SwiftUI
struct TransactionBarChartView: UIViewRepresentable {
let entries: [BarChartDataEntry]
let barChart = BarChartView()
#Binding var selectedYear: Int
#Binding var selectedItem: String
func makeUIView(context: Context) -> BarChartView {
barChart.delegate = context.coordinator
return barChart
}
func updateUIView(_ uiView: BarChartView, context: Context) {
let dataSet = BarChartDataSet(entries: entries)
dataSet.label = "Transactions"
uiView.noDataText = "No Data"
uiView.data = BarChartData(dataSet: dataSet)
uiView.rightAxis.enabled = false
/*if uiView.scaleX == 1.0 {
uiView.zoom(scaleX: 1.5, scaleY: 1, x: 0, y: 0)
}*/
if entries.count < 8 {
uiView.zoom(scaleX: 0.2, scaleY: 1, x: 0, y: 0)
}
if entries.count < 4 {
uiView.zoom(scaleX: 0.2, scaleY: 1, x: 0, y: 0)
}
if entries.count < 2 {
uiView.zoom(scaleX: 0.0005, scaleY: 1, x: 0.05, y: 0)
}
uiView.setScaleEnabled(false)
formatDataSet(dataSet: dataSet)
formatLeftAxis(leftAxis: uiView.leftAxis)
formatXAxis(xAxis: uiView.xAxis)
formatLegend(legend: uiView.legend)
uiView.notifyDataSetChanged()
}
class Coordinator: NSObject, ChartViewDelegate {
let parent:TransactionBarChartView
init(parent: TransactionBarChartView) {
self.parent = parent
}
func chartValueSelected(_ chartView: ChartViewBase, entry: ChartDataEntry, highlight: Highlight) {
let month = WineTransaction.months[Int(entry.x)]
let quantity = Int(entry.y)
parent.selectedItem = "\(quantity) sold in \(month)"
}
}
func makeCoordinator() -> Coordinator {
return Coordinator(parent: self)
}
func formatDataSet(dataSet: BarChartDataSet) {
dataSet.colors = [.red]
dataSet.valueColors = [.red]
let formatter = NumberFormatter()
formatter.numberStyle = .none
dataSet.valueFormatter = DefaultValueFormatter(formatter: formatter)
}
func formatLeftAxis(leftAxis: YAxis) {
leftAxis.labelTextColor = .red
let formatter = NumberFormatter()
formatter.numberStyle = .none
leftAxis.valueFormatter = DefaultAxisValueFormatter(formatter: formatter)
leftAxis.axisMinimum = 0
}
func formatXAxis(xAxis: XAxis) {
xAxis.valueFormatter = IndexAxisValueFormatter(values: WineTransaction.months)
xAxis.labelPosition = .bottom
xAxis.granularityEnabled = true
xAxis.labelTextColor = .red
}
func formatLegend(legend: Legend) {
legend.textColor = .red
legend.horizontalAlignment = .right
legend.verticalAlignment = .top
legend.drawInside = true
legend.yOffset = 30.0
}
}

Allow PieChartView to hide labels for tiny slices in 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.

Smooth zoomable collection view

I am trying to figure out how could I achieve smooth zooming inside UICollectionView, so far I've got first step - It's zooming in/out but without greater control on which value I would like to stop it.
Here is my implementation of zoom in/out :
Default zooming lvl.
EDIT
Improved zooming algorythm.
var lastZoomLvl: CGFloat = 1 {
willSet(newValue) {
self.lastZoomLvl = newValue
}
}
// Zooming logic.
func zoom(scale: CGFloat) {
if scale > 1 {
lastZoomLvl += scale
} else {
lastZoomLvl -= scale
}
if lastZoomLvl < 1 {
lastZoomLvl = 1
} else if lastZoomLvl > 12 {
lastZoomLvl = 12
}
flowLayout.zoomMultiplier = lastZoomLvl
}
The call of zoom method is in VC with UICollectionView.
func zoom(_ gesture: UIPinchGestureRecognizer) {
let scale = gesture.scale
if gesture.state == .began {
chartCollectionView.isScrollEnabled = false
}
if gesture.state == .ended {
chartCollectionView.isScrollEnabled = true
if scale > 1 {
self.chart.lastZoomLvl += scale
}
}
chartCollectionView.performBatchUpdates({
self.chart.zoom(scale: scale)
}, completion: { _ in
// TODO: Maybe something in the future ?
})
}
As you can see I've achieve zooming by adding UIPinchGestureRecognizer and then using performBatchUpdates my layout is going to be updated by new cell sizes.
The layout implementation :
class ChartCollectionViewFlowLayout: UICollectionViewFlowLayout {
private var cellAttributes = [UICollectionViewLayoutAttributes]()
private var supplementaryAttributes = [UICollectionViewLayoutAttributes]()
private var newIndexPaths = [IndexPath]()
private var removedIndexPaths = [IndexPath]()
private let numberOfSections = 5
private var suplementaryWidth: CGFloat = 0
private let horizontalDividerHeight: CGFloat = 1
private var timeLineHeight: CGFloat = 0
fileprivate var contentSize: CGSize?
static let numberOfVisibleCells: Int = 4
// Calculate the width available for time entry cells.
var itemsWidth: CGFloat = 0 {
willSet(newValue) {
self.itemsWidth = newValue
}
}
var cellWidth: CGFloat = 0 {
willSet(newValue) {
self.cellWidth = newValue
}
}
var numberOfCells: Int = 24 {
willSet(newValue) {
self.numberOfCells = newValue
}
}
var zoomMultiplier: CGFloat = 1 {
willSet(newValue) {
self.zoomMultiplier = newValue
}
}
override init() {
super.init()
scrollDirection = .horizontal
sectionHeadersPinToVisibleBounds = true
sectionFootersPinToVisibleBounds = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func invalidateLayout() {
super.invalidateLayout()
supplementaryAttributes.removeAll()
cellAttributes.removeAll()
}
override func prepare() {
if collectionView == nil { return }
var cellX: CGFloat = 0
var rowY: CGFloat = 0
let xOffset = collectionView!.contentOffset.x
// Calculate the height of a row.
let availableHeight = collectionView!.bounds.height - collectionView!.contentInset.top - collectionView!.contentInset.bottom - CGFloat(numberOfSections - 1) * horizontalDividerHeight
if deviceIdiom == .pad {
suplementaryWidth = 75
timeLineHeight = 30
} else {
suplementaryWidth = 30
timeLineHeight = 25
}
itemsWidth = collectionView!.bounds.width - collectionView!.contentInset.left - collectionView!.contentInset.right - suplementaryWidth
var rowHeight = availableHeight / CGFloat(numberOfSections)
let differenceBetweenTimeLine = rowHeight - timeLineHeight
let toAddToRowY = differenceBetweenTimeLine / CGFloat(numberOfSections - 1)
rowHeight += toAddToRowY
// For each section.
for i in 0..<numberOfSections {
// Generate and store layout attributes header cell.
let headerIndexPath = IndexPath(item: 0, section: i)
let headerCellAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, with: headerIndexPath)
supplementaryAttributes.append(headerCellAttributes)
if i == 0 {
rowY = CGFloat(i) * (30 + horizontalDividerHeight)
headerCellAttributes.frame = CGRect(x: 0, y: rowY, width: suplementaryWidth, height: timeLineHeight)
} else {
rowY = CGFloat(i) * (rowHeight + horizontalDividerHeight) - (toAddToRowY * CGFloat(numberOfSections))
headerCellAttributes.frame = CGRect(x: 0, y: rowY, width: suplementaryWidth, height: rowHeight)
}
// Sticky header / footer while scrolling.
headerCellAttributes.zIndex = 1
headerCellAttributes.frame.origin.x = xOffset
// Set the initial X position for cell.
cellX = suplementaryWidth
// For each cell set a width.
for j in 0..<numberOfCells {
//Get the width of the cell.
cellWidth = CGFloat(Double(itemsWidth) / Double(numberOfCells)) * zoomMultiplier
// Generate and store layout attributes for the cell
let cellIndexPath = IndexPath(item: j, section: i)
let entryCellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndexPath)
cellAttributes.append(entryCellAttributes)
if i == 0 {
entryCellAttributes.frame = CGRect(x: cellX, y: rowY, width: cellWidth, height: timeLineHeight)
} else {
entryCellAttributes.frame = CGRect(x: cellX, y: rowY, width: cellWidth, height: rowHeight)
}
cellX += cellWidth
}
}
contentSize = CGSize(width: cellX, height: collectionView!.bounds.height)
super.prepare()
}
override var collectionViewContentSize: CGSize {
get {
if contentSize != nil {
return contentSize!
}
return .zero
}
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return cellAttributes.first(where: { (attributes) -> Bool in
return attributes.indexPath == indexPath
})
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return supplementaryAttributes.first(where: { (attributes) -> Bool in
return attributes.indexPath == indexPath
})
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributes = [UICollectionViewLayoutAttributes]()
for attribute in supplementaryAttributes {
if attribute.frame.intersects(rect) {
attributes.append(attribute)
}
}
for attribute in cellAttributes {
if attribute.frame.intersects(rect) {
attributes.append(attribute)
}
}
return attributes
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
Layout let me to place the sticky header to the left hand side and the cell size is defined by cellWidth. I suppose that the size is growing exponentially, how could I get a better control ? Does any one have ideas how could I prove it ?
Thanks in advance!

Decreasing number of cells causes NSInternalInconsistencyException exception

I've been working from a while on the custom layouts for UICollectionView's. So far so good I've manage to place a header on the left hand side, made it sticky to avoid scrolling for him and also I've made a zoom in-out algorithm.
In my custom UICollectionView I have UIPinchGestureRecognizer which tell us if we zoomed in/out.
let pinchRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(zoom(_:)))
pinchRecognizer.delegate = self
isUserInteractionEnabled = true
collectionView.addGestureRecognizer(pinchRecognizer)
func zoom(_ gesture: UIPinchGestureRecognizer) {
let scale = gesture.scale
if gesture.state == .ended {
if scale > 1 {
self.lastZoomScale += self.zoomScale
} else if scale < 1 {
self.lastZoomScale -= self.zoomScale
}
}
collectionView.performBatchUpdates({
if scale > 1 {
self.flowLayout.isZoomed = true
self.zoomScale = scale * 4
// If last zoom is equal 0 then user does not zoom it yet.
if self.lastZoomScale == 0 {
// Assign current zoom.
self.flowLayout.previousCellCount = Int(self.zoomScale)
self.flowLayout.numberOfCells = Int(self.zoomScale)
} else {
// User did scroll before and max of zooming scale might be 24 becouse of 24 hours.
if self.lastZoomScale > 24 {
self.lastZoomScale = 24
}
self.flowLayout.previousCellCount = Int(self.lastZoomScale)
self.flowLayout.numberOfCells = Int(self.lastZoomScale)
}
} else if scale < 1 {
self.zoomScale = scale * 4
// User did not zoom in then lets mark it as a default number of cells.
if self.lastZoomScale == 0 {
self.flowLayout.numberOfCells = 4
} else {
let scrollingDifference = self.lastZoomScale - self.zoomScale
if scrollingDifference > 4 {
self.flowLayout.previousCellCount = Int(self.lastZoomScale)
self.flowLayout.isZoomed = false
self.flowLayout.numberOfCells = Int(scrollingDifference)
} else {
self.flowLayout.numberOfCells = 4
}
}
}
}) { _ in
// TODO: Change size of cells while zoomed in/out.
// self.collectionView.performBatchUpdates({
// self.flowLayout.zoomMultiplier = 2
// })
}
}
From the first statement we can see that while the state is ended then increase lastZoomScale for aditional scale and also decrease while we zooming out back to oryginal scale.
Later on the UICollectionView have minimal 4 visible cells as an example and 24 maximum as following this. From the comments we can see what is going on. Also there is TODO in the completion statement, but we are not intrested into this.
Going thorugh the code time for the custom flow layout which looks like this :
import UIKit
class MyCollectionViewFlowLayout: UICollectionViewFlowLayout {
private var cellAttributes = [UICollectionViewLayoutAttributes]()
private var headerAttributes = [UICollectionViewLayoutAttributes]()
private var newIndexPaths = [IndexPath]()
private var removedIndexPaths = [IndexPath]()
private let numberOfSections = 4
private let headerWidth: CGFloat = 75
private let verticalDividerWidth: CGFloat = 1
private let horizontalDividerHeight: CGFloat = 1
fileprivate var contentSize: CGSize?
static let numberOfVisibleCells: Int = 4
var previousCellCount: Int = 0 {
willSet(newValue) {
self.previousCellCount = newValue
}
}
var numberOfCells: Int = 4 {
willSet(newValue) {
self.numberOfCells = newValue
} didSet {
invalidateLayout()
}
}
var zoomMultiplier: CGFloat = 1 {
willSet(newValue) {
self.zoomMultiplier = newValue
} didSet {
invalidateLayout()
}
}
var isZoomed: Bool = false {
willSet(newValue) {
self.isZoomed = newValue
}
}
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
removedIndexPaths.removeAll()
super.prepare(forCollectionViewUpdates: updateItems)
var howManyToDelete = previousCellCount - numberOfCells
while howManyToDelete > 1 {
removedIndexPaths.append(cellAttributes.last!.indexPath)
howManyToDelete -= 1
}
}
override func prepare() {
scrollDirection = .horizontal
minimumLineSpacing = 1
minimumInteritemSpacing = 1
sectionHeadersPinToVisibleBounds = true
if collectionView == nil { return }
cellAttributes.removeAll()
headerAttributes.removeAll()
var cellX: CGFloat = 0
let xOffset = collectionView!.contentOffset.x
// Calculate the height of a row.
let availableHeight = collectionView!.bounds.height - collectionView!.contentInset.top - collectionView!.contentInset.bottom - CGFloat(numberOfSections - 1) * horizontalDividerHeight
let rowHeight = availableHeight / CGFloat(numberOfSections)
// Calculate the width available for time entry cells.
let itemsWidth: CGFloat = collectionView!.bounds.width - collectionView!.contentInset.left - collectionView!.contentInset.right - headerWidth
// For each section.
for i in 0..<numberOfSections {
// Y coordinate of the row.
let rowY = CGFloat(i) * (rowHeight + horizontalDividerHeight)
// Generate and store layout attributes header cell.
let headerIndexPath = IndexPath(item: 0, section: i)
let headerCellAttributes = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, with: headerIndexPath)
headerAttributes.append(headerCellAttributes)
headerCellAttributes.frame = CGRect(x: 0, y: rowY, width: headerWidth, height: rowHeight)
// Sticky header while scrolling.
headerCellAttributes.zIndex = 1
headerCellAttributes.frame.origin.x = xOffset
// TODO: Count of the cells for each section.
// Guesing it is going to be 24 = 24hs per day.
// Set the initial X position for cell.
cellX = headerWidth
// For each cell set a width.
for j in 0..<numberOfCells {
//Get the width of the cell.
var cellWidth = CGFloat(Double(itemsWidth) / Double(numberOfCells)) * zoomMultiplier
cellWidth -= verticalDividerWidth
cellX += verticalDividerWidth
// Generate and store layout attributes for the cell
let cellIndexPath = IndexPath(item: j, section: i)
let entryCellAttributes = UICollectionViewLayoutAttributes(forCellWith: cellIndexPath)
cellAttributes.append(entryCellAttributes)
entryCellAttributes.frame = CGRect(x: cellX, y: rowY, width: cellWidth, height: rowHeight)
cellX += cellWidth
}
}
contentSize = CGSize(width: cellX, height: collectionView!.bounds.height)
}
override var collectionViewContentSize: CGSize {
get {
if contentSize != nil {
return contentSize!
}
return .zero
}
}
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
// FIXME: Zoom-out crash.
return cellAttributes.first(where: { attributes -> Bool in
return attributes.indexPath == indexPath
})
}
override func layoutAttributesForSupplementaryView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return headerAttributes.first(where: { attributes -> Bool in
return attributes.indexPath == indexPath
})
}
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var attributes = [UICollectionViewLayoutAttributes]()
for attribute in headerAttributes {
if attribute.frame.intersects(rect) {
attributes.append(attribute)
}
}
for attribute in cellAttributes {
if attribute.frame.intersects(rect) {
attributes.append(attribute)
}
}
print("Inside layout attribs \(cellAttributes.count)")
return attributes
}
override func indexPathsToDeleteForSupplementaryView(ofKind elementKind: String) -> [IndexPath] {
return removedIndexPaths
}
override func finalizeCollectionViewUpdates() {
removedIndexPaths.removeAll()
}
override func shouldInvalidateLayout(forBoundsChange newBounds: CGRect) -> Bool {
return true
}
}
As you can see I've manage to paste a comments into the code.
My problem is that whenever I increase numberOfCells it is going to works like a charm, but whenever I am going to decrease number of them it is going to crash with an exception :
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no UICollectionViewLayoutAttributes instance for -layoutAttributesForItemAtIndexPath: <NSIndexPath: 0xc000000001600016> {length = 2, path = 0 - 11}'
I've already deleted these indexes (prepare(forCollectionViewUpdates...), later on inside indexPathsToDeleteForSupplementaryView) and have no idea what is going on there.
Any one have an idea how could I fix this ?
Thank you !

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!]
}