You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS自定义绘图:如何在两条折线间正确填充颜色?

问题描述

我拥有三个数组xValues、yMinValues和yMaxValues,想要绘制一张图表,需基于yMinValues和yMaxValues数组绘制两条折线,并在两条折线之间填充颜色。目前已完成折线绘制,但填充区域不符合预期,期望实现的效果是仅在两条折线之间填充颜色,不要填充外部区域。

原实现代码如下:

class CustomView: UIView {
    let xValues: [CGFloat] = [0, 2, 4, 6, 8, 10, 12, 14]
    let yMaxValues: [CGFloat] = [4, 6, 9, 10, 10, 11, 10, 11]
    let yMinValues: [CGFloat] = [2, 4, 5, 6, 7, 8, 9, 11]
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        guard let context = UIGraphicsGetCurrentContext() else { return }
        
        // Arka planı beyaz yapın
        UIColor.white.setFill()
        context.fill(rect)
        
        // Grafik boyutlarını alın
        let graphWidth = rect.width
        let graphHeight = rect.height
        
        // Min ve max değerleri belirleyin
        let minValue = min(yMinValues.min() ?? 0.0, yMinValues.max() ?? 0.0)
        let maxValue = max(yMaxValues.min() ?? 0.0, yMaxValues.max() ?? 0.0)
        
        // Y değerlerini ölçeklendirin
        let yScaleFactor = graphHeight / (maxValue - minValue)
        
        // X değerlerini ölçeklendirin
        let xScaleFactor = graphWidth / CGFloat(xValues.count - 1)
        
        // Grafik alanını doldurun
        let yFillValuesPath = UIBezierPath()

        // YFillValues için line çizin
        for i in 0..<xValues.count {
            let x = CGFloat(i) * xScaleFactor
            let yFill = graphHeight - (yMinValues[i] - minValue) * yScaleFactor // Burada yMinValues kullanılmalı
            if i == 0 {
                yFillValuesPath.move(to: CGPoint(x: x, y: yFill))
            } else {
                yFillValuesPath.addLine(to: CGPoint(x: x, y: yFill))
            }

            // Noktalara yFillValues değerlerini yaz
            let pointLabel = UILabel(frame: CGRect(x: x - 15, y: yFill - 30, width: 30, height: 30))
            pointLabel.text = "\(yMinValues[i])"
            pointLabel.textColor = UIColor.black
            pointLabel.textAlignment = .center
            pointLabel.font = UIFont.systemFont(ofSize: 10)
            addSubview(pointLabel)
        }
        
        // Grafik alanını doldurun
        let fillColor = UIColor(red: 0.5, green: 0.5, blue: 1.0, alpha: 0.5)
        fillColor.setFill()
        
        yFillValuesPath.addLine(to: CGPoint(x: yMaxValues.last ?? 0, y: graphHeight - (yMaxValues.last ?? 0 - minValue) * yScaleFactor)) // yMinValues kullanılmalı
        yFillValuesPath.addLine(to: CGPoint(x: yMinValues.first ?? 0, y: graphHeight - (yMinValues.first ?? 0 - maxValue) * yScaleFactor)) // yMinValues kullanılmalı
    
        
        yFillValuesPath.fill()

        yFillValuesPath.close()
        
        // YValues için line çizin
        let yValuesPath = UIBezierPath()
        for i in 0..<xValues.count {
            let x = CGFloat(i) * xScaleFactor
            let y = graphHeight - (yMaxValues[i] - minValue) * yScaleFactor
            if i == 0 {
                yValuesPath.move(to: CGPoint(x: x, y: y))
            } else {
                yValuesPath.addLine(to: CGPoint(x: x, y: y))
            }
            
            // Noktalara yValues değerlerini yaz
            let pointLabel = UILabel(frame: CGRect(x: x - 15, y: y - 30, width: 30, height: 30))
            pointLabel.text = "\(yMaxValues[i])"
            pointLabel.textColor = UIColor.black
            pointLabel.textAlignment = .center
            pointLabel.font = UIFont.systemFont(ofSize: 10)
            addSubview(pointLabel)
        }
        
        // Çizgileri çizin
        let lineColor = UIColor.black.cgColor
        context.setStrokeColor(lineColor)
        yValuesPath.stroke()
        yFillValuesPath.stroke()
        
        // X ekseni çizin
        context.move(to: CGPoint(x: 0, y: graphHeight))
        context.addLine(to: CGPoint(x: graphWidth, y: graphHeight))
        context.strokePath()
        
        // Y ekseni çizin
        context.move(to: CGPoint(x: 0, y: graphHeight))
        context.addLine(to: CGPoint(x: 0, y: 0))
        context.strokePath()
        
        // X değerlerini gösterin
        for i in 0..<xValues.count {
            let x = CGFloat(i) * xScaleFactor
            let label = UILabel(frame: CGRect(x: x - 15, y: graphHeight + 5, width: 30, height: 20))
            label.text = "\(xValues[i])"
            label.textColor = UIColor.black
            label.textAlignment = .center
            label.font = UIFont.systemFont(ofSize: 12)
            addSubview(label)
        }
        
        // Y değerlerini gösterin
        for i in 0..<yMaxValues.count {
            let y = graphHeight - (yMaxValues[i] - minValue) * yScaleFactor
            let label = UILabel(frame: CGRect(x: -35, y: y - 10, width: 30, height: 20))
            label.text = "\(yMaxValues[i])"
            label.textColor = UIColor.black
            label.textAlignment = .right
            label.font = UIFont.systemFont(ofSize: 12)
            addSubview(label)
        }
    }
}
解决方案

问题出在填充路径的构建逻辑上,当前路径没有正确闭合两条折线的端点。正确的做法是创建一个包含yMin折线所有点 + 反向的yMax折线所有点的闭合路径,这样就能精准填充两条线之间的区域。

修正后的完整代码如下:

class CustomView: UIView {
    let xValues: [CGFloat] = [0, 2, 4, 6, 8, 10, 12, 14]
    let yMaxValues: [CGFloat] = [4, 6, 9, 10, 10, 11, 10, 11]
    let yMinValues: [CGFloat] = [2, 4, 5, 6, 7, 8, 9, 11]
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        
        guard let context = UIGraphicsGetCurrentContext() else { return }
        
        // 填充白色背景
        UIColor.white.setFill()
        context.fill(rect)
        
        // 获取图表尺寸
        let graphWidth = rect.width
        let graphHeight = rect.height
        
        // 计算全局的最小和最大Y值(确保覆盖所有数据)
        let allYValues = yMinValues + yMaxValues
        let minValue = allYValues.min() ?? 0.0
        let maxValue = allYValues.max() ?? 0.0
        
        // 计算缩放因子
        let yScaleFactor = graphHeight / (maxValue - minValue)
        let xScaleFactor = graphWidth / CGFloat(xValues.count - 1)
        
        // 构建填充路径:先画yMin折线,再反向画yMax折线,最后闭合
        let fillPath = UIBezierPath()
        
        // 添加yMin折线的所有点
        for i in 0..<xValues.count {
            let x = CGFloat(i) * xScaleFactor
            let y = graphHeight - (yMinValues[i] - minValue) * yScaleFactor
            if i == 0 {
                fillPath.move(to: CGPoint(x: x, y: y))
            } else {
                fillPath.addLine(to: CGPoint(x: x, y: y))
            }
        }
        
        // 反向添加yMax折线的所有点(从最后一个到第一个)
        for i in (0..<xValues.count).reversed() {
            let x = CGFloat(i) * xScaleFactor
            let y = graphHeight - (yMaxValues[i] - minValue) * yScaleFactor
            fillPath.addLine(to: CGPoint(x: x, y: y))
        }
        
        // 闭合路径并填充
        fillPath.close()
        let fillColor = UIColor(red: 0.5, green: 0.5, blue: 1.0, alpha: 0.5)
        fillColor.setFill()
        fillPath.fill()
        
