You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI折线图RuleMark偏移及X轴HH:mm格式设置求助

SwiftUI折线图问题修复方案

问题1:RuleMark过度左右偏移修复

当前代码中proxy.value(at: location)会返回图表范围内任意位置的插值日期,包括数据点之外的区域,导致RuleMark可拖拽至数据范围外出现偏移。解决方案是匹配数据集内最接近的时间点,而非任意插值日期:

在DragGesture的onChanged中替换原有逻辑:

onChanged { value in
    let origin = geometry[proxy.plotAreaFrame].origin
    let location = CGPoint(
        x: value.location.x - origin.x,
        y: value.location.y - origin.y
    )
    
    if let (date, _) = proxy.value(at: location, as: (Date, Double).self) {
        // 找到最接近当前位置的数据集时间点
        let closestData = stockDatas.min {
            abs($0.timestamp.timeIntervalSince(date)) < abs($1.timestamp.timeIntervalSince(date))
        }
        selectedDate = closestData?.timestamp
        selectedClose = closestData?.close
    }
}

同时可通过限制X轴范围进一步避免偏移,为图表添加chartXScale:

.chartXScale(domain: stockDatas.map(\.timestamp).min()!...stockDatas.map(\.timestamp).max()!)

问题2:X轴日期格式设置为HH:mm且仅显示传入的时间数据

修改chartXAxis配置,指定轴标记仅使用数据中的时间点,并设置格式为小时分钟:

.chartXAxis {
    AxisMarks(values: stockDatas.map(\.timestamp)) { value in
        AxisGridLine()
        AxisTick()
        AxisLabel(
            content: {
                if let date = value.as(Date.self) {
                    Text(date, format: .dateTime.hour().minute())
                }
            },
            position: .bottom
        )
    }
}

完整修改后的ContentView代码

struct ContentView: View {
    
    @State var stockDatas: [StockPriceData]  = stockData
    @State private var selectedDate: Date?
    @State private var selectedClose: Double?
    
    var body: some View {
        
        ChartView
            .chartYScale(domain: stockDatas.map {$0.close}.min()!...stockDatas.map{$0.close}.max()!)
            .chartXScale(domain: stockDatas.map(\.timestamp).min()!...stockDatas.map(\.timestamp).max()!)
            .chartXAxis {
                AxisMarks(values: stockDatas.map(\.timestamp)) { value in
                    AxisGridLine()
                    AxisTick()
                    AxisLabel(
                        content: {
                            if let date = value.as(Date.self) {
                                Text(date, format: .dateTime.hour().minute())
                            }
                        },
                        position: .bottom
                    )
                }
            }
    }
    
    private var ChartView: some View {
        
        Chart {
            ForEach(stockDatas) { data in
                LineMark(x: .value("time", data.timestamp),
                         y: .value("price", data.close))
                .foregroundStyle(Color.green)
                
                AreaMark(x: .value("time", data.timestamp),
                         yStart: .value("Min", stockDatas.map{$0.close}.min()!),
                         yEnd: .value("Max", data.close)
                ).foregroundStyle(LinearGradient(gradient: Gradient(colors: [Color.green, .clear]), startPoint: .top, endPoint: .bottom)).opacity(0.1)
            }
            
            if let selectedDate = selectedDate, let selectedClose = selectedClose {
                RuleMark(x: .value("Selected Date", selectedDate))
                    .foregroundStyle(.gray)
                    .annotation(position: .top, alignment: .top) {
                        VStack {
                            Text(String(format: "Close: %.5f", selectedClose))
                                .font(.system(size: 12))
                        }
                        .background(RoundedRectangle(cornerRadius: 5).fill(Color.gray))
                        .foregroundColor(Color.white)
                    }
            }
        }
        .frame(width: UIScreen.main.bounds.width-23, height: 200)
        .chartOverlay { proxy in
            GeometryReader { geometry in
                Rectangle().fill(.clear).contentShape(Rectangle())
                    .gesture(
                        DragGesture()
                            .onChanged { value in
                                let origin = geometry[proxy.plotAreaFrame].origin
                                let location = CGPoint(
                                    x: value.location.x - origin.x,
                                    y: value.location.y - origin.y
                                )
                                
                                if let (date, _) = proxy.value(at: location, as: (Date, Double).self) {
                                    let closestData = stockDatas.min {
                                        abs($0.timestamp.timeIntervalSince(date)) < abs($1.timestamp.timeIntervalSince(date))
                                    }
                                    selectedDate = closestData?.timestamp
                                    selectedClose = closestData?.close
                                }
                            }
                    )
            }
        }
    }
}

额外说明

  • RuleMark偏移修复:通过匹配数据集内的实际时间点,确保RuleMark仅在数据范围内的点上显示,避免过度偏移。
  • X轴格式优化:指定values为数据的时间数组,确保仅显示传入的时间点,同时通过format: .dateTime.hour().minute()将日期转为HH:mm格式。

内容的提问来源于stack exchange,提问作者user20166709

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 09:02:05