Home Science & Technology Swift Charts Tutorial: Getting Started

Swift Charts Tutorial: Getting Started

60
0


Learn how to use Swift Charts to transform data into elegant and accessible graphs.

An attractive, well-designed chart is more useful to the user than rows and columns of data. If you need to make complex data simple and easy to understand in your app, this tutorial is for you!

Swift Charts is a flexible framework that allows you to create charts using the declarative syntax you’re already familiar with from SwiftUI. Out of the box, it supports dynamic font sizes, many screen sizes, and accessibility.

Before this framework existed, you had to create visualizations from scratch or use a third-party package.

Swift Charts gives you an elegant experience to create beautiful charts. You’ll add features to a starter app named WeatherChart. Your goal is to transform lists of historical weather data into appealing charts.

Along the way, you’ll:

  • Learn about marks and properties — the building blocks for any Swift Chart.
  • Create bar, line, area and point charts.
  • Customize those charts.
  • Improve the accessibility of the charts.

Are you ready to learn how to improve your apps with beautiful visualizations? Great! You can dive right in or use the navigation to jump ahead to a specific section.

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of this page.

Open the WeatherChart project from the starter folder. You may remember this app from SwiftUI Tutorial for iOS: Creating Charts.

Build and run.

The app shows historical weather data from four stations in and around the Great Smoky Mountains National Park:

  • Cherokee, NC and Gatlinburg, TN: The two cities on the main road through the park.
  • Newfound Gap: The gap that intersects the main road.
  • Mount LeConte: One of the highest mountains in the park.

The dataset contains each day’s precipitation, snowfall and temperature data.

Tap a location to show basic information about the location and a map of the area. Note the three tabs that show precipitation by month, daily snowfall and temperature ranges.

If you’re interested, you can review the raw data in weather-data.csv.

Getting Aquainted with Swift Charts

Take a moment to get familiar with the building blocks of any Swift chart: marks, properties, modifiers and data.

A mark is a graphical element that represents data; for example, the rectangular bars in a bar chart.

Swift charts include the following marks by default:

  • BarMark
  • PointMark
  • LineMark
  • AreaMark
  • RuleMark
  • RectangleMark

Marks are extensible, so you can create custom marks.

In this tutorial, you’ll use properties to provide data, and customize their appearance with modifiers.

Swift charts support three types of data:

  • Quantitative: represents numerical values, such as temperature, inches of snowfall, etc.
  • Nominal: values are discrete categories or groups, such as a city, name of a person, etc. This data type often becomes the labels.
  • Temporal: represents a point or interval in time, such as the duration of a particular day part.

There’s more to learn, but this is enough to get you started and into the next part, where you actually get to build something.

Developing Charts

Enough theory — it’s time to start the hands-on part of this tutorial. From here to the end, you’ll develop and change several charts.

By the time you reach the end of this tutorial, you’ll have hands-on experience creating marks and modifying their properties.

Creating a Bar Chart

Your first task is to create a bar chart for the precipitation data. A bar chart provides a bar for each data point. The length of each bar represents a numerical value, and it can be horizontally or vertically oriented.

Go to the Tabs group and open PrecipitationTab.swift.

You’ll see a standard SwiftUI List() that loops through the integers 0 through 11, representing the months of the year. It displays the total precipitation in inches for each month.

Expand the Charts group and open PrecipitationChart.swift. This is currently an empty view. Add the following variable to PrecipitationChart:


var measurements: [DayInfo]

With this, you pass the weather data to measurements from PrecipitationTab.

Replace the content of previews in PrecipitationChart_Previews with:


// swiftlint:disable force_unwrapping
PrecipitationChart(
  measurements: WeatherInformation()!.stations[2].measurements)

Here you pass weather data in for the preview.

Next, add a helper method to PrecipitationChart:


func sumPrecipitation(_ month: Int) -> Double {
  self.measurements.filter {
    Calendar.current.component(.month, from: $0.date) == month + 1
  }
  .reduce(0) { $0 + $1.precipitation }
}

