Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add filled circles (markers) at SwiftUI path points

Tags:

swift

swiftui

I'm trying to draw a line with markers at each point by using a Shape view in SwiftUI. I want the line to have a filled circle at each CGPoint on the line. The closest to this that I've gotten is by adding an arc at each point. Instead of using the arc, how can I add a Circle() shape at each point? I'm open to other approaches to accomplish this. My only requirements are to use SwiftUI and have the ability to interact with the markers.

line plot

import SwiftUI

struct LineShape: Shape {
    
    let values: [Double]
    
    func path(in rect: CGRect) -> Path {
        let xStep = rect.width / CGFloat(values.count - 1)
        
        var path = Path()
        path.move(to: CGPoint(x: 0.0, y: (1 - values[0]) * Double(rect.height)))
        
        for i in 1..<values.count {
            let pt = CGPoint(x: Double(i) * Double(xStep), y: (1 - values[i]) * Double(rect.height))
            path.addLine(to: pt)
            path.addArc(center: pt, radius: 8, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 360), clockwise: false)
        }
        
        return path
    }
}

struct ContentView: View {
    var body: some View {
        LineShape(values: [0.2, 0.4, 0.3, 0.8, 0.5])
            .stroke(.red, lineWidth: 2.0)
            .padding()
            .frame(width: 400, height: 300)
    }
}
like image 668
wigging Avatar asked Nov 24 '25 23:11

wigging


1 Answers

You can make two different shapes, one for the line and one for the markers, and overlay them. Then you can also control their coloring individually:

enter image description here

struct LineShape: Shape {
    
    let values: [Double]
    
    func path(in rect: CGRect) -> Path {
        let xStep = rect.width / CGFloat(values.count - 1)
        
        var path = Path()
        path.move(to: CGPoint(x: 0.0, y: (1 - values[0]) * Double(rect.height)))
        
        for i in 1..<values.count {
            let pt = CGPoint(x: Double(i) * Double(xStep), y: (1 - values[i]) * Double(rect.height))
            path.addLine(to: pt)
        }
        
        return path
    }
}

struct MarkersShape: Shape {
    
    let values: [Double]
    
    func path(in rect: CGRect) -> Path {
        let xStep = rect.width / CGFloat(values.count - 1)
        
        var path = Path()
        
        for i in 1..<values.count {
            let pt = CGPoint(x: Double(i) * Double(xStep), y: (1 - values[i]) * Double(rect.height))
            path.addEllipse(in: CGRect(x: pt.x - 8, y: pt.y - 8, width: 16, height: 16))
        }
        
        return path
    }
}

struct ContentView: View {
    var body: some View {
        
        LineShape(values: [0.2, 0.4, 0.3, 0.8, 0.5])
            .stroke(.red, lineWidth: 2.0)
            .overlay(
                MarkersShape(values: [0.2, 0.4, 0.3, 0.8, 0.5])
                    .fill(.blue)
            )
            .frame(width: 350, height: 300)
    }
}
like image 51
ChrisR Avatar answered Nov 26 '25 15:11

ChrisR



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!