Donut / Radial Chart with Raphael - javascript

Im a beginner to Raphael. Can anyone show me how I can do a donut/radial chart, with animation, similar to these.
http://dribbble.com/shots/670348-Segment-Graphs
Im working at it now. So far Ive got this far. I will update as I make progress. My sumbling block right now is animating a change in color for the outer ring.
window.onload = function () {
// Creates canvas 320 × 200 at 10, 50
var paper = Raphael(10, 50, 320, 200);
// Creates circle at x = 50, y = 40, with radius 10
var circle1 = paper.circle(50, 40, 40);
var circle2 = paper.circle(50, 40, 20);
circle2.attr("fill", "#fff");
circle2.attr("stroke", "#fff");
circle1.attr("fill", "#336699");
circle1.attr("stroke", "#fff");
}

Credits:
On the raphael website there is an example that uses arcs. There is another question on stackoverflow with a similar topic: drawing centered arcs in raphael js. The accepted answer there has a simplified and commented version of the most important parts of the code, plus there is a jsfiddle link showing the code in action: http://jsfiddle.net/Bzdnm/2/
So what I did: I took the code from the linked question, combined it with eve, another javascript library made by the creator of RaphaelJS and what I got was this: http://jsfiddle.net/cristighenea/aP7MK/
At a glance:
1.after the arc is created we rotate it 180 degrees and begin animating it:
theArc.rotate(180, 100, 100).animate({
arc: [100, 100, amount, 100, 40]
}, 1900, function(){
//animation finish callback goes here
});
2.using eve we bind an event to *raphael.anim.frame.**
3.each time the event is fired we update the text in the middle with the new value of the arc
If you have any questions let me know

Related

`line()` in p5js crosses the edges. Beside doing the math myself, is there an easier way to make the line just connect their edges?

