I've been trying to understand arc svg since it seems I need them in plotly -- my goal is to plot circle intersections.
My original idea was something like this:
for every intersection, to find the start and end coordinates as well as the height - but I am not very sure of where to go from here. It seems I am lacking the rotation and Large Arc Flag / Sweep parameters, and I am not sure how I would go about retrieving them. If anyone could point me into the right direction here, that would be great!
Circles and intercepting points
Don't know much about SVG arcTo. MDN gives "A rx,ry xAxisRotate LargeArcFlag,SweepFlag x,y". as an arc in the path element. What rx and ry are??? I would guess radius for x,y.
I am guessing you would use it as
// x,y start position
// rx,ry radius x and y
// x1,y1 end position
<path d="M x,y A rx, ry, 0 1 1 x1, y1"/>
Below is the problem solved as javascript. I have Commented the part you need for the SVG. The two end points (intercepts)
There is a lot of redundancy but its not clear what you want so the code provides how to find other parts of two intersecting circles.
Law of Cosines
The math to solve the problem is called the law of cosines that is used to solve triangles.
In this case the triangle is created from 3 lengths. One each of the circle radius and one is the distance between circle centers. The image gives more details
With the angle c you can find the lengths GE, DE, and EF. If you want the angle for the other side at point f just swap B and C.
Example
Move mouse to check intercept.
const ctx = canvas.getContext("2d");
const m = {
x: 0,
y: 0
};
document.addEventListener("mousemove", e => {
var b = canvas.getBoundingClientRect();
m.x = e.pageX - b.left - scrollX;
m.y = e.pageY - b.top - scrollY;
});
const PI = Math.PI;
const PI2 = Math.PI * 2;
const circles = [];
function circle(x, y, r, col, f = 0, t = PI2, w = 2) {
var c;
circles.push(c = { x, y,r, col, f, t, w});
return c;
};
function drawCircle(A) {
ctx.strokeStyle = A.col;
ctx.lineWidth = A.w;
ctx.beginPath();
ctx.arc(A.x, A.y, A.r, A.f, A.t);
ctx.stroke();
}
function mark(x, y, r, c) {
ctx.strokeStyle = c;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.arc(x, y, r, 0, PI2);
ctx.stroke();
}
function line(A, B, c) {
ctx.strokeStyle = c;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.lineTo(A.x, A.y);
ctx.lineTo(B.x, B.y);
ctx.stroke();
}
// note I am sharing calc results between function
function circleIntercept(A, B) {
var vx, vy, dist, c, d, x, y, x1, y1, x2, y2, dir, a1, a2;
// Vec from A to B
vx = B.x - A.x;
vy = B.y - A.y;
// Distance between
dist = Math.sqrt(vx * vx + vy * vy);
// Are the intercepting
if (dist < A.r + B.r && dist > B.r - A.r) {
c = (B.r * B.r - (dist * dist + A.r * A.r)) / (-2 * dist);
// Find mid point on cord
x = A.x + vx * (c / dist);
y = A.y + vy * (c / dist);
mark(x, y, 5, "blue");
// Find circumference intercepts
//#################################################################
//=================================================================
// SVG path
// Use x1,y1 and x2,y2 as the start and end angles of the ArcTo SVG
d = Math.sqrt(A.r * A.r - c * c);
x1 = x - vy * (d / dist);
y1 = y + vx * (d / dist);
x2 = x + vy * (d / dist);
y2 = y - vx * (d / dist);
// SVG path from above coords
// d = `M ${x1}, ${y1} A ${A.r}, ${A,r1} 0, 1, 1, ${x2}, ${y2}`;
//=================================================================
// draw the chord
line({x: x1,y: y1}, {x: x2,y: y2}, "red");
// mark the intercepts
mark(x1, y1, 5, "Green");
mark(x2, y2, 5, "Orange");
// Get direction from A to B
dir = Math.atan2(vy, vx);
// Get half inside sweep
a1 = Math.acos(c / A.r);
// Draw arc for A
A.col = "black";
A.w = 4;
A.f = dir - a1;
A.t = dir + a1;
drawCircle(A);
A.col = "#aaa";
A.w = 2;
A.f = 0;
A.t = PI2;
// inside sweep for B
a2 = Math.asin(d / B.r);
// Draw arc for B
B.col = "black";
B.w = 4;
if (dist < c) {
B.t = dir - a2;
B.f = dir + a2;
} else {
B.f = dir + PI - a2;
B.t = dir + PI + a2;
}
drawCircle(B);
B.col = "#aaa";
B.w = 2;
B.f = 0;
B.t = PI2;
}
}
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var C1 = circle(cw, ch, ch * 0.5, "#aaa");
var C2 = circle(cw, ch, ch * 0.8, "#aaa");
function update(timer) {
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.globalAlpha = 1;
if (w !== innerWidth || h !== innerHeight) {
cw = (w = canvas.width = innerWidth) / 2;
ch = (h = canvas.height = innerHeight) / 2;
C1.x = cw;
C1.y = ch;
C1.r = ch * 0.5;
ctx.lineCap = "round";
}
C2.x = m.x;
C2.y = m.y;
ctx.clearRect(0, 0, w, h);
drawCircle(C1);
drawCircle(C2);
circleIntercept(C1, C2);
requestAnimationFrame(update);
}
requestAnimationFrame(update);
canvas {
position: absolute;
top: 0px;
left: 0px;
}
<canvas id="canvas"></canvas>
Let's start with some terminology to clear up what is a clockwise direction (remember the y axis of SVG goes down): the first circle has radius r1, the second r2.
If the center of the first circle is lower than that of the second (cy1 > cy2), then name the intersection point with the smaller x coordinate (x1, y1), and the other (x2, y2).
If the center of the first circle is higher than that of the second (cy1 < cy2), then name the intersection point with the greater x coordinate (x1, y1), and the other (x2, y2).
Else, name the intersection point with the smaller y coordinate (x1, y1), and the other (x2, y2).
Now we will draw an arc from the first to the second intersection point with the radius of the first circle. The first two arc parameters are the horizontal and vertical radius. Since we are drawing a circle, both are identical. For the same reason, rotating the radii does not make sense and the third parameter is 0.
The intersection of two circles always uses the small arc (the large arc would be used for the union), therefore the large arc flag is 0. We are drawing the arc clockwise, therefore the sweep flag is 1.
Still unclear? The spec uses this picture for explaining the flags:
The second arc is going from the second to the first intersection point with the radius of the second circle. The flags remain the same.
The result looks like this:
M x1, y1 A r1 r1 0 0 1 x2, y2 A r2 r2 0 0 1 x1, y1 Z
Related
The red circle is at a known angle of 130°, then I want to draw the navy line from the center to 130° using x and y of the red circle but it looks like I missed the calculation.
Currently, the angle of the Navy line is a reflection to the angle of the red line and if I add minus sign ➖ to *diffX * at line13, it'll work as expected but Why do I need to do that by myself, why can't the Calculations at line 10 and 13 figured out if x should be minus ➖ or plus.
I couldn't figure out where I was wrong..any help/suggestions are appreciated!
let ctx, W = innerWidth,
H = innerHeight;
// params for the red circle
let hypothenus = 100;
let knownAngle = (-130 * Math.PI) / 180;
let x = (W / 2) + Math.cos(knownAngle) * hypothenus;
let y = (H / 2) + Math.sin(knownAngle) * hypothenus;
// params for navy line
let diffX = x - (W / 2);
let diffY = (H / 2) - y;
let dist = Math.hypot(diffX, diffY); // pythagoras
let unknownAngle = -Math.atan2(diffY, diffX);
let newX = (W / 2) + Math.cos(unknownAngle) * dist;
let newY = (H / 2) + Math.sin(unknownAngle) * dist;
let angInDegree1 = ~~Math.abs(knownAngle * 180 / Math.PI);
let angInDegree2 = ~~Math.abs(unknownAngle * 180 / Math.PI) | 0;
const msg = document.getElementById("msg")
msg.innerHTML = `Hypothenus1: ${hypothenus}, angle: ${angInDegree1}<br>`;
msg.innerHTML +=`Hypothenus2: ${dist}, angle: ${angInDegree2}`;
// everything to be rendered to the screen
const update = () => {
if (ctx == null) return;
// drawing the red line
draw.line([W / 2, 0], [W / 2, H], 6, "red");
draw.line([0, H / 2], [W, H / 2], 6, "red");
// the red circle
draw.circle([x, y], 10, "red");
// draw line
draw.line([W / 2, H / 2], [newX, newY], 4, "navy");
}
// utility object for drawing
const draw = {
line(from, to, width, color) {
with(ctx) {
beginPath();
lineWidth = width;
strokeStyle = color;
moveTo(...from);
lineTo(...to);
stroke();
closePath();
}
},
circle(pos, radius, color) {
ctx.beginPath();
ctx.fillStyle = color;
ctx.arc(...pos, radius, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
}
}
// init function
const init = () => {
ctx = document.querySelector("#cvs").getContext("2d");
W = ctx.canvas.width = innerWidth;
H = ctx.canvas.height = innerHeight;
update();
}
window.addEventListener("load", init);
<div id="msg"></div>
<canvas id="cvs"></canvas>
Seems you are using too much minuses.
At first, you define angle -130 degrees, close to -3Pi/4. Cosine and sine values for this angle are about -0.7, using hypothenus = 100, we get x =W/2-70, y = H/2-70
diffX = x - W/2 = -70
diffY = y - H/2 = -70
atan2(-70, -70) gives -2.3561 radians = -3/4*Pi = -135 degrees
When you change sign of diffY (note - diffY formula is wrong, not difX one!), you make reflection against OX axis, and change angle sign - that is why another minus before Math.atan2 is required
Corrected code:
let diffX = x - (W / 2);
let diffY = y - (H / 2);
let dist = Math.hypot(diffX, diffY); // pythagoras
let unknownAngle = Math.atan2(diffY, diffX);
void ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle [, anticlockwise]);
The canvas context 2D API ellipse() method creates an elliptical arc centered at (x, y) with the radii radiusX and radiusY. The path starts at startAngle and ends at endAngle, and travels in the direction given by anticlockwise.
How to get the axis-aligned bounding box of a ellipse with the given parameters:x, y, radiusX, radiusY, rotation, startAngle, endAngle , anticlockwise?
Two solutions
This answer contains two exact solutions it is not an approximation.
The solutions are
boundEllipseAll will find the bounds of the full ellipse. If is a lot less complex than the full solution but you need to ensure that the x radius is greater than the y radius (eg rotate the ellipse 90 deg and swap the x, y radius)
boundEllipse Will find the bounds for a segment of an ellipse. It will work for all ellipses however I have not included the CCW flag. To get the bounds for CCW ellipse swap the start and end angles.
It works by first finding the x, y coords at the start and end points calculating the min and max along each axis. It then calculates the extrema angles, first for the x axis extremes, then the y axis extremes.
If the extrema angle is between the start and end angle, the x,y position of that angle is calculated and the point tested against the min and max extent.
There is a lot of room to optimize as many of the points need only the x, or y parts, and the inner while loop in function extrema can exit early if min and max are change for the axis it is working on.
Example
The example ensures I have not made any mistakes, and uses the second solution, animating an ellipse by moving the start and end angles, rotation, and y axis radius. Drawing the bounding box and the ellipse it bounds.
Update April 2022
Example shows use of both full ellipse boundEllipseAll and ellipse segment boundEllipse.
Note that boundEllipse is only for ellipse segment where endAngle n and startAngle m fit the rule {m <= n <= m + 2Pi}
Fixed bug in boundEllipse that did not show full ellipse when endAngle == startAngle + 2 * Math.PI
const ctx = canvas.getContext("2d");
const W = 200, H= 180;
const TAU = Math.PI * 2;
const ellipse = {
x: W / 2,
y: H / 2,
rx: W / 3,
ry: W / 3,
rotate: 0,
startAng: 0,
endAng: Math.PI * 2,
dir: false,
};
function boundEllipseAll({x, y, rx, ry, rotate}) {
const xAx = Math.cos(rotate);
const xAy = Math.sin(rotate);
const w = ((rx * xAx) ** 2 + (ry * xAy) ** 2) ** 0.5;
const h = ((rx * xAy) ** 2 + (ry * xAx) ** 2) ** 0.5;
return {x: -w + x, y: -h + y, w: w * 2, h: h * 2};
}
function boundEllipse({x, y, rx, ry, rotate, startAng, endAng}) {
const normalizeAng = ang => (ang % TAU + TAU) % TAU;
const getPoint = ang => {
const cA = Math.cos(ang);
const sA = Math.sin(ang);
return [cA * rx * xAx - sA * ry * xAy, cA * rx * xAy + sA * ry * xAx];
}
const extrema = a => { // from angle
var i = 0;
while(i < 4) {
const ang = normalizeAng(a + Math.PI * (i / 2));
if ((ang > startAng && ang < endAng) || (ang + TAU > startAng && ang + TAU < endAng)) {
const [xx, yy] = getPoint(ang);
minX = Math.min(minX, xx);
maxX = Math.max(maxX, xx);
minY = Math.min(minY, yy);
maxY = Math.max(maxY, yy);
}
i ++;
}
}
// UPDATE bug fix (1) for full ellipse
const checkFull = startAng !== endAng; // Update fix (1)
startAng = normalizeAng(startAng);
endAng = normalizeAng(endAng);
(checkFull && startAng === endAng) && (endAng += TAU); // Update fix (1)
const xAx = Math.cos(rotate);
const xAy = Math.sin(rotate);
endAng += endAng < startAng ? TAU : 0;
const [sx, sy] = getPoint(startAng);
const [ex, ey] = getPoint(endAng);
var minX = Math.min(sx, ex);
var maxX = Math.max(sx, ex);
var minY = Math.min(sy, ey);
var maxY = Math.max(sy, ey);
extrema(-Math.atan((ry * xAy) / (rx * xAx))); // Add x Axis extremas
extrema(-Math.atan((rx * xAy) / (ry * xAx))); // Add y Axis extremas
return {x: minX + x, y: minY + y, w: maxX - minX, h: maxY - minY};
}
function drawExtent({x,y,w,h}) {
ctx.moveTo(x,y);
ctx.rect(x, y, w, h);
}
function drawEllipse({x, y, rx, ry, rotate, startAng, endAng, dir}) {
ctx.ellipse(x, y, rx, ry, rotate, startAng, endAng, dir);
}
function drawFullEllipse({x, y, rx, ry, rotate, dir}) {
ctx.ellipse(x, y, rx, ry, rotate, 0, TAU, dir);
}
mainLoop(0);
function mainLoop(time) {
ctx.clearRect(0, 0, W, H);
// Animate ellipse
ellipse.startAng = time / 1000;
ellipse.endAng = time / 2000;
ellipse.rotate = Math.cos(time / 14000) * Math.PI * 2;
ellipse.ry = Math.cos(time / 6000) * (W / 4 - 10) + (W / 4);
// Draw full ellipse and bounding box.
ctx.strokeStyle = "#F008";
ctx.beginPath();
drawFullEllipse(ellipse);
drawExtent(boundEllipseAll(ellipse));
ctx.stroke();
// Draw ellipse segment and bounding box.
ctx.strokeStyle = "#0008";
ctx.beginPath();
drawEllipse(ellipse);
drawExtent(boundEllipse(ellipse));
ctx.stroke();
requestAnimationFrame(mainLoop)
}
canvas { border: 1px solid black }
<canvas id="canvas" width="200" height="180"></canvas>
for a game I'm building, I need to draw a rectangle on two sides of a line made from two coordinates.
I have an image illustrating this "hard to ask" question.
given coordinates (-4,3) and (3, -4)
given that the width of the rectangle will be 4 (for example)
I need to find all (x1, y1), (x2, y2), (x3, y3), (x4, y4)
** I need to write this in Javascript eventually.
your help is much appreciated.
I've tried to solve this using javascript & canvas. The problem is that the coordinates in canvas are upside down, I suppose you already know this. Also since your rect would be extremely small, I've multiplied your numbers by 10.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 300,
cx = cw / 2;
let ch = canvas.height = 300,
cy = ch / 2;
const rad = Math.PI / 180;
ctx.translate(cx,cy)
//axis
ctx.strokeStyle = "#d9d9d9";
ctx.beginPath();
ctx.moveTo(-cx,0);
ctx.lineTo(cx,0);
ctx.moveTo(0,-cy);
ctx.lineTo(0,cy);
ctx.stroke();
// your data
let p1={x:-40,y:30};
let p2={x:30,y:-40};
// the angle of the initial line
let angle = Math.atan2(p2.y-p1.y, p2.x-p1.x);
// the center of the line
let c =
{ x: p1.x + (p2.x - p1.x)/2,
y: p1.y + (p2.y - p1.y)/2
}
let w = dist(p1, p2);//the width of the rect
let h = 60;//the height of the rect
// draw the initial line
line(p1,p2);
// draw the center as a red point
marker(c);
// calculate the opoints of the rect
function rectPoints(w,h){
let p1 = {
x : c.x -w/2,
y : c.y -h/2
}
let p2 = {
x : c.x + w/2,
y : c.y -h/2
}
let p3 = {
x : c.x + w/2,
y : c.y +h/2
}
let p4 = {
x : c.x -w/2,
y : c.y +h/2
}
// this rotate all the points relative to the center c
return [
rotate(p1,c, angle),
rotate(p2,c, angle),
rotate(p3,c, angle),
rotate(p4,c, angle)
]
}
// draw the rect
ctx.strokeStyle = "blue";
drawRect(rectPoints(w,h));
// some helpful functions
function line(p1,p2){
ctx.beginPath();
ctx.moveTo(p1.x,p1.y);
ctx.lineTo(p2.x,p2.y);
ctx.stroke();
}
function dist(p1, p2) {
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
function marker(p,color){
ctx.beginPath();
ctx.fillStyle = color || "red";
ctx.arc(p.x,p.y,4,0,2*Math.PI);
ctx.fill();
}
function rotate(p,c, angle){
let cos = Math.cos(angle);
let sin = Math.sin(angle);
return {
x: c.x + (p.x - c.x) * cos - (p.y - c.y) * sin,
y: c.y + (p.x - c.x) * sin + (p.y - c.y) * cos
}
}
function drawRect(points){
ctx.beginPath();
ctx.moveTo(points[0].x,points[0].y);
ctx.lineTo(points[1].x,points[1].y);
ctx.lineTo(points[2].x,points[2].y);
ctx.lineTo(points[3].x,points[3].y);
ctx.lineTo(points[0].x,points[0].y);
ctx.closePath();
ctx.stroke();
}
canvas{border:1px solid #d9d9d9}
<canvas></canvas>
Points A, B form vector
M.X = B.X - A.X
M.Y = B.Y - A.Y
Perpendicular vector
P.X = -M.Y
P.Y = M.X
Length of P:
Len = Math.sqrt(P.X*P.X + P.Y*P.Y)
Normalized (unit) perpendicular:
uP.X = P.X / Len
uP.Y = P.Y / Len
Points
X1 = A.X + uP.X * HalfWidth
Y1 = A.Y + uP.Y * HalfWidth
(X4, Y4) = (A.X - uP.X * HalfWidth, A.Y - uP.Y * HalfWidth)
and similar for points 2 and 3 around B
i have tried this,
public drawNumbers(ctx, x1, y1, length, count) {
let angle = 0;
for (let i = 0; i <= count; i++ ) {
angle += 2 * Math.PI / (count );
const x2 = x1 + length * Math.cos(angle),
y2 = y1 + length * Math.sin(angle);
ctx.beginPath();
ctx.fillRect(x2, y2, 10, 20);
ctx.stroke();
}
}
this.canvas.drawNumbers(ctx, this.midX, this.midY, 160, 60);
output:
expected result:
i want to calculate a four coordinate(rectangle) of rotated axis.
How do i detect click event on each rectangle?
Using setTransform
Salix alba answer is a solution though a few too many steps.
It can be done in a single transform using setTransform and applying the translate and rotations in one step. Also the second translation is where you draw the box relative to its origin. When using transforms always draw objects around the center of rotation.
ctx.strokeRect(-10,-10,20,20); // rotation is always around 0,0
const ctx = canvas.getContext("2d");
const centerX = 250;
const centerY = 250;
const radius = 200;
const boxWidth = 10;
const bobLength = 20;
// draw boxs around circle center at cx,cy and radius rad
// box width bw, and box height bh
// spacing optional is the distance between boxes
function drawCircleOfBoxes(cx,cy,rad,bw,bh,spacing = 5){
var steps = ((rad - bw /2) * Math.PI * 2) / (bw + spacing) | 0; // get number boxes that will fit circle
ctx.beginPath();
for(var i = 0; i < steps; i ++){
const ang = (i / steps) * Math.PI * 2;
var xAxisX = Math.cos(ang); // get the direction of he xAxis
var xAxisY = Math.sin(ang);
// set the transform to circle center x Axis out towards box
// y axis at 90 deg to x axis
ctx.setTransform(xAxisX, xAxisY, -xAxisY, xAxisX, cx, cy);
// draw box offset from the center so its center is distance radius
ctx.rect(rad - bh / 2, -bw / 2, bh, bw);
}
ctx.fill();
ctx.stroke();
ctx.setTransform(1,0,0,1,0,0); // reset transform
}
ctx.fillStyle = "#FCD";
ctx.strokeStyle = "#000";
drawCircleOfBoxes(centerX, centerY, radius, boxWidth, bobLength);
<canvas id="canvas" width="500" height="500"></canvas>
Manually apply the transform to a point
If you wish to transform the box in code you can use the transform applied in the above and apply it directly to a set of points. You can not apply it to the ctx.rect function that needs the API transform.
To transform a point px,py you need the the direction of the rotated x axis
const xAx = Math.cos(dirOfXAxis);
const xAy = Math.sin(dirOfXAxis);
You can then move the point px distance along the xAxis and then turn 90 deg and move py distance along the y axis
var x = px * xAx; // move px dist along x axis
var y = px * xAy;
x += py * -xAy; // move px dist along y axis
y += py * xAx;
Then just add the translation
x += translateX;
y += translateY;
Or done in one go
var x = px * xAx - py * xAy + translateX; // move px dist along x axis
var y = px * xAy + py * xAx + translateY;
The snippet shows it in action
const ctx = canvas.getContext("2d");
const centerX = 250;
const centerY = 250;
const radius = 200;
const boxWidth = 10;
const boxLength = 20;
// draw boxs around circle center at cx,cy and radius rad
// box width bw, and box height bh
// spacing optional is the distance between boxes
function drawCircleOfBoxes(cx,cy,rad,bw,bh,spacing = 5){
var points = [ // setout points of box with coord (0,0) as center
{x : bh / 2, y : -bw / 2},
{x : bh / 2 + bh, y : -bw / 2},
{x : bh / 2 + bh, y : -bw / 2 + bw},
{x : bh / 2, y : -bw / 2 + bw},
];
var steps = (((rad - bw /2) * Math.PI * 2) / (bw + spacing) )+ 4| 0; // get number boxes that will fit circle
ctx.beginPath();
for(var i = 0; i < steps; i ++){
const ang = (i / steps) * Math.PI * 2;
const xAx = Math.cos(ang); // get the direction of he xAxis
const xAy = Math.sin(ang);
var first = true
for(const p of points){ // for each point
// Apply the transform to the point after moving it
// to the circle (the p.x + rad)
const x = (p.x + rad) * xAx - p.y * xAy + cx;
const y = (p.x + rad) * xAy + p.y * xAx + cy;
if(first){
ctx.moveTo(x,y);
first = false;
}else{
ctx.lineTo(x,y);
}
}
ctx.closePath();
}
ctx.fill();
ctx.stroke();
}
ctx.fillStyle = "#CFD";
ctx.strokeStyle = "#000";
for(var i = boxLength + 5; i < radius; i += boxLength + 5){
drawCircleOfBoxes(centerX, centerY, i , boxWidth, boxLength);
}
<canvas id="canvas" width="500" height="500"></canvas>
To get rotated rectangles you need to use the transform() method of the graphics context.
Imagine a set of axis at the top left of the drawing area. Any drawing will be done relative to these axis which we can move with transform.
To translate by xshift, yshift
ctx.transform(1,0,0,1, xshift, yshift);
ctx.fillRect(0,0,100,100);
To rotate by angle ang in radians
ctx.transform(Math.cos(ang),Math.sin(ang),
-Math.sin(ang),Math.cos(ang), 0,0);
We can combine things with three transformations. The first moves the origin to the center of the circle. Then rotate the axes about this point,
then shift the axes to where you want the shape to appear. Finally, draw the shape.
for(deg = 0; deg < 360; deg+=20) {
ctx.setTransform(1,0,0,1,0,0); // reset transformation
ang = deg * Math.PI/180;
ctx.transform(1,0,0,1,100,100); // shift origin
ctx.transform(Math.cos(ang),Math.sin(ang),
-Math.sin(ang),Math.cos(ang), 0,0);
ctx.transform(1,0,0,1,50,0);
ctx.fillRect(0,0,30,10);
}
You can achieve the same this using the translate and rotate
for(deg = 0; deg < 360; deg+=20) {
ctx.setTransform(1,0,0,1,0,0); // reset transformation
ang = deg * Math.PI/180;
ctx.translate(100,100); // shift origin
ctx.rotate(ang);
ctx.translate(50,0);
ctx.fillRect(0,0,30,10);
}
I am using following javascript algorithm to draw dashed line on canvas. this algo draws horizontal line correctly but unable to draw vertical line:
g.dashedLine = function (x, y, x2, y2, dashArray) {
this.beginPath();
this.lineWidth = "2";
if (!dashArray)
dashArray = [10, 5];
if (dashLength == 0)
dashLength = 0.001; // Hack for Safari
var dashCount = dashArray.length;
this.moveTo(x, y);
var dx = (x2 - x);
var dy = (y2 - y);
var slope = dy / dx;
var distRemaining = Math.sqrt(dx * dx + dy * dy);
var dashIndex = 0;
var draw = true;
while (distRemaining >= 0.1) {
var dashLength = dashArray[(dashIndex++) % dashCount];
if (dashLength > distRemaining)
dashLength = distRemaining;
var xStep = Math.sqrt((dashLength * dashLength) / (1 + slope * slope));
if (x < x2) {
x += xStep;
y += slope * xStep;
}
else {
x -= xStep;
y -= slope * xStep;
}
if (draw) {
this.lineTo(x, y);
}
else {
this.moveTo(x, y);
}
distRemaining -= dashLength;
draw = !draw;
}
this.stroke();
this.closePath();
};
following Points used for testing vertical line draw:
g.dashedLine(157, 117, 157,153, [10, 5]);
following Points used for testing horizontal line draw:
g.dashedLine(157, 117, 160,157, [10, 5]);
When the line is vertical, dx = 0 which leads to slope = Infinity. If you write your own dash logic then you need to handle the special case where dx = 0 (or very near 0). In this special case you would have to work with the inverse slope (i.e. dx / dy) and yStep (instead of xStep).
A bigger question is why are you writing your own dash logic. Canvas has built-in support for dashed lines. Call setLineDash() function to set the dash pattern. Restore the previous dash pattern when done. For example...
g.dashedLine = function (x, y, x2, y2, dashArray) {
this.beginPath();
this.lineWidth = "2";
if (!dashArray)
dashArray = [10, 5];
var prevDashArray = this.getLineDash();
this.setLineDash(dashArray);
this.moveTo(x, y);
this.lineTo(x2, y2);
this.stroke();
this.closePath();
this.setLineDash(prevDashArray);
};