Allow PieChartView to hide value line for tiny slices in Swift - swift

I'm building a pie chart by chart iOS framework. I'm able to hide the value and label when the the slice is tiny but I can't hide the value line for tiny slice.
If I add this code on setDataCount()
set.valueLineWidth = 0.0
It will hide all the value line. How to hide it by the size of slice?
#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() {
var totalValue = 0.0
for a in categoryTotal {
totalValue += a
}
UserDefaults.standard.set(totalValue, forKey: "totalValue")
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 valueAndColor(){
for i in 0..<categoryArray.count{
let dataEntry = PieChartDataEntry(value: categoryTotal[i], label: categoryArray[i % categoryArray.count])
dataEntries.append(dataEntry)
//I'm using this code to hide the label
let value = categoryTotal[i]
let total = UserDefaults.standard.double(forKey: "totalValue")
var valueToUse = value/total * 100
valueToUse = Double(round(10*valueToUse)/10)
let minNumber = 10.0
if(valueToUse < minNumber) {
dataEntries[i].label = ""
}else{
dataEntries[i].label = categoryArray[i % categoryArray.count]
}
if categoryArray[i] == "吃喝" {
valueColors.append(UIColor.yellow)
}...
}
I'm using this code to hide the value %
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)
let minNumber = 10.0
if(valueToUse < minNumber) {
return ""
}
else {
let pFormatter = NumberFormatter()
pFormatter.numberStyle = .percent
pFormatter.maximumFractionDigits = 1
pFormatter.multiplier = 1
pFormatter.percentSymbol = " %"
let hideValue = pFormatter.string(from: NSNumber(value: value))
return String(hideValue ?? "0")
}
}
}

Inside the file PieChartRenderer change from this:
if dataSet.valueLineColor != nil
{
context.setStrokeColor(dataSet.valueLineColor!.cgColor)
context.setLineWidth(dataSet.valueLineWidth)
context.move(to: CGPoint(x: pt0.x, y: pt0.y))
context.addLine(to: CGPoint(x: pt1.x, y: pt1.y))
context.addLine(to: CGPoint(x: pt2.x, y: pt2.y))
context.drawPath(using: CGPathDrawingMode.stroke)
}
to this:
if dataSet.valueLineColor != nil
{
if(valueText == "") {
context.setStrokeColor(UIColor.clear.cgColor)
}
else {
context.setStrokeColor(dataSet.valueLineColor!.cgColor)
}
context.setLineWidth(dataSet.valueLineWidth)
context.move(to: CGPoint(x: pt0.x, y: pt0.y))
context.addLine(to: CGPoint(x: pt1.x, y: pt1.y))
context.addLine(to: CGPoint(x: pt2.x, y: pt2.y))
context.drawPath(using: CGPathDrawingMode.stroke)
}
The change basically checks if the valueText is the empty string, and if so it changes the linecolor to a clear color.

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

How to update UIBezierpath and CAShapeLayer path with pan gesture of UIView?

