Drawing a ring of circles
The goal of this example is to write a procedure for drawing a ring of circles. This is
almost identical to drawing a row of adjacent circles -- the only difference is that
the formula for computing the center/position of each circle is more "complicated".
Goal: Given a pair of (x, y) coordinates, a ringRadius value, a circleRadius value, and
an integer n, draw a ring of n circles of the given ringRadius at a distance ringRadius
from the given center (x, y).
Fortunately, the Physics majors have given us a formula for the position of a planet
orbiting at a given distance around the sun.
Given:
- the position of the sun
- the length of the orbit
- the angle of rotation of the planet
the formula for the position of the planet is:
planetX = sunX + orbitRadius * cos(angle )
planetY = sunY + orbitRadius * sin(angle )
We can think of the ring of circles as planets arranged around the sun. In our case (x, y)
are the coordinates of the sun and ringRadius is the orbit radius.
Describing the drawing process in English
The process of drawing a ring is similar to the process of drawing a row.
We need to keep track of:
- the current angle of rotation for the circle to draw
- the current position where to draw the circle
- how many circles we have drawn
Then we repeatedly draw a circle and update the angle and position for the next circle to draw.
The important component here is the angle of rotation, since it determines the position of the circle to draw.
Note that:
- the angle for the first circle is 0°
- the angle increases by 360°/n (the angle of each pizza slice when divided among n people)
Here is a step-by-step outline of the above process:
1. Create a Post-It note to keep track of the angle of rotation for the circle to draw
- the initial value for the angle is 0° (first circle has not rotated)
2. Create two Post-It notes to keep track of the location where to draw a circle
- the initial values for these Post-It notes are computed from the Physics formula based on the current angle
3. Create a Post-It note to keep track of how many circles have been drawn so far
- the initial value of this Post-It note is 0 -- no circles have been drawn
4. As long as number drawn is less than number requested:
5. draw one circle at the location specified by the Post-It notes from step 2
6. update the values of the Post-It notes from steps 1 and 2 to reflect the location of the next circle
- increase the angle by 360°/n
- recompute the position of the next circle with the Physics formula formula based on the updated angle
7. increment by 1 Post-It note from step 4 (one more circle drawn)
Here is the code (note the similarities with the code for drawing a row of smileys).
void drawCirclesRing( double ringX, double ringY, double ringRadius, double circleRadius, int n )
{
double curAngle = 0; // (step 1)
double curX = ringX + ringRadius * Math.cos( curAngle ); // (step 2.1)
double curY = ringY + ringRadius * Math.sin( curAngle ); // (step 2.2)
int count = 0; // (step 3)
while ( count < n ) // (step 4)
{
String curColor = canvas.getRandomColor();
canvas.drawCircle( curX, curY, circleRadius, curColor ); // (step 5)
curAngle = curAngle + 2*Math.PI / n; // (step 6.1)
curX = ringX + ringRadius * Math.cos( curAngle ); // (step 6.2)
curY = ringY + ringRadius * Math.sin( curAngle ); // (step 6.2)
count = count + 1; // (step 7 )
}
}
Note that:
- the mathematical operations cos and sin are in the Java Math package
so we had to refer to them as Math.cos and Math.sin
- Java does not measure angles in degrees but in radians, so we used 360° = 2π
1.2. Animation -- orbiting circle
We can modify the code above to give the illusion of a moving circle/planet. The idea is to:
- draw the circle
- pause briefly, so that the user can see the circle
- draw a white circle to cover/hide the red circle
To pause briefly the execution of the procedure use:
canvas.sleep( number of seconds )
Some possibilities are 0.1 (one-tenth of a second) or 0.5 (half a second), etc.
Since we are trying to draw a moving circle/planet we expect the speed of the planet
(e.g. 10° per second) as opposed to number of hops n. Thus the procedure will be:
void drawMovingPlanet( double sunX, double sunY, double orbitRadius, double planetRadius, double speed )
{
}
Finally, we have to decide on the loop condition. We are not given total number of hops n.
Instead, the planet keeps moving until it goes back to original position, i.e. until it
completes full circle around the sun, i.e. it goes around 360°.
The the loop condition becomes:
English: Java:
as long as traveled less than 360: while ( curAngle < 2*Math.PI )
{
move a bit. move a bit;
}
Here is the code for a procedure that shows a rotating circle. Notice that there is no
count here, since we do not know how many hops around the sun the planet makes. Instead
we keep going as long as the planet rotation is under 360° total.
void drawMovingPlanet( double sunX, double sunY, double orbitRadius, double planetRadius, double speed )
{
double curAngle = 0; // (step 1)
double curX = sunX + orbitRadius * Math.cos( curAngle ); // (step 2)
double curY = sunY + orbitRadius * Math.sin( curAngle ); // (step 2)
while ( curAngle < 2*Math.PI ) // 2*Math.PI is the math way of saying 360 degrees
{
canvas.drawCircle( curX, curY, circleRadius, "red" ); // (show the circle)
canvas.sleep( 0.5 ); // (pause for half a second)
canvas.drawCircle( curX, curY, circleRadius, "white" ); // (hide the circle)
curAngle = curAngle + speed; // (step 6.1)
curX = sunX + orbitRadius * Math.cos( curAngle ); // (step 6.2)
curY = sunY + orbitRadius * Math.sin( curAngle ); // (step 6.2)
}
}
Here are example test cases in the run() procedure:
public void run()
{
// draw a ring of 10 circles; the ring is centered at (50, 80)
// each circle has radius 5 and is 40 units away from the ring center
drawCirclesRing( 50, 80, 40, 5, 10 );
// draw a ring of 6 circles; the ring is centered at (200, 100)
// each circle has radius 20 and is 90 units away from the ring center
drawCirclesRing( 200, 100, 90, 20, 6 );
// draw a moving planet of radius 5 traveling at speed .1 (approx 5.7 degrees per second)
// 40 units away from the sun centered at (60, 300)
drawMovingPlanet( 60, 300, 40, 5, 0.1 );
// draw a moving planet of radius 20 traveling at speed .3 (approx 17 degrees per second)
// 70 units away from the sun centered at (250, 350)
drawMovingPlanet( 250, 350, 70, 20, 0.3 );
}
Using procedures in other procedures
We have already seen one example of using a procedure that we wrote in another procedure
that we wrote. For example, inside drawSmileyRow we used drawSmiley to draw a single smiley.
The next example was to use drawCirclesRing inside drawSmiley to draw a ring of dots
as tattoos around the eyes of the Smiley
In order to use the procedure drawCirclesRing we had to decide:
- where the ring is centered -- it is centered at the eye, i.e. at (eyeX, eyeY)
- the radius of each dot -- we decided to make them 1/5th of the eye radius
- the radius of the ring -- this is the distance between the center of the eye and
the center of each dot, i.e. eye radius + dot radius
- the number of dots -- we decided on 6 dots
Overall, we made the following modifications to drawSmiley:
public class SmileyApp
{
// draws a smiley face centered at the given *(x,y)* and of the
// given *radius*; the face features are scaled relative to the face
void drawSmiley( double x, double y, double radius )
{
// draw the face
canvas.drawCircle( x, y, radius, "yellow" );
// draw the left eye
double eyeX = x + 0.3*radius;
double eyeY = y - 0.3*radius;
double eyeRadius = 0.2*radius;
canvas.drawCircle( eyeX, eyeY, eyeRadius );
// draw a ring of dots around the left eye
double dotRadius = 0.2*eyeRadius;
double ringRadius = eyeRadius + dotRadius;
drawCirclesRing( eyeX, eyeY, ringRadius, dotRadius, 6 );
// draw the right eye -- only the x coordinate needs to be updated
eyeX = x - 0.3*radius;
canvas.drawCircle( eyeX, eyeY, eyeRadius );
// draw a ring of dots around the right eye
drawCirclesRing( eyeX, eyeY, ringRadius, dotRadius, 6 );
// draw the mouth
double mouthX = x;
double mouthY = y + 0.3*radius;
double mouthRadius = 0.4*radius;
canvas.drawCircle( mouthX, mouthY, mouthRadius );
// draw the mouth cover -- only the y coordinate is updated
mouthY = y + 0.2*radius;
canvas.drawCircle( mouthX, mouthY, mouthRadius, "yellow" );
}
public void run()
{
drawSmiley( 150, 200, 60 ); // smiley of radius 60 centered at (150, 200)
drawSmiley( 10, 20, 8 ); // smiley of radius 8 centered at (10, 20)
drawSmiley( 300, 350, 70 ); // smiley of radius 70 centered at (300, 350)
}
}