D3 spacing between arcs - d3.js

I am pretty new to D3 chart and I created my first donut chart using D3, but I was wondering if there is anyway I can put some padding/spacing between each arc.
I know I could reduce each arc's start and end angles, for example,
arc 1: from 90degree to 120degree
arc 2: from 120degree to 150degree
reduce the angles above like
arc 1: from 92degree to 118degree
arc 2: from 122 degree to 148degree
and so on..
but I am curious if there is any easier way to put some spacing.
Here's my code and you can see the full code in JSfiddle.
var vis = d3.select(elementSelector);
var arc = d3.svg.arc()
.innerRadius(svgInnerRadius)
.outerRadius(svgOuterRadius)
.startAngle(function(d){return anglePercentage(d[0]);})
.endAngle(function(d){return anglePercentage(d[1]);});
...
http://jsfiddle.net/24FaQ/
Thank you so much in advance.

This is actually what you're looking for:
http://bl.ocks.org/mbostock/f098d146315be4d1db52
var pie = d3.layout.pie()
.padAngle(.02);

If you're drawing on top of a solid background (white or otherwise), you can add stroke to achieve this effect without modifying the angles.
Here's a modified fiddle.
vis.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("d", arc)
.style("fill", function(d){return color(d[2]);})
.attr('stroke', '#fff') // <-- THIS
.attr('stroke-width', '6') // <-- THIS
.attr("transform", "translate(" + svgWidth / 2 + ", " + svgHeight / 2 + ")");
This applies the stroke to all the edges, including the curved ones. If you need to avoid that, the you have to instead draw and position lines with white strokes at the start/end of each slice.

Related

D3 Transition Path Left And Shift Up/Down Simultaneously

Following the tutorial of Mike Bostock on Path transitions here, I am trying to create an interpolated line chart that not only shifts through time but also transitions the y-scale / y-axis, such that is always fits to the lower and upper bounds of the data.
Some background information: The line is clipped by a clipPath and is shifted to the left whenever a new data point is added. Each new data point is added by the tick function, which also transitions the path to slide to the left.
Now the problem is, when I update the y-axis domain, it jumps to the new position. However, I would like it to smoothly transition up or down, similar to how it shifts along the x-axis. The solution probably lies in transforming the scaling of the path based on the new maximum of the data. Is there any way to achieve this or would it require a different approach by building a custom d3.interpolator() for interpolating the path?
function tick() {
// Push a new data point onto the back.
data.push(random());
// Redraw the line.
d3.select(this)
.attr("d", line)
.attr("transform", null);
// Slide it to the left.
d3.active(this)
.attr("transform", "translate(" + x(0) + ",0)")
.transition()
.on("start", tick);
// Pop the old data point off the front.
data.shift();
let max = d3.max(data, (d) => {
return d;
});
y = d3.scaleLinear()
.domain([-1, max])
.range([height, 0]);
d3.select('g .axis.axis--y').transition().duration(500).call(d3.axisLeft(y))
d3.select('g .axis.axis--x').transition().duration(500)
.attr("transform", "translate(0," + y(0) + ")")
}
I have created a jsfiddle which demonstrates the problem here.
Found a solution by using the external d3-interpolate-path library from here. Instead of transitioning the path using a transform, I interpolate the path with the old data and the path with the new data using d3.interpolatePath. Where previous is the old path and current is the new path with the newly added data point.
d3.select(this)
.attr("d", lineOld)
.attr("transform", null)
.transition().duration(500).ease(d3.easeLinear).attrTween('d', (d) => {
let previous = d3.select(this).attr('d');
let current = line(d);
return d3.interpolatePath(previous, current)
}).on("end", tick);
The Jsfiddle with my solution can be found here

Add text in rect svg and append it to arc in donut chart