'''
import UIKit
class CanvasView: UIView {
var circleViewTag = 1000
var coordinatePoints: [String] = ["243,103","534,86","243,286","426,286"] {
didSet {
self.updateCoordinateArray()
self.drawPoints()
}
}
fileprivate var coordArray: [CGPoint] = []
var shape = CAShapeLayer()
var path = UIBezierPath()
/*// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}*/
private func drawPoints() -> Void {
CommonMethods.printLog("\(coordinatePoints)")
self.layer.addSublayer(shape)
shape.opacity = 0.5
shape.lineWidth = 2
shape.lineJoin = CAShapeLayerLineJoin.miter
shape.strokeColor = UIColor.white.cgColor
shape.fillColor = UIColor(red: 1.0, green: 0.2, blue: 0.2, alpha: 0.2).cgColor
if let firstCoord = self.coordArray.first {
path.move(to: firstCoord)
}
for (index, cgPoint) in self.coordArray.enumerated() {
self.drawCircularPoint(points: cgPoint)
if index == 0 {
continue
}
path.addLine(to: cgPoint)
}
path.close()
shape.path = path.cgPath
//self.drawLineBetweenPoints()
}
private func drawCircularPoint(points: CGPoint) -> Void {
let circleView = UIView.init(frame: .zero)
circleViewTag = circleViewTag + 1
circleView.tag = circleViewTag
circleView.frame.size = CGSize.init(width: 30.0, height: 30.0)
circleView.layer.cornerRadius = 15.0
circleView.center = points
circleView.backgroundColor = .random()
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.draggedView(_:)))
panGesture.view?.tag = circleView.tag
circleView.isUserInteractionEnabled = true
circleView.addGestureRecognizer(panGesture)
self.addSubview(circleView)
}
#objc func draggedView(_ sender:UIPanGestureRecognizer){
guard let getTag = sender.view?.tag else { return }
if let viewToDrag = self.viewWithTag(getTag) as? UIView {
var currentPoint: CGPoint = .zero
if path.contains(viewToDrag.center) {
print("YES")
currentPoint = path.currentPoint
}
let translation = sender.translation(in: self)
viewToDrag.center = CGPoint(x: viewToDrag.center.x + translation.x, y: viewToDrag.center.y + translation.y)
sender.setTranslation(CGPoint.zero, in: self)
if sender.state == .began && currentPoint != .zero {
let coordinateIndex = self.coordArray.firstIndex { (cgpoint) -> Bool in
if currentPoint == cgpoint {
return true
}
return false
}
if coordinateIndex != nil {
self.coordArray[coordinateIndex!] = viewToDrag.center
self.shape.removeFromSuperlayer()
self.path.removeAllPoints()
self.setNeedsDisplay()
self.layer.addSublayer(self.shape)
self.shape.opacity = 0.5
self.shape.lineWidth = 2
self.shape.lineJoin = CAShapeLayerLineJoin.miter
self.shape.strokeColor = UIColor.white.cgColor
self.shape.fillColor = UIColor(red: 1.0, green: 0.2, blue: 0.2, alpha: 0.2).cgColor
if let firstCoord = self.coordArray.first {
path.move(to: firstCoord)
}
for (index, cgPoint) in self.coordArray.enumerated() {
//self.drawCircularPoint(points: cgPoint)
if index == 0 {
continue
}
path.addLine(to: cgPoint)
}
path.close()
shape.path = path.cgPath
}
}
}
//self.bringSubviewToFront(viewDrag)
}
private func updateCoordinateArray() -> Void {
for singleCoordinate in self.coordinatePoints {
if singleCoordinate.contains(",") == true {
let splitCoordinate = singleCoordinate.split(separator: ",")
if splitCoordinate.count == 2 {
let xPos = CGFloat(Float(splitCoordinate[0]) ?? 0.0)
let yPos = CGFloat(Float(splitCoordinate[1]) ?? 0.0)
let cgPoint = CGPoint(x: xPos, y: yPos)
self.coordArray.append(cgPoint)
}
}
}
var penultimateIndex: Int?
if let penultimateCoordinate = self.coordArray.penultimate() {
penultimateIndex = self.coordArray.firstIndex { (cgpoint) -> Bool in
if penultimateCoordinate == cgpoint {
return true
}
return false
}
}
var lastIndex: Int?
if let lastCoordinate = self.coordArray.last {
lastIndex = self.coordArray.firstIndex { (cgpoint) -> Bool in
if lastCoordinate == cgpoint {
return true
}
return false
}
}
if penultimateIndex != nil && lastIndex != nil {
self.coordArray.swapAt(penultimateIndex!, lastIndex!)
}
}
'''
I am creating a polygon using UIBezierpath and CAShapelayer. Added pan gesture on all 4 points that is UIView. When I drag the point A,B,C,D the expected behaviour is that bezierpath and CAShapelayer gets updated with the updated points. And when user drag the inner part of the shape all the path gets updated. But I am unable to update the path and shape. Can anyone help me with this?
yon set name for layer
var shape = CAShapeLayer()
shape.name = "name1"
and then, instead of updating, you can delete it by first searching by name, and then add

Swift spritekit didbegincontact being called with delay

