K线开发之分时图整体搭建

提出问题

记得曾经有一个脑筋急转弯:

问:如何把大象装进冰箱里?
答:总共分三步(1)把冰箱门开起来(2)把大象推进去(3)把冰箱门关上

虽然是一个笑话,但是从另一种角度来讲也是一种解决问题的思路。2008年北京奥运会作为一个大型项目,时长持续8年之久,可是项目的过程也仅仅只分为五个过程组:

启动过程组 -> 规划过程组 -> 执行过程组 -> 监控过程组 -> 收尾过程组

那对于我们的主题:分时图,其实也可以这样看待。如何绘制一张分时图,大体的过程如下:

  • 绘制分时图的边框
  • 绘制分时图的X轴时间点
  • 绘制价格区间标识
  • 绘制分时线
  • 绘制均线
  • 绘制呼吸灯

完成6个步骤,那一个分时图就已经绘制完成。

开始搞起

有了思路,就开始干活!虽然在前几篇文章中有说过一些绘制的方法,这里就再说一次,权当复习。

(一)绘制分时图边框

要实现的效果是一个6 * 4 方格的边框,如下图:

分时图边框

那就是说,我们分两个for循环,来完成横向7条线、竖向5条线的绘制工作。上代码!

    CGRect rect = CGRectMake(frameX, frameY, frameW, frameH);
    UIBezierPath *framePath = [UIBezierPath bezierPathWithRect:rect];
    CAShapeLayer *layer = [CAShapeLayer layer];
    
    float unitW = frameW/6;
    float unitH = frameH/4;
    //绘制7条竖线
    for (int idx=0; idx<7; idx++)
    {
        CGPoint startPoint = CGPointMake(frameX + unitW * idx, frameY);
        CGPoint endPoint   = CGPointMake(frameX + unitW * idx, frameY + frameH);
        [framePath moveToPoint:startPoint];
        [framePath addLineToPoint:endPoint];
    }
    //绘制5条横线
    for (int idx=0; idx<5; idx++)
    {
        CGPoint startPoint = CGPointMake(frameX, frameY + unitH * idx);
        CGPoint endPoint   = CGPointMake(frameX + frameW, frameY + unitH * idx);
        [framePath moveToPoint:startPoint];
        [framePath addLineToPoint:endPoint];
    }
    //设置图层的属性
    layer.path = framePath.CGPath;
    layer.lineWidth = 0.5f;
    layer.strokeColor = [UIColor colorWithRed:220.f/255.f green:220.f/255.f blue:220.f/255.f alpha:1.f].CGColor;
    layer.fillColor = [UIColor clearColor].CGColor;

(二)绘制分时图的X轴时间点

框绘制完成以后,就开始绘制边框最下方的时间点,由于现货类K线框架默认交易时间为24小时,所以时间也设置为6.01至6.00。这样的话,每4个小时一个方格,共需要绘制7个时间点。代码如下:

    //坐标点数组
    NSArray *timePointArr = @[@"06:01", @"10:00", @"14:00", @"18:00", @"22:00", @"02:00", @"06:00"];
    NSDictionary *attribute = @{NSFontAttributeName:[UIFont systemFontOfSize:9.f]};
    CGRect strRect = [self rectOfNSString:@"00:00" attribute:attribute];
    float strW = CGRectGetWidth(strRect);
    float strH = CGRectGetHeight(strRect);
    
    float unitW = CGRectGetWidth(self.frame) / 6;
    //循环绘制坐标点
    for (int idx = 0; idx < timePointArr.count; idx++)
    {
        CATextLayer *textLayer = nil;
        
        if (idx == timePointArr.count-1)
        {//最后一个
            CGRect rect = CGRectMake(idx * unitW - strW, CGRectGetHeight(self.frame)-timePointH, strW, strH);
            textLayer = [CATextLayer getTextLayerWithString:timePointArr[idx] textColor:[UIColor blackColor] fontSize:9.f backgroundColor:[UIColor clearColor] frame:rect];
        }else if(idx == 0)
        {//第一个
            CGRect rect = CGRectMake(idx * unitW, CGRectGetHeight(self.frame)-timePointH, strW, strH);
            textLayer = [CATextLayer getTextLayerWithString:timePointArr[idx] textColor:[UIColor blackColor] fontSize:9.f backgroundColor:[UIColor clearColor] frame:rect];
        }else
        {//中间
            CGRect rect = CGRectMake(idx * unitW - strW/2, CGRectGetHeight(self.frame)-timePointH, strW, strH);
            textLayer = [CATextLayer getTextLayerWithString:timePointArr[idx] textColor:[UIColor blackColor] fontSize:9.f backgroundColor:[UIColor clearColor] frame:rect];
        }
        
        [self.layer addSublayer:textLayer];
    }

