I am unable to render a dc.js stacked bar chart successfully and I receive a console error
unable to read property 'Total' of undefined
I am new to the library and suspect my group or reduce is not successfully specified.
How do I resolve this issue?
$scope.riskStatusByMonth = function(){
var data = [
{"Month":"Jan","High":12},{"Month":"Jan","Med":14},{"Month":"Jan","Low":2},{"Month":"Jan","Closed":8},
{"Month":"Feb","High":12},{"Month":"Feb","Med":14},{"Month":"Feb","Low":2},{"Month":"Feb","Closed":8},
{"Month":"Mar","High":12},{"Month":"Mar","Med":14},{"Month":"Mar","Low":2},{"Month":"Mar","Closed":8},
{"Month":"Apr","High":12},{"Month":"Apr","Med":14},{"Month":"Apr","Low":2},{"Month":"Apr","Closed":8},
{"Month":"May","High":12},{"Month":"May","Med":14},{"Month":"May","Low":2},{"Month":"May","Closed":8},
{"Month":"Jun","High":12},{"Month":"Jun","Med":14},{"Month":"Jun","Low":2},{"Month":"Jun","Closed":8},
{"Month":"Jul","High":12},{"Month":"Jul","Med":14},{"Month":"Jul","Low":2},{"Month":"Jul","Closed":8},
{"Month":"Aug","High":12},{"Month":"Aug","Med":14},{"Month":"Aug","Low":2},{"Month":"Aug","Closed":8},
{"Month":"Sep","High":12},{"Month":"Sep","Med":14},{"Month":"Sep","Low":2},{"Month":"Sep","Closed":8},
{"Month":"Oct","High":12},{"Month":"Oct","Med":14},{"Month":"Oct","Low":2},{"Month":"Oct","Closed":8},
{"Month":"Nov","High":12},{"Month":"Nov","Med":14},{"Month":"Nov","Low":2},{"Month":"Nov","Closed":8},
{"Month":"Dec","High":8},{"Month":"Dec","Med":6},{"Month":"Dec","Low":13},{"Month":"Dec","Closed":8},
]
data.forEach(function(x) {
x.Total = 0;
});
var ndx = crossfilter(data)
var xdim = ndx.dimension(function (d) {return d.Month;});
function root_function(dim,stack_name) {
return dim.group().reduce(
function(p, v) {
p[v[stack_name]] = (p[v[stack_name]] || 0) + v.High;
return p;},
function(p, v) {
p[v[stack_name]] = (p[v[stack_name]] || 0) + v.Med;
return p;},
function(p, v) {
p[v[stack_name]] = (p[v[stack_name]] || 0) + v.Low; <-------------------here is where error occurs
return p;},
function(p, v) {
p[v[stack_name]] = (p[v[stack_name]] || 0) + v.Closed;
return p;},
function() {
return {};
});}
var ydim = root_function(xdim,'Total')
function sel_stack(i) {
return function(d) {
return d.value[i];
};}
$scope.monthlyRiskStatus = dc.barChart("#risk-status-by-month");
$scope.monthlyRiskStatus
.x(d3.scaleLinear().domain(xdim))
.dimension(xdim)
.group(ydim, '1', sel_stack("Jan"))
.xUnits(dc.units.ordinal);
month = [null,'Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
for(var i = 2; i<=12; ++i)
$scope.monthlyRiskStatus.stack(ydim, ''+i, sel_stack(month[i]));
$scope.monthlyRiskStatus.render();
}
group.reduce() takes three arguments: add, remove, init.
You are passing 5.
Looks like it is trying to call the third one as the initializer, with no arguments, so therefore v
is undefined.
It looks like what you're really trying to do is group by month (X axis) and then stack by status or level. Here's one way to do that.
First, you're on the right track with a function that takes a stack name, but we'll want it to take all of the stack names:
function root_function(dim,stack_names) {
return dim.group().reduce(
function(p, v) {
stack_names.forEach(stack_name => { // 1
if(v[stack_name] !== undefined) // 2
p[stack_name] = (p[v[stack_name]] || 0) + v[stack_name] // 3
});
return p;},
function(p, v) {
stack_names.forEach(stack_name => { // 1
if(v[stack_name] !== undefined) // 2
p[stack_name] = (p[v[stack_name]] || 0) + v[stack_name] // 3
});
return p;},
function() {
return {};
});}
stack_name
from the field with the same name in the current bin.We'll define both levels
and months
arrays. levels
will be used for stacking and months
will be used for the ordinal X domain:
var levels = ['High', 'Med', 'Low', 'Closed']
var months = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];
When we define the group, we'll pass levels
to root_function()
:
var ygroup = root_function(xdim,levels)
I see you had some confusion between the English/math definition of "dimension" and the crossfilter dimension. Yes, in English "Y" would be a dimension, but in crossfilter and dc.js, "dimensions" are what you aggregate on, and groups are the aggregations that often go into Y. (Naming things is difficult.)
We'll use an ordinal scale (you had half ordinal half linear, which won't work):
$scope.monthlyRiskStatus
.x(d3.scaleOrdinal().domain(months))
.dimension(xdim)
.group(ygroup, levels[0], sel_stack(levels[0]))
.xUnits(dc.units.ordinal);
Passing the months to the domain of the ordinal scale tells dc.js to draw the bars in that order. (Warning: it's a little more complicated for line charts because you also have to sort the input data.)
Note we are stacking by level, not by month. Also here:
for(var i = 1; i<levels.length; ++i)
$scope.monthlyRiskStatus.stack(ygroup, levels[i], sel_stack(levels[i]));
Let's also add a legend, too, so we know what we're looking at:
.margins({left:75, top: 0, right: 0, bottom: 20})
.legend(dc.legend())