Tuesday, August 14, 2007

Fun With Circles

I recently decided that I wanted to create a "Progress Spinner." That's a stupid name for a control that looks like its spinning around to indicate that something is happening in the background. In other words, its a progress bar without the bar. The picture shows my end result (I was too lazy to try to make an animated gif).

I decided I wanted this because I hate progress bars that just go back and forth endlessly. Progress bars should be reserved for times when you can actually estimate how far along in a process you are as a discrete percentage. When you have no idea how far along you are, then just display a "spinny thingy" (an alternate name).

Progress spinners are pretty popular today. You see them in Micrsoft software like the Reporting Services WinForms viewer, or Apple's iPod, or the new iPhone. They're everywhere.

Anyway, I thought it would be a fun little side project to create one. Mostly because I would have to re-teach myself some trigonometry, all of which I had forgotten.

Here's the details: Display x number of circles (with r radii) in a circle which fills the size of the user control. Use two colors, a base color and an active color, which can be changed by the user. The active color appears to move clockwise around the outer circle. Allow the speed of the rotation to be changed.

The math was the fun part. When I started, I had no idea how to calculate the location of each of the circles (henceforth referred to as markers). It turns out its quite easy though, just use Polar coordinates and convert to Cartesian coordinates.

In case you can't remember, just like I couldn't, Polar is given by the distance r from the center to the point and the angle theta from what would usually be the positive x axis to the point. In my case, r is constant so I only need to calculate theta for each marker. 2 * PI / num markers gives the angle between each marker. So just loop for each marker and increment theta by that amount.

So that was fun and easy. Of course, it couldn't remain that simple... You also have to account for the radius of the markers in r so your markers don't leave the visible space of the control. And you have to convert from Cartesian coordinates to Computer coordinates (cause 0,0 is the top left in a computer, not the center). And finally, the biggest annoyance: account for borders on the WinForms UserControl.

If you have BorderStyle set to anything other than none, all kinds of weird things starts happening. It turns out that if you draw a line from (0,0) to (0, width) ( top-left to top-right) it will be visible. But if you draw a line from (0, height-1) to (width-1, height-1) (bottom-left to bottom-right) it will not be visible. To make that line visible you actually have to use (0, height-3) to (width-3, height-3). The borders take up 2 pixels, even when they're not using them as in BorderStyle.Single. The borders also only steal space at the bottom and right of the control, not the left or top. It makes no sense to me what-so-ever.

Anyway, it was a fun little project. While I was at it, I added the ability to draw the markers as lines instead of circles:

I also added the ability to register a paint event and paint your own markers however you please.

The control also includes the concept of color profiles allowing you to select from green, black, blue, or red so you don't always have to figure out your own colors.

Just for kicks I also added a feature so that as you resize the control, not only does the diameter of the main circle grow, but the diameter of the markers grows proportionally as well.

Finally I got to use a really nice trick with IEnumerable. I created a MarkerPositionManager : IEnumerable class. This allows me to write code like this in the OnPaint method:

foreach( MarkerPosition mp in markerPositionManager )
// draw ellipse in mp.BoundingRect

I like that because it completely abstracts the logic for positioning the markers from the logic for drawing the markers. I love using this pattern because its very effective and easy to refactor to.

The end result is pretty fun to play with.


  1. http://www.napyfab.com/ajax-indicators/

    I have another link in my mailbox at home that lets you specify colors and styles and dynamically creates one for you. Ill try and remember to post it when i get home.

  2. I think that page may have given me a slight seizure...

  3. Sounds interesting. It would be nice if you could post your code...;-) TIA!