Creating multiple column dimension from csv - dc.js

I'm trying to make a pie chart using multiple columns from a csv file, I can produce a pie chart but it only use the last value d.gre. What am I doing wrong?
Code is below
d3.csv("elecVotes.csv", function (data) {
var ndx = crossfilter(data);
var partysDim = ndx.dimension(function(d) { return +d.rep, +d.dem, +d.lib, +d.ind, +d.gre;})
var partys = partysDim.group();
var pie = dc.pieChart('#chart-pie');
pie
.width(180)
.height(180)
.radius(80)
.dimension(partysDim)
.group(partys)
.renderLabel(true)
.innerRadius(10)
.transitionDuration(500)
.colorAccessor(function (d, i) { return d.value; });
dc.renderAll();
});
electVotes.csv
state, rep,dem,lib,ind,gre
Alabama,1314431,725704,44211,20276,9341
New York,5655,54444,65666,2355,12225
Texas,4355,543234,12321,12331,45644

Gordon's suggestion should work, but I would also urge you to consider getting the data into a more appropriate format up front. It will make your life easier in a lot of ways and when using a lot of different tools. Specifically, you would need to transform your file to having only three columns: State, Party, and Votes. If you are maintaining this data in Excel, do it there. Or you can run this on the file you load:
var newData = []
data.forEach(function(d) {
newData.push({state: d.state, party: 'rep', votes: d.rep})
newData.push({state: d.state, party: 'dem', votes: d.dem})
newData.push({state: d.state, party: 'lib', votes: d.lib})
newData.push({state: d.state, party: 'ind', votes: d.ind})
newData.push({state: d.state, party: 'gre', votes: d.gre})
})
data = newData
Then your Crossfilter dimension and group creation will look like this:
var ndx = crossfilter(data);
var partysDim = ndx.dimension(function(d) { return d.party;})
var partys = partysDim.group().reduceSum(function(d) { return d.votes;});
Again, this is another way of handling the same problem. I think there are big payoffs from organizing your data the right way up front so I usually recommend doing that work and then keeping the Crossfilter/dc.js code nice and simple.

There are a couple of problems here. On the JavaScript level, the comma "operator" just evaluates both sides of the comma and returns the second. So the value of
1,2,3,4,5
is just 5. So that explains the result you're currently getting.
The other problem is more conceptual. Crossfilter is not designed to display each column in a group. I think the main reason is that there would be no way to filter by political party, since each region or state has values for every party. You would need data at the voter level to filter by party.
Given that restriction, you definitely won't be able to filter by clicking on the pie chart. However, with a little work, you can have the pie chart display the column sums as bins of a group.
As Ethan says, maybe it's better to transform your data, but this answer shows how to produce a group which rotates the data on the fly.

Related

Unable to filter individual stacks using dc.js with multiple X keys

