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

Swift Charts中AreaMark在特定数据结构下失效的原因及解决方法

问题描述

我尝试用Swift Charts的AreaMark可视化数据,但不同数据结构会影响结果。以下两个结构体存储了相同的数据,但组织方式不同:

import SwiftUI
import Charts

struct Food: Identifiable {
  let name: String
  let sales: Int
  let day: Int
  let id = UUID()
  
  init(name: String, sales: Int, day: Int) {
    self.name = name
    self.sales = sales
    self.day = day
  }
}

struct Sales: Identifiable {
  let day: Int
  let burger: Int
  let salad: Int
  let steak: Int
  let id = UUID()
  
  var total: Int {
    burger + salad + steak
  }
  
  init(day: Int, burger: Int, salad: Int, steak: Int) {
    self.day = day
    self.burger = burger
    self.salad = salad
    self.steak = steak
  }
}

使用第一个Food结构体时,所有图表类型都能正常工作;但第二个Sales结构体用BarMark和PointMark没问题,用AreaMark就会失效——注释掉三个AreaMark中的两个,剩下的一个能正常显示,多个共存时就出问题。

复现代码如下:

struct ExperimentView: View {
  let cheeseburgerSalesByItem: [Food] = [
    .init(name: "Burger", sales: 29, day: 1),
    .init(name: "Salad", sales: 35, day: 1),
    .init(name: "Steak", sales: 30, day: 1),
    .init(name: "Burger", sales: 32, day: 2),
    .init(name: "Salad", sales: 38, day: 2),
    .init(name: "Steak", sales: 42, day: 2),
    .init(name: "Burger", sales: 35, day: 3),
    .init(name: "Salad", sales: 29, day: 3),
    .init(name: "Steak", sales: 41, day: 3),
    .init(name: "Burger", sales: 29, day: 4),
    .init(name: "Salad", sales: 38, day: 4),
    .init(name: "Steak", sales: 39, day: 4),
    .init(name: "Burger", sales: 43, day: 5),
    .init(name: "Salad", sales: 42, day: 5),
    .init(name: "Steak", sales: 30, day: 5),
    .init(name: "Burger", sales: 45, day: 6),
    .init(name: "Salad", sales: 39, day: 6),
    .init(name: "Steak", sales: 31, day: 6),
    .init(name: "Burger", sales: 37, day: 7),
    .init(name: "Salad", sales: 35, day: 7),
    .init(name: "Steak", sales: 30, day: 7),
  ]
  
  let cheeseburgerSalesByDay: [Sales] = [
    .init(day: 1, burger: 29, salad: 35, steak: 30),
    .init(day: 2, burger: 32, salad: 38, steak: 42),
    .init(day: 3, burger: 35, salad: 29, steak: 41),
    .init(day: 4, burger: 29, salad: 38, steak: 39),
    .init(day: 5, burger: 43, salad: 42, steak: 30),
    .init(day: 6, burger: 45, salad: 39, steak: 31),
    .init(day: 7, burger: 37, salad: 35, steak: 30)
  ]
  
  var body: some View {
    VStack {
      Chart(cheeseburgerSalesByItem) { sale in
        AreaMark(
          x: .value("Day", sale.day),
          y: .value("Sales", sale.sales)
        )
        .foregroundStyle(by: .value("Food Item", sale.name))
      }
      .chartXScale(domain: 1...7)
      .padding()
      
      Spacer()
      
      Chart(cheeseburgerSalesByDay) { sale in
        AreaMark(
          x: .value("Day", sale.day),
          y: .value("Burger", sale.burger)
        )
        .foregroundStyle(.brown)
        
        AreaMark(
          x: .value("Day", sale.day),
          y: .value("Salad", sale.salad)
        )
        .foregroundStyle(.green)
        
        AreaMark(
          x: .value("Day", sale.day),
          y: .value("Steak", sale.steak)
        )
        .foregroundStyle(.red)
      }
      .padding()
    }
  }
}

请问问题出在哪里?如果想使用第二种Sales数据结构绘制AreaMark图表,应该怎么做?


问题原因与解决方案

问题原因

Swift Charts绘制AreaMark时,需要为每个数据系列(比如Burger、Salad、Steak)构建连续的点序列。在你当前的Sales结构体写法中,每个Sales实例对应一天的全品类数据,你在同一个Chart的循环里添加三个AreaMark,相当于给每个Day生成了三个独立的、仅包含单个点的系列——这种情况下图表无法将分散的单点连接成完整的区域。

而Food结构体的写法是把每个品类的每日数据拆成独立实例,图表能自动识别同一个品类的所有点属于同一系列,从而正确连接成区域。

解决方案

有两种可行的解决方式:

方式1:将Sales数据转换为扁平结构(复用Food逻辑)

在使用前把cheeseburgerSalesByDay转换成[Food]格式,这样就能直接复用第一个图表的写法:

var flattenedSales: [Food] {
    cheeseburgerSalesByDay.flatMap { daySales in
        [
            Food(name: "Burger", sales: daySales.burger, day: daySales.day),
            Food(name: "Salad", sales: daySales.salad, day: daySales.day),
            Food(name: "Steak", sales: daySales.steak, day: daySales.day)
        ]
    }
}

之后在Chart中使用flattenedSales即可。

方式2:为每个品类单独构建数据序列

不转换原始数据,而是为每个品类单独提取每日数据,作为独立序列传入Chart:

Chart {
    // Burger系列
    ForEach(cheeseburgerSalesByDay) { sale in
        AreaMark(
            x: .value("Day", sale.day),
            y: .value("Sales", sale.burger)
        )
    }
    .foregroundStyle(.brown)
    .seriesName("Burger")
    
    // Salad系列
    ForEach(cheeseburgerSalesByDay) { sale in
        AreaMark(
            x: .value("Day", sale.day),
            y: .value("Sales", sale.salad)
        )
    }
    .foregroundStyle(.green)
    .seriesName("Salad")
    
    // Steak系列
    ForEach(cheeseburgerSalesByDay) { sale in
        AreaMark(
            x: .value("Day", sale.day),
            y: .value("Sales", sale.steak)
        )
    }
    .foregroundStyle(.red)
    .seriesName("Steak")
}
.chartXScale(domain: 1...7)

这种写法的核心是:每个品类的所有AreaMark都被包裹在同一个ForEach中,图表会识别这些点属于同一系列,从而正确绘制出连续的区域。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 21:43:18