使用CorePlot绘制股票图表时股价显示过于平缓的问题
CorePlot绘制股票图表时股价显示过于平缓的问题
使用CorePlot绘制股票图表时,股价曲线显示过于平缓,已尝试以下操作但均未解决:
- 缩小Y轴范围以凸显小幅价格波动,无效果;
- 确认API返回的数据正常;
- 更换点图、折线图等图表类型,问题依旧。
仅需使用Y轴,无需显示X轴,完整实现代码如下:
ChartViewController.swift
// // ChartViewController.swift // MCOS // // Created by Burhan Cankurt on 01.09.23. // import UIKit import CorePlot class ChartViewController: UIViewController, CPTScatterPlotDataSource, CPTScatterPlotDelegate, CPTAxisDelegate { var ArrayStockClose: [(date: String, close: Double)] = [] @IBOutlet weak var graphHostingView: CPTGraphHostingView! override func viewDidLoad() { super.viewDidLoad() chartViewStockClose(with: .fiveMinute) } func chartViewStockClose(with timeFrame: TimeFrame) { print("chartViewStockClose called") // Debug let fetcher = chartFetcherStockClose() chartLoadStockClose(with: timeFrame, fetcher: fetcher) { [weak self] result in print("chartLoadStockClose completion called") // Debug switch result { case .success(let data): self?.ArrayStockClose = data print("Daten im ViewController: \(data) und zählung \(data.count)") DispatchQueue.main.async { print("initializeGraph called") // Debug // 初始化或更新图表 self?.initializeGraph() } case .failure(let error): print("Error fetching data: \(error)") } } } func initializeGraph() { // 配置图表视图和图形 configureGraphView(for: graphHostingView, plotData: ArrayStockClose, delegate: self) // 配置绘图 configurePlot(for: graphHostingView, dataSource: self, delegate: self) } } // CorePlot数据源与代理扩展 extension ChartViewController { func numberOfRecords(for plot: CPTPlot) -> UInt { print("numberOfRecords called, count: \(ArrayStockClose.count)") return UInt(ArrayStockClose.count) } func number(for plot: CPTPlot, field: UInt, record: UInt) -> Any? { print("number called for field: \(field), record: \(record)") switch CPTScatterPlotField(rawValue: Int(field)) { case .X: return NSNumber(value: record) // 不使用X轴,直接用索引作为X值 case .Y: return NSNumber(value: ArrayStockClose[Int(record)].close) default: return nil } } }
ChartFetcherStockClose.swift
// // ChartFetcherStockClose.swift // MCOS // // Created by Burhan Cankurt on 01.09.23. // import Foundation class chartFetcherStockClose { func fetch(url: URL, completion: @escaping (Result<[(date: String, close: Double)], Error>) -> Void) { let task = URLSession.shared.dataTask(with: url) { (data, response, error) in if let error = error { completion(.failure(error)) return } guard let data = data else { completion(.failure(NSError(domain: "No data", code: 0, userInfo: nil))) return } do { let decoder = JSONDecoder() let stockData = try decoder.decode([StructStockClose].self, from: data) // 按日期排序数据点 let sortedStockData = stockData.sorted { $0.date < $1.date } // 创建结果数组 var resultArray: [(date: String, close: Double)] = [] // 遍历排序后的数据 for data in sortedStockData { resultArray.append((date: data.date, close: data.close)) } // 返回结果 completion(.success(resultArray)) } catch { completion(.failure(error)) } } task.resume() } }// Class End
ChartLoadStockClose.swift
// // ChartLoadStockClose.swift // MCOS // // Created by Burhan Cankurt on 01.09.23. // import Foundation enum TimeFrame { case oneMinute case fiveMinute case fifteenMinute case thirtyMinute case oneHour case fourHour case oneDay } func chartLoadStockClose(with timeFrame: TimeFrame, fetcher: chartFetcherStockClose, completion: @escaping (Result<[(date: String, close: Double)], Error>) -> Void) { let timeFrameParameter: String switch timeFrame { case .oneMinute: timeFrameParameter = "1min" case .fiveMinute: timeFrameParameter = "5min" case .fifteenMinute: timeFrameParameter = "15min" case .thirtyMinute: timeFrameParameter = "30min" case .oneHour: timeFrameParameter = "1hour" case .fourHour: timeFrameParameter = "4hour" case .oneDay: timeFrameParameter = "1day" } let url = URL(string: "https://financialmodelingprep.com/api/v3/historical-chart/\(timeFrameParameter)/AAPL?apikey=BLABLABLA")! fetcher.fetch(url: url) { result in switch result { case .success(let data): // 仅使用最后48个数据点 let filteredData = Array(data.suffix(48)) completion(.success(filteredData)) case .failure(let error): completion(.failure(error)) } } }
ChartConfigureGraphView.swift
// // ChartConfigureGraphView.swift // MCOS // // Created by Burhan Cankurt on 01.09.23. // import CorePlot import UIKit func configureGraphView(for graphView: CPTGraphHostingView, plotData: [(date: String, close: Double)], delegate: CPTAxisDelegate) { print("configureGraphView called") // Debug graphView.allowPinchScaling = false // 配置图形 let graph = CPTXYGraph(frame: graphView.bounds) graph.plotAreaFrame?.masksToBorder = false graphView.hostedGraph = graph graph.backgroundColor = UIColor.black.cgColor graph.paddingBottom = 40.0 graph.paddingLeft = 50.0 graph.paddingTop = 30.0 graph.paddingRight = 5.0 // 设置绘图空间 let yMin = plotData.min(by: { $0.close < $1.close })?.close ?? 0 let yMax = plotData.max(by: { $0.close < $1.close })?.close ?? 0 print("yMin: \(yMin)") // Debug print("yMax: \(yMax)") // Debug let yRange = yMax - yMin let paddingPercentage = 0.05 print("yRange: \(yRange)") // Debug let yMinAdjusted = yMin - (yRange * paddingPercentage) let yMaxAdjusted = yMax + (yRange * paddingPercentage) print("yMinAdjusted: \(yMinAdjusted)") // Debug print("yMaxAdjusted: \(yMaxAdjusted)") // Debug guard let plotSpace = graph.defaultPlotSpace as? CPTXYPlotSpace else { return } plotSpace.yRange = CPTPlotRange(locationDecimal: CPTDecimalFromDouble(yMinAdjusted), lengthDecimal: CPTDecimalFromDouble(yMaxAdjusted - yMinAdjusted)) // 配置坐标轴 let axisSet = graph.axisSet as! CPTXYAxisSet let axisTextStyle = CPTMutableTextStyle() axisTextStyle.color = CPTColor.white() axisTextStyle.fontName = "HelveticaNeue-Bold" axisTextStyle.fontSize = 10.0 axisTextStyle.textAlignment = .center if let x = axisSet.xAxis { x.isHidden = true // 隐藏X轴 } if let y = axisSet.yAxis { print("Configuring yAxis") // Debug y.majorIntervalLength = NSNumber(value: Double((yMaxAdjusted - yMinAdjusted) / 10.0)) // 动态调整间隔 y.minorTicksPerInterval = 5 y.labelTextStyle = axisTextStyle y.axisConstraints = CPTConstraints(lowerOffset: 0.0) y.delegate = delegate } }
Structs.swift
// // Structs.swift // MCOS // // Created by Burhan Cankurt on 01.09.23. // import Foundation struct StructStockClose: Decodable { let close: Double let date: String }
ChartConfigurePlot.swift
// // ChartConfigurePlot.swift // MCOS // // Created by Burhan Cankurt on 01.09.23. // import CorePlot import UIKit func configurePlot(for graphView: CPTGraphHostingView, dataSource: CPTScatterPlotDataSource, delegate: CPTScatterPlotDelegate) { print("configurePlot called") // Debug let plot = CPTScatterPlot() let plotLineStile = CPTMutableLineStyle() plotLineStile.lineJoin = .round plotLineStile.lineCap = .round plotLineStile.lineWidth = 2 plotLineStile.lineColor = CPTColor.white() plot.dataLineStyle = plotLineStile plot.curvedInterpolationOption = .catmullCustomAlpha plot.interpolation = .curved plot.identifier = "coreplot-graph" as NSCoding & NSCopying & NSObjectProtocol guard let graph = graphView.hostedGraph else { return } plot.dataSource = dataSource plot.delegate = delegate graph.add(plot, to: graph.defaultPlotSpace) }
问题修复方案
- 设置X轴范围,保证数据在X方向合理分布
尽管隐藏了X轴,但CPTXYPlotSpace需要同时定义X和Y的范围,否则CorePlot会自动调整布局,导致Y轴的波动被压缩。在configureGraphView函数中,设置完yRange后添加X轴范围配置:
// 添加X轴范围设置,覆盖所有数据点 plotSpace.xRange = CPTPlotRange(locationDecimal: CPTDecimalFromDouble(0), lengthDecimal: CPTDecimalFromDouble(Double(plotData.count - 1)))
- 处理Y轴范围计算的边界情况
当所有股价数据波动极小时,yRange会趋近于0,导致yMinAdjusted和yMaxAdjusted计算异常。添加兜底逻辑确保最小范围:
// 确保yRange至少为0.1,避免除以0的情况 let yRange = max(yMax - yMin, 0.1)
- 优化图表内边距,释放显示空间
当前设置的graph.paddingLeft = 50.0等内边距可能过度压缩图表区域,可根据实际需求调整或减少不必要的内边距,让Y轴有足够空间展示波动:
// 调整内边距,示例值可根据UI需求修改 graph.paddingBottom = 20.0 graph.paddingLeft = 30.0 graph.paddingTop = 20.0 graph.paddingRight = 20.0
内容的提问来源于stack exchange,提问作者noSkill06s
相关产品推荐
相关产品推荐

