English · Türkçe · 简体中文 · 日本語 · 한국어 · Español · Deutsch · Français · Português (Brasil) · Русский
A Swift package of chart views for SwiftUI on iOS 17+ and macOS 14+, built on Swift Charts. It
covers the charts that Swift Charts leaves to you — time series with tens of thousands of
points, histograms, box plots, trend lines, candlesticks, waterfalls, heatmaps, radar charts,
treemaps and live tickers — and makes the analysis behind them public, so you can use it in a
Chart of your own. The package has no dependencies.
- 10 chart views —
TimeSeriesChart,HistogramChart,BoxPlotChart,ScatterChart,CandlestickChart,WaterfallChart,HeatmapChart,RadarChart,TreemapChartandLiveTickerChart. Each takes plain value types and needs only a frame. - Large and live data —
TimeSeriesChartdownsamples each series to 1,000 points with Largest-Triangle-Three-Buckets, so 50,000 readings per series stay smooth; it can scroll through a time window and report the selected date.LiveSerieskeeps the latest values in a ring buffer forLiveTickerChart. - The analysis is public —
Histogram(Sturges, Freedman–Diaconis, fixed width or count),BoxPlotSummary(Tukey whiskers),Statistics,LinearRegression,MovingAverage,NiceScaleaxis ticks,TreemapLayout(squarified),Candle.aggregateandDownsampling. - Safe with any input — values that are not finite and dates outside the years 1 to 4001 are
left out, finite values beyond ±1e307 are drawn at that limit, ranges near
Double.greatestFiniteMagnitudeand subnormal ranges stay finite, and tick counts, bins, buffers, scroll windows and exports have upper limits. A dedicated test suite feedsnan, infinity,Int.max, extreme ranges and extreme dates to the analysis types and the charts. - Accessible — the Swift Charts based views get VoiceOver navigation and Audio Graphs, with
readable labels for bins, boxes, candles and steps; the views drawn without Swift Charts
(radar, treemap, ticker) attach an
AudioGraphDescriptor. A color-blind safe palette is built in. - Tested — 188 Swift Testing tests in 33 suites, run on macOS and an iPhone simulator, including off-screen renders of every README sample in light and dark mode.
- Usage samples that compile — every sample below comes from the example app, which CI builds for iOS and macOS.
| Area | Types |
|---|---|
| Time series | TimeSeriesChart, TimeSeries, TimeSeriesPoint, MovingAverage, Downsampling |
| Statistics | HistogramChart, Histogram, BoxPlotChart, BoxPlotSummary, ScatterChart, LinearRegression, Statistics |
| Finance | CandlestickChart, Candle, WaterfallChart, WaterfallStep |
| Grids and shapes | HeatmapChart, HeatmapCell, RadarChart, RadarGeometry, TreemapChart, TreemapLayout |
| Live data | LiveTickerChart, LiveSeries, RingBuffer |
| Shared | ChartPalette and .chartPalette(_:), NiceScale, ChartExport, AudioGraphDescriptor |
1. Add the package
In Xcode, choose File › Add Package Dependencies… and enter the repository URL:
https://github.com/muhittincamdali/SwiftUI-Data-Visualization.git
Paste it into the search field at the top right of the window. Leave Dependency Rule at
Up to Next Major Version from 2.0.2, then click Add Package. In the next window,
choose your app target next to SwiftUIDataVisualization and click Add Package again.
2. Import the module
import SwiftUIDataVisualizationPut this line at the top of the Swift file where you draw a chart, below import SwiftUI.
3. Use it
In a new Xcode app (File › New › Project… › App), replace everything in ContentView.swift
with:
import SwiftUI
import SwiftUIDataVisualization
struct ContentView: View {
let steps = TimeSeries(
name: "Steps",
values: [4_200, 8_100, 6_900, 10_400, 7_800, 12_100, 9_300],
start: .now.addingTimeInterval(-6 * 86_400),
interval: 86_400
)
var body: some View {
TimeSeriesChart(steps)
.frame(height: 240)
.padding()
}
}Press Run (⌘R). The app shows a blue line of the last seven days; touch the line (or hover over it on the Mac) to see the date and the number of steps.
In Xcode, choose File › Add Package Dependencies… and enter the repository URL:
https://github.com/muhittincamdali/SwiftUI-Data-Visualization.git
Or add it to the dependencies in your Package.swift:
dependencies: [
.package(url: "https://github.com/muhittincamdali/SwiftUI-Data-Visualization.git", from: "2.0.2")
],
targets: [
.target(name: "YourApp", dependencies: [
.product(name: "SwiftUIDataVisualization", package: "SwiftUI-Data-Visualization")
])
]Version 2.0.0 (October 2026) is the first release of the charts described here. The earlier release, 1.0.0, had a different API that did not build with current Xcode versions; 2.0.0 also brings in the work of SwiftUI-Charts-Pro, which is being archived. CHANGELOG.md lists the changes, including the breaking ones.
The samples below are copied from
Examples/ChartGallery/ChartGallery/ReadmeSamples.swift.
The example app and the test suite both compile that file.
struct ServerLoad: View {
// 50,000 readings per server, one every 30 seconds; the chart draws 1,000 of each.
let servers = [readings(name: "api-1", phase: 0), readings(name: "api-2", phase: 1)]
static func readings(name: String, phase: Double) -> TimeSeries {
let values = (0..<50_000).map { index -> Double in
let wave = 25 * sin(Double(index) / 3_000 + phase)
let noise = Double(index % 97) / 40
return 40 + wave + noise
}
return TimeSeries(name: name, values: values, start: Date(timeIntervalSinceReferenceDate: 800_000_000), interval: 30)
}
var body: some View {
TimeSeriesChart(servers, movingAverageWindow: 2_000)
.frame(height: 240)
.padding()
}
}The dashed lines are the moving averages over 2,000 readings. Pass style: .area to fill below
the lines.
struct ScrollingHistory: View {
@State private var selected: Date?
@State private var exported: Data?
let load = ServerLoad.readings(name: "api-1", phase: 0)
var chart: some View {
// Two days at a time out of 17; the chart opens at the newest data.
TimeSeriesChart(load, selection: $selected, visibleDuration: 2 * 86_400)
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text(selected.map { "Selected \($0.formatted(date: .abbreviated, time: .shortened))" } ?? "Touch the chart")
.font(.headline)
chart
.frame(height: 220)
Button("Export PNG") {
exported = ChartExport.pngData(of: chart, size: CGSize(width: 600, height: 300))
}
if let exported {
Text("\(exported.count) bytes")
}
}
.padding()
}
}struct LatencyReport: View {
let ios: [Double] = [112, 98, 130, 101, 145, 120, 99, 180, 104, 117, 125, 133, 230, 108, 121]
let android: [Double] = [140, 151, 128, 160, 149, 171, 138, 155, 162, 147, 158, 133, 166, 152]
var body: some View {
VStack(spacing: 24) {
HistogramChart(values: ios + android, binning: .width(25), valueLabel: "Latency (ms)")
.frame(height: 180)
BoxPlotChart([
.init(name: "iOS", values: ios),
.init(name: "Android", values: android),
], valueLabel: "Latency (ms)")
.frame(height: 200)
}
.padding()
}
}Without binning, the histogram chooses the bin width with Sturges' or the Freedman–Diaconis
rule, whichever gives more bins. The box plot marks values beyond 1.5 interquartile ranges as
outliers.
struct HeightWeight: View {
let people: [PlotPoint] = [
.init(x: 158, y: 54), .init(x: 163, y: 60), .init(x: 167, y: 59), .init(x: 171, y: 68),
.init(x: 174, y: 70), .init(x: 178, y: 74), .init(x: 181, y: 77), .init(x: 186, y: 85),
]
var body: some View {
ScatterChart(people, xLabel: "Height (cm)", yLabel: "Weight (kg)", showsTrendLine: true)
.frame(height: 220)
.padding()
}
}struct PriceAndProfit: View {
// Trades every 5 minutes for three days, grouped into one candle per 6 hours.
let candles = Candle.aggregate(trades, interval: 6 * 3_600)
static let trades = (0..<864).map { index -> TimeSeriesPoint in
let date = Date(timeIntervalSinceReferenceDate: 800_000_000 + Double(index) * 300)
let price = 100 + 6 * sin(Double(index) / 40) + Double(index % 13) / 4
return TimeSeriesPoint(date: date, value: price)
}
var body: some View {
VStack(spacing: 24) {
CandlestickChart(candles)
.frame(height: 200)
WaterfallChart([
WaterfallStep("Revenue", 420),
WaterfallStep("Costs", -260),
WaterfallStep("Tax", -40),
.total("Profit"),
])
.frame(height: 200)
}
.padding()
}
}struct CommitActivity: View {
let days = ["Mon", "Tue", "Wed", "Thu", "Fri"]
let hours = ["9", "11", "13", "15", "17"]
var cells: [HeatmapCell] {
days.enumerated().flatMap { day, dayName in
hours.enumerated().map { hour, hourName in
HeatmapCell(column: hourName, row: dayName, value: Double((day * 7 + hour * 3) % 11))
}
}
}
var body: some View {
HeatmapChart(cells, valueLabel: "Commits", showsValues: true)
.frame(height: 220)
.padding()
}
}struct ProfileAndStorage: View {
var body: some View {
VStack(spacing: 24) {
RadarChart(
axes: ["Speed", "Power", "Range", "Comfort", "Price"],
series: [
.init(name: "Model A", values: [8, 6, 7, 9, 5]),
.init(name: "Model B", values: [6, 9, 5, 6, 8]),
],
maximum: 10
)
.frame(height: 260)
TreemapChart([
TreemapItem("Photos", value: 48),
TreemapItem("Apps", value: 31),
TreemapItem("Music", value: 12),
TreemapItem("Mail", value: 6),
TreemapItem("Notes", value: 3),
])
.frame(height: 200)
}
.padding()
}
}struct LiveLoad: View {
@State private var load = LiveSeries(capacity: 120)
var body: some View {
LiveTickerChart(values: load.values, title: "CPU load")
.frame(height: 80)
.padding()
.task {
var tick = 0.0
while !Task.isCancelled {
tick += 1
load.append(50 + 30 * sin(tick / 8)) // read the real value here
try? await Task.sleep(for: .milliseconds(100))
}
}
}
}struct ColorblindSafeCharts: View {
var body: some View {
LatencyReport()
.chartPalette(.colorblindSafe)
}
}Every chart below the modifier uses the palette. The default, .ocean, is the blue palette in
the screenshots; build your own with ChartPalette(series:positive:negative:neutral:gradient:).
| Light | Dark |
|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
The images are rendered on the iOS simulator from the Usage samples by
Tests/SwiftUIDataVisualizationTests/RenderTests.swift
and scripts/generate-screenshots.sh.
The time series and live data screens of the example app, captured on an iPhone simulator by
scripts/capture-example-screens.sh:
| Time series | Live data |
|---|---|
![]() |
![]() |
![]() |
![]() |
To try the charts yourself, open ChartGallery.xcodeproj in
Examples/ChartGallery with Xcode, choose an iPhone simulator or
My Mac, and press Run. The app has one screen per sample above;
Examples/README.md has the details.
Reference documentation and articles:
- API reference
(DocC, published to GitHub Pages from
master) - Build it locally in Xcode with Product › Build Documentation, or from the command line:
xcodebuild docbuild -scheme SwiftUIDataVisualization -destination 'generic/platform=macOS'- Getting Started — add the package, draw a first chart, map your own data and set a palette (source).
- Choosing a Chart — which chart answers which question, and the statistics behind them (source).
- Large and Live Data — downsampling, scrolling long histories, live values and the input limits (source).
- Accessible Charts — what VoiceOver reads for each chart, and the color-blind safe palette (source).
| Platform | Minimum version |
|---|---|
| iOS | 17 |
| macOS | 14 |
| Toolchain | Swift 6.2 · Xcode 26+ |
CI builds with the default Xcode of GitHub's macos-26 runner image. The package is developed
with Xcode 27.
Sources/SwiftUIDataVisualization/Charts/holds one file per chart view. Seven of them are Swift ChartsCharts;RadarChart,TreemapChartandLiveTickerChartdraw withCanvasor plain views, because Swift Charts has no polar or treemap coordinates and a ticker redrawn at display rate is cheaper in oneCanvaspass.Analysis/,Layout/andData/hold the value types and functions the charts use (Histogram,BoxPlotSummary,LinearRegression,NiceScale,TreemapLayout,Candle, …). They have no view code and areSendable, so they can run off the main actor.- The preparation each chart does before drawing (downsampling, whisker domains, candle body
widths, waterfall bars, radar maximum) lives in
nonisolated staticfunctions that the tests call directly. Streaming/hasRingBufferandLiveSeries, the only reference type, an@Observableclass on the main actor.Style/ChartPalette.swiftpasses colors through the environment.- CI runs
swift teston macOS,xcodebuild teston an iPhone simulator, builds the example app for iOS and macOS, and publishes the DocC documentation.
Run the tests locally:
swift test # macOS
xcodebuild test -scheme SwiftUIDataVisualization \
-destination 'platform=iOS Simulator,name=iPhone 17'- Add the package to the Swift Package Index;
.spi.ymlis already in the repository. - Pinch to zoom on the time axis of
TimeSeriesChart. - Test and enable visionOS, which Swift Charts supports.
Contributions are welcome. Read the contributing guide and the code of conduct before opening a pull request. Report security issues privately as described in the security policy.
Released under the MIT license. See LICENSE.















