swiftui - How can we make the chartXAxis area register taps for chartXSelection in Swift Charts? - Stack Overflow

I'm curious, is there a way to tell the chartXSelection(value:) modifier to include the x-axis are

I'm curious, is there a way to tell the chartXSelection(value:) modifier to include the x-axis area when responding to user gestures? I know that starting a tap in the chart area and dragging down works, but I would like the user to just be able to tap on a value that they like in the x-axis, and have it registered by chartXSelection.

See the example code below (edited to include tall axisMarks to demonstrate a problem with one proposed solution):

private struct PlotPoint {
    let x: Double
    let y: Double
}

private struct PlotSeries: Identifiable {
    let id: Int
    let name: String
    let points: [PlotPoint]
}

private let data: [PlotSeries] = [
    PlotSeries(id: 0, name: "Series A", points: stride(from: 0.0, through: 1.0, by: 0.05).map { y in PlotPoint(x: y*y, y: y) }),
    PlotSeries(id: 1, name: "Series B", points: stride(from: 0.0, through: 1.0, by: 0.05).map { y in PlotPoint(x: y*y, y: y/2) })
]

struct AxisTap: View {
    @State private var selectedX: Double?
    var body: some View {
        VStack {
            Chart {
                if let selectedX {
                    BarMark(xStart: .value("x", selectedX), xEnd: .value("x", selectedX + 0.05))
                        .foregroundStyle(.yellow.opacity(0.5))
                }

                ForEach(data) { series in
                    let plot = LinePlot( series.points, x: .value("x", \.x), y: .value("y", \.y))
                    
                    plot
                        .symbol(by: .value("Series", series.name))
                        .foregroundStyle(by: .value("Series", series.name))
                }
            }
            .chartXAxis {
                AxisMarks() { value in
                    AxisGridLine()
                    AxisTick()
                    AxisValueLabel() {
                        if let axisValue = value.as(Double.self) {
                            VStack {
                                Text("|")
                                Text("v")
                                Text("\(axisValue.formatted())")
                            }
                        }
                    }
                }
            }
            .chartXSelection(value: $selectedX)
            .padding()
            Text("selectedX is: \(selectedX ?? 0.0)")
        }
    }
}

I would like the user to be able to tap on the "0.5" in the x-axis and get a result like shown below (with "selectedX is: ~0.5" below).

I'm curious, is there a way to tell the chartXSelection(value:) modifier to include the x-axis area when responding to user gestures? I know that starting a tap in the chart area and dragging down works, but I would like the user to just be able to tap on a value that they like in the x-axis, and have it registered by chartXSelection.

See the example code below (edited to include tall axisMarks to demonstrate a problem with one proposed solution):

private struct PlotPoint {
    let x: Double
    let y: Double
}

private struct PlotSeries: Identifiable {
    let id: Int
    let name: String
    let points: [PlotPoint]
}

private let data: [PlotSeries] = [
    PlotSeries(id: 0, name: "Series A", points: stride(from: 0.0, through: 1.0, by: 0.05).map { y in PlotPoint(x: y*y, y: y) }),
    PlotSeries(id: 1, name: "Series B", points: stride(from: 0.0, through: 1.0, by: 0.05).map { y in PlotPoint(x: y*y, y: y/2) })
]

struct AxisTap: View {
    @State private var selectedX: Double?
    var body: some View {
        VStack {
            Chart {
                if let selectedX {
                    BarMark(xStart: .value("x", selectedX), xEnd: .value("x", selectedX + 0.05))
                        .foregroundStyle(.yellow.opacity(0.5))
                }

                ForEach(data) { series in
                    let plot = LinePlot( series.points, x: .value("x", \.x), y: .value("y", \.y))
                    
                    plot
                        .symbol(by: .value("Series", series.name))
                        .foregroundStyle(by: .value("Series", series.name))
                }
            }
            .chartXAxis {
                AxisMarks() { value in
                    AxisGridLine()
                    AxisTick()
                    AxisValueLabel() {
                        if let axisValue = value.as(Double.self) {
                            VStack {
                                Text("|")
                                Text("v")
                                Text("\(axisValue.formatted())")
                            }
                        }
                    }
                }
            }
            .chartXSelection(value: $selectedX)
            .padding()
            Text("selectedX is: \(selectedX ?? 0.0)")
        }
    }
}

I would like the user to be able to tap on the "0.5" in the x-axis and get a result like shown below (with "selectedX is: ~0.5" below).

Share edited Mar 8 at 2:50 Curious Je asked Mar 7 at 20:02 Curious JeCurious Je 1,5297 silver badges34 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

A chartGesture would also cover the area where the x axis lies.

.chartGesture { proxy in
    DragGesture(minimumDistance: 0)
        .onChanged { value in
            selectedX = proxy.value(atX: value.location.x)
        }
        .onEnded { _ in
            selectedX = nil
        }
}

Now you don't need .chartXSelection(value: $selectedX) anymore, consider changing selectedX to a @GestureState if you don't need to set it programmatically.

@GestureState private var selectedX: Double?

// ...

.chartGesture { proxy in
    DragGesture(minimumDistance: 0)
        .updating($selectedX) { value, state, _ in
            print(value.location.x)
            state = proxy.value(atX: value.location.x)
        }
}

For a taller x axis, you can add a Rectangle just below the plotFrame rectangle for detecting gestures, in addition to chartGesture or chartXSelection.

.chartOverlay { proxy in
    GeometryReader { geo in
        let frame = geo[proxy.plotFrame!]
        Rectangle()
            .fill(.clear)
            .frame(width: frame.width, height: 50) // choose a desired height here
            .contentShape(.rect)
            .gesture(
                DragGesture(minimumDistance: 0)
                    .updating($selectedX) { value, state, _ in
                        state = proxy.value(atX: value.location.x)
                    }
            )
            .offset(x: frame.minX, y: frame.maxY)
    }
}

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744911070a4600553.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信