swiftyplace site icon

Introduction to SwiftCharts: Creating Charts and Graphs in SwiftUI

Data visualization plays a crucial role in conveying information effectively and making complex data more accessible to users. In iOS development, the Swift Charts framework provides a powerful and user-friendly solution for creating stunning charts and graphs in SwiftUI. This blog post will give you an introduction to SwiftCharts and show you how to create visually appealing and informative charts in your iOS and macOS apps.

SwiftCharts Examples in SwiftUI

In this blog post, I will share my experience and insights into using Swift Charts to create various types of charts, including bar charts, line charts, point charts, area charts, ruler charts, rectangle charts, and pie charts. I will provide step-by-step examples and explanations to help you understand the fundamentals of each chart type and demonstrate how to customize them to suit your specific needs.

ÔČç´ŞĆ Get the example project files from this GitHub repo!

So, if youÔÇÖre ready to take your data visualization skills to the next level and create compelling charts in your iOS apps, letÔÇÖs dive into the world of Swift Charts!

How to create Charts in Swift?

To start using Swift Charts in your SwiftUI app, you need to import the SwiftCharts framework:

import Charts

You can then use the Chart view in SwiftUI:.

import SwiftUI
import Charts
struct ContentView: View {
  var body: some View {
      Chart {
          // Add chart content
      }
     //Add chart modifiers to customize the axis, legends and grids
      .chartXScale(domain: 25...225)
      .chartLegend(.hidden)
        .chartXAxis(.hidden)
        .chartYAxis {
            AxisMarks { _ in
                AxisValueLabel()
            }
        }
}

The content of the chart can be set by various chart marker types. Here is an example chart with bar marks:

import SwiftUI
import Charts
struct ContentView: View {
    var body: some View {
        Chart {
            BarMark(x: .value("Type", "bird"),
                    y: .value("Population", 1))
            .foregroundStyle(.pink)

            BarMark(x: .value("Type", "dog"),
                    y: .value("Population", 2))
            .foregroundStyle(.green)

            BarMark(x: .value("Type", "cat"),
                    y: .value("Population", 3))
            .foregroundStyle(.blue)
        }
        .aspectRatio(1, contentMode: .fit)
        .padding()
    }
}
swiftui chart with bar marks

For the x and y values you have to specify a PlottableValue instance. This requires a label string value describing the axis name and an actual value. The above example shows values for population of different pet types. If you work with Date type, PlottableValue makes it easy to define date units. In the following example, a bar chart with weekly bars is defined:

BarMark(
     x: .value("Week", $0.day, unit: .weekOfYear),
     y: .value("Sales", $0.sales)
)

Other markers are RuleMark, PointMark, SectionMark, and AreaMark. You will find examples of these further down in this blog post.

What data can I use with SwiftCharts?

Swift Charts allow you to work with different types of data, such as numerical values, strings, and dates. You can organize your data into arrays or collections and pass them to the appropriate chart type for rendering. Swift Charts also supports dynamic data, enabling you to update and animate your charts in real-time as the data changes.

In the following, I will walk you through how to handle your data with SwiftCharts from simple static data to more complex dynamic data.

How to create Charts with Static Data

When working with static data, you can easily create charts in SwiftUI using the Swift Charts framework. You can pass static data to the Chart Marks:

import SwiftUI
import Charts
struct ContentView: View {
    var body: some View {
            Chart {
                BarMark(x: .value("Type", "bird"),
                        y: .value("Population", 1))
                .opacity(0.5)

                BarMark(x: .value("Type", "dog"),
                        y: .value("Population", 2))
                .opacity(0.5)

                BarMark(x: .value("Type", "cat"),
                        y: .value("Population", 3))
            }
            .aspectRatio(1, contentMode: .fit)
    }
}

SwiftCharts are build for composition and you use as many different chart types together. For example you can show and hide a rule marker that highlights the average value in a bar chart. In the following example, I created a state property ┬┤averageIsShown┬┤ that the user can change with a toggle. I then use this property with an if statement to show or hide the RuleMarker:

struct ContentView: View {
    @State private var averageIsShown = false
    var body: some View {
        VStack {
            Chart {
                BarMark(x: .value("Type", "bird"),
                        y: .value("Population", 1))
                .opacity(0.5)

                BarMark(x: .value("Type", "dog"),
                        y: .value("Population", 2))
                .opacity(0.5)

                BarMark(x: .value("Type", "cat"),
                        y: .value("Population", 3))
            }
            .aspectRatio(1, contentMode: .fit)

            if averageIsShown {
                RuleMark(y: .value("Average", 1.5))
                    .foregroundStyle(.gray)
                    .annotation(position: .bottom,
                                alignment: .bottomLeading) {
                        Text("average 1.5")
                    }
            }

                .aspectRatio(1, contentMode: .fit)

            Toggle(averageIsShown ? "show average" : "hide average",
                   isOn: $averageIsShown.animation())
        }
        .padding()
    }
}
swiftui chart with bar marks and a rule mark

Working with Dynamic Data and Swift Charts

Most apps have more complex data and need to effiencently show charts. Let’s have a look at an example where I want to show the statistics for the population of different pet breeds. I start by defining a data model with 2 properties for the type of pet and the count of this pet:

struct ChartData: Identifiable, Equatable {
    let type: String
    let count: Int

