I'm working with a data set that's categorically identical from year to year, and I want to make a D3 pie chart with animated transitions from year to year. The data is in a 2-d array, each inner array is a year. Because the number of values isn't changing, I think I can just replace the data set for the transition, and I don't need to do a data join (?).
I have the pie chart working well initially, and I'm updating the data via click event. But my transitions aren't working. Here's the code for the first pie chart (there are variable declarations and other data managing that I've left out to save space, and because that stuff's working):
var outerRadius = w/2;
var innerRadius = 0;
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var svg= d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
var arcs = svg.selectAll("g.arc")
.data(pie(datamod[0]))
.enter()
.append("g")
.attr("class", "arc")
.attr("transform", "translate(" + outerRadius + ", " +outerRadius + ")");
arcs.append("path")
.attr("fill", function(d,i){
return colors[i];
})
.attr("d", arc);
And then to update...clickToChange() is called when users click anywhere in the body. it's loading new data from the next spot in the 2-d array and also updates text for the year, and there's some code in here to keep it from restarting if it's already running... But the main problem I think is with the code to update the arcs...
function clickToChange()
{ if(!isRunning)
{
isRunning = true;
myTimer =setInterval(function() {if (yearcounter < 11)
{
yearcounter++;
}
else
{
yearcounter = 0;
stopDisplay();
}
var thisyear = 2000 + yearcounter; //updating happens here...
svg.selectAll("g.arc")
.data(pie(datamod[yearcounter]))
.transition()
.attr("class", "arc")
.attr("transform", "translate(" + outerRadius + ", " +outerRadius + ")");
arcs.attr("fill", function(d,i){
return colors[i];
// console.log(d.value);
// return "rgb(" + colorscale(d.value) + ",50,50)";
})
.attr("d", arc);
document.getElementById('year').innerHTML = thisyear;
}, 2000); //end set interval
}//end if
}
function stopDisplay()
{
clearInterval(myTimer);
isRunning = false;
}
I think the problem is that I'm possibly not binding the data properly to the correct elements, and if I'm using the correct notation to select the arcs?
Okay, I can see multiple issues/drawbacks with your approach.
1) In your code:
arcs.append("path")
.attr("fill", function(d,i){
return colors[i];
})
.attr("d", arc);
arc is a function call that you are making that doesn't actually exist in the code that you have shared with us, or you need to write. You have this arc function call multiple times, so this will need to be addressed.
2) I would check into using the .on("click", function(d,i) { do your transitions here in this function call }); method instead of setting the transition and attributes of each of the items. I have found that it makes the transition calls easier to manage if you start doing anything more fancy with the transitions. You can see an example of what I mean in the Chord Diagram at http://bl.ocks.org/mbostock/4062006
Hopefully this helps you out a bit.
Related
I'm trying to have the chart tickets in a D3 bullet chart follow the data itself, as per the 2nd example here:
Bullet chart ticks & labels in D3.js
The issue is that the source of this (http://boothead.github.io/d3/ex/bullet.html) no longer exists on the internet, the only thing out there is the gif in this post that I've linked.
enter image description here
Does anyone have the original copy of this project or have any advice?
I'm using the first example by mbostock and trying to replicate the bottom one.
Many thanks
In the original bullet.js from the bostock example https://bl.ocks.org/mbostock/4061961
Instead of getting the ticks from the scale you get the values from the range, measure and mark
change around line 109
// var tickVals = x1.ticks(8);
var tickVals = rangez.concat(measurez).concat(markerz);
// Update the tick groups.
var tick = g.selectAll("g.tick")
.data(tickVals, function(d) {
return this.textContent || format(d);
});
Edit
There is a problem if you update the data based on a new fetch from the server. some of the ticks end up on the wrong location. If the number of markers,measures,ranges also change they also end up at the wrong location.
It depends on the selection you supply to the bullet call.
The confusion is caused by the poor naming of the main program.
var svg = d3.select("body").selectAll("svg")
.data(data)
.enter().append("svg")
.attr("class", "bullet")
.attr("width", svgWidth)
.attr("height", svgHeight)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.call(chart);
The name suggests that svg is a selection of svg elements. This is incorrect, it is a selection of g elements.
The update() function should reflect this
function updateData() {
d3.json("mailboxes.json", function (error, data) {
d3.select("body")
.selectAll("svg")
.select('g')
.data(data)
.call(chart.duration(500));
});
}
If the number of bullet graphs changes on the update there is the problem that they are not created or deleted if needed. So we need to make a function that can be used for initial and update calls.
function drawCharts() {
d3.json("mailboxes.json", function (error, data) {
var svg = d3.select("body").selectAll("svg").data(data);
svg.exit().remove();
svg.enter().append("svg")
.attr("class", "bullet")
.attr("width", svgWidth)
.attr("height", svgHeight)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.select("body")
.selectAll("svg")
.select('g')
.data(data)
.call(chart.duration(500));
});
}
A better change in bullet.js [109] would be:
// var tickVals = x1.ticks(8);
var tickVals = [];
rangez.concat(measurez).concat(markerz).forEach(e => {
if (tickVals.indexOf(e) == -1) tickVals.push(e);
});
// Update the tick groups.
var tick = g.selectAll("g.tick").data(tickVals);
That is do not use the value of the tick to match, in case we have multiple values in the ticks, and remove the duplicates.
We also need to change the update of the ticks, about 30 lines down
tickUpdate.select("text")
.attr("y", height * 7 / 6);
to
tickUpdate.select("text")
.text(format)
.attr("y", height * 7 / 6);
I am using leaflet and am displaying a json with d3 (vers3) on the map.
Basically I am following this tutorial here.
This is my code. The first callbackHandler describes a method that receives information which another language sends to JavaScript, based on userinteraction with the website.
pathToFile is a link to a file (json) that is then loaded by d3.json(...).
var svg = d3.select(map.getPanes().overlayPane).append("svg"),
g = svg.append("g").attr("class", "leaflet-zoom-hide");
someMethod("myName", function(pathToFile) {
console.log(pathToFile);
d3.json(pathToFile, function(error, collection) {
console.log(collection);
if (error) throw error;
// Use Leaflet to implement a D3 geometric transformation.
function projectPoint(x, y) {
var point = map.latLngToLayerPoint(new L.LatLng(y, x));
this.stream.point(point.x, point.y);
}
var transform = d3.geo.transform({point: projectPoint}),
path = d3.geo.path().projection(transform);
var feature = g1.selectAll("path")
.data(collection.features)
.enter().append("path")
.attr("stroke-width", 0.5)
.attr("fill-opacity", 0.7)
.attr("stroke", "white")
map.on("viewreset", reset);
reset();
// Reposition the SVG to cover the features.
function reset() {
var bounds = path.bounds(collection),
topLeft = bounds[0],
bottomRight = bounds[1];
svg1 .attr("width", bottomRight[0] - topLeft[0])
.attr("height", bottomRight[1] - topLeft[1])
.style("left", topLeft[0] + "px")
.style("top", topLeft[1] + "px");
g.attr("transform", "translate(" + -topLeft[0] + "," + -topLeft[1] + ")");
feature.attr("d", path);
}
console.log("1");
});
console.log("2");
});
The funny part is: The first time the code is executed it works fine. My json gets displayed on the map as it should. However, when the first callbackHandler (someMethod) gets executed a second time (upon interaction through some user with the website), the new json does not get displayed on leaflet.
Thats the output of the console.log I included after trying to update the map:
// on startup of website, the callbackHandler "someMethod" gets
./some/path/toFile
Object {crs: Object, type: "FeatureCollection", features: Array[20]}
2
1
// after interaction with the website and execution of the callbackHandler "someMethod"
./some/other/path/toFile
Object {crs: Object, type: "FeatureCollection", features: Array[9]}
2
1
But, the new json does not get displayed. Instead the old one stays.
Why is that?
Since I dont have a code to play around.
My hunch is:
When you first call someMethod the file gets uploaded and everything works fine.
var feature = g1.selectAll("path")
.data(collection.features)
.enter().append("path")
.attr("stroke-width", 0.5)
.attr("fill-opacity", 0.7)
.attr("stroke", "white")
Reason:
First time g1.selectAll("path") runs. The selection is empty and you append path as per the data in the collection.features this will work.
Second time when you do g1.selectAll("path") it will return paths you bind data but append will not work.
So the problem is you need to remove old collection.features or need to update it.
To do that
Option1
var paths = g1.selectAll("path").data()
paths.exit().remove();//remove old data paths
var feature = paths
.enter().append("path")
.attr("stroke-width", 0.5)
.attr("fill-opacity", 0.7)
.attr("stroke", "white")
Option 2 remove all the paths
var paths = g1.selectAll("path").data()
g1.selectAll("path").remove();//remove all paths
var feature = paths
.enter().append("path")
.attr("stroke-width", 0.5)
.attr("fill-opacity", 0.7)
.attr("stroke", "white")
Hope this fixes your problem!
Read update/enter/exit here
I trying to understand how the D3 chord diagram works. My first step is to display the arcs for the diagram with following script. But for some reason, the arcs are not showing up.
See web page HERE
Can some one tell me what I am missing?
<body>
<script>
// Chart dimensions.
var width = 960,
height = 750,
innerRadius = Math.min(width, height) * .41,
outerRadius = innerRadius * 1.1;
//Create SVG element with chart dementions
var svg = d3. select("body")
.append("svg")
.attr("width", width)
.attr("height", height)
.append ("g")
.attr("transform", "translate (" + width / 2 + "," + height / 2 + ")");
//------------Reformat Data ------------------------------------------
var matrix = []; // <- here is the data
d3.tsv('picData.tsv', function(err, data)
{
//console.log(data);
pictures = d3.keys(data[0]).slice(1);
//console.log(pictures);
data.forEach(function(row)
{
var mrow = [];
pictures.forEach(function(c)
{
mrow.push(Number(row[c]));
});
matrix.push(mrow);
//console.log(mrow);
});
//console.log('1st row: ' + matrix[0]);
//console.log(matrix);
});
//---------------- Define diagram layout ----------------------------
var chord = d3.layout.chord() //<-- produce a chord diagram from a matrix of input data
.matrix(matrix) //<-- data in matrix form
.padding(0.05)
.sortSubgroups(d3.descending);
var fill = d3.scale.category20(); //<-- https://github.com/mbostock/d3/wiki/API-Reference#d3scale-scales
//console.log(fill);
var g = svg.selectAll("g.group")
.data(chord.groups)
.enter().append("svg:g")
.attr("class", "group");
//console.log(g);
// create arcs
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
//console.log(arc);
g.append("path")
.attr("d", arc)
.style("fill", function(d) { console.log(d.index); return fill(d.index);})
.style("stroke", function(d) { return fill(d.index); })
.attr("id", function(d, i) { return"group-" + d.index });;
g.append("svg:text")
.attr("x", 6)
.attr("class", "picture")
.attr("dy", 15)
.append("svg:textPath")
.attr("xlink:href", function(d) { return "#group-" + d.index; })
.text(function(d) { return pictures[d.index]; });
//console.log(g);
</script>
</body>
Your problem stems from the fact that d3.tsv is asynchronous:
Issues an HTTP GET request for the comma-separated values (CSV) file at the specified url... The request is processed asynchronously.
As a result, all of your code under "Define diagram layout" is being executed before any data is loaded. Otherwise, your code works fine (See image below). So just move all your code into your d3.tsv(...) call and you'll be all set.
Your script is running without errors, but no elements are being created from your data join. That's usually a sign that you are passing in a zero-length data array.
In fact, you're not passing in an array at all; you're passing a function object. When d3 looks up the array length of that object, it returns undefined, which gets coerced to the number zero, and so no groups and no chords are created.
Relevant part of your code:
var g = svg.selectAll("g.group")
.data(chord.groups)
.enter().append("svg:g")
.attr("class", "group");
To actually get the array of chord group data objects, you need to call chord.groups(). Without the () at the end, chord.groups is just the name of the function as an object.
Edited to add:
Ooops, I hadn't even noticed that your drawing code wasn't included inside your d3.tsv callback function. Fix that, as described in mdml's answer, then fix the above.
I have some code on this jsFiddle here that generates a histogram for a data array called "values". That's all well and good.
When I want to update this histogram with a new data array, called "newData", things go wrong. I am trying to adhere to the enter(), update(), exit() D3 strategy (which I am obviously extremely new with). An animation does indeed occur, but as you can see by the fiddle, it just squishes everything into the upper right hand corner. Can someone point out what I am doing wrong in this segment of the code (the update)?
//Animations
d3.select('#new')
.on('click', function(d,i) {
var newHist = d3.layout.histogram().bins(x.ticks(bins))(newData);
var rect = svg.selectAll(".bar")
.data(values, function(d) { return d; });
// enter
rect.enter().insert("g", "g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x(d) + "," + y(d) + ")"; });
rect.enter().append("rect")
.attr("x", 1)
.attr("width", w)
.attr("height", function(d) { return y(d); });
rect.enter().append("text")
.attr("dy", ".75em")
.attr("y", 6)
.attr("x", x(histogram[0].dx) / 2)
.attr("text-anchor", "middle")
.text(function(d) { return formatCount(d); });
// update
svg.selectAll('.bar')
.data(newHist)
.transition()
.duration(3000)
.attr("transform", function(d) { return "translate(" + x(d.x) + "," + y(d.y) + ")"; });
svg.selectAll("rect")
.data(newHist)
.transition()
.duration(3000)
.attr("height", function(d) { return height - y(d.y); });
svg.selectAll("text")
.data(newHist)
.transition()
.duration(3000)
.text(function(d) { return formatCount(d.y); });
// exit
rect.exit()
.remove();
});
The entirety of the code is on the JSFiddle linked above. Thanks!
Looking at the code above and the fiddle, a few things jump out at me:
(Line 85) You are still binding the original data
(Lines 105, 115) You are binding the data multiple times
(Line 99) You are still referencing the original histogram variable without updating it with the new data
You are declaring multiple bind/add/update/remove patterns for a single set of (changing) data
You're on the right track, but you need to differentiate between things that need to be updated when the data changes, and things that should not be updated/declared when the data changes. You should only have to declare the d3 pattern (bind, add, update, remove) once... it will work for updated datasets.
So, declare as much as you can outside the makeHist(values) function, and only have code that needs the changed data inside the function (this includes modifying a previously declared scale's domain and range). Then, the on click function can simply call the makeHist function again with the new data.
Here's a rough outline:
// generate data
// declare everything that can be static
// (add svg to dom, declare axes, etc)
// function that handles everything that new data should modify
function makeHist(values) {
// modify domains of axes, create histogram
// bind data
var rect = svg.selectAll('rect')
.data(histogram);
// add new elements
rect.enter().append('rect');
// update existing elements
rect.transition()
.duration(3000)
.attr('transform', '...');
// remove old elements
rect.exit().remove();
}
// generate initial histogram
makeHist(initialValues);
// handle on click event
d3.select('#new')
.on('click', function() {
makeHist(newData);
});
Here's a mostly working updated fiddle, it needs a little bit of cleanup, though:
http://jsfiddle.net/spanndemic/rf4cw/
Spoiler Alert: the two datasets aren't all that different
I'm looking to create a pie chart with floating labels using D3. I'm new to D3 and I'm not even sure this is possible? Can you use the labels of one graph in another somehow? If you can, can you point me to an example?
Shorter Explanation:
I want labels from:
http://bl.ocks.org/1691430
...to be on a pie chart.
Here's the code I was running below:
Or in a JSBIN: http://jsbin.com/awilak/1/edit
If I understand his code correctly, this is the section that adds the labels. I don't understand what the labelForce.update does. From there, I don't care about transition, so that line isn't needed. Then the rest is just drawing the circles and adds a link / line? If someone could integrate that would be amazing but if you can help me understand what's going on and what I'm missing I'd be more than grateful.
// Now for the labels
// This is the only function call needed, the rest is just drawing the labels
anchors.call(labelForce.update)
labels = svg.selectAll(".labels")
.data(data, function(d,i) {return i;})
labels.exit()
.attr("class","exit")
.transition()
.delay(0)
.duration(500)
.style("opacity",0)
.remove();
// Draw the labelbox, caption and the link
newLabels = labels.enter().append("g").attr("class","labels")
newLabelBox = newLabels.append("g").attr("class","labelbox")
newLabelBox.append("circle").attr("r",11)
newLabelBox.append("text").attr("class","labeltext").attr("y",6)
newLabels.append("line").attr("class","link")
labelBox = svg.selectAll(".labels").selectAll(".labelbox")
links = svg.selectAll(".link")
labelBox.selectAll("text").text(function(d) { return d.num})
}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Testing Pie Chart</title>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.js?2.1.3"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.geom.js?2.1.3"></script>
<script type="text/javascript" src="http://mbostock.github.com/d3/d3.layout.js?2.1.3"></script>
<style type="text/css">
.slice text {
font-size: 16pt;
font-family: Arial;
}
</style>
</head>
<body>
<button id="button"> Test </button>
<br>
<form id="controls">
<div>
<h2>Y axis</h2>
<ul id="y-axis">
<li><label><input checked="checked" type="radio" name="y-axis" value="Component">Component</label></li>
<li><label><input type="radio" name="y-axis" value="Browser">Browser</label></li>
<li><label><input type="radio" name="y-axis" value="Version">Version</label></li>
</ul>
</div>
</form>
<script type="text/javascript">
// return a list of types which are currently selected
function plottableTypes () {
var types = [].map.call (document.querySelectorAll ("#coaster-types input:checked"), function (checkbox) { return checkbox.value;} );
return types;
}
var w = 600, //width
h = 600, //height
r = 100,
r2 = 200, //radius
axis = getAxis (), //axes
color = d3.scale.category20c(); //builtin range of colors
data = [
{"Browser":"Internet Explorer ","Version":"8.0","Toatl":2000,"Component":"6077447412293130422"},
{"Browser":"Internet Explorer ","Version":"9.0 ","Toatl":1852,"Component":"6077447412293130422"},
{"Browser":"Internet Explorer ","Version":"6.0 ","Toatl":1754,"Component":"6077447412293130422"},
{"Browser":"Firefox ","Version":"16.0 ","Toatl":1020,"Component":"6077447412293130422"},
{"Browser":"Chrome ","Version":"23.0 ","Toatl":972,"Component":"6077447412293130422"},
{"Browser":"Internet Explorer ","Version":"7.0 ","Toatl":700,"Component":"6077447412293130422"},
{"Browser":"Mobile Safari ","Version":"6.0 ","Toatl":632,"Component":"6077447412293130422"},
{"Browser":"BOT ","Version":"BOT ","Toatl":356,"Component":"6077447412293130422"},
{"Browser":"Firefox ","Version":"8.0 ","Toatl":196,"Component":"6077447412293130422"},
{"Browser":"Mobile Safari ","Version":"5.1 ","Toatl":184,"Component":"6077447412293130422"}
];
var vis = d3.select("body")
.append("svg:svg") //create the SVG element inside the <body>
.data([data]) //associate our data with the document
.attr("width", w) //set the width and height of our visualization (these will be attributes of the <svg> tag
.attr("height", h)
.append("svg:g") //make a group to hold our pie chart
.attr("transform", "translate(" + r2 + "," + r2 + ")") //move the center of the pie chart from 0, 0 to radius, radius
var arc = d3.svg.arc() //this will create <path> elements for us using arc data
.outerRadius(r);
var pie = d3.layout.pie() //this will create arc data for us given a list of values
.value(function(d) { return d.Toatl; }); //we must tell it out to access the value of each element in our data array
var arcs = vis.selectAll("g.slice") //this selects all <g> elements with class slice (there aren't any yet)
.data(pie) //associate the generated pie data (an array of arcs, each having startAngle, endAngle and value properties)
.enter() //this will create <g> elements for every "extra" data element that should be associated with a selection. The result is creating a <g> for every object in the data array
.append("svg:g") //create a group to hold each slice (we will have a <path> and a <text> element associated with each slice)
.attr("class", "slice"); //allow us to style things in the slices (like text)
arcs.append("svg:path")
.attr("fill", function(d, i) { return color(i); } ) //set the color for each slice to be chosen from the color function defined above
.attr("d", arc); //this creates the actual SVG path using the associated data (pie) with the arc drawing function
arcs.append("svg:text") //add a label to each slice
.attr("transform", function(d) { //set the label's origin to the center of the arc
//we have to make sure to set these before calling arc.centroid
d.innerRadius = r2;
d.outerRadius = r;
return "translate(" + arc.centroid(d) + ")"; //this gives us a pair of coordinates like [50, 50]
})
.attr("text-anchor", "middle") //center the text on it's origin
.text(function(d, i) {
if(axis.yAxis == "Component"){
return data[i].Component;
}
return data[i].Browser; //get the label from our original data array
});
d3.select('#button').on('click', reColor);
var arcOver = d3.svg.arc()
.outerRadius(r + 30)
.innerRadius(0);
var arc = d3.svg.arc()
.outerRadius(r)
.innerRadius(0);
var arcs = vis.selectAll("g.slice")
.attr("class", "slice")
.on("mouseover", function(d) {
getAxis();
d3.select(this)
.select("path")
.transition()
.duration(500)
.attr("d", arcOver);
d3.select(this).select("text")
.text(function(d, i) {
if(axis.yAxis == "Component"){
return data[i].Component;
}
return data[i].Browser; //get the label from our original data array
});
})
.on("mouseout", function(d) {
getAxis();
d3.select(this)
.select("path")
.transition()
.duration(500)
.attr("d", arc);
d3.select(this)
.select("text")
.text(function(d, i) {
if(axis.yAxis == "Component"){
return data[i].Component;
}
return data[i].Browser; //get the label from our original data array
});
});
function reColor(){
var slices = d3.select('body').selectAll('path');
slices.transition()
.duration(2000)
.attr("fill", function(d, i) { return color(i+2); } );
slices.transition()
.delay(2000)
.duration(2000)
.attr("fill", function(d, i) { return color(i+10); } )
}
function makeData(){
}
// return an object containing the currently selected axis choices
function getAxis () {
var y = document.querySelector("#y-axis input:checked").value;
return {
yAxis: y,
};
}
function update() {
axis = getAxis()
arcs.selectAll("text") //add a label to each slice
.text(function(d, i) {
if(axis.yAxis == "Component"){
return data[i].Component;
}
return data[i].Browser; //get the label from our original data array
});
}
document.getElementById("controls").addEventListener ("click", update, false);
document.getElementById("controls").addEventListener ("keyup", update, false);
</script>
</body>
</html>
As others mentioned in the comments to your introduction-post it's possible to achieve a solution like you described it and it's possible using your code plus parts of the "moving-labels"-example. If I understand you correctly, you want to achieve non-overlapping labels using the force-layout, which is a pretty nice idea that I didn't stumble upon yet.
The code-part you pasted from the example just draws the labels and the lines as you already explained correctly. The next step is to rearrange the labels in a force-like layout around your pie chart.
The part that rearranges the labels (and links) in the example is the following:
function redrawLabels() {
labelBox
.attr("transform",function(d) { return "translate("+d.labelPos.x+" "+d.labelPos.y+")"})
links
.attr("x1",function(d) { return d.anchorPos.x})
.attr("y1",function(d) { return d.anchorPos.y})
.attr("x2",function(d) { return d.labelPos.x})
.attr("y2",function(d) { return d.labelPos.y})
}
// Initialize the label-forces
labelForce = d3.force_labels()
.linkDistance(0.0)
.gravity(0)
.nodes([]).links([])
.charge(-60)
.on("tick",redrawLabels)
The function is the one that changes the positions of the labels and lines. The force is calculated by D3 and started with the d3.force_labels().... As you can see, the function is assigned as an event-handler for the tick-event. In other words: After every step of calculating the force, D3 calls the 'drawer' for every label and updates the positions.
Unfortunately I'm not very familiar with the force_labels() method of D3, but I would assume it works pretty much like the regular force().
An anchor, in your case, is placed somewhere in each pie-piece for each label. The more centered within each pie-piece (NOT the pie itself), the better. Unfortunately you have to calculate this anchor-position somehow (sin and cos stuff) and set the line-ends to this fixed position within redrawLabels().
After you've done this you will see the first result. You may have to play around with gravity, linkDistance etc values of the force to achieve good results. (That's what the silders in the example do.)
See d3 docs for more info: https://github.com/mbostock/d3/wiki/Force-Layout
Then you will maybe stumble upon the problem that the labels are ordered around the pie without overlapping but in some strange order. You could solve this by initially placing the labels in correct order on a larger circle around your pie instead of positioning them randomly around the panel, which is the cause for the problem. This way you will experience way less jitter and misplacements.
The idea also is described in another blocks example: http://bl.ocks.org/mbostock/7881887
In this example, the nodes are initially placed on a virtual circle. The positioning is calculated by the following functions:
x: Math.cos(i / m * 2 * Math.PI) * 200 + width / 2 + Math.random(),
y: Math.sin(i / m * 2 * Math.PI) * 200 + height / 2 + Math.random()
They represent a circle with a radius of 200, place in the center of the drawing-panel. The circle is divided into m equally large pieces. i/m just calculates the 'piece-positions' where i ranges from 0 to m-1.
Hope I could help!
Yes, you can definitely combine force-labels with a pie chart! There is nothing particularly special about the pie chart labels you started with, they're just text elements that can be positioned like anything else using transform or x/y. It looks like you were initially positioning these labels according to the centroids of the arcs they were labelling, but you can just as easily use another criteria (like the output of a force layout).
D3's force layout calculates positions for things based on a set of constraints about what is fixed, what is movable, and which are connected to which. The labelForce.update method from Mike's bl.ocks example is being used to inform the force layout about how many objects need to be positioned, and where the fixed "anchor" points are. It then saves the computed positions for the labels into the diagram's data model, and they are used later on in the redrawLabels function.
You need to create two arcs. One for the pie chart drawing, and one which is large for the labels to sit on.
// first arc used for drawing the pie chart
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
// label attached to first arc
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.age; });
// second arc for labels
var arc2 = d3.svg.arc()
.outerRadius(radius + 20)
.innerRadius(radius + 20);
// label attached to second arc
g.append("text")
.attr("transform", function(d) { return "translate(" + arc2.centroid(d) + ")"; })
.attr("dy", ".35em")
.style("text-anchor", "middle")
.text(function(d) { return d.data.age; });