d3.js: Generating pie charts over map, unexpected NaN - d3.js

The data
I've got a .csv which has data of the following format:
city, industry, X, Non-X, latitude, longitude, Total
The goal
I've made a map of the country, and have created some g elements over each capital (using the latitude and longitude data). Within each g element, I'd like to create a pie chart, showing the proportions of X and Non-X. I'd like the radius of each pie to be a function of Total.
The code
Skipping over creating the map, etc, here's my function to create the pie charts (scroll down; a bit long):
function drawPies(csv) {
var radius = d3.scale.sqrt()
.domain([1, d3.max(csv, function(d) {
return d.Total;
})])
.range([2, 25]);
var color = d3.scale.ordinal();
var arc = d3.svg.arc();
var pie = d3.layout.pie();
var circleLayer = d3.select("svg")
.append("g")
.attr("class", "circleLayer");
var pies = circleLayer.selectAll("g")
.data(csv)
.enter()
.append("g")
.attr("id", function(d) { return d.city; })
.attr("class", "capitals")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.attr("transform", function(d) {
return "translate(" + projection([d.lon, d.lat])[0] + "," + projection([d.lon, d.lat])[1] + ")";
});
arc.outerRadius(function (d) { return radius(d.Total); })
.innerRadius(0);
pies.append("text")
.text("Hi");
pies.append("path")
.attr("d", arc) //this is not working...
.style("fill", function(d) { return color(d['X']); });
}
pies.append("text") works fine; the text elements appear at the right lat and long. Something's wrong with my arc variable; and I think it must be to do with how I've defined .outerRadius(). The path elements are created, but I'm getting NaN (and, hence, no pies). Here's what the HTML looks like from an example g element for each pie chart:
<path d="MNaN,NaNA4.859251238796309,4.859251238796309 0 1,1 NaN,NaNL0,0Z">
I suspect it's something with how I'm binding the data, and how I'm passing the data into arc.outerRadius(). I've tried re-binding the data to each pies element; I've also tried binding .data(d3.layout.pie()) (following Mike Bostock's example here), but that just doesn't generate d altogether.
What I'd like to do, is have d['X'] and d['Non-X'] be used to generate each pie's arc.outerRadius() (I think) - any guidance would be much appreciated!

You need a nested selection:
var arc = d3.svg.arc().innerRadius(0).outerRadius(20)
pie = d3.layout.pie()
.value(function(d){ return d });
var pies = svg.selectAll('.pie') //<-- this is each row
.data(data)
.enter()
.append('g')
.attr('class', 'pie')
...
pies.selectAll('.slice')
.data(function(d){
return pie([d.X, d.NonX]); //<-- this is each slice
})
.enter()
.append('path')
.attr('class', 'slice')
.attr('d', arc);
Working code:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
</head>
<body>
<script>
var data = [{
X: Math.random(),
NonX: Math.random(),
Total: Math.random() * 10
},{
X: Math.random(),
NonX: Math.random(),
Total: Math.random() * 10
},{
X: Math.random(),
NonX: Math.random(),
Total: Math.random() * 10
},{
X: Math.random(),
NonX: Math.random(),
Total: Math.random() * 10
}];
data.forEach(function(d){
d.pieVals = [d.X, d.NonX];
})
var svg = d3.select('body')
.append('svg')
.attr('width', 500)
.attr('height', 500);
var arc = d3.svg.arc().innerRadius(0).outerRadius(20)
pie = d3.layout.pie()
.value(function(d){ return d }),
color = d3.scale.category10();
var pies = svg.selectAll('.pie')
.data(data)
.enter()
.append('g')
.attr('class', 'pie')
.attr('transform',function(d,i){
return 'translate(' + 20 + ',' + (i + 1) * 40 + ')';
})
pies.selectAll('.slice')
.data(function(d){
return pie([d.X, d.NonX]);
})
.enter()
.append('path')
.attr('d', arc)
.style('fill', function(d,i){
return color(i);
});
</script>
</body>
</html>

Related

D3 path.transition is not a function