I wanted to add labels to each arc in donut chart. I've added by taking the centroid of each arc and adding, but somehow it is not adding in correct position. I can't figure it out so I need some help regarding it. I've added my code in codepen. The link is here.
My donut should look like this.
Sample code is:
svg.selectAll(".dataText")
.data(data_ready)
.enter()
.each(function (d) {
var centroid = arc.centroid(d);
d3.select(this)
.append('rect')
.attr("class", "dataBG_" + d.data.value.label)
.attr('x', (centroid[0]) - 28)
.attr('y', (centroid[1]) - 5)
.attr('rx', '10px')
.attr('ry', '10px')
.attr("width", 50)
.attr("height", 20)
.style('fill', d.data.value.color)
.style("opacity", 1.0);
d3.select(this)
.append('text')
.attr("class", "dataText_" + d.data.value.label)
.style('fill', 'white')
.style("font-size", "11px")
.attr("dx", (centroid[0]) - 7)
.attr("dy", centroid[1] + 7)
.text(Math.round((d.data.value.value)) + "%");
});
Thanks in advance.
The difference between the "bad" state on codepen and the desired state is that in the one you don't like, you take the centroid and then you center your text on it. The centroid of a thick arc is the midpoint of the arc that runs from the midpoint of one line-segment cap to the other. This is roughly "center of mass" of the shape if it had some finite thickness and were a physical object. I don't think it's what you want. What you want is the midpoint of the outer arc. There's no function to generate it, but it's easy enough to calculate. Also, I think you want to justify your text differently for arcs whose text-anchor point is on the left hand of the chart from those on the right half. I'm going copy your code and modify it, with comments explaining.
// for some reason I couldn't get Math.Pi to work in d3.js, so
// I'm just going to calculate it once here in the one-shot setup
var piValue = Math.acos(-1);
// also, I'm noting the inner radius here and calculating the
// the outer radius (this is similar to what you do in codepen.)
var innerRadius = 40
var thickness = 30
var outerRadius = innerRadius + thickness
svg.selectAll(".dataText")
.data(data_ready)
.enter()
.each(function (d) {
// I'm renaming "centroid" to "anchor - just a
// point that relates to where you want to put
// the label, regardless of what it means geometrically.
// no more call to arc.centroid
// var centroid = arc.centroid(d);
// calculate the angle halfway between startAngle and
// endAngle. We can just average them because the convention
// seems to be that angles always increase, even if you
// if you pass the 2*pi/0 angle, and that endAngle
// is always greater than startAngle. I subtract piValue
// before dividing by 2 because in "real" trigonometry, the
// convention is that a ray that points in the 0 valued
// angles are measured against the positive x-axis, which
// is angle 0. In D3.pie conventions, the 0-angle points upward
// along the y-axis. Subtracting pi/2 to all angles before
// doing any trigonometry fixes that, because x and y
// are handled normally.
var bisectAngle = (d.startAngle + d.endAngle - piValue) / 2.0
var anchor = [ outerRadius * Math.cos(bisectAngle), outerRadius * Math.sin(bisectAngle) ];
d3.select(this)
.append('rect')
.attr("class", "dataBG_" + d.data.value.label)
// now if you stopped and didn't change anything more, you'd
// have something kind of close to what you want, but to get
// it closer, you want the labels to "swing out" from the
// from the circle - to the left on the left half of the
// the chart and to the right on the right half. So, I'm
// replacing your code with fixed offsets to code that is
// sensitive to which side we're on. You probably also want
// to replace the constants with something related to the
// the dynamic size of the label background, but I leave
// that as an "exercise for the reader".
// .attr('x', anchor[0] - 28)
// .attr('y', anchor[1] - 5)
.attr('x', anchor[0] < 0 ? anchor[0] - 48 : anchor[0] - 2)
.attr('y', anchor[1] - 10
.attr('rx', '10px')
.attr('ry', '10px')
.attr("width", 50)
.attr("height", 20)
.style('fill', d.data.value.color)
.style("opacity", 1.0);
d3.select(this)
.append('text')
.attr("class", "dataText_" + d.data.value.label)
.style('fill', 'white')
.style("font-size", "11px")
// changing the text centering code to match the box
// box-centering code above. Again, rather than constants,
// you're probably going to want something a that
// that adjusts to the size of the background box
// .attr("dx", anchor[0] - 7)
// .attr("dy", anchor[1] + 7)
.attr("dx", anchor[0] < 0 ? anchor[0] - 28 : anchor[0] + 14)
.attr("dy", anchor[1] + 4)
.text(Math.round((d.data.value.value)) + "%");
});
I tested. this code on your codepen example. I apologize if I affected your example for everyone - I'm not familiar with codepen and I don't know the collaboration rules. This is all just meant by way of suggestion, it can be made a lot more efficient with a few tweaks, but I wanted to keep it parallel to make it clear what I was changing and why. Hope this gives you some good ideas.

How can i bring gridlines to the front?

In d3 graphs, how can i bring gridlines to the front or back of the bars. Which parameter is responsible for the same?
Sample working fiddle
The code for ticks is:-
var yAxisGrid = yAxis.ticks(numberOfTicks)
.tickSize(w, 0)
.tickFormat("")
.orient("right");
There is nothing similar to the z index (http://www.w3schools.com/cssref/pr_pos_z-index.asp) in SVG.
So, your question:
Which parameter is responsible for the same?
Has the answer: none.
That being said, this is the rule: who's painted later remains on top (just like a real painter using ink in a canvas).
So, just move the code for the circles to be before the code for the gridline.
//create the circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
/)...
//draw axes here
svg.append("g")
.attr("class", "axis") //assign "axis" class
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
//...
This is your updated fiddle (I made the circles larger): http://jsfiddle.net/e4L7sn37/

How to limit the text of polygons in Voronoi diagram with D3.js?

I've see the Example of D3.js-Voronoi Tessellation.But I want to put some text in each of polygons instead of a circle,Here is my js code:
var width = 600, height = 400;
var vertices = d3.range(20).map(function(d){
return [Math.random() * width, Math.random() * height]
});
var voronoi = d3.geom.voronoi();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
path = svg.append("g").selectAll("path");
svg.selectAll("info")
.data(vertices.slice(1))
.enter().append("text")
.attr("transform", function(d) {
return "translate(" + d + ")";
})
.text("someText")
.attr("shape-rendering","crispEdges")
.style("text-anchor","middle");
redraw();
function redraw(){
path = path
.data(voronoi(vertices), polygon);
path.exit().remove();
path.enter().append("path")
.attr("class", function(d, i) {return "q" + (i % 9) + "-9";})
.attr("d", polygon);
path.order();
}
function polygon(d){
return "M" + d.join("L") + "Z";
}
I have a JSFiddle for that basic example here:
my voronoi code
now, I want each of the polygons' text in the center of the polygon, and don't cross with the polygon's border. If the polygon have not enough space to contain the all text, just contain the first part of it!
Let me know if there is anything I can do to solve this issue, thank you!
PS:I'm so sorry to my English, yes, it's so poor! :)
Have a look at this example http://bl.ocks.org/mbostock/6909318 , you probably want to place the text at the polygon centroid and not the seed (point) used to determine the voronoi tessellation.
That should fix the majority of your layout issues.
Automatically scaling the text to fit is a little bit harder, if you are willing to scale and rotate the text you can use a technique similar to the following to determine the length of the line at that point:
https://mathoverflow.net/questions/116418/find-longest-segment-through-centroid-of-2d-convex-polygon
Then you need to determine the angle of the line. I have a plugin that should help with that:
http://bl.ocks.org/stephen101/7640188/3ffe0c5dbb040f785b91687640a893bae07e36c3
Lastly you need to scale and rotate the text to fit. To determine the width of the text use getBBox() on the text element:
var text = svg.append("svg:text")
.attr("x", 480)
.attr("y", 250)
.attr("dy", ".35em")
.attr("text-anchor", "middle")
.style("font", "300 128px Helvetica Neue")
.text("Hello, getBBox!");
var bbox = text.node().getBBox();
Then you use the angle you calculated earlier to scale and rotate your text:
text.attr("transform", "rotate(40) scale(7)")
I would love to give a complete example but this is quite a bit of work to get it right.
There are other options to achieve the same effect but none of them are simple (ie you could anneal the layout similar to the way d3 does the Sankey layout)