Stacked Bar chart not able to filter on click of any Stack
I need to filter all the charts when clicking on any stack, which is not happening and struggling for a few days.
I've created a fiddle with link
http://jsfiddle.net/praveenNbd/09t5fd7v/13/
I feel am messing up with keys creation as suggested by gordonwoodhull.
function stack_second(group) {
return {
all: function () {
var all = group.all(),
m = {};
// build matrix from multikey/value pairs
all.forEach(function (kv) {
var ks = kv.key;
m[ks] = kv.value;
});
// then produce multivalue key/value pairs
return Object.keys(m).map(function (k) {
return {
key: k,
value: m[k]
};
});
}
};
}
I tried to follow this example https://dc-js.github.io/dc.js/examples/filter-stacks.html
Not able to figure out how below code works:
barChart.on('pretransition', function (chart) {
chart.selectAll('rect.bar')
.classed('stack-deselected', function (d) {
// display stack faded if the chart has filters AND
// the current stack is not one of them
var key = multikey(d.x, d.layer);
//var key = [d.x, d.layer];
return chart.filter() && chart.filters().indexOf(key) === -1;
})
.on('click', function (d) {
chart.filter(multikey(d.x, d.layer));
dc.redrawAll();
});
});
Can someone please point me out in the right direction.
Thanks for stopping by.
You usually don't want to use multiple keys for the X axis unless you have a really, really good reason. It is just going to make things difficult
Here, the filter-stacks example is already using multiple keys, and your data also has multiple keys. If you want to use your data with this example, I would suggest crunching together the two keys, since it looks like you are really using the two together as an ordinal key. We'll see one way to do that below.
You were also trying to combine two different techniques for stacking the bars, stack_second() and your own custom reducer. I don't think your custom reducer will be compatible with filtering by stacks, so I will drop it in this answer.
You'll have to use the multikey() function, and crunch together your two X keys:
dim = ndx.dimension(function (d) {
return multikey(d[0] + ',' + d[1], d[2]);
});
Messy, as this will create keys that look like 0,0xRejected... not so human-readable, but the filter-stacks hack relies on being able to split the key into two parts and this will let it do that.
I didn't see any good reason to use a custom reduction for the row chart, so I just used reduceCount:
var barGrp = barDim.group();
I found a couple of new problems when working on this.
First, your data doesn't have every stack for every X value. So I added a parameter to stack_second() include all the "needed" stacks:
function stack_second(group, needed) {
return {
all: function() {
var all = group.all(),
m = {};
// build matrix from multikey/value pairs
all.forEach(function(kv) {
var ks = splitkey(kv.key);
m[ks[0]] = m[ks[0]] || Object.fromEntries(needed.map(n => [n,0]));
m[ks[0]][ks[1]] = kv.value;
});
// then produce multivalue key/value pairs
return Object.entries(m).map(([key,value]) => ({key,value}));
}
};
}
Probably the example should incorporate this change, although the data it uses doesn't need it.
Second, I found that the ordinal X scale was interfering, because there is no way to disable the selection greying behavior for bar charts with ordinal scales. (Maybe .brushOn(false) is completely ignored? I'm not sure.)
I fixed it in the pretransition handler by explicitly removing the built-in deselected class, so that our custom click handler and stack-deselected class can do their work:
chart.selectAll('rect.bar')
.classed('deselected', false)
All in all, I think this is way too complicated and I would advise not to use multiple keys for the X axis. But, as always, there is a way to make it work.
Here is a working fork of your fiddle.

dc.js Incorporate regression chart into existing scatterplot with crossfilter

I am using dc.js and crossfilter.js to create a d3 dashboard, and am wondering how to implement a regression line into a scatterplot chart that responds to filtering.
I have been playing with a few examples re adding a regression line, but I have been unsuccessful extracting and incorporating the code.
I don't have a problem with the math, but rather with how to access the filtered data from the dimension, and then how to add the regression line to to the filtered scatterplot chart (so that the regression line also responds to future filtering).
jsFiddle Demo
var data = [
{"record":"record","date":"date","cars":"cars","bikes":"bikes"},
{"record":"1","date":"01/05/2012","cars":"1488.1","bikes":"49.73"},
{"record":"2","date":"02/05/2012","cars":"1374.29","bikes":"52.44"},
{"record":"3","date":"03/05/2012","cars":"1353.01","bikes":"47.92"},
{"record":"4","date":"04/05/2012","cars":"1420.33","bikes":"50.69"},
{"record":"5","date":"05/05/2012","cars":"1544.11","bikes":"47.47"},
{"record":"6","date":"06/05/2012","cars":"1292.84","bikes":"47.75"},
{"record":"7","date":"07/05/2012","cars":"1318.9","bikes":"48.64"},
{"record":"8","date":"08/05/2012","cars":"1686.3","bikes":"50.9"},
{"record":"9","date":"09/05/2012","cars":"1603.99","bikes":"53.44"},
{"record":"10","date":"10/05/2012","cars":"1420.1","bikes":"53.29"},
{"record":"11","date":"11/05/2012","cars":"1410.8","bikes":"54.06"},
{"record":"12","date":"12/05/2012","cars":"1374.62","bikes":"51.24"},
{"record":"13","date":"13/05/2012","cars":"1279.53","bikes":"53.96"},
{"record":"14","date":"14/05/2012","cars":"1330.47","bikes":"49.5"},
{"record":"15","date":"15/05/2012","cars":"1377.61","bikes":"52.32"},
{"record":"16","date":"16/05/2012","cars":"1302.12","bikes":"51.96"},
{"record":"17","date":"17/05/2012","cars":"1326.9","bikes":"49.86"},
{"record":"18","date":"18/05/2012","cars":"1181.55","bikes":"50.25"},
{"record":"19","date":"19/05/2012","cars":"1493.75","bikes":"51.24"},
{"record":"20","date":"20/05/2012","cars":"1463.9","bikes":"50.88"},
{"record":"21","date":"21/05/2012","cars":"1370.16","bikes":"51.09"},
{"record":"22","date":"22/05/2012","cars":"1403.3","bikes":"51.67"},
{"record":"23","date":"23/05/2012","cars":"1277.65","bikes":"49.3"},
{"record":"24","date":"24/05/2012","cars":"1361.94","bikes":"50.47"},
{"record":"25","date":"25/05/2012","cars":"1400.8","bikes":"51.55"},
{"record":"26","date":"26/05/2012","cars":"1289.09","bikes":"47.17"},
{"record":"27","date":"27/05/2012","cars":"1258.39","bikes":"52.12"},
{"record":"28","date":"28/05/2012","cars":"1288.71","bikes":"49.28"},
{"record":"29","date":"29/05/2012","cars":"1511.86","bikes":"50.73"},
{"record":"30","date":"30/05/2012","cars":"1300.38","bikes":"52.39"},
{"record":"31","date":"31/05/2012","cars":"1455.19","bikes":"49.53"},
{"record":"32","date":"01/06/2012","cars":"1311.89","bikes":"50.37"},
{"record":"33","date":"02/06/2012","cars":"1368.64","bikes":"50.87"},
{"record":"34","date":"03/06/2012","cars":"1360.05","bikes":"50.51"},
{"record":"35","date":"04/06/2012","cars":"1382.56","bikes":"49.67"},
{"record":"36","date":"05/06/2012","cars":"1304.15","bikes":"47.6"},
{"record":"37","date":"06/06/2012","cars":"1271.57","bikes":"50.22"},
{"record":"38","date":"07/06/2012","cars":"1442.38","bikes":"50.8"},
{"record":"39","date":"08/06/2012","cars":"1406.38","bikes":"53.14"},
{"record":"40","date":"09/06/2012","cars":"1724.16","bikes":"49.66"},
{"record":"41","date":"10/06/2012","cars":"1931.05","bikes":"53"},
{"record":"42","date":"11/06/2012","cars":"1669.47","bikes":"53.71"},
{"record":"43","date":"12/06/2012","cars":"1794.06","bikes":"51.78"},
{"record":"44","date":"13/06/2012","cars":"1625.98","bikes":"51.58"},
{"record":"45","date":"14/06/2012","cars":"1371.51","bikes":"52.36"},
{"record":"46","date":"15/06/2012","cars":"1418.05","bikes":"47.64"},
{"record":"47","date":"16/06/2012","cars":"1431","bikes":"53.14"},
{"record":"48","date":"17/06/2012","cars":"1527.21","bikes":"48.63"},
{"record":"49","date":"18/06/2012","cars":"1320.95","bikes":"51.7"},
{"record":"50","date":"19/06/2012","cars":"1396.93","bikes":"52.92"}
];
tSel1 = "cars";
tSel2 = "bikes";
data.forEach(function (d) {
d[tSel1] = +d[tSel1];
d[tSel2] = +d[tSel2];
});
var facts = crossfilter(data);
var allDimension = facts.groupAll();
var scatterDimension = facts.dimension(function(d) {return [+d[tSel1], +d[tSel2]];});
var scatterGroup = scatterDimension.group().reduceSum(function(d) { return d[tSel1]; });
var maxY1 = d3.max(data, function(d) {return d[tSel1]});
var maxY2 = d3.max(data, function(d) {return d[tSel2]});
var maxY1Plus = maxY1 + (maxY1 * 0.1);
var maxY2Plus = maxY2 + (maxY2 * 0.1);
var minY1 = d3.min(data, function(d) {return d[tSel1]});
var minY1Minus = minY1 * 0.9;
var minY2 = d3.min(data, function(d) {return d[tSel2]});
var minY2Minus = minY2 * 0.9;
xyScatterChart = dc.scatterPlot("#scatterPlot");
xyScatterChart
.width(600)
.height(400)
.margins({top: 20, right: 20, bottom: 20, left: 60})
.dimension(scatterDimension)
.group(scatterGroup)
.symbolSize(6)
.highlightedSize(15)
.brushOn(false)
.excludedOpacity(0.5)
.excludedSize(5)
.renderHorizontalGridLines(true)
.renderVerticalGridLines(true)
.x(d3.scale.linear().domain([minY1Minus,maxY1Plus]))
.y(d3.scale.linear().domain([minY2Minus,maxY2Plus]));
dc.renderAll();
dc.redrawAll();
<link href="http://dc-js.github.io/dc.js/css/dc.css" rel="stylesheet"/>
<script src="http://dc-js.github.io/dc.js/js/d3.js"></script>
<script src="http://dc-js.github.io/dc.js/js/crossfilter.js"></script>
<script src="http://dc-js.github.io/dc.js/js/dc.js"></script>
<div id="scatterPlot"></div>
References:
https://groups.google.com/forum/#!topic/dc-js-user-group/HaQMegKa_U0
https://bl.ocks.org/ctufts/298bfe4b11989960eeeecc9394e9f118
It would be awesome to include an example in dc.js, since this is something lots of people can use.
Maybe we can work together on that? I don't know the math but here's a simple way to use a composite chart to display a line on data calculated from an aggregated group.
First off, here's the composite chart with the old scatter plot embedded in it:
var composite = dc.compositeChart("#composite");
composite
.width(600)
.height(400)
.margins({top: 20, right: 20, bottom: 20, left: 60})
.dimension(scatterDimension)
.group(scatterGroup)
.compose([
dc.scatterPlot(composite)
.symbolSize(6)
.highlightedSize(15)
.brushOn(false)
.excludedOpacity(0.5)
.excludedSize(5)
.renderHorizontalGridLines(true)
.renderVerticalGridLines(true),
dc.lineChart(composite)
.group(regressionGroup(scatterGroup))
])
.x(d3.scale.linear().domain([minY1Minus,maxY1Plus]))
.y(d3.scale.linear().domain([minY2Minus,maxY2Plus]));
Note that we're supplying the scatter group to both the composite and the scatter plot. That's just because the composite chart requires a group even though it doesn't actually use it.
We've moved the parameters that have to do with coordinates to the main (composite) chart, but everything that is specific to the scatter plot stays on it. We've also added a line chart to the composite, which uses a "fake group" based on the scatter group.
This fake group is particularly fake, but it should be enough to get you started. Since I don't have time to learn the math today, I'll just pretend that the first and last points are the regression:
function regressionGroup(group) {
return {
all: function() {
var _all = group.all();
var first, last;
for(var i=0; i < _all.length; ++i) {
var key = _all[i].key;
if(!isNaN(key[0]) && !isNaN(key[1])) {
var kv = {key: key[0], value: key[1]};
if(!first)
first = kv;
last = kv;
}
}
return [first, last];
}
};
}
As with all fake groups, the idea is to calculate some group-like data when the chart asks for it (and no sooner), based on another group. Here the calculation is not very interesting, because you know how to calculate a regression and I don't. You'll want to replace first and last and the for loop with a real calculation; all this is doing is checking for valid points and keeping the first and last ones that it finds.
Interestingly, the scatter plot takes data where the key contains both x and y coordinates, but the line chart takes data where the key is x and the value is y. That's why we have the transformation kv = {key: key[0], value: key[1]}
Postscript
Note that you'll run into a dc.js bug if you put the regression guide points outside of the domain - the stack mixin is too aggressive about clipping points to the domain. There is an easy, ugly workaround that seems to work in this case: tell the line chart it has an ordinal x scale even though it doesn't:
var composite = dc.compositeChart("#composite"),
lineChart;
composite
.width(600)
// ...
.compose([
// ...
lineChart = dc.lineChart(composite)
.group(regressionGroup(scatterGroup))
])
lineChart.isOrdinal = d3.functor(true);
Yuck! But it works! This hack probably only works inside a composite!
https://jsfiddle.net/gordonwoodhull/5tpcxov1/12/
I have a fully functional example of regression. I was precisely doing it when I came here for help and I found your question. It requires regression.js (here).
This follows Gordon's excellent suggestion of a "fake group", which should really be called an inline group, or immediate group, or even group on-the-fly. Here is mine:
function myRegressionGroup(group, min, max, filter = false) {
return {
all: function() {
var _all = group.all();
var first, last;
if(filter) reg = regression.linear(_all.filter(function(k,v) {if(k.key[0]) return k.key}).map((k,v) => k.key));
else reg = regression.linear(_all.map((k,v) => k.key));
first = reg.predict(min);
last = reg.predict(max)
return [{key:first[0], value: first[1]}, {key: last[0], value: last[1]}]
}
};
}
Please notice that this function requires a crossfilter group and also the min and max from the x-scale. Since you typically have these values calculated for your xScale, all it takes is reusing them here. This is because the function uses the extremes with the predict method to calculate the two points of the regression line.
The optional filter data wrangler is for you to decide whether to remove empty values on x or not.
#Gordon, how should I do in order to include my regression example in the Examples of using dc.js?

dc.js Composite Graph - Plot New Line for Each Person

Good Evening Everyone,
I'm trying to take the data from a database full of hour reports (name, timestamp, hours worked, etc.) and create a plot using dc.js to visualize the data. I would like the timestamp to be on the x-axis, the sum of hours for the particular timestamp on the y-axis, and a new bar graph for each unique name all on the same chart.
It appears based on my objectives that using crossfilter.js the timestamp should be my 'dimension' and then the sum of hours should be my 'group'.
Question 1, how would I then use the dimension and group to further split the data based on the person's name and then create a bar graph to add to my composite graph? I would like for the crossfilter.js functionality to remain intact so that if I add a date range tool or some other user controllable filter, everything updates accordingly.
Question 2, my timestamps are in MySQL datetime format: YYYY-mm-dd HH:MM:SS so how would I go about dropping precision? For instance, if I want to combine all entries from the same day into one entry (day precision) or combine all entries in one month into a single entry (month precision).
Thanks in advance!
---- Added on 2017/01/28 16:06
To further clarify, I'm referencing the Crossfilter & DC APIs alongside the DC NASDAQ and Composite examples. The Composite example has shown me how to place multiple line/bar charts on a single graph. On the composite chart I've created, each of the bar charts I've added a dimension based off of the timestamps in the data-set. Now I'm trying to figure out how to define the groups for each. I want each bar chart to represent the total time worked per timestamp.
For example, I have five people in my database, so I want there to be five bar charts within the single composite chart. Today all five submitted reports saying they worked 8 hours, so now all five bar charts should show a mark at 01/28/2017 on the x-axis and 8 hours on the y-axis.
var parseDate = d3.time.format('%Y-%m-%d %H:%M:%S').parse;
data.forEach(function(d) {
d.timestamp = parseDate(d.timestamp);
});
var ndx = crossfilter(data);
var writtenDimension = ndx.dimension(function(d) {
return d.timestamp;
});
var hoursSumGroup = writtenDimension.group().reduceSum(function(d) {
return d.time_total;
});
var minDate = parseDate('2017-01-01 00:00:00');
var maxDate = parseDate('2017-01-31 23:59:59');
var mybarChart = dc.compositeChart("#my_chart");
mybarChart
.width(window.innerWidth)
.height(480)
.x(d3.time.scale().domain([minDate,maxDate]))
.brushOn(false)
.clipPadding(10)
.yAxisLabel("This is the Y Axis!")
.compose([
dc.barChart(mybarChart)
.dimension(writtenDimension)
.colors('red')
.group(hoursSumGroup, "Top Line")
]);
So based on what I have right now and the example I've provided, in the compose section I should have 5 charts because there are 5 people (obviously this needs to be dynamic in the end) and each of those charts should only show the timestamp: total_time data for that person.
At this point I don't know how to further breakup the group hoursSumGroup based on each person and this is where my Question #1 comes in and I need help figuring out.
Question #2 above is that I want to make sure that the code is both dynamic (more people can be handled without code change), when minDate and maxDate are later tied to user input fields, the charts update automatically (I assume through adjusting the dimension variable in some way), and if I add a names filter that if I unselect names that the chart will update by removing the data for that person.
A Question #3 that I'm now realizing I'll want to figure out is how to get the person's name to show up in the pointer tooltip (the title) along with timestamp and total_time values.
There are a number of ways to go about this, but I think the easiest thing to do is to create a custom reduction which reduces each person into a sub-bin.
First off, addressing question #2, you'll want to set up your dimension based on the time interval you're interested in. For instance, if you're looking at days:
var writtenDimension = ndx.dimension(function(d) {
return d3.time.hour(d.timestamp);
});
chart.xUnits(d3.time.hours);
This will cause each timestamp to be rounded down to the nearest hour, and tell the chart to calculate the bar width accordingly.
Next, here's a custom reduction (from the FAQ) which will create an object for each reduced value, with values for each person's name:
var hoursSumGroup = writtenDimension.group().reduce(
function(p, v) { // add
p[v.name] = (p[v.name] || 0) + d.time_total;
return p;
},
function(p, v) { // remove
p[v.name] -= d.time_total;
return p;
},
function() { // init
return {};
});
I did not go with the series example I mentioned in the comments, because I think composite keys can be difficult to deal with. That's another option, and I'll expand my answer if that's necessary.
Next, we can feed the composite line charts with value accessors that can fetch the value by name.
Assume we have an array names.
compositeChart.shareTitle(false);
compositeChart.compose(
names.map(function(name) {
return dc.lineChart(compositeChart)
.dimension(writtenDimension)
.colors('red')
.group(hoursSumGroup)
.valueAccessor(function(kv) {
return kv.value[name];
})
.title(function(kv) {
return name + ' ' + kv.key + ': ' + kv.value;
});
}));
Again, it wouldn't make sense to use bar charts here, because they would obscure each other.
If you filter a name elsewhere, it will cause the line for the name to drop to zero. Having the line disappear entirely would probably not be so simple.
The above shareTitle(false) ensures that the child charts will draw their own titles; the title functions just add the current name to those titles (which would usually just be key:value).

How to use Reductio's Exception Aggregation function on multiple columns of data?

I'm working on a data visualisation project, and I'm using the dc.js library. After converting the data into a flat data structure (for cross filter), the data looks like this:
[{"date":"2015-01-01","region":1,"cancer":10,"diabetes":5,"aqi_index":66,"pm2_5":20,"pm10":35},{"date":"2015-01-01","region":2,"cancer":30,"diabetes":25,"aqi_index":66,"pm2_5":20,"pm10":35}]
I have a line chart to display the air pollution data and a stacked area chart to display the medical records.
This is where I face a problem. As you can see, my table contains two rows for each date, one for each region. The medical data differs according to region; However, the air pollution data is the same across both regions for the same date. As I use the following code to obtain the plot of the air pollutant reading against time:
var ndx = crossfilter(data);
var dateDim = ndx.dimension(function(d) {return d["date"];});
var aqi = dateDim.group().reduceSum(function(d) {return d["aqi_index"];});
var pm2_5 = dateDim.group().reduceSum(function(d) {return d["pm2_5"];});
var pm10 = dateDim.group().reduceSum(function(d) {return d["pm10"];});
My chart for the air pollution data becomes inaccurate as I display twice the amount of pollutants for each date. How can I display the unique value of each pollutant reading (AQI Index, PM 2.5, PM 10) for each date using Reductio's Exception Aggregation function? Will doing so affect my chart for medical data?
I think this should work:
var ndx = crossfilter(data);
var dateDim = ndx.dimension(function(d) {return d["date"];});
var dateGroup = dateDim.group()
var reducer = reductio()
// Value allows multiple aggregations on the same group.
// Here aggregate all values on the "cancer" property for a date.
reducer.value("cancer").sum("cancer")
// Here aggregate only the first value of the "aqi_index" property for a date to
// avoid double-counting
reducer.value("aqi_index").exception("date").exceptionSum("aqi_index")
reducer(dateGroup)
Add as many value aggregations as you want to aggregate all of your measures on the group. Let me know if you get an error.
Example JSFiddle (see the console for results): https://jsfiddle.net/esjewett/5onebhsd/1/

How to show "missing" rows in a rowChart using crossfilter and dc.js?

I'm using code similar to that in the dc.js annotated example:
var ndx = crossfilter(data);
...
var dayName=["0.Sun","1.Mon","2.Tue","3.Wed","4.Thu","5.Fri","6.Sat"];
var dayOfWeek = ndx.dimension(function (d) {
var day = d.dd.getDay();
return dayName[day];
});
var dayOfWeekGroup = dayOfWeek.group();
var dayOfWeekChart = dc.rowChart("#day-of-week-chart");
dayOfWeekChart.width(180)
.height(180)
.group(dayOfWeekGroup)
.label(function(d){return d.key.substr(2);})
.dimension(dayOfWeek);
The issue I've got is that only days of the week present in the data are displayed in my rowChart, and there's no guarantee every day will be represented in all of my data sets.
This is desirable behaviour for many types of categories, but it's a bit disconcerting to omit them for short and well-known lists like day and month names and I'd rather an empty row was included instead.
For a barChart, I can use .xUnits(dc.units.ordinal) and something like .x(d3.scale.ordinal.domain(dayName)).
Is there some way to do the same thing for a rowChart so that all days of the week are displayed, whether present in data or not?
From my understanding of the crossfilter library, I need to do this at the chart level, and the dimension is OK as is. I've been digging around in the dc.js 1.6.0 api reference, and the d3 scales documentation but haven't had any luck finding what I'm looking for.
Solution
Based on #Gordon's answer, I've added the following function:
function ordinal_groups(keys, group) {
return {
all: function () {
var values = {};
group.all().forEach(function(d, i) {
values[d.key] = d.value;
});
var g = [];
keys.forEach(function(key) {
g.push({key: key,
value: values[key] || 0});
});
return g;
}
};
}
Calling this as follows will fill in any missing rows with 0s:
.group(ordinal_groups(dayNames, dayOfWeekGroup))
Actually, I think you are better off making sure that the groups exist before passing them off to dc.js.
One way to do this is the "fake group" pattern described here:
https://github.com/dc-js/dc.js/wiki/FAQ#filter-the-data-before-its-charted
This way you can make sure the extra entries are created every time the data changes.
Are you saying that you tried adding the extra entries to the ordinal domain and they still weren't represented in the row chart, whereas this did work for bar charts? That sounds like a bug to me. Specifically, it looks like support for ordinal domains needs to be added to the row chart.

Resources