(三)绘制价格区间标识

到现在,边框和时间点绘制完成。接下来,再绘制价格区间。价格区间的求法是先要出极限值,也就是最大值最小值,然后按下列的规则得出:

if(ABS(当前分时线中最大值 - 昨日收盘价)) >= (ABS(昨日收盘价-当前分时线中最小值))
{
最上侧价格 = 当前分时线中最大值;
最下侧价格 = 昨日收盘价 - ABS(当前分时线中最大值 - 昨日收盘价);
}else
{
最上侧价格 = 昨日收盘价 + ABS(昨日收盘价-当前分时线中最小值);
最下侧价格 = 当前分时线中最小值;
}

代码如下:

    //循环绘制5行数据
    //左边是价格  右边是百分比
    for (int idx = 0; idx < 5; idx++)
    {
        float height = 0.f;
        if (idx == 4)
        {
            height = idx * unitH - CGRectGetHeight(priceRect);
        } else
        {
            height = idx * unitH;
        }
        CGRect leftRect = CGRectMake(0,
                                     height,
                                     CGRectGetWidth(priceRect),
                                     CGRectGetHeight(priceRect));
        CGRect rightRect = CGRectMake(CGRectGetMaxX(self.frame)-CGRectGetWidth(perRect)-14,
                                      height,
                                      CGRectGetWidth(perRect),
                                      CGRectGetHeight(perRect));
        //计算价格和百分比
        NSString *leftStr = [NSString stringWithFormat:@"%.2f", self.maxValue - idx * unitPrice];
        NSString *rightStr = [NSString stringWithFormat:@"%.2f%%", (self.maxValue - idx * unitPrice - self.yc)/self.yc];
        
        CATextLayer *leftLayer = [CATextLayer getTextLayerWithString:leftStr
                                                           textColor:[UIColor blackColor]
                                                            fontSize:9.f
                                                     backgroundColor:[UIColor clearColor]
                                                               frame:leftRect];
        CATextLayer *rightLayer = [CATextLayer getTextLayerWithString:rightStr
                                                            textColor:[UIColor blackColor]
                                                             fontSize:9.f
                                                      backgroundColor:[UIColor clearColor]
                                                                frame:rightRect];
        
        [self.layer addSublayer:leftLayer];
        [self.layer addSublayer:rightLayer];
    }

(四)绘制分时线

左右的价格区间绘制完以后,接下来是绘制分时线。

1、这里要注意,因为默认是24小时的交易时间,那分时线是每一个点为一分钟,24小时换算成分钟是1440分钟。

2、使用边框的宽 除以 1440,就可以得出每一个点做占的宽,这样在转换每一个分时点的坐标时,x值就可以使用这个宽得出。

3、那每一个分时点的y值是如何求出的?是先用最大值减去最小值得出边框高所对应的值,然后用这个值除以边框高,就得出单位值所对应的高,那求y值时就可以直接用这个值。

