Skip to content

Mastering Charts: Custom Marks (SwiftUI)

A SwiftUI guide for coding agents. Also covers candlestick chart swift charts, chartcontent protocol, reusable mark type, rectanglemark ystart yend, compose marks in one chart.

Swift Charts has no candlestick mark, but marks compose: several marks inside one ForEach draw as one glyph per data point. Wrap the composition in a ChartContent type and it becomes a reusable mark of your own.

Composition Inside the Chart

A line with visible points is already composition: two marks per data point.

Chart {
  ForEach(Array(numbers.enumerated()), id: \.element) { index, number in
    LineMark(x: .value("index", index), y: .value("value", number))
    PointMark(x: .value("index", index), y: .value("value", number))
  }
}

A Candlestick From Two RectangleMarks

RectangleMark takes an x value plus yStart and yEnd, and a width in points. A thin rectangle spans low to high, a wide one spans open to close.

Chart {
  ForEach(Array(zip(candles.indices, candles)), id: \.1) { index, candle in
    RectangleMark(
      x: .value("index", index),
      yStart: .value("low", candle.low),
      yEnd: .value("high", candle.high),
      width: 4
    )
    RectangleMark(
      x: .value("index", index),
      yStart: .value("open", candle.open),
      yEnd: .value("close", candle.close),
      width: 16
    )
    .foregroundStyle(.red)
  }
}

Extract a Mark With ChartContent

ChartContent is to marks what View is to views: one body requirement returning some ChartContent. Take PlottableValue parameters to match the built-in marks' API.

struct CandlestickMark<X: Plottable, Y: Plottable>: ChartContent {
  let x: PlottableValue<X>
  let low: PlottableValue<Y>
  let high: PlottableValue<Y>
  let open: PlottableValue<Y>
  let close: PlottableValue<Y>

  var body: some ChartContent {
    RectangleMark(x: x, yStart: low, yEnd: high, width: 4)
    RectangleMark(x: x, yStart: open, yEnd: close, width: 16)
  }
}

The custom mark inherits every standard mark modifier, so callers style it like a built-in.

Chart {
  ForEach(0...10, id: \.self) { index in
    CandlestickMark(
      x: .value("index", index),
      low: .value("low", 1),
      high: .value("high", 9),
      open: .value("open", 3),
      close: .value("close", 7)
    )
    .foregroundStyle(.green)
  }
}

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-mastering-charts-custom-marks" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems