[This is part of the Android Diary.] For my little piece of demo-ware, I wanted to draw curved lines between the circles representing entries in a geotagged feed. Android has a function for drawing arcs, but I had to do a little trigonometry to work out the arguments. This is by way of sharing the answer with any other Androiders who want to draw curved lines, and, well, I kind of enjoyed the math and who knows, maybe someone else will too.

The Android function is like this:

``````
public void drawArc(RectF oval, float startAngle, float sweepAngle,
boolean useCenter, Paint paint)``````

So it’ll draw part of the oval contained in the rectangle you give it; a circle if the rectangle is a square.

Suppose you’re starting with two points, let’s call them `e1` and `e2`. What we want to do is draw part of a circle that goes through those two points. Here’s a plausible method declaration.

``````	public static void connect(PointF e1, PointF e2, Paint paint, Canvas canvas) {
``````

Here are the two points in their lonely splendor.

We’ll use one fixed parameter: how much of the circle’s arc to use. I used 10% or 36º to keep the arcs nice and smooth. Let’s call that `a1`:

``````		float a1Degrees = 36.0f;

Given all that, we’re going to need to figure out the circle’s center and radius:

So let’s have a point `c` representing the circle’s center. We’ll draw a line between `e1` and `e2`, then connect its midpoint `m`, as well as `e1` and `e2`, to `c`. Of course, the line from the center to `m` is perpendicular to the line connecting `e1` and `e2`.

Now we have a couple of right triangles whose base is half the length of the line from `e1` to `e2`. Let’s compute that and call it `l1`:

``````		// l1 is half the length of the line from e1 to e2
double dx = e2.x - e1.x, dy = e2.y - e1.y;
double l = Math.sqrt((dx * dx) + (dy * dy));
double l1 = l / 2.0;``````

Since we know the angle at the pointy end of the right triangles, it’s easy to compute the the circle’s radius `r` and also `h`, the length of the line from `m` to `c`.

``````		// h is length of the line from the middle of the connecting line to the
//  center of the circle.
double h = l1 / (Math.tan(a1 / 2.0));

// r is the radius of the circle
double r = l1 / (Math.sin(a1 / 2.0));``````

Now let’s draw a horizontal line through `e2` and a vertical one through `e1` and look at one of the angles, which we’ll call `a2`.

It’s easy to compute `a2`.

``````		// a2 is the angle at which L intersects the x axis
double a2 = Math.atan2(dy, dx);``````

A glance shows that `a2` is part of another right triangle too. Let’s name its other corner `a3`.

`a3` is easy to compute, and is also the angle beween the x-axis and the line from `c` to `m`.

``````            // a3 is the angle at which H intersects the x axis
double a3 = (Math.PI / 2.0) - a2;``````

Now we’re pretty well done; we can compute `cx` and `cy`, the deltas from `m` to `c`.

``````
// m is the midpoint of the line from e1 to e2
double mX = (e1.x + e2.x) / 2.0;
double mY = (e1.y + e2.y) / 2.0;

// c is the the center of the circle
double cY = mY + (h * Math.sin(a3));
double cX = mX - (h * Math.cos(a3));

``````

All that’s left is the housekeeping to work out the sweep angle:

``````            // rect is the square RectF that bounds the "oval"
RectF oval =
new RectF((float) (cX - r), (float) (cY - r), (float) (cX + r), (float) (cY + r));

// a4 is the starting sweep angle
double rawA4 = Math.atan2(e1.y - cY, e1.x - cX);
float a4 = (float) Math.toDegrees(rawA4);

canvas.drawArc(oval, a4, a1Degrees, false, paint);
}``````

Afterthoughts · When I was writing this up I noticed that you don’t really need to compute the value `h`, you can work it all out in terms of `r`. But then it turns out that you have to be really clear when you’re talking about `e1` and when `e2`, and it matters that on the Android, as in most GUI libraries, the y-axis points down. Using the `h` line makes those problems go away, and the computation cost seems invisible anyhow, this redraws an immense number of times while you’re zooming and panning without perceptible delay.

## Contributions

Comment feed for ongoing: From: Gavin (Jan 02 2009, at 23:00)

Hi Tim,

Thanks for the writeup. Once I needed a curved line but didn't have the time to implement it last year when I was playing with Android.

http://www.flickr.com/photos/chihiro/2306440593/

Another need: add text labels onto those curved edges.

From: Pat Patterson (Jan 03 2009, at 00:00)

Nice! Brought memories flooding back from when I used to work on this - http://www.ces-ltd.co.uk/products/shapenest/ - heavy duty geometry there - approximating curves with straight lines to arbitrary accuracy, computing the area of profiles comprising any number of lines and arcs.

Graphics programming was SO rewarding - mostly, you could just SEE when you'd got it right :-)

From: Rui Carmo (Jan 03 2009, at 01:49)

While reading this, I wondered two things:

1. How many more people still enjoy doing this kind of math in these days of math illiteracy

2. If your app could be used to do crop circles :)

From: Daniel (Jan 03 2009, at 05:28)

Happy New Year, Tim!

If your intention is to draw a smooth path between a sequence of points then why don't you just draw a Bezier curve? That's the popular choice since about the late 1980s.

From browsing the online docs: Building an Android.graphics.Path looks promising, then calling quadTo (or cubicTo, or even arcTo), and then passing it into android.graphics.Canvas.drawPath().

Daniel

From: John Cowan (Jan 03 2009, at 10:24)

OS/2 fixed the wrong-way-up Y axis, but that idea died with it. I suppose the inverted Y derives from the fact that we (well, most of us) read top to bottom, so it makes sense for text.

From: Christian (Jan 03 2009, at 11:39)

It looks like http://www.tbray.org/ongoing/ongoing.atom doesn't load inline pictures correctly. I get an error symbol instead of pictures in NetNewsWire.

(I actually wanted to post that some time ago)

From: Tim (Jan 03 2009, at 13:10)

Daniel: I didn't discover the Bezier-curve methods hidden down in Path until I was already working on the arc. Note that they take 1 (quad) or 2 (cubic) interpolation points, so you're going to have to do some geometry anyhow. And the canvas.drawArc curves are nice and graceful.

From: other daniel (Jan 03 2009, at 17:48)

Very cool. Lost me about half way through, but still very cool :-)

Other Daniel.

From: Luke Jones (Jan 03 2009, at 19:49)

I'm curious what is the tool you used to draw the diagrams on this posting?

From: Tim (Jan 04 2009, at 20:51)

Luke: Omnigraffle; and I don't particularly recommend it for the purpose.