在SwiftUI路径点处添加实心圆(标记)

djp7away  于 2023-03-22  发布在  Swift
关注(0)|答案(3)|浏览(125)

我尝试使用SwiftUI中的Shape视图在每个点处绘制一条带有标记的线。我希望线上的每个CGPoint处都有一个实心圆。我得到的最接近的方法是在每个点处添加一个圆弧。而不是使用圆弧,我如何在每个点添加Circle()形状?我对其他方法持开放态度。我唯一的要求是使用SwiftUI并能够与标记交互。

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)
    }
}
9vw9lbht

9vw9lbht1#

您可以制作两个不同的形状,一个用于线条,一个用于标记,并将它们叠加。然后您还可以单独控制它们的颜色:

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

vmpqdwk32#

根据以前的帖子和评论,这里是我的解决方案:
首先,需要使用扩展来填充和设置形状笔划:

extension Shape {
    public func fill<Shape: ShapeStyle>(_ fillContent: Shape, strokeColor: Color, lineWidth: CGFloat) -> some View {
        ZStack {
            self.fill(fillContent)
            self.stroke(strokeColor, lineWidth: lineWidth)
        }
    }
}

另一件事是Scaler struct用于进行缩放计算:

struct Scaler {
    let bounds: CGPoint
    let maxVal: Double
    let minVal: Double
    let valuesCount: Int
    
    var xFactor: CGFloat {
        valuesCount < 2 ? bounds.x : bounds.x / CGFloat(valuesCount - 1)
    }
    
    var yFactor: CGFloat {
        bounds.y / maxVal
    }
}

现在是时候绘制真实的的图表形状了:

struct ChartLineDotsShape: Shape {
    let values: [Double]
    let dotRadius: CGFloat
    
    func path(in rect: CGRect) -> Path {
        guard let maxVal = values.max(), let minVal = values.min() else { return Path() }
        let scaler = Scaler(bounds: CGPoint(x: rect.width, y: rect.height), maxVal: maxVal, minVal: minVal, valuesCount: values.count)
        return Path { path in
            var valuesIterator = values.makeIterator()
            
            let dx = scaler.xFactor
            var x = 0.0
            guard let y = valuesIterator.next() else { return }
            path.move(to: CGPoint(x: x, y: calculate(y, scaler: scaler)))
            
            draw(point: CGPoint(x: x, y: calculate(y, scaler: scaler)), on: &path)
            x += dx
            
            while let y = valuesIterator.next() {
                draw(point: CGPoint(x: x, y: calculate(y, scaler: scaler)), on: &path)
                x += dx
            }
        }
    }
    
    private func calculate(_ value: CGFloat, scaler: Scaler) -> CGFloat {
        scaler.bounds.y - value * scaler.yFactor
    }
    
    private func draw(point: CGPoint, on path: inout Path) {
        path.addLine(to: CGPoint(x: point.x, y: point.y))
        path.addEllipse(in: CGRect(x: point.x - dotRadius * 0.5, y: point.y - dotRadius * 0.5, width: dotRadius, height: dotRadius))
        path.move(to: CGPoint(x: point.x, y: point.y))
    }
}

如果有单次迭代循环线点绘制形状,我们可以尝试在某些视图中使用它:

public struct ChartView: View {
    public var body: some View {
        ChartLineDotsShape(values: [0.3, 0.5, 1.0, 2.0, 2.5, 2.0, 3.0, 6.0, 2.0, 1.5, 2.0], dotRadius: 4.0)
            .fill(Color.blue, strokeColor: Color.blue, lineWidth: 1.0)
            .shadow(color: Color.blue, radius: 2.0, x: 0.0, y: 0.0)
            .frame(width: 320.0, height: 200.0)
            .background(Color.blue.opacity(0.1))
            .clipped()
    }
}

最后预览设置:

struct ChartView_Previews: PreviewProvider {
    static var previews: some View {
        ChartView()
            .preferredColorScheme(.dark)
    }
}

中提琴:

nbysray5

nbysray53#

对于图表,您现在可以使用.symbol(.circle)

相关问题