Control what elements get shown in dc.js datatable - d3.js

I'm stuck on a seamingly easy problem with dc.js and crossfilter.
I have a datatable on a dimension and it shows my data correctly. The problem is that I want to show the 'worst' data items but the datatable picks the 'best' items in the filters.
The following is a scetch of the current table code.
var alertnessDim = ndx.dimension(function(d) {return d.alertness1;});
dc.dataTable(".dc-data-table")
.dimension(alertness1)
.group(function (d) {
return d.DATE.getYear();
})
.columns([
function (d) {
return d.DATE;
},
function (d) {
return d['FLEET'];
},
function (d) {
return d.alertness1;
}
])
.sortBy(function (d) {
return d.alertness1;
})
.order(d3.ascending);
This connects to the crossfilter properly and it sorts the items in the correct order, but the 25 items it is showing are the ones with the highest alertness values, not the lowest.
Anyone have any ideas on how to solve this, preferbly without creating another dimension?

You are right to be confused here. You would think this would be a supported use case but it is not, as far as I can tell. The data table uses dimension.top so it is always going to take the highest values.
So I don't think there is a way around using a special dimension with opposite ordering/keys. For the other charts you could use group.order in order (heh) to get it to return the lowest values. But the data table doesn't use a group because it's not reducing its values.
It's confusing that the data table also has an order parameter which doesn't help here.
Hope that is acceptable. Otherwise I think you'd have to poke around in the code. Pull Requests always welcome! (preferably with tests)

One quick way to achieve descending sort on a specific column, is to sort by its negative value:
.sortBy(function(d){ return -d.ALARM_OCCURRENCE; }); // show highest alarm count first

Related

Using DC.js datatable to create a table with aggregated data by year

I've been trying to use dc.js and crossfilter to both build charts and tables from a certain dataset.
So far building charts works fine, but I want to use the datatable functionality to build a small html table to summarize the data as follows:
|Year|TotalEmployees|
|2015|555|
|2016|666|
|2017|777|
My dataset has around 20 000 rows, here's a sample of the data:
var data = [
{"Year":"2015","Category":"1","NbEmployee":"51"},
{"Year":"2015","Category":"2","NbEmployee":"31"},
{"Year":"2015","Category":"3","NbEmployee":"14"}
{"Year":"2016","Category":"1","NbEmployee":"51"},
{"Year":"2016","Category":"2","NbEmployee":"55"},
{"Year":"2016","Category":"3","NbEmployee":"65"},
{"Year":"2017","Category":"1","NbEmployee":"76"},
{"Year":"2017","Category":"2","NbEmployee":"98"},
];
So far this piece of code returns one row of result per row of data, and although it feels like it should be a simple manipulation, I can't figure out the right syntax to build a summarized table with one row per year:
var ndx = crossfilter(data);
var tableDim = ndx.dimension(function(d) {
return d.Year;
});
var datatable = dc.dataTable("#dc-data-table");
datatable
.dimension(tableDim)
.group(function(d) {
d.NbEmployee += d.NbEmployee;
return d.Year;
})
.columns([
function(d) {return d.Year;},
function(d) {return d.NbEmployee;},
]);
I've tried countless times to apply the
.group().reduceSum()
functions to the dimension into a variable and then passing it to the .group() parameter, but I always end up with a compilation error, I'm pretty clueless right now.
The SQL translation of what I'm looking for is this:
SELECT
Year,
NbEmp = SUM(NbEmploye)
FROM DB
GROUP BY
Year
ORDER BY
Year
Thanks in advance for your help!
The dataTable's group is not a group - yes, pretty confusing to use this method to mean something completely different from what it means in all the other charts. Here, it's a function, everywhere else it's a crossfilter object.
The dataTable is unique out of the dc.js charts in that it reads its data from the .dimension() object. This is because it displays the raw rows of data, rather than aggregated data, by default.
However, it can be used to display a group instead. This works because the only method it actually calls on the dimension is .top(), if you choose to display in descending order.
If you want to display in ascending order, you can use a fake group to produce an object which supports the .bottom() method.

