SVG donut slice as path element (annular sector) - javascript

Ok so granted, its not a bug, but I am confounded by how to get a perfect circle arc between points via Bézier curve.
I need a shape like this:
So I've been calculating the four corner points like this from the center point, radius and angle with the following formula: (x?,y?)=(x+d cos α,y+d sin α), which in my coffeescript looks something like this:
x1 = centerPointX+outerRadius*Math.cos(currentAngle)
y1 = centerPointY+outerRadius*Math.sin(currentAngle)
x2 = centerPointX+innerRadius*Math.cos(currentAngle)
y2 = centerPointY+innerRadius*Math.sin(currentAngle)
x3 = centerPointX+outerRadius*Math.cos(currentAngle2)
y3 = centerPointY+outerRadius*Math.sin(currentAngle2)
x4 = centerPointX+innerRadius*Math.cos(currentAngle2)
y4 = centerPointY+innerRadius*Math.sin(currentAngle2)
How can I take the information I have and result in a path element with perfect circular curves?
(PS I am newish to SVG and if you want to help me out with the proper syntax for d= that would be cool, but I can always just write it myself. The challenge I would like help with is really more to do with Bézier.
UPDATE / SOLUTION
Using the answer below a guidance below is the function I actually used:
annularSector = (centerX,centerY,startAngle,endAngle,innerRadius,outerRadius) ->
startAngle = degreesToRadians startAngle+180
endAngle = degreesToRadians endAngle+180
p = [
[ centerX+innerRadius*Math.cos(startAngle), centerY+innerRadius*Math.sin(startAngle) ]
[ centerX+outerRadius*Math.cos(startAngle), centerY+outerRadius*Math.sin(startAngle) ]
[ centerX+outerRadius*Math.cos(endAngle), centerY+outerRadius*Math.sin(endAngle) ]
[ centerX+innerRadius*Math.cos(endAngle), centerY+innerRadius*Math.sin(endAngle) ]
]
angleDiff = endAngle - startAngle
largeArc = (if (angleDiff % (Math.PI * 2)) > Math.PI then 1 else 0)
commands = []
commands.push "M" + p[0].join()
commands.push "L" + p[1].join()
commands.push "A" + [ outerRadius, outerRadius ].join() + " 0 " + largeArc + " 1 " + p[2].join()
commands.push "L" + p[3].join()
commands.push "A" + [ innerRadius, innerRadius ].join() + " 0 " + largeArc + " 0 " + p[0].join()
commands.push "z"
return commands.join(" ")

Demo: http://phrogz.net/svg/procedural_annular_sector.xhtml
Usage:
annularSector( myPathElement, {
centerX:100, centerY:150,
startDegrees:190, endDegrees:230,
innerRadius:75, outerRadius:100
});
Core function:
// Options:
// - centerX, centerY: coordinates for the center of the circle
// - startDegrees, endDegrees: fill between these angles, clockwise
// - innerRadius, outerRadius: distance from the center
// - thickness: distance between innerRadius and outerRadius
// You should only specify two out of three of the radii and thickness
function annularSector(path,options){
var opts = optionsWithDefaults(options);
var p = [ // points
[opts.cx + opts.r2*Math.cos(opts.startRadians),
opts.cy + opts.r2*Math.sin(opts.startRadians)],
[opts.cx + opts.r2*Math.cos(opts.closeRadians),
opts.cy + opts.r2*Math.sin(opts.closeRadians)],
[opts.cx + opts.r1*Math.cos(opts.closeRadians),
opts.cy + opts.r1*Math.sin(opts.closeRadians)],
[opts.cx + opts.r1*Math.cos(opts.startRadians),
opts.cy + opts.r1*Math.sin(opts.startRadians)],
];
var angleDiff = opts.closeRadians - opts.startRadians;
var largeArc = (angleDiff % (Math.PI*2)) > Math.PI ? 1 : 0;
var cmds = [];
cmds.push("M"+p[0].join()); // Move to P0
cmds.push("A"+[opts.r2,opts.r2,0,largeArc,1,p[1]].join()); // Arc to P1
cmds.push("L"+p[2].join()); // Line to P2
cmds.push("A"+[opts.r1,opts.r1,0,largeArc,0,p[3]].join()); // Arc to P3
cmds.push("z"); // Close path (Line to P0)
path.setAttribute('d',cmds.join(' '));
function optionsWithDefaults(o){
// Create a new object so that we don't mutate the original
var o2 = {
cx : o.centerX || 0,
cy : o.centerY || 0,
startRadians : (o.startDegrees || 0) * Math.PI/180,
closeRadians : (o.endDegrees || 0) * Math.PI/180,
};
var t = o.thickness!==undefined ? o.thickness : 100;
if (o.innerRadius!==undefined) o2.r1 = o.innerRadius;
else if (o.outerRadius!==undefined) o2.r1 = o.outerRadius - t;
else o2.r1 = 200 - t;
if (o.outerRadius!==undefined) o2.r2 = o.outerRadius;
else o2.r2 = o2.r1 + t;
if (o2.r1<0) o2.r1 = 0;
if (o2.r2<0) o2.r2 = 0;
return o2;
}
}

Related

Isometric simple sorting with xdim and ydim

I have a simple isometric sorting system with this function (code is in Typescript/Javascript) :
public Sort(a: PIXI.Sprite, b: PIXI.Sprite) {
return ((a.IsoZ - b.IsoZ) == 0 ? (a.TileZ - b.TileZ == 0 ? (a.Tile2Z ? (a.Tile2Z < b.Tile2Z ? -1 : (a.Tile2Z > b.Tile2Z ? 1 : 0)) : 0) : a.TileZ - b.TileZ) : (a.IsoZ - b.IsoZ));
}
It depends on three parameters:
IsoZ: the first sorting variables, used to sort tiles
TileZ: the tile
sorting variable, used if a.IsoZ == b.IsoZ
Tile2Z: used if a.TileZ == b.TileZ
Here's how IsoZ is basically calculated for most objects:
this.Position is an array of x and y coordinates
this.Position[0] + this.Position[1] + 1000;
now I want to support object x and y dimensions, so how can I implement something like this in this expression?
x and y dimensions values are for example (2, 2) for a cube or (2, 4) for a cuboid
this.Position[0] + this.Position[1] + 1000 // + x dimension + y dimension ???
Isometric visual occlusion sort (depth sort)
Defining depth:
Higher depths values are closer to the screen. Unlike 3D perspective projection where depth is distance from the front plane, this answer uses depth as distance towards the screen.
Iso projection
If you have a iso projection
const P2 = (x = 0,y = 0) => ({x, y});
const isoProjMat = {
xAxis : P2(1 , 0.5),
yAxis : P2(-0.5, 1 ),
zAxis : P2(0 , -1 ),
}
That takes a 3d point and projects to screen space
const P3 = (x = 0, y = 0, z = 0) => ({x, y, z});
isoProjMat.project = function (p, retP = P2()) { // p is 3D point
retP.x = p.x * this.xAxis.x + p.y * this.yAxis.x + p.z * this.zAxis.x;
retP.y = p.x * this.xAxis.y + p.y * this.yAxis.y + p.z * this.zAxis.y;
return retP;
}
You can add the depth of a point as the z value of the 2D projected point. You need to add a transform axis for the depth.
isoProjMat.depth = P3(0.5,1, 1 );
For x move closer by half its size, y * 1 and z * 1.
The modified project now adds z to the returned point.
isoProjMat.project = function (p, retP = P3()) {
retP.x = p.x * this.xAxis.x + p.y * this.yAxis.x + p.z * this.zAxis.x;
retP.y = p.x * this.xAxis.y + p.y * this.yAxis.y + p.z * this.zAxis.y;
retP.z = p.x * this.depth.x + p.y * this.depth.y + p.z * this.depth.z;
return retP;
}
Thus for a set of points in 3D space projected to 2D iso screen space you sort on the z
const points = mySetOfPoints(); // what ever your points come from
const projected = points.map(p => isoProjMat.project(p));
projected.sort((a,b) => a.z - b.z);
All good for points but for sprites which occupy a 3D volume this does not work.
What you need to do is add a bounding volume ie a square. If your projection is static then we can simplify the bounding volume to the nearest point. For the box that is the vertex at the top bottom right eg sprite at (0,0,0) has a size (10,10,20) the nearest point in 3d is at (10,10,20).
I can not work your sort out as there is not enough info in the question but I am guessing sprite.Iso is the base origin of the sprite and sprite.Tile & Tile2 represent bounding box.
Thus to get the nearest point
const depthProj = P3(0.5,1, 1 ); // depth projection matrix
// get the depth of each sprite adding the property depth
sprites.forEach(spr => {
const p = {
x : spr.IsoX + Math.max(spr.TileX,spr.Tile2X),
y : spr.IsoY + Math.max(spr.TileY,spr.Tile2Y),
z : spr.IsoZ + Math.max(spr.TileZ,spr.Tile2Z)
};
spr.depth = p.x * depthProj.x + p.y * depthProj.y + p.z * depthProj.z;
})
sprites.sort((a,b) => a.depth - b.depth);
Then render from index 0 up.
An example.
The following is not fully applicable as it sorts by polygons and uses the polygons mean depth rather than its max depth (really should use max but cant be bothered ATM)
I add it only to show how the above code for the isoProjMat is used. It draws stacked boxes from pixel alpha and color rendered on a canvas.
Click rendered result to switch projections from bi-morphic to tri-morphic (as you did not specify the type of projection you used this shows how the depth transform changes between two types of parallel projection.
const ctx = canvas.getContext("2d");
var count = 0;
var firstRun = 0;
function doIt(){
// 3d 2d points
const P3 = (x=0, y=0, z=0) => ({x,y,z});
const P2 = (x=0, y=0) => ({x, y});
// isomorphic projection matrix
const isoProjMat = {
xAxis : count ? P2(1 , 0.5) : P2(1 , 0.5) ,
yAxis : count ? P2(-0.5, 1) : P2(-1 , 0.5) ,
zAxis : count ? P2(0 , -1) : P2(0 , -1) ,
depth : count ? P3(0.5,1, 1) : P3(0.5,0.5,1) , // projections have z as depth
origin : P2(), // (0,0) default 2D point
project (p, retP = P3()) {
retP.x = p.x * this.xAxis.x + p.y * this.yAxis.x + p.z * this.zAxis.x + this.origin.x;
retP.y = p.x * this.xAxis.y + p.y * this.yAxis.y + p.z * this.zAxis.y + this.origin.y;
retP.z = p.x * this.depth.x + p.y * this.depth.y + p.z * this.depth.z;
return retP;
}
}
// isomorphic mesh shape as vertices and polygons
const isoMesh = (()=>{
const polygon = {
inds : null,
depth : 0,
fillStyle : "#888",
lineWidth : 0.5,
strokeStyle : "#000",
setStyle(ctx) {
ctx.fillStyle = this.fillStyle;
ctx.lineWidth = this.lineWidth;
ctx.strokeStyle = this.strokeStyle;
},
}
const isoShape = {
verts : null,
pVerts : null, // projected verts
polys : null,
addVert(p3 = P3()) { this.verts.push(p3); return p3 },
addPoly(poly = isoShape.createPoly()) { this.polys.push(poly); return poly },
createPoly(options = {}) { return Object.assign({}, polygon, {inds : []}, options) },
render(ctx,mat = isoProjMat) {
var i,j,d;
const pv = this.pVerts === null ? this.pVerts = [] : this.pVerts;
const v = this.verts;
const ps = this.polys;
for(i = 0; i < v.length; i += 1){ pv[i] = mat.project(v[i], pv[i]) }
for(i = 0; i < ps.length; i += 1) {
const p = ps[i];
j = 0; d = 0;
while(j < p.inds.length) { d += pv[p.inds[j++]].z }
p.depth = d / p.inds.length;
}
ps.sort((a,b)=>a.depth - b.depth);
for(i = 0; i < ps.length; i += 1) {
const p = ps[i];
p.setStyle(ctx);
ctx.beginPath();
j = 0;
while(j < p.inds.length) { ctx.lineTo(pv[p.inds[j]].x, pv[p.inds[j++]].y) }
if (p.fillStyle !== "") { ctx.fill() }
if (p.strokeStyle !== "" && p.lineWidth !== 0) {ctx.closePath(); ctx.stroke() }
}
}
}
return () => Object.assign({},isoShape,{verts : [], polys : []});
})();
// Lazy coding I am using Point3 (P3) to hold RGB values
function createBoxMesh(box = isoMesh(), pos = P3(), size = P3(10,10,10), rgb = P3(128,128,128)){ // x,y,z are sizes in those directions
const PA3 = (x,y,z) => P3(x + pos.x, y + pos.y, z + pos.z);
const RGB = (s) => `rgb(${(rgb.x * s) | 0},${(rgb.y * s) | 0},${(rgb.z * s) | 0})`;
const indA = (inds) => inds.map(ind => ind + i);
const i = box.verts.length; // get top vert index
if(typeof size === "number") { size = P3(size,size,size) }
const x = size.x / 2;
const y = size.y / 2;
const z = size.z;
box.addVert(PA3(-x,-y, 0)); // ind 0
box.addVert(PA3( x,-y, 0));
box.addVert(PA3( x, y, 0));
box.addVert(PA3(-x, y, 0));
box.addVert(PA3(-x,-y, z)); // ind 4
box.addVert(PA3( x,-y, z));
box.addVert(PA3( x, y, z));
box.addVert(PA3(-x, y, z));
// box.addPoly(box.createPoly({ inds : indA([0,1,5,4]), fillStyle : RGB(0.5) }));
box.addPoly(box.createPoly({ inds : indA([1,2,6,5]), fillStyle : RGB(0.7) }));
box.addPoly(box.createPoly({ inds : indA([2,3,7,6]), fillStyle : RGB(1) }));
// box.addPoly(box.createPoly({ inds : indA([3,0,4,7]), fillStyle : RGB(0.8) }));
box.addPoly(box.createPoly({ inds : indA([4,5,6,7]), fillStyle : RGB(1.5) }));
return box;
}
function createDrawable(w,h){
const c = document.createElement("canvas");
c.width = w;
c.height = h;
c.ctx = c.getContext("2d");
return c;
}
const map = createDrawable(40,30);
map.ctx.font = "20px arial";
map.ctx.textAlign = "center";
map.ctx.textBaseline = "middle";
map.ctx.fillStyle = "rgba(0,128,0,0.5)";
map.ctx.strokeStyle = "rgba(255,0,0,0.5)";
map.ctx.lineWidth = 2;
map.ctx.fillRect(1,1,map.width - 2, map.height - 2);
map.ctx.strokeRect(1,1,map.width - 2, map.height - 2);
map.ctx.fillStyle = "#AAA";
map.ctx.strokeStyle = "rgba(255,128,0,0.5)";
map.ctx.strokeText("text",map.width / 2, map.height / 2);
map.ctx.fillText("text",map.width / 2, map.height / 2);
var dat = map.ctx.getImageData(0, 0, map.width , map.height).data;
ctx.setTransform(1,0,0,1,0,0);
// get total projection area and size canvas so that the iso projection fits
const boxSize = P3(10,10,5);
const topLeft = isoProjMat.project(P3(0,0,10 * boxSize.z));
const botRight = isoProjMat.project(P3(map.width * boxSize.x,map.height * boxSize.y,0));
const topRight = isoProjMat.project(P3(map.width * boxSize.x,0,0));
const botLeft = isoProjMat.project(P3(0,map.height * boxSize.y,0));
canvas.width = ((topRight.x - botLeft.x) + 10)|0;
canvas.height = ((botRight.y - topLeft.y) + 10)|0;
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Rendering will take a moment.",Math.min(innerWidth,canvas.width)/2,Math.min(innerHeight,canvas.height)/2)
setTimeout(function(){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.setTransform(1,0,0,1,-botLeft.x+10,-topLeft.y+10);
const alphaThresh = 100;
const boxes = isoMesh();
for(var y = 0; y < map.height; y ++){
for(var x = 0; x < map.width; x ++){
const ind = (x + y * map.width) * 4;
if(dat[ind + 3] > alphaThresh){
const h = (((dat[ind + 3]-alphaThresh)/(255-alphaThresh)) * 10) | 0;
for(var z = 0; z < h; z++){
createBoxMesh(
boxes,
P3(x * boxSize.x,y * boxSize.y, z * boxSize.z),
boxSize,
P3(dat[ind],dat[ind+1],dat[ind+2])
);
}
}
}
}
boxes.render(ctx);
if(firstRun === 0){
firstRun = 1;
ctx.setTransform(1,0,0,1,0,0);
ctx.font = "24px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillStyle = "black";
ctx.fillText("Bimorphic projection. Click for Trimorphic projection..",canvas.width/2,30)
canvas.onclick =()=>{
count += 1;
count %= 2;
doIt();
};
}
},0);
};
doIt();
canvas {
border : 2px solid black;
}
<canvas id="canvas"></canvas>

How to draw multiple self-edges in a node-link diagram in D3

Drawing a single self-link on a node in a node-link diagram can be done as described here: D3 Force Layout Self-Linking Node
What would you change if you need to draw multiple links on the same node?
I tried to add a 'rotation' to it based on the number of self-links that exist.
Given the code from the linked example I made the following changes:
function tick() {
link.attr("d", function(d) {
var x1 = d.source.x,
y1 = d.source.y,
x2 = d.target.x,
y2 = d.target.y,
dx = x2 - x1,
dy = y2 - y1,
dr = Math.sqrt(dx * dx + dy * dy),
// Defaults for normal edge.
drx = dr,
dry = dr,
xRotation = 0, // degrees
largeArc = 0, // 1 or 0
sweep = 1; // 1 or 0
// Self edge.
if ( x1 === x2 && y1 === y2 ) {
// Fiddle with this angle to get loop oriented.
var index = getIndexOfDuplicateEdge();
var degree = 360 / numberOfDuplicateEdges();
var degreeForIndex = degree * index;
xRotation = degreeForIndex; // Previously: -45;
// Needs to be 1.
largeArc = 1;
// Change sweep to change orientation of loop.
//sweep = 0; // I also tried to change it based on index % 2
// Make drx and dry different to get an ellipse
// instead of a circle.
drx = 30;
dry = 20;
// For whatever reason the arc collapses to a point if the beginning
// and ending points of the arc are the same, so kludge it.
x2 = x2 + 1;
y2 = y2 + 1;
}
return "M" + x1 + "," + y1 + "A" + drx + "," + dry + " " + xRotation + "," + largeArc + "," + sweep + " " + x2 + "," + y2;
});
This won't draw my ellipses as expected and I cannot find a way to handle this. Based on SVG from Mozilla the large-arc has to be 1. Sweep can be 0 or 1 and will 'mirror' my ellipsis. I can use xRotation between 90-180 with sweep 0/1 which will cover 180 degrees of my circle. However, i do not find a way to draw the ellipsis at the other 180 degree positions.
The number of self-links can vary, and I always want to have the 'best' distribution between ellipsis.
Ideally, it should look like:
The idea is to divide the circle into as many segments as petals your flower has. Then calculate the start- and end points for each petal on the circle and fitting an elipse on those points.
You can use the following code snippet to do achieve this: (the function assumes you have a svg element with the id "svgthing")
function radtodeg(angle) {
return angle * (180/Math.PI);
}
function flower( center_x, center_y, num_self_edges, start_angle, end_angle, radius, length ) {
var angle_sector = end_angle - start_angle;
var num_points = num_self_edges * 2;
var angle_per_point = angle_sector / num_points;
var angle_per_sector = angle_per_point * 2;
var str_builder = [];
for( var angle = start_angle; angle < end_angle; angle += angle_per_sector ) {
var start_sector_angle = angle;
var end_sector_angle = angle + angle_per_point;
var mid_sector_angle = angle + angle_per_point / 2;
var start_x = center_x + (radius * Math.cos(start_sector_angle));
var start_y = center_y + (radius * Math.sin(start_sector_angle));
var end_x = center_x + (radius * Math.cos(end_sector_angle));
var end_y = center_y + (radius * Math.sin(end_sector_angle));
var mid_x = center_x + (radius * Math.cos(mid_sector_angle));
var mid_y = center_y + (radius * Math.sin(mid_sector_angle));
str_builder.push("<path d='");
str_builder.push("M" + start_x + " " + start_y + ",");
str_builder.push("A " + length + " 1 " + radtodeg(mid_sector_angle) + " 0 1 " + end_x + " " + end_y);
str_builder.push("'/>\n");
str_builder.push("<circle cx='" + start_x + "' cy='" + start_y + "' r='5' />\n");
str_builder.push("<circle cx='" + end_x + "' cy='" + end_y + "' r='5'/>\n");
str_builder.push("<circle cx='" + mid_x + "' cy='" + mid_y + "' r='5'/>\n");
}
str_builder.push("<circle cx='" + center_x + "' cy='" + center_y + "' r='" + radius + "' />\n");
$("#svgthing").html(str_builder.join(""));
}
flower(60, 50, 8, 0, 2 * Math.PI, 50, 10);
The example call will generate a flower with 8 petals.

NodeJS, multiple attempts at getting 360 angle between two points fails

I'm trying to simple get the angle between two points in Node JS, I've gone though a few answers and none of them seem to work.
Here's what I've got so far as a test.js file.
function getDegree(x1,y1, x2,y2) {
var dy = y2 - y1;
var dx = x2 - x1;
var theta = Math.atan2(dy, dx); // range (-PI, PI]
theta *= 180 / Math.PI; // rads to degs, range (-180, 180]
// range [0, 360)
if (theta < 0) {
theta = 360 + theta;
}
return theta;
}
function output(expected, angle) {
console.log('\n');
var result = getDegree(angle[0], angle[1], angle[2], angle[3]);
console.log('getDegree(' + angle + ') = ' + result);
console.log('Expected: ' + expected + ', Result: ' + result + ' : ' + (expected == result));
}
var angle = [0,0, 0,0]; //(Current Pos), (Pos to Go to)
angle = [0,0,0,0];
output(0, angle);
angle = [0,0,0,9];
output(0, angle);
angle = [0,0,9,0];
output(90, angle);
angle = [0,0,0,-9];
output(180, angle);
angle = [0,0,-9,0];
output(270, angle);
// Should be 90ish
angle = [5,5,31,4];
output(90, angle);
module.exports = {
getDegree: getDegree
};
And the output by running directly from node in the same directory.
var test = require('./test');
getDegree(0,0,0,0) = 0
Expected: 0, Result: 0 : true
getDegree(0,0,0,9) = 90
Expected: 0, Result: 90 : false
getDegree(0,0,9,0) = 0
Expected: 90, Result: 0 : false
getDegree(0,0,0,-9) = 270
Expected: 180, Result: 270 : false
getDegree(0,0,-9,0) = 180
Expected: 270, Result: 180 : false
// 90ish
getDegree(5,5,31,4) = 357.7974018382342
Expected: 90, Result: 357.7974018382342 : false
The angle you are looking for is different from what math.atan2 gives you.i made this image , try to understand it.
so what you can do ? swap y and x.
you should substitute angle from 90 .
θ -> 90-θ
tan(90- θ) = cot θ // you can use this one too without interchanging x and y
cot θ = 1 / tan θ // so that's why you should swap x and y
as you can see you have to use 1/tan θ same as swapping y and x
var theta = Math.atan2(dx, dy);

Draw regular polygons inscribed in a circle

I'm trying to draw regular polygons(square and equilateral triangle) inscribed in a circle of a given centre (x,y) and a radius (r). I'm using raphael.js.
Here's my function to draw a inscribed square:
function draw_square(x,y,radius){
var side= radius*(Math.sqrt(2));
var x = x - (side/2);
var y = y - (side/2);
var square= paper.rect(x, y, side, side);
}
Can anyone shed some light on how I could draw an equilateral triangle(inscribed in a given circle)?
First time I've used raphael, so you'll have to extract what you need from the following:
<html>
<body>
<div id="paper"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.4/raphael-min.js"></script>
<script>
var paper = new Raphael(document.getElementById('paper'), 256, 256);
var x = 128, y = 128, r = 64, n = 9;
paper.circle(x, y, r);
var xx, yy, i, a, pathString = "";
for (i = 0; i <= n; ++i) {
a = ((4 * Math.PI * i) + (Math.PI * n) + (2 * Math.PI)) / (2 * n);
xx = x + r * Math.cos(a);
yy = y + r * Math.sin(a);
pathString += (i == 0 ? "M " : " L ") + xx + " " + yy;
}
pathString += " z";
paper.path(pathString);
</script>
</body>
</html>
EDIT: Refactored to use var a, and to always have a horizontal base.
function draw_triangle(x, y, radius){
var x_offset =radius*(Math.cos(Math.PI/6));
var y_offset =radius*(Math.sin(Math.PI/6));
var x1 = x;
var y1 = y - radius;
var x2 = x + x_offset;
var y2 = y + y_offset;
var x3 = x - x_offset;
var y3 = y + y_offset;
var triangle = "M"+x1+","+y1+"L"+x2+","+y2+"L"+x3+","+y3+"Z";
var triangle= paper.path(triangle);
}
With a little help of trigo and raphael paper.path().

How can I gradually animate path to be circle using Rapheal.js library

I have a circular sector, I want to animate the sector gradually to be a complete circle, how can I make it with Rapheal.js.
BTW, I am new to SVG, and I am using svg-editor to draw the shapes using SVG.
here's my code
var arcPath1 = paper.path("m325.30255,127.93972c33.86002,6.238 52.02301,8.85 90.823,49.616l105.53491,-65.494c-44.84491,-59.9466 -101.70261,-109.25547 -201.98761,-108.83956l5.6297,124.71756z");
arcPath1.attr({
"fill": "#003a60",
stroke: 'none'
});
arcPath1.rotate(1.0569896697998047 ,420.6666870117177,90.38764953613116);
arcPath1.node.id= "arch1";
here is an example of my code http://jsfiddle.net/v2KeV/
How can I make the animation to to complete the path to be a complete circle?
Note: the animation I make it just testing for path animation..
Well, first things first: in order to animate the arc segment gracefully, we'll need to be able to generate them programmatically. Consider this clumsy bit of trigonometry:
// arcPath:
// cx, cy are the center point
// inner_radius and outer_radius describe the distance of the arc segment's inner and outer boundaries from the center point.
// starting_radians describes the offset of the segment start;
// arc_radians describes the width of the arg segment.
function arcPath( cx, cy, inner_radius, outer_radius, starting_radians, arc_radians )
{
var x1 = cx + Math.cos( starting_radians ) * inner_radius;
var y1 = cy + Math.sin( starting_radians ) * inner_radius;
var x2 = cx + Math.cos( starting_radians ) * outer_radius;
var y2 = cy + Math.sin( starting_radians ) * outer_radius;
var x3 = cx + Math.cos( starting_radians + arc_radians ) * outer_radius;
var y3 = cy + Math.sin( starting_radians + arc_radians ) * outer_radius;
var x4 = cx + Math.cos( starting_radians + arc_radians ) * inner_radius;
var y4 = cy + Math.sin( starting_radians + arc_radians ) * inner_radius;
var pathstr = "M" + x1 + "," + y1 + " "
+ "L" + x2 + "," + y2 + " "
+ "A" + outer_radius + "," + outer_radius + " " + arc_radians + " " + ( arc_radians > Math.PI ? "1" : "0" ) + " 1 " + x3 + "," + y3 + " "
+ "L" + x4 + "," + y4 + " "
+ "A" + inner_radius + "," + inner_radius + " " + ( 0 - arc_radians ) + " " + ( arc_radians > Math.PI ? "1" : "0" ) + " 0 " + x1 + "," + y1 + " z";
return pathstr;
}
With a simpler geometrical construct, you could simply animate the path from the partial arc segment to the whole arc segment and let Raphael do the lifting, like so:
var arcPath1 = paper.path(arcPath( 150, 150, 75, 125, Math.PI / 2, Math.PI * 0.65 ) )
.attr({ fill: "#003a60", 'fill-opacity': 0.5, stroke: 'black' });
arcPath1.click(function ()
{
arcPath1.animate( { path: arcPath( 150, 150, 75, 125, Math.PI / 2, Math.PI * 2 - 0.0001 ) }, 2000, "<>" );
});
Unfortunately, Raphael does an absolutely terrible job of this, and so you get an explosion of intersecting arc segments reshaping themselves into a complete circle instead of the desired arc sweep. So in order to make it do what you want, we'll have to perform the animation manually.
function arcSweep( arc, x, y, inner, outer, angle_offset, from_sweep, to_sweep, duration )
{
var steps = 100;
var current_step = 0;
var intervalID = setInterval( function()
{
current_step++;
if ( current_step >= steps )
clearInterval( intervalID );
arc.attr( { path: arcPath( x, y, inner, outer, angle_offset, from_sweep + ( ( to_sweep - from_sweep ) * ( current_step / steps ) ) ) } );
}, duration / steps );
}
So basically we just calculate a series of intermediate arcs and institute them sequentially until we've reached the target angle. The animation function requires all of the original parameters for the arc in addition to the target ending angle (which must NOT be Math.PI * 2 -- since that calculates as an empty arc segment!). It might be desirable to abstract this into a class so that each instance of the arc segment can keep track of its own variables.
Here's a fiddle demonstrating the finished product.

Categories

Resources