So I have a piechart that all transitions will not work on with the message that they're not a function. Which is true when I dig in the console. The window.d3 har a transition function, but not d3.selectAll('path').transition
I'm a bit of a loss as to why this does not work. Obviously my selection to do the transition is wrong, but how?
(function(d3) {
'use strict';
var tooltip = d3.select('body')
.append('div')
.attr('class', 'pie-tooltip')
.style("opacity", 0);
/**
* Width and height has to be the same for a circle, the variable is in pixels.
*/
var width = 350;
var height = 350;
var radius = Math.min(width, height) / 2;
/**
* D3 allows colours to be defined as a range, beneath is input the ranges in same order as our data set above. /Nicklas
*/
var color = d3.scaleOrdinal()
.range(['#ff875e', '#f6bc58', '#eae860', '#85d280']);
var svg = d3.select('#piechart')
.append('svg')
.attr('width', width+20)
.attr('height', height+20)
.append('g')
.attr('transform', 'translate(' + ((width+20) / 2) +
',' + ((height+20) / 2) + ')');
var arc = d3.arc()
.innerRadius(0)
.outerRadius(radius);
/**
* bArc = biggerArc, this is the arc with a bigger outerRadius thats used when a user mouseovers.
*/
var bArc = d3.arc()
.innerRadius(0)
.outerRadius(radius*1.05);
var pie = d3.pie()
.value(function(d){
return d.value;
})
.sort(null);
var path = svg.selectAll('path')
.data(pie(data))
.enter()
.append('path')
.attr('d', arc)
.attr('fill', function(d) {
return color(d.data.color);
});
path.transition()
.duration(600)
.attrTween("d", makePieAnimation);
path.on("mouseover", function(d){
d3.select(this)
.attr("width", width+10)
.attr("height", height+10);
tooltip.transition()
.duration(200)
.style("opacity", .9)
.style("display", null)
.text(d.data.label + ": " + d.data.value);
d3.select(this).transition()
.duration(300)
.style('fill', d.data.highlight).attr("d", bArc);
});
path.on("mousemove", function(){
tooltip.style("top", (event.pageY-10)+"px")
.style("left",(event.pageX+10)+"px");
});
path.on("mouseout", function(d){
d3.select(this).style('fill', d.data.color);
tooltip.transition()
.duration(300)
.style("opacity", 0);
d3.select(this).transition()
.duration(300)
.attr("d", arc);
});
/**
* makePieAnimation() animates the creation of the pie, setting startangles to 0, interpolating to full circle on creation in path.transition. D3 magic.
* b is an array of arc objects.
*/
function makePieAnimation(b) {
b.innerRadius = 0;
var angles = d3.interpolate({startAngle: 0, endAngle: 0}, b);
return function(t) {
return arc(angles(t));
};
}
})(window.d3);
$.each(data, function (index, value) {
$('#legend').append('<span class="label label-legend" style="background-color: ' + value['color'] + '">' + value['label'] + ': ' + value['value'] + '</span>');
});
EDIT:
After digging around Ive found that the d3 file used by typo3 is manually edited: https://forge.typo3.org/issues/83741
I cannot see how this impacts this issue, but it does. When using a CDN with d3 v4.12.2 the error disappears.

Using Math.sqrt() instead of d3.scaleSqrt() for sizing circles on map?

