Raphael JS sweeping clock - javascript

Using Raphael JS I want to have a continuing sweeping clock.
I have kind of got it implemented but there is a problem.
The transform on the animation constantly requries a increasing integer to rotate. Eventually I'm going to hit a max integer.
If I use 0,90,180,270. When it hits 0 again it goes back the other way.
CodePen ->
http://codepen.io/ianw92/pen/yNLdZz
Javascript:
var svg = Raphael("container",400,400),
triangle = svg.path("M210 200L190 200L200 100Z").attr({fill:"#000"}),
circle = svg.circle(200,200,5).attr({fill:"#f00"});
// Rotation settings
var handAngle = 45,
centerX = 200,
centerY = 200;
y = 0
function a() {
y = (y + 90) % 360;
triangle.animate({transform: "r"+y + "," + centerX + "," + centerY}, 500, b);
}
function b() {
y = (y + 90) % 360;
triangle.animate({transform: "r"+y + "," + centerX + "," + centerY}, 500, a);
}
a()

Key was to use Raphael.animation(...).repeat(Infinity)
http://codepen.io/ianw92/pen/xGbKJq
var anim=Raphael.animation({transform: "r360" + "," + centerX + "," + centerY}, 60000, 'linear').repeat(Infinity);
triangle.animate(anim);

Related

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.

How to draw a rounded corner in rectangle's bottom svg JS

I want to draw a rectangle with rounded corner only on the bottom of the place without using external source using HTML5 SVG element. Any one can help on this?. I have attached the expected output.
Thanks,
Bharathi
Below is a function entitled makeShape that draws the desired shape. The input parameters are x (i.e. left), y (i.e. top), width, height and r (i.e. corner radius). It returns an svg path element.
To learn more you can consult this general reference for SVG, as well as specific information about how to create a path.
The demo below creates shapes with random positions and dimensions, including random corner radii. Comments in the code explain how this is done.
// the shape creation function
var makeShape = function(x, y, width, height, r) {
var xmlns = "http://www.w3.org/2000/svg";
var
spc = " ", // path drawing instruction letters with readable names
moveTo = "M",
horizLineTo = "h",
vertLineTo = "v",
arcTo = "a",
closePath = "z";
var dStr = // the "d" path for the svg path
moveTo + spc + x + spc + y + spc +
vertLineTo + spc + (height - r) + spc +
arcTo + spc + r + spc + r + spc + 0 + spc + 0 + spc + 0 + spc + r + spc + r + spc +
horizLineTo + spc + (width - 2 * r) + spc +
arcTo + spc + r + spc + r + spc + 0 + spc + 0 + spc + 0 + spc + r + spc + (-r) + spc +
vertLineTo + spc + (r - height) + spc +
closePath;
var shape = document.createElementNS(xmlns, "path"); // create the (orphan) svg path element
shape.setAttribute("d", dStr); // add the "d" attribute to the path, giving it a shape
return shape; // return it from the function
};
// demo code for creating randomly positioned and proportioned example shapes
document.querySelector("button").addEventListener("click", function() {
var path = document.querySelector("path"); // if any previous paths exist, delete them
if (path) {path.parentNode.removeChild(path);}
var x = Math.random() * 140 + 10; // calculate random location and proportions for the shape
var y = Math.random() * 65 + 10;
var width = 400 - x * 2;
var height = 150 - y * 2;
var r = Math.random() * Math.min(width / 2, height); // the corner radius
var shape = makeShape(x, y, width, height, r); // create the orphan shape
shape.setAttribute("stroke", "grey"); // style its appearance
shape.setAttribute("fill", "red");
shape.setAttribute("stroke-width", "4");
document.querySelector("svg").appendChild(shape); // add the shape to the SVG DOM
});
<svg width="400" height="150"></svg>
<div><button>Draw random shape</button></div>

Rotation & Scale in Matrices of SVG Element

I am helping someone solve this. We have SVG elements in g tag. We are using the transformation on the g tag and converting it into matrix.
Here is the jsbin link.
We are a bit confused by the following:
How to calculate the 'resistor', denoted by dx & dy in line 23 & 24 in the jsbin.
Where we are going wrong with the calculations for rotate & scale using matrices.
Is there a better way of doing this mathematically? I need to mention that the relevant person has been struggling with this for over two weeks so appreciate the help.
The relevant Js code:
var mouseXY; //object to store the mouse event x & y
var angle1; //first angle before rotate
var center; //center of the shape that we want to rotate
//this function is call when mouse down
//on the resize button
//this function bind the mouse event
//to scale the shape
function bindScaleShapeEvent(){
mouseXYPosition(event);
document.addEventListener("mousemove",scaleImage);
document.addEventListener("mouseup",removeScaleImage);
}
//function to scale the shape when
//user mouse move
function scaleImage(event){
var parent = document.getElementById("1"); //parent of shape
var dx = (event.pageX - mouseXY.x);
var dy = (event.pageY - mouseXY.y);
dx /= 80; // 80 is the resistor value
dy /= 80;
var scaleX = dx;
var scaleY = dy;
var matrix = parent.getAttribute("transform");
var matrixArray = matrixToArray(matrix);
//make the new matrix of the shape
scaleString = "matrix(" +
(parseFloat(matrixArray[0]) + scaleX) + " " +
parseFloat(matrixArray[1]) + " " +
parseFloat(matrixArray[2]) + " " +
(parseFloat(matrixArray[3]) + scaleY) + " " +
parseFloat(matrixArray[4]) + " " +
parseFloat(matrixArray[5]) +")";
parent.setAttribute("transform", scaleString);
mouseXYPosition(event);
}
//convert the transformation to array
function matrixToArray(matrix){
matrix = matrix.split('(');
matrix = matrix[1].split(')');
matrix = matrix[0].split(' ');
return matrix;
}
//remove eventListener from the
//shape resize button
function removeScaleImage(){
document.removeEventListener("mousemove",scaleImage);
document.removeEventListener("mouseup",removeScaleImage);
}
//this function is call when mouse down
//on the rotate button
//this function bind the mouse event
//to rotate the shape
function bindRotateShapeEvent(event){
mouseXYPosition(event);
centerXY();//store the center of the shape that we want to rotate
var dx = (mouseXY.x) *3;
var dy = (mouseXY.y) *3;
angle1 = (180 * Math.atan2(dy, dx) / Math.PI); //first angle of the shape
document.addEventListener("mousemove",rotateImage);
document.addEventListener("mouseup",removeRotateImage);
}
//applying rotation on the shape
function rotateImage(event){
var dx = (event.pageX - mouseXY.x);
var dy = (event.pageY - mouseXY.y);
var parent = document.getElementById("1");
var angle2 = (180 * (Math.atan2(dy,dx)) / Math.PI);
var angle = angle2 - angle1;
var radian= 0.06 * angle;
var cos = Math.cos(radian);
var sin = Math.sin(radian);
var matrix ='matrix(' +
(cos) +
' ' + (sin) +
' ' + (-sin) +
' ' + (cos) +
' ' + (-center.cx * cos + center.cy * sin + center.cx) +
' ' + (-center.cx * sin - center.cy * cos + center.cy) +
')';
parent.setAttribute("transform",matrix);
}
function removeRotateImage(){
document.removeEventListener("mousemove",rotateImage);
document.removeEventListener("mouseup",removeRotateImage);
}
//save the center of the shape
function centerXY(){
var shape = document.getElementById("1");
var bbox = shape.getBBox();
center ={
cx: bbox.x + (bbox.width / 2),
cy: bbox.y + (bbox.height / 2)
};
}
//save the move x and y
function mouseXYPosition(event){
mouseXY = {
x: event.pageX,
y: event.pageY
};
}
The Jsbin link to the working demo.

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.

A relative rotation in an animation

I have a problem with Raphael.js. I want to rotate the "compassScale" - set in the following code - in a relative manner.
This works for the paths, but all the texts "animate" to the absolute rotation of 30 degree. I want them to rotate to the 30 degrees relative from their actual positions.
var compassScale = paper.set();
var centerX = 200;
var centerY = 200;
var radius = 195;
var compasCircle = paper.circle(centerX, centerY, radius);
for(var i = 0; i < 360; i++) {
var winkelRad = i * (Math.PI/180)
var xStart = centerX + Math.sin(winkelRad) * radius;
var yStart = centerY + Math.cos(winkelRad) * radius;
var diff = 6;
if(i % 10 === 0){
compassScale.push(paper.text(centerX, centerY - radius + 18, i).rotate(i, centerX, centerY, true));
diff = 12;
} else if(i % 5 === 0) {
diff = 8;
}
var xEnd = centerX + Math.sin(winkelRad) * (radius - diff);
var yEnd = centerY + Math.cos(winkelRad) * (radius - diff);
compassScale.push(paper.path("M" + xStart + " " + yStart + " L" + xEnd + " " + yEnd));
}
compassScale.animate({rotation:"30 " + centerX + " " + centerY}, 5000);
Like you said, the problem is that you're animating all elements to 30 degrees, not their current rotation + 30 degrees. It's actually quite simple once you think of it that way. Here is your revised code that works:
var compassScale = paper.set();
var texts = []; // array to hold the text elements
var centerX = 200;
var centerY = 200;
var radius = 195;
var compasCircle = paper.circle(centerX, centerY, radius);
for(var i = 0; i < 360; i++) {
var winkelRad = i * (Math.PI/180)
var xStart = centerX + Math.sin(winkelRad) * radius;
var yStart = centerY + Math.cos(winkelRad) * radius;
var diff = 6;
if(i % 10 === 0){
texts.push(paper.text(centerX, centerY - radius + 18, i).rotate(i, centerX, centerY, true));
diff = 12;
} else if(i % 5 === 0) {
diff = 8;
}
var xEnd = centerX + Math.sin(winkelRad) * (radius - diff);
var yEnd = centerY + Math.cos(winkelRad) * (radius - diff);
compassScale.push(paper.path("M" + xStart + " " + yStart + " L" + xEnd + " " + yEnd));
}
compassScale.animate({rotation:"30 " + centerX + " " + centerY}, 5000);
// loop through the text elements, adjusting their rotation by adding 30 to their individual rotation
for (var i = 0, l = texts.length; i < l; i += 1) {
// node.attr("rotation") returns something like 50 200 200, so we have to split the string and grab the first number with shift
texts[i].animate({rotation: (30 + +texts[i].attr("rotation").split(" ").shift()) + " " + centerX + " " + centerY}, 5000);
}
Just a quick observation:
Looks like "Rotation" isn't part of the Atrr anymore since ver 2, so you can't use it in "animate", but you can replace that with "transform: "r" + (some degree)"..
eg:
element.animate( {transform: "r" + (-90)}, 2000, 'bounce');

Categories

Resources