This is my GameScene code.
class GameScene: SKScene, SKPhysicsContactDelegate {
let orcWidth = UIScreen.main.bounds.width / 5
var orcCategory:UInt32 = 0x1 << 0
var knightCategory:UInt32 = 0x1 << 1
private var orc = SKSpriteNode()
private var knight = SKSpriteNode()
private var orcWalkingFrames: [SKTexture] = []
private var knightIdleFrames: [SKTexture] = []
private var knightAttackFrame: [SKTexture] = []
var background = SKSpriteNode(imageNamed: "game_background1")
override func didMove(to view: SKView) {
physicsWorld.contactDelegate = self
physicsWorld.gravity = CGVector(dx: 0, dy: 0)
setupbackground()
startGame()
}
func setupbackground() {
background.zPosition = 0
background.size = self.frame.size
background.position = CGPoint(x: frame.size.width / 2, y: frame.size.height / 2)
addChild(background)
}
func startGame() {
buildRandomOrcs()
buildKnight()
}
func stopGame() {
}
func buildOrc(yposition: CGFloat) {
var orcWalkFrames: [SKTexture] = []
let orcAnimatedAtlas = SKTextureAtlas(named: "OrcWalking")
let numImages = orcAnimatedAtlas.textureNames.count
for i in 0...numImages - 1 {
let orcTextureName = "0_Orc_Walking_\(i)"
orcWalkFrames.append(orcAnimatedAtlas.textureNamed(orcTextureName))
}
self.orcWalkingFrames = orcWalkFrames
let firstFrameTexture = orcWalkingFrames[0]
orc = SKSpriteNode(texture: firstFrameTexture)
orc.name = "orc"
orc.position = CGPoint(x: frame.minX-orcWidth/2, y: yposition)
self.orc.zPosition = CGFloat(self.children.count)
orc.scale(to: CGSize(width: orcWidth, height: orcWidth))
orc.physicsBody = SKPhysicsBody(rectangleOf: orc.size, center: orc.position)
orc.physicsBody?.affectedByGravity = false
orc.physicsBody?.isDynamic = true
orc.physicsBody?.categoryBitMask = orcCategory
orc.physicsBody?.contactTestBitMask = knightCategory
orc.physicsBody?.collisionBitMask = knightCategory
addChild(orc)
walkOrc()
moveOrcForward()
}
func buildKnight() {
var knightIdleFrames: [SKTexture] = []
let knightIdleAtlas = SKTextureAtlas(named: "KnightIdle")
let numImages = knightIdleAtlas.textureNames.count
for i in 0...numImages - 1 {
let orcTextureName = "_IDLE_00\(i)"
knightIdleFrames.append(knightIdleAtlas.textureNamed(orcTextureName))
}
self.knightIdleFrames = knightIdleFrames
let firstFrameTexture = knightIdleFrames[0]
knight = SKSpriteNode(texture: firstFrameTexture)
knight.name = "knight"
knight.position = CGPoint(x: frame.maxX-orcWidth/2, y: frame.midY)
self.knight.zPosition = 1
knight.scale(to: CGSize(width: -orcWidth, height: orcWidth))
knight.physicsBody = SKPhysicsBody(rectangleOf: knight.size, center: knight.position)
knight.physicsBody?.affectedByGravity = false
knight.physicsBody?.isDynamic = false
knight.physicsBody?.categoryBitMask = knightCategory
knight.physicsBody?.contactTestBitMask = orcCategory
knight.physicsBody?.collisionBitMask = orcCategory
addChild(knight)
idleKnight()
}
func idleKnight() {
knight.run(SKAction.repeatForever(SKAction.animate(with: knightIdleFrames, timePerFrame: 0.1)))
}
func walkOrc() {
orc.run(SKAction.repeatForever(SKAction.animate(with: orcWalkingFrames,timePerFrame: 0.025)))
}
func moveOrcForward() {
orc.run(SKAction.repeatForever(SKAction.moveBy(x: 55, y: 0, duration: 0.25)))
}
func buildRandomOrcs () {
let wait = SKAction.wait(forDuration: TimeInterval(makeRandomNumberBetween(min: 0, max: 0)))
let spawn = SKAction.run {
self.buildOrc(yposition: self.makeRandomCGFloatNumber())
}
let spawning = SKAction.sequence([spawn,wait])
self.run(SKAction.repeat(spawning, count: 10))
}
func makeRandomCGFloatNumber() -> CGFloat {
let randomNumber = arc4random_uniform(UInt32((frame.maxY-orcWidth/2) - (frame.minY+orcWidth/2))) + UInt32(frame.minY+orcWidth/2)
return CGFloat(randomNumber)
}
func makeRandomNumberBetween (min: Int, max: Int) -> Int{
let randomNumber = arc4random_uniform(UInt32(max - min)) + UInt32(min)
return Int(randomNumber)
}
func didBegin(_ contact: SKPhysicsContact) {
let collision:UInt32 = contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask
if collision == orcCategory | knightCategory {
self.scene?.view?.isPaused = true
print("COLLIDED")
}
}
}
The problem is that the scene pauses almost 2-3 seconds after the collision.
I changed the position of knight and delay time changed.
For example, if I set position to frame.minX+orcWidth/2 there is no delay.
What is wrong with my code?
Your problem isn't things are being delayed, your problem is your bounding box is not where you think it is
use view.showPhysics = true to determine where your boxes are
once you realize they are in the wrong spots, go to this line
knight.physicsBody = SKPhysicsBody(rectangleOf: knight.size, center: knight.position)
and fix it
knight.physicsBody = SKPhysicsBody(rectangleOf: knight.size)
Do so for the rest of your bodies
My guess is that manipulations of SKView properties must happen on main thread, i.e.
DispatchQueue.main.async { [unowned self] in
self.scene?.view?.isPaused = true
print("COLLIDED")
}

SwiftChart Add Range Highlighting