I am building a map where circles appended to cities on a US map are sized based upon a value in the CSV (guns column or d.guns in the JavaScript).
I was able to get the circles to resize using Math.sqrt() while appending the circles, but I do not think this is the right way to do it (or is it fine?):
.attr('r', function(d) {
return Math.sqrt(d.guns * 0.0010);
})
I attempted to use the d3.scaleSqrt() and d3.extent to resize the circles based upon the values, but I was thrown these errors in the console:
Here is the code when I attempted using d3.scaleSqrt:
<head>
<script>
function draw(geo_data) {
'use strict';
var margin = 75,
width = 1920 - margin,
height = 1080 - margin;
var svg = d3.select('body')
.append('svg')
.attr('width', width + margin)
.attr('height', height + margin)
.append('g')
.attr('class', 'map');
var projection = d3.geoAlbersUsa();
var path = d3.geoPath().projection(projection);
var map = svg.selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', 'rgba(253, 227, 167, 0.8)')
.style('stroke', 'black')
.style('stroke-width', 0.4);
d3.csv('top100cities.csv', function(error, data) {
// Converts strings to integers.
data.forEach(function(d) {
return d.guns = +d.guns;
})
var guns_extent = d3.extent(function(d) {
return d.guns;
});
var radius = d3.scaleSqrt()
.domain(guns_extent)
.range([0, 12]);
svg.append('g')
.attr('class', 'bubble')
.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', function(d) {
return projection([d.lon, d.lat])[0];
})
.attr('cy', function(d) {
return projection([d.lon, d.lat])[1];
})
.attr('r', function(d) {
return radius(d.guns);
})
.style('fill', 'rgba(103, 65, 114, 0.5)');
});
};
</script>
<body>
<script>
d3.json('us_states.json', draw);
</script>
</body>
Although Xavier Guihot's answer is technically correct and proposes a working solution it slightly deviates from the D3 track. The error in your code was caused by not providing all parameters to d3.extent(); you simply forgot to pass in the array, namely data, from which to determine the extent (emphasis mine):
# d3.extent(array[, accessor]) <>
Returns the minimum and maximum value in the given array using natural order. Providing both, the array as well as the accessor, your code would look like this:
var guns_extent = d3.extent(data, function(d) { // Pass in data as first parameter
return d.guns;
});
Below is the working demo:
function draw(geo_data) {
'use strict';
var margin = 75,
width = 1920 - margin,
height = 1080 - margin;
var svg = d3.select('body')
.append('svg')
.attr('width', width + margin)
.attr('height', height + margin)
.append('g')
.attr('class', 'map');
var projection = d3.geoAlbersUsa();
var path = d3.geoPath().projection(projection);
var map = svg.selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', 'rgba(253, 227, 167, 0.8)')
.style('stroke', 'black')
.style('stroke-width', 0.4);
d3.csv("https://raw.githubusercontent.com/dieterholger/US-Gun-Manufacturing-Interactive/master/top100cities.csv", function(error, data) {
// Converts strings to integers.
data.forEach(function(d) {
return d.guns = +d.guns;
})
var guns_extent = d3.extent(data, function(d) { // Pass in data
return d.guns;
});
var radius = d3.scaleSqrt()
.domain(guns_extent)
.range([0, 12]);
svg.append('g')
.attr('class', 'bubble')
.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', function(d) {
return projection([d.lon, d.lat])[0];
})
.attr('cy', function(d) {
return projection([d.lon, d.lat])[1];
})
.attr('r', function(d) {
return radius(d.guns);
})
.style('fill', 'rgba(103, 65, 114, 0.5)');
});
};
d3.json('https://raw.githubusercontent.com/dieterholger/US-Gun-Manufacturing-Interactive/master/us_states.json', draw);
<script src="https://d3js.org/d3.v4.js"></script>
The error, I think, was in the retrieval of the max number of guns.
Here is the diff:
let guns = data.map( function(d) { return d.guns });
console.log(Math.max(...guns));
var radius = d3.scaleSqrt().domain([0, Math.max(...guns)]).range([0, 25]);
Here is the modified demo:
<head>
<script>
function draw(geo_data) {
'use strict';
var margin = 75,
width = 1920 - margin,
height = 1080 - margin;
var svg = d3.select('body')
.append('svg')
.attr('width', width + margin)
.attr('height', height + margin)
.append('g')
.attr('class', 'map');
var projection = d3.geoAlbersUsa();
var path = d3.geoPath().projection(projection);
var map = svg.selectAll('path')
.data(geo_data.features)
.enter()
.append('path')
.attr('d', path)
.style('fill', 'rgba(253, 227, 167, 0.8)')
.style('stroke', 'black')
.style('stroke-width', 0.4);
d3.csv('https://raw.githubusercontent.com/dieterholger/US-Gun-Manufacturing-Interactive/master/top100cities.csv', function(error, data) {
let guns = data.map( function(d) { return d.guns });
console.log(Math.max(...guns));
var radius = d3.scaleSqrt().domain([0, Math.max(...guns)]).range([0, 25]);
svg.append('g')
.attr('class', 'bubble')
.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr('cx', function(d) {
return projection([d.lon, d.lat])[0];
})
.attr('cy', function(d) {
return projection([d.lon, d.lat])[1];
})
.attr('r', function(d) {
return radius(d.guns);
})
.style('fill', 'rgba(103, 65, 114, 0.5)');
});
};
</script>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>
d3.json('https://raw.githubusercontent.com/dieterholger/US-Gun-Manufacturing-Interactive/master/us_states.json', draw);
</script>
</body>
Setting up the formula for the area of a circle could work.
The formula is A=πr2, where A equals the area of the circle, r equals the radius.
if A = d.guns
We know Area and π
then:
r = √(d.guns/π)
.attr("r", function (d) {return Math.sqrt(d.guns/3.1416);})
https://www.wikihow.com/Calculate-the-Radius-of-a-Circle#/Image:Calculate-the-Radius-of-a-Circle-Step-10-Version-4.jpg

