统计图之折线图 - SJChartLine
RalfEpstein
8年前
<h2><strong>效果图</strong></h2> <p style="text-align:center"><br> <img src="https://simg.open-open.com/show/5c0014271444dc3c5c3e252aef8c28b6.png"> </p> <h2><strong>用法</strong></h2> <pre> <code class="language-objectivec">// 初始化折线图 SJLineChart *lineChart = [[SJLineChart alloc] initWithFrame:CGRectMake(10, 100, [UIScreen mainScreen].bounds.size.width - 20, 200)]; // 设置折线图属性 lineChart.title = @"折线图"; // 折线图名称 lineChart.maxValue = 100; // 最大值 lineChart.yMarkTitles = @[@"20",@"40",@"60",@"80",@"100"]; // Y轴刻度标签 [lineChart setXMarkTitlesAndValues:@[@{@"item":@"9月1日",@"count":@60},@{@"item":@"9月2日",@"count":@30},@{@"item":@"9月3日",@"count":@90},@{@"item":@"9月4日",@"count":@100},@{@"item":@"9月5日",@"count":@60},@{@"item":@"9月6日",@"count":@60},@{@"item":@"9月7日",@"count":@12}] titleKey:@"item" valueKey:@"count"]; // X轴刻度标签及相应的值 // lineChart.xScaleMarkLEN = 60; // 可以不设,会根据视图的宽度自适应,设置后如果折线图的宽度大于视图宽度,折线图可以滑动 //设置完数据等属性后绘图折线图 [lineChart mapping]; [self.view addSubview:lineChart];</code></pre> <h2><strong>知识点回顾</strong></h2> <ol> <li><a href="/misc/goto?guid=4959716213785040674" rel="nofollow,noindex">UIBezierPath + CAShapeLayer 画线</a></li> <li><a href="/misc/goto?guid=4959716213877078533" rel="nofollow,noindex">UIBezierPath + CAGradientLayer 画渐变色</a></li> </ol> <h2><strong>画图步骤</strong></h2> <h3>画坐标轴,刻度标签以及网格线</h3> <ul> <li>如图:</li> </ul> <p style="text-align:center"><br> <img src="https://simg.open-open.com/show/582defa72cd5dcb3116c6937bc42f383.png"> </p> <p>1 . 自定义SJAxisView(坐标轴视图)定义属性 xScaleMarkLEN(X轴单位长度) 和 yScaleMarkLEN (Y轴单位长度 starPoint(坐标轴起始点、xAxis_L(X轴长度)、yAxis_L(Y轴长度) 并宏定义标签的宽高 以及通过计算或赋值得到的属性值 然后通过 for 循环 添加X轴和Y轴上的刻度标签</p> <pre> <code class="language-objectivec">#pragma mark Y轴上的刻度标签 - (void)setupYMarkLabs { for (int i = 0; i < self.yMarkTitles.count; i ++) { UILabel *markLab = [[UILabel alloc] initWithFrame:CGRectMake(0, self.startPoint.y - YMARKLAB_HEIGHT / 2 + i * self.yScaleMarkLEN, YMARKLAB_WIDTH, YMARKLAB_HEIGHT)]; markLab.textAlignment = NSTextAlignmentRight; markLab.font = [UIFont systemFontOfSize:12.0]; markLab.text = [NSString stringWithFormat:@"%@", self.yMarkTitles[self.yMarkTitles.count - 1 - i]]; [self addSubview:markLab]; } } #pragma mark X轴上的刻度标签 - (void)setupXMarkLabs { for (int i = 0;i < self.xMarkTitles.count; i ++) { UILabel *markLab = [[UILabel alloc] initWithFrame:CGRectMake(self.startPoint.x - XMARKLAB_WIDTH / 2 + i * self.xScaleMarkLEN, self.yAxis_L + self.startPoint.y + YMARKLAB_HEIGHT / 2, XMARKLAB_WIDTH, XMARKLAB_HEIGHT)]; markLab.textAlignment = NSTextAlignmentCenter; markLab.font = [UIFont systemFontOfSize:11.0]; markLab.text = self.xMarkTitles[i]; [self addSubview:markLab]; } }</code></pre> <p>2 . 画X轴 、Y轴, 确定X轴Y轴的起点和终点后,通过UIBezierPath + CAShapeLayer画线</p> <pre> <code class="language-objectivec">#pragma mark Y轴 - (void)drawYAxsiLine { UIBezierPath *yAxisPath = [[UIBezierPath alloc] init]; [yAxisPath moveToPoint:CGPointMake(self.startPoint.x, self.startPoint.y + self.yAxis_L)]; [yAxisPath addLineToPoint:CGPointMake(self.startPoint.x, self.startPoint.y)]; CAShapeLayer *yAxisLayer = [CAShapeLayer layer]; [yAxisLayer setLineDashPattern:[NSArray arrayWithObjects:[NSNumber numberWithInt:1], [NSNumber numberWithInt:1.5], nil]]; // 设置线为虚线 yAxisLayer.lineWidth = 0.5; yAxisLayer.strokeColor = [UIColor redColor].CGColor; yAxisLayer.path = yAxisPath.CGPath; [self.layer addSublayer:yAxisLayer]; } #pragma mark X轴 - (void)drawXAxsiLine { UIBezierPath *xAxisPath = [[UIBezierPath alloc] init]; [xAxisPath moveToPoint:CGPointMake(self.startPoint.x, self.yAxis_L + self.startPoint.y)]; [xAxisPath addLineToPoint:CGPointMake(self.xAxis_L + self.startPoint.x, self.yAxis_L + self.startPoint.y)]; CAShapeLayer *xAxisLayer = [CAShapeLayer layer]; [xAxisLayer setLineDashPattern:[NSArray arrayWithObjects:[NSNumber numberWithInt:1], [NSNumber numberWithInt:1.5], nil]]; xAxisLayer.lineWidth = 0.5; xAxisLayer.strokeColor = [UIColor redColor].CGColor; xAxisLayer.path = xAxisPath.CGPath; [self.layer addSublayer:xAxisLayer]; }</code></pre> <p>3 . 网格线 (同 坐标轴)</p> <p>与Y轴平行的网格线的X坐标第一条线的x坐标即为 坐标系中startPoint.x,后面的各线的x坐标加上相应倍数的X轴单位长度(xScaleMarkLEN)。</p> <p>与Y轴平行的网格线Y坐标第一条的y坐标为 startPoint.y ,后边的各线的y坐标加上相应倍数的y轴单位长度</p> <pre> <code class="language-objectivec">#pragma mark 与 Y轴 平行的网格线 - (void)drawYGridline { for (int i = 0; i < self.xMarkTitles.count - 1; i ++) { CGFloat curMark_X = self.startPoint.x + self.xScaleMarkLEN * (i + 1); UIBezierPath *yAxisPath = [[UIBezierPath alloc] init]; [yAxisPath moveToPoint:CGPointMake(curMark_X, self.yAxis_L + self.startPoint.y)]; [yAxisPath addLineToPoint:CGPointMake(curMark_X, self.startPoint.y)]; CAShapeLayer *yAxisLayer = [CAShapeLayer layer]; [yAxisLayer setLineDashPattern:[NSArray arrayWithObjects:[NSNumber numberWithInt:1], [NSNumber numberWithInt:1.5], nil]]; // 设置线为虚线 yAxisLayer.lineWidth = 0.5; yAxisLayer.strokeColor = [UIColor blackColor].CGColor; yAxisLayer.path = yAxisPath.CGPath; [self.layer addSublayer:yAxisLayer]; } } #pragma mark 与 X轴 平行的网格线 - (void)drawXGridline { for (int i = 0; i < self.yMarkTitles.count - 1; i ++) { CGFloat curMark_Y = self.yScaleMarkLEN * i; UIBezierPath *xAxisPath = [[UIBezierPath alloc] init]; [xAxisPath moveToPoint:CGPointMake(self.startPoint.x, curMark_Y + self.startPoint.y)]; [xAxisPath addLineToPoint:CGPointMake(self.startPoint.x + self.xAxis_L, curMark_Y + self.startPoint.y)]; CAShapeLayer *xAxisLayer = [CAShapeLayer layer]; [xAxisLayer setLineDashPattern:[NSArray arrayWithObjects:[NSNumber numberWithInt:1], [NSNumber numberWithInt:1.5], nil]]; xAxisLayer.lineWidth = 0.5; xAxisLayer.strokeColor = [UIColor blackColor].CGColor; xAxisLayer.path = xAxisPath.CGPath; [self.layer addSublayer:xAxisLayer]; } }</code></pre> <h3><strong>画折线、渐变阴影、以及点击层视图</strong></h3> <p>因为折线是画在坐标系中,所以画折线的视图继承自坐标系视图 ,并设置坐标轴中的最大值属性(maxValue)和 折线点值得数组(valueArray)</p> <p>==折线图的重点是确定折线上各点的位置坐标==</p> <p>画折线</p> <p>折线点X坐标折线点所在,Y网格线的 x坐标(确定折线的各点坐标第一个点的x坐标即为 坐标系中startPoint.x,后面的各点加上相应倍数的X轴单位长度(xScaleMarkLEN)。)</p> <p>折线点Y坐标假设折线点的值为value ,那么 percent = value / maxVlue 即为,折线点应该在其所在Y网格线的位置的百分比,因为iOS屏中坐标是以左上为起点,所以点的Y坐标为:起点的Y坐标(starPoint.y) + (1 - percent) * Y轴坐标轴长度</p> <pre> <code class="language-objectivec">#pragma mark 画折线图 - (void)drawChartLine { UIBezierPath *pAxisPath = [[UIBezierPath alloc] init]; for (int i = 0; i < self.valueArray.count; i ++) { CGFloat point_X = self.xScaleMarkLEN * i + self.startPoint.x; CGFloat value = [self.valueArray[i] floatValue]; CGFloat percent = value / self.maxValue; CGFloat point_Y = self.yAxis_L * (1 - percent) + self.startPoint.y; CGPoint point = CGPointMake(point_X, point_Y); // 记录各点的坐标方便后边添加渐变阴影 和 点击层视图 等 [pointArray addObject:[NSValue valueWithCGPoint:point]]; if (i == 0) { [pAxisPath moveToPoint:point]; } else { [pAxisPath addLineToPoint:point]; } } CAShapeLayer *pAxisLayer = [CAShapeLayer layer]; pAxisLayer.lineWidth = 1; pAxisLayer.strokeColor = [UIColor orangeColor].CGColor; pAxisLayer.fillColor = [UIColor clearColor].CGColor; pAxisLayer.path = pAxisPath.CGPath; [self.layer addSublayer:pAxisLayer]; }</code></pre> <p>画阴影、圆环、点击图层等</p> <p>因为画完折线后点的坐标已经获取到了,阴影以及其他视图的添加就容易多了。有几个注意的地方:</p> <ol> <li>阴影的显示范围的起点为第一个折线点的最下边,即我们数学坐标系的原点处,阴影的终点为,最后一个折线点的最下边,即X轴上x坐标和最后一个折线点相同的点。经过点即为折线点</li> <li>圆环就是自定义了一个视图SJCircleView,添加在折线点出(也可以通过UIbezierPath画)</li> <li>点击层视图就是普通的视图(UIView),添加了点击手势</li> <li>弹出视图为UIButton,设置了特殊背景图片</li> </ol> <pre> <code class="language-objectivec">#pragma mark 渐变阴影 - (void)drawGradient { CAGradientLayer *gradientLayer = [CAGradientLayer layer]; gradientLayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height); gradientLayer.colors = @[(__bridge id)[UIColor colorWithRed:250/255.0 green:170/255.0 blue:10/255.0 alpha:0.8].CGColor,(__bridge id)[UIColor colorWithWhite:1 alpha:0.1].CGColor]; gradientLayer.locations=@[@0.0,@1.0]; gradientLayer.startPoint = CGPointMake(0.0,0.0); gradientLayer.endPoint = CGPointMake(0.0,1); UIBezierPath *gradientPath = [[UIBezierPath alloc] init]; [gradientPath moveToPoint:CGPointMake(self.startPoint.x, self.yAxis_L + self.startPoint.y)]; for (int i = 0; i < pointArray.count; i ++) { [gradientPath addLineToPoint:[pointArray[i] CGPointValue]]; } CGPoint endPoint = [[pointArray lastObject] CGPointValue]; endPoint = CGPointMake(endPoint.x + self.startPoint.x, self.yAxis_L + self.startPoint.y); [gradientPath addLineToPoint:endPoint]; CAShapeLayer *arc = [CAShapeLayer layer]; arc.path = gradientPath.CGPath; gradientLayer.mask = arc; [self.layer addSublayer:gradientLayer]; } #pragma mark 折线上的圆环 - (void)setupCircleViews { for (int i = 0; i < pointArray.count; i ++) { SJCircleView *circleView = [[SJCircleView alloc] initWithCenter:[pointArray[i] CGPointValue] radius:4]; circleView.tag = i + BASE_TAG_CIRCLEVIEW; circleView.borderColor = [UIColor orangeColor]; circleView.borderWidth = 1.0; [self addSubview:circleView]; } } #pragma mark 覆盖一层点击图层 - (void)setupCoverViews { for (int i = 0; i < pointArray.count; i ++) { UIView *coverView = [[UIView alloc] init]; coverView.tag = BASE_TAG_COVERVIEW + i; if (i == 0) { coverView.frame = CGRectMake(self.startPoint.x, self.startPoint.y, self.xScaleMarkLEN / 2, self.yAxis_L); [self addSubview:coverView]; } else if (i == pointArray.count - 1 && pointArray.count == self.xMarkTitles.count) { CGPoint point = [pointArray[i] CGPointValue]; coverView.frame = CGRectMake(point.x - self.xScaleMarkLEN / 2, self.startPoint.y, self.xScaleMarkLEN / 2, self.yAxis_L); [self addSubview:coverView]; } else { CGPoint point = [pointArray[i] CGPointValue]; coverView.frame = CGRectMake(point.x - self.xScaleMarkLEN / 2, self.startPoint.y, self.xScaleMarkLEN, self.yAxis_L); [self addSubview:coverView]; } UITapGestureRecognizer *gesutre = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(gesutreAction:)]; [coverView addGestureRecognizer:gesutre]; } }</code></pre> <ul> <li>点击事件是点击当前的点击层,移除上一个弹出视图,在该点击层的折线点添加一个弹出视图</li> </ul> <pre> <code class="language-objectivec">#pragma mark- 点击层视图的点击事件 - (void)gesutreAction:(UITapGestureRecognizer *)sender { NSInteger index = sender.view.tag - BASE_TAG_COVERVIEW; if (lastSelectedIndex != -1) { SJCircleView *lastCircleView = (SJCircleView *)[self viewWithTag:lastSelectedIndex + BASE_TAG_CIRCLEVIEW]; lastCircleView.borderWidth = 1; UIButton *lastPopBtn = (UIButton *)[self viewWithTag:lastSelectedIndex + BASE_TAG_POPBTN]; [lastPopBtn removeFromSuperview]; } SJCircleView *circleView = (SJCircleView *)[self viewWithTag:index + BASE_TAG_CIRCLEVIEW]; circleView.borderWidth = 2; CGPoint point = [pointArray[index] CGPointValue]; UIButton *popBtn = [UIButton buttonWithType:(UIButtonTypeCustom)]; popBtn.tag = index + BASE_TAG_POPBTN; popBtn.frame = CGRectMake(point.x - 10, point.y - 20, 20, 15); [popBtn setBackgroundImage:[UIImage imageNamed:@"btg_pop_bg.png"] forState:UIControlStateNormal]; [popBtn setTitleEdgeInsets:UIEdgeInsetsMake(- 3, 0, 0, 0)]; popBtn.titleLabel.font = [UIFont systemFontOfSize:10]; [popBtn setTitle:[NSString stringWithFormat:@"%@",self.valueArray[index]] forState:(UIControlStateNormal)]; [self addSubview:popBtn]; lastSelectedIndex = index; }</code></pre> <p>折线图基本完成</p> <p>为折线图添加 名称 说明等信息</p> <p>自定义一个视图,在视图中添加 名称 说明等视图,在自定义的视图中添加一个UIScrollView,ScrollView添加上折线图</p> <p>==注意:将折线图添加在一个UIScrollView中,当设置X轴单位长度后,如果折线宽度大于ScrollView的宽度,便可以滑动。不设置X轴单位长度,折线图会自适应ScrollView的宽度; 折线图的高度智能通过设置ScrollView的高度来设置,即Y轴单位长度ScrollView自适应高度,垂直方向不可滑动==</p> <p> </p> <p> </p> <p>来自:http://www.cnblogs.com/jaesun/p/tong-ji-tu-zhi-zhe-xian-tu--SJChartLine.html</p> <p> </p>