[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.

Starting with two points

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;
		double a1 = Math.toRadians(a1Degrees); 

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

Essential pieces of the circle

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.

Computing some lengths

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.

Some angles

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.

More angles

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:Comments feed

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.

[link]

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 :-)

[link]

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 :)

I can already envision aliens/nuts/gardeners going about their business with an Android handset...

[link]

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

[link]

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.

[link]

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)

[link]

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.

[link]

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

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

Other Daniel.

[link]

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?

[link]

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

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

[link]

author · Dad
colophon · rights

January 02, 2009
· Technology (90 fragments)
· · Math (5 more)
· · Mobile (86 more)

By .

The opinions expressed here
are my own, and no other party
necessarily agrees with them.

A full disclosure of my
professional interests is
on the author page.

I’m on Mastodon!