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

SwiftUI Charts组合Area与Line Mark时颜色异常问题求助

问题描述

尝试创建包含两条折线的Line Chart,每条折线下方使用渐变颜色填充区域。当仅存在单条折线或折线与区域标记颜色相同时功能正常,但为折线和区域标记设置不同颜色时出现异常,分离不同Area系列也无法解决问题。

代码片段

Chart(data.chartData) { series in
    ForEach(Array(series.chartData), id: \.self) { element in
        LineMark(
            x: .value("Date", element.date),
            y: .value("Performance", element.value)
        )
        .lineStyle(StrokeStyle(lineWidth: 1))
        .foregroundStyle(by: .value("Name", series.name))
    }
    if series.type == .type1 {
        AreaMark(
            x: .value("Date", element.date),
            y: .value("Performance", element.value)
        )
        .foregroundStyle(areaGradient)
    } else if series.type == .index {
        AreaMark(
            x: .value("Date", element.date),
            y: .value("Performance", element.value)
        )
        .foregroundStyle(areaIndexGradient)
    }
}
.chartForegroundStyleScale(range: [.green, .orange])

完整可运行示例

import SwiftUI
import Charts

struct PData: Codable, Hashable {
    var name: String
    var chartData: [Performance]
}
struct Performance: Codable, Hashable, Identifiable {
    var id: UUID = UUID()
    var name: String
    var type: SeriesType
    var chartData: [PerformanceValue]
}

struct PerformanceValue: Codable, Hashable, Identifiable {
    var id: UUID = UUID()
    var date: Date
    var value: Double
}
enum SeriesType: Codable {
    case type1, index
}

struct Example: View {
    
    let areaGradient = LinearGradient(
            gradient: Gradient (
                colors: [
                    Color.green.opacity(0.5),
                    Color.green.opacity(0.2),
                    Color.green.opacity(0.05),
                    Color.green.opacity(0.00)
                ]
            ),
            startPoint: .top,
            endPoint: .bottom
        )

    let areaIndexGradient = LinearGradient(
        gradient: Gradient (
            colors: [
                Color.orange.opacity(0.5),
                Color.orange.opacity(0.2),
                Color.orange.opacity(0.05),
                Color.orange.opacity(0.00)
            ]
        ),
        startPoint: .top,
        endPoint: .bottom
    )
    
    var body: some View {
        
        let series1 = Performance(name: "Series 1", type: .index, chartData: generatePerformanceValues())
        let series2 = Performance(name: "Series 2", type: .type1, chartData: generatePerformanceValues())
        let data = PData(name: "Chart Date", chartData: [series2, series1])

        VStack {
            Chart(data.chartData) { series in
                ForEach(series.chartData) { element in
                    LineMark(
                        x: .value("Date", element.date),
                        y: .value("Performance", element.value)
                    )
                    .foregroundStyle(by: .value("Name", series.name))
                    
                    if series.type == .type1 {
                        AreaMark(
                            x: .value("Date", element.date),
                            y: .value("Performance", element.value)
                        )
                        .foregroundStyle(areaGradient)
                    } 
                    else if series.type == .index {
                        AreaMark(
                            x: .value("Date", element.date),
                            y: .value("Performance", element.value)
                        )
                        .foregroundStyle(areaIndexGradient)
                    }
                }
            }
            .chartForegroundStyleScale(range: [.green, .orange])
        }
        .padding()
    }
    
    func generatePerformanceValues() -> [PerformanceValue] {
        var performanceValues: [PerformanceValue] = []
        var value = 3.0 // Starting value
        
        for month in 0..<50 {
            let date = Calendar.current.date(byAdding: .month, value: month, to: Date())!
            
            // Add a small random increment or decrement to the value
            let change = Double.random(in: -0.2...0.2)
            value += change
            
            // Clamp the value between 1 and 5
            value = min(max(value, 1), 5)
            
            let performanceValue = PerformanceValue(date: date, value: value.rounded(toPlaces: 2))
            performanceValues.append(performanceValue)
        }
        
        return performanceValues
    }
}

