SwiftUI · · 7 min read

Building Pie Charts and Donut Charts with SwiftUI in iOS 17

Building Pie Charts and Donut Charts with SwiftUI in iOS 17

Pie charts and donut charts are two popular chart types used in data visualization. Prior to iOS 17, if you want to create these types of charts using SwiftUI, you’ll have to build the charts on your own using components like Path and Arc. Previously, we wrote a detailed tutorial on how to implement pie charts and donut charts from scratch. However, in the upcoming release of iOS 17, this is no longer necessary. SwiftUI simplifies the process of creating these charts by introducing a new mark type called SectorMark. This makes it easy for developers to build all kinds of pie and donut charts.

In this tutorial, we will guide you through the process of building pie charts and donut charts using SwiftUI. On top of that, you’ll also learn how to add interactivity to the charts.

To follow the code sample of this tutorial, please make sure you use Xcode 15 (Beta 2 or up).

Revisiting Bar Charts

Let’s start by implementing a simple bar chart using the Charts framework. Assuming you have created a new SwiftUI project, insert the lines of code below to initialize the sample data for the bar chart:

private var coffeeSales = [
    (name: "Americano", count: 120),
    (name: "Cappuccino", count: 234),
    (name: "Espresso", count: 62),
    (name: "Latte", count: 625),
    (name: "Mocha", count: 320),
    (name: "Affogato", count: 50)
]

These are just some random data on coffee sales for chart rendering. For simplicity, I used an array of tuples to hold the data. The Charts framework makes it very easy for developers to create a bar chart from these data.

First, import the Charts framework and replace the body part with the following code:

VStack {
    Chart {
        ForEach(coffeeSales, id: \.name) { coffee in
            BarMark(
                x: .value("Type", coffee.name),
                y: .value("Cup", coffee.count)
            )
            .foregroundStyle(by: .value("Type", coffee.name))
        }
    }
}
.padding()

Whether you’re creating a bar chart or a pie chart, it all starts with the Chart view. Within this view, we define a set of BarMark for rendering a vertical bar chart that plots coffee types on the x-axis and counts on the y-axis. The foregroundStyle modifier automatically assigns a unique color for each of the bars.

swiftui-bar-chart-demo

You can easily create a different type of bar chart by altering some of the BarMark parameters.

swiftui-1d-bar-chart

For example, if you want to create a one dimensional bar chart, you just need to provide the values for the x or y axis:

VStack {
    Chart {
        ForEach(coffeeSales, id: \.name) { coffee in

            BarMark(
                x: .value("Cup", coffee.count)
            )
            .foregroundStyle(by: .value("Type", coffee.name))
        }
    }
    .frame(height: 100)
}
.padding()

By default, it shows the accumulated count in the x-axis. If you want to normalized the values, simple specify the stacking parameter for BarMark like this:

BarMark(
    x: .value("Cup", coffee.count),
    stacking: .normalized
)
.foregroundStyle(by: .value("Type", coffee.name))

Creating Pie Charts with SectorMark

Now that we’ve built a bar chart, let’s how it can be converted to a pie chart using the new SectorMark introduced in iOS 17.

The SectorMark, as the name suggests, represents a sector of the pie chart that corresponds to a specific category. Each SectorMark is defined by the value it represents. By using SectorMark, developers can easily create various types of pie (or donut charts) without having to build them from scratch using components like Path and Arc.

For example, if we want to convert the bar chart into a pie chart, all you need to do is replace BarMark with SectorMark like this:

Chart {
    ForEach(coffeeSales, id: \.name) { coffee in

        SectorMark(
            angle: .value("Cup", coffee.count)
        )
        .foregroundStyle(by: .value("Type", coffee.name))
    }
}
.frame(height: 500) 

Instead of specifying the value of x-axis, you pass the values to the angle parameter. SwiftUI will automatically compute the angular size of the sector and generate the pie chart.

swiftui-simple-pie-chart

Customizing the Pie Chart

SectorMark comes with a number of parameters for you to customize each of the sectors. To add some spacing between sectors, you can provide the value of angularInset.

swiftui-pie-chart-angularinset

You can control the size of the sectors by specifying a value for the outerRadius parameter. For example, if you want to highlight the Latte sector by making it a bit larger, you can add the outerRadius parameter.