This short block of code does a lot:

  • sumPrecipitation(_:) takes an Int to represent the month.
  • filter gets the measurements for that specific month then adjusts for the integer which is passed in as a zero index — this adjusts it to one.
  • reduce totals the precipitation values for those measurements.

Next, add the following below import SwiftUI:


import Charts

Here, you import the Charts framework.

Adding the Bar Chart

Replace the contents of body with:


// 1
Chart {
  // 2
  ForEach(0..<12, id: .self) { month in
    // 3
    let precipitationValue = sumPrecipitation(month)
    let monthName = DateUtils.monthAbbreviationFromInt(month)
    // 4
    BarMark(
      // 5
      x: .value("Month", monthName),
      // 6
      y: .value("Precipitation", precipitationValue)
    )
  }
}

Here’s what is going on in there:

  1. Start creating a chart by adding a Chart struct. Then declare marks and set the corresponding properties within its body.
  2. Add a ForEach loop to generate a bar chart for each month.
  3. Use two utility methods to:
    1. Get the sum of the precipitation data for the month.
    2. Get the abbreviated month name by passing the month number to monthAbbreviationFromInt(_:) from DateUtils.
  4. Create a BarMark for the chart to show the bars — marks denote the visual elements.
  5. Set the name of the month to the x argument. The first argument to .value modifier is the description of the value. The second argument is the actual value itself.
  6. Set the sum of the monthly precipitation data as the value — the height of each bar is controlled by y argument.

Turn your attention to the preview window. The bar chart should now show precipitation data for each month.

Vertical Bar Chart in Xcode Preview Canvas

Notice how Swift Charts elegantly used the abbreviated month name as a label for each bar along the x-axis. The y-axis is also set to an appropriate range based on the provided rainfall data.

Pretty cool! Pat yourself on the back and treat yourself to a candy bar for raising the bar…with a bar! :]

Tidying up the Bar Chart

There’s a better and more succinct way to write the code above! When ForEach is the only content within the chart body, you can move the data from it into the chart initializer.

Remove ForEach from Chart{} body and move the data into the chart initializer as below:


Chart(0..<12, id: .self) { month in
  let precipitationValue = sumPrecipitation(month)
  let monthName = DateUtils.monthAbbreviationFromInt(month)
  BarMark(
    x: .value("Month", monthName),
    y: .value("Precipitation", precipitationValue)
  )
}

Check the preview again. There is no change to the bar chart’s appearance, and the code is cleaner.

Vertical Bar Chart in the Xcode preview canvas

Does that chart look a bit cramped though? It could look better.

Thankfully, you can adjust that, and that’s exactly what you’ll do in the next section.

Changing to a Horizontal Bar Chart

Making a horizontal bar chart — rather than a vertical one — is as simple as swapping the axes.

Update the values of BarMark as shown below:


BarMark(
  x: .value("Precipitation", precipitationValue),
  y: .value("Month", monthName)
)

Here, you’ve swapped the values of x and y. Check the preview again.

Horizontal Bar Chart in the Xcode preview canvas

Voila! You’ll see that the chart is transposed and no longer looks cramped.

So the chart is there, but it doesn’t stand out nor does it specify the values for each bar and units for the axes. Your next task is to customize the chart so it’s easier to read and more informative.

Customizing the Bar Chart

By default, the color of the bar charts is blue, which isn’t a bad choice for a chart about water. But you’re here to learn, so keep going to learn how to change it.

Add the following to BarMark():


.foregroundStyle(.mint)

This sets the bar color to mint.

Bar Chart with Mint Style

Take a moment to look at the chart — can you tell exactly how much rain fell in a given month? There’s no indication, and that’s what you’ll fix next.

Add the following below .foregroundStyle(.mint):


.annotation {
  Text(String(format: "%.2f", precipitationValue))
    .font(.caption)
}

You annotate each BarMark with Text. The value is set to the sum of the precipitation for each month.

Bar Chart with Annotations

Refresh the preview in Canvas. Now your chart explicitly shows the values.

Using the Variants Feature in Xcode

At the bottom of Xcode’s preview Canvas is a grid icon — it’s two rows of three boxes. Click it to activate the variants feature.

