How to move labels to outside pie chart in D3 - javascript

I'm trying to recreate this in D3
I've got my D3 code here: http://codepen.io/jpezninjo/pen/XpoVwQ
I can't figure out how to move labels to outside my pie chart. I know it's this line
.attr("transform", function(d) {
return "translate(" + labelArc.centroid(d) + ")"; })
but I'm having a hard time looking for information about centroid. I'm guessing it's taking the center between labelArc's inner and outer radius, but I tried messing with that and got no difference.

Try this
.attr("transform", function(d) {
var c = labelArc.centroid(d);
return "translate(" + c[0]*1.2 +"," + c[1]*1.2 + ")";
})
You can play with 1.2 which allows you to position the labels outside the pie chart.

Related

D3 v4: force tick vs click to rotate and breaking the force

I am trying to have rectangles on d3v4 draggable nodes in a force layout to rotate on click with a transition but the tick function seems to interfere with it, either by resetting the rotation attribute or ignoring the transition function.
These are the bits that trouble me:
//CLICK
function clicked(d, i) {
d.ang = d.ang+180
d3.select(this)
.transition()
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")"
});
}
//FORCE
function ticked() {
d.ang += 360
var tiles = box.selectAll('.tile')
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ") rotate(" + d.ang + ")"
});
}
And here is a
bin
with an example
There is also another glitch that I can't figure out, where if you change the window size too fast or have too many objs (so that force makes them bounce wildly) eventually some of them will return NaN in their d.x and d.y and break their translation transformation. Any thoughts on that too would be great.
Thanks in advance!
Separate your transforms.
Add another <g> element that will only control the location of the tiles and one that controls the rotation.
.e.g
<g class="controls-location">
<g class="controls-rotation">
<rect>
<circle>
</g>
</g>

D3 Categorical Area Chart - Scale Issue

