简体   繁体   中英

How to change stroke and path color on UIBezierPath when it crosses a threshold

New to Core Graphics and have a question I'm not sure how to approach.

I need to draw a graph that changes stroke and fill color when the path crosses a threshold line as seen in this example:

在此处输入图片说明

I can draw the graph in all green with no problem. It is the parts that are above or below the threshold lines that I am not sure how to do. The path may cross the threshold in between points.

I have thought of drawing the graph twice, first in orange, then in green, then adding a mask over the green path with the threshold lines forming a rect that would allow the orange version of the graph to show through.

I'm sure there is a better approach. Any pointers would be appreciated. I'm using Swift 4.

You're on the right track. For each color band:

  1. Save the graphics state.
  2. Clip the context to just the area covered by the current color band.
  3. Fill and stroke the full path in the appropriate colors.
  4. Restore the graphics state.

In code, it could look something like this:

    for band in bands {
        let y0 = max(CGPoint(x: 0, y: band.min).applying(transform).y, 0)
        let y1 = min(CGPoint(x: 0, y: band.max).applying(transform).y, mySize.height)
        gc.saveGState(); do {
            gc.clip(to: CGRect(x: 0, y: y0, width: mySize.width, height: y1 - y0))
            band.fillColor.setFill()
            gc.addPath(pathForFilling)
            gc.fillPath()
            band.strokeColor.setStroke()
            gc.addPath(pathForStroking)
            gc.strokePath()
        }; gc.restoreGState()
    }

Result:

演示结果

Here's my full playground code:

import UIKit

class BandedGraphView: UIView {

    struct Band {
        var min: CGFloat // In data geometry
        var max: CGFloat // In data geometry
        var strokeColor: UIColor
        var fillColor: UIColor { return strokeColor.withAlphaComponent(0.2) }
    }

    var bands: [Band] = [] {
        didSet { setNeedsDisplay() }
    }

    /// The minimum visible data geometry coordinate
    var minVisiblePoint = CGPoint.zero {
        didSet { setNeedsDisplay() }
    }

    /// The maximum visible data geometry coordinate
    var maxVisiblePoint = CGPoint(x: 1, y: 1) {
        didSet { setNeedsDisplay() }
    }

    /// Data points, in data geometry.
    var data: [CGPoint] = [] {
        didSet { setNeedsDisplay() }
    }

    var lineWidth: CGFloat = 2 {
        didSet { setNeedsDisplay() }
    }

    override func draw(_ rect: CGRect) {
        guard
            minVisiblePoint.x != maxVisiblePoint.x,
            minVisiblePoint.y != maxVisiblePoint.y,
            !bands.isEmpty,
            !data.isEmpty,
            let gc = UIGraphicsGetCurrentContext()
            else { return }

        let mySize = bounds.size

        var transform = CGAffineTransform.identity
        transform = transform.scaledBy(x: mySize.width / (maxVisiblePoint.x - minVisiblePoint.x), y: mySize.height / (maxVisiblePoint.y - minVisiblePoint.y))
        transform = transform.translatedBy(x: -minVisiblePoint.x, y: -minVisiblePoint.y)

        let pathForStroking = CGMutablePath()
        pathForStroking.addLines(between: data, transform: transform)

        let pathForFilling = pathForStroking.mutableCopy(using: nil)!
        let firstPoint = data.first!.applying(transform)
        let lastPoint = data.last!.applying(transform)
        print(pathForFilling)
        pathForFilling.addLine(to: CGPoint(x: lastPoint.x, y: -CGFloat.greatestFiniteMagnitude))
        pathForFilling.addLine(to: CGPoint(x: firstPoint.x, y: -CGFloat.greatestFiniteMagnitude))
        pathForFilling.closeSubpath()
        print(pathForFilling)

        // Transform the context so the origin is at the lower left.
        gc.translateBy(x: 0, y: mySize.height)
        gc.scaleBy(x: 1, y: -1)

        for band in bands {
            let y0 = max(CGPoint(x: 0, y: band.min).applying(transform).y, 0)
            let y1 = min(CGPoint(x: 0, y: band.max).applying(transform).y, mySize.height)
            gc.saveGState(); do {
                gc.clip(to: CGRect(x: 0, y: y0, width: mySize.width, height: y1 - y0))
                band.fillColor.setFill()
                gc.addPath(pathForFilling)
                gc.fillPath()
                band.strokeColor.setStroke()
                gc.addPath(pathForStroking)
                gc.strokePath()
            }; gc.restoreGState()
        }
    }

}

import PlaygroundSupport

let view = BandedGraphView(frame: CGRect(x: 0, y: 0, width: 400, height: 300))
view.backgroundColor = .white
view.bands = [
    .init(min: -CGFloat.infinity, max: -0.8, strokeColor: .blue),
    .init(min: -0.8, max: 0.2, strokeColor: .red),
    .init(min: 0.2, max: CGFloat.infinity, strokeColor: .orange),
]
view.minVisiblePoint = CGPoint(x: 0, y: -2)
view.maxVisiblePoint = CGPoint(x: 10, y: 2)
view.lineWidth = 2
view.data = stride(from: CGFloat(0), through: CGFloat(10), by: CGFloat(0.01)).map { CGPoint(x: $0, y: cos($0)) }

PlaygroundPage.current.liveView = view

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM