Last active
September 21, 2025 13:40
-
-
Save arunavo4/731c00ef19c5da0910c81f8da031571d to your computer and use it in GitHub Desktop.
Guide to Swift Charts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| *** | |
| # The Complete Guide to Swift Charts | |
| Swift Charts is a powerful and declarative framework for creating a wide variety of charts to visualize data and mathematical functions in your apps. This guide combines foundational knowledge from its introduction with advanced features like pie charts, interactivity, function plots, and high-performance vectorized plots. | |
| ## Table of Contents | |
| 1. [Introduction to Swift Charts](#1-introduction-to-swift-charts) | |
| 2. [Core Concepts: Marks and Composition](#2-core-concepts-marks-and-composition) | |
| 3. [Data Types in Swift Charts](#3-data-types-in-swift-charts) | |
| 4. [Plotting Data with Mark Properties](#4-plotting-data-with-mark-properties) | |
| 5. [Building Common Charts](#5-building-common-charts) | |
| * [Bar Charts](#bar-charts) | |
| * [Line Charts](#line-charts) | |
| * [Area Charts](#area-charts) | |
| * [Combining Marks](#combining-marks) | |
| 6. [New in iOS 18: Function and Vectorized Plots](#6-new-in-ios-18-function-and-vectorized-plots) | |
| * [Function Plots](#function-plots) | |
| * [Vectorized Plots for Performance](#vectorized-plots-for-performance) | |
| * [When to Use Mark-based vs. Vectorized Plots](#when-to-use-mark-based-vs-vectorized-plots) | |
| 7. [Pie and Donut Charts](#7-pie-and-donut-charts) | |
| * [Creating a Pie Chart with `SectorMark`](#creating-a-pie-chart-with-sectormark) | |
| * [Customizing Pie Charts](#customizing-pie-charts) | |
| * [Creating a Donut Chart](#creating-a-donut-chart) | |
| 8. [Chart Interactivity](#8-chart-interactivity) | |
| * [Selection](#selection) | |
| * [Scrolling](#scrolling) | |
| 9. [Customizing Chart Components](#9-customizing-chart-components) | |
| * [Customizing Axes](#customizing-axes) | |
| * [Customizing Legends](#customizing-legends) | |
| * [Customizing the Plot Area](#customizing-the-plot-area) | |
| 10. [Designing Effective Charts](#10-designing-effective-charts) | |
| --- | |
| ## 1. Introduction to Swift Charts | |
| Data visualization makes your app more informative and engaging. Swift Charts achieves this by offering: | |
| * **Declarative Syntax**: You specify *what* you want in your chart with a small amount of code, and Swift Charts handles the rendering. It feels just like SwiftUI. | |
| * **Rich Customization**: While the framework provides excellent defaults, it also offers a rich set of options to style your chart to match your app's unique design. | |
| * **Accessibility First**: Charts are automatically accessible to VoiceOver users and support Audio Graphs, with options for further customization to ensure they are usable by everyone. | |
| A simple chart can be created with just a few lines of code: | |
| ```swift | |
| import SwiftUI | |
| import Charts | |
| // 1. Define your data structure | |
| struct PancakeSales: Identifiable { | |
| let name: String | |
| let sales: Int | |
| var id: String { name } | |
| } | |
| // 2. Create your data array | |
| let salesData: [PancakeSales] = [ | |
| .init(name: "Cachapa", sales: 916), | |
| .init(name: "Injera", sales: 850), | |
| // ... more data | |
| ] | |
| // 3. Create the Chart View | |
| struct SimpleBarChart: View { | |
| var body: some View { | |
| Chart(salesData) { pancake in | |
| BarMark( | |
| x: .value("Pancake Style", pancake.name), | |
| y: .value("Sales", pancake.sales) | |
| ) | |
| } | |
| } | |
| } | |
| ``` | |
| --- | |
| ## 2. Core Concepts: Marks and Composition | |
| The fundamental building block of any chart is a **Mark**. A mark is a graphical element that represents a single piece of data (e.g., a bar, a point, a line segment). You create charts by composing one or more marks inside a `Chart` view. | |
| ```swift | |
| Chart { | |
| // One or more marks go here | |
| BarMark(...) | |
| LineMark(...) | |
| } | |
| ``` | |
| Swift Charts provides several mark types: `BarMark`, `LineMark`, `PointMark`, `AreaMark`, `RuleMark`, `RectangleMark`, and `SectorMark`. | |
| --- | |
| ## 3. Data Types in Swift Charts | |
| Swift Charts understands three main types of data: | |
| 1. **Quantitative**: Numerical values like `Int` or `Double`. | |
| 2. **Nominal**: Categorical data, often a `String` or an `enum`. | |
| 3. **Temporal**: Time-based values represented by `Date`. | |
| --- | |
| ## 4. Plotting Data with Mark Properties | |
| You map your data to the visual properties of a mark using `.value(label, value)`. Common properties include: | |
| * **`x` and `y`**: The position of the mark. | |
| * **`foregroundStyle`**: The color or style of the mark. | |
| * **`symbol`**: The shape of a point mark. | |
| By mapping different data fields to these properties, you can encode multiple dimensions of information into your chart. | |
| ```swift | |
| .foregroundStyle(by: .value("City", series.city)) // Colors each line by city and adds a legend | |
| ``` | |
| --- | |
| ## 5. Building Common Charts | |
| ### Bar Charts | |
| Bar charts are great for comparing quantities across different categories. | |
| ```swift | |
| Chart(salesData) { | |
| BarMark( | |
| x: .value("Pancake", $0.name), | |
| y: .value("Sales", $0.sales) | |
| ) | |
| } | |
| ``` | |
| ### Line Charts | |
| Line charts are excellent for showing trends over a continuous domain, like time. | |
| ```swift | |
| Chart(dailySales) { | |
| LineMark( | |
| x: .value("Day", $0.day, unit: .day), | |
| y: .value("Sales", $0.sales) | |
| ) | |
| } | |
| ``` | |
| ### Area Charts | |
| Area charts emphasize the volume or magnitude of change over time by filling the area below the line. | |
| ```swift | |
| Chart(monthlySales) { | |
| AreaMark( | |
| x: .value("Month", $0.month, unit: .month), | |
| yStart: .value("Min Sales", $0.minSales), | |
| yEnd: .value("Max Sales", $0.maxSales) | |
| ) | |
| } | |
| ``` | |
| ### Combining Marks | |
| Layer marks to create richer visualizations, such as adding points on top of a line. | |
| ```swift | |
| Chart(dailySales) { sale in | |
| LineMark(...) | |
| PointMark(...) | |
| } | |
| ``` | |
| --- | |
| ## 6. New in iOS 18: Function and Vectorized Plots | |
| Swift Charts now extends beyond plotting discrete data points to visualizing mathematical functions and rendering large datasets with high performance. | |
| ### Function Plots | |
| You can now plot functions directly without pre-calculating data points. This is ideal for scientific, educational, and analytical apps. | |
| **`LinePlot` and `AreaPlot`** | |
| These new views take a closure that defines the function to be plotted. | |
| **Example: Plotting a Normal Distribution Over a Histogram** | |
| ```swift | |
| // A standard math function for a normal distribution curve | |
| func normalDistribution(x: Double, mean: Double, standardDeviation: Double) -> Double { ... } | |
| Chart { | |
| // 1. The histogram of the actual data | |
| ForEach(bins) { bin in | |
| BarMark( | |
| x: .value("Capacity", bin.range), | |
| y: .value("Probability", bin.probability) | |
| ) | |
| } | |
| // 2. The function plot layered on top | |
| LinePlot(x: "Capacity density", y: "Probability") { x in | |
| // The closure calculates the y-value for any given x-value | |
| normalDistribution(x: x, mean: mean, standardDeviation: sd) | |
| } | |
| .foregroundStyle(.gray) | |
| } | |
| ``` | |
| **Handling Discontinuous or Undefined Functions** | |
| If a function is undefined in parts of its domain (e.g., `y = 1/x` at `x=0`), return `Double.nan` from the closure. Swift Charts will automatically create a gap in the plot. | |
| ```swift | |
| LinePlot { x in | |
| guard x != 0 else { return .nan } | |
| return 1 / x | |
| } | |
| ``` | |
| **Parametric Functions** | |
| For functions where `x` and `y` are both dependent on a third parameter (e.g., `t`), the `LinePlot` closure can return a tuple `(x, y)`. | |
| ```swift | |
| // Plot a heart shape using a parametric function | |
| LinePlot(t: "t", domain: -Double.pi...Double.pi) { t in | |
| let x = sqrt(2) * pow(sin(t), 3) | |
| let y = cos(t) * (2 - cos(t) - pow(cos(t), 2)) | |
| return (x, y) | |
| } | |
| ``` | |
| ### Vectorized Plots for Performance | |
| For very large datasets (thousands or millions of points), iterating with `ForEach` can be inefficient. **Vectorized plots** process entire collections of data at once for significantly better performance. | |
| This is achieved by passing the data collection directly to the plot initializer and using **key paths** to map properties. | |
| **Example: Plotting a Scatter Plot of Thousands of Solar Panels** | |
| ```swift | |
| // Data structure with stored properties for performance | |
| struct DataPoint: Identifiable { | |
| let id: Int | |
| let capacity: Double | |
| let panelAxisType: String | |
| var x: Double // Projected longitude | |
| var y: Double // Projected latitude | |
| } | |
| struct SolarMap: View { | |
| @ObservedObject var model: Model | |
| var body: some View { | |
| Chart { | |
| // No ForEach loop! The plot handles the entire collection. | |
| PointPlot( | |
| model.data, | |
| x: .value("Longitude", \.x), // Key path for x | |
| y: .value("Latitude", \.y) // Key path for y | |
| ) | |
| .symbolSize(by: .value("Capacity", \.capacity)) | |
| .foregroundStyle(by: .value("Axis Type", \.panelAxisType)) | |
| } | |
| } | |
| } | |
| ``` | |
| **Performance Tips for Vectorized Plots:** | |
| 1. **Group Data by Style**: If your data is sorted by the property used for styling (e.g., `panelAxisType`), Swift Charts can render it more efficiently. | |
| 2. **Avoid Computed Properties**: Use stored properties for plotted values (`x`, `y`, `capacity`) instead of computed properties to prevent repeated calculations during rendering. | |
| 3. **Specify Scale Domains**: If you know the range of your data, specify it with `.chartXScale(domain:)` and `.chartYScale(domain:)`. This prevents Swift Charts from iterating over the entire dataset just to determine the bounds. | |
| ### When to Use Mark-based vs. Vectorized Plots | |
| | Plot Type | Best For | Key Advantage | | |
| | ---------------- | ---------------------------------------------------------------------- | ------------------ | | |
| | **Vectorized Plot** | Large datasets (> hundreds of points) where all points are styled homogeneously. | **High Performance** | | |
| | **Mark-based Plot** | Smaller datasets or when individual points require unique styling, conditional logic, or complex layering. | **High Flexibility** | | |
| --- | |
| ## 7. Pie and Donut Charts | |
| Pie charts are excellent for visualizing part-to-whole relationships. | |
| ### Creating a Pie Chart with `SectorMark` | |
| Use `SectorMark` and map your quantitative value to the **`angle`** property. | |
| ```swift | |
| struct PieChartExample: View { | |
| var body: some View { | |
| Chart(salesData) { element in | |
| SectorMark( | |
| angle: .value("Sales", element.sales) | |
| ) | |
| .foregroundStyle(by: .value("Name", element.name)) | |
| } | |
| } | |
| } | |
| ``` | |
| ### Customizing Pie Charts | |
| Use modifiers like `.cornerRadius(_:)` and `.angularInset(_:)` to add visual flair. | |
| ### Creating a Donut Chart | |
| Create a donut chart by setting the **`innerRadius`**. | |
| ```swift | |
| SectorMark( | |
| angle: .value("Sales", element.sales), | |
| innerRadius: .ratio(0.618) | |
| ) | |
| ``` | |
| --- | |
| ## 8. Chart Interactivity | |
| Swift Charts includes powerful APIs for selection and scrolling. | |
| ### Selection | |
| Use modifiers like `.chartXSelection(value:)` or `.chartAngleSelection(value:)` to bind user interactions to a `@State` variable. Use the `chartOverlay` or `chartBackground` modifiers with a `ChartProxy` to display contextual information based on the selection. | |
| ```swift | |
| @State private var selectedSales: PancakeSales? | |
| Chart(...) | |
| .chartAngleSelection(value: $selectedSales) | |
| .chartBackground { proxy in | |
| if let selectedSales { | |
| Text("\(selectedSales.sales) sold") | |
| .position(x: proxy.plotAreaCenter.x, y: proxy.plotAreaCenter.y) | |
| } | |
| } | |
| ``` | |
| ### Scrolling | |
| For large datasets, enable scrolling with these modifiers: | |
| 1. `.chartScrollableAxes(_:)`: Enable scrolling on an axis. | |
| 2. `.chartXVisibleDomain(length:)`: Set the initial visible data range. | |
| 3. `.chartScrollPosition(x:)`: Bind the scroll position to a `@State` variable. | |
| 4. `.chartScrollTargetBehavior(_:)`: Customize snapping and pagination behavior. | |
| --- | |
| ## 9. Customizing Chart Components | |
| ### Customizing Axes | |
| Use the `.chartXAxis` and `.chartYAxis` modifiers to gain full control over grid lines, ticks, and labels. | |
| ```swift | |
| .chartXAxis { | |
| AxisMarks(values: .stride(by: .month)) { value in | |
| AxisGridLine() | |
| AxisTick() | |
| AxisValueLabel(format: .dateTime.month(.narrow)) | |
| } | |
| } | |
| ``` | |
| ### Customizing Legends | |
| Control the legend's visibility and position with `.chartLegend()`. | |
| ### Customizing the Plot Area | |
| The **plot area** is the region inside the axes. Style it using `.chartPlotStyle`. | |
| ```swift | |
| .chartPlotStyle { plotArea in | |
| plotArea.background(.blue.opacity(0.1)) | |
| } | |
| ``` | |
| --- | |
| ## 10. Designing Effective Charts | |
| * **Describe Your Charts**: Always provide a title and summary. | |
| * **Progressively Reveal Complexity**: Start simple and allow users to drill down into more interactive and detailed views. | |
| * **Use a Consistent Design System**: Use visual differences (color, shape) to signal differences in data, and maintain consistency across your app. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment