iosswiftuiswiftui-charts

Styling chart axes


I am updating an old app written in Objective C to SwiftUI. One thing I am struggling with is how to style the axes with SwiftUI Charts. The old app used CorePlot.

This is the CorePlot version:

enter image description here

And the SwiftUI version:

enter image description here

I cannot figure out how to code in SwiftUI to get the same result as in the old version:

This is my code so far:

Chart(data) {
    LineMark(
        x: .value("x", $0.x),
        y: .value("y", $0.y)
    )
    .interpolationMethod(.catmullRom)
    .lineStyle(StrokeStyle(lineWidth: 1))
}
.chartXAxis {
    AxisMarks(position: .bottom, values: .automatic) { _ in
        AxisValueLabel(anchor: .top)
    }
}
.chartXAxisLabel(position: .bottom, alignment: .center) {
    Text(chartProperties.xTitle)
}
.chartXScale(domain: [chartProperties.minX, chartProperties.maxX])

.chartYAxisLabel(position: .leading, alignment: .center) {
    Text(chartProperties.yTitle)
}
.chartYScale(domain: [chartProperties.minY, chartProperties.maxY])

.chartYAxis {
    AxisMarks(position: .leading)
}

chartProperties is a struct that has info such as axis titles and min and max values.


Solution

  • You can add the axis ticks simply using AxisTick, and the axis lines can be added with AxisGridLine. .foregroundStyle can be added to both of these.

    The grid lines "through" the chart that you see are also AxisGridLines added by SwiftUI by default. The defaults will not be added if you use the AxisMarks initialiser that takes a @AxisMarkBuilder closure.

    In total, you need 3 sets of AxisMarks for each axis:

    Additionally, the tick marks should be offsetted, so that the grid lines go though the ticks.

    Here is a complete example, where the y axis is (-2, 2), and x axis is (0, 400).

    struct Foo: Identifiable {
        let id: Double
        let y: Double
    }
    
    
    let data: [Foo] = (0..<400).map { Foo(id: Double($0), y: log2(1 - Double.random(in: 0..<1)) / -10 - 1) }
    
    struct ContentView: View {
        var body: some View {
            Chart(data) {
                LineMark(
                    x: .value("x", $0.id),
                    y: .value("y", $0.y)
                )
                .interpolationMethod(.catmullRom)
                .lineStyle(StrokeStyle(lineWidth: 1))
            }
            .chartXAxisLabel(position: .bottom, alignment: .center) {
                Text("X Axis")
            }
            .chartXScale(domain: [0, 400])
            .chartXAxis {
                AxisMarks(position: .bottom, values: .stride(by: 100)) {
                    AxisValueLabel(anchor: .top)
                }
                AxisMarks(position: .bottom, values: .stride(by: 20)) { value in
                    if value.index != 0 { // the leftmost value does not need a tick
                        AxisTick(length: 8, stroke: .init(lineWidth: 1))
                            .offset(y: -4)
                    }
                }
                AxisMarks(position: .bottom, values: [0]) {
                    AxisGridLine(stroke: .init(lineWidth: 1))
                }
            }
            .chartYAxisLabel(position: .leading, alignment: .center) {
                Text("Y Label")
            }
            .chartYScale(domain: [-2, 2])
            .chartYAxis {
                AxisMarks(position: .leading, values: .stride(by: 1)) {
                    AxisValueLabel()
                }
                AxisMarks(position: .leading, values: .stride(by: 0.2)) { value in
                    if value.index != 0 { // the bottommost value does not need a tick
                        AxisTick(length: 8, stroke: .init(lineWidth: 1))
                            .offset(x: 4)
                    }
                }
                AxisMarks(position: .leading, values: [-2]) {
                    AxisGridLine(stroke: .init(lineWidth: 1))
                }
            }
            .padding()
            .aspectRatio(1, contentMode: .fit)
        }
    }
    

    enter image description here