统计图之折线图 - SJChartLine

RalfEpstein 7年前
   <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>