Search code examples
swiftswiftuiswiftui-charts

Order of layers in Swift combined chart


I am making a chart which consists of an Area Mark, 2 Line Marks & 2 Point Marks.

No matter which order I place the different chart elements, the Area Mark is on top and I can't find any documentation or online articles explaining how to change the z-value attribute as I normally would with other Swift views.

Screenshot showing a graph where the opaque area mark is over the top of both line marks and their annotations

var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            
            Chart(data) { mark in
                
                // Global average fill
                
                if mark.owner == .global {
                    AreaMark (
                        x: .value("day", mark.day),
                        y: .value("score", mark.score)
                    )
                    .foregroundStyle(.indigo)
                    .opacity(1)
                    .interpolationMethod(.catmullRom)
                }
                
                // Player line
                
                if mark.owner == .player {
                    LineMark (
                        x: .value("day", mark.day),
                        y: .value("score", mark.score),
                        series: .value("Player Score", "player")
                    )
                    .interpolationMethod(.catmullRom)
                }
                // Player annotation
                
                if mark.owner == .player {
                    
                    PointMark (
                        x: .value("day", mark.day),
                        y: .value(dataPoint == .score ? "score" : "seconds", mark.score)
                    )
                    .symbol() {
                        Circle()
                            .strokeBorder(.black)
                            .background(Circle().fill(.blue))
                            .frame(width: 10)
                    }
                    .annotation() {
                        Text("\(mark.score)")
                    }
                    .interpolationMethod(.catmullRom)
                }
                
                // Friends line
                
                if mark.owner == .friends {
                    LineMark(
                        x: .value("day", mark.day),
                        y: .value(mark.type == .score ? "score" : "seconds", mark.score),
                        series: .value("Friends Score", "friends")
                    )
                    .interpolationMethod(.catmullRom)
                    .foregroundStyle(.yellow)
                    .lineStyle(StrokeStyle(lineWidth: 3, dash: [5, 10]))
                    
                }
                
                // friends annotation
                
                if mark.owner == .friends {
                    PointMark (
                        x: .value("day", mark.day),
                        y: .value("score", mark.score)
                    )
                    
                    .interpolationMethod(.catmullRom)
                    
                    .symbol() {
                        Circle()
                            .strokeBorder(.black)
                            .background(Circle().fill(.orange))
                            .frame(width: 10)
                    }
                    .annotation() {
                        Text("\(mark.score)")
                        
                    }
                }
            }
            .frame(minWidth: screen.size.height*3, maxHeight: screen.size.height/3)
            .chartXScale(domain: dates)
            .chartYScale(domain: getYAxis())
}
}

I've tried switching around the order of the elements. Even when the Area Mark is sandwiched between 2, it's still on top. Also the friends line is always above the player line (on the z-axis).

I realised it might be useful to have some example data:

var data: [ChartData] = [
        // player
        ChartData(day: "28/5", score: 18, owner: .player, type: .score),
        ChartData(day: "29/5", score: 19, owner: .player, type: .score),
        ChartData(day: "30/5", score: 15, owner: .player, type: .score),
        ChartData(day: "31/5", score: 11, owner: .player, type: .score),
        // friends
        ChartData(day: "28/5", score: 11, owner: .friends, type: .score),
        ChartData(day: "29/5", score: 12, owner: .friends, type: .score),
        ChartData(day: "30/5", score: 11, owner: .friends, type: .score),
        ChartData(day: "31/5", score: 15, owner: .friends, type: .score),
        // global
        ChartData(day: "28/5", score: 21, owner: .global, type: .score),
        ChartData(day: "29/5", score: 22, owner: .global, type: .score),
        ChartData(day: "30/5", score: 21, owner: .global, type: .score),
        ChartData(day: "31/5", score: 25, owner: .global, type: .score),
        
    ]

Solution

  • Try reordering your [ChartData]. Start with the global ChartData Elements.

    In your example the global ChartData elements are ordered at the end of your array and therefore drawn last == at the top