Reputation: 716
I'm looking for some pointers on how I can fix how my data is being visualized as a bar chart. I've created a small multiples visualization of data that appears over time. Because of the nature of the way the data was collected, the data shares certain attributes. By way of example, you can see an excerpt of the .csv below where group
and date
are repeated:
group,date,totals
acid,1641,8438
acid,1641,0
acid,1641,0
beef,1641,977.85
beef,1641,0
beef,1641,0
beef,1641,164
beef,1641,5.25
bird,1641,121
bird,1641,0
bird,1641,12
bird,1641,1558
bird,1641,729
bird,1641,1680
bird,1641,0
bird,1641,343
bird,1641,3
bird,1641,2092
bird,1641,0
bird,1641,0
bird,1641,107
bird,1641,2679
bird,1641,167
bird,1641,649
boar,1641,41
boar,1641,13
cheese,1641,13
cheese,1641,22
cheese,1641,1071
cheese,1641,17195
(You can see the entire dataset here.)
The problem I'm running in to is wherever a date
is shared, instead of summing the totals
column it stacks the data on top of one another. For example, see:
What I'm struggling with is having the data displayed as a single bar chart. (I have a secondary problem with the y-axis not scaling correctly). I've tried to use nest to fix the problem of the years (this worked for groups
) but I can't seem to get it with date
.
Here's my code, thus far:
var margin = {top: 20, right: 30, bottom: 30, left: 50},
width = 400 - margin.right - margin.left,
height = 150 - margin.top - margin.bottom,
parse = d3.time.format("%Y").parse;
// scales
var x = d3.time.scale().range([0, width]),
y = d3.scale.linear().range([0, height]),
yscaled = d3.scale.linear().range([height, 0]);
// load data
d3.csv("revised.csv", function(error, data) {
// nest values by group
var groups = d3.nest()
.key(function(d) { return d.group; })
.entries(data);
// parse dates and numbers, assume values are sorted by date
// compute the maximum totals per group
groups.forEach(function(s) {
s.values.forEach(function(d) { d.date = parse(d.date); d.totals = +d.totals; });
s.maxtotals = d3.max(s.values, function(d) { return d.totals; });
});
// compute the minimum and maximum date across groups
x.domain([
d3.min(groups, function(s) { return s.values[0].date; }),
d3.max(groups, function(s) { return s.values[s.values.length - 1].date; })
]);
// y.domain([0, d3.max(data, function(d) { return d.totals; })]);
// add an SVG element for each group
var svg = d3.select("body").selectAll("g")
.data(groups)
.enter().append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var canvas_g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.each(function(d) {
var g = d3.select(this);
g.append("g");
// .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
y.domain([0, d3.max(d.values, function(r) { return r.totals; })]);
yscaled.domain([0, d3.max(d.values, function(r) { return r.totals; })]);
// Draw the charts.
g.selectAll("rect.aevents")
.data(d.values)
.enter().append("rect")
.attr("height", function(p) {return y(p.totals)})
.attr("width", 5)
.attr("x", function(p, q) {return x(p.date)})
.attr("y", function(p) {return height - y(p.totals)})
// .on("click", function(p) {console.log(p.date)})
.attr("class", "bar");
});
// draw x axis
canvas_g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.svg.axis().scale(x).orient("bottom"));
// draw y axis
canvas_g.append("g")
.attr("class", "y axis")
.call(d3.svg.axis().scale(yscaled).orient("left"));
// add a small label for the group name
svg.append("text")
.attr("x", width + 40)
.attr("y", height + 15)
.attr("text-anchor", "end")
.text(function(d) { return d.key; });
});
Is there a different way I should loop through the data? Or, do I need to restructure the data somehow? Thank you in advance!
Upvotes: 1
Views: 4508
Reputation: 15335
First you can read this answer that will help you if you want to know more about d3.nest()
.
For your problem, a good method can be to first nest the elements using d3.nest()
and then to map the groups to their desired form. Which gives:
function formatData(inputData){
var array = d3.nest()
.key(function(d){return d.group})
.key(function(d){return d.date})
.entries(inputData)
.map(function(d){
var group = d.key
var values = d.values.map(function(dd){
var date = dd.key
var total = 0
dd.values.forEach(function(ddd){
total = total + ddd.totals
})
return {date:date, totals:total}
})
return {'group':group, 'values':values}
})
obj = {}
array.forEach(function(d){
obj[d.group] = d.values
})
return obj
}
with inputData
being the data loaded using d3.csv()
. This way you can get the best of nest()
and `map()``
If you want to draw the bar chart only for beef you can now do:
var beefData = formatData(myInput).beef
jsFiddle: http://jsfiddle.net/chrisJamesC/FJmMD/3/
Upvotes: 3