You use this feature to preview your SwiftUI view in different color schemes, orientations and font sizes so you can make appropriate adjustments.

Click the grid icon and select Color Scheme Variants

Using Color Scheme Variants

Color scheme variants allow you to preview your chart in both light and dark mode.

Click the grid icon again, and select Orientation Variants to inspect your chart in portrait and landscape orientations.

Showing Orientation Variants

Again, click the grid icon and select Dynamic Type Variants.

Showing Dynamic Type Variants

Using Dynamic Type Variants, you can preview the chart with different font scales. Click on a dynamic type variant to enlarge that variant and inspect it closely.

Now you know:

  • More about the types of variants you can create.
  • Swift Charts provides support for dark mode, orientations, and dynamic type out of the box.
  • It also supports Accessibility out of the box and you can customize the content for VoiceOver.

Look closely at the chart again.

Annotation overlapping the month name

You may have noticed the text overlaps on months that had minimal precipitation. It’s particularly evident when looking at the dynamic type variants.

Fixing the Annotation

In this section, you’ll address the text overlap issue, and add a label to the axis to make the chart’s purpose clear.

There are 3 optional parameters to .annotation{}, position, alignment, and spacing:

  • Use position to place the annotation above, below, over or at the end of the item.
  • Use alignment to control the alignment relative to the annotated item.
  • Finally, use spacing to specify the distance between the item and the annotation.

Change the annotation code to:


.annotation(position: .trailing) {
  Text(String(format: "%.2f in", precipitationValue))
    .font(.caption)
}

You use position with .trailing to place the annotation after the bar. You also added “in” to indicate the unit of the measure.

Another way to show the unit is by adding a label to the x-axis of the chart with .chartXAxisLabel(_:position:alignment:spacing:). Similar to annotation, you can also provide an optional position, alignment and spacing.

Add the following below Chart{}:


.chartXAxisLabel("Inches", position: .leading)

This sets the label to “Inches” and centers it along y-axis. The default for spacing: is .center. Look at the preview to confirm the label is showing.

Precipitation chart with a labeled axis

Next, you’ll make your chart more accessible by customizing the VoiceOver content.

Supporting Accessibility

Add the following modifiers to Chart{}, below .annotation{}:


.accessibilityLabel(DateUtils.monthFromInt(month))
.accessibilityValue("Precipitation (precipitationValue)")

This sets the month name as the accessibility label, and the precipitation value for that month as the accessibility value.

Now, the bar chart is ready for its prime time!

Putting it together

Open PrecipitationTab.swift and replace the contents of body with:


VStack {
  Text("Precipitation for 2018")
  PrecipitationChart(measurements: self.station.measurements)
}

Here, you replace a boring list of precipitation data with a newly minted, shiny chart! Build and run.

Viewing Precipitation Chart

Now you’re ready to enable VoiceOver.

Note: Take a moment to create a shortcut for VoiceOver by navigating to Settings ▸ Accessibility ▸ Accessibility Shortcut and selecting VoiceOver. This gives you the option to turn VoiceOver on or off by triple-clicking the power button.

You can only test VoiceOver on a physical device. You may think you can use Xcode Accessibility Inspector with the simulator. However, the inspector does not read out the .accessibilityValue. Always test on real hardware.

Activate VoiceOver by triple-clicking the power button.

Accessibility In Precipitation Chart

You should hear VoiceOver read each bar mark as the month name and the corresponding precipitation value.

Adding a Point Chart

Point charts are useful for showing quantitative data in an uncluttered fashion.

The Great Smoky Mountains contain some of the highest elevations in the eastern United States, and they receive less snow than you might expect.

The scarcity of snow means data may not be present for each month.

To check this out for yourself, run the app and tap on Cherokee station. Select the Snowfall tab and inspect the data.

A point chart is a good candidate to visualize this data.

Find the Charts group in the Project navigator and open SnowfallChart.swift.

Add the following below import SwiftUI:


import Charts

Again, you simply import Charts framework.

Add the following variable to SnowfallChart:


var measurements: [DayInfo]

This will hold the measurements.

Still in the same file, replace the contents of previews with:


