I have a map display and would like to total all electoral votes of the states that have been selected and display it in a number display. I was given the advice in a previous thread to wrap my group with an object with a top method. I'm not sure how to do this, so far I have:
var stateDim4 = ndx.dimension(function(d) { return d.state; }),
group = stateDim4.group().reduceSum(function(d) {return d.elecVote }),
count = group.top(51);
count[0].key;
count[0].value;
Please could someone help me on how to do this?
Previous Thread: Summing a column and displaying percentage in a number chart
Building on your previous example, here is a fake group with a top method that sums up all the electoral votes for the selected states.
var fakeGroup = {
top: function() {
return [
grp2.top(Infinity).reduce(function(a,b) {
if(b.value > 0) {
a.value += elecVotesMap.get(b.key)
}
return a
}, {
key: "",
value: 0
})
]
}
}
percentElec
.group(fakeGroup)
.formatNumber(d3.format("d"))
Here is a working JSFiddle: https://jsfiddle.net/esjewett/u9dq33v2/2/
There are a bunch of other fake group examples in the dc.js FAQ, but I don't think any of them do exactly what you want here.
Related
I'm trying to create a bubble chart with dc.js that will have a bubble for each data row and will be filtered by other charts on the same page. The initial bubble chart is created correctly, but when items are filtered from another chart and added or removed from the group it looks like they are being applied to the wrong group. I'm not sure what I'm messing up on the grouping or dimensions. I've created an example fiddle here
There's simple pie chart to filter on filterColumn, a bubble chart that uses identifer1, a unique field, as the dimension and xVal, yVal, and rVal to display the data, and a dataTable to display the current records.
I've tried other custom groups functions, but switched to the example from the FAQ and still had problems.
var
filterPieChart=dc.pieChart("#filterPieChart"),
bubbleChart = dc.bubbleChart('#bubbleChart'),
dataTable = dc.dataTable('#data-table');
var
bubbleChartDim=ndx.dimension(dc.pluck("identifier1")),
filterPieChartDim=ndx.dimension(dc.pluck("filterColumn")),
allDim = ndx.dimension(function(d) {return d;});
var filterPieChartGroup=filterPieChartDim.group().reduceCount();
function reduceFieldsAdd(fields) {
return function(p, v) {
fields.forEach(function(f) {
p[f] += 1*v[f];
});
return p;
};
}
function reduceFieldsRemove(fields) {
return function(p, v) {
fields.forEach(function(f) {
p[f] -= 1*v[f];
});
return p;
};
}
function reduceFieldsInitial(fields) {
return function() {
var ret = {};
fields.forEach(function(f) {
ret[f] = 0;
});
return ret;
};
}
var fieldsToReduce=['xVal', 'yVal', 'rVal'];
var bubbleChartGroup = bubbleChartDim.group().reduce(
reduceFieldsAdd(fieldsToReduce),
reduceFieldsRemove(fieldsToReduce),
reduceFieldsInitial(fieldsToReduce)
);
filterPieChart
.dimension(filterPieChartDim)
.group(filterPieChartGroup)
...
;
bubbleChart
.dimension(bubbleChartDim)
.group(bubbleChartGroup)
.keyAccessor(function (p) { return p.value.xVal; })
.valueAccessor(function (p) { return p.value.yVal; })
.radiusValueAccessor(function (p) { return p.value.rVal; })
...
;
This was a frustrating one to debug. Your groups and reductions are fine, and that's the best way to plot one bubble for each row, using a unique identifier like that.
[It's annoying that you have to specify a complicated reduction, when the values will be either the original value or 0, but the alternatives aren't much better.]
The reductions are going crazy. Definitely not just original values and zero, some are going to other values, bigger or negative, and sometimes clicking a pie slice twice does not even return to the original state.
I put breakpoints in the reduce functions and noticed, as you did, that the values were being removed from the wrong groups. How could this be? Finally, by logging bubbleChartGroup.all() in a filtered handler for the pie chart, I noticed that the groups were out of order after the first rendering!
Your code is fine. But you've unearthed a new bug in dc.js, which I filed here.
In order to implement the sortBubbleSize feature, we sort the bubbles. Unfortunately we are also sorting crossfilter's internal array of groups, which it trusted us with. (group.all() returns an internal data structure which must never be modified.)
The fix will be easy; we just need to copy the array before sorting it. You can test it out in your code by commenting out sortBubbleSize and instead supplying the data function, which is what it does internally:
bubbleChart.data(function (group) {
var data = group.all().slice(0);
if (true) { // (_sortBubbleSize) {
// sort descending so smaller bubbles are on top
var radiusAccessor = bubbleChart.radiusValueAccessor();
data.sort(function (a, b) { return d3.descending(radiusAccessor(a), radiusAccessor(b)); });
}
return data;
});
Notice the .slice(0) at the top.
Hope to fix this in the next release, but this workaround is pretty solid in case it takes longer.
Here is a fiddle demonstrating the workaround.
I have line chart where I need to show frequency of order executions over the course of a day. These orders are grouped by time interval, for example every hour, using custom reduce functions. There could be an hour interval when there were no order executions, but I need to show that as a zero point on the line. I create a 'fake group' containing all the bins with a zero count...and the initial load of the page is correct.
However the line chart is one of 11 charts on the page, and needs to be updated when filters are applied to other charts. When I filter on another chart, the effects on this particular frequency line chart are incorrect. The dimension and the 'fake group' are used for the dc.chart.
I put console.log messages in the reduceRemove function and can see that there is something wrong...but not sure why.
Any thoughts on where I could be going wrong.
FrequencyVsTimeDimension = crossfilterData.dimension(function (d) { return d.execution_datetime; });
FrequencyVsTimeGroup = FrequencyVsTimeDimension.group(n_seconds_interval(interval));
FrequencyVsTimeGroup.reduce(
function (p, d) { //reduceAdd
if (d.execution_datetime in p.order_list) {
p.order_list[d.execution_datetime] += 1;
}
else {
p.order_list[d.execution_datetime] = 1;
if (d.execution_type !== FILL) p.order_count++;
}
return p;
},
function (p, d) { //reduceRemove
if (d.execution_type !== FILL) p.order_count--;
p.order_list[d.execution_datetime]--;
if (p.order_list[d.execution_datetime] === 0) {
delete p.order_list[d.execution_datetime];
}
return p;
},
function () { //reduceInitial
return { order_list: {}, order_count: 0 };
}
);
var FrequencyVsTimeFakeGroup = ensure_group_bins(FrequencyVsTimeGroup, interval); // function that returns bins for all the intervals, even those without data.
I have a data table made with dc.js that groups data on month and shows daily data from various suppliers. I would like to include a monthly total within the table, in the row of the month group.
Is this possible? Here's the definition of my table:
aTable
.dimension(dailyDimension)
.group(function (d) {
return formatDateMonth(parseDate(d.date));
})
.size(Infinity)
.columns([{
label : 'Date',
format : function(d) {
return formatDateDay(parseDate(d.date))
}
}, {
label : 'Supplier',
format : function (d) {
return d.Supplier;
}
}, {
label : 'Volume',
format : function (d) {
return +d.total;
}
}])
And the fiddle: https://jsfiddle.net/urz6rjf8/
It's not something that's built into dc.js, but you can add it with a postprocessing step.
Here's one way:
var colspan = null;
aTable
.on('pretransition', function (table) {
var grouprow = table.selectAll('tr.dc-table-group').classed('info', true);
// fetch previous colspan only once
if(colspan === null) {
colspan = +grouprow.select('td').attr('colspan') - 1;
}
// reduce colspan of group label by 1
grouprow.selectAll('td.dc-table-label')
.attr('colspan', colspan);
// for each row, join td's to a single-element array
// containing the sum. this is a trick to only create a child element once
var sum = grouprow.selectAll('td.sum').data(function(d) {
return [d.values.map(function(d) {
return d.total;
}).reduce(function(a, b) {
return a+b;
})];
});
sum.enter().append('td').attr('class', 'sum');
sum.text(function(d) { return d; });
});
First we select the existing group label rows. We need to reduce the colspan of the existing tds (and any that get created later) by one. (But we should only calculate the colspan once, or they would keep getting smaller.)
Then we can add another td for the sum. This uses a trick where you selectAll an element that doesn't exist, and join to an array of size one. The result is that the element will get created if it doesn't exist, but won't get added if it's already there.
The sum is just map-to-value and then reduce with +.
Finally we set the value of the td.sum, whether or not it is new.
Fork of your fiddle: https://jsfiddle.net/gordonwoodhull/at95n2zg/11/
What i want to achieve is:
Have a Master Grid. Clicking on a row of this grid, i want to filter the rows of the ChildGrid.
What i have done so far:
function updateChildGridRows(field, operator, value) {
// get the kendoGrid element.
var gridData = $("#childGrid").data("kendoGrid");
var filterField = field;
var filterValue = value;
// get currently applied filters from the Grid.
var currFilterObj = gridData.dataSource.filter();
// if the oject we obtained above is null/undefined, set this to an empty array
var currentFilters = currFilterObj ? currFilterObj.filters : [];
// iterate over current filters array. if a filter for "filterField" is already
// defined, remove it from the array
// once an entry is removed, we stop looking at the rest of the array.
if (currentFilters && currentFilters.length > 0) {
for (var i = 0; i < currentFilters.length; i++) {
if (currentFilters[i].field == filterField) {
currentFilters.splice(i, 1);
break;
}
}
}
if (filterValue != "") {
currentFilters.push({
field: filterField,
operator: "eq",
value: filterValue
});
}
gridData.dataSource.filter({
filters: currentFilters
}); }
I got this code from the following jsfiddle:
http://jsfiddle.net/randombw/27hTK/
I have attached the MasterGrid's Change event to MasterGridSelectionChange() method. From there i am calling my filter method.
But when i click on the MasterGrid's row, all of the rows in my ChildGrid are getting removed.
One thing i can understand is, if i give wrong column name in the filter list, all the rows will be removed. But even though i have given correct ColumnName, my rows are getting deleted.
Sorry for the long post.
Please help me with this issue, as i am stuck with this for almost 4 days!
Thanks.
You can define ClientDetailTemplateId for the master grid and ToClientTemplate() for the Child grid
read Grid Hierarchy for a example
I am stuck at a unique problem involving dc.js and crossfilter. I have some data which i need to display using Number Charts powered by dc.js. However i found minimal documentation for the number charts and hence posting my query.
Here is the JSFiddle for what i have conceptualized so far.
I basically want to show the unique project count in box 1 which in this case would be 3, unique place count in box 2 which in this case would be 11 and the screen failure rate which would be 2/15*100 i.e. 15.3%
Currently i have made this working using jquery but thats just a hack. I would like to have these number charts based on cross table aggregation so that i can drill down on the data.
I have come across examples for reductions to calculate counts but they were for bar charts but in the number chart we need to have a value accessor for displaying data.
Can someone help me out please?
PS:
Here is the jquery code that i wrote. Dont know if this would be helpful.
$(document).ready(function() {
var baseURL = window.location.origin;
$.ajax({
url : baseURL + '/api/PlaceTable',
type : 'GET',
data : {},
async : true,
dataType : "json",
success : function(response) {
//Project Count
var projectIdCount = [];
for (i = 0; i < response.length; i++) {
if(response[i].Project != undefined){
if($.inArray(response[i].Project, projectIdCount) === -1){
projectIdCount.push(response[i].Project);
}
}
}
$('#number-box1').text(ProjectIdCount.length);
//Place Count
var placeIdCount = [];
for (i = 0; i < response.length; i++) {
if(response[i].Place != undefined){
if($.inArray(response[i].Place, placeIdCount) === -1){
placeIdCount.push(response[i].Place);
}
}
}
And for displaying a running sum of a column containing binary values i used this code, which worked in the number chart:
numberChart
.valueAccessor(function(x){ return +flag.groupAll().reduceCount().reduceSum(function(d) { return d.Flag; }).value();})
.group(ndx.groupAll());
The failure percentage calculation is a separate problem which I think you've asked elsewhere. To get the unique count, it is pretty easy to make a "fake groupAll" which returns the number of unique keys in its value method.
We'll also need to filter out the empty bins since crossfilter doesn't do that automatically.
function bin_counter(group) {
return {
value: function() {
return group.all().filter(function(kv) {
return kv.value > 0;
}).length;
}
};
}
var projectGroup = project.group();
projectCount
.valueAccessor(function(x){ return x;})
.group(bin_counter(projectGroup));
Updated fiddle here, still ignoring the failure% part:
http://jsfiddle.net/gordonwoodhull/vct0dzou/1/