Can d3 build shapes within a shape?

I have a newby question. Can D3 draw this:
http://www.nytimes.com/interactive/2008/05/03/business/20080403_SPENDING_GRAPHIC.html?_r=0
using the voronoi function within d3? What i am thinking is a svg that behaves like a and binds the voronoi found here http://bl.ocks.org/mbostock/4060366 to a circle. NY Times has accomplished the above visualization using flash.
Any ideas?
I have tried creating a large circle and embeding the smaller circles, but the voronoi does not show up and the points are not confined to the outer circle.
Code generated:
<svg class="PiYG" width="560" height="570">
<circle cx="270" cy="300" r="260" style="stroke: rgb(0, 0, 0);">
<g>
My js code looks something like this:
var width = 560, height = 570;
var svg = d3.select("#VD1").append("svg")
.attr("width", width)
.attr("height", height)
.attr("class", "PiYG");
var path = svg.append("circle")
.attr("cx", 270)
.attr("cy", 300)
.attr("r", 260)
.style("stroke", "#000")
.append("g")
.selectAll("path");
var vertices = d3.range(count).map(function(d) {
return [Math.random() * width, Math.random() * height];
});
var voronoi = d3.geom.voronoi()
.clipExtent([[0, 0], [width, height]]);
svg.selectAll("circle")
.data(vertices.slice(2))
.enter().append("circle")
.attr("transform", function(d) { return "translate(" + d + ")"; })
.attr("r", 2);
Thanks so much!
Not really, but not due to any shortcomings of d3, but rather because that's not what a Voronoi function does. A Voronoi function builds lines based on which regions of a graph are closest to a given point. It is not a way of proportionately dividing a circle into smaller segments, with size corresponding to data.
That being said, it is definitely possible to create a circular Voronoi diagram. To do so, you'll have to make a few changes to the example Voronoi diagram code.
First you'll have to make sure that all of the points fit in a circle. In your example, point location is given by d, here:
.attr("transform", function(d) { return "translate(" + d + ")"; })
Either d (your dataset) needs to fit in the circle, or you need to make some transformation of it. For data normalized to (-1,1) in both dimensions, the function
.attr("transform", function(d) { return "translate([" +
d[0]*Math.sqrt(1 - Math.pow(d[1],2)/2)
+ "," +
d[1]*Math.sqrt(1 - Math.pow(d[0],2)/2)
+ "])"; })
will do so. Here, we've created a new array that will be bounded by a circle from the array originally in d.
Next, you would need to clip your Voronoi diagram to be contained within a circle. The fun part is, there isn't a built in 'circle' geometric object to clip with, so you'll need to get creative! Either build a custom way to do this, or let the Voronoi extend beyond your circle and build an SVG to cover it up. Either should work.

Resources