SwiftUI可视化图表框架Charts
SwiftUI可视化图表框架Charts

SwiftUI可视化图表框架Charts

SwiftUI 的 Charts 是一个非常强大且直观的框架,专门用于创建数据可视化图表。它引入了简洁的 API 来轻松创建各种类型的图表,比如柱状图、折线图、饼图等。以下是一些关于 SwiftUI Charts 的关键概念和使用方式。

基本结构

在 SwiftUI 中创建图表,需要导入 Charts 模块

import Charts

使用 Chart 视图:

import SwiftUI
import Charts

struct ContentView: View {
    var data: [SalesData] = [
        SalesData(month: "Jan", sales: 200),
        SalesData(month: "Feb", sales: 150),
        SalesData(month: "Mar", sales: 300)
    ]

    var body: some View {
        Chart(data) { item in
            BarMark(
                x: .value("Month", item.month),
                y: .value("Sales", item.sales)
            )
        }
        .frame(height: 300)
        .padding()
    }
}

struct SalesData: Identifiable {
    let id = UUID()
    let month: String
    let sales: Int
}

支持的图表类型

SwiftUI Charts 支持多种类型的图表,每种类型都有其独特的 Mark,用来绘制对应的图表形态。以下是 SwiftUI Charts 目前支持的主要图表类型:

1、柱状图 (Bar Chart)

标记类型: BarMark

用于展示分类数据之间的比较。

既支持垂直柱状图,也支持水平柱状图。

Chart(data) {
    BarMark(
        x: .value("Category", $0.category),
        y: .value("Value", $0.value)
    )
}

2、折线图 (Line Chart)

标记类型: LineMark

用于表示连续数据的趋势。

通常用在时间序列数据的可视化中。

Chart(data) {
    LineMark(
        x: .value("Time", $0.time),
        y: .value("Value", $0.value)
    )
}

3、面积图 (Area Chart)

标记类型: AreaMark

用于展示数据随时间的变化,并填充曲线下方的区域。

可以叠加多个 AreaMark 创建堆叠图。

Chart(data) {
    AreaMark(
        x: .value("Date", $0.date),
        y: .value("Count", $0.count)
    )
}

4、散点图 (Scatter Plot)

标记类型: PointMark

用于表示两个变量之间的关系。

可以通过形状、大小或颜色来区分类别。

Chart(data) {
    PointMark(
        x: .value("X Value", $0.x),
        y: .value("Y Value", $0.y)
    )
}

5、饼图 / 环形图 (Pie/Donut Chart)

标记类型: 使用 SectorMark

适用于展示分类数据的占比。

通过调整起点和终点角度绘制。

Chart(data) { item in
    SectorMark(
        angle: .value("Sales", item.sales)
    )
    .foregroundStyle(by: .value("Month", item.month))
}

SectorMark默认情况下,它会将所有数据的数值加总,并根据每个数据占总数的比例生成一个完整的圆。数据被映射成扇形角度,但它们没有视觉上的区分,导致它们叠加在一起,看起来像一个单一的圆。

因此,希望饼图能按某个参数分区并以不同颜色显示,可以通过设置 foregroundStyle 来实现。

6、组合图表 (Combination Charts)

组合标记: 使用多种 Mark 叠加。

例如,将柱状图和折线图组合在一起:

Chart(data) {
    BarMark(
        x: .value("Category", $0.month),
        y: .value("Value", $0.sales)
    )
    LineMark(
        x: .value("Category", $0.month),
        y: .value("Value", $0.sales)
    )
}

7、雷达图 (Radar Chart)

目前 Radar Charts 并未原生支持,需要通过自定义绘制来实现。

8、条形图 (Horizontal Bar Chart)

标记类型: 水平的 BarMark,将 x 和 y 值互换。

Chart(data) {
    BarMark(
        x: .value("Value", $0.sales),
        y: .value("Category", $0.month)
    )
}

自定义图表样式

颜色:可以自定义每个 Mark 的颜色。

图例:通过 .chartLegend() 添加图例。

轴样式:自定义轴线、刻度和标签。

BarMark(
    x: .value("Category", "Electronics"),
    y: .value("Revenue", 1000)
)
.foregroundStyle(Color.blue)

数据动态更新

Charts 的数据源可以绑定到动态数据模型,从而支持实时更新。

