Streaming is available in most browsers,
and in the Developer app.
-
Swift Charts: Vectorized and function plots
The plot thickens! Learn how to render beautiful charts representing math functions and extensive datasets using function and vectorized plots in your app. Whether you're looking to display functions common in aerodynamics, magnetism, and higher order field theory, or create large interactive heat maps, Swift Charts has you covered.
Chapters
- 0:00 - Introduction
- 1:01 - Function plots
- 6:48 - Vectorized plots
- 11:27 - Best practices
Resources
Related Videos
WWDC23
WWDC22
-
Download
Hi, I’m Apollo. Let me tell you about what's new in Swift Charts. Swift Charts enables the creation of informative, accessible, and delightful visualizations using SwiftUI. This release brings to you new charts to present weather trends, track your mood and vitals, and graph functions in Math Notes.
That's right! Swift Charts now allows you to visualize things beyond data, by plotting mathematical functions in your apps.
Swift Charts now also has vectorized plotting APIs that support visualizing larger data sets even more efficiently. I've plotted this video to teach you more about each of these exciting new capabilities of Swift Charts. Starting with function plots, which introduce two new APIs: LinePlot, for visualizing a single function, And AreaPlot, to fill in the area between two functions. Let me take you through how function plots can help me with data analysis.
I’ve been researching Large-Scale Solar energy projects in the contiguous United States, using a dataset from US Geological Survey.
I built a histogram to visualize the solar panels’ capacity densities with a ForEach over the data points, and a BarMark for each element. You know what? This histogram suggests that capacity density may be normally distributed. I’ll compare by plotting a normal distribution using the new function plot API.
I've defined a function that calculates the points on a normal distribution curve and I can plot it using the new LinePlot API, which accepts a closure that takes a double and returns a double, so I can call my function with a precomputed mean and standard deviation. It's fundamental to Swift Charts that data visualizations are accessible to everyone. Swift Charts makes your chart accessible by default. I can use Voice Over to describe the chart. “The x axis is capacity density, The y axis is probability. There are two data series." Audio Graph works on a function plot, too.
"Complete." Great! And just like in SwiftUI, you can use modifiers to customize how your functions look.
Here, because the LinePlot has the same default color as the bars, it's a good idea to customize the color of my function plot with a different foregroundStyle.
Much better now. But, to make it stand out just a little more, I would like to fill in the area below the curve. To do that, I can simply change the LinePlot to an AreaPlot. Then, to increase the contrast, I can further customize the AreaPlot with an opacity to make it more legible.
So that's how to plot a simple math function. But Swift Charts makes it just as easy to build more advanced function plots.
For example, not only can AreaPlot visualize the area under a curve, you can use it to visualize the area between two functions as well, by returning a tuple of yStart and yEnd for a given input x.
Unlike visualizing data, a function can accept an unbounded range of x values. By default, Swift Charts automatically infers the domain by sampling the function. But I can customize the overall bounds of the chart by setting the X scale and the Y scale to include only parts of the function that I find interesting.
I also have the ability to limit the domain of the function plot itself.
By restricting AreaPlot’s sampling domain, the chart now includes only the middle part of this function.
Swift Charts also supports plotting parametric functions.
Here's a parametric function, where x and y are defined in terms of a 3rd variable, T. Let's plot it.
You can graph parametric functions in Swift Charts by using the same LinePlot API, but return both x and y values given the value t. I love it! Next, let's talk about how to handle piece-wise functions.
Sometimes, a piece-wise function doesn't have an output for certain values in it's domain.
In those cases, you can return .nan to inform Swift Charts that there's not a number for that input value x.
In other cases, your code might trap for certain x values, such as when evaluating 1 over x when x is equal to 0.
Similarly, you should handle the special values by returning .nan.
That's function plotting with Line Plot and Area Plot, where they treat an entire math function as a single entity. But the plot APIs are useful for more than just functions. They can make it more convenient and more efficient to visualize larger datasets.
So we've added plot API variants for all the other mark types, too.
These vectorized plot APIs can handle an entire collection in parallel to draw extensive data visualizations, such as a scatter plot for a classification model, or a heatmap visualizing self attention in a transformer language model. But before I let the plot thicken, let's review how you would declare a Chart using the Mark API.
Marks are super flexible, allowing you to style each individual data point differently; from choosing which modifiers to apply, to even what kind of mark to use. However, most often you don't need this level of customization. It's common for an entire collection of data points to be styled homogeneously, using the same element properties for X, Y, foregroundStyle, and other visual attributes.
In contrast, the new Vectorized Plot API, such as RectanglePlot, allows Swift Charts to process larger collections of data more efficiently. For an example of vectorized plots, let’s go back to our solar panel dataset. I want to add a visualization of ALL the solar panel installations to my app. For all the points in this chart, I'd like to customize them in the same way. The size will be determined by the capacity, and they'll be colored differently based on the panel's axis type.
The dataset has raw GPS coordinates in longitude and latitudes, but I want to apply Albers projection to display the points on a flat surface. I could add computed properties in an extension to do this conversion on the fly, but to get the most out of vectorized plots, I'll add them as stored properties instead. Stored properties allows Swift Charts to access the x and y values for all data points with a constant memory offset instead of calling the getter for every data point.
The new PointPlot API takes an entire collection of data to plot.
For the x and y values of all the points in the plot, I can use the same .value syntax with a label, And a KeyPath to the stored properties x and y of the DataPoint structure. If you've used SwiftUI before, you might have already used KeyPaths. Using KeyPaths lets Swift Charts style all the points without iterating over the dataset.
Modifiers for vectorized plots take keyPaths as well. With symbolSize, I make the size of each point represent its solar panel capacity. And similarly, I use a key path to the solar panel's axis type to customize the color of each point. All other modifiers that are often used for homogenous modification support a key path parameter, too. My app looks stunning in Apple Vision Pro with spatial computing. The vectorized plot on my left animates smoothly, and all the charts on my dashboard update simultaneously when I scrub through the bar chart.
With pinch and drag, I can get a closer look at the vectorized plot we just added to learn more about each solar panel installation and glance over the normal distribution plot that we created earlier.
That's vectorized plots. Now you might be wondering how Vectorized plots and Marks complement each other. Use Vectorized Plots for larger datasets where the entire plot is customized with the same modifiers and properties.
Use the Mark API when you have fewer data points, but need to customize each element with individual mark types and modifiers, or if you need complex layering with zIndex.
When using vectorized plots, you can help Swift Charts reduce the number of style alternation by grouping the collection of data by the style you'll use for them. Avoiding other computations during rendering can help too, such as by converting computed properties to stored properties.
If you already know the few distinct styles you'll use, or the over all bounds of the chart, specifying them will render your Charts more efficiently. And lastly, it's common for some style customizations to be unnoticeable with larger amount of datapoints, so you can skip those entirely to make your chart even more performant.
That's the new vectorized and function plots in Swift Charts. Try out these new features to take your visualizations to the next level. And download the Sample Project to check out more examples of function plots, including how you can add interactions to them. If you are new to Swift Charts, check out previous talks to get started. Thank you, and I look forward to what you'll plot next.
-
-
1:43 - Histogram that shows distribution of capacity density
Chart { ForEach(bins) { bin in BarMark( x: .value("Capacity density", bin.range), y: .value("Probability", bin.probability) ) } }
-
2:18 - Visualize function with LinePlot
Chart { LinePlot( x: "Capacity density", y: "Probability" ) { x in // (Double) -> Double normalDistribution( x, mean: mean, standardDeviation: standardDeviation ) } }
-
3:36 - Customize function plot with modifiers
Chart { LinePlot( x: "Capacity density", y: "Probability" ) { x in normalDistribution(x, ...) } .foregroundStyle(.gray }
-
3:57 - Visualize area under a curve with AreaPlot
Chart { AreaPlot( x: "Capacity density", y: "Probability" ) { x in normalDistribution(x, ...) } .foregroundStyle(.gray) .opacity(0.2) }
-
4:21 - Visualize area between curves with AreaPlot
Chart { AreaPlot( x: "x", yStart: "cos(x)", yEnd: "sin(x)" ) { x in (yStart: cos(x / 180 * .pi), yEnd: sin(x / 180 * .pi)) } }
-
4:59 - Specify domain for function plots
Chart { AreaPlot( x: "x", yStart: "cos(x)", yEnd: "sin(x)" ) { x in (yStart: cos(x / 180 * .pi), yEnd: sin(x / 180 * .pi)) } } .chartXScale(domain: -315...225) .chartYScale(domain: -5...5)
-
5:18 - Specify sampling domain for function plots
Chart { AreaPlot( x: "x", yStart: "cos(x)", yEnd: "sin(x)", domain: -135...45 ) { x in (yStart: cos(x / 180 * .pi), yEnd: sin(x / 180 * .pi)) } } .chartXScale(domain: -315...225) .chartYScale(domain: -5...5)
-
5:55 - Visualize parametric functions
Chart { LinePlot( x: "x", y: "y", t: "t", domain: -.pi ... .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) } } .chartXScale(domain: -3...3) .chartYScale(domain: -4...2)
-
6:40 - Use Double.nan to represent no value
Chart { LinePlot(x: "x", y: "1 / x") { x in guard x != 0 else { return .nan } return 1 / x } } .chartXScale(domain: -10...10) .chartYScale(domain: -10...10)
-
7:43 - Highly customized Chart
Chart { ForEach(model.data) { if $0.capacityDensity > 0.0001 { RectangleMark( x: .value("Longitude", $0.x), y: .value("Latitude", $0.y) ) .foregroundStyle(by: .value("Axis type", $0.axisType)) } else { PointMark( x: .value("Longitude", $0.x), y: .value("Latitude", $0.y) ) .opacity(0.5) } } }
-
8:00 - Homogeneously styled Chart
Chart { ForEach(model.data) { RectangleMark( x: .value("Longitude", $0.x), y: .value("Latitude", $0.y) ) .foregroundStyle(by: .value("Axis type", $0.panelAxisType)) .opacity($0.capacityDensity) } }
-
8:23 - Vectorized plot for homogeneously styled chart
Chart { RectanglePlot( model.data, x: .value("Longitude", \.x), y: .value("Latitude", \.y) ) .foregroundStyle(by: .value("Axis type", \.panelAxisType)) .opacity(\.capacityDensity) }
-
9:42 - Vectorized point plot API
Chart { PointPlot( model.data, x: .value("Longitude", \.x), y: .value("Latitude", \.y) ) }
-
10:26 - Vectorized plot modifiers
Chart { PointPlot( model.data, x: .value("Longitude", \.x), y: .value("Latitude", \.y) ) .symbolSize(by: .value("Capacity", \.capacity)) .foregroundStyle( by: .value("Axis type", \.panelAxisType) ) }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.