Changing the angle of a D3 doughnut chart to 180

I need to make my doughnut chart a horizontal graph like in this image >
this is the code that i use for other doughnut charts
var dataset = {
hddrives: [total - value, value],
};
var width = 460,
height = 300,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range([secondColor, mainColor]);
var pie = d3.layout.pie()
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 100)
.outerRadius(radius - 70);
var svg = d3.select(divName).append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
//Draw the Circle
svg.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", 65)
.attr("fill", "#F6FBF3");
var path = svg.selectAll("path")
.data(pie(dataset.hddrives))
.enter().append("path")
.attr("class", "arc")
.attr("fill", function (d, i) { return color(i); })
.attr("d", arc);
svg.append("text")
.attr("dy", "0em")
.style("text-anchor", "middle")
.attr("class", "inside")
.attr("font-size", "30px")
.text(function (d) { return value; });
svg.append("text")
.attr("dy", "1.5em")
.style("text-anchor", "middle")
.attr("class", "data")
.text(function (d) { return nomeGtin; });
}
I tried messing around with the attr values and the arc value, but without success, any ideas on how to approach this? Thanks
That isn't much of a donut chart, it's now a stacked bar chart (with a single bar). The pie and arc helpers aren't much help for that, they are concerned with calculating angles and circular things; you are now dealing with rectangles. d3.stack could help, but is probably overkill. Here's a quicky where I've just done the math (ie positioning) myself:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3#3.5.17" data-semver="3.5.17" src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
</head>
<body>
<script>
var width = 500,
height = 200,
w = 300,
h = 100;
var svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height);
var total = 0,
l = 0,
// fake random data
raw = d3.range(5).map(function(d){
var v = Math.random() * 10;
total += v;
return v;
}),
// calculate percents and cumulative position
data = raw.map(function(d){
var rv = {
v: d,
l: l,
p: d/total
}
l += rv.p;
return rv;
});
// scale and color
var s = d3.scale.linear()
.range([0, w])
.domain([0, 1]),
c = d3.scale.category20();
svg.selectAll('rect')
.data(data)
.enter()
.append('rect')
.attr('x', function(d){
return s(d.l) + width/2 - w/2; // place based on cumulative
})
.attr('width', function(d){
return s(d.p); // width from scale
})
.attr('height', h)
.attr('y', height/2 - h/2)
.style('fill', function(d,i){
return c(i);
})
.style('stroke', 'white')
.style('stroke-width', '2px');
</script>
</body>
</html>

D3.js pie chart to show the percentage of sales in each quarter