// swiftlint:disable force_unwrapping
SnowfallChart(
  measurements: WeatherInformation()!.stations[2].measurements)

Here, you pass the measurements for the preview to display.

Next, replace contents of body with:


// 1
Chart(measurements) { dayInfo in
  // 2
  PointMark(
    x: .value("Day", dayInfo.date),
    y: .value("Inches", dayInfo.snowfall)
  )
}

This code does a few things:

  1. Create a chart by adding a Chart.
  2. Create a point chart by adding a PointMark.
  • Set the date of the snowfall as the value for x.
  • Set the day’s total snowfall as the value for y.

To put this in action, open SnowfallTab.swift, and replace the contents of body with the following:


VStack {
  Text("Snowfall for 2018")
  SnowfallChart(measurements: measurementsWithSnowfall)
}
.padding()

A chart is worth a thousand data points!

Build and run.

Point chart showing snowfall with default scales

Tap a weather station and select the Snowfall tab. It only took a few lines of code to add a point chart to visualize snowfall data — nice job!

Now, compare snowfall data between the cities. You’ll notice the scale of the y-axis scales changes dynamically based on the snowfall data for the corresponding station.

It’s accurate, but when the scale changes, it becomes harder to make mental comparisons. You can set a fixed y-axis scale for all stations.

Customizing the Point Chart

Open SnowfallChart.swift again, and add the following to Chart{}:


.chartYScale(domain: 0...10)

You’ve just set y-axis scale to always start at 0 and end at 10.

Next, you’ll customize the background color of this chart.

Just below .chartYScale(domain: 0...10) add:


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

Here, you change the background of the plot area to blue with an opacity of 0.2 by using .chartPlotStyle.

Below charPlotStyle{} add:


.chartYAxisLabel("Inches")

This adds a label to the y-axis that specifies the unit of measure.

Build and run.

Showing snowfall data with Point Chart and a scaled axis

Take a moment to compare the snowfall data between different weather stations.

Notice the y-axis scale is the same for every chart and the background color is blue. It only took a few lines of code to do all that!

Next, you’ll learn how to create a line chart and combine different marks.

Adding a Line Chart

Of all the charts you’ve built so far, this one will be the fanciest.

Take a peek at the data you’re working with:

  1. Run WeatherChart then select a weather station.
  2. Tap Temperatures to view a list that shows daily high and low temperatures for a year.

List showing raw temperature data

This list isn’t user-friendly. It’s hard to say how it changed as you scroll.

Temperature readings look great in a line chart because they fluctuate over time. You can almost feel the temperature changes as your eyes trace the line.

You could show high and low temperatures separately, but that’d make it harder to compare month to month.

But if you first calculate average temperatures, you could feed just one set of data into a chart for each month and show one line.

In the next few steps, you’ll build a line chart that shows multiple months side by side with clearly marked axes to indicate each week and the temperature readings.

Calculating and Creating the Line Chart

In the Project navigator, find and expand the Charts group. Open MonthlyTemperatureChart.swift.

Similar to the previous charts you’ve built, add the following after import SwiftUI:


import Charts

Add the following variable to MonthlyTemperatureChart:


var measurements: [DayInfo]

Replace the contents of previews in MonthlyTemperatureChart_Previews with:


// swiftlint:disable force_unwrapping
MonthlyTemperatureChart(
  measurements: WeatherInformation()!.stations[2].measurements)

Add the following utility method in MonthlyTemperatureChart:


func measurementsByMonth(_ month: Int) -> [DayInfo] {
  return self.measurements.filter {
    Calendar.current.component(.month, from: $0.date) == month + 1
  }
}

You’re telling your new method measurementsByMonth(_:) to return an array of daily weather information for the specified month.

Next, add the following in MonthlyTemperatureChart:


// 1
var monthlyAvgTemperatureView: some View {
  // 2
  List(0..<12) { month in
    // 3
    VStack {
      // 4
      Chart(measurementsByMonth(month)) { dayInfo in
        // 5
        LineMark(
          x: .value("Day", dayInfo.date),
          y: .value("Temperature", dayInfo.temp(type: .avg))
        )
        // 6
        .foregroundStyle(.orange)
        // 7
        .interpolationMethod(.catmullRom)
      }

      Text(Calendar.current.monthSymbols[month])
    }
    .frame(height: 150)
  }
  .listStyle(.plain)
}

