<commit_before>//
//  HSKLineNew.swift
//  HSStockChartDemo
//
//  Created by Hanson on 2017/2/16.
//  Copyright © 2017年 hanson. All rights reserved.
//

import UIKit

class HSCAShapeLayer: CAShapeLayer {
    
    // 关闭 CAShapeLayer 的隐式动画，避免滑动时候或者十字线出现时有残影的现象(实际上是因为 Layer 的 position 属性变化而产生的隐式动画)
    override func action(forKey event: String) -> CAAction? {
        return nil
    }
}


public class HSKLine: UIView, HSDrawLayerProtocol {

    var kLineType: HSChartType = HSChartType.kLineForDay
    var theme = HSKLineStyle()
    
    var dataK: [HSKLineModel] = []
    var positionModels: [HSKLineCoordModel] = []
    var klineModels: [HSKLineModel] = []
    
    var kLineViewTotalWidth: CGFloat = 0
    var showContentWidth: CGFloat = 0
    var contentOffsetX: CGFloat = 0
    var highLightIndex: Int = 0
    
    var maxPrice: CGFloat = 0
    var minPrice: CGFloat = 0
    var maxVolume: CGFloat = 0
    var maxMA: CGFloat = 0
    var minMA: CGFloat = 0
    var maxMACD: CGFloat = 0
    
    var priceUnit: CGFloat = 0.1
    var volumeUnit: CGFloat = 0
    
    var renderRect: CGRect = CGRect.zero
    var renderWidth: CGFloat = 0
    
    var candleChartLayer = HSCAShapeLayer()
    var volumeLayer = HSCAShapeLayer()
    var ma5LineLayer = HSCAShapeLayer()
    var ma10LineLayer = HSCAShapeLayer()
    var ma20LineLayer = HSCAShapeLayer()
    var xAxisTimeMarkLayer = HSCAShapeLayer()
    
    var uperChartHeight: CGFloat {
        get {
            return theme.uperChartHeightScale * self.frame.height
        }
    }
    var lowerChartHeight: CGFloat {
        get {
            return self.frame.height * (1 - theme.uperChartHeightScale) - theme.xAxisHeitht
        }
    }
    
    // 计算处于当前显示区域左边隐藏的蜡烛图的个数，即为当前显示的初始 index
    var startIndex: Int {
        get {
            let scrollViewOffsetX = contentOffsetX < 0 ? 0 : contentOffsetX
            var leftCandleCount = Int(abs(scrollViewOffsetX) / (theme.candleWidth + theme.candleGap))
            
            if leftCandleCount > dataK.count {
                leftCandleCount = dataK.count - 1
                return leftCandleCount
            } else if leftCandleCount == 0 {
                return leftCandleCount
            } else {
                return leftCandleCount + 1
            }
        }
    }
    
    // 当前显示区域起始横坐标 x
    var startX: CGFloat {
        get {
            let scrollViewOffsetX = contentOffsetX < 0 ? 0 : contentOffsetX
            return scrollViewOffsetX
        }
    }
    
