How can I create a multi layer pie chart with d3.js which looks like below
Every section doesn't have an inner subsection and when it has a subsection then it has darker color than the outer subsection as shown in the above image.
I tried searching for multilayer pie chart but what all I could do is this.
http://jsfiddle.net/ZpQ3x/
Here is corresponding javascript code
var dataset = {
final: [7000],
process: [1000, 1000, 1000, 7000],
initial: [10000],
};
var width = 660,
height = 500,
cwidth = 75;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.sort(null);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("class","wrapper")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")")
var gs = svg.selectAll("g.wrapper").data(d3.values(dataset)).enter()
.append("g")
.attr("id",function(d,i){
return Object.keys(dataset)[i];
});
var gsLabels = svg.selectAll("g.wrapper").data(d3.values(dataset)).enter()
.append("g")
.attr("id",function(d,i){
return "label_" + Object.keys(dataset)[i];
});
var count = 0;
var path = gs.selectAll("path")
.data(function(d) { return pie(d); })
.enter().append("path")
.attr("fill", function(d, i) { return color(i); })
.attr("d", function(d, i, j) {
d._tmp = d.endAngle;
d.endAngle = d.startAngle;
if(Object.keys(dataset)[j] === "final"){
d.arc = d3.svg.arc().innerRadius(cwidth*j).outerRadius(cwidth*(j+1));
}
else{
d.arc = d3.svg.arc().innerRadius(10+cwidth*j).outerRadius(cwidth*(j+1));
}
return d.arc(d);
})
.transition().delay(function(d, i, j) {
return i * 500;
}).duration(500)
.attrTween('d', function(d,x,y) {
var i = d3.interpolate(d.startAngle, d._tmp);
return function(t) {
d.endAngle = i(t);
return d.arc(d);
}
});
Thank you very much.
I have changed your dataset into a single JSON.
Just to ensure that mentioned above array x and x1 are related together i made data set like this.
data = [{
major: 100,//this is the X array first element
minor: 70,//this is the X1 array first element
grp: 1//here grp is for coloring the segment
}, {
major: 100,
minor: 30,
grp: 2
}, {
major: 100,
minor: 50,
grp: 3
}, {
major: 140,
minor: 70,
grp: 4
}, {
major: 80,
minor: 10,
grp: 5
}];
I have made two arc function.
var arcMajor = d3.svg.arc()
.outerRadius(function (d) {
return radius - 10;
})
.innerRadius(0);
//this for making the minor arc with variable radius as per scale
var arcMinor = d3.svg.arc()
.outerRadius(function (d) {
// scale for calculating the radius range([20, radius - 40])
return scale((d.data.major - d.data.minor));
})
This is the code which makes the path.
//this makes the major arc
g.append("path")
.attr("d", function (d) {
return arcMajor(d);
})
.style("fill", function (d) {
return d3.rgb(color(d.data.grp));
});
//this makes the minor arcs
g.append("path")
.attr("d", function (d) {
return arcMinor(d);
})
.style("fill", function (d) {
return d3.rgb(color(d.data.grp)).darker(2);//for making the inner path darker
});
Working code here with comments
Hope this helps!
Related
I'm trying to plot some cordinates on an image using d3 v4 following this Link.When i'm trying to pass my co-ordinates to the projection function it returns NAN for some of the data points. I got some help from here that javascript follows the following latitude and longitude convention but not sure how it exacty works.
This is the format of my data:
{coordinates: [60, 84],coordinates: [204, 92.4],coordinates: [117, 132.72]}
D3 code :
var el = d3.select('.js-map'),
// 150 DPI image
width = 300,
// 150 DPI image
height = 300;
var thisObj = this;
var projection = d3.geoMercator()
.scale(1)
.translate([0, 0])
console.log('projection', projection);
var path = d3.geoPath()
.projection(projection);
var map = el.append('svg')
.attr('width', width)
.attr('height', height);
map.append('image')
.attr('xlink:href', this.floorMaps[0])
.attr('width', width)
.attr('height', height);
this.floorSensorInfo.forEach((data, index) => {
var lonlat = projection(data.coordinates);
console.log('Longitude Latitude', lonlat);
I can see my data output like [2.0420352248333655, NaN]and not sure what happened exactly.
and moreover if someone can explain following the first link which i realy don't understand it would be really helpful
Exported bounds of raster image
rasterBounds = [[-122.7895, 45.4394], [-122.5015, 45.6039]]
Update:
#Andrew suggested to plot normal co-ordinates because latitude and longitude apply only to world maps. So i had pasted my below working code version now which is plotting the points on the image now.
var svg = d3.select("body")
.append("svg")
.attr("width",960)
.attr("height",500)
// image width and height in pixels, we don't want to skew this or scale this (then image units aren't straight pixels)
var imageWidth = 300;
var imageHeight = 168;
var color_hash = { 0 : ["apple", "green"],
1 : ["mango", "orange"],
2 : ["cherry", "red"]
}
function scale(coords) {
return [coords[0] * imageWidth / 100, coords[1] * imageHeight / 100];
}
svg.append("image")
.attr("width",imageWidth)
.attr("height",imageHeight)
.attr("x", 0) // could be non-zero, but we would have to shift each circle that many pixels.
.attr("y", 0)
.attr("xlink:href", this.floorMaps[0])
var data = this.floorSensorInfo
// var dataNest = d3.nest()
// .key(function (d) { return d['sensor_name']; })
// .entries(data)
data.forEach(function (d, i) {
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return (d.value)[0]; })
.attr("cy", function(d) { return (d.value)[1]; })
.attr("r", 5)
.style("fill", function(d) {
var color = color_hash[data.indexOf(d)][1]
return color;
})
svg.append('text')
.attr("x", 20+(i)*100) // space legend
.attr("y", imageHeight+20)
// style the legend
.style("stroke", function () { // Add the colours dynamically
return d['color'] = color_hash[data.indexOf(d)][1];
})
//.attr("dy", ".35em")
.text( d.sensor_name);
//.text("jjjjjjj")
})}
var svg = d3.select("body")
.append("svg")
.attr("width",960)
.attr("height",500)
// image width and height in pixels, we don't want to skew this or scale this (then image units aren't straight pixels)
var imageWidth = 300;
var imageHeight = 168;
var color_hash = { 0 : ["apple", "green"],
1 : ["mango", "orange"],
2 : ["cherry", "red"]
}
function scale(coords) {
return [coords[0] * imageWidth / 100, coords[1] * imageHeight / 100];
}
svg.append("image")
.attr("width",imageWidth)
.attr("height",imageHeight)
.attr("x", 0) // could be non-zero, but we would have to shift each circle that many pixels.
.attr("y", 0)
.attr("xlink:href", this.floorMaps[0])
var data = this.floorSensorInfo
// var dataNest = d3.nest()
// .key(function (d) { return d['sensor_name']; })
// .entries(data)
data.forEach(function (d, i) {
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d) { return (d.value)[0]; })
.attr("cy", function(d) { return (d.value)[1]; })
.attr("r", 5)
.style("fill", function(d) {
var color = color_hash[data.indexOf(d)][1]
return color;
})
svg.append('text')
.attr("x", 20+(i)*100) // space legend
.attr("y", imageHeight+20)
// style the legend
.style("stroke", function () { // Add the colours dynamically
return d['color'] = color_hash[data.indexOf(d)][1];
})
//.attr("dy", ".35em")
.text( d.sensor_name);
//.text("jjjjjjj")
})}
javascript d3.js
I trying to mimic an example from Mike Bostock Extending Arcs. My code is very similar to Mike's, but mines doesn't work like his.
Here below is the JavaScript code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Example</title>
<script src="static/d3.min.js"></script>
</head>
<body>
<script>
var Width = 500, Height = 400, innerRadius = 45, outerRadius = 100;
var colors = d3.scale.category10();
var svg = d3.select('body')
.append('svg')
.attr({ width: Width, height: Height });
var data = [40, 32, 35, 64, 83],
pieData = d3.layout.pie()(data)
var ArcGen = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.padAngle(0.02)
.startAngle(function(d){
return d.startAngle;
})
.endAngle(function(d){
return d.endAngle;
});
var group = svg.append('g')
.attr('transform', 'translate(' + [Width / 2.0, Height / 2.0] + ')');
var segment = group.selectAll('g')
.data(pieData)
.enter()
.append('g');
segment
.append('path')
.attr('d', ArcGen)
.attr('fill', function(d,i){ return colors(i); })
.on('mouseover', arcTween(outerRadius * 1.2, 0))
.on('mouseout', arcTween(outerRadius, 150));
function arcTween(oRadius, delay){
// closure function
return function(){
d3.select(this)
.transition()
.delay(delay)
.attrTween('d', function(d){
var i = d3.interpolate(d.outerRadius, oRadius);
return function(t){
d.outerRadius = i(t);
return ArcGen(d);
}
})
};
};
</script>
</body>
</html>
This routine is simple, no error no action either.
I have two questions:
If it's necessary to chain startAngle and endAngle attributes at every arc generator? I read some pros' code, such as Mike Bostock's, don't add this two attributes at the initial stage and their codes work fine when constructing path elements.
Where am I wrong? is anybody can give me more attrTween examples.
Thanks, everyone!
Your code has 2 problems, that you can easily find comparing it with Bostock's code.
First, this line:
var i = d3.interpolate(d.outerRadius, oRadius);
Uses the property outerRadius in the element's datum. But it has none. You can fix this with:
.each(function(d) { d.outerRadius = outerRadius; })
Second, your arc generator is setting the outer radius:
var ArcGen = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(outerRadius)
.padAngle(0.02)
.startAngle(function(d) {
return d.startAngle;
})
.endAngle(function(d) {
return d.endAngle;
});
Remove it:
var ArcGen = d3.svg.arc()
.innerRadius(innerRadius)
.padAngle(0.02)
.startAngle(function(d) {
return d.startAngle;
})
.endAngle(function(d) {
return d.endAngle;
});
Here is your working code with the 2 changes:
var Width = 500,
Height = 400,
innerRadius = 45,
outerRadius = 100;
var colors = d3.scale.category10();
var svg = d3.select('body')
.append('svg')
.attr({
width: Width,
height: Height
});
var data = [40, 32, 35, 64, 83],
pieData = d3.layout.pie()(data)
var ArcGen = d3.svg.arc()
.padAngle(0.02)
.innerRadius(innerRadius)
.startAngle(function(d) {
return d.startAngle;
})
.endAngle(function(d) {
return d.endAngle;
});
var group = svg.append('g')
.attr('transform', 'translate(' + [Width / 2.0, Height / 2.0] + ')');
var segment = group.selectAll('g')
.data(pieData)
.enter()
.append('g');
segment
.append('path')
.each(function(d) {
d.outerRadius = outerRadius;
})
.attr('d', ArcGen)
.attr('fill', function(d, i) {
return colors(i);
})
.on('mouseover', arcTween(outerRadius * 1.2, 0))
.on('mouseout', arcTween(outerRadius, 150));
function arcTween(oRadius, delay) {
// closure function
return function() {
d3.select(this)
.transition()
.delay(delay)
.attrTween('d', function(d) {
var i = d3.interpolate(d.outerRadius, oRadius);
return function(t) {
d.outerRadius = i(t);
return ArcGen(d);
}
})
};
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.js"></script>
I'm trying to replace the lines in this Sankey migration map with tapered arcs, such as are used here, but I'm having trouble even getting the tapered arc shape to appear within the same div as the map.
How do I replace L.polyline with tapered arcs from d3.svg.area?
Here are the code changes I've made to my Sankey map (codepen link above), without success:
d3.json('http://codepen.io/laissezpasser/pen/RaeKLQ.js', function(error, links) {
if (error) throw error;
var weightMin = d3.min(links, function(l) { return l.value }),
weightMax = d3.max(links, function(l) { return l.value });
var weightScale = d3.scale.linear()
.domain([weightMin, weightMax])
.range([0, 300]);
links.forEach(function (link) {
if (link.source != link.target) {
var path = [ link.source, link.target ].join('-');
var pathReverse = [ link.target, link.source ].join('-')
var linkStyle = {
color: (linkLayers[pathReverse]) ? 'violet' : 'orange',
weight: weightScale(link.value),
smoothFactor: 1,
opacity: .5,
fill: false,
class: path
};
var linkCenter = L.area([ atollLayers[link.source].center, atollLayers[link.target].center ]).getBounds().getCenter();
if (linkStyle.color == 'violet') {
var lineCenter = L.latLng(
(linkCenter.lat * .001) + linkCenter.lat,
(linkCenter.lng * .001) + linkCenter.lng
);
}
else {
var lineCenter = L.latLng(
linkCenter.lat - (linkCenter.lat * .001),
linkCenter.lng - (linkCenter.lng * .001)
);
}
var link = L.area([ atollLayers[link.source].center, lineCenter, atollLayers[link.target].center ], linkStyle);
var svg = d3.select("#map").select("svg"),
g = svg.append("g");
var area = d3.svg.area()
.x(function(d) { return d.x; })
.x0(function(d) { return d.x0; })
.y0(function(d) { return d.y0; })
.y1(function(d) { return d.y1; })
.interpolate("cardinal")
.tension(0);
var link = {
width: weightScale(link.value),
offset: link.value / 2 + Math.random(),
};
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
var path = svg.append("path")
.datum(toArc(link))
.attr("class", "area")
.attr("d", area);
svg.append(atollLayers)
.attr("x", link.source.center[0])
.attr("y", link.source.center[1]);
svg.append(atollLayers)
.attr("x", link.target.center[0])
.attr("y", link.target.center[1]);
I'm new to d3.js but I have made some practices already.
I have a pie chart which is devided into 19 pieces (picture1)based on this csv file (picture2). Each piece means a year and the area of that piece means its score. Picture 1&2
Now I want to build a parent-children relationship in the csv, like picture 3(each year will contains 5 continents). Picture3 The sum of five continents' scores equals to the score of that year.
And I want the pie to be change so that all pieces are cut into 5 layers from inner side to out side.
Part of my current code is here. Can anyone tell me how to make the hierarchy? If the structure in picture3 is not right, how should the structure be?
And do I need json? if so, how to change the data loading part for csv files into for json files?
var width = 650, height = 650, radius = Math.min(width, height) / 2, innerRadius=0;
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.width; });
var arc = d3.svg.arc()
.innerRadius(innerRadius)
.outerRadius(function (d) {
return (radius - innerRadius) * Math.sqrt(d.data.score / 2900.0) + innerRadius;
});
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
//data loading
d3.csv('./src/InCountry-v1.csv', function(error, data) {
data.forEach(function(d) {
d.id = d.year;
d.order = +d.order;
d.color = d.color;
d.weight = +d.weight;
d.score = +d.score;
d.width = +d.weight;
d.label = d.label;
});
var path = svg.selectAll(".solidArc")
.data(pie(data))
.enter().append("path")
.attr("fill", function(d) { return d.data.color})
.attr("class", "solidArc")
.attr("stroke", "gray")
.attr("d", arc)
.attr("opacity",0.5)
.on("mouseenter", function() {d3.select(this)
.style("fill", function(d) { return d.data.color})
.attr("opacity",1); })
.on("mouseleave", function() { d3.select(this).attr("opacity", 0.5); });
You need to preprocess your data to give it the structure you need.
For this you can define a dataPreparation function:
function dataPreparation ( data ) {
var byYear = {};
var result = [];
data.forEach(function (d) {
if ( !byYear.hasOwnProperty(d.year) ) {
byYear[d.year] = {
year: d.year,
order: +d.order,
score: +d.score,
weight: +d.weight,
width: +d.width,
color: d.color,
label: d.label,
entries: [d]
};
result.push(byYear[d.year]);
} else {
byYear[d.year].score += +d.score;
byYear[d.year].entries.push(d);
}
});
return result;
}
And then in your csv load callback, you can do:
d3.csv('./src/InCountry-v1.csv', function(error, data) {
var hierarchicalData = dataPreparation(data);
And the provide your hierarchicalData to your pie generator function.
Good luck!
I'm looking to somehow get two donut charts on top of one another, or atleast just the arcs. I want to hide one specific arc, and show the other on click, and then revert on click again.
I figured out you can simply hide an arc on click by selecting that slice, and doing d3.select("the arc").attr("visibility", "hidden");
So I want to hide one slice, and show the other. I want the arcs to take up the same spot, so showing the other appears to only change the arc.
Thank you,
Brian
As far as I understand your problem, you want to update a particular arc on click.
So, instead of creating two donuts, one on top of another, just create one donut chart and update it whenever the arc is clicked.
$(document).ready(function() {
var width = 400,
height = 250,
radius = Math.min(width, height) / 2;
var color = d3.scale.category20();
var pie = d3.layout.pie()
.value(function(d) {
return d.apples;
})
.sort(null);
var arc = d3.svg.arc()
.innerRadius(radius - 70)
.outerRadius(radius - 20);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
var data = [{
"apples": 53245,
"oranges": 200
}, {
"apples": 28479,
"oranges": 200
}, {
"apples": 19697,
"oranges": 200
}, {
"apples": 24037,
"oranges": 200
}];
var path = svg.datum(data).selectAll("path")
.data(pie)
.enter().append("path")
.attr("fill", function(d, i) {
return color(i);
})
.attr("d", arc)
.each(function(d) {
this._current = d;
}) // store the initial angles
.on("click", function(d) {
var key = d.data.getKeyByValue(d.value);
var oppKey = (key === "apples") ? "oranges" : "apples";
change(oppKey);
});
function change(keyVal) {
var value = keyVal;
pie.value(function(d) {
return d[value];
}); // change the value function
path = path.data(pie); // compute the new angles
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
function type(d) {
d.apples = +d.apples;
d.oranges = +d.oranges;
return d;
}
// 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 arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
Object.prototype.getKeyByValue = function(value) {
for (var prop in this) {
if (this.hasOwnProperty(prop)) {
if (this[prop] === value)
return prop;
}
}
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>