Quartz2D绘制网状图
wuzhiyong
8年前
<h3><strong>引言</strong></h3> <p>做开发也有一段时间了,但是从来没有自己研究过Quartz2D绘图,这几天帮朋友忙,顺便研究了一下Quartz2D,做了一个简单的效果,在这里和大家分享一下, 直接上代码。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/5ae766ba31e6a47cc42c1581a9f3a569.png"></p> <p style="text-align:center">效果图</p> <p><strong>画圆 (绘图中所与角度相关的都是使用弧度制)</strong></p> <pre> <code class="language-objectivec">CGFloat width = rect.size.width; UIBezierPath *rectPath = [UIBezierPath bezierPathWithRect:rect]; [[UIColor whiteColor] set]; [rectPath fill]; for (int a = 1; a <= 4; a++) { UIBezierPath *zero = [UIBezierPath bezierPathWithArcCenter:CGPointMake(width / 2, rect.size.height / 2) radius:width / 8 * a startAngle:0 endAngle:M_PI * 2 clockwise:YES]; [[UIColor lightGrayColor] set]; [zero stroke]; }</code></pre> <p><strong>绘制虚线</strong></p> <pre> <code class="language-objectivec">CGFloat width = rect.size.width; NSArray *blueArray = @[@0.5, @0.7, @0.8]; // 这里的数组是数据源, 浮点线, 表示技能百分比 NSArray *readArray = @[@0.7, @0.85, @0.3]; CGFloat dash[] = {4, 4}; // 虚线长度与间距 for (int a = 0; a <= 2; a++) { NSNumber *blueProportion = blueArray[a]; float blueLength = width / 2 * blueProportion.floatValue; NSNumber *readProportion = readArray[a]; float readLength = width / 2 * readProportion.floatValue; CGFloat blueX; CGFloat blueY; CGFloat readX; CGFloat redaY; UIBezierPath *pathLine = [UIBezierPath bezierPath]; [pathLine moveToPoint:CGPointMake(width / 2, width / 2)]; CGFloat x; CGFloat y; switch (a) { case 0: x = width / 2 - (width / 2 * cos(M_PI / 6)); y = width / 2 * sin(M_PI / 6) + width / 2; readX = width / 2 - (readLength * cos(M_PI / 6)); redaY = readLength * sin(M_PI / 6) + width / 2; blueX = width / 2 - (blueLength * cos(M_PI / 6)); blueY = blueLength * sin(M_PI / 6) + width / 2; break; case 1: x = width / 2; y = 0; readX = width / 2 ; redaY = width / 2 - readLength; blueX = width / 2 ; blueY = width / 2 - blueLength; break; case 2: x = width / 2 * cos(M_PI / 6) + width / 2; y = width / 2 * sin(M_PI / 6) + width / 2; readX = readLength * cos(M_PI / 6) + width / 2; redaY = readLength * sin(M_PI / 6) + width / 2; blueX = blueLength * cos(M_PI / 6) + width / 2; blueY = blueLength * sin(M_PI / 6) + width / 2; break; default: break; } CGPoint point = CGPointMake(x, y); [pathLine addLineToPoint:point]; [[UIColor lightGrayColor] set]; [pathLine setLineDash:dash count:2 phase:0]; [pathLine stroke]; NSValue *read = [NSValue valueWithCGPoint:CGPointMake(readX, redaY)]; NSValue *blue = [NSValue valueWithCGPoint:CGPointMake(blueX, blueY)]; [self.boolArray addObject:(blueLength > readLength) ? @1 : @0]; // 判断蓝色顶点是否在外面,保存方面后面使用 [self.readPointArray addObject:read]; [self.bluePointArray addObject:blue]; // 保存蓝色和红色顶点方面后面使用 }</code></pre> <p>绘制虚线会用到这个方法</p> <pre> <code class="language-objectivec">(void)setLineDash:(nullable const CGFloat *)pattern count:(NSInteger)count phase:(CGFloat)phase;</code></pre> <ul> <li>pattern:浮点型数组,表示长度间隙值</li> <li>count:整形,一般pattern个数,</li> <li>phase:偏移量</li> </ul> <p><strong>绘制红色区域</strong></p> <pre> <code class="language-objectivec">UIBezierPath *readPathLind = [UIBezierPath bezierPath]; NSValue *one = self.readPointArray[0]; NSValue *two = self.readPointArray[1]; NSValue *three = self.readPointArray[2]; [readPathLind moveToPoint:one.CGPointValue]; [readPathLind addLineToPoint:two.CGPointValue]; [readPathLind addLineToPoint:three.CGPointValue]; [[[UIColor alloc] initWithRed:239.0 / 255 green:76.0 / 255 blue:110 / 255 alpha:1] set]; [readPathLind fill];</code></pre> <p><strong>绘制蓝色重叠区域</strong></p> <pre> <code class="language-objectivec">for (int idex = 0; idex <= 2; idex++) { CGFloat width = self.frame.size.width; CGContextRef inRef = UIGraphicsGetCurrentContext(); CGContextMoveToPoint(inRef, width / 2, width / 2); NSValue *read = self.readPointArray[idex]; NSValue *blue = self.bluePointArray[idex]; CGPoint readPoint = read.CGPointValue; CGPoint bluePoint = blue.CGPointValue; NSNumber *numberOne; NSNumber *numberTow; NSValue *otherRead; NSValue *otherBlue; switch (idex) { case 0: numberOne = self.boolArray[0]; numberTow = self.boolArray[1]; otherRead = self.readPointArray[idex + 1]; otherBlue = self.bluePointArray[idex + 1]; break; case 1: numberOne = self.boolArray[1]; numberTow = self.boolArray[2]; otherRead = self.readPointArray[idex + 1]; otherBlue = self.bluePointArray[idex + 1]; break; case 2: numberOne = self.boolArray[2]; numberTow = self.boolArray[0]; otherRead = self.readPointArray[0]; otherBlue = self.bluePointArray[0]; break; default: break; } CGPoint otherReadPoint = otherRead.CGPointValue; CGPoint otherBluePoint = otherBlue.CGPointValue; /* 蓝色区域如果和红色有相交区域,则这部分在分块绘制; 下面是判断蓝色当前顶点和下一个顶点的连线与红色当前顶点和下一个顶点连线有没有交点; 单独一块中蓝色红色顶点连线如果没有交点, 则取靠内的两个顶点,与中心点作为绘图区域; 如果有交点还是取靠内顶点, 中心点, 外加交点坐标。 :warning::warning::warning: 有交点时一定要注意四个点的顺序, 必须是交点在两个顶点中间加入绘图路径 */ if (numberOne.intValue == numberTow.intValue && numberOne.intValue == 1) { // 判断当前顶点和下一个顶点是不是都在外面 CGContextAddLineToPoint(inRef, readPoint.x, readPoint.y); CGContextAddLineToPoint(inRef, otherReadPoint.x, otherReadPoint.y); } else if (numberOne.intValue == numberTow.intValue && numberOne.intValue == 0) { // 判断当前顶点和下一个顶点是不是都在里面 CGContextAddLineToPoint(inRef, bluePoint.x, bluePoint.y); CGContextAddLineToPoint(inRef, otherBluePoint.x, otherBluePoint.y); } else { // 当前顶点和下一个顶点一个在内一个在外, 蓝色顶点和红色顶点有交点 CGPoint point = [self intersection2:readPoint u2:otherReadPoint v1:bluePoint v2:otherBluePoint]; // 获取交点 if (numberOne.intValue == 1) { // 判断当前顶点是不是在外面 CGContextAddLineToPoint(inRef, readPoint.x, readPoint.y); CGContextAddLineToPoint(inRef, point.x, point.y); CGContextAddLineToPoint(inRef, otherBluePoint.x, otherBluePoint.y); } else { CGContextAddLineToPoint(inRef, bluePoint.x, bluePoint.y); CGContextAddLineToPoint(inRef, point.x, point.y); CGContextAddLineToPoint(inRef, otherReadPoint.x, otherReadPoint.y); } } UIColor *color = [[UIColor alloc] initWithRed:45.0 / 255 green:110.0 / 255 blue:250.0 / 255 alpha:1]; UIColor *endColor = [[UIColor alloc] initWithRed:118.0 / 255 green:173.0 / 255 blue:250.0 / 255 alpha:1]; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGFloat locations[] = {0.0, 1.0}; NSArray *array = @[(__bridge id)color.CGColor, (__bridge id)endColor.CGColor]; CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)array, locations); CGContextSaveGState(inRef); // 剪切路径, 类似视图裁剪 CGContextClip(inRef); CGRect rect = CGContextGetClipBoundingBox(inRef); // 渐变路径 CGContextDrawLinearGradient(inRef, gradient, CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect)), CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect)), 0); CGContextRestoreGState(inRef); // 注意释放 CGGradientRelease(gradient); CGColorSpaceRelease(colorSpace); }</code></pre> <p>添加渐变中会使用CGGradientRef这个玩意,</p> <pre> <code class="language-objectivec">CGGradientCreateWithColors( CGColorSpaceRef __nullable space, CFArrayRef cg_nullable colors,const CGFloat * __nullable locations)</code></pre> <ul> <li>space: CGColorSpaceRef实例</li> <li>CFArrayRef: 存放颜色的数组@[(__bridge id)color.CGColor……]</li> <li>locations:一个浮点型数组, 取之0-1,我理解应该是渐变的速度之类的</li> </ul> <p><strong>绘制蓝色非重叠区域</strong></p> <pre> <code class="language-objectivec">CGContextRef outRef = UIGraphicsGetCurrentContext(); NSMutableArray *points = [NSMutableArray array]; // 存放路径点的数组 // 判断蓝色如果全部在内就不画外面 if (![self.boolArray containsObject:@1]) { return; } for (int idex = 0; idex <= 2; idex++) { NSValue *read = self.readPointArray[idex]; NSValue *blue = self.bluePointArray[idex]; CGPoint readPoint = read.CGPointValue; CGPoint bluePoint = blue.CGPointValue; NSNumber *numberOne; NSNumber *numberTow; NSValue *otherRead; NSValue *otherBlue; switch (idex) { case 0: numberOne = self.boolArray[0]; numberTow = self.boolArray[1]; otherRead = self.readPointArray[idex + 1]; otherBlue = self.bluePointArray[idex + 1]; break; case 1: numberOne = self.boolArray[1]; numberTow = self.boolArray[2]; otherRead = self.readPointArray[idex + 1]; otherBlue = self.bluePointArray[idex + 1]; break; case 2: numberOne = self.boolArray[2]; numberTow = self.boolArray[0]; otherRead = self.readPointArray[0]; otherBlue = self.bluePointArray[0]; break; default: break; } CGPoint otherReadPoint = otherRead.CGPointValue; CGPoint otherBluePoint = otherBlue.CGPointValue; /* :warning::warning::warning:最烦的地方,自己都快整疯了 这里没有使用设置起点, 添加点的方式绘制, 利用的点数组,将需要绘制的点按顺序添加至数组利用CGContextAddLines函数绘制,所以点的顺序就尤为关键 */ // 如果蓝色顶点在外面肯定要添加至数组 if (numberOne.intValue == 1) { NSValue *blue = [NSValue valueWithCGPoint:bluePoint]; [points addObject:blue]; } if (numberTow.intValue != numberOne.intValue) { CGPoint point = [self intersection2:readPoint u2:otherReadPoint v1:bluePoint v2:otherBluePoint]; NSValue *pointV= [NSValue valueWithCGPoint:point]; NSValue *read = [NSValue valueWithCGPoint:readPoint]; NSValue *otherRead = [NSValue valueWithCGPoint:otherReadPoint]; // 交点肯定也要添加至数组 if (numberOne.intValue == 1) { // 蓝色顶点先外后内,则先添加交点, 后添加第一个红色顶点 [points addObject:pointV]; [points addObject:read]; } else { // 蓝色顶点先内后外,则先添加第二个红色顶点,后添加交点 [points addObject:otherRead]; [points addObject:pointV]; } } /* :warning::warning::warning:这里比较绕,最好自己画图理解一下, 自己排一下顺序 */ } NSInteger count = points.count; CGPoint pointLine[count]; for (int a = 0; a< count; a++) { NSValue *value = points[a]; CGPoint point = value.CGPointValue; pointLine[a].x = point.x; pointLine[a].y = point.y; } CGContextAddLines(outRef, pointLine, count); UIColor *color = [[UIColor alloc] initWithRed:45.0 / 255 green:110.0 / 255 blue:250.0 / 255 alpha:1]; UIColor *endColor = [[UIColor alloc] initWithRed:118.0 / 255 green:173.0 / 255 blue:250.0 / 255 alpha:1]; CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); CGFloat locations[] = {0.0, 1.0}; CGContextClosePath(outRef); NSArray *array = @[(__bridge id)color.CGColor, (__bridge id)endColor.CGColor]; CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)array, locations); CGContextSaveGState(outRef); CGContextClip(outRef); CGRect rect = CGContextGetClipBoundingBox(outRef); CGContextDrawLinearGradient(outRef, gradient, CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect)), CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect)), 0); CGContextRestoreGState(outRef); CGGradientRelease(gradient);</code></pre> <p><strong>绘制白色分割线</strong></p> <pre> <code class="language-objectivec">CGFloat width = rect.size.width; for (int a = 0; a <= 2; a++) { UIBezierPath *pathLine = [UIBezierPath bezierPath]; [pathLine moveToPoint:CGPointMake(width / 2, width / 2)]; NSNumber *numberOne = self.boolArray[a]; NSValue *value; if (numberOne.integerValue > 0) { value = self.bluePointArray[a]; } else { value = self.readPointArray[a]; } CGPoint point = value.CGPointValue; [pathLine addLineToPoint:point]; [[UIColor whiteColor] setStroke]; [pathLine strokeWithBlendMode:kCGBlendModeNormal alpha:0.1]; }</code></pre> <p><strong>换一组数据看看效果</strong></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/6658823dea066627cd45fff11b1a39d5.png"></p> <p style="text-align:center">效果图</p> <pre> <code class="language-objectivec">NSArray *blueArray = @[@0.9, @0.7, @0.8]; NSArray *readArray = @[@0.7, @0.85, @0.3];</code></pre> <p>提醒:绘制圆和绘制虚线顺序不能颠倒,不然会导致虚线看不见</p> <p> </p> <p>来自:http://www.jianshu.com/p/e33244d96430</p> <p> </p>