@State private var salesData = [
    SalesData(month: "Jan", sales: 200),
    SalesData(month: "Feb", sales: 150)
]

Button("Add Data") {
    salesData.append(SalesData(month: "Mar", sales: 300))
}

Mark参数

在 SwiftUI 的 Charts 中,每个 Mark 都有多个参数,因为它们提供了高度的灵活性和可定制性。这些参数控制图表的外观和行为,使其适配不同的数据结构和可视化需求。

参考示例

struct SampleRating {
    let place: String
    let rating: Int
    
    static let ratings: [SampleRating] = [
        SampleRating(place: "Bellagio", rating: 88),
        SampleRating(place: "Paris", rating: 75),
        SampleRating(place: "Treasure Island", rating: 33),
        SampleRating(place: "Excalibur", rating: 88)
    ]
}

struct VegasChart: View {
    var body: some View {
        Chart(SampleRating.ratings, id: \.place) { rating in
            SectorMark(angle: .value("Ratings", rating.rating), innerRadius: .ratio(0.5), angularInset: 1)
                .cornerRadius(7)
                .foregroundStyle(by: .value("Place", rating.rating))
        }
        .padding()
        .frame(height: 500)
    }
}

在这段SectorMark的代码中:

1)angle控制扇形的角度大小:.value(“Ratings”, rating.rating) 表示从数据中取 rating.rating 作为角度值,并将该数据绑定到图表的 “Ratings” 维度。

2)innerRadius设置扇形的内半径:.ratio(0.5) 表示内半径是图表外半径的 50%。

3)angularInset设置扇形的间隔角度:angularInset: 1 表示在每个扇形之间留出 1° 的间隙, 提高了可视化的清晰度,特别是在扇形数据较多时。

4)cornerRadius设置扇形的边缘圆角:.cornerRadius(7) 会使扇形的外边缘变得平滑。

5)foregroundStyle设置扇形的不同颜色:.value(“Place”, rating.rating) 将数据绑定到 “Place” 维度, Charts 会自动为不同的 “Place” 分配不同的颜色。通过颜色区分数据类别,提高数据的可读性

高级功能

组合图表:在一个图表中叠加不同类型的 Mark。

动画效果:通过 withAnimation 添加动态效果。

筛选数据:使用 SwiftUI 的 Filter。

隐藏图例

在饼图中通常会自动生成图例和注释,如果图例影响到布局,可以通过chartLegend(.hidden)进行隐藏:

Chart(data, id: \.PiggyBank) { item in
    SectorMark(
        ...
    )
}
.chartLegend(.hidden) // 隐藏默认的图例

实用场景

数据可视化:展示统计数据(如财务报表、用户增长趋势)。

健康和活动:显示步数、心率等。

科学实验:展示实验结果图表。

实际应用

例如,我想要在存钱罐的应用中,将现有的目标金额和当前金额两个字段,转换为饼图,以便更清晰的了解到存钱的进度。

这个DetailView就是显示的视图,有两个金额参数:

struct DetailView: View {
    var CurrentAmount: Double   // 当前金额
    var TargetAmount: Double    // 目标金额
    var body: some View {
        // 可视化图表
    }
}

1、数据结构

首先,我需要创建一个数组结构,作为Chart组件的数据源:

var data: [(label: String, value: Double, color: Color)] {
    return [
        (String(localized:"Deposit"), CurrentAmount, .blue),   // 已存入金额
        (String(localized:"Remaining"), max(TargetAmount - CurrentAmount,0), .gray)
    ]
}

data 是一个数组,包含 (label, value, color) 三个属性:

label:存钱罐的分类(“Deposit” 和 “Remaining”),用于本地化显示。

value:代表金额(当前存入金额、剩余金额)。

color:指定该类别的颜色(蓝色表示已存入,灰色表示剩余部分)。

2、创建Chart组件

Chart(data, id: \.label) { item in ...}

Chart 组件使用 data 数组作为数据源,并指定 label 作为唯一标识。

id: \.label 确保每个扇形区域在数据变化时正确更新。

3、SectorMark(扇形图)

SectorMark(
    angle: .value("Amount", item.value),
    innerRadius: .ratio(0.5),
    outerRadius: .ratio(1),
    angularInset: 1
)

SectorMark 是 Swift Charts 用于绘制 饼图(Pie Chart)的 Mark 类型。

