Mastering SwiftUI for iOS 16 and Xcode 14

Chapter 38
Creating Bar Charts and Line Charts with the Charts Framework

You no longer need to build your own chart library or rely on third-party libraries to create charts. The SwiftUI framework now comes with the Charts APIs. With the brand new Charts framework in iOS 16, you can present animated charts with just a few lines of code.

Building a Simple Bar Chart

The Charts framework is very simple to use. In brief, you build SwiftUI Charts by defining what it calls Mark. Here is a quick example:

import SwiftUI
import Charts

struct ContentView: View {
    var body: some View {
        Chart {
            BarMark(
                x: .value("Day", "Monday"),
                y: .value("Steps", 6019)
            )

            BarMark(
                x: .value("Day", "Tuesday"),
                y: .value("Steps", 7200)
            )
        }
    }
}

Whether you want to create a bar chart or line chart, we start with the Chart view. In the chart view, we define the bar marks to provide the chart data. The BarMark view is used for creating a bar chart. Each BarMark view accepts the x and y value. The x value is used for defining the chart data for x-axis. In the code above, the label of the x-axis is set to Day. The y axis shows the total number of steps.

If you input the code in Xode 14, the preview automatically displays the bar chart with two vertical bars.

Figure 1. A simple bar chart
Figure 1. A simple bar chart

The code above shows you the simplest way to create a bar chart. However, instead of hardcoding the chart data, you usually use the Charts API with a collection of data. Here is an example:

struct ContentView: View {
    let weekdays = Calendar.current.shortWeekdaySymbols
    let steps = [ 10531, 6019, 7200, 8311, 7403, 6503, 9230 ]

    var body: some View {
        Chart {
            ForEach(weekdays.indices, id: \.self) { index in
                BarMark(x: .value("Day", weekdays[index]), y: .value("Steps", steps[index]))
            }
        }
    }
}

We created two arrays (weekdays and steps) for the chart data. In the Chart view, we loop through the weekdays array and present the chart data. If you've written the code in your Xcode project, the preview should render the bar chart as shown in figure 2.

Figure 2. Using an array to represent the chart data
Figure 2. Using an array to represent the chart data

By default, the Charts API renders the bars in the same color. To display a different color for each of the bars, you can attach the foregroundStyle modifier to the BarMark view:

.foregroundStyle(by: .value("Day", weekdays[index]))

To add an annotation to each bar, you use the annotation modifier like this:

.annotation {
    Text("\(steps[index])")
}

By making these changes, the bar chart becomes more visually appealing.

Figure 3. Displaying a bar chart with colors and annotations
Figure 3. Displaying a bar chart with colors and annotations

To create a horizontal bar chart, you can simply swap the values of x and y parameter of the BarMark view.

Figure 4. A horizontal bar chart
Figure 4. A horizontal bar chart

Creating a Line Chart

Now that you understand how to create a bar chart, let's see how to create a line chart using the Chart framework. As a demo, we will create a line chart that displays the average temperature of Hong Kong, Taipei, and London from 2021-Jul to 2022-Jun.

To store the weather data, I created a WeatherData struct. In your Xcode project, create a new file named WeatherData using the Swift File template. Insert the following code in the file:

struct WeatherData: Identifiable {
    let id = UUID()
    let date: Date
    let temperature: Double

    init(year: Int, month: Int, day: Int, temperature: Double) {
        self.date = Calendar.current.date(from: .init(year: year, month: month, day: day)) ?? Date()
        self.temperature = temperature
    }
}

let hkWeatherData = [ 
  WeatherData(year: 2021, month: 7, day: 1, temperature: 30.0),
  WeatherData(year: 2021, month: 8, day: 1, temperature: 29.0),
  WeatherData(year: 2021, month: 9, day: 1, temperature: 30.0),
  WeatherData(year: 2021, month: 10, day: 1, temperature: 26.0),
  WeatherData(year: 2021, month: 11, day: 1, temperature: 23.0),
  WeatherData(year: 2021, month: 12, day: 1, temperature: 19.0),
  WeatherData(year: 2022, month: 1, day: 1, temperature: 18.0),
  WeatherData(year: 2022, month: 2, day: 1, temperature: 15.0),
  WeatherData(year: 2022, month: 3, day: 1, temperature: 22.0),
  WeatherData(year: 2022, month: 4, day: 1, temperature: 24.0),
  WeatherData(year: 2022, month: 5, day: 1, temperature: 26.0),
  WeatherData(year: 2022, month: 6, day: 1, temperature: 29.0)
]

let londonWeatherData = [ 
  WeatherData(year: 2021, month: 7, day: 1, temperature: 19.0),
  WeatherData(year: 2021, month: 8, day: 1, temperature: 17.0),
  WeatherData(year: 2021, month: 9, day: 1, temperature: 17.0),
  WeatherData(year: 2021, month: 10, day: 1, temperature: 13.0),
  WeatherData(year: 2021, month: 11, day: 1, temperature: 8.0),
  WeatherData(year: 2021, month: 12, day: 1, temperature: 8.0),
  WeatherData(year: 2022, month: 1, day: 1, temperature: 5.0),
  WeatherData(year: 2022, month: 2, day: 1, temperature: 8.0),
  WeatherData(year: 2022, month: 3, day: 1, temperature: 9.0),
  WeatherData(year: 2022, month: 4, day: 1, temperature: 11.0),
  WeatherData(year: 2022, month: 5, day: 1, temperature: 15.0),
  WeatherData(year: 2022, month: 6, day: 1, temperature: 18.0)
]