Using d3.js I want to make a chart showing each item as pie chart displaying the quarterly sale of tickets for different programs
here is the sample picture of chart I want to make,each segment in the chart represents ticket for a program and its percentage of sales in each quarter. Now using this link enter link description here
I have made a graph but which is not exactly the one I needed.Is there any charts available in d3.js to show a graph as I mentioned in the picture or we need to customize it to get a graph like that.
Is there any charts available in d3.js to show a graph as I mentioned
in the picture or we need to customize it to get a graph like that?
No there isn't a ready made solution, d3 as the comment on the question notes is a collection of methods for manipulating the DOM, this allows a great deal of flexibility in creating custom visualizations (users aren't as limited as with many ready-made solutions that only allow defined modifications). Consequently, yes, you can make a chart like that in d3 taking elements and ideas from both scatter plot and pie chart implementations with d3 to make your chart.
This answer shows one approach that could be used in creating such a graph. Ideally it can provide ideas in crafting your own visualization that meets your need.
First, you need a mechanism to make variable sized pie charts and to place them - arguably this is the hardest part (after that you just have a scatter plot that's easier to manipulate). This requires some thought as to data structure, I've used a structure such as:
var data = [
{x:100,y:100,radius:20,slices:[1,5]},
{x:150,y:180,radius:10,slices:[1,2,3,4]},
You could add other properties as needed, all that this does is specify an x and y coordinate for the pie chart center, a radius for the pie chart, and the values of the wedges for each pie chart.
With that, you can append a group element (g) to your svg, one for each pie chart (or item in the data array) using a standard enter cycle in d3, positioning the groups as we go:
var pies = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.property("radius",function(d) { return d.radius; })
.attr("transform",function(d) { return "translate("+d.x+","+d.y+")"; });
Because the data array used to append the wedges themselves will only include the wedge values, we can save the radius property as a property of the group and access that when appending the wedges:
pies.selectAll()
.data(function(d){ return pie(d.slices); })
.enter()
.append("path")
.attr("d",function(d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d) })
.attr("fill",function(d,i){
return color[i];
});
A basic example might look like this:
var data = [
{x:100,y:100,radius:20,slices:[1,5]},
{x:150,y:180,radius:10,slices:[1,2,3,4]},
{x:180,y:130,radius:30,slices:[1,2,3,4,5,6,7]},
{x:50,y:50,radius:15,slices:[5,3]},
{x:50,y:180,radius:40,slices:[6,3]}
]
var width = 500;
var height = 300;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var arc = d3.arc()
.innerRadius(0)
.outerRadius(50);
var pie = d3.pie()
.sort(null)
.value(function(d) { return d; });
var color = d3.schemeCategory10;
// Append a group for each pie chart, it will store the radius of each pie as a property
var pies = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.property("radius",function(d) { return d.radius; })
.attr("transform",function(d) { return "translate("+d.x+","+d.y+")"; });
// draw each pie wedge, using the slices property of the data bound to the parent g
pies.selectAll()
.data(function(d){ return pie(d.slices); })
.enter()
.append("path")
.attr("d",function(d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d) })
.attr("fill",function(d,i){
return color[i];
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
If you want to set each circle to have it's own color scheme, a few options might be available. If every pie has only two colors, you could assign a fill to the parent group and use the wedge increment to set transparency, creating lighter wedges such as in your image:
var data = [
{x:100,y:100,radius:20,slices:[1,5]},
{x:150,y:180,radius:10,slices:[1,2]},
{x:180,y:130,radius:30,slices:[1,7]},
{x:50,y:50,radius:15,slices:[5,3]}
]
var width = 500;
var height = 300;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var arc = d3.arc()
.innerRadius(0)
.outerRadius(50);
var pie = d3.pie()
.sort(null)
.value(function(d) { return d; });
var color = ["steelblue","orange","pink","crimson"]
// Append a group for each pie chart, it will store the radius of each pie as a property
var pies = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.property("radius",function(d) { return d.radius; })
.attr("fill",function(d,i) { return color[i] })
.attr("transform",function(d) { return "translate("+d.x+","+d.y+")"; });
// draw each pie wedge, using the slices property of the data bound to the parent g
pies.selectAll()
.data(function(d){ return pie(d.slices); })
.enter()
.append("path")
.attr("d",function(d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d) })
.attr("opacity",function(d,i){
return 1-i*0.2;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Other options are available, such as storing a local variable, storing the color as a property as we did for radius, or modifying our data structure to include a color for each wedge:
var data = [
{x:100,y:100,radius:20,
slices:[{value:1,color:"steelblue"},{value:5,color:"lightblue"} ]},
{x:150,y:180,radius:10,
slices:[{value:1,color:"crimson"},{value:2,color:"pink"}]},
{x:180,y:130,radius:30,
slices:[{value:1,color:"lawngreen"},{value:7,color:"darkgreen"}]}
]
var width = 500;
var height = 300;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var arc = d3.arc()
.innerRadius(0)
.outerRadius(50);
var pie = d3.pie()
.sort(null)
.value(function(d) { return d.value; });
// Append a group for each pie chart, it will store the radius of each pie as a property
var pies = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.property("radius",function(d) { return d.radius; })
.attr("transform",function(d) { return "translate("+d.x+","+d.y+")"; });
// draw each pie wedge, using the slices property of the data bound to the parent g
pies.selectAll()
.data(function(d){ return pie(d.slices); })
.enter()
.append("path")
.attr("d",function(d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d) })
// remember that d3.pie creates it's own data array, thus using d.data.property:
.attr("fill",function(d){ return d.data.color; })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Now we can adapt and implement characteristics of a scatter plot such as scales and axes. This would be the same for any other scatter plot essentially, we would scale the max and min (or a defined range) for the x and y scales, and add the axes. Altogether, that might look something like:
var data = [
{x:100,y:100,radius:10,slices:[1,5]},
{x:150,y:180,radius:10,slices:[1,2,3,4]},
{x:180,y:110,radius:30,slices:[1,2,3,4,5,6,7]},
{x:50,y:100,radius:15,slices:[5,3]},
{x:50,y:180,radius:40,slices:[6,3]}
]
var width = 500;
var height = 300;
var margin = {left:30,right:10,top:30,bottom:30}
var xScale = d3.scaleLinear()
.range([0,width-margin.left-margin.right])
.domain([0,d3.max(data,function(d) { return d.x + 20 }) ]);
var yScale = d3.scaleLinear()
.range([height-margin.top-margin.bottom,0])
.domain([0,d3.max(data,function(d) { return d.y + 20}) ]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g")
.attr("transform", "translate("+margin.left+","+margin.top+")")
var xAxis = d3.axisBottom(xScale);
g.append("g")
.attr("transform", "translate(0,"+(height-margin.bottom-margin.top)+")")
.call(xAxis);
var yAxis = d3.axisLeft(yScale);
g.append("g")
.call(yAxis);
var arc = d3.arc()
.innerRadius(0)
.outerRadius(50);
var pie = d3.pie()
.sort(null)
.value(function(d) { return d; });
var color = d3.schemeCategory10;
var pies = g.selectAll(null)
.data(data)
.enter()
.append("g")
.property("radius",function(d) { return d.radius; })
.attr("transform",function(d) { return "translate("+xScale(d.x)+","+yScale(d.y)+")"; });
pies.selectAll()
.data(function(d){ return pie(d.slices); })
.enter()
.append("path")
.attr("d",function(d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d) })
.attr("fill",function(d,i){
return color[i];
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Adding grid lines, legends, mouse over functionality, and other features should be relatively straightforward now - look at scatterplot examples with d3 to see how these and other features might be implemented, modifying a scatterplot of cirlces is about the same as modifying a scatterplot of pie charts.
From the sample provided by #Andrew Reid I have made it , the sample code for reference is posted here
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
</head>
<body>
<script>
var data = [
{x: 170, y: 160, radius: 20, slices: [3, 4]},
{x: 180, y: 40, radius: 30, slices: [ 6, 7]},
{x: 50, y: 80, radius: 20, slices: [5, 3]},
{x: 50, y: 180, radius: 40, slices: [6, 3]}
]
var width = 500;
var height = 300;
var margin = {left: 30, right: 10, top: 30, bottom: 30}
var xScale = d3.scaleLinear()
.range([0, width - margin.left - margin.right])
.domain([0, d3.max(data, function (d) {
return d.x + 20
})]);
var yScale = d3.scaleLinear()
.range([height - margin.top - margin.bottom, 0])
.domain([0, d3.max(data, function (d) {
return d.y + 20
})]);
xMid=d3.max(xScale.domain())/2;
yMid=d3.max(yScale.domain())/2;
console.log(xMid,yMid)
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
var xAxis = d3.axisBottom(xScale);
g.append("g")
.attr("transform", "translate(0," + (height - margin.bottom - margin.top) + ")")
.call(xAxis);
var yAxis = d3.axisLeft(yScale);
g.append("g")
.call(yAxis);
var lineX= g.append("line")
.attr("x1", 0)
.attr("x2", 500)
.attr("y1", yMid+20)
.attr("y2", yMid+20)
.attr("stroke-width", 1)
.attr("stroke", "black")
.attr("stroke-dasharray", "7,7");
var liney= g.append("line")
.attr("x1", xMid+130)
.attr("x2", xMid+130)
.attr("y1", -10)
.attr("y2", 245)
.attr("stroke-width", 1)
.attr("stroke", "black")
.attr("stroke-dasharray", "7,7");
var arc = d3.arc()
.innerRadius(0)
.outerRadius(50);
var pie = d3.pie()
.sort(null)
.value(function (d) {
return d;
});
var colors = d3.schemeCategory20;
var color = ["steelblue","orange","green","red"]
var pies = g.selectAll(null)
.data(data)
.enter()
.append("g")
.property("radius", function (d) {
return d.radius;
})
.attr("transform", function (d) {
return "translate(" + xScale(d.x) + "," + yScale(d.y) + ")";
})
.attr("fill", function (d, i) {
return color[i];
});
pies.selectAll()
.data(function (d) {
return pie(d.slices);
})
.enter()
.append("path")
.attr("d", function (d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d)
})
.attr("opacity",function(d,i){ return 1-i*0.7; });
</script>
</body>

Adding a legend to a pie chart in D3js

I'm trying to plot a pie chart with a legend inside of it. And I got into troubles to get it plotted, since I get the errors abound undefined variables. I managed to draw the chart itself and the half of the legend, but not in the right colors, what should match the pie chart.
function drawPieChart(d3div, chart_data) {
// chart_data.data is a list of data elements.
// each should contain fields: val, col, name
d3div.html(""); // clear the div
var title = getopt(chart_data, 'title', '');
// desired width and height of chart
var w = getopt(chart_data, 'width', 300);
var h = getopt(chart_data, 'height', 300);
var pad = getopt(chart_data, 'pad', 50);
var textmargin = getopt(chart_data, 'textmargin', 20);
var r = Math.min(w, h) / 2 - pad; // radius of pie chart
var div = d3div.append('div');
if(title !== '') {
div.append('p').attr('class', 'pietitle').text(title);
}
var arc = d3.svg.arc()
.outerRadius(r)
.cornerRadius(20)
.innerRadius(150);
var arcLarge = d3.svg.arc()
.innerRadius(150)
.cornerRadius(20)
.outerRadius(r + 50);
var toggleArc = function(p){
p.state = !p.state;
var dest = p.state ? arcLarge : arc;
d3.select(this).select("path").transition()
.duration(160)
.attr("d", dest);};
var pie = d3.layout.pie()
.padAngle(.03)
.sort(null)
.value(function(d) { return d.val; });
var svg = d3.select("#piechart").append("svg")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
var g = svg.selectAll(".arc")
.data(pie(chart_data.data))
.enter().append("g")
.attr("class", "arc")
.attr("stroke", "#999")
.attr("id",function(d){return d.data;})
.on("mouseover",toggleArc)
.on("mouseout",toggleArc);
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return d.data.col; });
var color = d3.scale.category20b();
var legendRectSize = 18;
var legendSpacing = 4;
// FROM here the code is not produced the desired result
var legend = svg.selectAll('.legend')
.data(chart_data.data)
.enter()
.append('g')
.attr('class', 'legend')
.attr("id",function(d){return d.data;})
.attr('transform', function(d, i) {
var height = legendRectSize + legendSpacing;
var offset = height * chart_data.data.length / 2;
var horz = -2 * legendRectSize;
var vert = i * height - offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.data(chart_data.data)
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style("fill", function(d) { return d.data.col; });
legend.append("text")
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.text(function(d) { return d.data.name; });
}
The code actually works fine untill the line var legend = svg.selectAll('.legend')
Then i start to define the legend, but D3 complains about undefined d.data every time i try to access d.data below the line I written above(also in the last line of the code).
I don't understand where i got on the wrong way.
If instead of defining the whole non working part(var legend...) i write this code:
g.append("text")
.attr("stroke", "none")
.attr("fill", function(d) { return d.data.col; })
.text(function(d) { return d.data.name; });
I'm able to access the d.data.name.
Unfortunately wrong colors of the boxes and not description.
Thanks!

Resources