Search code examples
iosswiftcore-graphicsuibezierpathios-charts

iOS Radar Chart with 3D Effect


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? this


Solution

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