1)angle: .value(“Amount”, item.value)

以 item.value 作为角度数据,即已存入金额和剩余金额的数值决定了扇形的大小。

2)innerRadius: .ratio(0.5)

内半径设定为 50%(0.5),意味着这是一个 环形图(Donut Chart)。

3)outerRadius: .ratio(1)

外半径为 100%(1),确保图形铺满指定的 Chart 区域。

4)angularInset: 1

使扇形之间有 1 度 的间隙,增强视觉分离感。

需要注意的是,angle: .value(“Amount”, item.value),这里的”Amount” 是数据键(Data Key),代表该数据点的角度来源。

item.value 决定了扇形的大小(例如存入金额 vs 剩余金额)。

“Amount” 这个标签不会自动显示在图表上,它只是一个标识符,供 Swift Charts 内部使用

4、foregroundStyle 设定颜色

SectorMark设置foregroundStyle设置前景色:

.foregroundStyle(item.color)

如果想要使用Charts默认颜色,可以使用foregroundStyle(by:):

.foregroundStyle(by: .value("Amount", item.label))

foregroundStyle:根据 item.label 赋予不同的颜色。

by: .value(“Amount”, item.label) 让 Swift Charts 自动匹配相同 label 的数据点。

5、完整代码

struct DetailView: View {
    var CurrentAmount: Double
    var TargetAmount: Double
    var data:[(label: String, value: Double, color: Color)] {
        return [
            (String(localized:"Deposit"), CurrentAmount, .blue),   // 已存入金额
            (String(localized:"Remaining"), max(TargetAmount - CurrentAmount,0),.gray)
        ]
    }
    var body: some View {
        Chart(data, id: \.label) { item in
            SectorMark(
                angle: .value("Amount", item.value),
                innerRadius: .ratio(0.5),
                outerRadius: .ratio(1),
                angularInset: 1
            )
            .cornerRadius(7)
            .foregroundStyle(by: .value("Amount", item.label))
        }
        .frame(width: 120, height: 170)
        .padding(20)
    }
}

总结

SwiftUI Charts 是高度灵活的工具,可以通过以上类型以及自定义样式满足大多数数据可视化需求。

参考文章

iOS 18, SwiftUI 6, & Swift 6: 从零开始构建iOS应用程序, 涵盖visionOS, macOS, watchOS:https://www.bilibili.com/video/BV1b6421f7Px?spm_id_from=333.788.videopod.episodes&vd_source=f21219cb93118beac6a36b0ef961df6a&p=11

扩展知识

Argument ‘x’ must precede argument ‘y’报错

如果遇到以下报错:

Argument 'x' must precede argument 'y'
Replace 'y: .value("Category", $0.month),
x: .value("Value", $0.sales)' with 'x: .value("Value", $0.sales), y: .value("Category", $0.month)'

问题原因为:在 SwiftUI 的 Charts 框架中,参数的顺序有特定要求。BarMark 的参数顺序规定 x 必须在 y 之前。提示 Argument ‘x’ must precede argument ‘y’ 的原因正是因为代码中将 y 参数写在了 x 参数之前,违反了 API 的定义。

解决方案:根据提示修改参数的顺序即可。

BarMark(
    x: .value("Value", $0.sales), // x 参数在前
    y: .value("Category", $0.month) // y 参数在后
)

设置X轴和Y轴的范围

如果想要调整图表的X轴和Y轴范围,可以通过chartXScale和chartYScale进行调整:

Chart(SampleRating.ratings, id: \.trip) { rating in
    PointMark(x: .value("Year", rating.trip), y: .value("Ratings", rating.trip - 2020 + 88))
    LineMark(x: .value("Year", rating.trip), y: .value("Ratings", rating.rating))
}
.chartXScale(domain: 2020...2024)
.chartYScale(domain: 1...100)

chartXScale 和 chartYScale 是用于设置图表的 X 轴 和 Y 轴范围的修饰符。它们控制图表显示数据的范围,并帮助调整坐标轴的显示。

chartXScale(domain:):用于设置 X 轴的显示范围。可以定义一个 domain,例如 2020…2025,表示 X 轴的范围从 2020 到 2025。

chartYScale(domain:):用于设置 Y 轴的显示范围。可以定义一个 domain,例如 1…100,表示 Y 轴的范围从 1 到 100。

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注