    var id: String { return type }
}

I can generate a data array in my SwiftUI view and also calculate the most popular pet type ┬┤maxChartData┬┤:

struct DynamicChartView: View {

    let data = [ChartData(type: "bird", count: 1),
                ChartData(type: "dog", count: 2),
                ChartData(type: "cat", count: 3)]

    var maxChartData: ChartData? {
        data.max { $0.count < $1.count }
    }
    var body: some View {
        Chart {
            ...
        }
    }
}

Dynamic data arrays can be shown like in any other SwiftUI container view (e.g. List, Stacks, and Tables)  with an ForEach. I made the data model conform to ┬┤Identifiable┬┤ for convenience. This allows me to iterate over the array of pet statistics data and show a bar for each data point:

Chart {
    ForEach(data) { dataPoint in
        BarMark(x: .value("Type", dataPoint.type),
                y: .value("Population", dataPoint.count))
        .opacity(maxChartData == dataPoint ? 1 : 0.5)
        .foregroundStyle(maxChartData == dataPoint ? Color.accentColor : Color.gray)
    }

    RuleMark(y: .value("Average", 1.5))
        .foregroundStyle(.gray)
        .annotation(position: .bottom,
                    alignment: .bottomLeading) {
            Text("average 1.5")
        }
}

This results in the same chart as above where I used static data.

Working with Multiple Data Series┬┤ in SwiftUI

When working with data visualization, itÔÇÖs common to have multiple data series that you want to display in a chart. Swift Charts provides a convenient way to handle and present these multiple data series effectively. In this section, I will guide you through the process of working with multiple data series in Swift Charts.

As a first step, you need to organize your data into separate data series. Each data series represents a distinct set of data that you want to display in the chart.For example, if you are comparing the population for different pet types in Germany over a period of time, you would have a separate data series for each pet type:

let catData: [PetData] = [PetData(year: 2000, population: 6.8),
                          PetData(year: 2010, population: 8.2),
                          PetData(year: 2015, population: 12.9),
                          PetData(year: 2022, population: 15.2)]
let dogData: [PetData] = [PetData(year: 2000, population: 5),
                          PetData(year: 2010, population: 5.3),
                          PetData(year: 2015, population: 7.9),
                          PetData(year: 2022, population: 10.6)]

Use the ForEach view to iterate over your data series array and create a separate line for each data series:

Chart {
    ForEach(catData) { data in
        BarMark(x: .value("Year", data.year),
                y: .value("Population", data.population))
    }

    ForEach(dogData) { data in
        BarMark(x: .value("Year", data.year),
                y: .value("Population", data.population))
    }
}

If you have many data series to compare, it can be more convenient to collect all individual series into one array. For example, you can use an array of Tuple

var data: [(type: String, petData: [PetData])] {
        [(type: "cat", petData: catData),
         (type: "dog", petData: dogData)]
}

Or generate a data wrapper that you use for the collection:

struct PetDataSeries: Identifiable {
    let type: String
    let petData: [PetData]
    var id: String { type }
}

let catData = PetData.catExample
let dogData = PetData.dogExamples

var data: [PetDataSeries] {
    [PetDataSeries(type: "cat", petData: catData),
     PetDataSeries(type: "dog", petData: dogData)]
}

Both cases work well with SwiftChart and you can now create 2 ForEach: one for iterating over all series and one ForEach to iterate over the data points:

struct ContentView: View {
    let catData = PetData.catExample
    let dogData = PetData.dogExamples