extension Double {
    func rounded(toPlaces places: Int) -> Double {
        let divisor = pow(10.0, Double(places))
        return (self * divisor).rounded() / divisor
    }
}
解决方案

问题核心在于:原代码按单个数据点循环生成标记,Chart无法正确识别系列关联;同时全局样式chartForegroundStyleScale会覆盖AreaMark的手动渐变设置,导致样式冲突。

修正后的代码

import SwiftUI
import Charts

// 数据结构保持不变
struct PData: Codable, Hashable {
    var name: String
    var chartData: [Performance]
}
struct Performance: Codable, Hashable, Identifiable {
    var id: UUID = UUID()
    var name: String
    var type: SeriesType
    var chartData: [PerformanceValue]
}
struct PerformanceValue: Codable, Hashable, Identifiable {
    var id: UUID = UUID()
    var date: Date
    var value: Double
}
enum SeriesType: Codable {
    case type1, index
}

struct Example: View {
    
    let areaGradient = LinearGradient(
            gradient: Gradient (
                colors: [
                    Color.green.opacity(0.5),
                    Color.green.opacity(0.2),
                    Color.green.opacity(0.05),
                    Color.green.opacity(0.00)
                ]
            ),
            startPoint: .top,
            endPoint: .bottom
        )

    let areaIndexGradient = LinearGradient(
        gradient: Gradient (
            colors: [
                Color.orange.opacity(0.5),
                Color.orange.opacity(0.2),
                Color.orange.opacity(0.05),
                Color.orange.opacity(0.00)
            ]
        ),
        startPoint: .top,
        endPoint: .bottom
    )
    
    var body: some View {
        
        let series1 = Performance(name: "Series 1", type: .index, chartData: generatePerformanceValues())
        let series2 = Performance(name: "Series 2", type: .type1, chartData: generatePerformanceValues())
        let data = PData(name: "Chart Date", chartData: [series2, series1])

        VStack {
            Chart(data.chartData) { series in
                // 为整个系列创建折线标记
                LineMark(
                    x: .value("Date", series.chartData.map(\.date)),
                    y: .value("Performance", series.chartData.map(\.value))
                )
                .lineStyle(StrokeStyle(lineWidth: 2))
                // 手动指定折线颜色
                .foregroundStyle(series.type == .type1 ? .green : .orange)
                
                // 为整个系列创建区域填充标记
                AreaMark(
                    x: .value("Date", series.chartData.map(\.date)),
                    y: .value("Performance", series.chartData.map(\.value))
                )
                // 绑定对应渐变样式
                .foregroundStyle(series.type == .type1 ? areaGradient : areaIndexGradient)
            }
            // 手动映射系列名称与颜色,确保图例显示正确
            .chartForegroundStyleScale([
                "Series 1": .orange,
                "Series 2": .green
            ])
            .chartLegend(position: .bottom)
        }
        .padding()
    }
    
    func generatePerformanceValues() -> [PerformanceValue] {
        var performanceValues: [PerformanceValue] = []
        var value = 3.0
        
        for month in 0..<50 {
            let date = Calendar.current.date(byAdding: .month, value: month, to: Date())!
            let change = Double.random(in: -0.2...0.2)
            value += change
            value = min(max(value, 1), 5)
            
            let performanceValue = PerformanceValue(date: date, value: value.rounded(toPlaces: 2))
            performanceValues.append(performanceValue)
        }
        
        return performanceValues
    }
}

extension Double {
    func rounded(toPlaces places: Int) -> Double {
        let divisor = pow(10.0, Double(places))
        return (self * divisor).rounded() / divisor
    }
}

关键修正点

  1. 按系列组织标记:直接为每个series创建LineMark和AreaMark,使用整个系列的数据集,让Chart正确识别系列关联,避免单个数据点标记的混乱。
  2. 分离样式控制:手动为折线指定颜色,为区域填充设置独立渐变,避免全局样式覆盖自定义渐变。
  3. 明确图例映射:通过chartForegroundStyleScale手动绑定系列名称与颜色,保证图例显示和实际样式一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 01:02:04