I have a force layout that generates some node positions for a given node-link diagram. My task is to place these nodes and links to a radial scatter plot. The nodes should be placed in such a way that their distance from the center node can be visible. For this reason, I calculate the euclidean distance of the nodes from the center and find maximum value from the calculated distance. Then scaled the radius of the radial plot within 0 to maximum distance value. But when I tried This example, the node-link diagram did not place upon the scatter plot. My question is how can I place the node co-ordinates to the plot such that the distance of the nodes from the center node can be seen by the circle line of the scatter plot.
Here is a portion of my input data
{
"nodes": [
{
"id": "A0",
"group": 0,
"degree": 19,
"name": "x"
},
{
"id": "P0",
"group": 0,
"degree": 3,
"name": "y"
},
{
"id": "P1",
"group": 0,
"degree": 3,
"name": "z"
},
{
"id": "P2",
"group": 0,
"degree": 1,
"name": "w"
}
],
"links": [
{
"source": "P0",
"target": "A0"
},
{
"source": "P1",
"target": "A0"
},
{
"source": "P2",
"target": "A0"
}
]
}
Here is a portion of my code.
var width = 1200,
height = 800,
radius = Math.min(width, height) / 2 - 30;
//console.log(");
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var rd_at = 3,
rd_pp = 2.5;
var author_cord = [];author_cord.push({x:width / 2,y:height / 2});
var given_person = 'x';
d3.json("json_file.json", function(graph) {
var simulation = d3.forceSimulation(graph.nodes)
.force("charge", d3.forceManyBody().strength(-500))
.force("center", d3.forceCenter(width / 2, height / 2))//ATTRACT NODES TO CENTER(width / 2, height / 2)
.force("x", d3.forceX(width / 2).strength(1))//X ALLIGN
.force("y", d3.forceY(height / 2).strength(1))//Y ALLIGN
.force("link", d3.forceLink(graph.links).id(function(d) {return d.id; ).distance(40).strength(1))
.stop();
var loading = svg.append("text")
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.text("Simulating. One moment please…");
d3.timeout(function() {
loading.remove();
for (var i = 0, n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())); i < n; ++i) {
simulation.tick();
}
var all_dist = [];
graph.nodes.forEach(function(d){
var d_x = d.x - author_cord[0].x;
var d_y = d.y - author_cord[0].y;
var dist = Math.sqrt(d_x*d_x + d_y+d_y);
all_dist.push(dt);
});
var max_dist = d3.max(all_dist, function(d) {
return d;
});
var r = d3.scaleLinear()
.domain([0,12])
.range([0, max_dist]);
var line = d3.lineRadial()
.radius(function(d) {
return r(d[1]);
});
var gr = svg.append("g")
.attr("class", "r axis")
.selectAll("g")
.data(r.ticks(10).slice(1))
.enter().append("g");
gr.append("circle")
.attr("r", r);
var links = svg.append("g")
.attr("stroke", "grey")
.attr("stroke-width", 1)
.selectAll("line")
.data(graph.links)
.enter().append("line")
.style("opacity", function(d) { return d.target.name == given_author? 0 : 1; } )
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
var n = svg.append("g")
.selectAll("circle")
.data(graph.nodes)
.enter().append("circle")
.attr("r", function(d) {return d.group == 0 ? rd_at : rd_pp;})
.attr("fill", function(d) { return color(d.group); })
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("stroke", "#000")//.attr("stroke", function(d) {return d.group == 0 ? "red": "blue";})
.attr("stroke-width", function(d) {return d.group == 0 ? 1.5 : 1;});
});
});
Here is my output so far. . Here, in the picture green nodes are group 0 node and orange nodes are group 1. Here, center node is the ego node that should be place in the inner most center position of the radial plot
Your svg selection actually corresponds to a group already translated by half width and height:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
Therefore, you can either change the "center", "x" and "y" in the simulation or, alternatively, you can just remove that group from the svg selection and translating the circles' groups instead:
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var gr = svg.append("g")
.attr("class", "r axis")
.selectAll("g")
.data(r.ticks(10).slice(1))
.enter()
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
Related
Long story short. I need to display chart of target-source items and I found that d3.js can do the job. I spend already many hours to get something from there and I finished with using Hive Plot like this one:
https://bost.ocks.org/mike/hive/
But the problem is that I will have only one pair of items and I want to display nodes and corresponding to them items not in let's say circular manner but more conventional parallel (like in image below, left is actual result, right is desired one)[
I tried many things but unfortunately i get lost.
I tried also get jsfiddle up and running but for unknown reason for me is not displaying anything. Here's the jsfiddle:
https://jsfiddle.net/a7yrjfgc/2/
Code:
var width = 200,
height = 200,
innerRadius = 10,
outerRadius = 100,
majorAngle = 1 * Math.PI / 1,
minorAngle = 1 * Math.PI / 2;
var angle = d3.scaleOrdinal()
.domain(["source", "source-target", "target-source", "target"])
.range([0, majorAngle - minorAngle, majorAngle + minorAngle, 2 * majorAngle]);
radius = d3.scaleLinear()
.range([innerRadius, outerRadius]),
color = d3.scaleOrdinal(d3.schemeCategory10),
formatNumber = d3.format(",d");
var nodes = [
{x: 0, y: .4, name: "node1", color: "#0000FF"},
{x: 0, y: .2, name: "node2", color: "#FFA500"},
{x: 1, y: .2, name: "node3", color: "#008000"},
{x: 1, y: .3, name: "node4", color: "#A52A2A"},
];
var links = [
{source: nodes[0], target: nodes[2]},
{source: nodes[1], target: nodes[3]},
];
var info = d3.select("#info")
.text(defaultInfo = "Showing " + formatNumber(links.length) + " splices " + formatNumber(nodes.length) + " strands.");
console.log(info);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")");
svg.selectAll(".axis")
.data(d3.range(2))
.enter().append("line")
.attr("class", "axis")
.attr("transform", function(d) { return "rotate(" + degrees(angle(d)) + ")" })
.attr("x1", radius.range()[0])
.attr("x2", radius.range()[1]);
// draw links
svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", link()
.angle(function(d) { return angle(d.x); })
.radius(function(d) { return radius(d.y); }))
.style("stroke", function(d) { return color(d.color); })
.on("mouseover", linkMouseover)
.on("mouseout", mouseout);
// draw nodes
svg.selectAll(".node")
.data(nodes)
.enter().append("circle")
.attr("class", "node")
.attr("transform", function(d) { return "rotate(" + degrees(angle(d.x)) + ")"; })
.attr("cx", function(d) { return radius(d.y); })
.attr("r", 5)
.style("fill", function(d) { return d3.color(d.color); })
.on("mouseover", nodeMouseover)
.on("mouseout", mouseout);
Many, many thanks for helping in this topic.
As i get to know more about how d3 works, I realized that I need to simply draw everything from scratch.
function DisplayGraph(){
//D3 Configuration
var width = 400,
height = 50,
color = d3.scaleOrdinal(d3.schemeCategory10),
formatNumber = d3.format(",d"),
defaultInfo ="";
var nodes = [
{x: 0, y: 0, name: "node1", color: "#0000FF"},
{x: 0, y: 15, name: "node2", color: "#FFA500"},
{x: 100, y: 15, name: "node3", color: "#008000"},
{x: 100, y: 0, name: "node4", color: "#A52A2A"},
];
var links = [
{source: nodes[0], target: nodes[2]},
{source: nodes[1], target: nodes[3]},
];
var info = d3.select("#info")
.text(defaultInfo = "Showing " + formatNumber(links.length) + " splices " + formatNumber(nodes.length) + " strands.");
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")");
var diagonal = function link(d) {
let sourceX = (d.source.x + 50);
let sourceY = (d.source.y + 5);
let targetX = (d.target.x);
let targetY = (d.target.y + 5);
var output = "M" + sourceX + "," + sourceY
+ "C" + (sourceX + targetX) / 2 + "," + sourceY
+ " " + (sourceX + targetX) / 2 + "," + targetY
+ " " + targetX + "," + targetY;
return output;
};
//draw squares
svg.selectAll(".node")
.data(nodes)
.enter().append("rect")
.attr("class", "node")
.attr("width", 50)
.attr("height", 10)
.attr("x", function(d) { return d.x; })
.attr("y", function(d) { return d.y; })
.style("fill", function(d) { return d3.color(d.color); })
.on("mouseover", nodeMouseover)
.on("mouseout", mouseout);
// draw links
svg.selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", diagonal)
.style("stroke", function(d) { return color(d.color); })
.on("mouseover", linkMouseover)
.on("mouseout", mouseout);
//Helpers
function linkMouseover(d) {
svg.selectAll(".link")
.classed("turnedOn", function(dl) {
return dl === d;
})
.classed("turnedOff", function(dl) {
return !(dl === d);
})
svg.selectAll(".node")
.classed("turnedOn", function(dl) {
return dl === d.source || dl === d.target;
})
info.text(d.source.name + " → " + d.target.name);
}
// Highlight the node and connected links on mouseover.
function nodeMouseover(d) {
svg.selectAll(".node")
.classed("turnedOn", function(dl) {
return dl.source === d || dl.target === d;
})
.classed("turnedOff", function(dl) {
return !(dl.source === d || dl.target === d)
});
d3.select(this)
.classed("turnedOn", true);
info.text(d.name);
}
// Clear any highlighted nodes or links.
function mouseout() {
svg.selectAll(".turnedOn").classed("turnedOn", false);
svg.selectAll(".turnedOff").classed("turnedOff", false);
info.text("Hover to node or link");
}
}
I am creating a horizontal animated d3 chart. How do you reverse the x axis and position the bars in a more dynamic way.
Are the bars the correct width or is the xaxis scale correct? Using d3 version 4
//horizontal work in progress
http://jsfiddle.net/ueg3bjf7/
//vertical chart code this is based from
http://jsfiddle.net/myf1zhar/
$(document).ready(function() {
var $this = $(".barchart");
var w = $this.data("width");
var h = $this.data("height");
var data = $this.data("data");
var data = [{
"label": "Apples",
"value": 100
},
{
"label": "Pears",
"value": 120
},
{
"label": "Bananas",
"value": 20
}
];
var configurations = $this.data("configurations");
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#2b2d39", "#c12f39", "#f8dd2f", "#1b91dc"];
return colores_g[n % colores_g.length];
}
//asess the margin bottom for the chart based on the max char label
var charLabelCount = [];
data.map(function(d) {
var labelStr = d.label.toString();
charLabelCount.push(labelStr.length);
})
var maxChars = charLabelCount.reduce(function(a, b) {
return Math.max(a, b);
});
var bottomMarg = 60;
if (maxChars > 15) {
bottomMarg = 170;
}
//bottom margin calculation
var margin = {
top: 15,
right: 20,
bottom: bottomMarg,
left: 40
},
width = w - margin.left - margin.right,
height = h - margin.top - margin.bottom;
var x = d3.scaleBand()
.range([0, width]);
var y = d3.scaleLinear()
.range([height, 0]);
var yAxis = d3.axisBottom(y);
var xAxis = d3.axisLeft(x);
var svg = d3.select($this[0])
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("viewBox", "0 0 " + w + " " + h)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "barchartg");
function sortBy(array, key) {
var sorted = array.sort(function(a, b) {
return parseFloat(b[key]) - parseFloat(a[key]);
});
return sorted;
}
var sortedMax = 45;
if (configurations) {
if (configurations[0]["maxValue"]) {
sortedMax = configurations[0]["maxValue"] + 5;
}
} else {
sortedMax = sortBy(data, "value")[0]["value"] + 5;
}
x.domain(data.map(function(d) {
return d.label;
}));
y.domain([0, sortedMax]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0,25)")
.call(xAxis);
svg.selectAll(".x.axis text")
.attr("transform", "rotate(-60) translate(-5,-5)")
.style("text-anchor", "end");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("fill", function(d, i) {
return colores_google(i);
})
.attr("x", function(d) {
return 0;
})
.attr("width", function(d) {
return d.value;
})
.attr("y", function(d, i) {
return 45 + (i * 90);
})
.attr("height", function(d) {
return 50;
});
d3.selectAll("rect").transition()
.duration(500)
.delay(function(d, i) {
return 500 * i;
})
.attr("width", function(d) {
return 0;
})
setTimeout(function() {
d3.selectAll("rect").transition()
.duration(500)
.delay(function(d, i) {
return 600 * (3 - i);
})
.attr("width", function(d) {
return d.value;
})
}, 2000);
});
I will try to answer your questions.
How do you reverse the x axis
You have to change the domain of the axis
y.domain([sortedMax, 0]);
position the bars
You have to translate the axis to the width of your graph
svg.append("g").attr("transform", "translate(0, 300)").attr("class", "y axis")
Are the bars the correct width or is the xaxis scale correct?
You have to use a multiplier to calculate the width of each bar, using the max width of your graph and your max value. I have added the 25 pixels of the translate of the x axis
var mult = (w + 25) / sortedMax;
...
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("fill", function(d, i) {
return colores_google(i);
})
.attr("x", function(d) {
return 0;
})
.attr("width", function(d) {
return d.value * mult;
})
.attr("y", function(d, i) {
return 45 + (i * 90);
})
.attr("height", function(d) {
return 50;
});
...
setTimeout(function() {
d3.selectAll("rect").transition()
.duration(500)
.delay(function(d, i) {
return 600 * (3 - i);
})
.attr("width", function(d) {
return d.value * mult;
})
}, 2000);
You can see the result in this fiddle http://jsfiddle.net/jfLgawue/65/
This question already has an answer here:
D3 fill shape with image using pattern
(1 answer)
Closed 7 years ago.
I have spent over a day trying to get my square profile images to be round in the Tree graph Im creating. Here is a fiddle that demonstrates my problem. I have commented around the code that should allow me to do what I want.
http://jsfiddle.net/ssvuxb0k/4/
var data = [
{
"name": "Jules",
"parent": "null",
"facebookId": 100003252256072,
"children": [
{
"name": "Shawn Spencer",
"parent": "Jules",
"facebookId": 104088302962435,
"children": [
{
"name": "Carlton Lassiter",
"parent": "Shawn Spencer",
"facebookId": 126495827393151
}
]
},
{
"name": "Burton Guster",
"parent": "Jules",
"facebookId": 100002858896488
}
]
}
];
var svg, root, margin;
var width = 750;
var height = 500;
var margin = 50;
var count = 0;
var duration = 750;
var tree = d3.layout.tree().size([height, width])
var diagonal = d3.svg.diagonal().projection(function(d) {
return [d.y, d.x];
});
var svg = d3.select('svg')
.attr('width', width + margin + margin)
.attr('height', height + margin + margin)
.append('g')
.attr('transform', 'translate(' + margin + ',' + margin + ')');
var defs = svg.append("defs").attr("id", "imgdefs");
var root = data[0];
root.x0 = height / 2;
root.y0 = 0;
var update = function(source) {
// Compute the new tree layout.
var nodes = tree.nodes(root).reverse(),
links = tree.links(nodes);
// Normalize for fixed Depth
nodes.forEach(function(d) {
d.y = d.depth * 180;
});
// Update the nodes...
var node = svg.selectAll('g.node')
.data(nodes, function(d) {
return d.id || (d.id = ++count);
});
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr('transform', function() {
return 'translate(' + source.y0 + ',' + source.x0 + ')';
})
.on('click', this.nodeClicked);
//PART THATS NOT WORKING
nodes.forEach(function(d, i) {
defs
.data(nodes)
.append('clipPath')
.attr('id', function(d) {
return 'clip-circle-' + i;
})
.append("circle")
.attr('id', function(d) {
return 'test-id-' + i;
})
.attr('transform', function(d) {
return 'translate(' + d.y + ',' + d.x + ')';
})
.attr("r", 20)
.attr("cy", 0)
.attr("cx", 0);
});
// END
nodeEnter.append('image')
.attr('x', -20)
.attr('y', -20)
.attr('width', 40)
.attr('height', 40)
.attr("xlink:href", function(d) {
return "https://graph.facebook.com/" + d.facebookId + "/picture";
})
.attr("clip-path", function(d, i) {
return "url(#clip-circle-" + i + ")";
});
nodeEnter.append('text')
.attr('x', function(d) {
return d.children || d._children ? -25 : 25;
})
.attr('dy', '.35em')
.attr('text-anchor', function(d) {
return d.children || d._children ? 'end' : 'start';
})
.text(function(d) {
return d.name;
})
.style('fill-opacity', 1e-6);
// Transition nodes to their new position
var nodeUpdate = node.transition()
.duration(duration)
.attr('transform', function(d) {
return 'translate(' + d.y + "," + d.x + ')';
});
nodeUpdate.select('circle')
.attr('r', 10);
nodeUpdate.select('text')
.style('fill-opacity', 1);
//Transition exiting nodes to the parents new position
var nodeExit = node.exit().transition()
.duration(duration)
.attr('transform', function() {
return 'translate(' + source.y + ',' + source.x + ')';
})
.remove();
nodeExit.select('image')
.style('opacity', 1e-6);
nodeExit.select('text')
.style('fill-opacity', 1e-6);
// Update the links...
var link = svg.selectAll('path.link')
.data(links, function(d) {
return d.target.id;
});
// Enter any new links at the parents previous position
link.enter().insert('path', 'g')
.attr('class', 'link')
.attr('d', function() {
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
}.bind(this));
//Transition links to their new position.
link.transition()
.duration(duration)
.attr('d', diagonal);
// Transition exiting nodes to the parents new position.
link.exit().transition()
.duration(duration)
.attr('d', function() {
var o = {
x: source.x,
y: source.y
};
return diagonal({
source: o,
target: o
});
}.bind(this))
.remove();
// Stash the old positions for transition
nodes.forEach(function(d) {
d.x0 = d.x;
d.y0 = d.y;
});
};
update(root);
I had two issues, one was fixed by the answer below. The other, make sure that your URL location for the clippath is correct.
My graph was suppose to be displaying on /graph/1 so the code should be
.attr("clip-path", "url(" + document.location.pathname + "#clip-circle)");
You don't need to add a separate clip path for all the images.
Define one clip path like so:
<svg>
<defs>
<clipPath id="clip">
<circle cx="0" cy="0" r="20"/>
</clipPath>
</defs>
add the URL to the node and the circle will be applied to each image:
nodeEnter.append('image')
.attr('x', -20)
.attr('y', -20)
.attr('width', 40)
.attr('height', 40)
.attr("xlink:href", function(d) {
return "https://graph.facebook.com/" + d.facebookId + "/picture";
})
.attr("clip-path", "url(#clip)");
updated code: shown here in fiddle
Code below for legend works fine, but doesn't display the maximum value at the end of the last rect. The maximum value is gonna be 36 always, so I don't mind hard coding it, but don't know how to do it. data is an array of integer values ranging between 0 and 36 and the data array is fed into the eye_data[i].value. I cannot provide the actual data as it is uploaded from a database and a separate file takes care of uploading the values.
//this is just an example of the eye_data format
var eye_data = [
{ locus: "p1", coordinates: {x: "4", y: "1"}, value:"" },
{ locus: "p2", coordinates: {x: "5", y: "1"}, value:"" },
{ locus: "p3", coordinates: {x: "6", y: "1"}, value:"" },
{ locus: "p4", coordinates: {x: "7", y: "1"}, value:"" }];
function heat_map_graph() {
var colorScale = d3.scale.quantile()
.domain([0, buckets - 1, d3.max(data, function (d) { return parseInt(d); })])
.range(colors);
var svg = d3.select("#punchcard").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var heatMap = svg.selectAll("g")
.data(eye_data)
.enter().append("rect")
.attr("x", function(d) { return (parseInt(d.coordinates.x) - 1) * gridSize; })
.attr("y", function(d) { return (parseInt(d.coordinates.y) - 1) * gridSize; })
.attr("cx", 4)
.attr("cy", 4)
.attr("class", "hour bordered")
.attr("width", gridSize)
.attr("height", gridSize)
.style("fill", colors[0])
.style("stroke", "black");
heatMap.transition().duration(1000)
.style("fill", function(d) { return colorScale(d.value); });
heatMap.append("title").text(function(d) { return d.value; });
var legend = svg.selectAll(".legend")
.data([0].concat(colorScale.quantiles()), function(d) { return d; })
.enter().append("g")
.attr("class", "legend");
legend.append("rect")
.attr("x", function(d, i) { return legendElementWidth * i; })
.attr("y", height + 30)
.attr("width", legendElementWidth)
.attr("height", gridSize / 2)
.style("fill", function(d, i) { return colors[i]; });
legend.append("text")
.attr("class", "mono")
.text(function(d) { return Math.round(d); })
.attr("x", function(d, i) { return legendElementWidth * i; })
.attr("y", height + 30 + gridSize);
}
How can the maximum value be displayed at the end of the last legend rect ??
Any help much appreciated !!
I am trying to implement donut chart with multiple series (ex:2) using the d3.js library.
I need to use json data as source.
Any Idea how to do this.
This is the source code, i used for donut chart:
var width = 800,
height = 400,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888"]);
var arc = d3.svg.arc()
.outerRadius(radius - 70)
.innerRadius(radius - 120);
var pie = d3.layout.pie()
.sort(null)
.value(function (d) { return d.Count; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var jsondata = [{ "id": 1,"age": 5, "population": 2704659 }, { "id": 2, "age": 7, "population": 4499890 }, { "id": 3, "age": 8, "population": 2159981 }];
data = jsondata;
d3.json('talent/data', function (error, data) {
console.log(data);
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function (d) { return color(d.data.Name); });
g.append("text")
.attr("transform", function (d) { return "translate(" + arc.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function (d) { return d.data.Name; });
});
</script>
Multi-series donut chart like : http://www.jqplot.com/tests/pie-donut-charts.php