# How to calculate angles and X,Y coordinates for drawing a hand of playing cards on a canvas

Scenario: I'm programming a module to draw a deck of cards on a canvas as though they were being held by a person.

Edit:

I've cleaned up the question as best I can to be clearer.

What I'm looking for now:

The X,Y coords of each star, which represent the center of each card, as well as its rotation from that point to form a semi-circle.

To make it simpler than before, you can now also assume:

1. The total angle the cards cover is 90º.
2. There are 5 cards.
3. The middle card will always be at X = 0
4. Each end card will be on a rotation of 30º (the left side will be -30º)
5. The canvas is a fixed size. Note: I put 200 as the height, though this is somewhat of an arbitrary number and I don't even know if it helps or not.

I should also mention that drawing is not exactly to scale, I did the best I can with a somewhat primitive tool for drawing geometric shapes

Solution:

The source for my solution can be found here: https://github.com/tcrosen/playing-cards

A working demo can be found here: http://tcrosen.github.com/playing-cards/demo.html

function drawHand(el, originX, originY, numberOfCards, cardWidth, cardHeight, showOrigin) {

// The angle off the origin that each card will be referenced from
// The +1 is added because we want the first card to be positioned above the origin
var angle = 180 / (numberOfCards + 1);

// How far each card will be from the origin of the hand.
// This is proportional to the size of the card so that larger cards avoid too much overlap
var radius = cardWidth * 1.2;

// Through trial & error I determined a small hand (3-5 cards) looks most realistic
// when the end cards are at a rotation of 30 degrees (90 - 5 * 12). However when larger hands are created
// the end cards must be rotated at a larger angle in order to be "held" properly.  Anything that would
// calculate to an angle > 30 (6 cards or more) is simply capped at 45 degrees.
var endRotation = 12 * numberOfCards > 60 ? 45 : 90 - 12 * numberOfCards;

// Find an equal angle to split the cards across the entire hand
var rotationIncrement = endRotation * 2 / (numberOfCards + 1);

// If the user wants to see the origin for debugging/design purposes, show an X there
if (showOrigin) {
$(el).append($('<span>X</span>').css('color', 'red').css('position', 'absolute').css('top', originY + 'px').css('left', originX + 'px'));
}

// Loop through each card
// *Note: I start at 1 (instead of 0) in order to avoid multiplying by 0 and ending up with flat angles.
//  If you are using an array of cards (eventual scenario) you would need to account for the 0 index
for (var i = 1; i <= numberOfCards; i++) {

//  Set the card rotation - always rotate from the end point so use the card number as a multiplier
var rotation = endRotation - rotationIncrement * i;

// The X,Y coordinates of each card.
// Note that the origin X,Y is added to each coordinate as a hand would almost never be generated from 0,0
// on an HTML canvas.

// This next algorithm is used to push the cards "up" by a larger amount the further you get from the middle of the hand.
// This is done because a higher number of cards will start to push down and form a sharper circle.
// By moving them up it evens out the semi-circle to appear like something that would be more realistically held by a human.
// And as usual, this value is affected by existing variables to always position the hand based on its previous metrics.
y = y - Math.abs(Math.round(numberOfCards / 2) - i) * rotationIncrement;

// HTML positions elements relative to the top left corner, but the CSS3 "rotation" property uses the center.
// So I cut the values in half and use the center to position the cards.
// *Note: I realize both this and the previous line could have been included in the first X,Y calculation. They are separated for clarity.
x = x - cardWidth / 2;
y = y - cardHeight / 2;

// Create the card using my jQuery plugin
var $card =$('<div></div>').card({
width: cardWidth,
text: i,
rotation: rotation,
top: y,
left: x
});

// Draw it in the parent element
$(el).append($card);
}
}

// Helper function to convert to radians from degrees since Javascript's Math library defaults to radians.
return degrees * Math.PI / 180;
}


Here are some screenshots of the results in action with different values:

-
A possible reason there is no answer posted yet is that you haven't constrained your system enough or haven't provided enough information.Are you unhappy with your current method of generation? If so, what do you exactly expect? The figure you give gives some idea, but is not really enough to pin point an exact solution. Perhaps you could describe your expected layout more? –  Aryabhata Mar 26 '12 at 19:59
@Aryabhata Well with what I have now the point of rotation is limited to a single vertical axis (the middle pixel of each card is the same Y). What I want is for them to form a semi-circle (represented in the first diagram). I guess to simplify you could say n = 5 but the angle of rotation and X,Y coords really have to stay variable or else I would be finished already :) –  Terry Mar 26 '12 at 20:34
If you think it will help I can remove the "what I have now" stuff and leave it as a straightforward math problem. I just come from the programmer community and it's common practice to show what you have already and where you are struggling so people see you aren't just asking for them to do your work for you. –  Terry Mar 26 '12 at 20:37
I suggest you edit your question to include the information provided in the comments. –  Aryabhata Mar 26 '12 at 21:18
Rewrote question for clarity –  Terry Mar 28 '12 at 3:16

Relative to the center of the circle, the cards are at $(-30^{\circ},-15^{\circ},0^{\circ},15^{\circ},30^{\circ})$. If we take the center as (0,0), the coordinates are then $(r \sin -30^{\circ},-r \cos -30^{\circ}), (r \sin -15^{\circ},-r \cos -15^{\circ})$ and so on. Then you just need a calculator or sine and cosine tables to get $$\begin {array} {ccc} \\ angle & sine & cosine \\-30 & -0.5 & 0.866 \\-15 &-0.259 & 0.966 \\ 0 & 0 & 1 \\15 & 0.259 & 0.966\\30 & 0.5 & 0.866\end {array}$$ If your center is not $(0,0)$, just add it to these values. If you have a different number of cards, you can just equally space the angles from $-30$ to $+30$ degrees
@TerryR: I realized your $y$ coordinate probably increases downward, so have changed the signs on $y$ to reflect that you want it negative. –  Ross Millikan Mar 28 '12 at 13:16
If I have understood your question properly, you should just be able to apply a simple planar rotation, ie: $$\begin{bmatrix} Cos(\theta) &-Sin(\theta)) \\ Sin (\theta)&Cos(\theta)) \end{bmatrix} *\begin{bmatrix} x\\y \end{bmatrix}$$ where * denotes matrix multiplication, and $\theta$ is the angle you want to rotate by. This will rotate the point (x,y) along a circle centered at the origin.
The OP's coordinates are not standard-the origin of angles is up and positive is clockwise. It still works, but the vector should be $\begin{bmatrix} 0\\r \end{bmatrix}$ –  Ross Millikan Mar 28 '12 at 4:17