Updating data in D3 stacked bar graph - javascript

I have a new set of data that I want to have updated within the SVG but I'm not really sure how to correctly grab the elements I have replace the data while smoothly transitioning.
http://codepen.io/jacob_johnson/pen/jAkmPG
All relevant code is in the CodePen above; however, I will post some of it here.
// Array to supply graph data
var FOR_PROFIT = [10,80,10];
var NONPROFIT = [60,10,30];
var PUBLIC = [40,40,20];
var data = [
{
"key":"PUBLIC",
"pop1":PUBLIC[0],
"pop2":PUBLIC[1],
"pop3":PUBLIC[2]
},
{
"key":"NONPROFIT",
"pop1":NONPROFIT[0],
"pop2":NONPROFIT[1],
"pop3":NONPROFIT[2]
},
{
"key":"FORPROFIT",
"pop1":FOR_PROFIT[0],
"pop2":FOR_PROFIT[1],
"pop3":FOR_PROFIT[2]
}
];
I have two data arrays (one called data and another called data2 with modified information). I essentially want to transition the created graph into the new data. I understand enough to rewrite the graph over the old graph but I am not getting any transitions and am obviously just printing new data on top of the old instead of modifying what I have.
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; }),
layers = stack(d3.range(n).map(function(d)
{
var a = [];
for (var i = 0; i < m; ++i)
{
a[i] = { x: i, y: data[i]['pop' + (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: 150},
width = 677 - margin.left - margin.right,
height = 212 - margin.top - margin.bottom;
var y = d3.scale.ordinal()
.domain(d3.range(m))
.rangeRoundBands([2, height], .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);
});
This is what is handling the creation of my graph with its layers being the individual bars. Now I've seen this: https://bl.ocks.org/mbostock/1134768 but I still don't understand as its not very well commented.
Any guidance, links, or help in this would be amazing. Thanks.

Your Solution: http://codepen.io/typhon/pen/bZLoZW
Hope this helps!!
You can slow down the speed of transition by increasing parameter passed to duration(500) at line 200.(and vice versa)
Read update, enter and exit selections. They are handy almost all the time while working with D3. Take this for reference.

Related

d3 area stacked line chart

I'm working on modifying this stacked line chart example: https://bl.ocks.org/d3indepth/e4efd402b4d9fdb2088ccdf3135745c3
I'm adding a time x axis, but I'm struggling with this block of code:
var areaGenerator = d3.area()
.x(function(d, i) {
// return i * 100;
return i * 253.5;
})
.y0(function(d) {
return y(d[0]);
})
.y1(function(d) {
return y(d[1]);
});
The original example has the .x accessor as i * 100 which seems to be a random value. When I add the X axis the stacked line chart does not line up correctly with the date ticks. I can manually force it to line up by returning i * 253.5 but that is not ideal. I don't really understand how this area function is working - any help would be appreciated.
let height = 600;
let width = 800;
const yMax = 4000;
//var hEach = 40;
let margin = {top: 20, right: 15, bottom: 25, left: 25};
width = width - margin.left - margin.right;
height = height - margin.top - margin.bottom;
var svg = d3.select('body').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 + ")");
let formatDate = d3.timeFormat("%b-%Y")
let parseTime = d3.timeParse("%Y-%m-%d");
let data = [
{
"host_count": 2553,
"container_count": 875,
"hour": "2019-01-31",
"apm_host_count": 0,
"agent_host_count": 2208,
"gcp_host_count": 0,
"aws_host_count": 345
},
{
"host_count": 1553,
"container_count": 675,
"hour": "2019-02-01",
"apm_host_count": 0,
"agent_host_count": 1208,
"gcp_host_count": 0,
"aws_host_count": 445
},
{
"host_count": 716,
"container_count": 6234,
"hour": "2019-02-02",
"apm_host_count": 0,
"agent_host_count": 479,
"gcp_host_count": 0,
"aws_host_count": 237
},
{
"host_count": 516,
"container_count": 4234,
"hour": "2019-02-03",
"apm_host_count": 0,
"agent_host_count": 679,
"gcp_host_count": 0,
"aws_host_count": 137
}
];
// format the data
data.forEach(function(d) {
d.hour = parseTime(d.hour);
});
// set the ranges
var x = d3.scaleTime().range([0, width]);
x.domain(d3.extent(data, function(d) { return d.hour; }));
var xAxis = d3.axisBottom(x).ticks(11).tickFormat(d3.timeFormat("%y-%b-%d")).tickValues(data.map(d=>d.hour));
var y = d3.scaleLinear()
.domain([0, yMax])
.range([height, 0]);
var areaGenerator = d3.area()
.x(function(d, i) {
console.log(d);
return i * 100;
})
.y0(function(d) {
return y(d[0]);
})
.y1(function(d) {
return y(d[1]);
});
var colors = ['#FBB65B', '#513551', '#de3163']
var stack = d3.stack()
.keys(['agent_host_count', 'aws_host_count', 'container_count']);
var stackedSeries = stack(data);
d3.select('g')
.selectAll('path')
.data(stackedSeries)
.enter()
.append('path')
.style('fill', function(d, i) {
return colors[i];
})
.attr('d', areaGenerator)
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
<!doctype html>
<html lang="en">
<head>
<title>Usage</title>
</head>
<body>
<div id="svg"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
</body>
</html>
When using axes in d3, you actually need to use the axis variable to calculate scaling factors. The functions that do this calculation are returned by the scale*() methods. In your code you have this for the x-axis:
var x = d3.scaleTime().range([0, width]);
As such, the variable x now contains a function that will do interpolation for you. this is what your areaGenerator function should look like:
var areaGenerator = d3.area()
.x(function(d, i) {
return x(d.data.hour);
})
.y0(function(d) {
return y(d[0]);
})
.y1(function(d) {
return y(d[1]);
});
The only thing you need to remember is that when calculating the value you need to use the same variable that the axis is based on. I.e. your x-axis is a time axis so you need to calculate the interpolation using the time variable (d.data.hour).
As to where 100 comes from in the example, you are essentially correct. In that block the value of 100 is more or less arbitrary. It was likely chosen because the chart looks reasonably good at that scale. By choosing 100, each "tick" is spaced 100px apart and since there is no x-axis to be judged against it doesn't actually matter what is used as long as it changes for each data point.

Problem creating multiple charts in the same page

I have two functions, each supposed to draw a different chart from the same dataset to two SVGs with id attributes of one and two respectively.
I have created different variables to append the chart to its corresponding SVG:
var svg1 = d3.select("#one")
var svg2 = d3.select("#two")
The problem is the function that draws chart for the second SVG overwrites both charts.
You can see the charts return correctly if you run each function separately. (Comment out each function call to see the data from the other chart that is being overwritten)
JS Fiddle
<html>
<body>
<svg id="one"></svg>
<svg id="two"></svg>
<script>
// variables we'll be filtering by
var quantile;
var amount;
var type;
var risk;
w = window.innerWidth;
h = window.innerHeight;
// <-- Make Selection -->
// Possible values --
// quantile: [1 2 3 4]
// variable: ['Income' 'Payments' 'Consumption' 'Utility']
// amount: [ 5000 30000 70000]
// type: ['Loan' 'ISA']
// risk: [1 2 3 4]
quantile = 1;
amount=5000;
type = 'Loan';
risk = 2;
getFirst();
getSecond();
function getFirst() {
variable= 'Income';
// chart stuff
margin = {top: h/4, right: w/4, bottom: h/4, left: w/4},
width = window.innerWidth - margin.left - margin.right,
height = window.innerHeight - margin.top - margin.bottom;
x = d3.scaleLinear()
.rangeRound([0, width]);
y = d3.scaleLinear()
.rangeRound([height,0]);
valueline = d3.line()
.x(function(d) { return x(d.key); })
.y(function(d) { return y(d.value); });
var svg1 = d3.select("#one")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// data
d3.csv("https://raw.githubusercontent.com/JainFamilyInstitute/isa-app/master/data/data_vis.csv?token=AXiiVXcAwXZjLK4-3tiyxKwj8yaVMVDmks5b6b8NwA%3D%3D", function(error, data) {
if (error) throw error;
// filter by selection
data = data.filter(function(d) {
return (d.quantile == quantile) &
(d.variable == variable) &
(d.amount == amount) &
(d.type == type) &
(d.risk == risk) });
// create visualizable array with only ages and amounts for selected series
data_filtered = data.map(({ quantile,amount,risk,type,variable, ...item }) => item);
data_vis = data_filtered[0];
console.log(data_vis);
result = [];
for(i=22;i<101;i++){
key = i;
value =parseFloat(data_vis[i]);
result.push({
key: key,
value: value
});
}
// console.log(data_vis);
console.log(result);
// Scale the range of the data
x.domain([d3.min(result, function(d) { return d.key; }), d3.max(result, function(d) { return d.key; })]);
y.domain([0, d3.max(result, function(d) { return d.value; })]);
// Add the valueline path.
svg1.append("path")
.data([result])
.attr("class", "line")
.attr("d", valueline);
// Add the X Axis
svg1.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
function y_grid_lines() {
return d3.axisLeft(y)
}
svg1.append("g")
.attr("class", "grid")
.call(y_grid_lines()
.tickSize(-width)
);
});
}
function getSecond() {
variable= 'Payments';
// chart stuff
margin = {top: h/4, right: w/4, bottom: h/4, left: w/4},
width = window.innerWidth - margin.left - margin.right,
height = window.innerHeight - margin.top - margin.bottom;
x = d3.scaleLinear()
.rangeRound([0, width]);
y = d3.scaleLinear()
.rangeRound([height,0]);
valueline = d3.line()
.x(function(d) { return x(d.key); })
.y(function(d) { return y(d.value); });
var svg2 = d3.select("#two")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
// data
d3.csv("https://raw.githubusercontent.com/JainFamilyInstitute/isa-app/master/data/data_vis.csv?token=AXiiVXcAwXZjLK4-3tiyxKwj8yaVMVDmks5b6b8NwA%3D%3D", function(error, data) {
if (error) throw error;
// filter by selection
data = data.filter(function(d) {
return (d.quantile == quantile) &
(d.variable == variable) &
(d.amount == amount) &
(d.type == type) &
(d.risk == risk) });
// create visualizable array with only ages and amounts for selected series
data_filtered = data.map(({ quantile,amount,risk,type,variable, ...item }) => item);
data_vis = data_filtered[0];
console.log(data_vis);
result = [];
for(i=22;i<101;i++){
key = i;
value =parseFloat(data_vis[i]);
result.push({
key: key,
value: value
});
}
// console.log(data_vis);
console.log(result);
// Scale the range of the data
x.domain([d3.min(result, function(d) { return d.key; }), d3.max(result, function(d) { return d.key; })]);
y.domain([0, d3.max(result, function(d) { return d.value; })]);
// Add the valueline path.
svg2.append("path")
.data([result])
.attr("class", "line")
.attr("d", valueline);
// Add the X Axis
svg2.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
// Add the Y Axis
svg2.append("g")
.call(d3.axisLeft(y));
});
}
</script>
</html>
One possible fix is to fetch your data once and the filter twice.
Here is a fiddle I create using d3v5 which uses fetch API making it easier to avoid this kind of issues. In general, be wary of callbacks when you write Javascript code.
The main change in this code is the following:
d3.csv("<remote csv>").then(function(data) {
getIncome(data);
getPayments(data);
});
The rest is quite similar to your code.
EDIT: Gerardo Furtado's answer also addresses an important issue regarding the cause of the issue. You should also follow the suggestions in that answer. My solution leaves the issue addressed in that answer unresolved. Instead, my answer focuses on ensuring the intended order of execution.
A word of caution about Javascript: when you do this...
variable = 'Income';
... instead of:
var variable = 'Income';
... or even using const or let, your variable is a global variable, that is, it's not scoped inside getIncome() or getPayments().
Together with the fact that the callback of d3.csv doesn't run immediately, this is what happens:
You call both functions, one after the other:
getIncome();
getPayments();
Inside getIncome(), you have:
function getIncome() {
variable= 'Income';
//etc...
which makes variable a global with "Income" as value.
Then, when it comes to getPayments(), you have:
function getPayments() {
variable= 'Payments';
//etc...
now variable has "Payments" as value, but the callback from the first function (getIncome()) was not executed yet!
Therefore, when the first function runs, the value of variable is "Payments", and both charts have the same path.
Solution: don't create globals, do not assign a value to an undeclared variable.
Here is your same Fiddle, just adding var in front of each variable: https://jsfiddle.net/qrj803za/3/
Also, take care of other globals you're creating here (like result, data_vis, etc...)

How do I get data to show up in my graph?

I'm trying to get my data to show up in my graph, however I get an error that says that my data is "NaN" after I converted the Year and Miles column from strings to integers.
I'm guessing that it's something with my x_scale & y_scale...?
<!DOCTYPE html>
<html lang="en">
<head>
<description>
<!--charts - avg vehicle trip per mile, source: http://nhts.ornl.gov/2009/pub/stt.pdf-->
</description>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.0/jquery.min.js"></script>
<link rel="stylesheet" href="/bootstrap-3.3.7-dist/css/bootstrap.min.css">
<script type="text/javascript" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<script type="text/javascript">
// global variables
var dataset;
d3.csv("avgVehicleTripLengthMiles.csv", function (error, data) {
if (error) {
console.log(error);
} else {
console.log(data);
}
// once loaded, data is copied to dataset because js is asynchronous
dataset = data;
createScatterplot();
});
/*
function typeConv() {
// type conversion from string to integer
var typeConv = dataset.forEach(function (d) {
d["Year"] = +d["Year"];
d["Miles"] = +d["Miles"];
return d;
});
}
*/
function createScatterplot() {
// TEST
var typeConv = dataset.forEach(function (d) {
d["Year"] = +d["Year"];
d["Miles"] = +d["Miles"];
return d;
});
var title = d3.select("body")
.append("h4")
.text("Avg. Vehicle Trip Length per Mile");
// dimensions of canvas
var padding = 30;
var margin = {
top: 20,
right: 40,
bottom: 20,
left: 40
},
w = 800 - margin.left - margin.right,
h = 400 - margin.top - margin.bottom;
// create svg canvas
var svg_canvas = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom);
// create scale for axis
var x_scale = d3.scaleLinear().domain([1969, 2009]).range([padding, w - padding * 2]);
var y_scale =
d3.scaleLinear().domain([0, 20]).range([h - padding, padding]);
// r_scale created specifically for circles' radius to be mapped unto axes
var r_scale =
d3.scaleLinear().domain([0, d3.max(dataset, function (d) {
return d[1];
})]).range([0, 20]);
// define axis & ticks // .ticks(5) to x_axis and .ticks(1) to y_axis
var x_axis = d3.axisBottom().scale(x_scale);
var y_axis = d3.axisLeft().scale(y_scale);
// create group, "g" element, to create x_axis and y_axis
var x_group = svg_canvas.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(x_axis);
var y_group = svg_canvas.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(y_axis);
// create circles
svg_canvas.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function (d) {
return x_scale(d[0]);
})
.attr("cy", function (d) {
console.log(d); // TEST
return y_scale(d[1]);
})
.attr("cr", function (d) {
return r_scale(d[1]);
});
}
</script>
</body>
</html>
EDIT (new answer):
There are several issues, and I'll try to step through them one by one.
In order to test, I had to make up my own data. My test CSV file looked like this (so your final answer might change slightly if your file is different)
Year,Miles
2006,5.0
2007,7.2
2008,19.3
As was pointed out by #altocumulus in the comments above, your .attr() calls are referencing non-existant indexes, which might be part of the trouble.
The radius attribute for circles is r, not cr
I simplified the code by not calling a function for r, but rather doing a static value. You may want to play further with this.
The significantly changed portion of code is
svg_canvas.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function (d) {
return x_scale(d["Year"]);
})
.attr("cy", function (d) {
return y_scale(d["Miles"]);
})
.attr("r", function (d) {
return 5;
//return r_scale(d[1]);
});
You still have an issue with the x-axis acting like numbers, and not dates, making 2006 look like 2,006, for example. I haven't solved that issue here.
Lastly, I feel like you're complicating your code for no reason by trying to handle margin AND padding via the D3 code, when these end up meaning similar things in the Javascript context. I suggest accomplishing most of the margin/padding via CSS, which would simplify your code. Another example of an unnecessary complication is in the previous answer, below.
FIDDLE
OLD (incomplete, incorrect) ANSWER:
The return value of Array.forEach() is undefined, so it can't be assigned.
dataset.forEach(function (d) {
//d["Year"] = +d["Year"];
d["Miles"] = +d["Miles"];
// NOT NEEDED: return d;
});
If you need to keep your converted array separate, use Array.map().
// global variables
var dataset;
d3.csv("avgVehicleTripLengthMiles.csv", function (error, data) {
if (error) {
console.log(error);
} else {
console.log(data);
}
// once loaded, data is copied to dataset because js is asynchronous
dataset = data;
createScatterplot();
});
function createScatterplot() {
// TEST
var typeConv = dataset.forEach(function (d) {
// d["Year"] = +d["Year"];
d["Miles"] = +d["Miles"];
// return d;
});
var title = d3.select("body")
.append("h4")
.text("Avg. Vehicle Trip Length per Mile");
// dimensions of canvas
var padding = 30;
var margin = {
top: 20,
right: 40,
bottom: 20,
left: 40
},
w = 800 - margin.left - margin.right,
h = 400 - margin.top - margin.bottom;
// create svg canvas
var svg_canvas = d3.select("body")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom);
// create scale for axis
var x_scale = d3.scaleLinear().domain([1965, 2009]).range([padding, w - padding * 2]);
var y_scale =
d3.scaleLinear().domain([0, 20]).range([h - padding, padding]);
// r_scale created specifically for circles' radius to be mapped unto axes
var r_scale =
d3.scaleLinear().domain([0, d3.max(dataset, function (d) {
return d[1];
})]).range([0, 20]);
// define axis & ticks // .ticks(5) to x_axis and .ticks(1) to y_axis
var x_axis = d3.axisBottom().scale(x_scale);
var y_axis = d3.axisLeft().scale(y_scale);
// create group, "g" element, to create x_axis and y_axis
var x_group = svg_canvas.append("g")
.attr("class", "axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(x_axis);
var y_group = svg_canvas.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",0)")
.call(y_axis);
// create & color circles
svg_canvas.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function (d) {
return x_scale(d["Year"]);
})
.attr("cy", function (d) {
return y_scale(d["Miles"]);
})
.attr("r", function (d) {
return 5;
})
.style("fill", function (d) {
if (d["Trip Purpose"] === "All Purposes") {
return "pink";
} else if (d["Trip Purpose"] === "To or From Work") {
return "red";
} else if (d["Trip Purpose"] === "Shopping") {
return "blue";
} else if (d["Trip Purpose"] === "Other Family/Personal Errands") {
return "green";
} else if (d["Trip Purpose"] === "Social and Recreational") {
return "gray";
};
});
// create text label for x-axis
svg_canvas.append("text")
.attr("x", w / 2)
.attr("y", h + margin.top + 20)
.style("text-anchor", "middle")
.text("Year");
// create text label for y-axis
svg_canvas.append("text")
.attr("transform", "rotate(-90)")
.attr("x", (0 - margin.left / 2))
.attr("y", (h/2))
.style("text-anchor", "middle")
.text("Miles");

D3 creating bar chart from big csv

I would like to create a bar chart from a csv file. The CSV file is huge and a little bit confusing, because the key columns are mostly two or three words. I am able to read the csv and get the data, such as YEAR OF ARREST. Now I need a function to count for each year the arrestees. So I think, I need different arrays. With these arrays I would like to create a barchart, on the x-axis the years and on the y the numbers of arrestees in this year.
Can some one help me with this. I am quite new to JavaScript and it is a little bit confusing.
This is what I have so far:
var arrestdate = [];
console.log(arrestdate);
d3.csv("urbana_crimes.csv", function(error, data) {
data.map(function(m){
arrestdate.push(m["YEAR OF ARREST"]);
})
//console.log(arrestdate);
});
console.log(arrestdate);
count(arrestdate);
function count(data) {
data.sort();
var current = null;
var cnt = 0;
for (var i = 0; i < data.length; i++) {
if (data[i] != current) {
if (cnt > 0) {
document.write(current + ' comes --> ' + cnt + ' times<br>');
}
current = data[i];
cnt = 1;
} else {
cnt++;
}
}
if (cnt > 0) {
document.write(current + ' comes --> ' + cnt + ' times');
}
};
The csv can be found here: https://www.dropbox.com/s/sg4lj2nlv5xgga7/urbana_crimes.csv?dl=0
Thanks in advance
Bernhard
EDIT:
Updated code:
var arrestdate = [];
var svg = d3.select("svg"),
margin = {top: 20, right: 20, bottom: 30, left: 40},
width = +svg.attr("width") - margin.left - margin.right,
height = +svg.attr("height") - margin.top - margin.bottom;
var x = d3.scaleBand().rangeRound([0, width]).padding(0.1),
y = d3.scaleLinear().rangeRound([height, 0]);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
console.log(arrestdate);
d3.csv("urbana_crimes.csv", function(error, data) {
data.map(function(m){
arrestdate.push(m["YEAR OF ARREST"]);
})
var nested = d3.nest()
.key(function (d) { return d['YEAR OF ARREST'] })
.entries(data);
//console.log(nested[0].key);
//console.log(nested[0].values);
// Set X to all your 19 keys, which are your years
x.domain(nested.map(function(d) { return d.key }))
// Set Y between 0 and the maximum length of values, which are your arrests
y.domain([0, d3.max(data, function(d) { return d.values.length })])
g.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x));
g.append("g")
.attr("class", "axis axis--y")
.call(d3.axisLeft(y).ticks(10, "%"))
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.71em")
.attr("text-anchor", "end")
.text("Frequency");
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("width", x.bandwidth())
.attr("x", function(d) { return x(nested[0].values.length); }) //What to put here?
.attr("y", function(d) { return y(+nested[0].key); }) // What to put here?
.attr("height", function(d) { return height - y(+nested[0].key); });
});
I would group this huge data set by year first, like this:
var nested = d3.nest()
.key(function (d) { return d['YEAR OF ARREST'] })
.entries(data)
This gives you an array of all 19 years (accessed via nested[0].key) with their respective elements (accessed via nested[0].values). For example, 2016 has 4374 arrests so far.
Here's a link to the d3 documentation for d3.nest
From here on you can follow any bar chart tutorial, like Mike Bostock's example.
Set the domain of your scales like this:
// Set X to all your 19 keys, which are your years
x.domain(nested.map(function(d) { return d.key }))
// Set Y between 0 and the maximum length of values, which are your arrests
y.domain([0, d3.max(data, function(d) { return d.values.length })])
Good luck!
Edit:
I would also recommend you to either delete some information you don't need from the csv file before you load it in the browser (49 MB) or to use map to only extract the information you need (like you've done in your code already).

