I have just started learning about SVGs and wanted to create a for loop to draw many circles in my HTML. Can it be done the way I'm trying to do it, or is what I'm trying to do not possible?
<html>
<body>
<h1>My first SVG for loop</h1>
<script type="text/javascript">
var circlex = 50;
var circley = 50;
for (var i = 0; i < 100; i++) {
<svg width="100" height="100">
<circle cx="circlex + 1" cy="circley + 1" r="40" stroke="green" stroke-width="4" fill="yellow" />
</svg>
};
</script>
</body>
</html>
So close yet so far
You can not put html code directly into JavaScript (that would be cool)
The way JavaScript adds new elements is through DOM manipulation.
So let's go trough the code:
First created an empty SVG document with an xmlns (just set xmlns="http://www.w3.org/2000/svg", it works 99% of the time) and we need an ID to select the element.
Get the SVG element in JavaScript: document.getElementById("svg_circles"). Here we use the ID we set on the element to save it to a variable.
In the for loop: create a circle element: var circle = document.createElementNS(NS, "circle"); The namespace NS is found in 1. it's http://www.w3.org/2000/svg. This seems complex but is needed and just something you have to memorize.
Set circle attributes: now to the attributes: I set the position cx and cy with .setAttribute(). You can try to position them differently.
I also set the size r attribute and the fill (the long line of code on the fill is just for fun on my part, it creates random colors)
Now we are done with the circles, but the JavaScript code does not know where to put them. So we tell it: svgCircle.appendChild() sets the element as a child of our SVG document. So: svgCircle.appendChild(circle); where circle is the created SVG element.
document.addEventListener("DOMContentLoaded", function(event) {
var circlex = 0;
var circley = 0;
var svgCircle = document.getElementById("svg_circles");
var NS = "http://www.w3.org/2000/svg";
for (var i = 0; i < 100; i++) {
var circle = document.createElementNS(NS, "circle");
console.log(circle);
circle.setAttribute("cx", circlex + i);
circle.setAttribute("cy", circley + i);
circle.setAttribute("r", 10);
circle.setAttribute("fill", "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")");
svgCircle.appendChild(circle);
};
});
<svg id="svg_circles" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
</svg>
It is little bit complex than your example, here is fiddle where I converted your pseudo code to js code. This approach could be ok if you using some server side rendering e.g. .net mvc and than iterate svg element. But in js you need to create dom elements pass the configuration and than append that to dom. Here is code: https://jsfiddle.net/9c7ro6x3/1/
var circlex = 50;
var circley = 50;
var svg = document.createElementNS("http://www.w3.org/2000/svg", 'svg');
for (var i = 0; i < 100; i++) {
circlex = circlex + 1;
circley = circley + 1;
var circle = document.createElementNS("http://www.w3.org/2000/svg", 'circle');
circle.setAttribute("cx", circlex);
circle.setAttribute("cy", circley);
circle.setAttribute("r", "40");
circle.setAttribute("stroke", "green");
circle.setAttribute("strokeWidth", 4);
circle.setAttribute("fill", "yellow");
svg.appendChild(circle);
};
document.body.appendChild(svg);
Related
I have to implement the idea of a circle approximation that is a regular polygon with N corners, whereas N is defined by the user.
For example, if N=3 I would have a triangle. With n=5 I would a shape that starts resembling a circle. As I increase N, I would get closer and closer to the shape of a circle.
This idea it's very similar to what was asked and answered on the on the follwing question/solution:
Draw regular polygons inscribed in a circle , however, they used raphael.js and not D3.js.
What I tried to do:
var vis = d3.select("body").append("svg")
.attr("width", 1000)
.attr("height", 667);
var svg = d3.select('svg');
var originX = 200;
var originY = 200;
var outerCircleRadius = 60;
var outerCircle = svg.append("circle").attr({
cx: originX,
cy: originY,
r: outerCircleRadius,
fill: "none",
stroke: "black"
});
var chairWidth = 10;
var chairOriginX = originX + ((outerCircleRadius) * Math.sin(0));
var chairOriginY = originY - ((outerCircleRadius) * Math.cos(0));
var chair = svg.append("rect").attr({
x: chairOriginX - (chairWidth / 2),
y: chairOriginY - (chairWidth / 2),
width: chairWidth,
opacity: 1,
height: 20,
fill: "none",
stroke: "blue"
});
var n_number = 5
var n_angles = 360/n_number
var angle_start=0;
var angle_next;
console.log(chair.node().getBBox().x);
console.log(chair.node().getBBox().y);
chair.attr("transform", "rotate(" + (angle_start+n_angles+n_angles) + ", 200, 200)");
var circle = svg.append("circle")
.attr("cx", 195)
.attr("cy", 135)
.attr("r", 50)
.attr("fill", "red");
var chairOriginX2 = originX + ((outerCircleRadius) * Math.sin(0));
var chairOriginY2 = originY - ((outerCircleRadius) * Math.cos(0));
var chair2 = svg.append("rect").attr({
x: chairOriginX2 - (chairWidth / 2),
y: chairOriginY2 - (chairWidth / 2),
width: chairWidth,
opacity: 1,
height: 20,
fill: "none",
stroke: "blue"
});
console.log(chair2.node().getBBox().x);
console.log(chair2.node().getBBox().y);
My idea, that did not work, was trying to create a circle ("outerCircle") which I would slide within the circunference ("chair.attr("transform"...") of the circle, based on N, obtaining several different (x,y) coordinates.
Then, I would feed (x,y) coordinates to a polygon.
I believe that my approach for this problem is wrong. Also, the part that I got stuck is that I am not being able to keep sliding whitin the circunference and storing each different (x,y) coordinate. I tried "console.log(chair2.node().getBBox().x);" but it is always storing the same coordinate, which it is of the origin.
I've simplified your code for clarity. To get the x of a point on a circle you use the Math.cos(angle) and for the y you use the Math.sin(angle). This was your error. Now you can change the value of the n_number
var SVG_NS = 'http://www.w3.org/2000/svg';
var originX = 200;
var originY = 200;
var outerCircleRadius = 60;
var polygon = document.createElementNS(SVG_NS, 'polygon');
svg.appendChild(polygon);
let points="";
var n_number = 5;
var n_angles = 2*Math.PI/n_number
// building the value of the `points` attribute for the polygon
for(let i = 0; i < n_number; i++){
let x = originX + outerCircleRadius * Math.cos(i*n_angles);
let y = originY + outerCircleRadius * Math.sin(i*n_angles);
points += ` ${x},${y} `;
}
// setting the value of the points attribute of the polygon
polygon.setAttributeNS(null,"points",points)
svg{border:1px solid;width:90vh;}
polygon{fill: none;
stroke: blue}
<svg id="svg" viewBox = "100 100 200 200" >
<circle cx="200" cy="200" r="60" fill="none" stroke="black" />
</svg>
This is another demo where I'm using an input type range to change the n_numbervariable
var SVG_NS = 'http://www.w3.org/2000/svg';
var originX = 200;
var originY = 200;
var outerCircleRadius = 60;
var polygon = document.createElementNS(SVG_NS, 'polygon');
svg.appendChild(polygon);
let points="";
var n_number = 5;
setPoints(n_number);
theRange.addEventListener("input", ()=>{
n_number = theRange.value;
setPoints(n_number)
});
function setPoints(n_number){
var n_angles = 2*Math.PI/n_number;
points = ""
// building the value of the `points` attribute for the polygon
for(let i = 0; i < n_number; i++){
let x = originX + outerCircleRadius * Math.cos(i*n_angles);
let y = originY + outerCircleRadius * Math.sin(i*n_angles);
points += ` ${x},${y} `;
}
// setting the value of the points attribute of the polygon
polygon.setAttributeNS(null,"points",points);
}
svg{border:1px solid; width:90vh;}
polygon{fill: none;
stroke: blue}
<p><input type="range" min="3" max="50" value="5" id="theRange" /></p>
<svg id="svg" viewBox = "100 100 200 200" >
<circle cx="200" cy="200" r="60" fill="none" stroke="black" />
</svg>
The answer provided by enxaneta is perfectly fine and is certainly the classical approach to this. However, I often favor letting the browser do the trigonometry instead of doing it on my own. Typical examples include my answer to "Complex circle diagram" or the one to "SVG marker - can I set length and angle?". I am not even sure if they outperform the more classical ones but I like them for their simplicity nonetheless.
My solution focuses on the SVGGeometryElement and its methods .getTotalLength() and .getPointAtLength(). Since the SVGCircleElement interface extends that interface those methods are available for an SVG circle having the following meanings:
.getTotalLength(): The circumference of the circle.
.getPointAtLength(): The point in x-/y-coordinates on the circle at the given length. The measurement per definition begins at the 3 o'clock position and progresses clockwise.
Given these explanations it becomes apparent that you can divide the circle's total length, i.e. its circumference, by the number of points for your approximation. This gives you the step distance along the circle to the next point. By summing up these distances you can use the second method to obtain the x-/y-coordinates for each point.
The coding could be done along the following lines:
// Calculate step length as circumference / number of points.
const step = circleElement.getTotalLength() / count;
// Build an array of points on the circle.
const data = Array.from({length: count}, (_, i) => {
const point = circleElement.getPointAtLength(i * step); // Get coordinates of next point.
return `${point.x},${point.y}`;
});
polygon.attr("points", data.join(" "));
Slick and easy! No trigonometry involved.
Finally, a complete working demo:
// Just setup, not related to problem.
const svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 500);
const circle = svg.append("circle")
.attr("cx", "150")
.attr("cy", "150")
.attr("r", "100")
.attr("fill", "none")
.attr("stroke", "black");
const polygon = svg.append("polygon")
.attr("fill", "none")
.attr("stroke", "blue");
const circleElement = circle.node();
const ranger = d3.select("#ranger").on("input", update);
const label = d3.select("label");
// This function contains all the relevant logic.
function update() {
let count = ranger.node().value;
label.text(count);
// Calculate step length as circumference / number of points.
const step = circleElement.getTotalLength() / count;
// Build an array of all points on the circle.
const data = Array.from({length: count}, (_, i) => {
const point = circleElement.getPointAtLength(i * step); // Get coordinates of next point.
return `${point.x},${point.y}`;
});
polygon.attr("points", data.join(" "));
}
update();
<script src="https://d3js.org/d3.v5.js"></script>
<p>
<input id="ranger" type="range" min="3" max="15" value="5">
<label for="ranger"></label>
</p>
I've written a function which creates a custom map by translating lat, long points to x, y on a map inside a div. The map itself is contained as the background of the div, and the points are SVG elements on top, which allows the points to be interactive but the map to be static.
The way that it works right now is that each element has an id, and cx and cy are input by the programmer as coordinates. In the section of the document, there is an array that contains the id of each point. The program runs through the array using .forEach, grabs the cx and cy values, does some math, and then changes the cx and cy value to place the point correctly on the map (see code at the bottom of the document).
This works fine, but I'm wondering if there's a more 'graceful' way to do it. The code as is isn't very user-friendly, you have to go through and give each point an id and update the array every time you add a new point. I recognize that it's a kind of clunky system. Is there a way to do this by, for example, telling the script to run for each element with a specific class onload?
Code below:
#window {
background-image: url(https://upload.wikimedia.org/wikipedia/commons/thumb/5/5d/World_map_%28Miller_cylindrical_projection%2C_blank%29.svg/634px-World_map_%28Miller_cylindrical_projection%2C_blank%29.svg.png);
display: block;
filter: none;
background-color: #E3F2FD;
transition: .5s;
}
<html>
<head>
<link rel="stylesheet" href="pagestyle.css">
</head>
<body>
<div id="window">
<svg id="pins" viewBox="0 0 637.5 336.4">
<circle class="pin" id="md" cx="-76.609" cy="39.291" r=".25%" fill="#000"/>
<circle class="pin" id="ma" cx="-72.609" cy="42.371" r=".25%" fill="#000"/>
<circle class="pin" id="nd" cx="-97.032" cy="40.714" r=".25%" fill="#000"/>
<circle class="pin" id="tx1" cx="-94.741" cy="47.924" r=".25%" fill="#000"/>
<circle class="pin" id="tx2" cx="-97.149" cy="32.501" r=".25%" fill="#000"/>
<text id="tooltip" x="0" y="0" visibility="hidden" fill="#000" stroke="#000" font-size="10" font-family="Arial">Tooltip</text>
</svg>
</div>
<script>
//mapping function
var users = [md, ma, nd, tx1, tx2];
users.forEach(function(element) {
var lon = parseInt(element.getAttribute("cx"));
var xValue = (637.5 * ((lon + 180) / 360))-14;
element.setAttribute("cx", xValue);
var lat = parseInt(element.getAttribute("cy"));
var yValue = (474.691 * ((Math.abs((((5 / 4) *
(Math.asinh(Math.tan(((4 * Math.PI) / 900) * lat)))) + 2.30342) - 4.60684)) / 4.60684)) ;
element.setAttribute("cy", yValue);
});
</script>
</body>
</html>
Update: added a background url sourced online instead of locally on my laptop and changed the x and y shifts to match the new map
I would modify your code as the follows if you want the circles redrawn on load
document.body.onload = function(){
var users = document.getElementsByClassName('pin');
for (var i = 0; i < users.length; i++) {
var element = users[i]
var lon = parseInt(element.getAttribute("cx"));
var xValue = (637.5 * ((lon + 180) / 360)) - 22;
element.setAttribute("cx", xValue);
var lat = parseInt(element.getAttribute("cy"));
var yValue = (474.691 * ((Math.abs((((5/4) * (Math.asinh(Math.tan(((4*Math.PI)/900) * lat)))) + 2.30342) - 4.60684)) / 4.60684)) - 35;
element.setAttribute("cy", yValue);
}
}
I would like to measure the performance between canvas and svg in HTML5.
I have done so far. I have created multiple circles in svg and canvas.
Both have a 500 x 500 Element width and height.
I found out I am measuring the scripting time. If I use the dev tools in Chrome, the scripting time is nearly equal to my measured time.
Now, how can I measure the rendering time? Would be a code with separate canvas and svg circle creation and devtools for rendering a good way to compare svg and canvas rendering performance?
<html>
<head>
<script type="text/javascript">
var svgNS = "http://www.w3.org/2000/svg";
function createCircle1() {
var t3 = performance.now();
for (var x = 1; x <= 1000; x++) {
for (var y = 1; y <= 100; y++) {
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.arc(x, y, 5, 0, 2 * Math.PI);
ctx.stroke();
}
}
var t4 = performance.now();
console.log("canvas time " + (t4 - t3) + " milliseconds.")
var t0 = performance.now();
for (var x = 1; x <= 1000; x++) {
for (var y = 1; y <= 100; y++) {
var myCircle = document.createElementNS(svgNS, "circle"); //to create a circle, for rectangle use rectangle
myCircle.setAttributeNS(null, "cx", x);
myCircle.setAttributeNS(null, "cy", y);
myCircle.setAttributeNS(null, "r", 5);
myCircle.setAttributeNS(null, "stroke", "none");
document.getElementById("mySVG").appendChild(myCircle);
}
}
var t1 = performance.now();
console.log("svg time " + (t1 - t0) + " milliseconds.")
}
</script>
</head>
<body onload="createCircle1();">
<svg id="mySVG" width="500" height="500" style="border:1px solid #d3d3d3;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"></svg>
<canvas id="myCanvas" width="500" height="500" style="border:1px solid #d3d3d3;"></canvas>
</body>
</html>
Somehow the scripting time and my measured performance time is different.
Can someone tell me if this performance comparison is useful?
I did the test multiple times, the performance time is always different but canvas is faster than svg in rendering and also in scripting.
Why in rendering? Scripting should be because of the DOM reference of svg?
This test I did with seperate svg and canvas, I just rendered first only svg, and in the next test only canvas.
The problem with SVG.
Below is a performance test of drawing a big circle on a canvas and a SVG image.
Both get about the same performance 30ms per circle on my machine and chrome.
Run the test and see the result. If you watch the progress you may notice that it begins to slow down a little. When the first test is run click the button again and this time you will notice that there is even more of a slow down.
Do a 3rd test and slower still, but the performance per circle for both canvas and SVG has not changed, where is the slow down coming from.
The DOM is not javascript.
The code run to add a node to the SVG does not care how many nodes the SVG has, but when add nodes to the SVG image and your code exits you have informed the DOM that your SVG element is dirty and needs to be redrawn.
I grouped the test into groups of ~10 before exiting. This means that for every ten circles added the DOM will redraw all the SVG nodes from scratch and outside the javascript context and your ability to measure or control it.
When you click test the second time the SVG already has 10000 circles, so after adding the first ten the DOM happily re-renders 10000+10 circles.
It is next to impossible to get an accurate measure of SVG performance.
Using timeline
I ran the code snippet below with timeline recording. I ran the test two times.
The next two images show the same period. The top one is at the start of the test and the next one is at the end of the second test.
I have mark the GPU sections that are likely involved in rendering the SVG. Note how they change from trivial to excessive.
This image shows one cycle of the test 10 renders in the second test cycle. The code execution is barely visible at ~1ms but the GPU is flat out with a huge 175ms devoted to drawing all the SVG circle again.
When you use SVG you must remember that when you make a change to it the DOM will re-render all of it. It does not care if it's visible or not. If you change the size it's redrawn.
To use SVG you must bundle all your calls into one execution context to get the best performance.
Canvas V SVG
Both SVG and Canvas use the GPU with very similar shaders to do the work. Drawing a circle in SVG or Canvas takes the same time. The advantage that the canvas has over SVG is that you control the rendering, what when and where. For SVG you only control content and have little say over rendering.
var running = false;
var test = function(){
if(running){
return;
}
var mySVG = document.getElementById("mySVG");
var myCanvas = document.getElementById("myCanvas");
var ctx = myCanvas.getContext("2d");
var myCircle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
running = true;
const testCount = 1000;
const groupCount = 10;
var times = [[],[]];
var names =["Canvas test.","SVG test."];
var indexs = [0,0];
var tests = [function(){
now = performance.now();
ctx.beginPath();
ctx.arc(250, 250, 250, 0, 2 * Math.PI);
ctx.fill();
return performance.now()-now;
},
function(){
now = performance.now();
var circle = myCircle.cloneNode();
circle.setAttributeNS(null, "cx", 250);
circle.setAttributeNS(null, "cy", 250);
circle.setAttributeNS(null, "r", 250);
circle.setAttributeNS(null, "stroke", "none");
mySVG.appendChild(circle);
return performance.now()-now;
}];
for(var i = 0; i < testCount; i ++){ // preallocate and zeor arrays
times[0][i] = 0;
times[1][i] = 0;
}
var testComplete = false;
function doTests(){
for(i = 0; i < groupCount; i ++){
var testIndex = Math.floor(Math.random()*2);
times[testIndex][indexs[testIndex]] = tests[testIndex]();
indexs[testIndex] += 1;
if(indexs[testIndex] >= testCount){
testComplete = true;
return;
}
}
}
function getResults(){
// get the mean
var meanA = 0;
var meanB = 0;
var varianceA = 0;
var varianceB = 0;
var totalA = 0;
var totalB = 0;
for(var i = 0; i < testCount; i ++){ // preallocate and zero arrays
totalA += i < indexs[0] ? times[0][i] : 0;
totalB += i < indexs[1] ? times[1][i] : 0;
}
meanA = Math.floor((totalA / indexs[0]) * 1000) / 1000;
meanB = Math.floor((totalB / indexs[1]) * 1000) / 1000;
for(var i = 0; i < testCount; i ++){ // preallocate and zero arrays
varianceA += i < indexs[0] ? Math.pow((times[0][i] - meanA),2) : 0;
varianceB += i < indexs[1] ? Math.pow((times[1][i] - meanB),2) : 0;
}
varianceA = Math.floor((varianceA / indexs[0]) * 1000) / 1000;
varianceB = Math.floor((varianceB / indexs[1]) * 1000) / 1000;
result1.textContent = `Test ${names[0]} Mean : ${meanA}ms Variance : ${varianceA}ms Total : ${totalA.toFixed(3)}ms over ${indexs[0]} tests.`;
result2.textContent = `Test ${names[1]}. Mean : ${meanB}ms Variance : ${varianceB}ms Total : ${totalB.toFixed(3)}ms over ${indexs[1]} tests.`;
}
function test(){
doTests();
var p = Math.floor((((indexs[0] + indexs[1]) /2)/ testCount) * 100);
if(testComplete){
getResults();
p = 100;
running = false;
}else{
setTimeout(test,10);
}
progress.textContent = p+"%";
}
test()
}
startBut.addEventListener("click",test);
#ui {
font-family : Arial;
}
<div id="ui">
<h3>Comparative performance test</h3>
Adding circles to canvas V adding circles to SVG.<br> The test adds 1000 * 10 circles to the canvas or SVG with as much optimisation as possible and to be fair.<br>
<input id="startBut" type="button" value = "Start test"/>
<div id="progress"></div>
<div id="result1"></div>
<div id="result2"></div>
<h3>SVG element</h3>
<svg id="mySVG" width="500" height="500" style="border:1px solid #d3d3d3;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"></svg>
<h3>Canvas element</h3>
<canvas id="myCanvas" width="500" height="500" style="border:1px solid #d3d3d3;"></canvas>
</div>
I am generating 5 circles with a for loop in a canvas and I want to give them a class so I can control them with jquery, but I am doing something wrong. Can you guys figure out what's happening?
var stage;
var quantity = 6,
width = 60,
height = 60,
circles = [];
function init(){
stage = new createjs.Stage("myCanvas");
stage.width = 500;
stage.height = 600;
createjs.Ticker.setFPS(60);
createjs.Ticker.addEventListener("tick", onTick);
setupGame();
}
function setupGame() {
for(var i = 0; i < quantity; i++) {
var circle = document.createElement("img");
circle.setAttribute('src', 'images/circles/circle'+i+'.png');
circle.className = "circle";
circle.style.position = "absolute";
circle.style.left = Math.floor((Math.random() * 100)) + "%";
circle.style.top = Math.floor((Math.random() * 100)) + "%";
circle.style.width = width + "px";
circle.style.height = height + "px";
document.body.appendChild(circle);
circles.push(circle);
}
}
function onTick(e){
stage.update(e);
}
NEW VERSION. With the help from JonnyD, I now have a functional loop. The only problem is that the images get appended to the body, and not to my stage. I have tried stage.appendChild(circle), but it's not working.
Here is a link to an online source so you guys can check it out = LINK
A lot is wrong with your code.
You are trying to add properties to strings within an array which is not possible. Properties are added to objects using dot or bracket notation..
Dot notation
foo.bar.baz
Square bracket notation
foo['bar']['baz']
What I think you want to do is create five circles on the 'screen' or more technically correct DOM (https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model) at random positions with set H&W of 60px with classnames of myClass..
I have rewritten your code for you, you can remove the style javascript lines and add them in the CSS if you wish.. All you were really doing wrong was attempting to add properties to array values, wrong technique for the code and missing off .style before width, height. Note You add className's and width and height attributes to DOM elements only.
You can now access the individual circles through a for loop and the circles array or by using the nth-child selector with CSS. e.g .circle:nth-child(1) {animation/transition}
var quantity = 5,
width = 60,
height = 60
circles = [];
function setUp() {
for(var i = 0; i < quantity; i++) {
var circle = document.createElement("div");
circle.className = "circle";
circle.style.position = "absolute";
circle.style.left = Math.floor((Math.random() * 100)) + "%";
circle.style.top = Math.floor((Math.random() * 100)) + "%";
circle.style.backgroundColor = "black";
circle.style.width = width + "px";
circle.style.height = height + "px";
circles.push(circle);
document.body.appendChild(circle);
}
}
setUp();
.circle {
border-radius: 50%;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
}
I didn't see you were using CreateJS.. in that case using the notation like so is okay..
var circle = new createjs.Shape();
circle.graphics.beginFill("DeepSkyBlue").drawCircle(0, 0, 50);
circle.x = 100;
circle.y = 100;
stage.addChild(circle);
ensure that you update the stage as well.
stage.update();
I realize this question has been answered, but since I clicked on this for trying to find out how to add a class to a canvas object in jquery, I'll post how to do that.
var thing = canvas_object;
$('body').append(thing);
var canvas = $('canvas');
canvas.addClass('test');
Things inside canvas are not in DOM, but elements in Scalable Vector Graphics images are, and can be manipulated this way.
Try using SVG if convenient. svg.js is a lightweight library to manipulate SVG.
I'm looking for a way to generate pie charts using SVG.
The numbers I have are simple enough - just percentages, an array of numbers that obviously add up to 100.
I have a basic understanding of SVG, but I can't think how to translate these numbers into meaningful coordinates to use in the path tag
Can anyone point me to a useful utility or library, or give any hints as to how I could use percentages to draw a pie chart - in JavaScript?
Credits to https://stackoverflow.com/a/3642265/309483 and http://jbkflex.wordpress.com/2011/07/28/creating-a-svg-pie-chart-html5/ (note that the last one has a bug, fixed here)
function makeSVG(tag, attrs) {
var el= document.createElementNS('http://www.w3.org/2000/svg', tag);
for (var k in attrs)
if (attrs.hasOwnProperty(k)) el.setAttribute(k, attrs[k]);
return el;
}
function drawArcs(paper, pieData){
var total = pieData.reduce(function (accu, that) { return that + accu; }, 0);
var sectorAngleArr = pieData.map(function (v) { return 360 * v / total; });
var startAngle = 0;
var endAngle = 0;
for (var i=0; i<sectorAngleArr.length; i++){
startAngle = endAngle;
endAngle = startAngle + sectorAngleArr[i];
var x1,x2,y1,y2 ;
x1 = parseInt(Math.round(200 + 195*Math.cos(Math.PI*startAngle/180)));
y1 = parseInt(Math.round(200 + 195*Math.sin(Math.PI*startAngle/180)));
x2 = parseInt(Math.round(200 + 195*Math.cos(Math.PI*endAngle/180)));
y2 = parseInt(Math.round(200 + 195*Math.sin(Math.PI*endAngle/180)));
var d = "M200,200 L" + x1 + "," + y1 + " A195,195 0 " +
((endAngle-startAngle > 180) ? 1 : 0) + ",1 " + x2 + "," + y2 + " z";
//alert(d); // enable to see coords as they are displayed
var c = parseInt(i / sectorAngleArr.length * 360);
var arc = makeSVG("path", {d: d, fill: "hsl(" + c + ", 66%, 50%)"});
paper.appendChild(arc);
arc.onclick = (function (originalData) {
return function(event) {
alert("Associated pie piece data: " + originalData);
}
})(pieData[i]);
}
}
var svgdoc = document.getElementById("s");
drawArcs(svgdoc, [52,15,20,80]); // here is the pie chart data
// You can attach additional content (from e.g. AJAX) like this:
var parser = new DOMParser();
var docToEmbed = parser.parseFromString(
"<svg xmlns='http://www.w3.org/2000/svg'><text x='50' y='50' fill='black'>hi</text></svg>",
"image/svg+xml");
Array.prototype.slice.call(docToEmbed.documentElement.childNodes).forEach(function(elem) {
svgdoc.appendChild(document.importNode(elem, true));
});
#con {
resize:both;
overflow:hidden;
display:inline-block;
width:20em;
height:20em;
padding:0.5em;
}
<div id="con">
<!-- the div container is of course optional. It is used with
{width,height}="100%" below to make the chart resizable. -->
<svg width="100%" height="100%" id="s"
xmlns="http://www.w3.org/2000/svg" viewbox="0 0 400 400">
<style type="text/css">
path:hover {
opacity: 0.8;
}
</style>
</svg>
</div>
Here are a few more:
Elycharts (based on jQuery and Raphaël, MIT license)
ZingCharts (commercial, has SVG/VML/HTML5/Flash backends)
Grafico (based on Prototype and Raphaël, MIT license)
d3.js (very nice library for interactive and dynamic graphs, MIT-like license)
I try to collect links to all svg graphing libraries here.
Raphael is a very good SVG drawing library -- in particular, it beats the others because in older versions of IE, it automatically falls back to using VML, and therefore it works in IE from version 6 and up, as well as in all other mainstream browsers.
It has a separate graphing library, called gRaphael. This does all the usual graph types (pies, lines, bars, etc), and can animate them too.
If those aren't enough, it's easy enough to use the main Raphael library to roll your own - it's very easy to use.
The best (IMO): Highcharts
Others I have heard about:
PlotKit
Raphaël