I'm trying to create a number of interactive pie charts using dc.js to represent a basketball team's stats. One pie chart would have each player's points, another their salary, etc., represented as a portion of the team's total.
I don't really understand how to use the crossfilter .dimension() and .group() functions. And does reduceSum() really need to be in there? All I'm getting is an empty pie chart. Other examples I've seen have differenty structured data and don't apply to this situation.
Here is a bit of the relevant code for a salary pie chart:
var data = [
{"player":"boomhauer", "rebound_avg":12.1, "salary":4000, "point_avg":15},
{"player":"bill", "rebound_avg":4.2, "salary":3000, "point_avg":20},
{"player":"hank", "rebound_avg":12.1, "salary":4000, "point_avg":15},
{"player":"dale", "rebound_avg":16.1, "salary":6000, "point_avg":4},
];
var ndx = crossfilter(data);
var playerDim = ndx.dimension(function(d) {return d.player;});
var salaryPerPlayer = playerDim.group().reduceSum(function(d) {return d.salary;});
var salaryGroupPerPlayer = salaryPerPlayer.group(function(d) {return d.player; });
var salaryRingChart = dc.pieChart("#chart-ring-salary");
salaryRingChart
.width(200)
.height(200)
.slicesCap(17)
.innerRadius(10)
.dimension(salaryPerPlayer)
.group(salaryGroupPerPlayer)
.renderLabel(true);
dc.renderAll();
All I'm getting is an empty pie chart.
Thanks for your help!
I think just lose the salaryGroupPerPlayer:
var data = [
{"player":"boomhauer", "rebound_avg":12.1, "salary":4000, "point_avg":15},
{"player":"bill", "rebound_avg":4.2, "salary":3000, "point_avg":20},
{"player":"hank", "rebound_avg":12.1, "salary":4000, "point_avg":15},
{"player":"dale", "rebound_avg":16.1, "salary":6000, "point_avg":4},
];
var ndx = crossfilter(data);
var playerDim = ndx.dimension(function(d) {return d.player;});
var salaryPerPlayer = playerDim.group().reduceSum(function(d) {return d.salary;});
var salaryRingChart = dc.pieChart("#chart-ring-salary");
salaryRingChart
.width(200)
.height(200)
.slicesCap(17)
.innerRadius(10)
.dimension(salaryPerPlayer)
.group(salaryPerPlayer)
.renderLabel(true);
dc.renderAll();
Here's a good Crossfilter tutorial: http://blog.rusty.io/2012/09/17/crossfilter-tutorial/
The reduceSum is necessary because the default aggregation on a group in Crossfilter is a count. This would just count the number of records for each player, which is 1. So, not very interesting!
I would question why you are using Crossfilter at all though. The data is all pre-aggregated and you are only going to have 1 dimension (player). Filtering on these charts won't really make sense. Crossfilter is designed more for scenarios in which you have dis-aggregated data (maybe 1 record for each player in each game, o even play-by-play data) and you want to aggregate the data and filter dynamically on different dimensions.
Related
I've created a stacked area chart in Highcharts, which you can see in the image below and in the following jsfiddle: http://jsfiddle.net/m3dLtmoz/
I have a workaround for the gaps you see, which is to group the data for each series by month so that each series looks something like this instead:
series: [{
data: [
[1464739200000,2471],
[1467331200000,6275],
[1470009600000,2574],
[1472688000000,7221],
[1475280000000,3228]
]}
]
While the above isn't exactly what I'm going for, the way the series above is structured does give me what I ultimately want, which is this:
I'm really dying to know why the original setup isn't working appropriately, however. I've tested other instances where datetimes group and aggregate properly based on a single datetime x axis value. I'm stumped as to why this particular data set isn't working. I've tried using the dataGrouping option in the Highstock library, but wasn't able to integrate that effectively. I've messed with options as far as tickInterval goes to no avail. I tried setting the "stacking: 'normal' option in each series instead of in the plotOptions, but that made no difference. I've seen issues on github dealing with the stacked area charts, but nothing seems to exactly match up with what I'm seeing. Any help is appreciated - thank you much!
You receive the error in the console. Most of the series require data to be sorted in ascending order. Stacking has nothing do to it, see example.
Series which do not require data to be sorted are scatter or polygon. No error in scatter
You should sort and group the points on your own. If you want to group them by months you have to prepare the data before you put them in a chart. The example below takes averages from the same datetime.
function groupData(unsortedData) {
var data = unsortedData.slice();
data.sort(function (a, b) {
return a[0] - b[0]
});
var i = 1,
len = data.length,
den = 1,
sum = data[0][1],
groupedData = [[data[0][0], sum]],
groupedData = [];
for (; i < len; i++) {
if (data[i - 1][0] === data[i][0]) {
sum += data[i][1];
den++;
} else {
groupedData.push([data[i - 1][0], sum / den]);
den = 1;
sum = data[i][1];
}
}
groupedData.push([data[i-1][0], sum / den]);
return groupedData;
}
example: http://jsfiddle.net/e4enhw9a/1/
I am using crossfilter with several charts in combination with dc.js.
When filtering with ring charts, the data in the linechart disappears, but the x-axis remains unchanged and doesn't refresh.
var tempLineChartt1 = dc.lineChart("#chart-line-temp-t1");
tempLineChartt1
.width(768)
.height(480)
.elasticX(true)
.x(d3.time.scale().domain([dateDim.bottom(1)[0].dd,dateDim.top(1)[0].dd]))
.elasticX(true)
.dimension(dateDim)
.group(iotmPerDate)
.renderArea(true)
.brushOn(false)
.renderDataPoints(true)
.clipPadding(10)
.yAxisLabel("T1")
I know this has been answered a few times, but I couldn't find a reference to exactly this in a quick search.
Mostly likely you're referring to the fact that bins aren't automatically removed from crossfilter groups. So dc.js sees no reason to change the X domain - elasticX(true) will only kick in when the set of X keys changes, and here the Y values have only dropped to zero.
You can use a "fake group" to filter out these results dynamically:
function remove_empty_bins(source_group) {
return {
all:function () {
return source_group.all().filter(function(d) {
return d.value != 0;
});
}
};
}
var filtered_group = remove_empty_bins(group) // or filter_bins, or whatever
chart.dimension(dim)
.group(filtered_group)
https://github.com/dc-js/dc.js/wiki/FAQ#fake-groups
With this in place, each time the line chart is redrawn, the fake group will filter out the zeros as the data is read. Then the line chart will recalculate the domain and zoom to fit.
Mike Bostock uses the following snippet to generate uniformly-spaced bins for a histogram:
var data = d3.layout.histogram()
.bins(x.ticks(20))
(values);
source
Is there any way to adapt this to a project that uses dc.js and crossfilter.js?
Essentially, I want to dynamically generate the bins and then use the chart to filter on a particular attribute. Total newbie to this. Any guidance would be appreciated!
dc.js supports histograms via crossfilter. Use a group for your bar chart that looks something like this:
var binwidth = 0.2;
var dim = ndx.dimension(function(d) { return d.x; });
var group = dim.group(function(d) { return binwidth * Math.floor(d.x/binwidth); });
This tells crossfilter to use keys binwidth apart.
And initialize the bar chart with these units:
chart.xUnits(dc.units.fp.precision(binwidth));
I have summed up the dataset using nest,rollup and d3.sum functions and I'm able to display the pie chart properly.But I'm unable display percentage for each slice based on the summed total.Can anyone please give suggestions on this issue...
Mycode:
======
d3.csv("pi.csv", function(error, csv_data) {
var data = d3.nest()
.key(function(d) { return d.ip;})
.rollup(function(d) {
return d3.sum(d, function(g) {return g.value; });
}).entries(csv_data);
data.forEach(function(d) {
d.ip= d.key;
d.value = d.values;
});
My dataset:
=========
ip,timestamp,value
92.239.29.77,1412132430000,3190
92.239.29.77,1412142430000,319011
92.239.29.78,1412128830000,545568
92.239.29.78,1412130600000,616409
92.239.29.78,1412132430000,319087
92.239.29.76,1412130600000,616409
92.239.29.76,1412132430000,319087
Thanks in advance
So it seems that you want to display the percentage of each pie slice on the chart. All you really need to do is to calculate the the total of the values and iterate across the data variable adding in the percentage.
The data.forEach function doesn't add anything to the data variable, so I would drop that. I should also point out that the d3.nest function rolls up and sums each individual ip entry, so you're not getting sum of all values. However, this is pretty easy to do with d3, you can just use d3.sum:
var tots = d3.sum(data, function(d) {
return d.values;
});
Once you've done that you iterate across the data variable like:
data.forEach(function(d) {
d.percentage = d.values / tots;
});
You can then access the percentage on each pie slice using something like d.data.percentage
And putting it all together.
Note you could also compute the percentage from the start and end angles for each slice: (d.endAngle - d.startAngle)/(2*Math.PI)*100.
I'm trying to draw an area chart using dc.js, and the end date (i.e. far right) of the chart is based on the current date, not the last date in the dataset. In cases where there's a date gap between data points, I want the area to extend from one point to the next, not draw at 0.
Given this data:
var data = [
{domain: "foo.com", project: "pdp", repo: "myrepo", commit_date: "6/1/2014", lines_added: 100, lines_deleted: 50},
{domain: "foo.com", project: "pdp", repo: "myrepo", commit_date: "7/1/2014", lines_added: 100, lines_deleted: 50}
];
var ndx = crossfilter(data);
The chart's line/area currently ends at the "7/1/2014" data point, but I want it to stretch the entire length of the chart.
The relevant code for drawing the chart is:
var dateDim = ndx.dimension(function(d) {return d.commit_date;});
var minDate = dateDim.bottom(1)[0].commit_date;
var maxDate = new Date();
var domainGroup = dateDim.group().reduceSum(function(d) {return d.cumulative_lines;});
unshippedlineChart
.width(500).height(200)
.dimension(dateDim)
.group(domainGroup)
.renderArea(true)
.x(d3.time.scale().domain([minDate,maxDate]))
.brushOn(false)
.interpolate('step-after')
.yAxisLabel("Unshipped Value");
Full example is at http://jsfiddle.net/xayhkcvn/1/
You didn't actually ask a question :-), but I think you may be looking for ways to prefilter your data so that it gets extended to today, and to remove any zeros.
This stuff isn't built into dc.js, but there is some example code in the FAQ which may help. Specifically, there is a function remove_empty_bins which adapts a group to remove any zeros.
You could similarly define a function to add a final point (untested):
function duplicate_final_bin(source_group, key) {
return {
all:function () {
var ret = Array.prototype.slice.call(source_group.all()); // copy array
if(!ret.length) return ret;
ret.push({key: key, value: ret[ret.length-1].value});
return ret;
}
};
}
You can compose this with remove_empty_bins:
var super_group = duplicate_final_bin(remove_empty_bins(domainGroup), maxDate);
The idea is to create a wrapper object which dynamically adds or remove stuff from the (always changing) source_group.all() on demand. dc.js will call group.all() whenever it is redrawing, and these wrappers intercept that call and adapt the data the crossfilter group returns.