I'm trying to draw a line to connect two given circles.
function setup() {
createCanvas(300, 100);
background(220);
noFill();
ellipse(150, 30, 20, 20);
ellipse(100, 50, 20, 20);
line(100, 50, 150, 30);
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.min.js"></script>
The parameters I get are the x, y of the circle's center. If I use the info directly, the line crosses both circles.
I know I can do the math, I'd just like to know if there is an easier way to make the line just connect their edges?
One easy way would be draw the line first then draw the circles and fill them with the background color this way the line inside the circles will be hidden, this only work if you don't mind the background and the circles color to be the same
function setup() {
createCanvas(300, 100);
background(220);
line(100, 50, 150, 30);
fill(220);
ellipse(150, 30, 20, 20);
ellipse(100, 50, 20, 20);
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.min.js"></script>
Using opaque circles
A solution similar to what Sarkar said before, as far as you don't mind the circles having a fill color (whether their color is the same or different to the background color, it doesn't matter), the easiest way of doing this is by simply making the circles cover the line by drawing them afterwards with any fill opaque color.
Using a graphics object
However, if you would like to have this shape as a transparent shape, in order to have more freedom with the use you intend to do of it, you could try this: you create a graphics object, you draw the line, then you activate the erase mode and draw the circles so they erase the part of the line they are overlapping, then you exit the erase mode and draw normally the unfilled circles. Once you have finished with your graphic, you use the image function to draw it over the canvas.
let graphic;
function setup() {
createCanvas(300, 100);
graphic = createGraphics(width, height);
graphic.line(100, 50, 150, 30);
graphic.erase();
graphic.ellipse(150, 30, 20, 20);
graphic.ellipse(100, 50, 20, 20);
graphic.noErase();
graphic.noFill();
graphic.ellipse(150, 30, 20, 20);
graphic.ellipse(100, 50, 20, 20);
background(220);
image(graphic,0,0);
}
<script src="https://cdn.jsdelivr.net/npm/p5#1.4.1/lib/p5.min.js"></script>

Rotate objects as a group in Raphael

I'm new to Raphael and I'm trying to do two circles move in circular orbits related to the center of the canvas.
Here I made ellipses to illustrate the case. The black point is the center of the canvas.
http://jsfiddle.net/QCSb9/
I used set() to group the circles but when trying to rotate them, they rotate using their own centers individually, I thought that grouping the circles the new geometry of the group would change, becoming the black point the center of the group.
How can I rotate these circles continuously as one object.
Here is the code I'm using:
$().ready(function(){
var paper = Raphael("canvas", 640, 480);
paper.rect(0, 0, 640, 480, 10).attr({fill: "#fff", stroke: "none"});
paper.circle(320, 240, 1).attr({"fill":"#000000","stroke-width":0});
var circles = paper.set();
circles.push(
paper.ellipse(200, 240, 30, 25),
paper.ellipse(440, 240, 30, 25)
);
circles.attr({"fill":"#e00000","stroke-width":0});
var anim = Raphael.animation({"transform":"r360"},2000);
circles.animate(anim.repeat(Infinity));
});
Solved: It was a matter of specifying the center of rotation to "r360,320,240":
var anim = Raphael.animation({"transform":"r360,320,240"},2000);
rotate() is deprecated but explains the parameters admitted:
http://raphaeljs.com/reference.html#Element.rotate
Here the example corrected on jsfiddle.net
http://jsfiddle.net/SW3sP/1/

How to put image inside a circle with Raphael Js

I'm trying to put an image inside a circle but no success. This is my code:
//Elms.raphael() is my stage.
var circle = Elms.raphael().circle( 730, 200, 0 );
circle.attr( { fill : 'url(myImg.jpg)' } );
setTimeout( function()
{
circle.animate( { 'stroke' : '#000', r : 90, 'stroke-width' : '5' }, 300 );
}, 250 );
Instead of put the image in the circle It get colored with black ("#333"). I also tried to make an image-object but still doesn't work.
A little help please?
Another way to do, if you have separate image and want to bring it over you circle object.
This makes the whole image appear with smaller size that fits you circle. DEMO
var r = new Raphael(10,10, 500, 500);
var c = r.circle(200, 200, 80).attr({stroke: 'red', "stroke-width" : 3});
var img = r.image("http://www.eatyourcareer.com/wp-content/uploads/2012/06/ok-256x2561.png", 100, 105, 200, 200);
Here's a link to how I created a circle with an image in it:
jsfiddle
var paper = Raphael(document.getElementById("test"), 320, 200);
var circle = paper.circle(100, 100, 50);
circle.attr({
fill: 'url(http://upload.wikimedia.org/wikipedia/commons/thumb/b/b6/SIPI_Jelly_Beans_4.1.07.tiff/lossy-page1-220px-SIPI_Jelly_Beans_4.1.07.tiff.jpg)'
});
I left the animate out entirely to keep it as basic as I could. It seems to work fine and is very similar to your code. If you cannot see it in the example it may be a browser issue.

Zoom and Pan in KineticJS

Is there a way one could zoom and pan on a canvas using KineticJS? I found this library kineticjs-viewport, but just wondering if there is any other way of achieving this because this library seems to be using so many extra libraries and am not sure which ones are absolutely necessary to get the job done.
Alternatively, I am even open to the idea of drawing a rectangle around the region of interest and zooming into that one particular area. Any ideas on how to achieve this? A JSFiddle example would be awesome!
You can simply add .setDraggable("draggable") to a layer and you will be able to drag it as long as there is an object under the cursor. You could add a large, transparent rect to make everything draggable. The zoom can be achieved by setting the scale of the layer. In this example I'm controlling it though the mousewheel, but it's simply a function where you pass the amount you want to zoom (positive to zoom in, negative to zoom out). Here is the code:
var stage = new Kinetic.Stage({
container: "canvas",
width: 500,
height: 500
});
var draggableLayer = new Kinetic.Layer();
draggableLayer.setDraggable("draggable");
//a large transparent background to make everything draggable
var background = new Kinetic.Rect({
x: -1000,
y: -1000,
width: 2000,
height: 2000,
fill: "#000000",
opacity: 0
});
draggableLayer.add(background);
//don't mind this, just to create fake elements
var addCircle = function(x, y, r){
draggableLayer.add(new Kinetic.Circle({
x: x*700,
y: y*700,
radius: r*20,
fill: "rgb("+ parseInt(255*r) +",0,0)"
})
);
}
var circles = 300
while (circles) {
addCircle(Math.random(),Math.random(), Math.random())
circles--;
}
var zoom = function(e) {
var zoomAmount = e.wheelDeltaY*0.001;
draggableLayer.setScale(draggableLayer.getScale().x+zoomAmount)
draggableLayer.draw();
}
document.addEventListener("mousewheel", zoom, false)
stage.add(draggableLayer)
http://jsfiddle.net/zAUYd/
Here's a very quick and simple implementation of zooming and panning a layer. If you had more layers which would need to pan and zoom at the same time, I would suggest grouping them and then applying the on("click")s to that group to get the same effect.
http://jsfiddle.net/renyn/56/
If it's not obvious, the light blue squares in the top left are clicked to zoom in and out, and the pink squares in the bottom left are clicked to pan left and right.
Edit: As a note, this could of course be changed to support "mousedown" or other events, and I don't see why the transformations couldn't be implemented as Kinetic.Animations to make them smoother.
this is what i have done so far.. hope it will help you.
http://jsfiddle.net/v1r00z/ZJE7w/
I actually wrote kineticjs-viewport. I'm happy to hear you were interested in it.
It is actually intended for more than merely dragging. It also allows zooming and performance-focused clipping. The things outside of the clip region aren't rendered at all, so you can have great rendering performance even if you have an enormous layer with a ton of objects.
That's the use case I had. For example, a large RTS map which you view via a smaller viewport region -- think Starcraft.
I hope this helps.
As I was working with Kinetic today I found a SO question that might interest you.
I know it would be better as a comment, but I don't have enough rep for that, anyway, I hope that helps.
These answers seems not to work with the KineticJS 5.1.0. These do not work mainly for the signature change of the scale function:
stage.setScale(newscale); --> stage.setScale({x:newscale,y:newscale});
However, the following solution seems to work with the KineticJS 5.1.0:
JSFiddle: http://jsfiddle.net/rpaul/ckwu7u86/3/
Unfortunately, setting state or layer draggable prevents objects not draggable.
Duopixel's zooming solution is good, but I would rather set it for stage level, not layer level.
Her is my solution
var stage = new Kinetic.Stage({
container : 'container',
width: $("#container").width(),
height: $("#container").height(),
});
var layer = new Kinetic.Layer();
//layer.setDraggable("draggable");
var center = { x:stage.getWidth() / 2, y: stage.getHeight() / 2};
var circle = new Kinetic.Circle({
x: center.x-100,
y: center.y,
radius: 50,
fill: 'green',
draggable: true
});
layer.add(circle);
layer.add(circle.clone({x: center.x+100}));
// zoom by scrollong
document.getElementById("container").addEventListener("mousewheel", function(e) {
var zoomAmount = e.wheelDeltaY*0.0001;
stage.setScale(stage.getScale().x+zoomAmount)
stage.draw();
e.preventDefault();
}, false)
// pan by mouse dragging on stage
stage.on("dragstart dragmove", function(e) {window.draggingNode = true;});
stage.on("dragend", function(e) { window.draggingNode = false;});
$("#container").on("mousedown", function(e) {
if (window.draggingNode) return false;
if (e.which==1) {
window.draggingStart = {x: e.pageX, y: e.pageY, stageX: stage.getX(), stageY: stage.getY()};
window.draggingStage = true;
}
});
$("#container").on("mousemove", function(e) {
if (window.draggingNode || !window.draggingStage) return false;
stage.setX(window.draggingStart.stageX+(e.pageX-window.draggingStart.x));
stage.setY(window.draggingStart.stageY+(e.pageY-window.draggingStart.y));
stage.draw();
});
$("#container").on("mouseup", function(e) { window.draggingStage = false } );
stage.add(layer);
http://jsfiddle.net/bighostkim/jsqJ2/

Large SVG / Raphael circle distorts when being animated

I am animating a circle using Raphael. When the circle is large I get artifacts around the circle when its moving. It seems to be something of a clipping / redraw region issue and wondered if there was a work around?
It seems to be OK in firefox (if a little jerky) and appears very reliably in Chrome. It also is exacerbated by using opacity on the fill property i.e. rgba(255,0,0,0.7)
Here is a jsFiddle showing the issue. Just click around the paper on the right to move the circle.
Code:
var discattr = {
fill: "#666",
stroke: "none",
width: 35
};
var paper = Raphael("svgcontainer", 400, 400);
circle = paper.circle(150, 150, discattr.width, discattr.width).attr({
stroke: "none",
fill: "rgba(255,0,0,0.7)"
});
var coords = []
var animateCircle = function(coords) {
if (!coords.length) return;
var nextCoords = coords.shift()
var move = Raphael.animation(nextCoords, 500, "linear", function() {animateCircle(coords)});
circle.animate(move);
}
$("#svgcontainer").on("mouseup", function(e) {
coords.push({cx: e.pageX, cy: e.pageY})
animateCircle(coords);
});
Buffering is a technique used to prevent animation artifacts (tearing, as JamWaffles points out). If you look at the answer to this Stack Overflow question you'll find information about an SVG setting to turn on buffering, but so far it doesn't appear to be supported by major browsers.

Categories

Resources