Grouped pie chart segments in d3js using .padAngle() - d3.js

How would I add padding between some groups of segments in a donut/pie chart using d3?
UPDATE
I am using the d3.svg.arc shape generator and .padAngle(paddingFunction) where paddingFunction is defined as:
var paddingFunction = function(d,i) { return i%1==0 ? 0.1 : 0 };
This image is using the paddingFunction described above.
But if I change the padding function to this:
var paddingFunction = function(d,i) { return i%5==0 ? 0.1 : 0 };
I get this image:
Shouldn't the code return two groups of segments with a gap in-between?
Complete code:
// magic numbers
var t = 2 * Math.PI;
var arcHeight = 100;
var innerRadius = 50;
var hours = 10;
function postion(i,offset) {
offset = offset || 0;
return i*(t / hours) + offset*(t / hours)
}
var paddingFunction = function(d,i) { return i%1==0 ? 0.1 : 0 };
// arc generators
var arc = d3.svg.arc()
.innerRadius(function(d,i) { return innerRadius; })
.outerRadius(function(d,i) { return innerRadius + arcHeight; })
.startAngle(function(d, i){ return postion(d.hour);})
.endAngle(function(d, i){ return postion(d.hour,1);})
.padAngle(paddingFunction);
// data
var data = d3.range(0,hours);
data = data.map(function(d) {
return {
hour: d,
color: Math.random(),
}
});
// Scales
var colorScale = d3.scale.linear()
.domain([0, 1])
.interpolate(d3.interpolateRgb)
.range(["#ffffff", "#ffba19"]);
// render viz
var svg = d3.select("svg")
.append("g")
.attr("transform", "translate(320, 320)");
var select = svg.selectAll(".fore").data(data).enter();
select.append("path")
.classed("fore", true)
.style({
"stroke": "black",
"stroke-width": 1,
"fill": function(d) { return colorScale(d.color); }
})
.attr("d", arc);
UPDATE 2
Seems that I misunderstood how the .padAngle() method works. It adds padding on BOTH sides of a segment, I thought it added a gap between segments.
Is there an alternative method in d3 which adds a gap between segements (whilst recalculating the area of the segments so they all keep their proportions)?
Running code: http://blockbuilder.org/GitNoise/13f38aa8f4f2f06dd869

Related

d3.js click and apply zoom and pan to distribute points located inside a targeted division to the triggered subdivisions