I'm trying to create an area chart for statistics on each US state. I have a single number statistic for each state; an element of my data list looks like the following:
{'state':'CA','count':4000}
Currently, my area chart looks like this. The task is mainly complete, but you may notice how the very last category (in this case, UTAH) isn't filled. I'm not quite sure how to get around this. close_up
I am using a scaleBand axis; this felt appropriate. Perhaps it is not the correct approach. Here is the JS behind the chart:
var svg_area = d3.select("#area")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom),
g_area = svg_area.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var x = d3.scaleBand().range([0, width]),
y = d3.scaleLinear().range([height, 0]);
var area = d3.area()
.x(function(d) { return x(d.state); })
.y0(height)
.y1(function(d) { return y(d.count); });
d3.csv('data/states.csv', function(data) {
data.forEach(function(d) {
d.count = +d.count;
});
data.sort(function(a, b){
return b.count-a.count;
});
data = data.slice(0,30);
x.domain(data.map(function(d) { return d.state; }));
y.domain([0, d3.max(data, function(d) { return d.count; })]);
g_area.append('path')
.datum(data)
.attr('fill', solar[1])
.attr("class", "area")
.attr('d', area);
g_area.append("g")
.attr("class", "x-axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g_area.append("g")
.attr("class", "y-axis")
.attr("transform", "translate(0," + 0 + ")")
.call(d3.axisLeft(y));
});
Any suggestions on how I can fix this? Thanks for any feedback!
Contrary to your question's title (now edited), the area chart is not "leaving out the last data point".
What you're seeing is the expected result, since you are using a band scale. Actually, that value just above the horizontal axis (just in the "edge" of the area chart) is Utah value! Try to understanding it with this explanation: Imagine a bar chart with your data. Each bar has, of course, a given width. Now, draw a path going from the top left corner of one bar to the top left corner of the next bar, starting at the first bar and, when reaching the last bar, going down from the top left corner to the axis. That's the area you have right now.
There are two solutions here. The first one is using a point scale instead:
var x = d3.scalePoint().range([0, width])
However, this will trim the "margins" of the area path, before the first state and after the last state (Utah). That means, the area chart will start right over California tick and end right over Utah tick.
If you don't want that there is a second solution, which is hacky, but will keep those "margins": add the bandwidth() to the last state in the area generator:
var area = d3.area()
.x(function(d, i) {
return i === data.length - 1 ?
x(d.state) + x.bandwidth() : x(d.state)
})
It may be worth noting that, using a band scale, your chart is technically incorrect: the values in the area for each state are not over the tick for that state.

line d3.js graph shifting wrongly (to right)

Because I am having an issue with my own code, I am studying below link:
https://bost.ocks.org/mike/path/
I think I get below
// push a new data point onto the back
data.push(random());
// redraw the line, and then slide it to the left
path
.attr("d", line)
.attr("transform", null)
.transition()
.attr("transform", "translate(" + x(-1) + ")");
// pop the old data point off the front
data.shift();
But the part that doesn't work for me and I don't understand is
.attr("transform", "translate(" + x(-1) + ")");
My code's x is like below:
var x = d3.time.scale().range([-5, width])
domain is not known at this time and later domain is defined as
x.domain(d3.extent(callbackData, function(d){return d.reg_date;}));
I try to use x(-1) but entire graph(except axis) disappears when I call for update so I use something like below and initially, it seems to work.(graph shifts to the left). But as more data comes in(btw, my data comes in and updates the data properly(shift off the front data and push latest into the back) graph starts to shifting towards the right. I can see graph start taking points from the front(which is correct) but problem is, graph is starting to shift to right(instead of left).
Really beating my head w/ this issue so hopefully someone can kindly advise.
path
.attr("d",line)
.attr('transform', null)
.transition()
.duration(300)
.ease('linear')
.attr("transform", "translate(" + -2 + ")");
This is a quick implementation : https://jsfiddle.net/thatOneGuy/j2eovk9k/8/
I have added this to work out distance between the x ticks :
var testtickArr = y.ticks(10);
var testtickDistance = y(testtickArr[testtickArr.length - 2]) - y(testtickArr[testtickArr.length - 1]);
Then used this as the distance to move the path :
path
.attr("d", line)
.transition()
.duration(1000)
.ease('linear')
.attr("transform", function(d) {
console.log(d)
return "translate(" + (pathTrans) + ")";
})
Also, update the translation value :
pathTrans -= testtickDistance;
The way you're updating your data can be improved. But this should help you with the transition. This is still not working ok at all as the distances between your points are not equal. For yours to work like the example you need equal distances between each points so it transitions to the left smoothly.

d3js Redistributing labels around a pie chart

I'm using d3.js to create a donut chart with labels on the outside. Using some trigonometry based on the centroids of each slice of the pie, I position the labels.
g.append("g")
.attr("class", "percentage")
.append("text")
.attr("transform", function(d)
{
var c = arc.centroid(d);
var x = c[0];
var y = c[1];
var h = Math.sqrt(x*x + y*y);
return "translate(" + (x/h * obj.labelRadius) + ',' + (y/h * obj.labelRadius) + ")";
}
)
.attr("dy", ".4em")
.attr("text-anchor", function(d)
{
return (d.endAngle + d.startAngle)/2 > Math.PI ? "end" : "start";
}
)
.text(function(d) { return d.data.percentage+"%"; });
What I'm ultimately trying to accomplish is to rearrange labels that are outside the edges of the pie chart, to prevent overlaps.
One of the ways I have tried to solve the problem is to define set "anchor points", where labels can be positioned, guaranteeing that they will no overlap. Problem is mapping the centroids to the anchors and preserving some sense of visual correspondence between the slices and the labels (Specially difficult when slices are slim).
Image above shows the possible location of the anchors (centroids of the slices shown). With these positions it is impossible to have an overlap.
Adding complexity to the problem is the fact that when labels (they're horizontal) are close to the top or bottom of the pie, they are more easily overlapped, than when they are on the right or left of the pie.
Any ideas on how to approach this problem?
[EDIT] Following the suggestion of meetamit, I implemented the following:
.attr("dy", function(d)
{
var c = arc.centroid(d);
var x = c[0];
var y = c[1];
var h = Math.sqrt(x*x + y*y);
var dy = y/h * obj.labelRadius;
dy=dy*fontSizeParam*.14/heightParam);
return (dy)+"em";
})
It helps a bit, and gives some room to the labels, still looking for a solution that will cover all cases though...
Can't you create two arcs? one for the chart, and one for the labels?
// 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; });
There is a good, pragmatic d3.js-based solution by programmer John Williams presented here:
https://www.safaribooksonline.com/blog/2014/03/11/solving-d3-label-placement-constraint-relaxing/
It should work well for cases with reasonable restrictions, e.g. a maximum of 12 labels as discussed above. There are also pointers in the article to more advanced algorithms, but this simple approach may actually, when used with sufficient label-content constraints, give results that have a more orderly visual appearance than other methods would yield.

Display an SVG image at the middle of an SVG path

I have a force directed graph with different size nodes. I want to display a custom icon in the middle of each path connecting two nodes. From the d3 examples I found the way to display images within the nodes. However, when I try the same technique on the paths, the images are not shown.
var path = svg.append("svg:g").selectAll("path").data(force.links());
var pathEnter = path.enter().append("svg:path");
pathEnter.attr("class", function(d) {
return "link " + d.target.type;
})
pathEnter.append("svg:g").append("image")
.attr("xlink:href","http://127.0.0.1:8000/static/styles/images/add.png")
.attr("x",0).attr("y",0).attr("width",12).attr("height", 12)
.attr("class", "type-icon");
I guess I need a bit more patience before asking a question. The way I solved the problem is:
var icon = svg.append("svg:g").selectAll("g")
.data(force.links()).enter().append("svg:g");
icon.append("image").attr("xlink:href","imagePath")
.attr("x", -20)
.attr("y", -2)
.attr("width", 12).attr("height", 12)
.attr("class", "type-icon");
And then in the tick function:
icon.attr("transform", function(d) {
return "translate(" +((d.target.x+d.source.x)/2) + "," +
((d.target.y+d.source.y))/2 + ")";
});
to get the center point between the two nodes.

Categories