I'm using the Swift Chart. I'd like to modify it to allow the user to select a range. The idea is to touch, swipe left/right, and then lift your finger. This should highlight the area swiped and provide a way to get the beginning and ending values of the swipe. I expect I'll need to modify the touchesBegan() and touchesEnded() events, but I don't know how.
Here's what I did to make this work:
I added range selection variables to the class
// Range selection
open var leftRangePoint: UITouch!
open var rightRangePoint: UITouch!
open var leftRangeLocation: CGFloat = 0
open var rightRangeLocation: CGFloat = 0
I modified touchesBegan()
leftRangePoint = touches.first!
leftRangeLocation = leftRangePoint.location(in: self).x
And added a routine to touchesEnded()
handleRangeTouchesEnded(touches, event: event)
Here's the full code:
// Chart.swift
//
// Created by Giampaolo Bellavite on 07/11/14.
// Copyright (c) 2014 Giampaolo Bellavite. All rights reserved.
import UIKit
public protocol ChartDelegate: class {
func didTouchChart(_ chart: Chart, indexes: [Int?], x: Float, left: CGFloat)
func didFinishTouchingChart(_ chart: Chart)
func didEndTouchingChart(_ chart: Chart)
}
typealias ChartPoint = (x: Float, y: Float)
public enum ChartLabelOrientation {
case horizontal
case vertical
}
#IBDesignable open class Chart: UIControl {
#IBInspectable
open var identifier: String?
open var series: [ChartSeries] = [] {
didSet {
setNeedsDisplay()
}
}
open var xLabels: [Float]?
open var xLabelsFormatter = { (labelIndex: Int, labelValue: Float) -> String in
String(Int(labelValue))
}
open var xLabelsTextAlignment: NSTextAlignment = .left
open var xLabelsOrientation: ChartLabelOrientation = .horizontal
open var xLabelsSkipLast: Bool = true
open var xLabelsSkipAll: Bool = true
open var yLabels: [Float]?
open var yLabelsFormatter = { (labelIndex: Int, labelValue: Float) -> String in
String(Int(labelValue))
}
open var yLabelsOnRightSide: Bool = false
open var labelFont: UIFont? = UIFont.systemFont(ofSize: 12)
#IBInspectable
open var labelColor: UIColor = UIColor.black
#IBInspectable
open var axesColor: UIColor = UIColor.gray.withAlphaComponent(0.3)
#IBInspectable
open var gridColor: UIColor = UIColor.gray.withAlphaComponent(0.3)
open var showXLabelsAndGrid: Bool = true
open var showYLabelsAndGrid: Bool = true
open var bottomInset: CGFloat = 20
open var topInset: CGFloat = 20
#IBInspectable
open var lineWidth: CGFloat = 2
weak open var delegate: ChartDelegate?
open var minX: Float?
open var minY: Float?
open var maxX: Float?
open var maxY: Float?
open var highlightLineColor = UIColor.gray
open var highlightLineWidth: CGFloat = 0.5
open var areaAlphaComponent: CGFloat = 0.1
open var leftRangePoint: UITouch!
open var rightRangePoint: UITouch!
open var leftRangeLocation: CGFloat = 0
open var rightRangeLocation: CGFloat = 0
fileprivate var highlightShapeLayer: CAShapeLayer!
fileprivate var layerStore: [CAShapeLayer] = []
fileprivate var drawingHeight: CGFloat!
fileprivate var drawingWidth: CGFloat!
fileprivate var min: ChartPoint!
fileprivate var max: ChartPoint!
typealias ChartLineSegment = [ChartPoint]
override public init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
convenience public init() {
self.init(frame: .zero)
commonInit()
}
private func commonInit() {
backgroundColor = UIColor.clear
contentMode = .redraw // redraw rects on bounds change
}
override open func draw(_ rect: CGRect) {
#if TARGET_INTERFACE_BUILDER
drawIBPlaceholder()
#else
drawChart()
#endif
}
open func add(_ series: ChartSeries) {
self.series.append(series)
}
open func add(_ series: [ChartSeries]) {
for s in series {
add(s)
}
}
open func removeSeriesAt(_ index: Int) {
series.remove(at: index)
}
open func removeAllSeries() {
series = []
}
open func valueForSeries(_ seriesIndex: Int, atIndex dataIndex: Int?) -> Float? {
if dataIndex == nil { return nil }
let series = self.series[seriesIndex] as ChartSeries
return series.data[dataIndex!].y
}
fileprivate func drawIBPlaceholder() {
let placeholder = UIView(frame: self.frame)
placeholder.backgroundColor = UIColor(red: 0.93, green: 0.93, blue: 0.93, alpha: 1)
let label = UILabel()
label.text = "Chart"
label.font = UIFont.systemFont(ofSize: 28)
label.textColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.2)
label.sizeToFit()
label.frame.origin.x += frame.width/2 - (label.frame.width / 2)
label.frame.origin.y += frame.height/2 - (label.frame.height / 2)
placeholder.addSubview(label)
addSubview(placeholder)
}
fileprivate func drawChart() {
drawingHeight = bounds.height - bottomInset - topInset
drawingWidth = bounds.width
let minMax = getMinMax()
min = minMax.min
max = minMax.max
highlightShapeLayer = nil
// Remove things before drawing, e.g. when changing orientation
for view in self.subviews {
view.removeFromSuperview()
}
for layer in layerStore {
layer.removeFromSuperlayer()
}
layerStore.removeAll()
// Draw content
for (index, series) in self.series.enumerated() {
// Separate each line in multiple segments over and below the x axis
let segments = Chart.segmentLine(series.data as ChartLineSegment, zeroLevel: series.colors.zeroLevel)
segments.forEach({ segment in
let scaledXValues = scaleValuesOnXAxis( segment.map({ return $0.x }) )
let scaledYValues = scaleValuesOnYAxis( segment.map({ return $0.y }) )
if series.line {
drawLine(scaledXValues, yValues: scaledYValues, seriesIndex: index)
}
if series.area {
drawArea(scaledXValues, yValues: scaledYValues, seriesIndex: index)
}
})
}
drawAxes()
if showXLabelsAndGrid && (xLabels != nil || series.count > 0) {
drawLabelsAndGridOnXAxis()
}
if showYLabelsAndGrid && (yLabels != nil || series.count > 0) {
drawLabelsAndGridOnYAxis()
}
}
fileprivate func getMinMax() -> (min: ChartPoint, max: ChartPoint) {
// Start with user-provided values
var min = (x: minX, y: minY)
var max = (x: maxX, y: maxY)
// Check in datasets
for series in self.series {
let xValues = series.data.map({ (point: ChartPoint) -> Float in
return point.x })
let yValues = series.data.map({ (point: ChartPoint) -> Float in
return point.y })
let newMinX = xValues.min()!
let newMinY = yValues.min()!
let newMaxX = xValues.max()!
let newMaxY = yValues.max()!
if min.x == nil || newMinX < min.x! { min.x = newMinX }
if min.y == nil || newMinY < min.y! { min.y = newMinY }
if max.x == nil || newMaxX > max.x! { max.x = newMaxX }
if max.y == nil || newMaxY > max.y! { max.y = newMaxY }
}
// Check in labels
if xLabels != nil {
let newMinX = (xLabels!).min()!
let newMaxX = (xLabels!).max()!
if min.x == nil || newMinX < min.x! { min.x = newMinX }
if max.x == nil || newMaxX > max.x! { max.x = newMaxX }
}
if yLabels != nil {
let newMinY = (yLabels!).min()!
let newMaxY = (yLabels!).max()!
if min.y == nil || newMinY < min.y! { min.y = newMinY }
if max.y == nil || newMaxY > max.y! { max.y = newMaxY }
}
if min.x == nil { min.x = 0 }
if min.y == nil { min.y = 0 }
if max.x == nil { max.x = 0 }
if max.y == nil { max.y = 0 }
return (min: (x: min.x!, y: min.y!), max: (x: max.x!, max.y!))
}
fileprivate func scaleValuesOnXAxis(_ values: [Float]) -> [Float] {
let width = Float(drawingWidth)
var factor: Float
if max.x - min.x == 0 {
factor = 0
} else {
factor = width / (max.x - min.x)
}
let scaled = values.map { factor * ($0 - self.min.x) }
return scaled
}
fileprivate func scaleValuesOnYAxis(_ values: [Float]) -> [Float] {
let height = Float(drawingHeight)
var factor: Float
if max.y - min.y == 0 {
factor = 0
} else {
factor = height / (max.y - min.y)
}
let scaled = values.map { Float(self.topInset) + height - factor * ($0 - self.min.y) }
return scaled
}
fileprivate func scaleValueOnYAxis(_ value: Float) -> Float {
let height = Float(drawingHeight)
var factor: Float
if max.y - min.y == 0 {
factor = 0
} else {
factor = height / (max.y - min.y)
}
let scaled = Float(self.topInset) + height - factor * (value - min.y)
return scaled
}
fileprivate func getZeroValueOnYAxis(zeroLevel: Float) -> Float {
if min.y > zeroLevel {
return scaleValueOnYAxis(min.y)
} else {
return scaleValueOnYAxis(zeroLevel)
}
}
fileprivate func drawLine(_ xValues: [Float], yValues: [Float], seriesIndex: Int) {
// YValues are "reverted" from top to bottom, so 'above' means <= level
let isAboveZeroLine = yValues.max()! <= self.scaleValueOnYAxis(series[seriesIndex].colors.zeroLevel)
let path = CGMutablePath()
path.move(to: CGPoint(x: CGFloat(xValues.first!), y: CGFloat(yValues.first!)))
for i in 1..<yValues.count {
let y = yValues[i]
path.addLine(to: CGPoint(x: CGFloat(xValues[i]), y: CGFloat(y)))
}
let lineLayer = CAShapeLayer()
lineLayer.frame = self.bounds
lineLayer.path = path
if isAboveZeroLine {
lineLayer.strokeColor = series[seriesIndex].colors.above.cgColor
} else {
lineLayer.strokeColor = series[seriesIndex].colors.below.cgColor
}
lineLayer.fillColor = nil
lineLayer.lineWidth = lineWidth
lineLayer.lineJoin = kCALineJoinBevel
self.layer.addSublayer(lineLayer)
layerStore.append(lineLayer)
}
fileprivate func drawArea(_ xValues: [Float], yValues: [Float], seriesIndex: Int) {
// YValues are "reverted" from top to bottom, so 'above' means <= level
let isAboveZeroLine = yValues.max()! <= self.scaleValueOnYAxis(series[seriesIndex].colors.zeroLevel)
let area = CGMutablePath()
let zero = CGFloat(getZeroValueOnYAxis(zeroLevel: series[seriesIndex].colors.zeroLevel))
area.move(to: CGPoint(x: CGFloat(xValues[0]), y: zero))
for i in 0..<xValues.count {
area.addLine(to: CGPoint(x: CGFloat(xValues[i]), y: CGFloat(yValues[i])))
}
area.addLine(to: CGPoint(x: CGFloat(xValues.last!), y: zero))
let areaLayer = CAShapeLayer()
areaLayer.frame = self.bounds
areaLayer.path = area
areaLayer.strokeColor = nil
if isAboveZeroLine {
areaLayer.fillColor = series[seriesIndex].colors.above.withAlphaComponent(areaAlphaComponent).cgColor
} else {
areaLayer.fillColor = series[seriesIndex].colors.below.withAlphaComponent(areaAlphaComponent).cgColor
}
areaLayer.lineWidth = 0
self.layer.addSublayer(areaLayer)
layerStore.append(areaLayer)
}
fileprivate func drawAxes() {
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(axesColor.cgColor)
context.setLineWidth(0.5)
// horizontal axis at the bottom
context.move(to: CGPoint(x: CGFloat(0), y: drawingHeight + topInset))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: drawingHeight + topInset))
context.strokePath()
// horizontal axis at the top
context.move(to: CGPoint(x: CGFloat(0), y: CGFloat(0)))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: CGFloat(0)))
context.strokePath()
// horizontal axis when y = 0
if min.y < 0 && max.y > 0 {
let y = CGFloat(getZeroValueOnYAxis(zeroLevel: 0))
context.move(to: CGPoint(x: CGFloat(0), y: y))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: y))
context.strokePath()
}
// vertical axis on the left
context.move(to: CGPoint(x: CGFloat(0), y: CGFloat(0)))
context.addLine(to: CGPoint(x: CGFloat(0), y: drawingHeight + topInset))
context.strokePath()
// vertical axis on the right
context.move(to: CGPoint(x: CGFloat(drawingWidth), y: CGFloat(0)))
context.addLine(to: CGPoint(x: CGFloat(drawingWidth), y: drawingHeight + topInset))
context.strokePath()
}
fileprivate func drawLabelsAndGridOnXAxis() {
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(gridColor.cgColor)
context.setLineWidth(0.5)
var labels: [Float]
if xLabels == nil {
// Use labels from the first series
labels = series[0].data.map({ (point: ChartPoint) -> Float in
return point.x })
} else {
labels = xLabels!
}
let scaled = scaleValuesOnXAxis(labels)
let padding: CGFloat = 5
scaled.enumerated().forEach { (i, value) in
let x = CGFloat(value)
let isLastLabel = x == drawingWidth
// Add vertical grid for each label, except axes on the left and right
if x != 0 && x != drawingWidth {
context.move(to: CGPoint(x: x, y: CGFloat(0)))
if xLabelsSkipAll {
let height: CGFloat = bounds.height - 20.0
context.addLine(to: CGPoint(x: x, y: height))
} else {
context.addLine(to: CGPoint(x: x, y: bounds.height))
}
context.strokePath()
}
if (xLabelsSkipLast && isLastLabel) || xLabelsSkipAll {
// Do not add label at the most right position
return
}
// Add label
let label = UILabel(frame: CGRect(x: x, y: drawingHeight, width: 0, height: 0))
label.font = labelFont
label.text = xLabelsFormatter(i, labels[i])
label.textColor = labelColor
// Set label size
label.sizeToFit()
// Center label vertically
label.frame.origin.y += topInset
if xLabelsOrientation == .horizontal {
// Add left padding
label.frame.origin.y -= (label.frame.height - bottomInset) / 2
label.frame.origin.x += padding
// Set label's text alignment
label.frame.size.width = (drawingWidth / CGFloat(labels.count)) - padding * 2
label.textAlignment = xLabelsTextAlignment
} else {
label.transform = CGAffineTransform(rotationAngle: CGFloat(Double.pi / 2))
// Adjust vertical position according to the label's height
label.frame.origin.y += label.frame.size.height / 2
// Adjust horizontal position as the series line
label.frame.origin.x = x
if xLabelsTextAlignment == .center {
// Align horizontally in series
label.frame.origin.x += ((drawingWidth / CGFloat(labels.count)) / 2) - (label.frame.size.width / 2)
} else {
// Give some space from the vertical line
label.frame.origin.x += padding
}
}
self.addSubview(label)
}
}
fileprivate func drawLabelsAndGridOnYAxis() {
let context = UIGraphicsGetCurrentContext()!
context.setStrokeColor(gridColor.cgColor)
context.setLineWidth(0.5)
var labels: [Float]
if yLabels == nil {
labels = [(min.y + max.y) / 2, max.y]
if yLabelsOnRightSide || min.y != 0 {
labels.insert(min.y, at: 0)
}
} else {
labels = yLabels!
}
let scaled = scaleValuesOnYAxis(labels)
let padding: CGFloat = 5
let zero = CGFloat(getZeroValueOnYAxis(zeroLevel: 0))
scaled.enumerated().forEach { (i, value) in
let y = CGFloat(value)
// Add horizontal grid for each label, but not over axes
if y != drawingHeight + topInset && y != zero {
context.move(to: CGPoint(x: CGFloat(0), y: y))
context.addLine(to: CGPoint(x: self.bounds.width, y: y))
if labels[i] != 0 {
// Horizontal grid for 0 is not dashed
context.setLineDash(phase: CGFloat(0), lengths: [CGFloat(5)])
} else {
context.setLineDash(phase: CGFloat(0), lengths: [])
}
context.strokePath()
}
let label = UILabel(frame: CGRect(x: padding, y: y, width: 0, height: 0))
label.font = labelFont
label.text = yLabelsFormatter(i, labels[i])
label.textColor = labelColor
label.sizeToFit()
if yLabelsOnRightSide {
label.frame.origin.x = drawingWidth
label.frame.origin.x -= label.frame.width + padding
}
// Labels should be placed above the horizontal grid
label.frame.origin.y -= label.frame.height
self.addSubview(label)
}
UIGraphicsEndImageContext()
}
fileprivate func drawHighlightLineFromLeftPosition(_ left: CGFloat) {
if let shapeLayer = highlightShapeLayer {
// Use line already created
let path = CGMutablePath()
path.move(to: CGPoint(x: left, y: 0))
path.addLine(to: CGPoint(x: left, y: drawingHeight + topInset))
shapeLayer.path = path
} else {
// Create the line
let path = CGMutablePath()
path.move(to: CGPoint(x: left, y: CGFloat(0)))
path.addLine(to: CGPoint(x: left, y: drawingHeight + topInset))
let shapeLayer = CAShapeLayer()
shapeLayer.frame = self.bounds
shapeLayer.path = path
shapeLayer.strokeColor = highlightLineColor.cgColor
shapeLayer.fillColor = nil
shapeLayer.lineWidth = highlightLineWidth
highlightShapeLayer = shapeLayer
layer.addSublayer(shapeLayer)
layerStore.append(shapeLayer)
}
}
func handleTouchEvents(_ touches: Set<UITouch>, event: UIEvent!) {
let point = touches.first!
let left = point.location(in: self).x
let x = valueFromPointAtX(left)
if left < 0 || left > (drawingWidth as CGFloat) {
// Remove highlight line at the end of the touch event
if let shapeLayer = highlightShapeLayer {
shapeLayer.path = nil
}
delegate?.didFinishTouchingChart(self)
return
}
drawHighlightLineFromLeftPosition(left)
if delegate == nil {
return
}
var indexes: [Int?] = []
for series in self.series {
var index: Int? = nil
let xValues = series.data.map({ (point: ChartPoint) -> Float in
return point.x })
let closest = Chart.findClosestInValues(xValues, forValue: x)
if closest.lowestIndex != nil && closest.highestIndex != nil {
// Consider valid only values on the right
index = closest.lowestIndex
}
indexes.append(index)
}
delegate!.didTouchChart(self, indexes: indexes, x: x, left: left)
}
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouchEvents(touches, event: event)
leftRangePoint = touches.first!
leftRangeLocation = leftRangePoint.location(in: self).x
}
override open func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouchEvents(touches, event: event)
delegate?.didEndTouchingChart(self)
handleRangeTouchesEnded(touches, event: event)
}
func handleRangeTouchesEnded(_ touches: Set<UITouch>, event: UIEvent!) {
rightRangePoint = touches.first!
rightRangeLocation = rightRangePoint.location(in: self).x
// Make sure left is actually to the left
if rightRangeLocation < leftRangeLocation {
let rangePoint = leftRangePoint
let rangeLocation = leftRangeLocation
leftRangePoint = rightRangePoint
leftRangeLocation = rightRangeLocation
rightRangePoint = rangePoint
rightRangeLocation = rangeLocation
}
// Highlight the range
let layer = CAShapeLayer()
let width = rightRangeLocation - leftRangeLocation
layer.path = UIBezierPath(rect: CGRect(x: leftRangeLocation, y: topInset, width: width, height: drawingHeight)).cgPath
layer.fillColor = UIColor.red.cgColor
layer.opacity = 0.3
self.layer.addSublayer(layer)
}
override open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
handleTouchEvents(touches, event: event)
}
fileprivate func valueFromPointAtX(_ x: CGFloat) -> Float {
let value = ((max.x-min.x) / Float(drawingWidth)) * Float(x) + min.x
return value
}
fileprivate func valueFromPointAtY(_ y: CGFloat) -> Float {
let value = ((max.y - min.y) / Float(drawingHeight)) * Float(y) + min.y
return -value
}
fileprivate class func findClosestInValues(_ values: [Float],
forValue value: Float
) -> (
lowestValue: Float?,
highestValue: Float?,
lowestIndex: Int?,
highestIndex: Int?
) {
var lowestValue: Float?, highestValue: Float?, lowestIndex: Int?, highestIndex: Int?
values.enumerated().forEach { (i, currentValue) in
if currentValue <= value && (lowestValue == nil || lowestValue! < currentValue) {
lowestValue = currentValue
lowestIndex = i
}
if currentValue >= value && (highestValue == nil || highestValue! > currentValue) {
highestValue = currentValue
highestIndex = i
}
}
return (
lowestValue: lowestValue,
highestValue: highestValue,
lowestIndex: lowestIndex,
highestIndex: highestIndex
)
}
fileprivate class func segmentLine(_ line: ChartLineSegment, zeroLevel: Float) -> [ChartLineSegment] {
var segments: [ChartLineSegment] = []
var segment: ChartLineSegment = []
line.enumerated().forEach { (i, point) in
segment.append(point)
if i < line.count - 1 {
let nextPoint = line[i+1]
if point.y >= zeroLevel && nextPoint.y < zeroLevel || point.y < zeroLevel && nextPoint.y >= zeroLevel {
// The segment intersects zeroLevel, close the segment with the intersection point
let closingPoint = Chart.intersectionWithLevel(point, and: nextPoint, level: zeroLevel)
segment.append(closingPoint)
segments.append(segment)
// Start a new segment
segment = [closingPoint]
}
} else {
// End of the line
segments.append(segment)
}
}
return segments
}
fileprivate class func intersectionWithLevel(_ p1: ChartPoint, and p2: ChartPoint, level: Float) -> ChartPoint {
let dy1 = level - p1.y
let dy2 = level - p2.y
return (x: (p2.x * dy1 - p1.x * dy2) / (dy1 - dy2), y: level)
}
}