Based on the response and example made by Andrew Reid, I produced this
pen code here points_in_subdivisons: on clicking on areas(Germany) on the screen
We want to offer a smooth animation from one close-up on the map to another
by using ZOOM OUT, PAN, ZOOM IN.
I have many divisions(countries) on Country level and then many sub-divisions(regions) inside each country .
Many points scattered across all divisions (countries) on my example mainly above Germany.
when I have to click on a targeted division(country) I must get only the points which correspond to this targeted division(country) that I have just clicked on
That means when the zoom of the subdivision(regions) is triggered(when the click is
made),
the code should take all the points that exist already only inside the
contours of the targeted divison(country) (that have just been clicked on) and points
enclosed-in should scatter in their corresponding subdivisions(regions).
To achieve this functionality and
based on Michael Rovinsky comment:
in the function manipulate(), the code is able to filter and extract only points that are embedded inside the targeted and triggered subdivisions(regions) and exclude markers those that are outside.
Inside function redraw() the enter exit pattern works well .
var svg = d3.select("svg");
width = 960;
height = 500;
var dataArray = [];
var mydataArray= [];
var projection = d3.geoMercator();
var baseProjection = d3.geoMercator();
var path = d3.geoPath().projection(projection);
var gBackground = svg.append("g"); // appended first
var gProvince = svg.append("g");
var gDataPoints = svg.append("g"); // appended second
var ttooltip = d3.select("body").append("div")
.attr("class", "ttooltip");
var csvPath="https://dl.dropbox.com/s/rb9trt4zy87ezi3/lonlat.csv?dl=0";
d3.csv(csvPath, function(error, data) {
if (error) throw error;
d3.json("https://gist.githubusercontent.com/rveciana/5919944/raw/2fef6be25d39ebeb3bead3933b2c9380497ddff4/nuts0.json", function(error, nuts0) {
if (error) throw error;
d3.json("https://gist.githubusercontent.com/rveciana/5919944/raw/2fef6be25d39ebeb3bead3933b2c9380497ddff4/nuts2.json", function(error, nuts2) {
if (error) throw error;
// convert topojson back to geojson
var countries = topojson.feature(nuts0, nuts0.objects.nuts0);
var regions = topojson.feature(nuts2, nuts2.objects.nuts2);
baseProjection.fitSize([width,height],regions);
projection.fitSize([width,height],regions);
var color = d3.scaleLinear().range(["steelblue","darkblue"]).domain([0,countries.features.length]);
var regionColor = d3.scaleLinear().range(["orange","red"]);
baseProjection.fitSize([width,height],countries);
projection.fitSize([width,height],countries);
var featureCollectionCountries = { "type":"FeatureCollection", "features": countries.features };
gBackground
.attr("class", "country")
.selectAll("path")
.data(countries.features)
.enter()
.append("path")
.attr("fill",function(d,i) { return color(i); })
.attr("opacity",0.7)
.attr("d", path)
.style("stroke","black")
.style("stroke-width",0)
.on("mouseover", function() {
d3.select(this)
.style("stroke-width",1)
.raise();
})
.on("mouseout", function(d,i) {
d3.select(this)
.style("stroke-width", 0 );
})
///// now zoom in when clicked and show subdivisions:
.on("click", function(d) {
// remove all other subdivisions:
d3.selectAll(".region")
.remove();
// add new features:
var features = regions.features.filter(function(feature) { return feature.properties.nuts_id.substring(0,2) == d.properties.nuts_id; });
regionColor.domain([0,features.length])
gProvince.selectAll(null)
.data(features)
.enter()
.append("path")
.attr("class","region")
.attr("fill", function(d,i) { return regionColor(i) })
.attr("d", path)
.style("stroke","black")
.style("stroke-width",0)
.on("click", function() {
zoom(projection,baseProjection);
d3.selectAll(".subdivision")
.remove();
})
.on("mouseover", function() {
d3.select(this)
.style("stroke-width",1)
.raise();
})
.on("mouseout", function(d,i) {
d3.select(this)
.style("stroke-width", 0 );
})
.raise()
// zoom to selected features:
var featureCollection = { "type":"FeatureCollection", "features": features }
manipulate(data,features);
redraw(featureCollection);
var endProjection = d3.geoMercator();
zoom(projection,endProjection.fitExtent([[50,50],[width-50,height-50]],featureCollection));
});
dataArray = data;
redraw(featureCollectionCountries);
});
});
});
function zoom(startProjection,endProjection,middleProjection) {
if(!middleProjection) {
d3.selectAll("path")
.transition()
.attrTween("d", function(d) {
var s = d3.interpolate(startProjection.scale(), endProjection.scale());
var x = d3.interpolate(startProjection.translate()[0], endProjection.translate()[0]);
var y = d3.interpolate(startProjection.translate()[1], endProjection.translate()[1]);
return function(t) {
projection
.scale(s(t))
.translate([x(t),y(t)])
path.projection(projection);
return path(d);
}
})
.duration(1000);
}
else {
d3.selectAll("path")
.transition()
.attrTween("d", function(d) {
var s1 = d3.interpolate(startProjection.scale(),middleProjection.scale());
var s2 = d3.interpolate(middleProjection.scale(),endProjection.scale());
var x = d3.interpolate(startProjection.translate()[0], endProjection.translate()[0]);
var y = d3.interpolate(startProjection.translate()[1], endProjection.translate()[1]);
function s(t) {
if (t < 0.5) return s1; return s2;
}
return function(t) {
projection
.translate([x(t),y(t)])
.scale(s(t)(t))
path.projection(projection);
return path(d);
}
})
.duration(1500);
}
}
function redraw(featureCollection,type) {
var mapG = d3.select('svg g.country');
d3.selectAll('circle')
.remove();
let grp = gDataPoints
.attr("class", "circle")
.selectAll("circle")
.data(dataArray,function(d) { return d.NOM; })
let grpEnter = grp.enter()
let group = grpEnter
group.append("circle")
.attr('fill', 'rgba(135, 5, 151, 125)')
.attr('stroke', 'black')
.each(function(d) {
if (d.lon === null ) return;
if (isNaN(d.lon ))return;
if (d.lat === null) return;
if (isNaN(d.lat ))return;
var pos = projection([parseFloat(d.lon), parseFloat(d.lat)]);
d.cx = pos[0];
d.cy = pos[1];
})
.attr("cx", function(d) {
return d.cx;
})
.attr("cy", function(d) {
return d.cy;
})
.attr("r",0.5)
.on("mouseover", showTooltip)
.on("mouseout", hideTooltip)
.on('mousemove', function(d) {
var xPos = d3.mouse(this)[0] - 15;
var yPos = d3.mouse(this)[1] - 55;
ttooltip.attr('transform', 'translate(' + xPos + ',' + yPos + ')');
ttooltip.style('opacity', 1);
var html = "<span>" + d.lon+ "</span>, <span>" + d.lat + "</span>";
ttooltip.html(html);
});
// Setup each circle with a transition, each transition working on transform attribute,
// and using the translateFn
group
.transition()
.duration(2000)
.attrTween("transform",function(d) {
return mapG._groups[0][0] != null ? recenter(featureCollection): null;
});
group.exit().remove() // exit > remove > g
}
function recenter(featureCollection) {
console.log('recentering');
};
function manipulate(data,features){
dataArray= [];
mydataArray =[];
data.forEach(function(ddd)
{
features.forEach(function(feature)
{
var polygoneOriginal =feature;
var points = [parseFloat(ddd.lon), parseFloat(ddd.lat)];
var isIn = d3.geoContains(polygoneOriginal, points);
if(isIn)
{
var element = ddd;
mydataArray.pushIfNotExist(element, function(e) {
return e.lat === element.lat && e.lon === element.lon ;
});
}
});
});
if(mydataArray.length>0)
{
var columnsArray= ["lon","lat"];
dataArray=mydataArray;
dataArray.columns = columnsArray;
}
}
function showTooltip(d) {
var html = "<span>" + d.lon+ "</span>, <span>" + d.lat + "</span>";
ttooltip.html(html);
ttooltip
.style("left", window.pageXOffset + d3.event.x + 12 + "px")
.style("top", window.pageYOffset + d3.event.y + 12 + "px")
.transition()
.style("opacity", 1);
return d3.select(this).attr('fill', 'rgba(103, 65, 114, 0.8)');
}
function hideTooltip() {
ttooltip
.transition()
.style("opacity", 0);
return d3.select(this).attr('fill', 'rgba(103, 65, 114, 0.5)');
}
// check if an element exists in array using a comparer function
// comparer : function(currentElement)
Array.prototype.inArray = function(comparer) {
for(var i=0; i < this.length; i++) {
if(comparer(this[i])) return true;
}
return false;
};
// adds an element to the array if it does not already exist using a comparer
// function
Array.prototype.pushIfNotExist = function(element, comparer) {
if (!this.inArray(comparer)) {
this.push(element);
}
};
My Question is the following : How to make the Zooming (for points circle) to work adequately:
right now, on a map upon click the x y points not scale.
They are rendered as circles in background and I would like them to move with the map.
That means How to apply the same animation zoom (when subdivisions are triggered by click on a division) in order to those points inside the targeted subdivision follow in transition and move with the map and we could see circles points clearly distributed adequately in each correct corresponding subdivisions?
update
Andrew Reid described here How To accomplish a smooth zoom using d3.js
so following his hints.
I added the following instructions in redraw() function
var mapG = d3.select('svg g.country');
group
.transition()
.duration(2000)
.attrTween("transform",function(d) {
return mapG._groups[0][0] != null ? recenter(): null;
});
AND then we should add the code to the The function that should actually do the moving recenter(featureCollection) function to
function recenter(featureCollection) {
// TO ADD CODE TO BE IMPLEMENTED HERE
};
Thank You very much for your cooperation,participation and help !
1- To generate first iteration click on Region equal country
//GENERATE FIRST MAP
dataArray = data;
redraw();
2- To generate counties for example on click on region, we should first set startprojection and endprojection in zoom function and then trigger redraw of circles
//zoom to selected provinces features:
var countiesFeatureCollection = { "type":"FeatureCollection", "features": countiesFeatures }
//manipulate counties And Redraw
manipulateCounties(data,countiesFeatures);
baseProjection.fitExtent([[50,50],[width-50,height-50]],countiesFeatureCollection);
projection.fitExtent([[50,50],[width-50,height-50]],countiesFeatureCollection);
redraw(countiesFeatureCollection,"counties");
if ( projection.translate().toString() === baseProjection.translate().toString() && projection.scale() === baseProjection.scale() )
{
zoom(baseProjection,projection.fitExtent([[50,50],[width-50,height-50]],countiesFeatureCollection));
}
else
{
var endProjection = d3.geoMercator();
zoom(projection,endProjection.fitExtent([[50,50],[width-50,height-50]],countiesFeatureCollection));
}
3-the same thing should be applied to communities
var endProjection = d3.geoMercator();
endProjection.fitExtent([[50,50],[width-50,height-50]],communesfeatureCollection);
projection.fitExtent([[50,50],[width-50,height-50]],communesfeatureCollection);
redraw(communesfeatureCollection,"communes");
if ( projection.translate().toString() === projectioncommune.translate().toString() && projection.scale() === projectioncommune.scale()){
zoom(projectioncommune,projection.fitExtent([[50,50],[width-50,height-50]],communesfeatureCollection));
}
else {
var endProjection = d3.geoMercator();
zoom(projection,endProjection.fitExtent([[50,50],[width-50,height-50]],communesfeatureCollection));
}
4- Then reinitialise to go to first step 1 by
// start rendering points again
baseProjection.fitSize([width,height],regions);
projection.fitSize([width,height],regions);
//GENERATE AGAIN THE FIRST MAP
dataArray = data;
redraw();
zoom(projection,baseProjection);
ATTACHED WORKING PEN

