Warning
This article is extracted from the chat log with AI. Please identify it with caution.
我们将从基础的柱状图(Bar Chart)扩展到更复杂的图表类型和自定义功能。以下是学习目标:
- 学习不同类型的图表:比如折线图(Line Chart)、面积图(Area Chart)等。
- 自定义图表样式:调整颜色、标注、网格线等。
- 处理动态数据:从数组或模型中动态生成图表数据。
- 交互功能:添加用户交互,比如点击图表显示详细信息。
步骤 1:不同类型的图表
SwiftUI Charts 支持多种图表类型,我们可以用同一个数据集展示不同的可视化效果。这里我们将身高数据从柱状图改为折线图(Line Chart)。
示例代码:折线图
swift
Chart {
LineMark(
x: .value("Name", "Alice"),
y: .value("Height (cm)", 165)
)
LineMark(
x: .value("Name", "Bob"),
y: .value("Height (cm)", 180)
)
LineMark(
x: .value("Name", "Charlie"),
y: .value("Height (cm)", 172)
)
}
.chartYAxisLabel("Height (cm)") // 设置纵轴标题
.chartXAxisLabel("Names") // 设置横轴标题
.frame(height: 300)
.padding()
Chart {
LineMark(
x: .value("Name", "Alice"),
y: .value("Height (cm)", 165)
)
LineMark(
x: .value("Name", "Bob"),
y: .value("Height (cm)", 180)
)
LineMark(
x: .value("Name", "Charlie"),
y: .value("Height (cm)", 172)
)
}
.chartYAxisLabel("Height (cm)") // 设置纵轴标题
.chartXAxisLabel("Names") // 设置横轴标题
.frame(height: 300)
.padding()
代码解释
- LineMark:替代了之前的
BarMark
,将数据展示为折线图。折线图更适合展示连续变化的趋势(虽然这里是离散数据,但仍然可以展示)。 - 效果:你会看到一个折线图,连接了 Alice、Bob 和 Charlie 的身高数据点,而不是独立的柱子。
步骤 2:自定义图表样式
我们可以为图表添加颜色、符号等样式,以增强视觉效果。
示例代码:自定义折线图样式
swift
Chart {
LineMark(
x: .value("Name", "Alice"),
y: .value("Height (cm)", 165)
)
.foregroundStyle(.red) // 设置折线颜色,第一个生效
.symbol(.circle) // 在数据点上显示圆形符号
LineMark(
x: .value("Name", "Bob"),
y: .value("Height (cm)", 180)
)
.foregroundStyle(.purple) // not work
.symbol(.cross)
LineMark(
x: .value("Name", "Charlie"),
y: .value("Height1 (cm)", 172)
)
.symbol(.asterisk)
}
.chartYAxisLabel("Height (cm)")
.chartXAxisLabel("Names")
.chartYScale(domain: 150 ... 200) // 设置纵轴范围
.chartXAxis {
AxisMarks(values: .automatic) { _ in
AxisGridLine() // 显示 X 轴网格线 (竖线
AxisValueLabel() // 显示 X 轴刻度标签
}
}
.chartYAxis {
AxisMarks(values:.automatic)
{ _ in
AxisGridLine() // 显示纵轴网格线(横线
AxisValueLabel() // 显示纵轴标签
}
}
Chart {
LineMark(
x: .value("Name", "Alice"),
y: .value("Height (cm)", 165)
)
.foregroundStyle(.red) // 设置折线颜色,第一个生效
.symbol(.circle) // 在数据点上显示圆形符号
LineMark(
x: .value("Name", "Bob"),
y: .value("Height (cm)", 180)
)
.foregroundStyle(.purple) // not work
.symbol(.cross)
LineMark(
x: .value("Name", "Charlie"),
y: .value("Height1 (cm)", 172)
)
.symbol(.asterisk)
}
.chartYAxisLabel("Height (cm)")
.chartXAxisLabel("Names")
.chartYScale(domain: 150 ... 200) // 设置纵轴范围
.chartXAxis {
AxisMarks(values: .automatic) { _ in
AxisGridLine() // 显示 X 轴网格线 (竖线
AxisValueLabel() // 显示 X 轴刻度标签
}
}
.chartYAxis {
AxisMarks(values:.automatic)
{ _ in
AxisGridLine() // 显示纵轴网格线(横线
AxisValueLabel() // 显示纵轴标签
}
}
代码解释
- .foregroundStyle(.blue):设置折线颜色为蓝色。(设置第一个元素生效)
- .symbol(.circle):在每个数据点上显示一个圆形符号,增强可视化效果。
- .chartYScale(domain: 150...200):限制纵轴的范围在 150 到 200 之间,让图表更聚焦。
- .chartXAxis 和 .chartYAxis:自定义横轴和纵轴的网格线和标签,增加图表的可读性。
关于 chartYAxis & chartXAxis 概述
chartXAxis
:用于自定义图表的横轴(X轴),即通常位于图表底部的轴。它可以控制 X 轴的刻度(Ticks)、标签(Labels)、网格线(Grid Lines)等。chartYAxis
:用于自定义图表的纵轴(Y轴),即通常位于图表左侧或右侧的轴。它可以控制 Y 轴的刻度、标签、网格线等。
这两个修饰符接受一个 View
作为参数,通常使用 AxisMarks
来定义轴上的标记和样式。AxisMarks
是一个构建器(Builder),允许你指定刻度的位置、样式以及相关的标签和网格线。
基本用法
在 SwiftUI Charts 中,chartXAxis
和 chartYAxis
通常与 AxisMarks
结合使用。AxisMarks
提供了一种声明式的方式来定义轴上的刻度标记。
1). values
参数
values
用于指定刻度的位置,可以是以下几种类型:
.automatic
:由 SwiftUI 自动决定刻度的位置和数量,基于数据范围和图表大小。这是最常用的选项,适合大多数场景。.stride(by:)
:按照固定步长生成刻度,适用于数值轴(如 Y 轴)。例如,values: .stride(by: 10)
会在 Y 轴上每隔 10 个单位生成一个刻度。- 数组
[Value]
:手动指定刻度的位置。例如,values: [160, 170, 180]
会在 Y 轴的 160、170、180 处生成刻度。
2). 闭包参数
AxisMarks
的闭包参数允许你为每个刻度自定义样式。闭包接收一个 value
参数,表示当前刻度的值(类型取决于轴的数据类型,例如 Y 轴可能是 Double
,X 轴可能是 String
)。
在闭包中,你可以返回以下视图来定义刻度的样式:
AxisGridLine()
:绘制网格线。可以自定义线条样式(如颜色、宽度)。AxisValueLabel()
:显示刻度标签。可以自定义文本样式或内容。AxisTick()
:绘制刻度线(短线标记)。可以自定义长度和样式。
示例:基础的 X 轴和 Y 轴自定义
以下是一个简单的示例,展示如何为 X 轴和 Y 轴添加刻度标签和网格线 (默认情况都是显示的):
swift
.chartXAxis {
AxisMarks(values: .automatic) { _ in
AxisGridLine() // 显示 X 轴网格线 ( 竖线
AxisValueLabel() // 显示 X 轴刻度标签
}
}
.chartYAxis {
AxisMarks(values: .automatic) { _ in
AxisGridLine() // 显示 Y 轴网格线 (横线
AxisValueLabel() // 显示 Y 轴刻度标签
}
}
.chartXAxis {
AxisMarks(values: .automatic) { _ in
AxisGridLine() // 显示 X 轴网格线 ( 竖线
AxisValueLabel() // 显示 X 轴刻度标签
}
}
.chartYAxis {
AxisMarks(values: .automatic) { _ in
AxisGridLine() // 显示 Y 轴网格线 (横线
AxisValueLabel() // 显示 Y 轴刻度标签
}
}
手动指定 X 轴刻度:
这段代码只在 "Alice" 和 "Bob" 处显示刻度和标签,忽略其他类别。
swift
.chartXAxis {
// 这段代码只在 "Alice" 和 "Bob" 处显示刻度和标签,忽略其他类别。
AxisMarks(values: ["Alice", "Bob"]) { value in
AxisGridLine()
AxisValueLabel()
}
}
.chartYAxis {
AxisMarks(values:.automatic)
{ _ in
AxisGridLine() // 显示纵轴网格线(横线
AxisValueLabel() // 显示纵轴标签
}
}
.chartXAxis {
// 这段代码只在 "Alice" 和 "Bob" 处显示刻度和标签,忽略其他类别。
AxisMarks(values: ["Alice", "Bob"]) { value in
AxisGridLine()
AxisValueLabel()
}
}
.chartYAxis {
AxisMarks(values:.automatic)
{ _ in
AxisGridLine() // 显示纵轴网格线(横线
AxisValueLabel() // 显示纵轴标签
}
}
Y 轴每隔 5 个单位显示一个刻度和标签:
swift
.chartYAxis {
AxisMarks(values:
// Y 轴每隔 5 个单位显示一个刻度和标签
.stride(by: 5))
{ _ in
AxisGridLine() // 显示纵轴网格线(横线
AxisValueLabel() // 显示纵轴标签
}
}
.chartYAxis {
AxisMarks(values:
// Y 轴每隔 5 个单位显示一个刻度和标签
.stride(by: 5))
{ _ in
AxisGridLine() // 显示纵轴网格线(横线
AxisValueLabel() // 显示纵轴标签
}
}
定制化网格线的颜色
swift
.chartYAxis {
AxisMarks(values: .stride(by: 5)) { _ in
AxisGridLine()
.foregroundStyle(.red.opacity(0.3)) // 设置网格线颜色和透明度
AxisValueLabel()
.foregroundStyle(.green) // 设置标签颜色
AxisTick()
.foregroundStyle(.yellow) // 设置刻度线颜色
}
}
.chartYAxis {
AxisMarks(values: .stride(by: 5)) { _ in
AxisGridLine()
.foregroundStyle(.red.opacity(0.3)) // 设置网格线颜色和透明度
AxisValueLabel()
.foregroundStyle(.green) // 设置标签颜色
AxisTick()
.foregroundStyle(.yellow) // 设置刻度线颜色
}
}
刻度为指定值的时候进行高亮
swift
.chartYAxis {
AxisMarks(values: .stride(by: 5)) { value in
AxisGridLine()
if value.as(Double.self) == 170 {
AxisValueLabel()
.foregroundStyle(.red) // 特定值高亮
} else {
AxisValueLabel()
}
AxisTick()
}
}
.chartYAxis {
AxisMarks(values: .stride(by: 5)) { value in
AxisGridLine()
if value.as(Double.self) == 170 {
AxisValueLabel()
.foregroundStyle(.red) // 特定值高亮
} else {
AxisValueLabel()
}
AxisTick()
}
}
定制Y label单位
swift
.chartYAxis {
AxisMarks(values: .stride(by: 10)) { value in
AxisGridLine()
AxisValueLabel("\(value.as(Double.self) ?? 0) cm") // 添加单位
AxisTick()
}
}
.chartYAxis {
AxisMarks(values: .stride(by: 10)) { value in
AxisGridLine()
AxisValueLabel("\(value.as(Double.self) ?? 0) cm") // 添加单位
AxisTick()
}
}
点击交互示例
swift
import Charts
import SwiftUI
struct Person: Identifiable {
let id = UUID()
let name: String
let height: Double
}
struct AnotherChart: View {
let people = [
Person(name: "Alice", height: 165),
Person(name: "Bob", height: 180),
Person(name: "Charlie", height: 172)
]
@State private var selectedPerson: Person?
var body: some View {
VStack {
if let person = selectedPerson {
Text("Selected: \(person.name), Height: \(person.height) cm")
.font(.headline)
.padding()
} else {
Text("Tap a data point to see details")
.font(.headline)
.padding()
}
Chart(people) { person in
LineMark(
x: .value("Name", person.name),
y: .value("Height (cm)", person.height)
)
.foregroundStyle(.blue)
.symbol(.circle)
}
.chartYAxisLabel("Height (cm)")
.chartXAxisLabel("Names")
.chartYScale(domain: 150 ... 200)
.chartOverlay { proxy in
GeometryReader { _ in
Rectangle().fill(.clear).contentShape(Rectangle())
.gesture(
// 专为 Charts 等需要位置的场景设计
// 可以获取点击的图标
SpatialTapGesture()
.onEnded { value in
let location = value.location
if let (x, y) = proxy.value(at: location) as (String, Double)? {
if let person = people.first(where: { $0.name == x }) {
selectedPerson = person
}
}
}
)
}
}
.frame(height: 300)
.padding()
}
}
}
import Charts
import SwiftUI
struct Person: Identifiable {
let id = UUID()
let name: String
let height: Double
}
struct AnotherChart: View {
let people = [
Person(name: "Alice", height: 165),
Person(name: "Bob", height: 180),
Person(name: "Charlie", height: 172)
]
@State private var selectedPerson: Person?
var body: some View {
VStack {
if let person = selectedPerson {
Text("Selected: \(person.name), Height: \(person.height) cm")
.font(.headline)
.padding()
} else {
Text("Tap a data point to see details")
.font(.headline)
.padding()
}
Chart(people) { person in
LineMark(
x: .value("Name", person.name),
y: .value("Height (cm)", person.height)
)
.foregroundStyle(.blue)
.symbol(.circle)
}
.chartYAxisLabel("Height (cm)")
.chartXAxisLabel("Names")
.chartYScale(domain: 150 ... 200)
.chartOverlay { proxy in
GeometryReader { _ in
Rectangle().fill(.clear).contentShape(Rectangle())
.gesture(
// 专为 Charts 等需要位置的场景设计
// 可以获取点击的图标
SpatialTapGesture()
.onEnded { value in
let location = value.location
if let (x, y) = proxy.value(at: location) as (String, Double)? {
if let person = people.first(where: { $0.name == x }) {
selectedPerson = person
}
}
}
)
}
}
.frame(height: 300)
.padding()
}
}
}