There are a lot of cool things happening in this computed variable:

  1. You define monthlyAvgTemperatureView, which will populate the monthly temperature view.
  2. You add a List to show the monthly temperature charts.
  3. Inside the list, VStack shows the temperature chart and the name of the month below it.
  4. The Chart gets weather information for the corresponding month.
  5. You use LineMark to create a line chart. For each day within the month, you add a LineMark. The x-axis indicates the day and the y-axis the day’s average temperature.
  6. You set the color of the line chart to orange using .foregroundStyle.
  7. To smooth the rendered line, you use .interpolationMethod and call a Catmull-Rom spline to interpolate the data points.

Showing the Line Chart

Now, replace the contents of body with the following:


monthlyAvgTemperatureView

You’ve just set your fancy new computed variable to be the body content.

Check your work in the preview window.

Line chart showing monthly temperature data

Now that’s clean! Your line charts elegantly show the average temperature for each month. Great job!

Customizing the Line Chart

Still in MonthlyTemperatureChart.swift, find Chart{} within the implementation of monthlyAvgTemperatureView. Add the following:


// 1
.chartForegroundStyleScale([
  TemperatureTypes.avg.rawValue: .orange
])
// 2
.chartXAxisLabel("Weeks", alignment: .center)
.chartYAxisLabel("ºF")
// 3
.chartXAxis {
  AxisMarks(values: .automatic(minimumStride: 7)) { _ in
    AxisGridLine()
    AxisTick()
    AxisValueLabel(
      format: .dateTime.week(.weekOfMonth)
    )
  }
}
// 4
.chartYAxis {
  AxisMarks( preset: .extended, position: .leading)
}

Here’s what you do here:

  1. Add a .chartForegroundStyleScale modifier to define how the average maps to the foreground style and add a legend below the line chart.
  2. Make a label for both the x- and y-axis and specify the alignment of the x-axis so it doesn’t overlap the legend.
  3. Modify the x-axis with .chartXAxis to display the week of the month instead of the default. Set the visual marks on the x-axis to show the week number:
    1. Set AxisMarks minimum stride to 7, as each week consists of 7 days.
    2. Use AxisGridLine to show a line across the plot area.
    3. Use AxisTick to draw tick marks.
    4. Set AxisValueLabel to be the week of the month as a number.
  4. Adjust the y-axis with .chartYAxis and AxisMarks to snap it to the leading edge of the chart instead of the default trailing edge.

You have more options to customize the chart. For example, you could also use different fonts or foreground styles for axes.

Finishing Up the Line Chart

Open TemperatureTab.swift. Replace the content of body with the following:


VStack {
  Text("Temperature for 2018")
  MonthlyTemperatureChart(measurements: self.station.measurements)
}

You’ve just plugged in your newly created MonthlyTemperatureChart, and passed in the weather measurements.

Build and run.

Line Chart showing monthly temperature data

Select a weather station and navigate to the Temperature tab to play with your fancy new line charts that show the average temperature for each week and month.

Now your brain can quickly read and compare differences. Congratulations. :]

But your work isn’t quite finished.

In the next section, you’ll combine different marks to create a more meaningful chart.

Combining Marks in a Line Chart

In this section, you’ll illustrate to yourself how to use both RectangleMark and AreaMark to show low, high and average temperatures, as well as adding a drill-down functionality so the user can see the details for each day.

Find and open WeeklyTemperatureChart.swift under the Charts group.

Replace the contents of the entire file with the following:


import SwiftUI
// 1
import Charts

struct WeeklyTemperatureChart: View {
  // 2
  var measurements: [DayInfo]

  // 3
  var month: Int

  // 4
  let colorForAverageTemperature: Color = .red
  let colorForLowestTemperature: Color = .blue.opacity(0.3)
  let colorForHighestTemperature: Color = .yellow.opacity(0.4)

  var body: some View {
    // 5
    weeklyTemperatureView
  }