    var data: [(type: String, petData: [PetData])] {
        [(type: "cat", petData: catData),
         (type: "dog", petData: dogData)]
    }

    var body: some View {
        Chart(data, id: \.type) { dataSeries in
            ForEach(dataSeries.petData) { data in
                LineMark(x: .value("Year", data.year),
                         y: .value("Population", data.population))
            }
            .foregroundStyle(by: .value("Pet type", dataSeries.type))
            .symbol(by: .value("Pet type", dataSeries.type))
        }
        .chartXScale(domain: 1998...2024)
        .aspectRatio(1, contentMode: .fit)
        .padding()
    }
}
swiftui chart with line marker and symbol

Make sure that each data series is identifiable by assigning a unique identifier to it. This will allow Swift Charts to differentiate between the different data series.

I also had to tell charts to treat this as two different series by adding the style chart modifier:

.foregroundStyle(by: .value("Pet type", dataSeries.type))

This tells charts to create 2 series of data lines which are distinguished by the data series type property. Otherwise one continues line would be shown.

Examples of Different Chart Types

Swift Charts offers several types of charts, including bar charts, line charts, point charts, area charts, ruler charts, rectangle charts, and pie charts.

Each chart type has its own unique characteristics and use cases, allowing you to choose the most suitable one for your data visualization needs.

How to create a Bar Chart in SwiftUI

Bar charts are ideal for comparing different categories or data sets. With Swift Charts, you can easily create bar charts and customize their appearance to suit your appÔÇÖs design. Simply use the ┬┤BarMark┬┤ in your chart. In the following example, I used the pet population number in the x-axis and the pet type in the y-axis. This generates a chart with vertical stacked bars:

struct BarChartExampleView: View {
    let data = [ChartData(type: "fish", count: 10),
                ChartData(type: "reptils", count: 21),
                ChartData(type: "bird", count: 18),
                ChartData(type: "dog", count: 40),
                ChartData(type: "cat", count: 65)]
    var body: some View {
        Chart(data) { dataPoint in
            BarMark(x: .value("Population", dataPoint.count),
                    y: .value("Type", dataPoint.type))
            .foregroundStyle(by: .value("Type", dataPoint.type))
            .annotation(position: .trailing) {
                Text(String(dataPoint.count))
                    .foregroundColor(.gray)
            }
        }
        .chartLegend(.hidden)
        .chartXAxis(.hidden)
        .chartYAxis {
            AxisMarks { _ in
                AxisValueLabel()
            }
        }
        .aspectRatio(1, contentMode: .fit)
        .padding()
    }
}
swiftui chart with bar marks vertically stacked for dense data

Note that I customised the bar appearance with

  • removing the legend with .chartLegend(.hidden) 
  • removed the x-axis and grid with .chartXAxis(.hidden) 
  • only showed the y-axis labels with .chartYAxis { … }

Line, Point and Area Charts in SwiftUI

Line, point and area charts are perfect for visualizing trends and changes over time. Swift Charts allows you to create dynamic line charts that can be updated with real-time data.  Simply use the ┬┤LineMark┬┤ in your chart. In the following example, shows the population of cat and dogs in Germany in the years from 2000 to 2022:

struct LineChartExampleView: View {
    let catData = PetData.catExample
    let dogData = PetData.dogExamples

    var data: [(type: String, petData: [PetData])] {
        [(type: "cat", petData: catData),
         (type: "dog", petData: dogData)]
    }