    // 当前显示区域最多显示的蜡烛图个数
    var countOfshowCandle: Int {
        get{
            return Int((renderWidth - theme.candleWidth) / ( theme.candleWidth + theme.candleGap))
        }
    }
    
    
    // MARK: - Initialize
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.clear
    }
    
    required public init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    // MARK: - Drawing Function
    
    func drawKLineView() {
        calcMaxAndMinData()
        convertToPositionModel(data: dataK)

        clearLayer()
        drawxAxisTimeMarkLayer()
        drawCandleChartLayer(array: positionModels)
        drawVolumeLayer(array: positionModels)
        drawMALayer(array: positionModels)
    }
    
    /// 计算当前显示区域的最大最小值
    fileprivate func calcMaxAndMinData() {
        if dataK.count > 0 {
            self.maxPrice = CGFloat.leastNormalMagnitude
            self.minPrice = CGFloat.greatestFiniteMagnitude
            self.maxVolume = CGFloat.leastNormalMagnitude
            self.maxMA = CGFloat.leastNormalMagnitude
            self.minMA = CGFloat.greatestFiniteMagnitude
            self.maxMACD = CGFloat.leastNormalMagnitude
            let startIndex = self.startIndex
            // 比计算出来的多加一个，是为了避免计算结果的取整导致少画
            let count = (startIndex + countOfshowCandle + 1) > dataK.count ? dataK.count : (startIndex + countOfshowCandle + 1)
            if startIndex < count {
                for i in startIndex ..< count {
                    let entity = dataK[i]
                    self.maxPrice = self.maxPrice > entity.high ? self.maxPrice : entity.high
                    self.minPrice = self.minPrice < entity.low ? self.minPrice : entity.low
                    
                    self.maxVolume = self.maxVolume > entity.volume ? self.maxVolume : entity.volume
                    
                    let tempMAMax = max(entity.ma5, entity.ma10, entity.ma20)
                    self.maxMA = self.maxMA > tempMAMax ? self.maxMA : tempMAMax
                    
                    let tempMAMin = min(entity.ma5, entity.ma10, entity.ma20)
                    self.minMA = self.minMA < tempMAMin ? self.minMA : tempMAMin
                    
                    let tempMax = max(abs(entity.diff), abs(entity.dea), abs(entity.macd))
                    self.maxMACD = tempMax > self.maxMACD ? tempMax : self.maxMACD
                }
            }
            // 当均线数据缺失时候，注意注释这段，不然 minPrice 为 0，导致整体绘画比例不对
            self.maxPrice = self.maxPrice > self.maxMA ? self.maxPrice : self.maxMA
            self.minPrice = self.minPrice < self.minMA ? self.minPrice : self.minMA
        }
    }
    
    
    /// 转换为坐标 model
    ///
    /// - Parameter data: [HSKLineModel]
    fileprivate func convertToPositionModel(data: [HSKLineModel]) {
        self.positionModels.removeAll()
        self.klineModels.removeAll()
        
        let axisGap = countOfshowCandle / 3
        let gap = theme.viewMinYGap
        let minY = gap
        let maxDiff = self.maxPrice - self.minPrice
        if maxDiff > 0, maxVolume > 0 {
            priceUnit = (uperChartHeight - 2 * minY) / maxDiff
            volumeUnit = (lowerChartHeight - theme.volumeGap) / self.maxVolume
        }
        let count = (startIndex + countOfshowCandle + 1) > data.count ? data.count : (startIndex + countOfshowCandle + 1)
        if startIndex < count {
            for index in startIndex ..< count {
                let model = data[index]
                let leftPosition = startX + CGFloat(index - startIndex) * (theme.candleWidth + theme.candleGap)
                let xPosition = leftPosition + theme.candleWidth / 2.0
                
                let highPoint = CGPoint(x: xPosition, y: (maxPrice - model.high) * priceUnit + minY)
                let lowPoint = CGPoint(x: xPosition, y: (maxPrice - model.low) * priceUnit + minY)
                
                let ma5Point = CGPoint(x: xPosition, y: (maxPrice - model.ma5) * priceUnit + minY)
                let ma10Point = CGPoint(x: xPosition, y: (maxPrice - model.ma10) * priceUnit + minY)
                let ma20Point = CGPoint(x: xPosition, y: (maxPrice - model.ma20) * priceUnit + minY)
                
                let openPointY = (maxPrice - model.open) * priceUnit + minY
                let closePointY = (maxPrice - model.close) * priceUnit + minY
                var fillCandleColor = UIColor.black
                var candleRect = CGRect.zero
                
                let volume = (model.volume - 0) * volumeUnit
                let volumeStartPoint = CGPoint(x: xPosition, y: self.frame.height - volume)
                let volumeEndPoint = CGPoint(x: xPosition, y: self.frame.height)
                
                if(openPointY > closePointY) {
                    fillCandleColor = theme.riseColor
                    candleRect = CGRect(x: leftPosition, y: closePointY, width: theme.candleWidth, height: openPointY - closePointY)
                    
                } else if(openPointY < closePointY) {
                    fillCandleColor = theme.fallColor
                    candleRect = CGRect(x: leftPosition, y: openPointY, width: theme.candleWidth, height: closePointY - openPointY)
                    
                } else {
                    candleRect = CGRect(x: leftPosition, y: closePointY, width: theme.candleWidth, height: theme.candleMinHeight)
                    if(index > 0) {
                        let preKLineModel = data[index - 1]
                        if(model.open > preKLineModel.close) {
                            fillCandleColor = theme.riseColor
                        } else {
                            fillCandleColor = theme.fallColor
                        }
                    }
                }
                
                let positionModel = HSKLineCoordModel()
                positionModel.highPoint = highPoint
                positionModel.lowPoint = lowPoint
                positionModel.closeY = closePointY
                positionModel.ma5Point = ma5Point
                positionModel.ma10Point = ma10Point
                positionModel.ma20Point = ma20Point
                positionModel.volumeStartPoint = volumeStartPoint
                positionModel.volumeEndPoint = volumeEndPoint
                positionModel.candleFillColor = fillCandleColor
                positionModel.candleRect = candleRect
                if index % axisGap == 0 {
                    positionModel.isDrawAxis = true
                }
                self.positionModels.append(positionModel)
                self.klineModels.append(model)
            }
        }
    }
    
    /// 画蜡烛图
    func drawCandleChartLayer(array: [HSKLineCoordModel]) {
        candleChartLayer.sublayers?.removeAll()
        for object in array.enumerated() {
            let candleLayer = getCandleLayer(model: object.element)
            candleChartLayer.addSublayer(candleLayer)
        }
        self.layer.addSublayer(candleChartLayer)
    }
    
    /// 画交易量图
    func drawVolumeLayer(array: [HSKLineCoordModel]) {
        volumeLayer.sublayers?.removeAll()
        for object in array.enumerated() {
            let model = object.element
            let volLayer = drawLine(lineWidth: theme.candleWidth,
                                    startPoint: model.volumeStartPoint,
                                    endPoint: model.volumeEndPoint,
                                    strokeColor: model.candleFillColor,
                                    fillColor: model.candleFillColor)
            volumeLayer.addSublayer(volLayer)
        }
        self.layer.addSublayer(volumeLayer)
    }
    
    /// 画交均线图
    func drawMALayer(array: [HSKLineCoordModel]) {
        let ma5LinePath = UIBezierPath()
        let ma10LinePath = UIBezierPath()
        let ma20LinePath = UIBezierPath()
        for index in 1 ..< array.count {
            let preMa5Point = array[index - 1].ma5Point<commit_msg>Do not draw MALayer when data point is zero<commit_after>//
//  HSKLineNew.swift
//  HSStockChartDemo
//
//  Created by Hanson on 2017/2/16.
//  Copyright © 2017年 hanson. All rights reserved.
//

import UIKit

class HSCAShapeLayer: CAShapeLayer {
    
    // 关闭 CAShapeLayer 的隐式动画，避免滑动时候或者十字线出现时有残影的现象(实际上是因为 Layer 的 position 属性变化而产生的隐式动画)
    override func action(forKey event: String) -> CAAction? {
        return nil
    }
}


public class HSKLine: UIView, HSDrawLayerProtocol {

    var kLineType: HSChartType = HSChartType.kLineForDay
    var theme = HSKLineStyle()
    
    var dataK: [HSKLineModel] = []
    var positionModels: [HSKLineCoordModel] = []
    var klineModels: [HSKLineModel] = []
    
    var kLineViewTotalWidth: CGFloat = 0
    var showContentWidth: CGFloat = 0
    var contentOffsetX: CGFloat = 0
    var highLightIndex: Int = 0
    
    var maxPrice: CGFloat = 0
    var minPrice: CGFloat = 0
    var maxVolume: CGFloat = 0
    var maxMA: CGFloat = 0
    var minMA: CGFloat = 0
    var maxMACD: CGFloat = 0
    
    var priceUnit: CGFloat = 0.1
    var volumeUnit: CGFloat = 0
    
    var renderRect: CGRect = CGRect.zero
    var renderWidth: CGFloat = 0
    
    var candleChartLayer = HSCAShapeLayer()
    var volumeLayer = HSCAShapeLayer()
    var ma5LineLayer = HSCAShapeLayer()
    var ma10LineLayer = HSCAShapeLayer()
    var ma20LineLayer = HSCAShapeLayer()
    var xAxisTimeMarkLayer = HSCAShapeLayer()
    
    var uperChartHeight: CGFloat {
        get {
            return theme.uperChartHeightScale * self.frame.height
        }
    }
    var lowerChartHeight: CGFloat {
        get {
            return self.frame.height * (1 - theme.uperChartHeightScale) - theme.xAxisHeitht
        }
    }
    
    // 计算处于当前显示区域左边隐藏的蜡烛图的个数，即为当前显示的初始 index
    var startIndex: Int {
        get {
            let scrollViewOffsetX = contentOffsetX < 0 ? 0 : contentOffsetX
            var leftCandleCount = Int(abs(scrollViewOffsetX) / (theme.candleWidth + theme.candleGap))
            
            if leftCandleCount > dataK.count {
                leftCandleCount = dataK.count - 1
                return leftCandleCount
            } else if leftCandleCount == 0 {
                return leftCandleCount
            } else {
                return leftCandleCount + 1
            }
        }
    }
    
    // 当前显示区域起始横坐标 x
    var startX: CGFloat {
        get {
            let scrollViewOffsetX = contentOffsetX < 0 ? 0 : contentOffsetX
            return scrollViewOffsetX
        }
    }
    
    // 当前显示区域最多显示的蜡烛图个数
    var countOfshowCandle: Int {
        get{
            return Int((renderWidth - theme.candleWidth) / ( theme.candleWidth + theme.candleGap))
        }
    }
    
    
    // MARK: - Initialize
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        backgroundColor = UIColor.clear
    }
    
    required public init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    
    // MARK: - Drawing Function
    
    func drawKLineView() {
        calcMaxAndMinData()
        convertToPositionModel(data: dataK)

        clearLayer()
        drawxAxisTimeMarkLayer()
        drawCandleChartLayer(array: positionModels)
        drawVolumeLayer(array: positionModels)
        drawMALayer(array: positionModels)
    }
    
    /// 计算当前显示区域的最大最小值
    fileprivate func calcMaxAndMinData() {
        if dataK.count > 0 {
            self.maxPrice = CGFloat.leastNormalMagnitude
            self.minPrice = CGFloat.greatestFiniteMagnitude
            self.maxVolume = CGFloat.leastNormalMagnitude
            self.maxMA = CGFloat.leastNormalMagnitude
            self.minMA = CGFloat.greatestFiniteMagnitude
            self.maxMACD = CGFloat.leastNormalMagnitude
            let startIndex = self.startIndex
            // 比计算出来的多加一个，是为了避免计算结果的取整导致少画
            let count = (startIndex + countOfshowCandle + 1) > dataK.count ? dataK.count : (startIndex + countOfshowCandle + 1)
            if startIndex < count {
                for i in startIndex ..< count {
                    let entity = dataK[i]
                    self.maxPrice = self.maxPrice > entity.high ? self.maxPrice : entity.high
                    self.minPrice = self.minPrice < entity.low ? self.minPrice : entity.low
                    
                    self.maxVolume = self.maxVolume > entity.volume ? self.maxVolume : entity.volume
                    
                    let tempMAMax = max(entity.ma5, entity.ma10, entity.ma20)
                    self.maxMA = self.maxMA > tempMAMax ? self.maxMA : tempMAMax
                    
                    let tempMAMin = min(entity.ma5, entity.ma10, entity.ma20)
                    self.minMA = self.minMA < tempMAMin ? self.minMA : tempMAMin
                    
                    let tempMax = max(abs(entity.diff), abs(entity.dea), abs(entity.macd))
                    self.maxMACD = tempMax > self.maxMACD ? tempMax : self.maxMACD
                }
            }
            // 当均线数据缺失时候，注意注释这段，不然 minPrice 为 0，导致整体绘画比例不对
            self.maxPrice = self.maxPrice > self.maxMA ? self.maxPrice : self.maxMA
            self.minPrice = self.minPrice < self.minMA ? self.minPrice : self.minMA
        }
    }
    
    
    /// 转换为坐标 model
    ///
    /// - Parameter data: [HSKLineModel]
    fileprivate func convertToPositionModel(data: [HSKLineModel]) {
        self.positionModels.removeAll()
        self.klineModels.removeAll()
        
        let axisGap = countOfshowCandle / 3
        let gap = theme.viewMinYGap
        let minY = gap
        let maxDiff = self.maxPrice - self.minPrice
        if maxDiff > 0, maxVolume > 0 {
            priceUnit = (uperChartHeight - 2 * minY) / maxDiff
            volumeUnit = (lowerChartHeight - theme.volumeGap) / self.maxVolume
        }
        let count = (startIndex + countOfshowCandle + 1) > data.count ? data.count : (startIndex + countOfshowCandle + 1)
        if startIndex < count {
            for index in startIndex ..< count {
                let model = data[index]
                let leftPosition = startX + CGFloat(index - startIndex) * (theme.candleWidth + theme.candleGap)
                let xPosition = leftPosition + theme.candleWidth / 2.0
                
                let highPoint = CGPoint(x: xPosition, y: (maxPrice - model.high) * priceUnit + minY)
                let lowPoint = CGPoint(x: xPosition, y: (maxPrice - model.low) * priceUnit + minY)
                
                let ma5Point = CGPoint(x: xPosition, y: (maxPrice - model.ma5) * priceUnit + minY)
                let ma10Point = CGPoint(x: xPosition, y: (maxPrice - model.ma10) * priceUnit + minY)
                let ma20Point = CGPoint(x: xPosition, y: (maxPrice - model.ma20) * priceUnit + minY)
                
                let openPointY = (maxPrice - model.open) * priceUnit + minY
                let closePointY = (maxPrice - model.close) * priceUnit + minY
                var fillCandleColor = UIColor.black
                var candleRect = CGRect.zero
                
                let volume = (model.volume - 0) * volumeUnit
                let volumeStartPoint = CGPoint(x: xPosition, y: self.frame.height - volume)
                let volumeEndPoint = CGPoint(x: xPosition, y: self.frame.height)
                
                if(openPointY > closePointY) {
                    fillCandleColor = theme.riseColor
                    candleRect = CGRect(x: leftPosition, y: closePointY, width: theme.candleWidth, height: openPointY - closePointY)
                    
                } else if(openPointY < closePointY) {
                    fillCandleColor = theme.fallColor
                    candleRect = CGRect(x: leftPosition, y: openPointY, width: theme.candleWidth, height: closePointY - openPointY)
                    
                } else {
                    candleRect = CGRect(x: leftPosition, y: closePointY, width: theme.candleWidth, height: theme.candleMinHeight)
                    if(index > 0) {
                        let preKLineModel = data[index - 1]
                        if(model.open > preKLineModel.close) {
                            fillCandleColor = theme.riseColor
                        } else {
                            fillCandleColor = theme.fallColor
                        }
                    }
                }
                
                let positionModel = HSKLineCoordModel()
                positionModel.highPoint = highPoint
                positionModel.lowPoint = lowPoint
                positionModel.closeY = closePointY
                positionModel.ma5Point = ma5Point
                positionModel.ma10Point = ma10Point
                positionModel.ma20Point = ma20Point
                positionModel.volumeStartPoint = volumeStartPoint
                positionModel.volumeEndPoint = volumeEndPoint
                positionModel.candleFillColor = fillCandleColor
                positionModel.candleRect = candleRect
                if index % axisGap == 0 {
                    positionModel.isDrawAxis = true
                }
                self.positionModels.append(positionModel)
                self.klineModels.append(model)
            }
        }
    }
    
    /// 画蜡烛图
    func drawCandleChartLayer(array: [HSKLineCoordModel]) {
        candleChartLayer.sublayers?.removeAll()
        for object in array.enumerated() {
            let candleLayer = getCandleLayer(model: object.element)
            candleChartLayer.addSublayer(candleLayer)
        }
        self.layer.addSublayer(candleChartLayer)
    }
    
    /// 画交易量图
    func drawVolumeLayer(array: [HSKLineCoordModel]) {
        volumeLayer.sublayers?.removeAll()
        for object in array.enumerated() {
            let model = object.element
            let volLayer = drawLine(lineWidth: theme.candleWidth,
                                    startPoint: model.volumeStartPoint,
                                    endPoint: model.volumeEndPoint,
                                    strokeColor: model.candleFillColor,
                                    fillColor: model.candleFillColor)
            volumeLayer.addSublayer(volLayer)
        }
        self.layer.addSublayer(volumeLayer)
    }
    
    /// 画交均线图
    func drawMALayer(array: [HSKLineCoordModel]) {
        
        if(array.count == 0)
        {
            return
        }
        
        let ma5LinePath = UIBezierPath()
        let ma10LinePath = UIBezierPath()
        let ma20LinePath = UIBezierPath()
        for index in 1 ..< array.count {
            let preMa5Point = array[index - 1].ma5Point
{'fraction_non_alphanumeric': 0.0790691087613293, 'fraction_numerical': 0.008827416918429002, 'mean_word_length': 1.672848851879889, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 33, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6067422', 'n_tokens_mistral': 6583, 'n_tokens_neox': 6392, 'n_words': 1294}