D3 Multicolumn Relative Stack Chart

I am currently modifying the following example to produce a multi horizontal relative stack chart. The following example is a "single" stack chart example.
http://jsfiddle.net/zDkWP/
Here is my version however I get NaN X and Width values and I'm looking to find out why. Thank you.
Within the SVG the structure is as follows for each of the : -
g[ g [rect, rect, rect]], g[ g [rect, rect, rect]]
Here is my code: http://jsfiddle.net/scottietom/7c3vb4e9/
var dataset = [
[
[{x: 0,y: 100}],[{x: 0,y: 30}],[{x: 0,y: 50}]],
[[{x: 0,y: 100}],[{x: 0,y: 30}],[{x: 0,y: 50}]]
];
//Set up stack method
var stack = d3.layout.stack();
//Data, stacked
for (i=0; i<dataset.length; i++) {
stack(dataset[i]);
}
//Set up scales
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function (d) {
return d3.max(d, function (d) {
return d.y0 + d.y;
});
})])
.range([0, w]);
//Easy colors accessible via a 10-step ordinal scale
var colors = d3.scale.category10();
//or make your own colour palet
var color = d3.scale.ordinal()
.range(["#1459D9", "#148DD9", "#87ceeb", "#daa520"]);
//Create SVG element
var svg = d3.select(".pathanalysis_diagram")
.append("svg")
.attr("width", w + margin.left + margin.right)
.attr("height", h + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Add a group for each row of data
var groups = svg.selectAll("g")
.data(dataset)
.enter()
.append("g")
.style("fill", function (d, i) {
return color(i);
});
// Add a rect for each data value
var rects = groups.selectAll("rect")
.data(function (d) {
return d;
})
.enter()
.append("rect")
.attr("x", function (d) {
return xScale(d.y0);
})
.attr("y", 50)
.attr("height", 50)
.attr("width", function (d) {
return xScale(d.y);
});
Here's a JSFiddle with your solution: http://jsfiddle.net/ee2todev/z73u6mro/
I called the nested dataset arrayOfDatasets so the changes become more clear.
First you have to call stack() for each of the datasets:
arrayOfDatasets.forEach(function (dataset) {stack(dataset); });
Then you have to retrieve the max for xScale:
var xScale = d3.scale.linear()
.domain([0, d3.max(arrayOfDatasets, function (dataset) {
return d3.max(dataset, function (d) {
return d3.max(d, function (d) {
return d.y0 + d.y;
});
})
})])
.range([0, w]);
Finally, you just have to access the data properly and translate the datasets so they don't overlap:
var groupOfGroups = svg.selectAll("g.toplevel")
.data(arrayOfDatasets)
.enter()
.append("g")
.attr("class", "toplevel")
.attr("transform", function(d, i ) {return "translate(0, " + (-i * 55) + ")"; });
// Add a group for each row of data
var groups = groupOfGroups.selectAll("g.dataset")
.data(function(d) {return d; })
.enter()
.append("g")
.attr("class", "dataset")
.style("fill", function (d, i) {
return color(i);
});
You might still store your datasets differently since it's not clear why you have the x values in them.

Categories

Resources