自定义 Swift Charts 图表外观

了解如何通过使用 chartYAxis、AxisMarks 及其他修饰器,为 Swift Charts 图表添加更多自定义外观和功能。

自定义 Swift Charts 图表外观

Swift Charts 是 Apple 在 iOS 16 上推出的一个图表绘制框架,它提供了一套直观且强大的 API,帮助开发者快速构建各种类型的图表。

图表设计规范

有关图表及其坐标轴的设计指南,可参考 Apple 《人机界面指南》中的图表部分:

Charts | Apple Developer Documentation
Organize data in a chart to communicate information with clarity and visual appeal.

Swift Charts 基础用法

了解如何为你的应用添加条形图、散点图、折线图等基本的图表,请先阅读我之前的文章:

使用 Swift Charts 框架为 iOS 应用添加图表
了解如何通过使用 Swift 的 Charts 框架,为你的应用添加动态、交互性强的图表,包括条形图、折线图等不同类型的图表。
使用 Swift Charts 框架创建饼图或圆环图
了解如何使用 Swift Charts 框架,为应用添加美观具有交互性的饼图或圆环图。

自定义 Y 坐标轴外观(.chartYAxis)

Chart 提供了 .chartXAxis.chartYAxis修饰器,用于自定义 X 轴和 Y 轴的外观(如刻度、标签、网格线等)。chartYAxis 和 chartXAxis 的功能有很多相似之处,因此这里先通过 .chartYAxis 来演示它们的用法。

.chartXAxis.chartYAxis修饰器均作为用 Chart 组件层,而不是底层的 PointMark 或 LineMark,因此它们适用于所有类型的图表。

可结合 Apple 开发者文档的这篇文章来学习:

Customizing axes in Swift Charts | Apple Developer Documentation
Improve the clarity of your chart by configuring the appearance of its axes.

主要都是通过 AxisMarks 来实现调整。

使用预设的坐标轴配置(preset)

AxisMarks(preset:) 提供几种预设,简化轴的刻度和标签设置:

Chart {
    ForEach(salesData) { data in
        PointMark(
            x: .value("Month", data.month),
            y: .value("Sales", data.sales)
        )
        .foregroundStyle(by: .value("Sales", data.sales))  // 按销售额着色
        .symbolSize(100)  // 自定义数据点大小

    }
}
.chartYAxis {
    AxisMarks(preset: .automatic)
}
.frame(height: 300)
.padding()
  • .automatic:默认选项,自动调整刻度线和标签,适合大多数场景。
  • .aligned:轴上的刻度线和数据点对齐,适合需要数据点精确对齐的情况。
  • .extended:显示更多的刻度线和标签,增加轴的信息密度,适合需要详细展示的情况。

调整网格线和标签数(values)

Chart 会根据 Y 轴数值,自动选择默认的网格线和标签数。在下面这个代码中,Chart 默认创建了 4 条网格线:

struct PointMarkChartView: View {
    let salesData: [SalesData] = [
        SalesData(month: "Jan", sales: 200),
        SalesData(month: "Feb", sales: 220),
        SalesData(month: "Mar", sales: 190),
        SalesData(month: "Apr", sales: 260),
        SalesData(month: "May", sales: 280),
        SalesData(month: "Jun", sales: 300),
    ]

    var body: some View {
        Chart {
            ForEach(salesData) { data in
                PointMark(
                    x: .value("Month", data.month),
                    y: .value("Sales", data.sales)
                )
                .foregroundStyle(by: .value("Sales", data.sales))  // 按销售额着色
                .symbolSize(100)  // 自定义数据点大小

            }
        }
        .frame(height: 300)
        .padding()
    }
}

通过设置 AxisMarks 的 values 参数,我们可以指定网格线和标签数量:

Chart {
    ForEach(salesData) { data in
        PointMark(
            x: .value("Month", data.month),
            y: .value("Sales", data.sales)
        )
        .foregroundStyle(by: .value("Sales", data.sales))  // 按销售额着色
        .symbolSize(100)  // 自定义数据点大小

    }
}
.chartYAxis {
    AxisMarks(values: .automatic(desiredCount: 3))
}
.frame(height: 300)
.padding()

.automatic(desiredCount: 3) 指定网格数量为 3,Charts 会指定划分区间:

也可以使用数值数组在坐标轴上标出准确的数值:

Chart {
    ForEach(salesData) { data in
        PointMark(
            x: .value("Month", data.month),
            y: .value("Sales", data.sales)
        )
        .foregroundStyle(by: .value("Sales", data.sales))  // 按销售额进行着色
        .symbolSize(100)  // 自定义数据点大小

    }
}
.chartYAxis {
    AxisMarks(values: [0, 50, 100, 400])
}
.frame(height: 300)
.padding()

设置标签格式(formate)

format 参数用于指定显示标签的格式化方式。例如,下面这个示例代码为标签添加了百分比符号:

Chart {
    ForEach(salesData) { data in
        PointMark(
            x: .value("Month", data.month),
            y: .value("Sales", data.sales)
        )
        .foregroundStyle(by: .value("Sales", data.sales))  // 按销售额着色
        .symbolSize(100)  // 自定义数据点大小

    }
}
.chartYAxis {
    AxisMarks(
        format: Decimal.FormatStyle.Percent.percent.scale(1),
        values: .automatic(desiredCount: 3)
    )
}
.frame(height: 300)
.padding()

上面代码中的 Decimal.FormatStyle.Percent.percent.scale(1) 是一种百分比格式化方式,而 format 参数不仅限于百分比,这个参数接受符合 FormatStyle 协议的格式类型,根据数据的类型(如数值、日期等)选择合适的格式。

设置坐标轴方向(position)

将 Y 轴放置在图表的特定位置,例如左侧(leading)或右侧(trailing):

Chart {
    ForEach(salesData) { data in
        PointMark(
            x: .value("Month", data.month),
            y: .value("Sales", data.sales)
        )
        .foregroundStyle(by: .value("Sales", data.sales))  // 按销售额着色
        .symbolSize(100)  // 自定义数据点大小

    }
}
.chartYAxis {
    AxisMarks(
        format: Decimal.FormatStyle.Percent.percent.scale(1),
        position: .leading,
        values: .automatic(desiredCount: 3)
    )

}
.frame(height: 300)
.padding()

调整刻度线粗细、虚实(stroke)

通过 StrokeStyle 来调整轴标记线的粗细,例如下面的例子将标记线的宽度设置为 2。

chartYAxis {
    AxisMarks(stroke: StrokeStyle(lineWidth: 2))  // 更粗的标记线
}

还可以自定义线条样式,例如虚线或点线,通过 StrokeStyle 中的 dash 参数可以实现这种效果,还可以结合 lineCap 参数来定义每个点的样式。

chartYAxis {
    AxisMarks(stroke: StrokeStyle(lineWidth: 1, dash: [5, 3]))  // 虚线样式的标记线
}

dash: [5, 3] 的意思是绘制5个单位长度的线段,然后空3个单位长度。

完全自定义坐标轴外观(高级)

可以使用以下组件,完全自定义坐标轴线条、标签的样式和颜色。

  • AxisGridLine
  • AxisTick()
  • AxisValueLabel()

一旦采用这种做法,默认样式就会失效。

Chart {
    ForEach(salesData) { data in
        PointMark(
            x: .value("Month", data.month),
            y: .value("Sales", data.sales)
        )
        .foregroundStyle(by: .value("Sales", data.sales))  // 按销售额着色
        .symbolSize(100)  // 自定义数据点大小

    }
}
.chartYAxis {
    AxisMarks(
        values: .automatic(desiredCount: 3)
    ) { value in
        AxisGridLine()
            .foregroundStyle(Color.gray.opacity(0.5))
        AxisTick()
            .foregroundStyle(Color.blue)
        AxisValueLabel()
            .foregroundStyle(Color.red)
    }

}
.frame(height: 300)
.padding()

自定义 X 坐标轴外观(chartXAxis)

AxisMarks

stride 主要用于按一定步长生成坐标轴标记,可以按时间单位(如天、月、年)或数值递增,常见于时间轴或数值轴。

AxisMarks(values: .stride(by: .calendar, count: 3))

在上面的代码中:

  • by: .calendar 表示你希望按时间单位来生成标记。
  • count: 3 表示每隔 3 个时间单位生成一个标记(例如每 3 天、每 3 个月,取决于时间单位的选择)。

按年递增(每 1 年一个标记)

AxisMarks(values: .stride(by: .year, count: 1))

按月递增(每 3 个月一个标记)

AxisMarks(values: .stride(by: .month, count: 3))

按天递增(每 3 天一个标记)

AxisMarks(values: .stride(by: .day, count: 3))

隐藏 X/Y 坐标轴标签

要隐藏坐标轴标签,只需将 .chartYAxis设置为空即可:

Chart {
    ForEach(salesData) { data in
        PointMark(
            x: .value("Month", data.month),
            y: .value("Sales", data.sales)
        )
        .foregroundStyle(by: .value("Sales", data.sales))  // 按销售额着色
        .symbolSize(100)  // 自定义数据点大小

    }
}
.chartYAxis {
}
.frame(height: 300)
.padding()

自定义 chartForegroundStyleScale

  • 待补充

调整图例显示(.chartLegend)

隐藏图例显示

当我们使用.foregroundStyle(by: .value())为坐标轴设置颜色时,Swift Chart 会自动生成图例:

可以通过设置 .chartLegend(.hidden) 来隐藏图例:

Chart {
    ForEach(salesData) { data in
        PointMark(
            x: .value("Month", data.month),
            y: .value("Sales", data.sales)
        )
        .foregroundStyle(by: .value("Sales", data.sales))  // 按销售额着色
        .symbolSize(100)  // 自定义数据点大小

    }
}
.chartLegend(.hidden)
.frame(height: 300)
.padding()

调整图例位置

.chartLegend 提供了 position 参数,用于设置图例的位置:

.chartLegend(position:.topLeading)

另外,还提供了 spacingalignment 参数。