iosswiftswiftuiswiftcharts

SwiftUI charts - only show RuleMark below plotted line


I am having some issues with Swift charts. I have the following Chart which has RuleMark. As you can see from the screenshot below the RuleMark extends the length of the screen. I am attempting to only show the mark below the line. Something like this question is asking. If any further clarity is required let me know. The code is below.

enter image description here

struct Item: Identifiable {
    var date: String
    var count: Double
    var id = UUID()
}

var data: [Item] = [
    .init(date: "10AM", count: 5),
    .init(date: "12AM", count: 4),
    .init(date: "2PM", count: 7),
    .init(date: "4PM", count: 2),
    .init(date: "6PM", count: 9)
]

struct ContentView: View {
    var body: some View {
        Chart(data) { item in    
            LineMark(
                x: .value("Date", item.date),
                y: .value("Value", item.count)
            )
            
            PointMark(
                x: .value("Date", item.date),
                y: .value("Value", item.count)
            )
            
            RuleMark(
                x: .value("Value", item.date)
            )
        }
        .chartYAxis(.hidden)
    }
}

Solution

  • Assuming the y axis value of the bottom of the plot is known, you can do:

    RuleMark(
        x: .value("Date", item.date),
        yStart: .value("Bottom", 0), // assuming y=0 at the bottom of the chart
        yEnd: .value("Value", item.count)
    )
    

    Replace 0 with whatever value the y axis has at the very bottom. Note that you can change the y axis' range using the various chartYScale modifiers.

    If the y value of the bottom of the plot is not known (e.g. you are letting SwiftUI automatically decide what scale to use depending on the data), you can use chartBackground/chartOverlay to draw the lines with a Path:

    .chartBackground { proxy in
        if let frameAnchor = proxy.plotFrame {
            GeometryReader { geo in
                let frame = geo[frameAnchor]
                ForEach(data) { item in
                    if let point = proxy.position(for: (x: item.date, y: item.count)) {
                        Path { p in
                            p.move(to: .init(x: point.x + frame.minX, y: point.y + frame.minY))
                            p.addLine(to: .init(x: point.x + frame.minX, y: frame.maxY))
                        }
                        .stroke(.blue, lineWidth: 1)
                    }
                }
            }
        }
    }