I am working on a chart with data set change when the button is pressed. But it looks like when I have more than 2 bars, the chart origin point shifts.
Two columns is fine:
Creating data set:
private func buildMoodDummyBarData(period: Period) -> [BarChartDataEntry] {
if period == .week {
let entry1 = BarChartDataEntry(x: 0, y: 720)
let entry2 = BarChartDataEntry(x: 1, y: 440)
let entry3 = BarChartDataEntry(x: 2, y: 0)
let entry4 = BarChartDataEntry(x: 3, y: 30)
let entry5 = BarChartDataEntry(x: 4, y: 30)
return [entry1, entry2, entry3, entry4, entry5]
} else if period == .month {
let entry1 = BarChartDataEntry(x: 0, y: 100)
let entry2 = BarChartDataEntry(x: 1, y: 300)
let entry3 = BarChartDataEntry(x: 2, y: 50)
let entry4 = BarChartDataEntry(x: 3, y: 30)
let entry5 = BarChartDataEntry(x: 4, y: 30)
return [entry1, entry2, entry3, entry4, entry5]
} else {
return []
}
}
Finishing building data set and changing bar colors:
func buildMoodBarData(period: Period) -> BarChartData? {
let entries = buildMoodDummyBarData(period: period)
let set = BarChartDataSet(values: entries, label: nil)
set.highlightEnabled = false
let tempData = BarChartData(dataSet: set) // Just to find max
let max = tempData.yMax
let onePercent = max / 100
var colors = [UIColor]()
for item in entries {
let percentValue = item.y / onePercent
colors.append(setColor(value: percentValue))
}
set.colors = colors
let data = BarChartData(dataSet: set)
data.setDrawValues(true)
data.barWidth = 0.72
return data
}
// barChartView is storyboard outlet
private func makeMoodBarChart() {
barChartView.backgroundColor = UIColor.red
barChartView.chartDescription = nil
barChartView.leftAxis.enabled = false
barChartView.rightAxis.enabled = false
barChartView.legend.enabled = false
barChartView.xAxis.enabled = false
}
private func updateMoodBarChart(with data: BarChartData) {
barChartView.data = data
barChartView.fitBars = false
barChartView.fitScreen()
}
The problem is related to data set. When I replaced it to following data, problem disappeared. I think issue occurs because Y scale values was changing too much.
private func buildMoodDummyBarData(period: Period) -> [BarChartDataEntry] {
if period == .week {
let entry1 = BarChartDataEntry(x: 0, y: 10)
let entry2 = BarChartDataEntry(x: 1, y: 50)
let entry3 = BarChartDataEntry(x: 2, y: 100)
let entry4 = BarChartDataEntry(x: 3, y: 20)
let entry5 = BarChartDataEntry(x: 4, y: 30)
return [entry1, entry2, entry3, entry4, entry5]
} else if period == .month {
let entry1 = BarChartDataEntry(x: 0, y: 100)
let entry2 = BarChartDataEntry(x: 1, y: 30)
let entry3 = BarChartDataEntry(x: 2, y: 30)
let entry4 = BarChartDataEntry(x: 3, y: 20)
let entry5 = BarChartDataEntry(x: 4, y: 30)
return [entry1, entry2, entry3, entry4, entry5]
} else {
return []
}
}
This doesn't mean min and max value has to be exactly the same in both data sets (20 to 100), however if difference is too big (30 to 320 in first data set and 30 to 720 in second), chart baseline changes.
This is not ideal solution.
While updating Barchart with new dataset make chartdata as nil
if let bardata = buildMoodBarData(period: state) {
barChatView.data = nil
updateMoodBarChart(with: bardata)
}
Related
I'm using the Charts library to generate a bar chart. This is the design I've been given, which I am trying to recreate:
This is what I've managed to build:
It's getting close, but I'm having serious issues showing the bars. For example, the x-axis labels need to be shown in the center of the group, not directly under the vertical line. And the last gray bar is not shown at all for some reason.
Code:
import Charts
import UIKit
class DayAxisValueFormatter: NSObject, IAxisValueFormatter {
public func stringForValue(_ value: Double, axis: AxisBase?) -> String {
let day = Int(value)
return ["Fri", "Sat", "Sun", "Mon", "Tue", "Wed", "Thu"][day]
}
}
class ReportBarChart: UITableViewCell {
#IBOutlet private var chartView: BarChartView!
let groupSpace = 0.06
let barSpace = 0.02
let barWidth = 0.45
override func awakeFromNib() {
super.awakeFromNib()
chartView.backgroundColor = .reportCard
chartView.drawBarShadowEnabled = false
chartView.drawValueAboveBarEnabled = false
chartView.dragEnabled = false
chartView.setScaleEnabled(false)
chartView.pinchZoomEnabled = false
let xAxis = chartView.xAxis
xAxis.labelPosition = .bottom
xAxis.labelFont = .systemFont(ofSize: 11, weight: .semibold)
xAxis.labelTextColor = .reportGrayText
xAxis.granularity = 1
xAxis.labelCount = 7
xAxis.valueFormatter = DayAxisValueFormatter()
let leftAxis = chartView.leftAxis
leftAxis.enabled = false
let rightAxis = chartView.rightAxis
rightAxis.enabled = true
rightAxis.labelFont = .systemFont(ofSize: 11, weight: .semibold)
rightAxis.labelTextColor = .reportGrayText
rightAxis.axisMinimum = 0
rightAxis.labelCount = 3
let legend = chartView.legend
legend.font = .systemFont(ofSize: 11, weight: .semibold)
legend.textColor = .white
legend.form = .circle
legend.xEntrySpace = 16
}
func configure() {
let currentValues = [
BarChartDataEntry(x: 0, y: 1),
BarChartDataEntry(x: 1, y: 2),
BarChartDataEntry(x: 2, y: 3),
BarChartDataEntry(x: 3, y: 4),
BarChartDataEntry(x: 4, y: 4),
BarChartDataEntry(x: 5, y: 1),
BarChartDataEntry(x: 6, y: 6),
]
let currentValuesSet = BarChartDataSet(entries: currentValues, label: "This week")
currentValuesSet.setColor(UIColor.reportOrange)
currentValuesSet.drawValuesEnabled = false
let previousValues = [
BarChartDataEntry(x: 0, y: 4),
BarChartDataEntry(x: 1, y: 3),
BarChartDataEntry(x: 2, y: 2),
BarChartDataEntry(x: 3, y: 1),
BarChartDataEntry(x: 4, y: 3),
BarChartDataEntry(x: 5, y: 2),
BarChartDataEntry(x: 6, y: 5),
]
let previousValuesSet = BarChartDataSet(entries: previousValues, label: "Last week")
previousValuesSet.setColor(UIColor.reportGrayChart)
previousValuesSet.drawValuesEnabled = false
let data = BarChartData(dataSets: [currentValuesSet, previousValuesSet])
data.highlightEnabled = false
data.barWidth = barWidth
data.groupBars(fromX: 0, groupSpace: groupSpace, barSpace: barSpace)
chartView.data = data
}
}
Okay, found the answer 😅
xAxis.axisMinimum = 0
xAxis.axisMaximum = 7
xAxis.centerAxisLabelsEnabled = true
I would like to replicate this graph in my app.
I tried to search online but I found only pods, specifically Charts.
I tried to customize it but I was unable to give it the 3d effect by assigning each "triangle" a different color shade.
How can I replicate it?
Uibezierpath or something else?
Just have fun.
class GraphView: UIView {
let cirleSegnaposto:CGFloat = 20.0
let labelSize:Double = 50
let spacingGraphLabel:Double = 0
let widthOfZero:Double = 30
let labels = ["Label 1", "Label 2", "Label 3", "Label 4", "Label 5"]
let firstColors:[UIColor] = [.darkGray, .black, .darkGray, .lightGray, .white]
let secondColors:[UIColor] = [.orange, .brown, .orange, .yellow, .red]
var values: [Int]? = nil
var secondValues: [Int]? = nil
override func draw(_ rect: CGRect) {
for i in 0 ..< 4 {
let cirleLayer = CAShapeLayer()
let delta = Double(15 * i) + labelSize
let path = UIBezierPath(ovalIn: CGRect(x: delta,
y: delta,
width: Double(rect.width) - delta * 2,
height: Double(rect.width) - delta * 2))
cirleLayer.path = path.cgPath
cirleLayer.lineWidth = 1
cirleLayer.strokeColor = UIColor.lightGray.cgColor
cirleLayer.fillColor = UIColor.clear.cgColor
self.layer.addSublayer(cirleLayer)
}
let radius:Double = Double(rect.width/2) - (labelSize - spacingGraphLabel)
let labelRadius:Double = Double(rect.width/2) + (spacingGraphLabel)
let origin = CGPoint(x: rect.width/2, y: rect.height/2)
for i in 0..<5 {
let cirleLayer = CAShapeLayer()
let angle:Double = Double(i)/5.0 * (2 * .pi)
let centerX = Double(origin.x) + radius * cos(angle)
let centerY = Double(origin.y) - radius * sin(angle)
let path = UIBezierPath(ovalIn: CGRect(x: CGFloat(centerX) - cirleSegnaposto/2,
y: CGFloat(centerY) - cirleSegnaposto/2,
width: cirleSegnaposto,
height: cirleSegnaposto))
cirleLayer.path = path.cgPath
cirleLayer.fillColor = UIColor.lightGray.cgColor
cirleLayer.lineWidth = 0.5
cirleLayer.strokeColor = UIColor.black.cgColor
self.layer.addSublayer(cirleLayer)
let label = UILabel(frame: .zero)
label.font = UIFont.systemFont(ofSize: 12)
label.text = labels[i]
label.frame.size = CGSize(width: labelSize, height: labelSize/2)
let labelCenterX = Double(origin.x) + labelRadius * cos(angle)
let labelCenterY = Double(origin.y) - labelRadius * sin(angle)
label.center = CGPoint(x: labelCenterX, y: labelCenterY)
label.transform = label.transform.rotated(by: .pi/2)
self.addSubview(label)
}
if let values = secondValues {
drawGraph(values: values, center: origin, maxValue: radius, colors: secondColors.map({$0.cgColor}))
}
if let values = values {
drawGraph(values: values, center: origin, maxValue: radius, colors: firstColors.map({$0.cgColor}))
}
}
func drawGraph(values: [Int], center: CGPoint, maxValue: Double, colors: [CGColor]) {
var points = [CGPoint]()
for i in 0 ..< values.count {
let radius = Double(values[i])/10.0 * (maxValue - widthOfZero) + widthOfZero
let angle:Double = Double(i)/5.0 * (2 * .pi)
let x = Double(center.x) + radius * cos(angle)
let y = Double(center.y) - radius * sin(angle)
let point = CGPoint(x: x, y: y)
points.append(point)
}
for (i, point) in points.enumerated() {
let secondPoint = point == points.last ? points[0] : points[i+1]
let path = UIBezierPath()
path.move(to: center)
path.addLine(to: point)
path.addLine(to: secondPoint)
path.close()
let layer = CAShapeLayer()
layer.path = path.cgPath
layer.fillColor = colors[i]
layer.lineWidth = 1
layer.lineJoin = .round
layer.strokeColor = UIColor.black.cgColor
self.layer.addSublayer(layer)
}
}
}
I'm using this function to draw image on another images i'm using for in loop to iterate over and over so please help me to solve this issue every time i render it takes 50+mb each time
func image(byDrawingImage image: UIImage, inRect rect: CGRect) -> UIImage? {
return autoreleasepool { () -> UIImage? in
UIGraphicsBeginImageContext(size)
draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
image.draw(in: rect)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result
}
}
this is methood i'm calling when i get the image from server and trying to add image on the top of another image
private func modifyImagse() {
var parentSignatureCount = 0
var childSignatureCount = 0
guard let form = form else { return }
guard form.pages.count > 0 else { return }
var pages = downloadedImages
for input in form.inputs ?? [] {
for fieldId in input.fieldIds {
for field in form.fields {
if fieldId == field.id {
if input.inputType == .signature && input.label == "Signature"{
parentSignatureCount = parentSignatureCount + 1
let page = form.pages[field.pageNumber]
let x = field.x / 100.0 * page.width
let y = field.y / 100.0 * page.height
let signIndicator = UIImage(named: "sign_here_no_number.png")
pages[field.pageNumber] = pages[field.pageNumber].image(byDrawingImage: signIndicator!,
inRect: CGRect(x: CGFloat(x), y: CGFloat(y), width: 500, height: 150)) ?? pages[field.pageNumber]
pages[field.pageNumber] =
pages[field.pageNumber]
.textToImage(drawText: "\(parentSignatureCount)", atPoint: CGPoint(x: x + 60, y: y + 10) , textFontHeight: 70)
pages[field.pageNumber] = pages[field.pageNumber]
}else if input.inputType == .signature && input.label == "Child Signature" {
childSignatureCount = childSignatureCount + 1
let page = form.pages[field.pageNumber]
let x = field.x / 100.0 * page.width
let y = field.y / 100.0 * page.height
let signIndicator = UIImage(named: "sign_here_child.png")
pages[field.pageNumber] = pages[field.pageNumber].image(byDrawingImage: signIndicator!,
inRect: CGRect(x: CGFloat(x), y: CGFloat(y), width: 500, height: 150)) ?? pages[field.pageNumber]
pages[field.pageNumber] =
pages[field.pageNumber]
.textToImage(drawText: "\(childSignatureCount)", atPoint: CGPoint(x: x + 60, y: y + 10) , textFontHeight: 70)
pages[field.pageNumber] = pages[field.pageNumber]
}
}
}
}
}
downloadedImages = pages
}
I am using chart library to plot graph in swift. I want to show below values on LineChartView.
My values are as follows.
1) x: 0.0 y: 0.62020133693747
2) x: 0.0 y: 0.62020133693747
3) x: 0.0 y: 0.62020133693747
4) x: 8500.0 y: 0.62020133693747
5) x: 8500.0 y: 0.925703706122449
6) x: 9000.0 y: 0.925703706122449
7) x: 9000.0 y: 1.43307403327373
8) x: 9500.0 y: 1.43307403327373
9) x: 9500.0 y: 0.37799568
10) x: 9500.0 y: 0.37799568
11) x: 9500.0 y: 0.37799568
12) x: 9000.0 y: 0.37799568
13) x: 9000.0 y: 0.269996914285714
14) x: 8500.0 y: 0.269996914285714
15) x: 8500.0 y: 0.239997257142857
16) x: 6000.0 y: 0.239997257142857
17) x: 6000.0 y: 0.219552079597251
18) x: 0.0 y: 0.219552079597251
19) x: 0.0 y: 0.453594816
20) x: 0.0 y: 0.453594816
From these values the expected graph is:
But when I am using chart library I am getting the following graph:
It seems that the library is not able to plot the last values.
I have tried various setting but it didn't solve my issue.
My Code:
`func updateChartValues(arrLineCDE: Array<ChartDataEntry>) -> (LineChartDataSet) {
let Xvalues: [Double] = [/*All My X values are here*/]
let Yvalues: [Double] = [/*All My Y values are here*/]
var entries: [ChartDataEntry] = Array()
for (i, x) in Xvalues.enumerated() {
entries.append(ChartDataEntry(x: Xvalues[i], y: Yvalues[i], data: UIImage(named: "icon", in: Bundle(for: self.classForCoder), compatibleWith: nil)))
}
self.dataSet = LineChartDataSet(values: entries, label: "Depth vs. FV")
self.dataSet.mode = LineChartDataSet.Mode.cubicBezier
return dataSet
}`
Line Chart Sttings
`func setLineChartProperty(lineChartView lcv: LineChartView) {
//Line Chart Setting:
// lcv.delegate = self
lcv.setViewPortOffsets(left: 44.0, top: 30.0, right: 44.0, bottom: 44.0)
lcv.backgroundColor = darkBlueColor
lcv.chartDescription?.enabled = false
lcv.dragEnabled = false
lcv.setScaleEnabled(false)
lcv.pinchZoomEnabled = false
lcv.drawGridBackgroundEnabled = false
lcv.xAxis.labelFont = UIFont(name: "HelveticaNeue-Light", size: CGFloat(12.0))!
lcv.xAxis.setLabelCount(6, force: false)
lcv.xAxis.labelTextColor = UIColor.white
lcv.xAxis.labelPosition = .bottom
lcv.xAxis.axisLineColor = UIColor.white
lcv.xAxis.gridColor = UIColor.white
lcv.xAxis.drawGridLinesEnabled = true
lcv.xAxis.axisMaximum = 10000
lcv.xAxis.axisMinimum = 0
lcv.leftAxis.labelFont = UIFont(name: "HelveticaNeue-Light", size: CGFloat(12.0))!
lcv.leftAxis.setLabelCount(6, force: false)
lcv.leftAxis.labelTextColor = UIColor.white
lcv.leftAxis.labelPosition = .outsideChart
lcv.leftAxis.axisLineColor = UIColor.white
lcv.leftAxis.gridColor = UIColor.white
lcv.leftAxis.drawGridLinesEnabled = true
lcv.legend.horizontalAlignment = .right
lcv.legend.verticalAlignment = .bottom
lcv.legend.orientation = .horizontal
lcv.legend.drawInside = true
lcv.legend.form = .square
lcv.legend.formSize = 8.0
lcv.legend.formToTextSpace = 4.0
lcv.legend.xEntrySpace = 6.0
lcv.xAxis.enabled = true
lcv.rightAxis.enabled = false
lcv.leftAxis.enabled = true
lcv.legend.enabled = true
lcv.legend.textColor = UIColor.white
lcv.animate(xAxisDuration: 1.0, yAxisDuration: 1.0)
}`
I'm trying to create a parallax cloud effect with different speeds and sizes but i'm having a hard time when trying to add a different Y coordinate, not always in the same Y for all the clouds.
So I have 5 clouds, with its respective X and Y coordinate, and I use the SKAction.moveByX() function.
All the clouds starts from for example FRAME_WIDTH + 200 (out of bounds) and they end at -100. After that I reset the X to FRAME_WIDTH + 200 and do a "forever" sequence.
That was working perfectly, but I would like to add a random Y coordinate every time the animation finishes.
I was able to do it, but that would only change the Y coordinate once.
How can I achieve this?
Here's my current code:
func addClouds() {
let cloud1 = SKSpriteNode(imageNamed: "cloud1")
let cloud2 = SKSpriteNode(imageNamed: "cloud2")
let cloud3 = SKSpriteNode(imageNamed: "cloud3")
let cloud4 = SKSpriteNode(imageNamed: "cloud4")
let cloud5 = SKSpriteNode(imageNamed: "cloud5")
cloud1.zPosition = ZIndexPosition.CLOUD
cloud1.position = CGPoint(x: self.FRAME_WIDTH - 100, y: self.FRAME_HEIGHT / 2 - 150)
cloud1.size = CGSize(width: 44, height: 14)
cloud2.zPosition = ZIndexPosition.CLOUD
cloud2.position = CGPoint(x: self.FRAME_WIDTH + 150, y: self.FRAME_HEIGHT - 100)
cloud2.size = CGSize(width: 104, height: 16)
cloud3.zPosition = ZIndexPosition.CLOUD
cloud3.position = CGPoint(x: self.FRAME_WIDTH - 50, y: self.FRAME_HEIGHT - 200)
cloud3.size = CGSize(width: 8, height: 6)
cloud4.zPosition = ZIndexPosition.CLOUD
cloud4.position = CGPoint(x: self.FRAME_WIDTH + 200, y: self.FRAME_HEIGHT / 2 - 50)
cloud4.size = CGSize(width: 116, height: 32)
cloud5.zPosition = ZIndexPosition.CLOUD
cloud5.position = CGPoint(x: self.FRAME_WIDTH, y: self.FRAME_HEIGHT - 250)
cloud5.size = CGSize(width: 24, height: 6)
let resetCloud1YPos = SKAction.moveToY(self.randomCloud1, duration: 0)
let resetCloud2YPos = SKAction.moveToY(self.randomCloud2, duration: 0)
let resetCloud3YPos = SKAction.moveToY(self.randomCloud3, duration: 0)
let resetCloud4YPos = SKAction.moveToY(self.randomCloud4, duration: 0)
let resetCloud5YPos = SKAction.moveToY(self.randomCloud5, duration: 0)
let resetCloud1Pos = SKAction.moveToX((self.FRAME_WIDTH * 2) - 100, duration: 0)
let resetCloud2Pos = SKAction.moveToX((self.FRAME_WIDTH * 2) - 150, duration: 0)
let resetCloud3Pos = SKAction.moveToX((self.FRAME_WIDTH * 2) - 50, duration: 0)
let resetCloud4Pos = SKAction.moveToX((self.FRAME_WIDTH * 2) - 200, duration: 0)
let resetCloud5Pos = SKAction.moveToX((self.FRAME_WIDTH * 2) - 20, duration: 0)
let moveCloud1 = SKAction.moveToX(-100, duration: 28)
let cloud1Sequence = SKAction.sequence([moveCloud1, resetCloud1Pos, resetCloud1YPos])
let cloud1Forever = SKAction.repeatActionForever(cloud1Sequence)
let moveCloud2 = SKAction.moveToX(-100, duration: 24)
let cloud2Sequence = SKAction.sequence([moveCloud2, resetCloud2Pos, resetCloud2YPos])
let cloud2Forever = SKAction.repeatActionForever(cloud2Sequence)
let moveCloud3 = SKAction.moveToX(-100, duration: 35)
let cloud3Sequence = SKAction.sequence([moveCloud3, resetCloud3Pos, resetCloud3YPos])
let cloud3Forever = SKAction.repeatActionForever(cloud3Sequence)
let moveCloud4 = SKAction.moveToX(-100, duration: 13)
let cloud4Sequence = SKAction.sequence([moveCloud4, resetCloud4Pos, resetCloud4YPos])
let cloud4Forever = SKAction.repeatActionForever(cloud4Sequence)
let moveCloud5 = SKAction.moveToX(-100, duration: 18)
let cloud5Sequence = SKAction.sequence([moveCloud5, resetCloud5Pos, resetCloud5YPos])
let cloud5Forever = SKAction.repeatActionForever(cloud5Sequence)
cloud1.runAction(cloud1Forever)
cloud2.runAction(cloud2Forever)
cloud3.runAction(cloud3Forever)
cloud4.runAction(cloud4Forever)
cloud5.runAction(cloud5Forever)
self.addChild(cloud1)
self.addChild(cloud2)
self.addChild(cloud3)
self.addChild(cloud4)
self.addChild(cloud5)
}
randomCloud1,2,3,4 and 5 is just a random generation within the screen height bounds.
Thanks in advance.
If I understand what you're trying to accomplish, the following code should help you:
// ViewController.swift
import SpriteKit
class GameScene: SKScene {
override func didMoveToView(view: SKView) {
//create and position a test SKSpriteNode
let cloud = SKSpriteNode()
cloud.color = SKColor.blueColor()
cloud.position = CGPoint(x: self.frame.size.width - 100, y: 400)
cloud.size = CGSize(width: 44, height: 14)
self.addChild(cloud)
//pass the SKSpriteNode into the moveNode function
self.moveNode(cloud)
}
//reposition the node
func repositionNode(node: SKSpriteNode) {
print("repositioning node")
node.position = CGPointMake(self.frame.size.width - 100, self.randomCloudPosition())
print(node.position.y)
self.moveNode(node)
}
//move the node again
func moveNode(node: SKSpriteNode) {
node.runAction(SKAction.moveToX(-100, duration: 28), completion: {
self.repositionNode(node)
})
}
//return a random number between 300 and 799 (change this for your specific case)
func randomCloudPosition() -> CGFloat {
return CGFloat(arc4random_uniform(500) + 300) //should be between 300 and 799
}
}
You should be able to create your five clouds (or however many) and then just pass each one into moveNode and the animation will continue indefinitely