转换代码如下:

    CGFloat unitW = CGRectGetWidth(self.frame) / 1440;
    CGFloat unitValue = (self.maxValue - self.minValue) / (CGRectGetHeight(self.frame) - timePointH);
    
    NSMutableArray *pointArr = [NSMutableArray array];
    //遍历数据模型
    [self.timeCharModelArr enumerateObjectsUsingBlock:^(YKTimeChartModel * _Nonnull model, NSUInteger idx, BOOL * _Nonnull stop) {
        
        CGFloat x = idx * unitW;
        //生成分时线坐标点
        CGPoint linePoint = CGPointMake(x, ABS(CGRectGetMaxY(self.frame) - timePointH) - (model.clp - self.minValue)/ unitValue);
        //生成均线坐标点
        CGPoint avgPoint = CGPointMake(x, ABS(CGRectGetMaxY(self.frame) - timePointH) - (model.avp - self.minValue)/ unitValue);
        
        YKTimeLinePointModel *pointModel = [YKTimeLinePointModel new];
        pointModel.linePoint = linePoint;
        pointModel.avgPoint = avgPoint;
        [pointArr addObject:pointModel];
    }];
    
    return pointArr;

那每一个分时线的点的坐标全部转换完以后,我们就可以直接遍历这个数组来循环绘制了。上代码:

    //绘制分时线
    YKTimeLinePointModel *firstModel = pointArr.firstObject;
    [timeLinePath moveToPoint:firstModel.linePoint];
    for (int i=1; i<pointArr.count; i++)
    {
        YKTimeLinePointModel *model = pointArr[i];
        [timeLinePath addLineToPoint:model.linePoint];
    }
    lineLayer.path = timeLinePath.CGPath;
    lineLayer.lineWidth = 0.4f;
    lineLayer.strokeColor = [UIColor colorWithRed:100.f/255.f green:149.f/255.f blue:237.f/255.f alpha:1.f].CGColor;
    lineLayer.fillColor = [UIColor clearColor].CGColor;
    
    //绘制背景区域
    YKTimeLinePointModel *lastModel = [pointArr lastObject];
    [timeLinePath addLineToPoint:CGPointMake(lastModel.linePoint.x, CGRectGetHeight(self.frame) - timePointH)];
    [timeLinePath addLineToPoint:CGPointMake(firstModel.linePoint.x, CGRectGetHeight(self.frame)- timePointH)];
    fillLayer.path = timeLinePath.CGPath;
    fillLayer.fillColor = [UIColor colorWithRed:135.f/255.f green:206.f/255.f blue:250.f/255.f alpha:0.5f].CGColor;
    fillLayer.strokeColor = [UIColor clearColor].CGColor;
    fillLayer.zPosition -= 1;

(五)绘制均线

分时线绘制完以后,接着绘制均线,也就是那根黄色的线:

    CAShapeLayer *avgLineLayer = [CAShapeLayer layer];
    
    UIBezierPath *avgLinePath = [UIBezierPath bezierPath];
    YKTimeLinePointModel *firstModel = pointArr.firstObject;
    [avgLinePath moveToPoint:firstModel.avgPoint];
    
    for (int i=1; i<pointArr.count; i++)
    {
        YKTimeLinePointModel *model = pointArr[i];
        [avgLinePath addLineToPoint:model.avgPoint];
    }
    
    avgLineLayer.path = avgLinePath.CGPath;
    avgLineLayer.lineWidth = 2.f;
    avgLineLayer.strokeColor = [UIColor colorWithRed:255.f/255.f green:215.f/255.f blue:0.f/255.f alpha:1.f].CGColor;
    avgLineLayer.fillColor = [UIColor clearColor].CGColor;

至此,我们已经把分时图绘制完成,来看看我们的成果吧!

分时图

(六)绘制呼吸灯

怎么样? 是不是感觉很棒?不过刚才差点忘了一个元素,就是呼吸灯效果,它可是最能反映我们的分时图动态效果的地方。

