乐虎游戏|乐虎国际登录|欢迎你

iOS开拓:多个方可实时加点的Chart

日期:2020-04-05编辑作者:计算机资讯

商厦项目有亟待用到胎儿心率监测,设计的分界面逻辑是内需一个得以表格,可以动态的在下边画上数据。有一些相近散点图。找了无数demo发现基本都以一回性加完的,实时的周旋超少,于是就和煦写了多少个小demo。

图片 1

图片 2Animation.gif

图片 3胎儿心率监测,动态加多点

chart 统计图

  • 蜡烛图和山形图绘制切换
  • 5种指标绘制切换
  • 长按蜡烛和目的线实际情况展现
  • 触底加载更加的多
  • 实时蜡烛绘制达成
  • 二级横屏和蜡烛三级横屏

demo比较简单,全体的逻辑是,首先创设多少个view,前面这么些茶褐的作为大的背景。然后传入横坐标和竖坐标数组。

先说一下坐标轴吧
横轴纵轴是一条Path,长度和可观是规行矩步控件宽度设定的,那样保险在不一样手提式无线电话机上比例相通
包括箭头也是path,比较轻松

图片 4fullScreen1.png图片 5fullScreen2.png

 self.chartview = [[MChartMainView alloc]init]; self.chartview.frame = CGRectMake(0, 40, [UIScreen mainScreen].bounds.size.width, 200); self.chartview.backgroundColor = [UIColor blueColor]; self.chartview.horizontalArray = @[@" 0",@"",@"10",@"",@"20",@"",@"30",@"",@"40",@"",@"50",@"",@"60",@"",@"70",@"",@"80"]; self.chartview.verticalArray = @[@"80",@"90",@"100",@"110",@"120",@"130",@"140",@"150",@"160",@"170",@"180",@"190",@"200"]; [self.view addSubview:self.chartview];
    /**
     * 画坐标轴
     * @param canvas
     * @param paint
     */
    private void drawAxes(Canvas canvas, Paint paint) {
        //坐标轴
        Path path = new Path();//三角形
        path.moveTo(width/10, width/20);
        path.lineTo(width/10, width/2);
        path.lineTo(width*9/10, width/2);
        canvas.drawPath(path, paint);

        //向上箭头
        Path pathArrowTop=new Path();
        pathArrowTop.moveTo(width/10-width/60,width/20+width/60);
        pathArrowTop.lineTo(width/10, width/20);
        pathArrowTop.lineTo(width/10+width/60, width/20+width/60);
        canvas.drawPath(pathArrowTop, paint);

        //向下箭头
        Path pathArrowRight=new Path();
        pathArrowRight.moveTo(width*9/10-width/60, width/2-width/60);
        pathArrowRight.lineTo(width*9/10, width/2);
        pathArrowRight.lineTo(width*9/10-width/60, width/2+width/60);
        canvas.drawPath(pathArrowRight, paint);
    }
  • 适配三种构造

在MChartMainView中,总括出各类小单元格的大幅和惊人,因为要放横坐标和竖坐标的数值作者那边留下了八个格子的长空。

横纵坐标的数字是写死的多少个值,还还未增加动态设置格局
纵轴数字的间距是把纵轴高度除以纵轴数字数量
源点为原点
留意要衡量字体宽高

图片 6UI1.png图片 7UI2.png