  var weeklyTemperatureView: some View {
    // TODO: Chart will be added here
  }
}

struct WeeklyTemperatureChart_Previews: PreviewProvider {
  static var previews: some View {
    // swiftlint:disable force_unwrapping
    // 6
    WeeklyTemperatureChart(
      measurements: WeatherInformation()!.stations[2].measurements, month: 1)
  }
}

Here’s a breakdown:

  1. Import the Charts framework.
  2. Store weather data with measurements.
  3. Store the month number for which you want to view daily temperature data with month.
  4. Colors for average, lowest and highest temperatures, respectively.
  5. Create the weeklyTemperatureView computed variable to hold the contents of the chart. You’ll use it in the view body.
  6. Pass in weather data for the preview.

Add the following utility methods to WeeklyTemperatureChart:


// 1
func measurementsByMonth(_ month: Int) -> [DayInfo] {
  return self.measurements
    .filter {
      Calendar.current.component(.month, from: $0.date) == month + 1
    }
}

// 2
func measurementsBy(month: Int, week: Int) -> [DayInfo] {
  return self.measurementsByMonth(month)
    .filter {
      let day = Calendar.current.component(.day, from: $0.date)
      if week == 1 {
        return day <= 7
      } else if week == 2 {
        return (day > 7 && day <= 14)
      } else if week == 3 {
        return (day > 14 && day <= 21)
      } else if week == 4 {
        return (day > 21 && day <= 28)
      } else {
        return day > 28
      }
    }
}

Here’s what these new methods do:

  1. measurementsByMonth(_:) returns an array of the daily weather information for the specified month.
  2. measurementsBy(month:week:) returns an array of the daily weather information for the specified week of the month — you need this to show the chart for each week.

Adding Drill-Down Functionality

You need to provide an option to switch between two types of charts.

Add the following in WeeklyTemperatureChart:


enum TemperatureChartType {
  case bar
  case line
}

You added TemperatureChartType to determine the type of chart that will show temperature data.

Next, add the following below TemperatureChartType:


@State var chartType: TemperatureChartType = .bar

The chartType holds the current selection of the type of temperature chart to view.

Adding Chart Type Picker

Replace // TODO: Chart will be added here in weeklyTemperatureView with:


return VStack {
  // 1
  Picker("Chart Type", selection: $chartType.animation(.easeInOut)) {
    Text("Bar").tag(TemperatureChartType.bar)
    Text("Line").tag(TemperatureChartType.line)
  }
  .pickerStyle(.segmented)

  // 2
  List(1..<6) { week in
    VStack {
      // TODO: Add chart here
    }
    .frame(
      height: 200.0
    )
  }
  .listStyle(.plain)
}

With this, you’ve added:

  1. A Picker with the options to select a bar chart or a line Chart. The selection is stored in chartType.
  2. A List to show the weekly temperature data and within it you create a VStack as a list item for each week of that month. You’ll add a chart to it soon.

Adding Multiple Marks

Replace // TODO: Add chart here with:


// 1
Chart(measurementsBy(month: month, week: week)) { dayInfo in
  switch chartType {
    // 2
  case .bar:
    BarMark(
      x: .value("Day", dayInfo.date),
      yStart: .value("Low", dayInfo.temp(type: .low)),
      yEnd: .value("High", dayInfo.temp(type: .high)),
      width: 10
    )
    .foregroundStyle(
      Gradient(
        colors: [
          colorForHighestTemperature,
          colorForLowestTemperature
        ]
      )
    )

    // 3
  case .line:
    LineMark(
      x: .value("Day", dayInfo.date),
      y: .value("Temperature", dayInfo.temp(type: .avg))
    )
    .foregroundStyle(colorForAverageTemperature)
    .symbol(.circle)
    .interpolationMethod(.catmullRom)
  }
}
// 4
.chartXAxis {
  AxisMarks(values: .stride(by: .day))
}
.chartYAxisLabel("ºF")
.chartForegroundStyleScale([
  TemperatureTypes.avg.rawValue: colorForAverageTemperature,
  TemperatureTypes.low.rawValue: colorForLowestTemperature,
  TemperatureTypes.high.rawValue: colorForHighestTemperature
])

