I'm drawing the 'slices' in a custom pie chart in Core Graphics, using a UIBezierPath and the [path addArcWithCenter:radius:startAngle:endAngle:clockwise:] method. My problem is that often the pointy end of the slice actually juts past the center point, intruding into other slices space.
Is there a way to 'round' this edge?
Here is the code im using to draw the path
[path moveToPoint:center];
[path addArcWithCenter:center radius:radius startAngle:interiorAngle endAngle:totalAngle clockwise:YES];
[path addLineToPoint:center];
[path closePath];
Here is an image of the problem:
The white pointy end of the blue piece on the lower left intrudes slightly into the large blue piece in the upper right.
It's not "past" the center point. Your confusion lies in the fact that you're stroking the path. When you stroke a path, the stroke lies centered upon the path, and therefore half of the stroke is outside the path and half of the stroke is inside the path. If you want an accurate stroke, you have two options:
Fill your path with your stroke color, then construct another path that's inset into your first one by the desired line width, and then fill that path with your fill color. This will simulate an "inside" stroke, although it's not usable if your stroke or fill colors are semi-transparent.
Clip to your path, double the stroke width, and then stroke your path. The clipping will force the stroke to only draw inside the path. However, this may not look quite "accurate" at corners (not really sure) since it's doubling the stroke width rather than calculating the "desired" path.
Alternatively, you could try just setting the lineJoinStyle to something other than kCGLineJoinMiter. With the default miter style, the lines actually draw out as far as they need to from the corner in order to meet, which means they can go past 1/2 the line width. If you use kCGLineJoinRound or kCGLineJoineBevel they cannot go past 1/2 the line width. This may not be quite accurate, but it may be good enough for what you want.
I would suspect the problem is with the line width of the white line. For example, a line that is 8 points wide, would have 4 points on either side of the path.
I can't see the problem in that image (which slice is showing it?), but I can suggest that you remove the addLineToPoint: message. It isn't necessary; closePath will return to the center, since that's where you started.
Related
I have this strange problem with Java2D. I have a Path2D.Double that I want to draw, but depending on the stroke size, all segments are drawn correctly (curved) but when the stroke size is greater or equal than 0.00005d the segments are drawn straight. As you can see, the only parameter I'm changing is the stroke size. What am I doing wrong?
BasicStroke basicStroke1 = new BasicStroke(.00004f, CAP_ROUND, JOIN_ROUND);
g2draw.setStroke(basicStroke1);
g2draw.draw(path);
BasicStroke basicStroke2 = new BasicStroke(.00006f, CAP_ROUND, JOIN_ROUND);
g2draw.setStroke(basicStroke2);
g2draw.draw(path);
And here a picture with the result of this code:
Turns out the error seems to be rounding errors when transforming user coordinates to screen coordinates, when the scale difference is big (or not so big...). It's an odd behaviour of Java2D. Switched to JavaFX and the problem disappeared.
I am offsetting a CGPath using copy(strokingWithWidth:lineCap:lineJoin:miterLimit:transform:). The problem is the offset path introduces all kinds of jagged lines that seem to be the result of a miter join. Changing the miterLimit to 0 has no effect, and using a bevel line join also makes no difference.
In this image there is the original path (before applying strokingWithWidth), an offset path using miter join, and an offset path using bevel join. Why doesn't using bevel join have any affect?
Code using miter (Note that using CGLineJoin.round produces identical results):
let pathOffset = path.copy(strokingWithWidth: 4.0,
lineCap: CGLineCap.butt,
lineJoin: CGLineJoin.miter,
miterLimit: 20.0)
context.saveGState()
context.setStrokeColor(UIColor.red.cgColor)
context.addPath(pathOffset)
context.strokePath()
context.restoreGState()
Code using bevel:
let pathOffset = path.copy(strokingWithWidth: 4.0,
lineCap: CGLineCap.butt,
lineJoin: CGLineJoin.bevel,
miterLimit: 0.0)
context.saveGState()
context.setStrokeColor(UIColor.red.cgColor)
context.addPath(pathOffset)
context.strokePath()
context.restoreGState()
Here is a path consisting of two line segments:
Here's what it looks like if I stroke it with bevel joins at a line width of 30:
If I make a stroked copy of the path with the same parameters, the stroked copy looks like this:
Notice that triangle in there? That appears because Core Graphics creates the stroked copy in a simple way: it traces along the each segment of the original path, creating a copied segment that is offset by 15 points. It joins each of these copied segments with straight lines (because I specified bevel joins). In slow motion, the copy operation looks like this:
So on the inside of the joint, we get a triangle, and on the outside, we get the flat bevel.
When Core Graphics strokes the original path, that triangle is harmless, because Core Graphics uses the non-zero winding rule to fill the stroke. But when you stroke the stroked copy, the triangle becomes visible.
Now, if I scale down the line width used when I make the stroked copy, the triangle becomes smaller. And if I then increase the line width used to draw the stroked copy, and draw the stroked copy with mitered joins, the triangle can actually end up looking like it's filled in:
Now, suppose I replace that single joint in the original path with two joints connected by a very short line, creating a (very small) flat spot on the bottom:
When I make a stroked copy of this path, the copy has two internal triangles, and if I stroke the stroked copy, it looks like this:
So that's where those weird shapes star shapes come from when you make a stroked copy of your paths: very short segments creating overlapping triangles.
Note that I made my copies with bevel joins. Using miter joins when making the copy also creates the hidden triangles, because the choice of join only affects the outside of the joint, not the inside of the joint.
However, the choice of join does matter when stroking the stroked copy, because the use of miter joins makes the stars larger. See this document for a good illustration of how much the join style can affect the appearance of an acute angle.
So the miter joins make the triangles' points stick out quite far, which makes the overlapping triangles look like a star. Here's the result if I stroke the stroked copy using bevel joins instead:
The star is nigh-invisible here because the triangles are drawn with blunted corners.
If the inner triangles are unacceptable to you, you will have to write your own function (or find one on the Internet) to make a stroked copy of the path without the triangles, or to eliminate the triangles from the copy.
If your path consists entirely of flat segments, the easiest solution is probably to use an existing polygon-clipping library. The “union” operation, applied to the stroked copy, should eliminate the inner triangles. See this answer for example. Note that these libraries tend to be written in C++, so you'll probably have to write some Objective-C++ code since Swift cannot call C++ code directly.
In case you're wondering how I generated the graphics for this answer, I did it using this Swift playground.
I'm trying to develop a drawing application, and I need to change the width of the path according to the speed of the hand.
I tried to use moveToPoint to start another subpath
myPath.moveToPoint(myPath.currentPoint)
myPath.lineWidth = myPath.lineWidth + 1
but it doesn't work, it changes the width of the entire path.
Do you know if there's a way to change only a subpath's width?
That is not possible. All UIBezierPath properties (lineWidth, flatness, ...) apply to the entire path with all its subpaths.
To draw curves with different line widths you have to create multiple bezier paths.
I mean that I have custom view with one big circle and one smaller. But how I could cut corner (layer) of a big circle? Background is from parent view, not from custom, which have a clear color.
Icon, name label and label notifications I have been added in my custom view. So, problem still here with two intersecting circles.
I've suggested three approaches in my comments above. Here's a demonstration of one of them. Note that I didn't really do the math or try to approximate your drawing: it's just a demonstration of the principle:
That's actually three circles:
The big central circle (green) at lower left
The larger corner circle, used to "erase" the top right corner of the first circle
The second, smaller corner circle (green) at top left
Here's the code that generated that drawing (ignore the numbers; it's the principle that's important):
CGContextSetFillColorWithColor(con, UIColor.greenColor().CGColor)
CGContextFillEllipseInRect(con, CGRectMake(0,rect.height-130-10,130,130))
CGContextSetFillColorWithColor(con, UIColor.clearColor().CGColor)
CGContextSetBlendMode(con, kCGBlendModeClear) // erase
CGContextFillEllipseInRect(con, CGRectMake(rect.width-65, -5, 70, 70))
CGContextSetFillColorWithColor(con, UIColor.greenColor().CGColor)
CGContextSetBlendMode(con, kCGBlendModeNormal)
CGContextFillEllipseInRect(con, CGRectMake(rect.width-53,3,50,50))
I'm learning my way through UIBezierPaths by creating a table from scratch, and having individual cells filled with different colours.
This is a custom object I'm building which is contained in a subclassed UIView.
At the moment, I'm constructing this in this order:
'Cell' fill colours
Column lines
Row lines
Outer box (rounded rect)
As the picture suggests, I'm having trouble getting rid of the sharp corner of the cell fill outside the orange rounded rectangle.
Can anyone point me in the right direction to get rid of these?
Cheers! :)
At the beginning of your drawing code you should add the outer rounded rect path to the clipping path using its addClip method. That way nothing will get drawn that is outside this path.