i have data for the first and following attempt of seeking asylum in germany for refugees and therefore two graphs which are looking like this
First one
Second one
Transition from one to another shoulnd't be a problem i suppose but what I want is so show both of them in one graph too. So a combination of both datasets for each country. But is it possible to distinguish from one another if I combine countries and the first and following ("erst/folge")? Each country should have on layer divided by two, one is the first attempt the other is the second attempt of this country. One idea to differ those sub-layers is maybe custom coloscale which I try right now. How can I show both data in one graph? Is it even possible to join both datasets and still see the difference?
Here is my html:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.chart {
background: #fff;
}
p {
font: 12px helvetica;
}
.axis path, .axis line {
fill: none;
stroke: #000;
stroke-width: 2px;
shape-rendering: crispEdges;
}
button {
position: absolute;
right: 50px;
top: 10px;
}
</style>
<body>
<script src="http://d3js.org/d3.v2.js"></script>
<div id="option">
<input name="updateButton"
type="button"
value="Show Data for second Attemp"
onclick="updateSecond('folgeantraege_monatlich_2015_mitentscheidungenbisnovember.csv')" />
</div>
<div id="option">
<input name="updateButton"
type="button"
value="Show Data for first Attemp"
onclick="updateFirst('erstantraege_monatlich_2015_mitentscheidungenbisnovember.csv')" />
</div>
<div id="option">
<input name="updateButton"
type="button"
value="Show both"
onclick="updateBoth('erstantraege_monatlich_2015_mitentscheidungenbisnovember.csv', 'folgeantraege_monatlich_2015_mitentscheidungenbisnovember.csv')" />
</div>
<div class="chart">
</div>
<script>
var margin = {top: 20, right: 40, bottom: 30, left: 30};
var width = document.body.clientWidth - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height-10, 0]);
var dateParser = d3.time.format("%Y-%m-%d").parse;
var stack = d3.layout.stack()
.offset("zero")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.value; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
var z = d3.scale.category20()
chart("erstantraege_monatlich_2015_mitentscheidungenbisnovember.csv");
var datearray = [];
var colorrange = [];
function chart(csvpath) {
// var dateParser = d3.time.format("%Y-%m-%d").parse;
// var margin = {top: 20, right: 40, bottom: 30, left: 30};
// var width = document.body.clientWidth - margin.left - margin.right;
// var height = 400 - margin.top - margin.bottom;
// var x = d3.time.scale()
// .range([0, width]);
//
// var y = d3.scale.linear()
// .range([height-10, 0]);
// var z = d3.scale.category20()
//var color = d3.scale.category10()
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.months);
var yAxis = d3.svg.axis()
.scale(y);
var yAxisr = d3.svg.axis()
.scale(y);
// var stack = d3.layout.stack()
// .offset("zero")
// .values(function(d) { return d.values; })
// .x(function(d) { return d.date; })
// .y(function(d) { return d.value; });
var nest = d3.nest()
.key(function(d) { return d.Land});
// var nestFiltered = nest.filter(function(d){
// return d.Land != 'Total';
// })
// var area = d3.svg.area()
// .interpolate("cardinal")
// .x(function(d) { return x(d.date); })
// .y0(function(d) { return y(d.y0); })
// .y1(function(d) { return y(d.y0 + d.y); });
var svg = d3.select(".chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.csv(csvpath, function(data) {
data.forEach(function(d) {
d.date = dateParser(d.Datum);
d.value = +d.ErstanträgeZahl;
});
//onsole.log(data);
var layers = stack(nest.entries(data));
console.log(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return z(i); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.attr("transform", "translate(" + width + ", 0)")
.call(yAxis.orient("right"));
svg.append("g")
.attr("class", "y axis")
.call(yAxis.orient("left"));
});
}
function updateSecond(csvpath) {
var nest = d3.nest()
.key(function(d) { return d.Land});
d3.csv(csvpath, function(data) {
data.forEach(function(d) {
d.date = dateParser(d.Datum);
d.value = +d.Summe;
console.log(d.date);
console.log(d.value);
});
var layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
d3.selectAll("path")
.data(layers)
.transition()
.duration(750)
.style("fill", function(d, i) { return z(i); })
.attr("d", function(d) { return area(d.values); });
svg.select(".y.axis") // change the y axis
.duration(750)
.call(yAxis);
});
}
function updateFirst(csvpath) {
var nest = d3.nest()
.key(function(d) { return d.Land});
d3.csv(csvpath, function(data) {
data.forEach(function(d) {
d.date = dateParser(d.Datum);
d.value = +d.ErstanträgeZahl;
console.log(d.date);
console.log(d.value);
});
var layers = stack(nest.entries(data));
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
d3.selectAll("path")
.data(layers)
.transition()
.duration(750)
.style("fill", function(d, i) { return z(i); })
.attr("d", function(d) { return area(d.values); });
svg.select(".y.axis") // change the y axis
.duration(750)
.call(yAxis);
});
}
function updateBoth(csvpathFirst, csvpathSecond){
var nest = d3.nest()
.key(function(d) { return d.Land});
d3.csv(csvpathFirst, function(data1) {
d3.csv(csvpathSecond, function(data2) {
});
});
}
</script>
And my data is here at my repo
EDIT1:
For example csv1 contains
Datum,Land,Summe,Position,Antragsart,EntscheidungenInsgesamt,Asylberechtigt,Flüchtling,GewährungVonSubsidiäremSchutz,Abschiebungsverbot,UnbegrenzteAblehnungen,Ablehnung,sonstigeVerfahrenserledigungen
2015-01-01,Afghanistan,1129,5,Erst,418,0,105,4,58,66,6,179
2015-02-01,Afghanistan,969,5,Erst,849,9,186,16,100,131,10,397
2015-03-01,Afghanistan,885,5,Erst,1376,17,309,58,158,201,11,622
2015-04-01,Afghanistan,1119,6,Erst,1838,21,384,75,202,261,15,880
2015-05-01,Afghanistan,1151,6,Erst,2272,21,499,91,249,303,16,1093
2015-06-01,Afghanistan,2051,6,Erst,2911,23,683,132,313,377,19,1364
2015-07-01,Afghanistan,2104,6,Erst,3340,27,767,160,366,431,21,1568
2015-08-01,Afghanistan,2270,5,Erst,3660,28,922,172,409,453,23,1653
2015-09-01,Afghanistan,2724,4,Erst,4057,36,1049,201,455,475,26,1815
2015-10-01,Afghanistan,3770,4,Erst,4540,37,1188,234,516,538,29,1998
2015-11-01,Afghanistan,4929,0,Erst,5026,46,1340,253,620,623,49,2095
And csv2 contains
Datum,Antragsart,Land,Summe,Position,Datum2,Position,Herkunft,Entscheidungeninsgesamt,Asylberechtigt,Prozent,Flüchtling,Pronzent,GewährungvonsubisdiäremSchutz,Prozent,Abschiebungsverbot,Prozent,UnbegrenzteAblehnungen,Prozent,Ablehnung,Prozent,keinweiteresverfahren,Prozent,sonstigeVerfahrenserledigungen,Prozent
2015-01-01,Folge,Afghanistan,33,10,2015-01-01,10,Afghanistan,29,0,0,5,17.2,2,6.9,8,27.6,0,0,0,0,1,3.4,13,44.8
2015-02-01,Folge,Afghanistan,29,10,2015-02-01,10,Afghanistan,81,0,0,13,16,4,4.9,22,27.2,0,0,0,0,10,12.3,32,39.5
2015-03-01,Folge,Afghanistan,41,9,2015-03-01,9,Afghanistan,135,0,0,21,15.6,10,7.4,37,27.4,1,0.7,0,0,23,17,43,31.9
2015-04-01,Folge,Afghanistan,25,10,2015-04-01,10,Afghanistan,165,0,0,34,20.6,12,7.3,41,24.8,4,2.4,0,0,30,18.2,44,26.7
2015-05-01,Folge,Afghanistan,37,9,2015-05-01,9,Afghanistan,212,0,0,54,25.5,12,5.7,50,23.6,4,1.9,0,0,32,15.1,60,28.3
2015-06-01,Folge,Afghanistan,35,9,2015-06-01,9,Afghanistan,261,0,0,72,27.6,17,6.5,59,22.6,6,2.3,0,0,35,13.4,72,27.6
2015-07-01,Folge,Afghanistan,35,9,2015-07-01,9,Afghanistan,288,0,0,82,28.5,17,5.9,64,22.2,6,2.1,0,0,42,14.6,77,26.7
2015-08-01,Folge,Afghanistan,34,9,2015-08-01,9,Afghanistan,321,0,0,100,31.2,20,6.2,66,20.6,6,1.9,0,0,52,16.2,77,24
2015-09-01,Folge,Afghanistan,27,4,2015-09-01,9,Afghanistan,354,0,0,120,33.9,20,5.6,72,20.3,7,2,0,0,54,15.3,81,22.9
2015-10-01,Folge,Afghanistan,24,9,2015-10-01,9,Afghanistan,389,0,0,136,35,20,5.1,83,21.3,7,1.8,0,0,54,13.9,89,22.9
2015-11-01,Folge,Afghanistan,47,,,,,431,1,0.2,148,34.3,23,5.3,97,22.5,8,1.9,0,0,58,13.5,96,22.3
The values ("Summe") should sum up the values per month of each country (Afghanistan) but also should reflect the values for their stacks on their own (Right now I'm trying to figur out how to use texture.js and custom scales to use textures to distinguish the colors from another because every country should have it's own color in this graph but as I already mentioned they should be different in their sublayers
When I try to put both csv fiels in one file I get not exactly but something similar to this
Can you give me some tips how to archive sub-layers (data structure/algorithm or what i takes to achieve this) so I can proceed and try to implement textures?
Thanks in advance
Final EDIT as answer to Cyrils :
var margin = {top: 20, right: 40, bottom: 30, left: 30};
var width = document.body.clientWidth - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height-10, 0]);
var dateParser = d3.time.format("%Y-%m-%d").parse;
var stack = d3.layout.stack()
.offset("zero")
.values(function(d) { return d.values; })
.x(function(d) { return d.graphDate; })
.y(function(d) { return d.value; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.graphDate); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
var z = d3.scale.category20()
doInit();
updateFirst('data/all.csv');
function doInit(){
//make the svg and axis
xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.months);
yAxis = d3.svg.axis()
.scale(y);
yAxisr = d3.svg.axis()
.scale(y);
//make svg
var graph = d3.select(".chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
graph.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
graph.append("g")
.attr("class", "y axis yright")
.attr("transform", "translate(" + width + ", 0)")
.call(yAxis.orient("right"));
graph.append("g")
.attr("class", "y axis yleft")
.call(yAxis.orient("left"));
}
function updateFirst(csvpath) {
var nest = d3.nest()
.key(function(d) { return d.Land+ "-" + d.Antragsart});
//console.log(nest);
d3.csv(csvpath, function(data) {
data.forEach(function(d) {
//console.log(data);
d.graphDate = dateParser(d.Datum);
d.value = +d.Summe;
d.type= d.Antragsart;
});
var layers = stack(nest.entries(data)).sort(function(a,b){return d3.ascending(a.key, b.key)});
console.log(layers);
x.domain(d3.extent(data, function(d) { return d.graphDate; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
var k = d3.select("g .x")
.call(xAxis);
d3.select("g .yright")
.call(yAxis);
d3.select("g .yleft")
.call(yAxis);
d3.selectAll("defs").remove();
d3.select(".chart svg g").selectAll("path").remove();
d3.select(".chart svg g").selectAll("path")
.data(layers).enter().append("path")
//.style("fill", function(d, i) { console.log(d.key);return z(d.key); })
.attr("class", function(d){
var country = d.key.split("-")[0];
var src = d.key.split("-")[1];
return src;
})
.style("fill", function(d){
var country = d.key.split("-")[0];
var src = d.key.split("-")[1];
if (src === "Folge"){
var t = textures.lines().thicker(2).stroke(z(country));
d3.select(".chart").select("svg").call(t);
return t.url();
} else {
return z(country);
}
})
.attr("d", function(d) { return area(d.values); });
});
}
For merging the records I am making use of d3 queue..Read here
The purpose of this is to load 2 CSV via ajax and when both loaded calls the callback.
queue()
.defer(d3.csv, csvpathFirst) //using queue so that callback is called after loading both CSVs
.defer(d3.csv, csvpathSecond)
.await(makeMyChart);
function makeMyChart(error, first, second) {
var data = [];
Make the nested function based on country and csv
var nest = d3.nest()
.key(function(d) {
return d.Land + "-" + d.src; //d.src is first if first csv second if vice versa
});
Next I am merging the records like this:
//iterate first
first.forEach(function(d) {
d.graphDate = dateParser(d.Datum);
d.value = +d.Summe;
d.src = "first"
data.push(d)
});
//iterate second
second.forEach(function(d) {
d.graphDate = dateParser(d.Datum);
d.value = +d.Summe;
d.src = "second"
data.push(d)
});
//sort layers on basis of country
var layers = stack(nest.entries(data)).sort(function(a, b) {
return d3.ascending(a.key, b.key)
});
Regenerate the axis like this:
//regenerate the axis with new domains
var k = d3.select("g .x")
.call(xAxis);
d3.select("g .yright")
.call(yAxis);
d3.select("g .yleft")
.call(yAxis);
Remove all old paths and defs DOM like this:
d3.selectAll("defs").remove();
d3.select(".chart svg g").selectAll("path").remove();
Next based on country and first csv and second csv add style fill.
.style("fill", function(d) {
var country = d.key.split("-")[0];
var src = d.key.split("-")[1];
if (src === "first") {
//use texture.js for pattern
var t = textures.lines().thicker().stroke(z(country));
d3.select(".chart").select("svg").call(t);
return t.url();
} else {
return z(country);
}
})
Working code here
Hope this helps!
I found a reference to the issue I am facing in this link
'nemesv' has provided with a solution to the actual issue.
var myApp = angular.module('app', []);
myApp.directive("lineChart", function() {
return {
restrict: 'E',
scope: {
data: '=',
id: '#'
},
link: function (scope, element, attrs) {
scope.$watch( 'data', function ( data ) {
d3.select("#"+attrs.id).select("svg").remove();
if (data) {
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = element[ 0 ].parentElement.offsetWidth - margin.left - margin.right,
height = element[ 0 ].parentElement.offsetHeight - margin.top - margin.bottom;
var parseDate = d3.time.format("%d-%b-%y").parse;
var bisectDate = d3.bisector(function(d) { return d[0]; }).left;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.innerTickSize(-height)
.ticks(4)
.outerTickSize(0)
.tickPadding(5)
.tickFormat(function(d) { return d3.time.format('%d/%m %H:%M')(new Date(d)); });
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.innerTickSize(-width)
.outerTickSize(0)
.tickPadding(10);
var line = d3.svg.line()
.x(function(d) { return x(d[0]); })
.y(function(d) { return y(d[1]); });
var svg = d3.select(element[0]).append("svg")
.attr("width", '100%')
.attr("height", '100%')
.attr('viewBox','0 0 '+ element[ 0 ].parentElement.offsetWidth +' '+ element[ 0 ].parentElement.offsetHeight )
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var minX = d3.min(data, function (item) { return d3.min(item.values, function (d) { return d[0]; }); });
var maxX = d3.max(data, function (item) { return d3.max(item.values, function (d) { return d[0]; }); });
var minY = d3.min(data, function (item) { return d3.min(item.values, function (d) { return d[1]; }); });
var maxY = d3.max(data, function (item) { return d3.max(item.values, function (d) { return d[1]; }); });
x.domain([minX, maxX]);
y.domain([0, maxY]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
var domaine = svg.selectAll(".domaine")
.data(data)
.enter().append("g")
.attr("class", "domaine");
domaine.append("path")
.attr("class", "line")
.attr("d", function (d) {
return line(d.values);
})
.style("stroke", function (d) {
return d.color;
});
var focus = svg.append("g")
.attr("class", "focus")
.style("display", "none");
for(var i=0;i<data.length;i++){
focus.append("g")
.attr("class", "focus"+i)
.append("circle")
.attr("r", 4.5);
svg.select(".focus"+i)
.append("text")
.attr("x", 9)
.attr("dy", ".35em");
}
svg.append("rect")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", mousemove);
function mousemove() {
var x0 = x.invert(d3.mouse(this)[0]);
var series = data.map(function(e) {
var i = bisectDate(e.values, x0, 1),
d0 = e.values[i - 1],
d1 = e.values[i];
return x0 - d0[0] > d1[0] - x0 ? d1 : d0;
});
for(var i=0; i<series.length;i++){
var selectedFocus = svg.selectAll(".focus"+i);
selectedFocus.attr("transform", "translate(" + x(series[i][0]) + "," + y(series[i][1]) + ")");
selectedFocus.select("text").text(series[i][1]);
}
}
}
});
}
};
});
function MainCtrl($scope) {
$scope.lineData = [{"key": "users","color": "#16a085","values": [[1413814800000,4034.418],[1413815400000,5604.155000000001],[1413816000000,6343.079],[1413816600000,7308.226],[1413817200000,9841.185],[1413817800000,6571.891],[1413818400000,4660.6005000000005],[1413819000000,4555.4795],[1413819600000,5963.723],[1413820200000,9179.9595]]},{"key": "users 2","color": "#d95600","values": [[1413814800000,3168.183],[1413815400000,1530.8435],[1413816000000,2416.071],[1413816600000,1274.309],[1413817200000,1105.0445],[1413817800000,2086.0299999999997],[1413818400000,712.642],[1413819000000,1676.725],[1413819600000,3721.46],[1413820200000,2887.7975]]}];
}
Presently, if I hover over one line, I can see another dot hovering over the other line as well - i.e. mouseover is working simultaneously on both the lines. Could this mouseover/tooltip be done separately on each line i.e. when I hover over one line, focus circle will appear for only that line?
I'm rather a beginner at both JS and D3.js; my JSFiddle is here.
JSHint shows no errors, so I think I'm doing the D3 wrong.
I'm trying to do the same thing as in these questions, adapting a Tributary example to run outside of that platform: "Exporting" a Tributary example that makes use of the tributary object - d3.js and Getting horizontal stack bar example to display using d3.js (I'm even adapting the same code as the latter) Unfortunately, there is no corrected JSFiddle or other example in either answer
Here's my code:
var VanillaRunOnDomReady = function() {
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 24
};
var data = [
{"key":"Nonviolent", "cat1":0.69, "cat2":0.21, "cat3":0.10},
{"key":"Violent", "cat1":0.53, "cat2":0.29, "cat3":0.18}
];
var catnames = {cat1: "No mental illness",
cat2: "Mild mental illness",
cat3: "Moderate or severe mental illness"};
var svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')');
var n = 3, // number of layers
m = data.length, // number of samples per layer
stack = d3.layout.stack(),
labels = data.map(function(d) {return d.key;}),
//go through each layer (cat1, cat2 etc, that's the range(n) part)
//then go through each object in data and pull out that objects's catulation data
//and put it into an array where x is the index and y is the number
layers = stack(d3.range(n).map(function(d) {
var a = [];
for (var i = 0; i < m; ++i) {
a[i] = {x: i, y: data[i]['cat' + (d+1)]};
}
return a;
})),
//the largest single layer
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
//the largest stack
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var margin = {top: 40, right: 10, bottom: 20, left: 50},
width = 677 - margin.left - margin.right,
height = 533 - margin.top - margin.bottom;
var y = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([2, height], 0.08);
var x = d3.scale.linear()
.domain([0, yStackMax])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return color(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return y(d.x); })
.attr("x", function(d) { return x(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { return x(d.y); });
var yAxis = d3.svg.axis()
.scale(y)
.tickSize(1)
.tickPadding(6)
.tickValues(labels)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
};
I think you are just missing a (); at the end of your function and before the semicolon. That would make it self executing. I forked your fiddle.
var VanillaRunOnDomReady = function() {
var margins = {
top: 12,
left: 48,
right: 24,
bottom: 24
};
var data = [
{"key":"Nonviolent", "cat1":0.69, "cat2":0.21, "cat3":0.10},
{"key":"Violent", "cat1":0.53, "cat2":0.29, "cat3":0.18}
];
var catnames = {cat1: "No mental illness",
cat2: "Mild mental illness",
cat3: "Moderate or severe mental illness"};
var svg = d3.select('body')
.append('svg')
.attr('width', width + margins.left + margins.right)
.attr('height', height + margins.top + margins.bottom)
.append('g')
.attr('transform', 'translate(' + margins.left + ',' + margins.top + ')');
var n = 3, // number of layers
m = data.length, // number of samples per layer
stack = d3.layout.stack(),
labels = data.map(function(d) {return d.key;}),
//go through each layer (cat1, cat2 etc, that's the range(n) part)
//then go through each object in data and pull out that objects's catulation data
//and put it into an array where x is the index and y is the number
layers = stack(d3.range(n).map(function(d) {
var a = [];
for (var i = 0; i < m; ++i) {
a[i] = {x: i, y: data[i]['cat' + (d+1)]};
}
return a;
})),
//the largest single layer
yGroupMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y; }); }),
//the largest stack
yStackMax = d3.max(layers, function(layer) { return d3.max(layer, function(d) { return d.y0 + d.y; }); });
var margin = {top: 40, right: 10, bottom: 20, left: 50},
width = 677 - margin.left - margin.right,
height = 533 - margin.top - margin.bottom;
var y = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([2, height], 0.08);
var x = d3.scale.linear()
.domain([0, yStackMax])
.range([0, width]);
var color = d3.scale.linear()
.domain([0, n - 1])
.range(["#aad", "#556"]);
var svg = d3.select("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var layer = svg.selectAll(".layer")
.data(layers)
.enter().append("g")
.attr("class", "layer")
.style("fill", function(d, i) { return color(i); });
layer.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
.attr("y", function(d) { return y(d.x); })
.attr("x", function(d) { return x(d.y0); })
.attr("height", y.rangeBand())
.attr("width", function(d) { return x(d.y); });
var yAxis = d3.svg.axis()
.scale(y)
.tickSize(1)
.tickPadding(6)
.tickValues(labels)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
}();