This code is the bulk of your chart logic, and it creates two different chart styles to show the same data!

Here’s a section-by-section explanation:

  1. You create a Chart and pass to it weather measurements for each day of the week for a given month.
  2. Next, you add a BarMark for the bar visualization and set the date as the value for the x-axis, and you also:
    1. Provide a range for the y-axis using yStart to the lowest and yEnd to the highest temperature of the day.
    2. Control the mark’s width by setting the width.
    3. Set a nice Gradient color to visualize the range of lowest to highest temperature.
  3. Show the average temperature of the day with a LineMark, similar to the monthly temperature chart. Note that you specify the type of symbol the chart should use for each point using .symbol(.circle).
  4. Customize the x-axis by:
    1. Setting AxisMark stride to a day.
    2. Adding ºF as a label for the unit of the y-axis.
    3. Adding a legend to the chart by passing an array of KeyValue pairs to .chartForegroundStyleScale. Each pair represents a measurement on the chart, and the color it should use in the legend — the chart colors are not affected by this.

Notice in BarMark that the temperature is a range from high to low. Whereas in the LineMark it’s just the average temperature.

Can you show high, low and average in one visual? Yes, you can, and you’ll do that next. :]

Visualizing Multiple Data Points

Add the following to the end of case .bar:, right above case .line:


RectangleMark(
  x: .value("Day", dayInfo.date),
  y: .value("Temperature", dayInfo.temp(type: .avg)),
  width: 5,
  height: 5
)
.foregroundStyle(colorForAverageTemperature)

You can combine multiple marks to provide better visualization of the data!

Here you create a RectangleMark to show the average temperature of the day.

The BarMark combined with RectangleMark now shows high, low and average temperature for that day.

Add the following to case .line: below .interpolationMethod(.catmullRom):


AreaMark(
  x: .value("Day", dayInfo.date),
  yStart: .value("Low", dayInfo.temp(type: .low)),
  yEnd: .value("High", dayInfo.temp(type: .high))
)
.foregroundStyle(
  Gradient(
    colors: [
      colorForHighestTemperature,
      colorForLowestTemperature
    ]
  )
)

This adds an AreaMark to show the lowest and highest temperature of the day. The LineMark, combined with AreaMark, showcases the daily high, low and average temperatures with different visualizations.

One last step: You have the charts done but still need to enable a drill-down experience so the user can navigate freely between monthly and weekly charts.

Open MonthlyTemperatureChart.swift, and replace the contents of body with below:


NavigationView {
  monthlyAvgTemperatureView
}
.navigationTitle("Monthly Temperature")

This little chunk of code embeds monthlyAvgTemperatureView in a NavigationView and sets a title for the navigation.

Finally, in monthlyAvgTemperatureView, enclose the VStack in List within a NavigationLink as shown below:


List(0..<12) { month in
  let destination = WeeklyTemperatureChart(
    measurements: measurements, month: month)
  NavigationLink(destination: destination) {
    // VStack code
  }
}

Here, you make each VStack behave as a navigation link to present the associated details.

Build and run.

Bar and Line charts showing weekly temperature data

Select a weather station and tap the Temperature tab then select a chart from the monthly temperature view.

Use the picker to switch between Bar and Line to see the combined marks in action.

Wow, this is quite an accomplishment! Now it’s elegant and easy to look at temperatures over time and understand what the weather was like.

Where to Go From Here?

Download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.

In this tutorial you’ve learned how to:

  • Create different types of charts, such as bar, line and point.
  • Create and customize marks, unit labels and their properties.
  • Customize the chart style, color, axes style and position, and the overall plot area.
  • Combine marks to better visualize the data.
  • Build multiple styles of charts from the same data and enable the user to toggle between them.
  • Enable drill-down functionality so the user can jump between summary data and detailed visualizations.

To learn more about charts, check out these WWDC videos:

I hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below.

Previous articleThis Black Friday Galaxy S22 deal is the perfect budget phone upgrade
Next articleGoogle and TikTok will soon come to the Federal Government of Islamabad