D3.js animation for donut labels - javascript

I already have a donut chart in d3.js.
The animation for the labels is some thing like this right now :
starting point of labels : the labels are all in the center of the donut
ending point : they end up going behind the arcs.
Now below is what i am trying to achieve....
what i want to achieve :
I want to change the starting point of labels.
I want the labels to appear from behind the arcs of the donuts instead of
the center.
The ending point is fine like it is now.
Is there a way i can achieve this ?
i.e change the emission point of the labels instead from the center they should
appear from behind the arcs.
Here is the code that i am trying to modify :
var text=svg.selectAll('text')
.data(pie(dataset.data))
.enter()
.append("text")
.transition()
.duration(1000)
.attr("transform", function (d) {
console.log(d);
console.log(arc.centroid(d));
var c = arc.centroid(d),
x = c[0],
y = c[1],
h = Math.sqrt(x*x + y*y);
return "translate(" + (x/h * labelr) + ',' +
(y/h * labelr) + ")";
})
.attr("dy", ".4em")
.attr("text-anchor", "middle")
.text(function(d){
return d.data +"%";
})
.style({
fill:'#000',
'font-size':'11px'
});
Below is the link to the fiddle :
https://jsfiddle.net/ahc4wdjk/

In D3 transition selections, the starting value is the current atribute value. So, we start creating the texts behind the arcs:
.attr("x", function(d){
return arc.centroid(d)[0]
})
.attr("y", function(d){
return arc.centroid(d)[1]
})
I had a problem here, because your arcs don't show up at the same time. The solution was giving the texts an initial .attr("opacity", 0) and waiting a little bit (using delay(1000)).
Then, I used your code for the final position, but changing labelr to labelr = radius - 160;.
Here is the fiddle: https://jsfiddle.net/gerardofurtado/zrahm2h4/1/

Related

Radial time series, appending circles

I have some circles I want to append to a radial time series chart to indicate key events. Equivalent block here.
static picture:
The code for the circles:
var eventCircles = g.selectAll('.eventCirc')
.data(eventData)
.enter()
.append('circle')
.attr('class','eventCirc')
.attr('cx', function(d) { return x(d.date); })
.attr('cy', function(d) { return y(0)})
.attr('r', 5)
.style('fill', "#003366");
The y(0) scale seems to work fine, because the units are both in pixels, but I can't figure out how to convert degrees to pixels for use with cx -- which is a required attribute for a circle.
The scales are set up as so:
var x = d3.scaleTime()
.range([0, fullCircle]);
var y = d3.scaleRadial()
.range([innerRadius, outerRadius]);
Question
How can I use d.date in conjunction with the x scale to give me a pixel coordinate for the cx attribute (and not simply a degree/radian)?
You need a bit of trigonometry here. Given the specific bl.ocks you linked, this is the math you need:
eventCircles.attr('cx', function(d) {
return y(d.Close) * -Math.sin(x(d.Date) + Math.PI)
})
.attr('cy', function(d) {
return y(d.Close) * Math.cos(x(d.Date) + Math.PI)
})
In the bl.coks you linked, Close is the y value and Date is the x value. Change them according to your data.
Here is the forked bl.ocks: https://blockbuilder.org/GerardoFurtado/16adc1bb5677adfa501b3a03b3637d75

d3 chord: center text on circle

I use the d3 chord diagram example of Andrew and want to center all text labels within the curved slice. I tried many things but was never able to center the texts. Do you know what wizzard trick there is needed?
var width = 720,
height = 720,
outerRadius = Math.min(width, height) / 2 - 10,
innerRadius = outerRadius - 24;
var formatPercent = d3.format(".1%");
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius);
var layout = d3.layout.chord()
.padding(.04)
.sortSubgroups(d3.descending)
.sortChords(d3.ascending);
var path = d3.svg.chord()
.radius(innerRadius);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("id", "circle")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
svg.append("circle")
.attr("r", outerRadius);
d3.csv("ex_csv.csv", function(cities) {
d3.json("ex_json.json", function(matrix) {
// Compute the chord layout.
layout.matrix(matrix);
// Add a group per neighborhood.
var group = svg.selectAll(".group")
.data(layout.groups)
.enter().append("g")
.attr("class", "group")
.on("mouseover", mouseover);
// Add the group arc.
var groupPath = group.append("path")
.attr("id", function(d, i) { return "group" + i; })
.attr("d", arc)
.style("fill", function(d, i) { return cities[i].color; });
// Add a text label.
var groupText = group.append("text")
.attr("x", 6)
.attr("dy", 15);
groupText.append("textPath")
.attr("xlink:href", function(d, i) { return "#group" + i; })
.text(function(d, i) { return cities[i].name; });
// Remove the labels that don't fit. :(
groupText.filter(function(d, i) { return groupPath[0][i].getTotalLength() / 2 - 16 < this.getComputedTextLength(); })
.remove();
// Add the chords.
var chord = svg.selectAll(".chord")
.data(layout.chords)
.enter().append("path")
.attr("class", "chord")
.style("fill", function(d) { return cities[d.source.index].color; })
.attr("d", path);
}
});
});
</script>
As an aside, I would suggest looking to upgrade to v4, documentation for v2 is nearly non-existent and is very hard to help with.
You can set both the text-anchor and the startOffset property to achieve what you are looking for.
First, you'll want to set text-anchor to middle as it is easier to specify the middle point than to find the middle point and work back to find where the text should start.
Second you'll need to set a startOffset. Note that if you use 50%, the text will not appear where you want, as the total length of the text path is all sides of the closed loop (chord anchor) you are appending to. Setting it to 25 % would work if you did not have a different outer and inner radius. But, as you have an outer radius that is 24 pixels greater than the inner radius you can try something like this to calculate the number of pixels you need to offset the center of the text:
groupText.append("textPath")
.attr("xlink:href", function(d, i) { return "#group" + i; })
.text(function(d, i) { return cities[i].name; })
.attr("startOffset",function(d,i) { return (groupPath[0][i].getTotalLength() - 48) / 4 })
.style("text-anchor","middle");
I subtract 48 because the sides of the anchor are 24 pixels each (the difference in the radii). I divide by four because the path doubles back on itself. If it was a general line I would just divide by two.
This approach is a little simplistic as the outer circumference is not the same as the inner circumference of each chord anchor, so I am off by a little bit, but it should be workable.
For labels that are on the cusp of being displayed, this will be awkward: the inner radius is shorter, so the formula for deteriming if a string is short enough to be displayed may be wrong - which may lead to some characters climbing up the side of the anchor (your example also 16 pixels as the difference in radii to calculate if text is too long, rather than 24).
This is the end result:
Here is a demonstration.

D3.js Rotate Vertical Bar Graph Labels

I'm new to D3.js and using following example from D3.js to create a simple dashboard for one of my web application.
http://bl.ocks.org/NPashaP/96447623ef4d342ee09b
My requirement is to rotate top value labels of each bar vertically by 90 degrees.
I changed following method by adding "transform" attribute. Then the labels do not align properly.
//Create the frequency labels above the rectangles.
bars.append("text").text(function(d){ return d3.format(",")(d[1])})
.attr("x", function(d) { return x(d[0])+x.rangeBand()/2; })
.attr("y", function(d) { return y(d[1])-5; })
.attr("text-anchor", "middle")
.attr("transform", function(d) { return "rotate(-90)" });
I tried to find a solution for long time but couldn't. Links to my codes are given below.
https://jsfiddle.net/vajee555/7udmyj1k/
Can anybody please give me an idea how to archive this?
Thanks!
EDIT:
I have solved the problem here.
http://jsfiddle.net/vajee555/7udmyj1k/5/
Remember that when you rotate an element, the x and y coordinates are changed: they are no longer with respect to that of the chart, but with respect to the new rotated orientation of the element. Therefore, you will need to compute the x and y attributes differently.
By rotating -90deg, your x axis will be flipped to y, and the y will be flipped to -x:
I have made some small pixel adjustments to make it appear aesthetically pleasing, such as the +8 I have added to the y coordinate and the +5 I have added to the x coordinate, but the fine tuning is up to you.
// Create the frequency labels above the rectangles.
bars.append("text").text(function(d){ return d3.format(",")(d[1])})
.attr('transform', 'rotate(-90)')
.attr("y", function(d) { return x(d[0]) + x.rangeBand()/2 + 4; })
.attr("x", function(d) { return -y(d[1]) + 5; });
Also, change how the coordinates are calculated in the hG.update() function:
// transition the frequency labels location and change value.
bars.select("text").transition().duration(500)
.text(function(d){ return d3.format(",")(d[1])})
.attr("x", function(d) { return -y(d[1]) + 5; });
See working fiddle here: https://jsfiddle.net/teddyrised/7udmyj1k/2/
//Create the frequency labels above the rectangles.
bars.append("text").text(function(d){ return d3.format(",")(d[1])})
.attr("x", function(d) { return x(d[0])+x.rangeBand()/2; })
.attr("y", function(d) { return y(d[1])-5; })
.attr("text-anchor", "middle")
.attr("transform", "rotate(-90,0,0)" );
Change the last line as above.

d3 - Rotate text elements in an array

I am trying to use selectAll("text") in d3 to to add an array of string values (called 'data') to my graph. I want each individual data point to be rotated at the point it is placed at, defined here as (i * (width/ data.length) + 8, 170). However, it is currently rotating the entire array set as one long string, with the first element at the (x, y) point I set. How can I appropriately apply the translate rotation to rotate each element individually?
new_svg.selectAll("text")
.data(data)
.enter().append("text")
.attr("x", function(d, i) {
return i * (width / data.length) + 8;
})
.attr("y", function(d) {
return 170;
})
.attr("dx", -barWidth/2)
.attr("text-anchor", "middle")
.attr("style", "font-size: 12; font-family: Garamond, sans-serif")
.text(function(d) { return d;})
.attr("transform", function(d) {
return "rotate(45)"
});
There was more than one text element in the array, which I was able to verify. It turns out that the problem comes from setting the x and y elements separate from the translate transform. The solution offered here worked for me: d3 x axis labels outputted as long string

D3 - Pie Chart & Force Directed Labels

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; });

Categories

Resources