D3 Pie chart not updating correctly

I got the following D3 v4 pie chart, every time I try updating it the data doesn't update correctly. I have been reading around tried following some other example, but just can't seem to get it to work. Current update function looks like this:
function PieGenUpdater(data, colourRangeIn) {
var dataset = data;
var width = 400;
var height = 400;
var radius = Math.min(width, height) / 2;
var arc = d3.arc()
.innerRadius(radius/1.5)
.outerRadius(radius);
var pie = d3.pie()
.value(function(d) { return d.percent; })
.sort(null);
var svg = d3.select('#c-pie');
var path = svg.selectAll('path').data(pie(dataset));
path.enter()
.append("path")
.attr('fill', function(d, i) {
return d.data.color;
})
.attr("d", arc)
.each(function(d) {this._current = d;} );
path.transition()
.attrTween("d", arcTweenCoverage);
path.exit().remove();
// Store the displayed angles in _current.
// Then, interpolate from _current to the new angles.
// During the transition, _current is updated in-place by d3.interpolate.
function arcTweenCoverage(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
}
https://jsfiddle.net/mahsan/zup6kafk/
Any help is greatly appreciated.
Here is the 4 years too late answer...
In PieGenUpdater change
var path = svg.selectAll('path').data(pie(dataset));
to
var path = svg.select('g').selectAll('path').data(pie(dataset));
In your update function you were adding the additional paths from dataset 1 directly under element instead of the element

D3.js Donut Chart change node colors and add text label to middle

My goal is to create an animated donut chart that shows 75% - 90% accuracy rate. For this I've started with the code below, but I'd like to make a few tweaks:
I would like to customize the colors of each node output by the
chart (I've added the variable section_path_fill_colors). Currently the code
just chooses random colors I believe.
I would like to add a static text label in the middle of the donut
75% - 90% (I've added the variable static_label).
Currently the labels are attached to each node.
Can someone help me accomplish this?
UPDATE:
I was able to solve the coloring of nodes with:
var color = d3.scale.ordinal()
.domain(["one", "two", "three"])
.range(["#ffffff" , "#d1d2d4" , "#17afd1"]);
Now just need help setting the static label in the middle
JS:
var static_label = '75% - 90%';
var employees = [
{dept: '', count : 75},
{dept: '', count : 15},
{dept: '', count : 10}
];
var color = d3.scale.ordinal()
.domain(["one", "two", "three"])
.range(["#ffffff" , "#d1d2d4" , "#17afd1"]);
var maxWidth = 200;
var maxHeight = 200;
var outerRadius = 100;
var ringWidth = 20;
function checkEndAll(transition, callback) {
var n = 0;
transition
.each(function() { ++n; })
.each("end", function() {
if (!--n) callback.apply(this, arguments);
});
}
function drawAnimatedRingChart(config) {
var pie = d3.layout.pie().value(function (d) {
return d.count;
});
//var color = d3.scale.category10();
var arc = d3.svg.arc();
function tweenPie(finish) {
var start = {
startAngle: 0,
endAngle: 0
};
var i = d3.interpolate(start, finish);
return function(d) { return arc(i(d)); };
}
arc.outerRadius(config.outerRadius || outerRadius)
.innerRadius(config.innerRadius || innerRadius);
// Remove the previous ring
d3.select(config.el).selectAll('g').remove();
var svg = d3.select(config.el)
.attr({
width : maxWidth,
height: maxHeight
});
// Add the groups that will hold the arcs
var groups = svg.selectAll('g.arc')
.data(pie(config.data))
.enter()
.append('g')
.attr({
'class': 'arc',
'transform': 'translate(' + outerRadius + ', ' + outerRadius + ')'
});
// Create the actual slices of the pie
groups.append('path')
.attr({
'fill': function (d, i) {
return color(i);
}
})
.transition()
.duration(config.duration || 1000)
.attrTween('d', tweenPie)
.call(checkEndAll, function () {
// Finally append the title of the text to the node
groups.append('text')
.attr({
'text-anchor': 'middle',
'transform': function (d) {
return 'translate(' + arc.centroid(d) + ')';
}
})
.text(function (d) {
// Notice the usage of d.data to access the raw data item
return d.data.dept;
});
});
}
// Render the initial ring
drawAnimatedRingChart({
el: '.animated-ring svg',
outerRadius: outerRadius,
innerRadius: outerRadius - ringWidth,
data: employees
});
Just do this:
svg.append('text')
.attr({
x: outerRadius,
y: outerRadius,
'text-anchor': 'middle
})
.text(static_label);

Making different size of `radius` to create `arc` not working

I am creating pie chart using d3.js. I would like to create 3 pies with single svg element with animation.
This is working fine for me. But do creating different I am reducing the radius each time using a loop. But the radius not getting changed.
How to solve this?
my code (sample) :
var array1 = [
0,200
]
window.onload = function () {
var width = 660,
height = 200,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var arc = null;
var pie = d3.layout.pie()
.value(function(d) {
return d; })
.sort(null);
function tweenPie(finish) {
var start = {
startAngle: 0,
endAngle: 0
};
var i = d3.interpolate(start, finish);
return function(d) { return arc(i(d)); };
}
var svg1 = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
for( var i = 0; i < 3; i++) {
arc = d3.svg.arc()
.innerRadius(radius - (5*i)) //each time size differs
.outerRadius(radius - (6)*i); //each time size differs
svg1.append('g')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.datum(array1).selectAll("path")
.data(pie)
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.transition()
.duration(5000)
.attrTween('d', tweenPie)
}
}
Live Demo
There is a single arc variable that is being used in the tweenPie method and in the for loop. Each time through the for loop, the arc variable is set to a new value. The tweenPie method is called for each pie chart after the for loop exits. As a result, all the pie charts are using the same tweenPie method which is using the arc created in the last for loop.
For each pie chart, you need to create a separate tweenPie method with its own arc. For example...
var array1 = [ 0, 200 ]
window.onload = function () {
var width = 660,
height = 200,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var arc = null;
var pie = d3.layout.pie()
.value(function(d) {
return d; })
.sort(null);
function getTweenPie(arc) {
return function (finish) {
var start = {
startAngle: 0,
endAngle: 0
};
var i = d3.interpolate(start, finish);
return function(d) { return arc(i(d)); };
}
}
var svg1 = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
for( var i = 0; i < 3; i++) {
arc = d3.svg.arc()
.innerRadius(radius - (5*i)) //each time size differs
.outerRadius(radius - (6)*i); //each time size differs
svg1.append('g')
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
.datum(array1).selectAll("path")
.data(pie)
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.transition()
.duration(5000)
.attrTween('d', getTweenPie(arc))
}
}

D3.js Bar graph not displaying properly?

I have a bar graph in my program, but it doesn't seem to be displaying properly. All of the bars seem to be a lot bigger than they are supposed to be. Here's the relevant code:
//Bar Graph
var canvas = d3.select("#canvas");
canvas.width = 500;
canvas.height = 500;
var values = [1, 2, 3]
var colours = ['#FA0', '#0AF', '#AF0']
var data = []
var yOffset = 0
//create scale
yRange2 = d3.scale.linear().range([canvas.height - MARGINS.top,
MARGINS.bottom]).domain([0, 6]);
//Process the data
for (var i = 0; i < values.length; i++) {
var datum = {
value: yRange2(values[i]),
colour: colours[i],
x: 0,
y: yOffset
}
yOffset += datum.value;
data.push(datum)
}
//setup y
yAxis2 = d3.svg.axis()
.scale(yRange2)
.tickSize(5)
.orient("left")
.tickSubdivide(true);
canvas.append("svg:g")
.attr("class", "y axis")
.attr("transform", "translate(" + (MARGINS.left) + ",0)")
.call(yAxis2);
var bars = canvas.selectAll('rect').data(data)
bars
.enter()
.append('rect')
.attr({
width: 30,
x: 60,
y: function (d) {
return d.y
},
height: function (d) {
return d.value;
}
})
.style({
fill: function (d) {
return d.colour
}
})
//updates when slider changes
$("#myRange").change(function () {
slider = $("#myRange").val();
updateXs();
updateLineData();
displayVals();
d3.select(".myLine").transition()
.attr("d", lineFunc(lineData));
});
And here's the full code:
http://jsfiddle.net/tqj5maza/7/
To me, it looks like the bars are starting at the top for some reason, and then going downwards, hence the cutoff. The height for each seems too large, though.
You aren't setting the rects height property correctly. Generally this is height of the plotting area minus y position. The way you have your code structured its:
height: function (d) {
return canvas.height - MARGINS.top - d.value;
}
To fix the overlapping x value, you should set up an x d3.scale but a quick and dirty way would be:
x: function(d,i){
return (i + 1) * 60; //<-- move each bar over 60 pixels
}
Updated code here.

Resources