    var body: some View {
        Chart(data, id: \.type) { dataSeries in
            ForEach(dataSeries.petData) { data in
                LineMark(x: .value("Year", data.year),
                         y: .value("Population", data.population))
            }
            .foregroundStyle(by: .value("Pet type", dataSeries.type))
            .symbol(by: .value("Pet type", dataSeries.type))
        }
        .chartXScale(domain: 1998...2024)
        .aspectRatio(1, contentMode: .fit)
        .padding()
    }
}
swiftui chart with line marker and symbol

I am also showing symbols markers for each data point by using the chart modifier:

.symbol(by: .value("Pet type", dataSeries.type))

Which creates different symbol markers depending on the datas eries type property.

Similar to line marks, you can also show scatter plots with ┬┤PointMark┬┤ and area plots with ┬┤AreaMark┬┤:

SwiftCharts examples for point, line and area charts.
Examples of charts with the same data but different marker types: left – PointMark, center – LineMarker, and right – AreaMarker

How to make a line chart with a gradient background

You can use AreaMark to add an additional gradient background. First, define a gradient. The following example defines a linear gradient with the accent color and gets more opaque:

let linearGradient = LinearGradient(gradient: Gradient(colors: [Color.accentColor.opacity(0.4), Color.accentColor.opacity(0)]),
                                    startPoint: .top,
                                    endPoint: .bottom)

You can then use this gradient for the foreground style of the chart like so:

ForEach(catData) { data in
          AreaMark(x: .value("Year", data.year),
                   y: .value("Population", data.population))
 }
.foregroundStyle(linearGradient)

Combining a line and area chart gives you a great-looking chart with a gradient highlight:

struct GradientAreaChartExampleView: View {
    let catData = PetData.catExample
    let linearGradient = LinearGradient(gradient: Gradient(colors: [Color.accentColor.opacity(0.4),
                                                                    Color.accentColor.opacity(0)]),
                                        startPoint: .top,
                                        endPoint: .bottom)
    var body: some View {
        Chart {
            ForEach(catData) { data in
                LineMark(x: .value("Year", data.year),
                         y: .value("Population", data.population))
            }
            .interpolationMethod(.cardinal)
            .symbol(by: .value("Pet type", "cat"))

            ForEach(catData) { data in
                AreaMark(x: .value("Year", data.year),
                         y: .value("Population", data.population))
            }
            .interpolationMethod(.cardinal)
            .foregroundStyle(linearGradient)
        }
        .chartXScale(domain: 1998...2024)
        .chartLegend(.hidden)
        .chartXAxis {
            AxisMarks(values: [2000, 2010, 2015, 2022]) { value in
                AxisGridLine()
                AxisTick()
                if let year = value.as(Int.self) {
                    AxisValueLabel(formatte(number: year),
                                   centered: false,
                                   anchor: .top)
                }
            }
        }
        .aspectRatio(1, contentMode: .fit)
        .padding()
    }
}
SwiftCharts example with line and area mark to show gradient highlight

How to add RuleMark to Charts in SwiftUI

If you want to create a horizontal indicator, you can use ┬┤RuleMark┬┤. This is useful to show a threshold or average value in your chart. For example, I could show the average population:

struct ContentView: View {
    let data = [ChartData(type: "bird", count: 1),
                ChartData(type: "dog", count: 2),
                ChartData(type: "cat", count: 3)]

    var maxChartData: ChartData? {
        data.max { $0.count < $1.count }
    }

    var body: some View {
        Chart {
            ForEach(data) { dataPoint in

                BarMark(x: .value("Type", dataPoint.type),
                        y: .value("Population", dataPoint.count))
                .foregroundStyle(Color.gray.opacity(0.5))
            }

            RuleMark(y: .value("Average", 1.5))
                .annotation(position: .bottom,
                            alignment: .bottomLeading) {
                    Text("average 1.5")
                        .foregroundColor(.accentColor)
                }
        }
        .aspectRatio(1, contentMode: .fit)
        .padding()
    }
}

Creating Charts with RectangleMark

┬┤RectangleMark┬┤ can be used to mark certain areas in you charts. In the following example, I am generating a heat map with ┬┤RectangleMark┬┤:

struct HeatMapExampleView: View {
    let heatData: [HeatData] = HeatData.exampleData