let taipeiWeatherData = [ 
  WeatherData(year: 2021, month: 7, day: 1, temperature: 31.0),
  WeatherData(year: 2021, month: 8, day: 1, temperature: 30.0),
  WeatherData(year: 2021, month: 9, day: 1, temperature: 30.0),
  WeatherData(year: 2021, month: 10, day: 1, temperature: 26.0),
  WeatherData(year: 2021, month: 11, day: 1, temperature: 22.0),
  WeatherData(year: 2021, month: 12, day: 1, temperature: 19.0),
  WeatherData(year: 2022, month: 1, day: 1, temperature: 17.0),
  WeatherData(year: 2022, month: 2, day: 1, temperature: 17.0),
  WeatherData(year: 2022, month: 3, day: 1, temperature: 21.0),
  WeatherData(year: 2022, month: 4, day: 1, temperature: 23.0),
  WeatherData(year: 2022, month: 5, day: 1, temperature: 24.0),
  WeatherData(year: 2022, month: 6, day: 1, temperature: 27.0)
]

The Chart initializer takes in a list of Identifiable objects. This is why we make the WeatherData conform the Identifiable protocol. For each city, we create an array to store the sample weather data.

In the project navigator, create a new file named SimpleLineChartView using the SwiftUI View template. To create any types of chart using the Charts framework, you have to first import the Chartsframework:

import Charts

Declare a variable to store the sample weather data for the cities:

let chartData = [ (city: "Hong Kong", data: hkWeatherData),
                  (city: "London", data: londonWeatherData),
                  (city: "Taipei", data: taipeiWeatherData) ]

In the body variable, update the code like this to create the line chart:

VStack {
    Chart {
        ForEach(hkWeatherData) { item in
            LineMark(
                x: .value("Month", item.date),
                y: .value("Temp", item.temperature)
            )
        }
    }
    .frame(height: 300)
}

What the code above does is to plot a line chart for displaying the average temperature of Hong Kong. The ForEach statement loops through all items stored in hkWeatherData. For each item, we create a LineMark object that the x axis is set to the date and the y axis is set to the average temperature.

Optionally, you can resize the chart using the frame modifier. If you preview the code in Xcode preview, you should see the line chart in figure 5.

Figure 5. A simple line chart
Figure 5. A simple line chart

Customizing Chart Axes

You can customize both x and y axes by using the chartXAxis and chartYAxis modifiers respectively. Let's say, if we want to display the month labels using the digit format, we can attach the chartXAxis modifier to the Chart view like this:

.chartXAxis {
    AxisMarks(values: .stride(by: .month)) { value in
        AxisGridLine()
        AxisValueLabel(format: .dateTime.month(.defaultDigits))     
    }
}

Inside chartXAxis , we create a visual mark called AxisMarks for the values of month. For each value, we display a value label by using a specific format. This line of code tells SwiftUI chart to use the digit format:

.dateTime.month(.defaultDigits)

On top of that, we added some grid lines by using AxisGridLine.

For the y-axis, instead of display the axis on the trailing (or right) side, we want to switch it to the leading (or left) side. To do that, attach the chartYAxis modifier like this:

.chartYAxis {
    AxisMarks(position: .leading)
}

If you've made the change, Xcode preview should update the chart like figure 6. The y-axis is moved to the left side and the format of month is changed. Plus, you should see some grid lines.

Figure 6. Customizing the chart axes
Figure 6. Customizing the chart axes

Customizing the Background Color of the Plot Area

The chartPlotStyle modifier allows you to change the background color of the plot area. Attach the modifier to the Chart view like this:

.chartPlotStyle { plotArea in
    plotArea
        .background(.blue.opacity(0.1))
}

We can then change the plot area using the background modifier. As an example, we change the plot area to light blue.

Figure 7. Customizing the chart background
Figure 7. Customizing the chart background

Creating a Multi-line Chart

Now that the chart displays a single source of data (i.e. the weather data of Hong Kong), how can we display the weather data of London and Taipei in the same line chart?

You can rewrite the code of Chart view like this:

Chart {
    ForEach(chartData, id: \.city) { series in
        ForEach(series.data) { item in
            LineMark(
                x: .value("Month", item.date),
                y: .value("Temp", item.temperature)
            )
        }
        .foregroundStyle(by: .value("City", series.city))
    }
}

We have another ForEach to loop through all the cities of the chart data. Here, the foregroundStyle modifier is used to apply a different color for each line. You don't have to specify the color. SwiftUI will automatically pick the color for you.

Figure 8. Rendering a multi-line chart
Figure 8. Rendering a multi-line chart

Right now, all the cities share the same symbol. If you want to use a distinct symbol, place the following line of code after foregroundStyle:

.symbol(by: .value("City", series.city))

Now each city has its own symbol in the line chart.

Figure 9. Adding symbols for the lines
Figure 9. Adding symbols for the lines

Customizing the Interpolation Method

You can alter the interpolation method of the line chart by attaching the interpolationMethod modifier to LineMark. Here is an example:

.interpolationMethod(.stepStart)

If you change the interpolation method to .stepStart, the line chart now looks like that displayed in figure 10.

Figure 10. Customizing the interpolation method
Figure 10. Customizing the interpolation method

Other than .stepStart, you can try out the following options:

  • cardinal
  • catmullRom
  • linear
  • monotone
  • stepCenter
  • stepEnd

Summary

The Charts framework is a great addition to SwiftUI. Even if you just begin learning SwiftUI, you can create delightful charts with a few lines of code. While this chapter focuses on line charts and bar charts, the Charts API makes it very easy for developers to convert the chart to other forms such as scatter plots. You can check out the Swift Charts documentation for further reading.

For reference, you can download the complete project here: