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

使用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)
}

问题修复方案

  1. 设置X轴范围,保证数据在X方向合理分布
    尽管隐藏了X轴,但CPTXYPlotSpace需要同时定义X和Y的范围,否则CorePlot会自动调整布局,导致Y轴的波动被压缩。在configureGraphView函数中,设置完yRange后添加X轴范围配置:
// 添加X轴范围设置,覆盖所有数据点
plotSpace.xRange = CPTPlotRange(locationDecimal: CPTDecimalFromDouble(0), lengthDecimal: CPTDecimalFromDouble(Double(plotData.count - 1)))
  1. 处理Y轴范围计算的边界情况
    当所有股价数据波动极小时,yRange会趋近于0,导致yMinAdjusted和yMaxAdjusted计算异常。添加兜底逻辑确保最小范围:
// 确保yRange至少为0.1,避免除以0的情况
let yRange = max(yMax - yMin, 0.1)
  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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 20:42:31