- drawRect:rect { self.chartView = [[MChartView alloc]init]; self.chartView.horizontalArray = self.horizontalArray; self.chartView.verticalArray = self.verticalArray; //预留两格空位 self.chartView.averageWidth = self.frame.size.width/(self.horizontalArray.count+1); self.chartView.averageHeight = self.frame.size.height/(self.verticalArray.count+1); self.chartView.frame = CGRectMake(self.chartView.averageWidth*1.5, self.chartView.averageHeight*0.5, self.frame.size.width-self.chartView.averageWidth*2, self.frame.size.height-self.chartView.averageHeight*2); self.chartView.layer.borderColor = [UIColor redColor].CGColor; self.chartView.layer.borderWidth = 1.0f; self.chartView.backgroundColor = [UIColor yellowColor]; [self addSubview:self.chartView]; [self addHorizontalLabel]; [self addVerticalLabel];}- addHorizontalLabel { for (int i = 0; i<self.horizontalArray.count; i++) { [self addSubview:[self label:CGRectMake(self.chartView.averageWidth+self.chartView.averageWidth*i, self.chartView.frame.size.height+self.chartView.frame.origin.y,self.chartView.averageWidth , self.chartView.averageHeight) title:self.horizontalArray[i]]]; }}- addVerticalLabel { for (int i = 0; i<self.verticalArray.count; i++) { [self addSubview:[self label:CGRectMake(self.chartView.averageWidth*0.2, self.frame.size.height-self.chartView.averageHeight*2-self.chartView.averageHeight*i,self.chartView.averageWidth , self.chartView.averageHeight) title:self.verticalArray[i]]]; }}
Rect rect = new Rect();
paintText.getTextBounds(textVertical[i], 0, textVertical[i].length(), rect);
int textWidth = rect.width();
int textHeight = rect.height();

为啥选取了tableView

  • 品味是或不是能对绘制有candle的Cell实行理并答复用;
  • 换个思维造轮子;

在规定好内部表格的轻重之后,在MChartView中初叶绘制线条。

X轴要减去字体宽度,Y轴要丰硕字体中度的八分之四,同一时候增多了叁个间距textSpace,以作保字体不与坐标轴重叠,同期又处在当前数值所在纵轴地点的为主(横轴同理卡塔尔国

亟待消除的主题材料:变纵向滚动为纵向滚动

图片 8旋转.png

  • 如图所示:在转悠时,是绕tableView中心实行旋转的,为了使旋转后的tableView的frame能够和superView的深浅同样,那么将在使旋转前的tableView偏移一定间距;
 . . self.tableView.transform = CGAffineTransformMakeRotation; . . [self.view addSubview:self.tableView]; . . [self.tableView mas_updateConstraints:^(MASConstraintMaker *make) { make.left.mas_equalTo((width-height)/2); make.top.mas_equalTo(-(width-height)/2); make.width.mas_equalTo; make.height.mas_equalTo; }]; 
  • 利弊:即使实行到前面,蜡烛全部都以用CAShapeLayer+UIBeizerPath绘制的,cell的复用并从未起到多大的效能,况兼旋转之后涉及到了tableView的x,y坐标在采纳中的转变,但是能感觉庆幸的是:使用了cell之后,在思索蜡烛横坐标的时候正是cell.indexPath.row*rowHeight;再者正是在缩放的时候,能够从来退换cell的万丈就能够高达缩放的目标;
 //画横向线条 if (self.horizontalArray) { for (int i = 0; i<self.horizontalArray.count; i++) { double x = _averageWidth*i; [self drawLine:CGPointMake(x, self.frame.size.height) endPoint:CGPointMake]; } } //画竖向线条 if (self.verticalArray) { for (int i = 0; i<self.verticalArray.count; i++) { double y = _averageHeight*i; [self drawLine:CGPointMake endPoint:CGPointMake(self.frame.size.width, y)]; } } 
canvas.drawText(textVertical[i],width/10-textWidth-textSpace,width/2-(i+1)*width/10+textHeight/2,paintText);

    private String[] textVertical ={"100","200","300","400"};
    private String[] textHorizontal ={"0","1","2","3","4","5","6","7"};

    /**
     * 坐标轴汉字
     * @param canvas
     */
    private void drawText(Canvas canvas) {
        paintText.setTextSize(width/30);
        //横轴
        for (int i = 0; i < textHorizontal.length; i++) {
            Rect rect = new Rect();
            paintText.getTextBounds(textHorizontal[i], 0, textHorizontal[i].length(), rect);
            int textWidth = rect.width();
            int textHeight = rect.height();
            canvas.drawText(textHorizontal[i],width/10+i*width/10-textWidth/2,width/2+textHeight+textSpace,paintText);
        }

        //纵轴
        for (int i = 0; i < textVertical.length; i++) {
            Rect rect = new Rect();
            paintText.getTextBounds(textVertical[i], 0, textVertical[i].length(), rect);
            int textWidth = rect.width();
            int textHeight = rect.height();
            canvas.drawText(textVertical[i],width/10-textWidth-textSpace,width/2-(i+1)*width/10+textHeight/2,paintText);
        }
    }

缩放有度

- pinchAction:(UIPinchGestureRecognizer *)sender{ static CGFloat oldScale = 1.0f; CGFloat difValue = sender.scale - oldScale; NSLog(@"difValue=====%f",difValue); NSLog(@"oldScale=====%f",oldScale); if (ABS>StockChartScaleBound) { CGFloat oldKlineWidth = self.candleWidth; // NSLog(@"原来的index%ld",oldNeedDrawStartIndex); self.candleWidth = oldKlineWidth * ((difValue > 0) ? (1+StockChartScaleFactor):(1-StockChartScaleFactor)); oldScale = sender.scale; if (self.candleWidth < scale_MinValue) { self.candleWidth = scale_MinValue; }else if (self.candleWidth > scale_MaxValue) { self.candleWidth = scale_MaxValue; } }}
  • 在每一遍缩放的时候,举行推断:1State of Qatar独有触及的缩放大于某些预约值的时候才开展缩放2)调节每一趟缩放的比率;3)调整缩放的一体化范围;

到此地球表面格和左右坐标就画好了。上面是得以完成动态的丰盛数值。为了仿照效法胎儿心率监测仪重回的数值,作者那边写了贰个沙漏,0.25秒重回三次随机数值。

骨干部分,画折线数据(随机数,最大设定了400)
也是画叁个path
源点为原点 (width/10, width/2卡塔尔国
所经各点的横坐标为 原点横坐标+第几个点横轴每一种点间距 width(i+2)/10
纵坐标为 原点纵坐标 -(当前数值/100)*(width/10)
value为ValueAnimator当前值(动画)

定点缩放

//这句话达到让tableview在缩放的时候能够保持缩放中心点不变;//实现原理:在放大缩小的时候,计算出变化后和变化前中心点的距离,然后为了保持中心点的偏移值始终保持不变,就直接在原来的偏移上加减变换的距离//ceil(centerPoint.y/oldKlineWidth)中心点前面的cell个数//self.rowHeight-oldKlineWidth每个cell的高度的变化CGFloat pinchOffsetY = ceil(centerPoint.y/oldKlineWidth)*(self.candleWidth-oldKlineWidth)+oldNeedDrawStartPointY;if (pinchOffsetY<0) { pinchOffsetY = 0;}if (pinchOffsetY+self.subViewWidth>self.kLineModelArr.count*self.candleWidth) { pinchOffsetY = self.kLineModelArr.count*self.candleWidth - self.subViewWidth;}[self.tableView setContentOffset:CGPointMake(0, pinchOffsetY)];
- countDownTimer { dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)); dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0.25 * NSEC_PER_SEC, 0 * NSEC_PER_SEC); dispatch_source_set_event_handler(timer, ^{ self.timer += 0.25; [self pointTimer]; if (self.timer>=10) { dispatch_source_cancel; } }); dispatch_resume;}- pointTimer { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ __block int point = 80; point += arc4random() % 100; if (point>200) { point = 80; } //因为最终要分析数据,所以这里用了个字典保存。 [self.pointDic setObject:[NSNumber numberWithInt:point] forKey:[NSString stringWithFormat:@"%.2f",self.timer]]; //调用添加点的方法 [self.chartview addPoint:self.timer heartbeat:point]; });}
for (int i = 0; i < randomNums.length; i++) {
          pathData.lineTo(width*(i+2)/10, width/2-value*randomNums[i]*width/1000);
      }

    /**
     * 画数据
     * @param canvas
     */
    private void drawData(Canvas canvas) {
        if(value==0) return;
        //根据重力数据画阴影
        paintData.setShadowLayer(5,gravityX*shadowWidth,-gravityY*shadowWidth,Color.LTGRAY);

        Path pathData=new Path();
        pathData.moveTo(width/10, width/2);
        for (int i = 0; i < randomNums.length; i++) {
            pathData.lineTo(width*(i+2)/10, width/2-value*randomNums[i]*width/1000);
        }
        canvas.drawPath(pathData, paintData);
    }

微观构造

  • 荧屏中显得的第1个蜡烛图的X坐标:

    NSUInteger leftArrCount = ABS(scrollViewOffsetX/self.candleWidth);_needDrawStartIndex = leftArrCount; 
    
  • 显示屏中能够显得的火炬个数:

     - (NSInteger)needDrawKlineCount{ CGFloat width = self.subViewWidth; _needDrawKlineCount = ceil(width/self.candleWidth); return _needDrawKlineCount;} 
    

    听说那多少个参数,起源和长短,就足以从数量源数组中标准的抽出当前显示屏展现的蜡烛图的多少;然后滑动进度中实时总结并开展坐标转换

  • 极值:从这几天显示屏展现的数据源数组获得的最大值和最小值

  • 单位价格所代表的像素值

     self.heightPerPoint = self.candleChartHeight/(self.maxAssert-self.minAssert); 
    
  • 开收高低值从价格转移成像素值

接下来在放大计时器中对数据开展保存的同一时候,调用加多点的方法,横向数值与纵向数值。

突显当前数值大大小,其地方是折线的拐点,同数据地方,注意宽高的加减,保障不遮挡拐点,并与拐点主旨对其

火炬绘制

CAShapeLayer+UIBeizerPath

详见ZXSocketDataReformer 针对服务器再次回到的数码格式:@"时间戳,实时价格";大家要求利用那三个个的多寡本人创设蜡烛模型;

  • 率先进轨范型创设:假诺一分钟重返柒17个数据, 那么大家须求看清这一分钟初叶的时候,並且收取这一分钟的首先个数据First,创设一个崭新的模型A;模型A的开.收.高.平价都以第一数量的实时价格;
  • 模型替换:第一个模型创设之后,新的多少Second到来,那么大家相比较得出高值和低值替换模型A的高低值,况且当时模型A的收盘价为数据Second的实时价格;
  • 模型买单:付账:正是对个M1M5M15..中回到的有所数据本人付钱出一个蜡烛模型,也便是多少个值:开收高低;付账的风云点剖断方式:1卡塔尔国以socket重临数据的小运戳买下账单:那样付账在多少上不会有啥样固有误差,然而日子上会有绝对误差; eg:针对M1来说,假使在6'58''的时候回来此分蜡烛的结尾二个值,假设用socket的日子作为付账的话,那么大家必须等到下三个socket再次来到值的小运戳到来本事付账,要是socket在7'00''-7'01''之间回到了数量以来,很好,大家得以一向付钱上一个蜡烛,並且及时的创建叁个新的蜡烛模型;但是多少并非历次都会变卦如此每每,假诺下叁个多少的赶到是7'16'';那么中间那18'',k线图会静止18'',那么一定于6'的十分蜡烛会延迟16''实行推进,便诱致了时间上的基值误差;而且当数码涨到封顶大概股票停牌的时候,socket数据还没改观,便不会再次回到数据,那么这么些时刻k线图也是不会有其它动作;2State of Qatar以央浼服务器时间戳付钱:会导致数据上的引用误差;eg:在7'00''要求买单,可是这些时间socket在7'00''的时候回来了多少个数据,不过结账的时候只会取到当中贰个数目作为6'的收盘价,其余数据将残存到下个蜡烛;解决:1卡塔尔以socket和服务器的刻钟戳相结合的方法进行付钱:笔者在ZXSocketDataReformer中也是如此做的,第叁回倡议服务器时间,然后本地安装计时器举办服务器时间同步; 由socket时间戳进行模型布局,到了整点,优先socket实行模型推动,假诺整点的时候从不socket再次来到,就由服务器时间开展带动;2卡塔尔计时器由服务器创立,最佳正是在整点延迟1秒的时候,假若在00''-01''的时候已经有socket数据传送到移动端的话,那么就无需推送假数据,若无socket数据爆发,就推送多少个假数据到移动端,告诉移动端,数据要求张开买单,移动端只需求用socket进行结算; (好啊,本身都绕晕了,若是要求不是那么高其实只有遵照socket举行数据买单也够用了卡塔尔;

伪造如下景况:

图片 9实时绘制.png

代码大致是那般的 :

- handleNewestCellWhenScrollToBottomWithNewKlineModel:(KlineModel *)klineModel{ //==0的时候需要插入一个新的cell;否则只需要刷新最后一个cell if (self.isNew) { KlineModel *newsDataModel = [self calulatePositionWithKlineModel:klineModel]; [self.kLineModelArr addObject:newsDataModel]; double oldMax = self.maxAssert; double oldMin = self.minAssert; [self calculateNeedDrawKlineArr]; [self calculateMaxAndMinValueWithNeedDrawArr:self.needDrawKlineArr]; //不等的话就重绘 if (oldMax<self.maxAssert||oldMin>self.minAssert) { dispatch_async(dispatch_get_main_queue(), ^{ [self.tableView setContentOffset:CGPointMake(0, (self.kLineModelArr.count-self.needDrawKlineCount)*self.candleWidth+(self.needDrawKlineCount*self.candleWidth-self.subViewWidth))]; }); [self drawTopKline]; }else{ //否则就插入 NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.kLineModelArr.count-1 inSection:0]; dispatch_async(dispatch_get_main_queue(), ^{ //先增加 再偏移 [self.tableView beginUpdates]; [self.tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; [self.tableView endUpdates]; [self.tableView setContentOffset:CGPointMake(0, (self.kLineModelArr.count-self.needDrawKlineCount)*self.candleWidth+(self.needDrawKlineCount*self.candleWidth-self.subViewWidth))]; }); [self delegateToReturnKlieArr]; } }else{ KlineModel *newsDataModel = [self calulatePositionWithKlineModel:klineModel]; NSIndexPath *indexPath = [NSIndexPath indexPathForRow:self.kLineModelArr.count-1 inSection:0]; [self.kLineModelArr replaceObjectAtIndex:self.kLineModelArr.count-1 withObject:newsDataModel]; CGFloat oldMax = self.maxAssert; CGFloat oldMin = self.minAssert; [self calculateNeedDrawKlineArr]; [self calculateMaxAndMinValueWithNeedDrawArr:self.needDrawKlineArr]; //如果计算出来的最新的极值不在上一次计算的极值直接的话就重绘,否则就刷新最后一个即可 if (oldMax<self.maxAssert||oldMin>self.minAssert) { [self drawTopKline]; }else{ dispatch_async(dispatch_get_main_queue(), ^{ [self.tableView beginUpdates]; [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone]; [self.tableView endUpdates]; [self delegateToReturnKlieArr]; }); } }}

实质上应用进程中在insert可能reloadrows的时候,有的时候会并发崩溃,一时半刻还未有消亡,索性改为了直接重绘全屏了,要是你们也不敢后人让它平昔重绘,可到--ZXMainView.m--- handleNewestCellWhenScrollToBottomWithNewKlineModel:(KlineModel *卡塔尔klineModel;打开注释的情势,终结了它;

  • 基本的k线图的衔接能够在demo中SecondStepViewController中观看,运转需在appDelegate中切换rootViewController;
  • JoinUpSocketViewController是过渡socket实时绘制的demo,为了脱敏,调控器中的socket数据是专断爆发的;
  • 实际的连接代码只怕接口都能够在demo中见到,这里不做过多描述;
 [self.chartview addPoint:self.timer heartbeat:point];
    /**
     * 动态数值
     * @param canvas
     */
    private void drawNum(Canvas canvas) {
        paintNum.setTextSize(width/30);
        if(value==0) return;
        for (int i = 0; i < randomNums.length; i++) {
            Rect rect = new Rect();
            paintNum.getTextBounds((int)(value*randomNums[i])+"", 0, String.valueOf((int)(value*randomNums[i])).length(), rect);
            int textWidth = rect.width();
            int textHeight = rect.height();
            canvas.drawText((int)(value*randomNums[i])+"",width*(i+2)/10-textWidth/2, width/2-value*randomNums[i]*width/1000-textHeight/2,paintNum);
        }
    }

3.2.1 历史数据转模型

(详见Reformer---ZXCandleDataReformer卡塔尔国本地历史数据格式为:

/* @[@"时间戳,收盘价,开盘价,最高价,最低价,成交量", @"时间戳,收盘价,开盘价,最高价,最低价,成交量", @"时间戳,收盘价,开盘价,最高价,最低价,成交量", @"...", @"..."]; */ 

相应的模子转换格式为:

- (NSArray<KlineModel *>*)transformDataWithDataArr:(NSArray *)dataArr currentRequestType:(NSString *)currentRequestType{ self.currentRequestType = currentRequestType; //修改数据格式 → ↓↓↓↓↓↓↓终点到啦↓↓↓↓↓↓↓↓↓ ← NSMutableArray *tempArr = [NSMutableArray array]; __weak typeof weakSelf = self; [dataArr enumerateObjectsUsingBlock:^(NSString *dataStr, NSUInteger idx, BOOL * _Nonnull stop) { NSArray *strArr = [dataStr componentsSeparatedByString:@","]; KlineModel *model = [KlineModel new]; model.timestamp = [strArr[0] integerValue]; model.timeStr = [weakSelf setTime:strArr[0]]; model.closePrice = [strArr[1] doubleValue]; model.openPrice = [strArr[2] doubleValue]; model.highestPrice = [strArr[3] doubleValue]; model.lowestPrice = [strArr[4] doubleValue]; if (strArr.count>=6) { model.volumn = @([strArr[5] doubleValue]); }else{ model.volumn = @; } model.x = idx; [tempArr addObject:model]; model = nil; }]; return tempArr;}

历史数据模型转换需要使用者根据请求历史数据的实际格式进行转换;

本文由乐虎游戏发布于计算机资讯,转载请注明出处:iOS开拓:多个方可实时加点的Chart

关键词:

IOS TableViewCell嵌套webview

IMG_0521.JPG 我们开发中,在使用UITableView的时候经常会遇到这样的需求:tableview的cell中的内容是动态的。于是我们就在...

详细>>

乐虎国际登录iOS之Pod install VS Pod update

乐虎国际登录,此文为翻译文章,原文网址: 简介 大多数人使用CocoaPods的时候都会想当然的以为podinstall仅仅在第一...

详细>>

iOS 用RunTime重写KVO&lt;附德姆o&gt;

3.在第二步之后,我点击一个button ,push 到另外一个ViewController(TestViewController)里面,然后在TestViewController里面,点击...

详细>>

乐虎国际登录四十二十四线程与NSTimer

NSInteger转 Byte 数组,长度为2 1.Ios主线程,也称UI线程,在主线程中使用NSTimer,runloop是自动开启的,(如果NSTimer当前所...

详细>>