Line chart fill color is faded

I am trying to setup a line chart with one fill colour but for some reason, the fill colour is faded.
Example
Both the random view I have added to middle of screen and the fill colour of the line chart are set to be red, but for some reason the fill colour of the chart is faded.
Can see code here
#IBOutlet var liveChart : LineChartView!
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Chart Tests"
configureChart(chart: liveChart)
var xAxis = [String]()
var yAxis = [Double]()
for _ in 0..<10
{
xAxis.append("")
let yVal = Double(randomBetweenNumbers(firstNum: 1.0, secondNum: 100.0))
yAxis.append(yVal)
}
setData(xAxisArray: xAxis, yAxisArray: yAxis, chart: liveChart)
let testView = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
testView.center = view.center
testView.backgroundColor = UIColor.red
view.addSubview(testView)
}
func randomBetweenNumbers(firstNum: CGFloat, secondNum: CGFloat) -> CGFloat{
return CGFloat(arc4random()) / CGFloat(UINT32_MAX) * abs(firstNum - secondNum) + min(firstNum, secondNum)
}
func configureChart(chart : LineChartView)
{
chart.chartDescription?.text = ""
chart.noDataText = "Loading Data"
chart.backgroundColor = UIColor.clear
chart.drawGridBackgroundEnabled = false
chart.dragEnabled = true
chart.rightAxis.enabled = false
chart.leftAxis.enabled = true
chart.doubleTapToZoomEnabled = false
chart.legend.enabled = false
chart.pinchZoomEnabled = true
chart.highlightPerTapEnabled = false
chart.highlightPerDragEnabled = false
chart.xAxis.enabled = false
chart.leftAxis.drawAxisLineEnabled = false
chart.leftAxis.drawGridLinesEnabled = false
chart.leftAxis.labelCount = 5
chart.leftAxis.forceLabelsEnabled = true
}
func setData(xAxisArray : [String], yAxisArray : [Double], chart : LineChartView)
{
var yVals1 : [ChartDataEntry] = [ChartDataEntry]()
if(xAxisArray.count > 0)
{
for i in 0 ..< xAxisArray.count
{
let chartEntry = ChartDataEntry(x: Double(i), y: yAxisArray[i], data: nil)
yVals1.append(chartEntry)
}
}
let set1: LineChartDataSet = LineChartDataSet(values: yVals1, label: "")
set1.fillColor = UIColor.red
set1.drawFilledEnabled = true
set1.drawCirclesEnabled = false
let data = LineChartData()
data.addDataSet(set1)
liveChart.data = data
}
Is there a way to fix this? Or is this just the way the fill colour of the chart works?
Edit:
I am using
https://github.com/danielgindi/Charts
I assume you use this library: https://github.com/kevinbrewster/SwiftCharts
So the LineChartView automatically set alpha for the fill color: https://github.com/kevinbrewster/SwiftCharts/blob/master/SwiftCharts/LineChart.swift#L758
try to set fillAlpha property of the data set