    var body: some View {
        Chart(heatData) {
            RectangleMark(xStart: .value("start location x", $0.locationX - 25),
                          xEnd: .value("end location x", $0.locationX + 25),
                          yStart: .value("start location y", $0.locationY - 25),
                          yEnd: .value("end location y", $0.locationY + 25))
            .foregroundStyle(by: .value("Number", $0.temperatur))
        }
        .chartXScale(domain: 25...225)
        .chartYScale(domain: 25...225)
        .aspectRatio(1, contentMode: .fit)
        .padding()
    }
}
SwiftCharts example showing a heat map with rectangle markers
struct HeatData: Identifiable {
    let locationX: Int
    let locationY: Int
    let temperatur: Double
    let id = UUID()

    static var exampleData: [HeatData] {
        [HeatData(locationX: 50, locationY: 50, temperatur: 23.0),
         HeatData(locationX: 50, locationY: 100, temperatur: 15.0),
         HeatData(locationX: 50, locationY: 150, temperatur: 25.0),
         HeatData(locationX: 50, locationY: 200, temperatur: 22.5),

         HeatData(locationX: 100, locationY: 50, temperatur: 20.0),
         HeatData(locationX: 100, locationY: 100, temperatur: 26.5),
         HeatData(locationX: 100, locationY: 150, temperatur: 29.4),
         HeatData(locationX: 100, locationY: 200, temperatur: 17.0),

         HeatData(locationX: 150, locationY: 50, temperatur: 24.0),
         HeatData(locationX: 150, locationY: 100, temperatur: 23.5),
         HeatData(locationX: 150, locationY: 150, temperatur: 21.5),
         HeatData(locationX: 150, locationY: 200, temperatur: 15.0),

         HeatData(locationX: 200, locationY: 50, temperatur: 10.0),
         HeatData(locationX: 200, locationY: 100, temperatur: 26.5),
         HeatData(locationX: 200, locationY: 150, temperatur: 27.0),
         HeatData(locationX: 200, locationY: 200, temperatur: 17.0)
        ]
    }
}

Donut and Pie Charts for iOS 17 and macOS 14

Donut and Pie charts are effective for displaying proportions and percentages. New with iOS 17 and macOS 14, charts now can also generate pie and donut charts. In the following donut chart, I am comparing the cat and dog population in Germany over the last 20 years:  

struct PieChartExampleView: View {
    let catData = PetData.catExample
    let dogData = PetData.dogExamples
    var catTotal: Double {
        catData.reduce(0) { $0 + $1.population }
    }

    var dogTotal: Double {
        dogData.reduce(0) { $0 + $1.population }
    }

    var data: [(type: String, amount: Double)] {
        [(type: "cat", amount: catTotal),
         (type: "dog", amount: dogTotal)
        ]
    }

    var maxPet: String? {
        data.max { $0.amount < $1.amount }?.type
    }

    var body: some View {
        Chart(data, id: \.type) { dataItem in
            SectorMark(angle: .value("Type", dataItem.amount),
                       innerRadius: .ratio(0.5),
                       angularInset: 1.5)
                .cornerRadius(5)
                .opacity(dataItem.type == maxPet ? 1 : 0.5)
        }
        .frame(height: 200)
    }
}
SwiftCharts example with pie and donut chart

To generate pie charts use the ┬┤SectorMark┬┤ like so:

SectorMark(angle: .value("Type", dataItem.amount))

To change to a pie chart, use the innerRadius property. For example, set the donut width to 50% with the ratio:

SectorMark(angle: .value("Type", dataItem.amount),
                       innerRadius: .ratio(0.5))

If you want to use different colors for the sections use:

.foregroundStyle(by: .value("Type", dataItem.type))

Conclusion

In conclusion, mastering Swift Charts is a valuable skill for any iOS developer looking to create visually appealing and informative data visualizations in their apps. With the Swift Charts framework, you have access to a wide range of chart types and customization options, allowing you to present data in a way that is both visually engaging and easily understandable for users. By following the step-by-step guides and exploring the examples provided, you can confidently implement bar charts, line charts, pie charts, and more, while also incorporating advanced techniques such as animation and accessibility. So, dive into the world of Swift Charts and unlock the potential to create stunning data visualizations that enhance the user experience in your iOS and macOS apps.

Share:

Subscribe To My Newsletter

Table of Contents

Leave a Reply

Your email address will not be published. Required fields are marked *

More Posts