Skip to main content
ChartModel is an ObservableObject that holds all data and configuration for a chart. Pass it to ChartView and any change you make to the model — data, selection, axis attributes, or series colors — immediately re-renders the chart.
let model = ChartModel(
    chartType: .line,
    data: [[200, 170, 165, 143, 166, 112, 110],
           [150, 120, 130, 135, 120, 138, 137]],
    titlesForCategory: [["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"]]
)

ChartView(model)
    .frame(width: 300, height: 200)

Initializers

Two public convenience initializers cover nearly all use cases.

Standard 2D init (most chart types)

Use this initializer for line, column, bar, area, combo, stacked column, stacked bar, waterfall, donut, and micro charts.
public convenience init(
    chartType: ChartType,
    data: [[Double?]],
    titlesForCategory: [[String?]]? = nil,
    colorsForCategory: [Int: [Int: Color]]? = nil,
    titlesForAxis: [ChartAxisId: String]? = nil,
    labelsForDimension: [[String?]]? = nil,
    numberOfGridlines: Int = 3,
    selectionRequired: Bool = false,
    selectionMode: ChartSelectionMode = .single,
    selections: [Int: [Int]]? = nil,
    userInteractionEnabled: Bool = false,
    selectionEnabled: Bool = false,
    centerPosition: CGPoint? = nil,
    scaleX: CGFloat = 1.0,
    scaleY: CGFloat = 1.0,
    scaleXEnabled: Bool = false,
    scaleYEnabled: Bool = false,
    readableScaleEnabled: Bool = true,
    snapToPoint: Bool = false,
    seriesAttributes: [ChartSeriesAttributes]? = nil,
    categoryAxis: ChartCategoryAxisAttributes? = nil,
    numericAxis: ChartNumericAxisAttributes? = nil,
    secondaryNumericAxis: ChartNumericAxisAttributes? = nil,
    xAxisLabelsPosition: XAxisLabelsPosition = .fixedBottom,
    indexOfStockSeries: Int = 0,
    indexesOfSecondaryValueAxis: [Int]? = nil,
    indexesOfColumnSeries: [Int]? = nil,
    indexesOfTotalsCategories: [Int]? = nil,
    numericAxisLabelFormatHandler: NumericAxisLabelFormatHandler? = nil
)

3D init (bubble, scatter, stock)

Use this initializer when each data point has three dimensions — for example [x, y, radius] for a bubble chart.
public convenience init(
    chartType: ChartType,
    data3d: [[[Double?]]],
    // ... same options as above
)
For bubble charts, supply values in [x, y, radius] order per data point. The initializer reorders them internally.

Parameters

chartType
ChartType
required
The chart type to render. See Chart types for the full list of values.
data
[[Double?]]
required
A two-dimensional array structured as series → categories. The outer array contains one element per series; each inner array contains the values for that series, one per category. Use nil for missing data points — the chart will render a gap.
// Two series, seven categories each
data: [
    [nil, 220, nil, 250, 200, nil, 230],  // series 0
    [160, nil, 130, 170, nil, 190, 180]   // series 1
]
titlesForCategory
[[String?]]?
default:"nil"
Labels for the category axis. The outer array corresponds to series; in practice you usually supply a single inner array that applies to all series.
titlesForCategory: [["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"]]
colorsForCategory
[Int: [Int: Color]]?
default:"nil"
Per-category color overrides, keyed by [seriesIndex: [categoryIndex: Color]]. Overrides the color from seriesAttributes for the specified categories.
colorsForCategory: [0: [2: .red, 5: .orange]]
titlesForAxis
[ChartAxisId: String]?
default:"nil"
Axis title strings keyed by ChartAxisId. Use .x, .y, .category, or .dual.
titlesForAxis: [.x: "Month", .y: "Revenue (USD)"]
labelsForDimension
[[String?]]?
default:"nil"
Custom labels rendered on individual data points, in the same series × category layout as data. Useful for annotating specific values.
numberOfGridlines
Int
default:"3"
Number of gridlines drawn on the numeric axis. Constrained to the range 2–20.
selectionMode
ChartSelectionMode
default:".single"
Controls which plot items are selected when the user taps.
  • .single — selects one value in the active series and category.
  • .all — selects one value per series for the tapped category.
  • .multiple — multiple independent selections (donut charts only; set automatically).
selections
[Int: [Int]]?
default:"nil"
Initial selection state. The dictionary maps seriesIndex to an array of categoryIndex values.
// Pre-select categories 0–6 in series 0
selections: [0: [0, 1, 2, 3, 4, 5, 6]]

// Select category 0 in both series (selectionMode: .all)
selections: [0: [0], 1: [0]]
userInteractionEnabled
Bool
default:"false"
Enables pan, pinch-to-zoom, and drag gestures. Set to false when using the chart in an iOS Widget.
selectionEnabled
Bool
default:"false"
Enables tap and two-finger long-press selection gestures independently of userInteractionEnabled.
seriesAttributes
[ChartSeriesAttributes]?
default:"nil"
Per-series styling — colors, line width, point attributes. See Customizing charts for details.
categoryAxis
ChartCategoryAxisAttributes?
default:"nil"
Style and behavior attributes for the category axis (X axis for most chart types, Y axis for bar charts).
numericAxis
ChartNumericAxisAttributes?
default:"nil"
Style and behavior attributes for the primary numeric axis (Y axis for most chart types, X axis for bar charts).
secondaryNumericAxis
ChartNumericAxisAttributes?
default:"nil"
Attributes for the secondary numeric axis. Only applicable to .line, .area, and .combo chart types.
snapToPoint
Bool
default:"false"
When true, the chart snaps to the nearest data point while the user drags.
indexesOfSecondaryValueAxis
[Int]?
default:"nil"
Series indexes assigned to the secondary Y axis. Only applies to .line, .area, and .combo charts.
indexesOfColumnSeries
[Int]?
default:"nil"
For .combo charts, the series indexes that render as columns. All other series render as lines.
indexesOfTotalsCategories
[Int]?
default:"nil"
For .waterfall charts, the category indexes that represent totals. If the corresponding value is nil in data, the chart computes the running total automatically.
numericAxisLabelFormatHandler
NumericAxisLabelFormatHandler?
default:"nil"
A closure that formats numeric axis label values. Use this for custom units or precision.

Examples

Line chart with two series

let model = ChartModel(
    chartType: .line,
    data: [
        [nil, 220, nil, 250, 200, nil, 230],
        [160, nil, 130, 170, nil, 190, 180]
    ],
    titlesForCategory: [["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"]],
    titlesForAxis: [.x: "Month", .y: "Units"],
    numberOfGridlines: 4,
    selectionMode: .all,
    selectionEnabled: true
)

ChartView(model)
    .frame(width: 360, height: 220)

Donut chart

Donut charts use a single series and a single category per segment. The selectionMode is automatically set to .multiple for donut charts.
let model = ChartModel(
    chartType: .donut,
    data: [[1550, 1120, 830, 420]],
    titlesForCategory: [["Q1", "Q2", "Q3", "Q4"]],
    colorsForCategory: [
        0: [
            0: Color(hex: "#E57373"),
            1: Color(hex: "#FFB74D"),
            2: Color(hex: "#81C784"),
            3: Color(hex: "#64B5F6")
        ]
    ]
)

ChartView(model)
    .frame(width: 240, height: 240)

Waterfall chart with totals

let model = ChartModel(
    chartType: .waterfall,
    data: [[1200, -300, 450, -180, nil]],
    titlesForCategory: [["Start", "Costs", "Revenue", "Taxes", "Net"]],
    indexesOfTotalsCategories: [4] // index 4 is computed as running total
)

ChartView(model)
    .frame(width: 360, height: 220)

Observing selection changes

Subscribe to selectionDidChangePublisher using Combine to react to user-driven selection updates.
import Combine

var cancellables: Set<AnyCancellable> = []

model.selectionDidChangePublisher
    .receive(on: RunLoop.main)
    .sink { selections in
        guard let selections else {
            print("No selection")
            return
        }
        for (seriesIndex, categoryIndexes) in selections {
            print("Series \(seriesIndex), categories: \(categoryIndexes)")
        }
    }
    .store(in: &cancellables)
The selectionDidChangePublisher publishes only when the user interacts with the chart. Setting model.selections programmatically does not trigger the publisher.

Build docs developers (and LLMs) love