那接着上代码吧!(如果对CABasicAnimation不是太了解的话,点击这儿,有详细介绍)

/**
 绘制呼吸灯
 */
- (void)drawBreathingLightWithPoint:(CGPoint)point
{
    CALayer *layer = [CALayer layer];
    //设置任意位置
    layer.frame = CGRectMake(point.x, point.y, 3, 3);
    //设置呼吸灯的颜色
    layer.backgroundColor = [UIColor blueColor].CGColor;
    //设置好半径
    layer.cornerRadius = 1.5;
    //给当前图层添加动画组
    [layer addAnimation:[self createBreathingLightAnimationWithTime:2] forKey:nil];
    
    [self.layer addSublayer:layer];
}


/**
 生成动画

 @param time 动画单词持续时间
 @return 返回动画组
 */
- (CAAnimationGroup *)createBreathingLightAnimationWithTime:(double)time
{
    //实例化CABasicAnimation
    CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:@"transform.scale"];
    //从1开始
    scaleAnimation.fromValue = @1;
    //到3.5
    scaleAnimation.toValue = @3.5;
    //结束后不执行逆动画
    scaleAnimation.autoreverses = NO;
    //无限循环
    scaleAnimation.repeatCount = HUGE_VALF;
    //一次执行time秒
    scaleAnimation.duration = time;
    //结束后从渲染树删除,变回初始状态
    scaleAnimation.removedOnCompletion = YES;
    scaleAnimation.fillMode = kCAFillModeForwards;
    
    CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    opacityAnimation.fromValue = @1.0;
    opacityAnimation.toValue = @0;
    opacityAnimation.autoreverses = NO;
    opacityAnimation.repeatCount = HUGE_VALF;
    opacityAnimation.duration = time;
    opacityAnimation.removedOnCompletion = YES;
    opacityAnimation.fillMode = kCAFillModeForwards;
    
    CAAnimationGroup *group = [CAAnimationGroup animation];
    group.duration = time;
    group.autoreverses = NO;
    group.animations = @[scaleAnimation, opacityAnimation];
    group.repeatCount = HUGE_VALF;
    //这里也应该设置removedOnCompletion和fillMode属性,以具体情况而定
    
    return group;
}

最终的效果如下:

分时图

好啦,我们的分时图最终大功告成。但先别急着太高兴,其实还有很多欠缺的地方,比如:

  • 主题、颜色是不是需要可配置?
  • 参数、大小是不是需要动态自适应?
  • 边框线段是不是要可自定义?比如来个其他颜色的虚线?
  • 这可是现货类?股票类的X轴怎么修改?
  • demo的源码如何修改变成强扩展的框架的一部分呢?
  • .......

还有很多地方、很多效果值得我们去细细打磨它!如果有需要讨论的地方,随时欢迎拍砖灌水!最后献上demo源码一份,点这里

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • 本文是衔接上篇关于绘制分时图框的内容,如果你还不了解关于K线图、分时图的相关知识,可以点击这儿。 需求确定 在上篇...
    nethanhan阅读 1,186评论 0 0
  • 前言 当写完了所有需要使用的素材类后,我们开始搭建一个比较完整的K线Demo。 它包含以下功能: 1、可以展示蜡烛...
    nethanhan阅读 1,009评论 0 1
  • 怎样看K线图 K线图有直观、立体感强、携带信息量大的特点,蕴涵着丰富的东方哲学思想,能充分显示股价趋势的强弱、买卖...
    捉牛股阅读 1,495评论 0 0
  • 如果是已经支持的语言,直接CTRL+B(CTRL+SHIFT+B)如果暂不支持,就在顶部菜单Tools--Buil...
    xigua1234阅读 1,543评论 0 0
  • 想想美好的事,心就不痛了,因为毕竟,生活之中不只有美好!你过去的痛楚还是会时不时被人提起!这就是我的人生!我必须面...
    坏坏儿阅读 87评论 0 0