click in datatable to filter other charts (dc.js)

I need to filter other charts when I click a row in the datatable.
I did
my_table.on('pretransition', function (table) {
table.selectAll('td.dc-table-column')
.on('click',function(d){
table.filter(d.key)
dc.redrawAll();
})
});
but nothing happens in the other charts.
Can you help me, please?
If the table dimension is a dimension...
The data that ordinarily populates a data table is the raw rows from the original data set, not key/value pairs.
So it is likely that d.key is undefined.
I'd advise you first to stick
console.log(d)
into your click handler to see what your data looks like, to make sure d.key is valid.
Second, remember that a chart filters through its dimension. So you will need to pass a value to table.filter() that is a valid key for your dimension, and then it will filter out all rows for which the key is different. This may not be just the one row that you chose.
Typically a table dimension is chosen for the way it orders the values for the rows. You might actually want to filter some other dimension. But hopefully this is enough to get you started.
But what if the the table dimension is a group?
The above technique will only work if your table takes a crossfilter dimension as its dimension. If, as in the fiddle you linked in the comments, you're using a group as your dimension, that object has no .filter() method, so the table.filter() method won't do anything.
If you only need to filter the one item that was clicked, you could just do
foodim.filter(d.key)
This has an effect but it's not that useful.
If you need the toggle functionality used in dc's ordinal charts, you'll need to simulate it. It's not all that complicated:
// global
var filterKeys = [];
// inside click event
if(filterKeys.indexOf(d.key)===-1)
filterKeys.push(d.key);
else
filterKeys = filterKeys.filter(k => k != d.key);
if(filterKeys.length === 0)
foodim.filter(null);
else
foodim.filterFunction(function(d) {
return filterKeys.indexOf(d) !== -1;
})
Example fiddle: https://jsfiddle.net/gordonwoodhull/kfmfkLj0/9/

Ordering chords based on some property of the source data

I have a chord diagram in d3 that looks something like this:
The color of each group is calculated base on a property of the source data (before being transformed to a matrix) and is looked up from the index of each group with something like this:
var groupPath = group.append("path")
.attr("id", function(d, i) { return "group" + i; })
.attr("d", arc)
.style("fill", function(d, i) { return MySourceData[i].inArea ? "blue" : "red"; });
Now what I'd like to do is sort the chords so that all the blues are together and all the reds are together, but there doesn't seem to be a simple way to do that. I know you provide a function to sortGroups (and sortSubGroups for that matter), but it seems the arguments passed to the function are only the values of the chords and I would need to have the indexes to be able to figure out if they should be grouped together or not.
Is there any simple way to sort the groups based on some criteria that is ultimately derived from the index?
It seems the best way to achieve this is to simply order my source data first before I transform it into a matrix. This works fine if the data isn't going to change (which, in my case it wasn't), but if you had data that might change and in changing might end up needing to be resorted, this would be a real pain since you'd need to reorder the matrix.

Clearing empty positions and ordering dc.js bar chart

I've used a fake group to fix my issue with plotting only top(n) values on a graph. Now I found an other issue that the bar chart shows a few empty positions for those data fields that doesn't come under 'ton(n)', I want to remove them and sort the bars in descending order of their values.
Here is the JSFiddle.
orgHigestBandwidthConsumed.dimension(dimByOrgName)
.group(fakeGroup)
.ordering(function (d) { return -d.value; })
I've tried chart.ordering() to order the bars which didn't work. Can somebody help me fixing this?
Since you are specifying the ordinal domain, dc.js will use that instead of calculating it using the existing values and the ordering you specify.
Simply remove the domain and specify the ordering:
.x(d3.scale.ordinal())
.ordering(function(kv) { return -kv.value; })
Fork of your fiddle: http://jsfiddle.net/0p78m8vr/6/

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