        // 绘制yMin折线
        let yMinPath = UIBezierPath()
        for i in 0..<xValues.count {
            let x = CGFloat(i) * xScaleFactor
            let y = graphHeight - (yMinValues[i] - minValue) * yScaleFactor
            if i == 0 {
                yMinPath.move(to: CGPoint(x: x, y: y))
            } else {
                yMinPath.addLine(to: CGPoint(x: x, y: y))
            }
            
            // 添加yMin数值标签
            let pointLabel = UILabel(frame: CGRect(x: x - 15, y: y - 30, width: 30, height: 30))
            pointLabel.text = "\(yMinValues[i])"
            pointLabel.textColor = .black
            pointLabel.textAlignment = .center
            pointLabel.font = .systemFont(ofSize: 10)
            addSubview(pointLabel)
        }
        
        // 绘制yMax折线
        let yMaxPath = UIBezierPath()
        for i in 0..<xValues.count {
            let x = CGFloat(i) * xScaleFactor
            let y = graphHeight - (yMaxValues[i] - minValue) * yScaleFactor
            if i == 0 {
                yMaxPath.move(to: CGPoint(x: x, y: y))
            } else {
                yMaxPath.addLine(to: CGPoint(x: x, y: y))
            }
            
            // 添加yMax数值标签
            let pointLabel = UILabel(frame: CGRect(x: x - 15, y: y - 30, width: 30, height: 30))
            pointLabel.text = "\(yMaxValues[i])"
            pointLabel.textColor = .black
            pointLabel.textAlignment = .center
            pointLabel.font = .systemFont(ofSize: 10)
            addSubview(pointLabel)
        }
        
        // 设置折线颜色并绘制
        let lineColor = UIColor.black.cgColor
        context.setStrokeColor(lineColor)
        yMinPath.stroke()
        yMaxPath.stroke()
        
        // 绘制X轴
        context.move(to: CGPoint(x: 0, y: graphHeight))
        context.addLine(to: CGPoint(x: graphWidth, y: graphHeight))
        context.strokePath()
        
        // 绘制Y轴
        context.move(to: CGPoint(x: 0, y: graphHeight))
        context.addLine(to: CGPoint(x: 0, y: 0))
        context.strokePath()
        
        // 添加X轴数值标签
        for i in 0..<xValues.count {
            let x = CGFloat(i) * xScaleFactor
            let label = UILabel(frame: CGRect(x: x - 15, y: graphHeight + 5, width: 30, height: 20))
            label.text = "\(xValues[i])"
            label.textColor = .black
            label.textAlignment = .center
            label.font = .systemFont(ofSize: 12)
            addSubview(label)
        }
        
        // 添加Y轴数值标签(显示全局的min和max,避免重复)
        let yMinLabel = UILabel(frame: CGRect(x: -35, y: graphHeight - 10, width: 30, height: 20))
        yMinLabel.text = "\(minValue)"
        yMinLabel.textColor = .black
        yMinLabel.textAlignment = .right
        yMinLabel.font = .systemFont(ofSize: 12)
        addSubview(yMinLabel)
        
        let yMaxLabel = UILabel(frame: CGRect(x: -35, y: 0 - 10, width: 30, height: 20))
        yMaxLabel.text = "\(maxValue)"
        yMaxLabel.textColor = .black
        yMaxLabel.textAlignment = .right
        yMaxLabel.font = .systemFont(ofSize: 12)
        addSubview(yMaxLabel)
    }
}

关键修改点

  • 填充路径构建:先按顺序添加yMinValues的所有点,再反向添加yMaxValues的所有点,最后调用close()闭合路径,形成的区域就是两条折线之间的部分。
  • 全局Y值计算:合并yMinValues和yMaxValues计算全局的最小/最大值,确保所有数据点都能正确映射到图表范围内。
  • Y轴标签优化:改为显示全局的最小和最大值,避免重复添加相同标签。

内容的提问来源于stack exchange,提问作者esra didem

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 07:02:31