swiftui-pie-chart-outerradius

To add a label for each sector, you can attach the annotation modifier to SectorMark and set the position to .overlay:

.annotation(position: .overlay) {
    Text("\(coffee.count)")
        .font(.headline)
        .foregroundStyle(.white)
}

Here, we simply overlay a text label on each sector to display the count.

swiftui-chart-overlay-text

Converting the Pie Chart to Donut Chart

So, how can you create a donut chart? The new SectorMark is so powerful that you just need to add a single line of code to turn the pie chart into a donut chart. There is an optional parameter for SectorMark that I haven’t mentioned before.

To create a donut chart, simply specify the innerRadius parameter of the sector mark and pass it your preferred value:

SectorMark(
    angle: .value("Cup", coffee.count),
    innerRadius: .ratio(0.65),
    angularInset: 2.0
)

The value of innerRadius is either a size in points, or a .ratio or .inset relative to the outer radius. By having a value greater than zero, you create a hole in the pie and turn the chart into a donut chart.

swiftui-donut-chart-demo

Optionally, you can attach a cornerRadius modifier to the sector marks to round the corners of the sector.

swiftui-donut-chart-cornerradius

You can also add a view to the chart’s background by attaching the chartBackground modifier to the Chart view. Here is an example.

swiftui-donut-chart-background

Interacting with Charts

Other than introducing SectorMark, the new version of SwiftUI comes with new Chart APIs for handling user interactions. For both pie and donut charts, you attach the chartAngleSelection modifier and pass it a binding to capture user’s touches:

@State private var selectedCount: Int?

Chart {

    .
    .
    .

}
.chartAngleSelection(value: $selectedCount)

The chartAngleSelection modifier takes in a binding to a plottable value. Since all our plottable values are in integer, we declare a state variable of the type Int. With the implementation, the chart now can detect user’s touch and capture the selected count of the donut (or pie) chart.

swiftui-chartangleselection-pie-chart

You may attach the onChange modifier to the chart to reveal the selected value.

.onChange(of: selectedCount) { oldValue, newValue in
    if let newValue {
        print(newValue)
    }
}

The value captured doesn’t directly tell you the exact sector the user touched. Instead, it gives a value of the selected coffee count. For example, if the user taps the trailing edge of the green sector, SwiftUI returns you a value of 354.

swiftui-pie-chart-selected-values

To figure out the sector from the given value, we need to create a new function. This function takes the selected value and returns the name of the corresponding sector.

private func findSelectedSector(value: Int) -> String? {

    var accumulatedCount = 0

    let coffee = coffeeSales.first { (_, count) in
        accumulatedCount += count
        return value <= accumulatedCount
    }

    return coffee?.name
}

With the implementation above, we can declare a state variable to hold the selected sector and make some interesting changes to the donut chart.

@State private var selectedSector: String?

When a sector of the chart is selected, we will dim the remaining sectors to highlight the selected sector. Update the onChange modifier like this:

.onChange(of: selectedCount) { oldValue, newValue in
    if let newValue {
        selectedSector = findSelectedSector(value: newValue)
    } else {
        selectedSector = nil
    }
}

And then attach the opacity modifier to SectorMark like this:

SectorMark {

...

}
.opacity(selectedSector == nil ? 1.0 : (selectedSector == coffee.name ? 1.0 : 0.5))

We keep the original opacity when there is no selected sector. Once a user touches a specific sector, we change the opacity of those unselected sectors. Below shows the appearance of the donut chart when the Latte sector is selected.

swiftui-pie-chart-opacity

Summary

In this tutorial, we have guided you through the process of building pie charts and donut charts using SwiftUI. Prior to iOS 17, if you wanted to create these types of charts using SwiftUI, you had to build the charts on your own using components like Path. However, with the introduction of the new Chart API called SectorMark, it’s now easier than ever to create all kinds of pie and donut charts. As you can see, turning a bar chart into a pie (or donut) chart only requires some simple changes.

We also discussed with you how to add interactivity to the charts. This is another new feature of the SwiftUI Charts framework. With a few lines of code, you can detect users’ touches and highlight a certain part of the chart.

I hope you enjoy reading this tutorial and start building great charts with all the new functionalities provided in iOS 17.

Read next