Rotate axis label in Swift Charts
Asked Answered
B

2

8

I'm using Swift Charts in a SwiftUI app to make a line chart. I would like to rotate the Y axis label such that it reads from bottom to top. I tried to rotate the text but the rotation causes the label to not render properly. Is there a way to rotate the axis label?

import SwiftUI
import Charts

struct ContentView: View {

    struct Point: Identifiable {
        let id = UUID()
        let x: Float
        let y: Float
    }

    let points = [
        Point(x: 0, y: 1),
        Point(x: 1, y: 4.5),
        Point(x: 2, y: 3),
        Point(x: 3, y: 6),
        Point(x: 4, y: 7),
        Point(x: 5, y: 5.2),
        Point(x: 6, y: 9),
        Point(x: 7, y: 12.5),
    ]

    var body: some View {
        Chart {
            ForEach(points) { point in
                LineMark(
                    x: .value("X values", point.x),
                    y: .value("Y values", point.y)
                )
            }
        }
        .chartXAxisLabel("The x-axis", alignment: .center)
        .chartYAxisLabel(position: .leading) {
            Text("The y-axis")
                .rotationEffect(.degrees(180))
        }
        .chartYAxis {
            AxisMarks(position: .leading)
        }
        .padding()
        .frame(minWidth: 400, minHeight: 300)
    }
}

The window produced from the example is shown below. Notice, the label for the Y axis does not display properly.

Brennan answered 3/12, 2022 at 4:28 Comment(0)
O
4

This issue seems to be caused by the space the label requires.

For example by adding the line .frame(minWidth: 55, minHeight: 50) to the rotated text, the issue gets solved (however, the label requires quite some space, which is an issue I was unable to fix).

Updated chartYAxisLabel code:

 .chartYAxisLabel(position: .leading) {
                Text("The y-axis")
                    .rotationEffect(.degrees(180))
                    .frame(minWidth: 55, minHeight: 50)
        }

Updated preview:

updated graph)

Oneil answered 1/5, 2023 at 16:2 Comment(1)
I'm having better luck with .frame(width: 55, height: 50). The min width and height are stretching my y-Axis label.Schaaff
P
2

I'm coming late to the party, but I post my solution for future references.

For iOS 16 and later, one can use the Layout protocol to correctly size rotated text. With this, you can define a custom text modifier that allows rotating without sizing inconsistencies (and without setting a size manually, which produces blank spaces):

//  VerticalLayout.swift
//
// Layout to keep the correct size for vertically rotated text
//

import SwiftUI

private struct VerticalLayout: Layout {
    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        let size = subviews.first!.sizeThatFits(.unspecified)
        return .init(width: size.height, height: size.width)
    }

    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        subviews.first!.place(at: .init(x: bounds.midX, y: bounds.midY), anchor: .center, proposal: .unspecified)
    }
}

private struct RotatedContent: ViewModifier {
    let angle: Angle
    
    func body(content: Content) -> some View {
        VerticalLayout(){
            content
        }
        .rotationEffect(angle)
    }
}

extension View {
    func rotate(_ angle: Angle) -> some View {
        self.modifier(RotatedContent(angle: angle))
    }
}

This can then be used as Text("Your text").rotate(.degrees(270)) to rotate the Text element. However, when inserting this in the .chartYAxisLabel, the text still doesn't render correctly.

To solve this further problem and display the y axis label as desired, one can then use an HStack to display the legend separately from the Chart:

var body: some View {
    HStack(alingment: .center, spacing: 8){
        // Insert the Y axis label, changing the size and style to match
        // regular axis labels
        Text(yAxisLabel)
            .font(.caption2)
            .foregroundStyle(.secondary)
            .rotate(.degrees(-90))
    
        Chart{
            // ... Your Chart content 
        }
        // ... The options for the Chart, without .chartYAxisLabel, 
        // as we already rendered it
    }

}

This results in something like this:

example chart

Of course, it would be nice to have an easier way to do this...or to have the y axis on the left and correctly rotated by default, as that is the standard representation.

Pentateuch answered 22/2, 2024 at 10:35 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.