Search code examples
swiftswiftuiplotlineswiftui-charts

Chart Symbols and Legend Symbols Are Diferent in SwiftUI


Legend symbols produced by the .symbol(by:) modifier are different than the ones used in the chart. I specifically used the triangle and plus symbols in the chart, but they are disregarded and the circle and square symbols are produced automatically in the legend part. How can I make them agree?

struct Line: Identifiable {
    let xyzipped: [(Double, Double)]
    let symbol: BasicChartSymbolShape
    let legendStr: String
    let id = UUID()
    
    init(x: [Double],
         y: [Double],
         symbol: BasicChartSymbolShape,
         legendStr: String)
    {
        self.xyzipped = Array(zip(x, y))
        self.symbol = symbol
        self.legendStr = legendStr
    }
}


struct ChartOfLines: View {
    let lines: [Line]
    
    init() {
        let line1 = Line(x: [0, 1, 2, 3, 4],
                         y: [3, 5, 8, -1, 0],
                         symbol: BasicChartSymbolShape.triangle,
                         legendStr: "line1")
        let line2 = Line(x: [0, 1, 2, 3, 4],
                         y: [5, 4, 3, 7, 1],
                         symbol: BasicChartSymbolShape.plus,
                         legendStr: "line2")
        
        lines = [line1, line2]
    }
    
    var body: some View {
        Chart(lines, id: \.id) { line in
            ForEach(line.xyzipped, id:\.0) { xy in
                LineMark(
                    x: .value("X values", xy.0),
                    y: .value("Y values", xy.1)
                )
                .lineStyle(StrokeStyle(lineWidth: 0.5))
                
                PointMark(
                    x: .value("X values", xy.0),
                    y: .value("Y values", xy.1)
                )
            }
            .symbol(line.symbol)
            .symbol(by: .value("", line.legendStr))
            .foregroundStyle(by: .value("", line.legendStr))
        }
        .frame(width: 500, height: 500)
        .padding()
    }
}

Here is the produced chart: enter image description here


Solution

  • Create a custom chartLegend if you want the ChartSymbolShape to appear to the left of the legend string:

    Example to test, adjust UI as necessary:

    struct Line: Identifiable {
        let xyzipped: [(Double, Double)]
        let symbol: BasicChartSymbolShape
        let legendStr: String
        let lineColor: Color
        let id = UUID()
        
        init(x: [Double],
             y: [Double],
             symbol: BasicChartSymbolShape,
             legendStr: String,
             lineColor: Color)
        {
            self.xyzipped = Array(zip(x, y))
            self.symbol = symbol
            self.legendStr = legendStr
            self.lineColor = lineColor
        }
    }
    
    
    struct ContentView: View {
        let lines: [Line]
        
        init() {
            let line1 = Line(x: [0, 1, 2, 3, 4],
                             y: [3, 5, 8, -1, 0],
                             symbol: BasicChartSymbolShape.triangle,
                             legendStr: "line1", lineColor: .green)
            let line2 = Line(x: [0, 1, 2, 3, 4],
                             y: [5, 4, 3, 7, 1],
                             symbol: BasicChartSymbolShape.plus,
                             legendStr: "line2", lineColor: .blue)
            
            
            lines = [line1, line2]
        }
        
        var body: some View {
            Chart(lines, id: \.id) { line in
                ForEach(line.xyzipped, id:\.0) { xy in
                    LineMark(
                        x: .value("X values", xy.0),
                        y: .value("Y values", xy.1)
                    )
                    .lineStyle(StrokeStyle(lineWidth: 0.5))
                    .foregroundStyle(line.lineColor)
                    
                    PointMark(
                        x: .value("X values", xy.0),
                        y: .value("Y values", xy.1)
                    )
                    .foregroundStyle(line.lineColor)
                }
                .symbol(line.symbol)
                .foregroundStyle(by: .value("", line.legendStr))
            }
            .chartLegend(position: .bottomLeading, alignment: .bottomLeading) {
                HStack() {
                    ForEach(lines) { xy in
                        HStack {
                            xy.symbol
                                .frame(width: 10, height: 10)
                                .foregroundColor(xy.lineColor)
                            Text(xy.legendStr).foregroundColor(.black)
                        }
                    }
                }
            }
            .frame(width: 500, height: 500)
            .padding()
        }
    }