美文网首页
K线开发之K线整体搭建

K线开发之K线整体搭建

作者: nethanhan | 来源:发表于2017-10-14 14:23 被阅读0次

    前言

    当写完了所有需要使用的素材类后,我们开始搭建一个比较完整的K线Demo。

    它包含以下功能:

    1、可以展示蜡烛图
    2、可以展示OHLC图
    3、可以左右滑动
    4、可以长按出现十字叉
    5、有基本的价格和日期区间展示

    GO

    在上几篇文章中,我们已经知道如何绘制蜡烛、边框、OHLC等。所以在这里,可以直接使用已经写好的类。

    绘制边框

    首页,我们先绘制一个包含主副图的边框:

    /**
     绘制边框
     */
    - (void)drawBorder
    {
        //设置主图、主图指标、副图、副图指标rect
        _mainIndexRect = CGRectMake(0, 0, CGRectGetWidth(self.frame), mainIndexH);
        _mainRect = CGRectMake(0, mainIndexH, CGRectGetWidth(self.frame), (CGRectGetHeight(self.frame) - (mainIndexH + accessoryIndexH + dateH)) * mainFrameScale);
        _accessoryIndexRect = CGRectMake(0, mainIndexH + CGRectGetHeight(_mainRect)+dateH, CGRectGetWidth(self.frame), accessoryIndexH);
        _accessoryRect = CGRectMake(0, mainIndexH + CGRectGetHeight(_mainRect)+dateH+accessoryIndexH, CGRectGetWidth(self.frame), (CGRectGetHeight(self.frame) - (mainIndexH + accessoryIndexH + dateH)) * (1-mainFrameScale));
        
        
        CAShapeLayer *borderLayer = [CAShapeLayer layer];
        UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.bounds];
        
        [path moveToPoint:CGPointMake(0, mainIndexH)];
        [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), mainIndexH)];
        
        [path moveToPoint:CGPointMake(0, CGRectGetMaxY(_mainRect))];
        [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetMaxY(_mainRect))];
        
        [path moveToPoint:CGPointMake(0, CGRectGetMinY(_accessoryIndexRect))];
        [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetMinY(_accessoryIndexRect))];
        
        [path moveToPoint:CGPointMake(0, CGRectGetMinY(_accessoryRect))];
        [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetMinY(_accessoryRect))];
        
        float mainUnitH = CGRectGetHeight(_mainRect) / 4.f;
        float mainUnitW = CGRectGetWidth(_mainRect) / 4.f;
        
        for (int idx = 1; idx <= 3; idx++)
        {
            //画3条横线
            [path moveToPoint:CGPointMake(0, mainIndexH + mainUnitH * idx)];
            [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), mainIndexH + mainUnitH * idx)];
            
            //画3条竖线
            [path moveToPoint:CGPointMake(idx * mainUnitW, mainIndexH)];
            [path addLineToPoint:CGPointMake(idx * mainUnitW, CGRectGetMaxY(_mainRect))];
            
            //画3条竖线
            [path moveToPoint:CGPointMake(idx * mainUnitW, CGRectGetMinY(_accessoryRect))];
            [path addLineToPoint:CGPointMake(idx * mainUnitW, CGRectGetMaxY(_accessoryRect))];
        }
    
        float accessoryUnitH = CGRectGetHeight(_accessoryRect) / 2.f;
        [path moveToPoint:CGPointMake(0, CGRectGetMaxY(_accessoryRect) - accessoryUnitH)];
        [path addLineToPoint:CGPointMake(CGRectGetWidth(self.frame), CGRectGetMaxY(_accessoryRect) - accessoryUnitH)];
        
        borderLayer.path = path.CGPath;
        borderLayer.lineWidth = 0.5f;
        borderLayer.strokeColor = [UIColor colorWithRed:222.f/255.f green:222.f/255.f blue:222.f/255.f alpha:1.f].CGColor;
        borderLayer.fillColor = [UIColor clearColor].CGColor;
        
        [self.layer addSublayer:borderLayer];
        
    }
    

    效果如下:


    边框

    导入数据

    边框绘制完以后,我们把k线数据转换为坐标点。在转换之前,还需要寻找当前在屏幕上展示数据的极限值,也就是最大最小值。

    这里要注意,因为当前屏幕中一般最多显示60个左右的蜡烛数量,但是某一个周期K线的蜡烛数量一般是几百个,所以在K线数据转为模型数据后,需要有一个起始索引来标识当前展示的数据范围。

    寻找极限值:

    //求出最大最小值
        _min = (float)INT32_MAX;
        _max = (float)INT32_MIN;
        for (int idx=_startIndex; idx<_endIndex; idx++)
        {
            YKKLineModel *model = self.kLineModelArr[idx];
            if (_min > model.low)
            {
                _min = model.low;
            }
            if (_max < model.high)
            {
                _max = model.high;
            }
        }
    

    把数据转换为坐标点:

        [self.displayPointArr removeAllObjects];
        //每根蜡烛的宽度
        float candleW = CGRectGetWidth(_mainRect) / candleCount;
        for (int idx = _startIndex; idx<_endIndex; idx++)
        {
            YKKLineModel *model = self.kLineModelArr[idx];
            float x = CGRectGetMinX(_mainRect) + candleW * (idx - (_startIndex - 0));
         
            CGPoint hPoint = CGPointMake(x + candleW/2,
                                         ABS(CGRectGetMaxY(_mainRect) - (model.high  - _min)/unitValue));
            CGPoint lPoint = CGPointMake(x + candleW/2,
                                         ABS(CGRectGetMaxY(_mainRect) - (model.low   - _min)/unitValue));
            CGPoint oPoint = CGPointMake(x + candleW/2,
                                         ABS(CGRectGetMaxY(_mainRect) - (model.open  - _min)/unitValue));
            CGPoint cPoint = CGPointMake(x + candleW/2,
                                         ABS(CGRectGetMaxY(_mainRect) - (model.close - _min)/unitValue));
            [_displayPointArr addObject:[YKCandlePointModel candlePointModelWithOpoint:oPoint
                                                                                Hpoint:hPoint
                                                                                Lpoint:lPoint
                                                                                Cpoint:cPoint]];
        }
    

    绘制蜡烛/OHLC

    导入数据并且转换为坐标点以后,接下来开始绘制。

    绘制蜡烛:

        //每根蜡烛的宽度
        float candleW = CGRectGetWidth(_mainRect) / candleCount;
        
        for (int idx = 0; idx< candleCount; idx++)
        {
            YKCandlePointModel *model = pointModelArr[idx];
            CAShapeLayer *layer = [CAShapeLayer getCandleLayerWithPointModel:model candleW:candleW];
            
            [self.candleLayer addSublayer:layer];
        }
        
        [self.layer addSublayer:self.candleLayer];
    

    绘制OHLC:

        //每根OHLC的宽度
        float candleW = CGRectGetWidth(_mainRect) / candleCount;
        
        for (int idx = 0; idx< candleCount; idx++)
        {
            YKCandlePointModel *model = pointModelArr[idx];
            CAShapeLayer *layer = [CAShapeLayer getOHLCLayerWithPointModel:model candleW:candleW];
            
            [self.ohlcLayer addSublayer:layer];
        }
        
        [self.layer addSublayer:self.ohlcLayer];
    

    效果如下:

    蜡烛 OHLC

    绘制价格、日期区间

    蜡烛绘制完以后,我们还剩下价格、日期。价格是指左侧的5个价格的区间标识,从上往下降序排列;日期为下方5个日期的区间标识,每一个标识都是距离当前点最近的点的日期。

    绘制左侧价格:

        float unitPrice = (_max - _min) / 4.f;
        float unitH = CGRectGetHeight(_mainRect) / 4.f;
        
        //求得价格rect
        NSDictionary *attribute = @{NSFontAttributeName:[UIFont systemFontOfSize:9.f]};
        CGRect priceRect = [self rectOfNSString:[NSString stringWithFormat:@"%.2f", _max] attribute:attribute];
        
        for (int idx = 0; idx < 5; idx++)
        {
            float height = 0.f;
            if (idx == 4)
            {
                height = idx * unitH - CGRectGetHeight(priceRect);
            } else
            {
                height = idx * unitH;
            }
            CGRect rect = CGRectMake(CGRectGetMinX(_mainRect),
                                     CGRectGetMinY(_mainRect) + height,
                                     CGRectGetWidth(priceRect),
                                     CGRectGetHeight(priceRect));
            //计算价格
            NSString *str = [NSString stringWithFormat:@"%.2f", _max - idx * unitPrice];
            CATextLayer *layer = [CATextLayer getTextLayerWithString:str
                                                           textColor:[UIColor blackColor]
                                                            fontSize:9.f
                                                     backgroundColor:[UIColor clearColor]
                                                               frame:rect];
            
            [self.leftPriceLayer addSublayer:layer];
        }
        
        [self.layer addSublayer:self.leftPriceLayer];
    

    绘制日期(这里要注意,因为使用的Demo数据周期为天,所以日期也具体到天):

        NSMutableArray *kLineDateArr = [NSMutableArray array];
        int unitCount = candleCount / 4;
        for (int idx=0; idx<5; idx++)
        {
            YKKLineModel *model = self.kLineModelArr[_startIndex + idx * unitCount];
            
            NSDate *detaildate = [NSDate dateWithTimeIntervalSince1970:model.timeStamp];
            NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
            [dateFormatter setDateFormat:@"YYYY-MM-dd"];
            NSString *dateStr = [dateFormatter stringFromDate:detaildate];
            
            [kLineDateArr addObject:dateStr];
        }
        
        NSDictionary *attribute = @{NSFontAttributeName:[UIFont systemFontOfSize:9.f]};
        CGRect strRect = [self rectOfNSString:@"0000-00-00" attribute:attribute];
        float strW = CGRectGetWidth(strRect);
        float strH = CGRectGetHeight(strRect);
        
        float unitW = CGRectGetWidth(_mainRect) / 4;
        
        //循环绘制坐标点
        for (int idx = 0; idx < kLineDateArr.count; idx++)
        {
            CATextLayer *textLayer = nil;
            
            if (idx == kLineDateArr.count-1)
            {//最后一个
                CGRect rect = CGRectMake(idx * unitW - strW, CGRectGetMaxY(_mainRect), strW, strH);
                textLayer = [CATextLayer getTextLayerWithString:kLineDateArr[idx] textColor:[UIColor blackColor] fontSize:9.f backgroundColor:[UIColor clearColor] frame:rect];
            }else if(idx == 0)
            {//第一个
                CGRect rect = CGRectMake(idx * unitW, CGRectGetMaxY(_mainRect), strW, strH);
                textLayer = [CATextLayer getTextLayerWithString:kLineDateArr[idx] textColor:[UIColor blackColor] fontSize:9.f backgroundColor:[UIColor clearColor] frame:rect];
            }else
            {//中间
                CGRect rect = CGRectMake(idx * unitW - strW/2, CGRectGetMaxY(_mainRect), strW, strH);
                textLayer = [CATextLayer getTextLayerWithString:kLineDateArr[idx] textColor:[UIColor blackColor] fontSize:9.f backgroundColor:[UIColor clearColor] frame:rect];
            }
            
            [self.bottomDateLayer addSublayer:textLayer];
        }
        
        [self.layer addSublayer:self.bottomDateLayer];
    

    效果如下:

    区间标识

    添加左右滑动

    在以前我们讨论过滑动偏移量的获取方式,这里就暂且通过添加手势来获取偏移量。使用长按手势来做十字叉效果,使用拖动手势来做左右滑动效果。

    当检测到用户长按时,获取坐标点然后转换为柱子索引,再绘制十字叉,左侧和下侧展示当前索引的数据。当用户抬起手指时,可以选择及时清除掉十字叉,也可以加一个延时清除。

    当检测到用户拖动时,用偏移量的正负来判断用户是向左还是向右拖动。每次检测到拖动后,获取到偏移量,因为这个偏移量不是太线性,所以添加一个范围的判断。拿到偏移量以后,再更新展示数据的起始索引值,然后再更新视图。

    添加手势:

        //添加左右拖动手势
        UIPanGestureRecognizer *panG = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGestureAction:)];
        [_kLineView addGestureRecognizer:panG];
        
        //添加长按手势
        UILongPressGestureRecognizer *longG = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(kLineLongGestureAction:)];
        longG.minimumPressDuration = 0.5f;
        longG.numberOfTouchesRequired = 1;
        [_kLineView addGestureRecognizer:longG];
    

    响应手势:

    /**
     K线响应长按手势
     
     @param longGesture 手势对象
     */
    - (void)kLineLongGestureAction:(UILongPressGestureRecognizer *)longGesture
    {
        if (longGesture.state == UIGestureRecognizerStateBegan || longGesture.state == UIGestureRecognizerStateChanged)
        {
            CGPoint point = [longGesture locationInView:_kLineView];
            
            float x = 0.f;
            if (point.x < 0.f)
            {
                x = 0.f;
            }else if (point.x > CGRectGetWidth(_kLineView.frame))
            {
                x = CGRectGetWidth(_kLineView.frame)-1;
            }else
            {
                x = point.x;
            }
            //当长按滑动时,每滑动一次话会重新刷新十字叉
            [_kLineView drawCrossViewWithX:x];
        }else
        {
            //当手指抬起时,及时把十字叉取消掉
            [_kLineView clearCrossViewLayer];
        }
    }
    
    /**
     响应拖动手势
     
     @param panGesture 手势对象
     */
    - (void)panGestureAction:(UIPanGestureRecognizer *)panGesture
    {
        CGPoint point = [panGesture translationInView:_kLineView];
        float offset =  point.x - kLineGlobalOffset;
        if (panGesture.state == UIGestureRecognizerStateChanged && ABS(offset) > 3)
        {
            if (offset > 0)
            {
                if (ABS(offset) > 20)
                {
                    [_kLineView dragRightOffsetcount:5];
                    
                } else if(ABS(offset) > 6)
                {
                    [_kLineView dragRightOffsetcount:2];
                    
                } else
                {
                    [_kLineView dragRightOffsetcount:1];
                }
            }else
            {
                if (ABS(offset) > 20)
                {
                    [_kLineView dragLeftOffsetcount:5];
                    
                } else if(ABS(offset) > 6)
                {
                    [_kLineView dragLeftOffsetcount:2];
                    
                } else
                {
                    [_kLineView dragLeftOffsetcount:1];
                }
            }
            kLineGlobalOffset = point.x;
        }
        
        if (panGesture.state == UIGestureRecognizerStateEnded ||
            panGesture.state == UIGestureRecognizerStateCancelled ||
            panGesture.state == UIGestureRecognizerStateFailed)
        {
            kLineGlobalOffset= 0.f;
        }
    }
    

    效果如下:

    OHLC线

    Demo源码下载

    至此,我们已经把K线主图大部分的功能搭建完毕。Demo源码点击这里下载。下篇文章,会说到主副图指标的一些事,敬请期待。

    相关文章

      网友评论

          本文标题:K线开发之K线整体搭建

          本文链接:https://www.haomeiwen.com/subject/ygwxuxtx.html