dc.js - pieChart ignore unwanted values - dc.js

I have a piechart I'm displaying using the following dimension -
var types = facts.dimension(function (d) {
if (d.types === 2)
return "Type 2";
else if (d.types === 3)
return "Type 3";
else
return "Other";
});
I would like to not return, ignore, all other types, so the pie chart would just display Type 2 and Type 3. I cannot seem to get this working though I'm sure it's simple. Can I do something within that dimension or do I need to filter before?
Thanks for any help.

Have you tried creating a new type and then creating a dimension on top of that?
facts.forEach(function(d) {
if (d.types == 2) {
d._type = "Type 2";
} else if (d.types == 3) {
d._type = "Type 3";
} else {
d._type = "Other";
}
});
var types = facts.dimension(dc.pluck('_type'))

You need to (1) Filter your data then (2) remove it from the bin. Run your data through ensure_group_bins(myGroup) you'll get the chart you're after
function remove_empty_bins(source_group) {
return {
all:function () {
return source_group.all().filter(function(d) {
return d.key != "unwanted" ;
});
}
};}
function ensure_group_bins(source_group, bins) {
return {
all:function () {
var result = source_group.all().slice(0), // copy original results (we mustn't modify them)
found = {};
result.forEach(function(d) {
found[d.key] = true;
});
bins.forEach(function(d) {
if(!found[d])
result.push({key: d, value: 0});
});
return result;
}
};};
dc.js Github

Related

Display multiple bar on barChart from a custom reducer

I have a group with custom reducer calculating various total and average values. The goal is to show them all on the same barChart. But I can only get the first bar to show. Here is the JSFiddler
https://jsfiddle.net/71k0guxe/15/
Is it possible to show all the value on the barChart?
Thanks in advance!
Data
ID,SurveySent,ResponseReceived
1,Yes,No
2,No,No
3,Yes,Yes
4,No,No
5,Yes,Yes
6,No,No
7,Yes,No
8,No,No
9,Yes,No
10,No,No
Code
var chart = dc.barChart("#test");
//d3.csv("morley.csv", function(error, experiments) {
var experiments = d3.csvParse(d3.select('pre#data').text());
var ndx = crossfilter(experiments),
dimStat = ndx.dimension(function(d) {return "Statistics";}),
groupStat = dimStat.group().reduce(reduceAdd, reduceRemove, reduceInitial);
function reduceAdd(p, v) {
++p.count;
if (v.SurveySent === "Yes") p.sent++;
if (v.ResponseReceived === "Yes") p.received++;
return p;
}
function reduceRemove(p, v) {
--p.count;
if (v.SurveySent === "Yes") p.sent--;
if (v.ResponseReceived === "Yes") p.received--;
return p;
}
function reduceInitial() {
return {count: 0, sent: 0, received: 0};
}
chart
.width(400)
.height(400)
.xUnits(dc.units.ordinal)
.label(function(d) { return d.data.value })
.elasticY(true)
.x(d3.scaleOrdinal().domain(["Total", "Sent", "Received"]))
.brushOn(false)
.yAxisLabel("This is the Y Axis!")
.dimension(dimStat)
.group(groupStat)
.valueAccessor(function (d) {
//Is it possible to return count sent and received all from here?
return d.value.count;
})
.on('renderlet', function(chart) {
chart.selectAll('rect').on("click", function(d) {
console.log("click!", d);
});
});
chart.render();
Just got some idea from the FAQ section of dc.js/wiki/FAQ
Fake Groups
"dc.js uses a very limited part of the crossfilter API - in fact, it really only uses dimension.filter() and group.all()."
I don't care about filtering, so i just need to mark up my own group.all. Basically transpose it from one row to multiple row. Works my purpose.
/* solution */
var groupStatTranposed = group_transpose(groupStat);
function group_transpose(source_group, f) {
return {
all:function () {
return [
{key: "Total", value: source_group.all()[0].value.count},
{key: "Sent", value: source_group.all()[0].value.sent},
{key: "Received", value: source_group.all()[0].value.received}
];
}
};
}
//use groupStatTranposed in the chart.
/** solution */

Crossfilter and DC.js: reduce to unique number

In the example below, I am trying to sum by unique occurence of Respond_Id. eg. in this case, it should be in total 3, "Respond_Id" being 258,261 and 345.
This is my data:
{"Respond_Id":258,"Gender":"Female","Age":"18-21","Answer":424},
{"Respond_Id":258,"Gender":"Female","Age":"18-21","Answer":428},
{"Respond_Id":261,"Gender":"Male","Age":"22-26", "Answer":427},
{"Respond_Id":261,"Gender":"Male","Age":"22-26", "Answer":432},
{"Respond_Id":345,"Gender":"Female","Age":"27-30","Answer":424},
{"Respond_Id":345,"Gender":"Female","Age":"27-30","Answer":425},
{"Respond_Id":345,"Gender":"Female","Age":"27-30","Answer":433},
I know I should use group reduce for this, so I tried (adapted from an example):
var ntotal = answerDim.group().reduce(
function(p, d) {
if(d.Respond_Id in p.Respond_Ids){
p.Respond_Ids[d.Respond_Id]++;
}
else {
p.Respond_Ids[d.Respond_Id] = 1;
p.RespondCount++;
}
return p;
},
function (p, d) {
p.Respond_Ids[d.Respond_Id]--;
if(p.Respond_Ids[d.Respond_Id] === 0){
delete p.Respond_Ids[d.Respond_Id];
p.RespondCount--;
}
return p;
},
function () {
return {
RespondCount: 0,
Respond_Ids: {}
};
}
);
Then:
numberDisplay
.group(ntotal)
.valueAccessor(function(d){ return d.value.RespondCount; });
dc.renderAll();
But seems not working. Does someone know how to make it work ? Thank you
Based on your JSFiddle, your setup is like this:
var RespondDim = ndx.dimension(function (d) { return d.Respond_Id;});
var ntotal = RespondDim.group().reduce(
function(p, d) {
if(d.Respond_Id in p.Respond_Ids){
p.Respond_Ids[d.Respond_Id]++;
}
else {
p.Respond_Ids[d.Respond_Id] = 1;
p.RespondCount++;
}
return p;
},
function (p, d) {
p.Respond_Ids[d.Respond_Id]--;
if(p.Respond_Ids[d.Respond_Id] === 0){
delete p.Respond_Ids[d.Respond_Id];
p.RespondCount--;
}
return p;
},
function () {
return {
RespondCount: 0,
Respond_Ids: {}
};
});
What is important to note here is that your group keys, by default, are the same as your dimension keys. So you will have one group per respondent ID. This isn't what you want.
You could switch to using dimension.groupAll, which is designed for this use case, but unfortunately the dimension.groupAll.reduce signature is slightly different. The easiest fix for you is going to be to just define your dimension to have a single value:
var RespondDim = ndx.dimension(function (d) { return true;});
Now you'll see that ntotal.all() will look like this:
{key: true, value: {RespondCount: 3, Respond_Ids: {258: 2, 261: 2, 345: 3}}}
Working fiddle: https://jsfiddle.net/v0rdoyrt/2/

Issues filtering dc.seriesChart

I am new to posting a question on stackoverflow, so any guidance is much appreciated!
I am using crossfilter.js and dc.js to plot charts (fairly new to both). One of the requirements is for a seriesChart (scatterplot). Note: I am using the latest beta release, since the scatterplot is a requirement and the latest stable version does not appear support seriesChart & scatterplot. This particular chart is posing me a problem when I filter/zoom. I see the following error in the console when doing so:
Uncaught TypeError: dimension.filterFunction is not a function...
PriceVsTime = dc.seriesChart("#PriceVsTime");
//$('#PriceVsTime').parent('td').addClass('tdOrders1');
PriceVsTimeDimension = crossfilterData.dimension(function (d) { if (d.chart_price > 0) return d.start_datetime; });
PriceVsTimeGroup = PriceVsTimeDimension.group().reduce(
function reduceAdd(p, v) {
++p.count;
p.order_type = v.order_type;
p.execution_type = v.execution_type == 'REPLACED' ? (v.change_qty > 0 ? 'UPSIZE' : 'DOWNSIZE') : v.execution_type;
p.chart_price = v.chart_price;
return p;
},
function reduceRemove(p, v) {
--p.count;
return p;
},
function reduceInitial() {
return { count: 0 };
}
);
var symbolScale = d3.scale.ordinal().range(d3.svg.symbolTypes);
console.log(d3.svg.symbolTypes);
var symbolAccessor = function (d) {
switch (d.value.execution_type) {
case 'NEW':
return d3.svg.symbolTypes[1]; // diamond
case 'CANCELED':
return d3.svg.symbolTypes[0]; // cross
case 'UPSIZE':
return d3.svg.symbolTypes[4]; // triangle-up
case 'DOWNSIZE':
return d3.svg.symbolTypes[5]; // triangle-down
default:
return d3.svg.symbolTypes[3];
}
};
var subChart = function (c) {
return dc.scatterPlot(c)
.existenceAccessor(function (d) { if (d.value.count > 0) { return d.value.execution_type; } })
.symbol(symbolAccessor)
.symbolSize(8)
.highlightedSize(12)
;
};
var PriceVsTimeSeries = function (d) { if (d.value.count > 0) { return d.value.execution_type; } };
var PriceVsTimeKey = function (d) { if (d.value.count > 0) { return d.key; } };
var PriceVsTimeValue = function (d) { if (d.value.count > 0) { return d.value.chart_price; } };
var yPriceVsTime = roundAxis(d3.extent(PriceVsTimeGroup.all(), function (d) { if (d.value.chart_price > 0) { return d.value.chart_price; } }), 10);
function roundAxis(item, interval) {
return [item[0] - item[0] % interval - interval, item[1] - item[1] % interval + interval];
}
PriceVsTime
.chart(subChart)
.width(2 * width1)
.height(height3)
.dimension(PriceVsTimeDimension)
.group(PriceVsTimeGroup)
.seriesAccessor(PriceVsTimeSeries)
.keyAccessor(PriceVsTimeKey)
.valueAccessor(PriceVsTimeValue)
.x(d3.time.scale().domain(DateTimeDomain))
.y(d3.scale.linear().domain(yPriceVsTime))
.yAxisLabel('Price')
.xAxisLabel('Time')
.margins(margin1)
;
PriceVsTime.yAxis().tickFormat(d3.format('s'));
PriceVsTime.brushOn(true).mouseZoomable(true);
PriceVsTime.legend(dc.legend().x(1075).y(0).itemHeight(13).gap(5).horizontal(false).itemWidth(100));

p:selectOneMenu filter not working with accented characters

I use PrimeFaces SelectOneMenu advanced. Filter is wrong working when I input the i and ı character.
For example http://www.primefaces.org/showcase/ui/input/oneMenu.xhtml demo advanced one menu I search arI and arİ strings and it finds Aristo element.
In my application, my menu contains Isparta element. I input Isp and İsp and filter finds Isparta.
How can I solve this problem?
I resolve this problem with autocomplete component. Primefaces autocomplete component with dropdown="true" property works like one menu and this component don't have Turkish character problem.
Reported to PrimeFaces Team: https://github.com/primefaces/primefaces/issues/9629
Fixed for 13.0.0 but MonkeyPatch provided here:
if (PrimeFaces.widget.SelectOneMenu) {
PrimeFaces.widget.SelectOneMenu.prototype.normalize = function(string, lowercase) {
if (!string) return string;
var result = string.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
return lowercase ? result.toLowerCase() : result;
}
PrimeFaces.widget.SelectOneMenu.prototype.filter = function(value) {
this.cfg.initialHeight = this.cfg.initialHeight || this.itemsWrapper.height();
var filterValue = this.normalize(PrimeFaces.trim(value), !this.cfg.caseSensitive);
if (filterValue === '') {
this.items.filter(':hidden').show();
this.itemsContainer.children('.ui-selectonemenu-item-group').show();
} else {
var hide = [];
var show = [];
for (var i = 0; i < this.options.length; i++) {
var option = this.options.eq(i),
itemLabel = this.normalize(option.text(), !this.cfg.caseSensitive),
item = this.items.eq(i);
if (item.hasClass('ui-noselection-option')) {
hide.push(item);
} else {
if (this.filterMatcher(itemLabel, filterValue)) {
show.push(item);
} else if (!item.is('.ui-selectonemenu-item-group-children')) {
hide.push(item);
} else {
itemLabel = this.normalize(option.parent().attr('label'), !this.cfg.caseSensitive);
if (this.filterMatcher(itemLabel, filterValue)) {
show.push(item);
} else {
hide.push(item);
}
}
}
}
$.each(hide, function(i, o) {
o.hide();
});
$.each(show, function(i, o) {
o.show();
});
hide = [];
show = [];
//Toggle groups
var groups = this.itemsContainer.children('.ui-selectonemenu-item-group');
for (var g = 0; g < groups.length; g++) {
var group = groups.eq(g);
if (g === (groups.length - 1)) {
if (group.nextAll().filter('.ui-selectonemenu-item-group-children:visible').length === 0)
hide.push(group);
else
show.push(group);
} else {
if (group.nextUntil('.ui-selectonemenu-item-group').filter('.ui-selectonemenu-item-group-children:visible').length === 0)
hide.push(group);
else
show.push(group);
}
}
$.each(hide, function(i, o) {
o.hide();
});
$.each(show, function(i, o) {
o.show();
});
}
var firstVisibleItem = this.items.filter(':visible:not(.ui-state-disabled):first');
if (firstVisibleItem.length) {
this.highlightItem(firstVisibleItem);
PrimeFaces.scrollInView(this.itemsWrapper, firstVisibleItem);
}
if (this.itemsContainer.height() < this.cfg.initialHeight) {
this.itemsWrapper.css('height', 'auto');
} else {
this.itemsWrapper.height(this.cfg.initialHeight);
}
this.alignPanel();
}
};
From PrimeFaces 13 normalization can be applied on both the item label and the filter value by setting the filterNormalize attribute to true. You can do the same in older versions using a filterFunction. For example:
<script>
function searchable(string) {
return !string ? '' : string.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
}
function customFilter(itemLabel, filterValue) {
return searchable(itemLabel).includes(searchable(filterValue));
}
</script>
<p:selectOneMenu filter="true" filterMatchMode="custom" filterFunction="customFilter"
.../>
See also:
https://primefaces.github.io/primefaces/12_0_0/#/components/selectonemenu?id=filtering

Update multiple tags rowchart in dc.js

I am looking for how to create a rowchart in dc.js to show and filter items with multiple tags. I've summed up a few answers given on stack overflow, and now have a working code.
var data = [
{id:1, tags: [1,2,3]},
{id:2, tags: [3]},
{id:3, tags: [1]},
{id:4, tags: [2,3]},
{id:5, tags: [3]},
{id:6, tags: [1,2,3]},
{id:7, tags: [1,2]}];
var content=crossfilter(data);
var idDimension = content.dimension(function (d) { return d.id; });
var grid = dc.dataTable("#idgrid");
grid
.dimension(idDimension)
.group(function(d){ return "ITEMS" })
.columns([
function(d){return d.id+" : "; },
function(d){return d.tags;},
])
function reduceAdd(p, v) {
v.tags.forEach (function(val, idx) {
p[val] = (p[val] || 0) + 1; //increment counts
});
return p;
}
function reduceRemove(p, v) {
v.tags.forEach (function(val, idx) {
p[val] = (p[val] || 0) - 1; //decrement counts
});
return p;
}
function reduceInitial() {
return {};
}
var tags = content.dimension(function (d) { return d.tags });
var groupall = tags.groupAll();
var tagsGroup = groupall.reduce(reduceAdd, reduceRemove, reduceInitial).value();
tagsGroup.all = function() {
var newObject = [];
for (var key in this) {
if (this.hasOwnProperty(key) && key != "") {
newObject.push({
key: key,
value: this[key]
});
}
}
return newObject;
}
var tagsChart = dc.rowChart("#idtags")
tagsChart
.width(400)
.height(200)
.renderLabel(true)
.labelOffsetY(10)
.gap(2)
.group(tagsGroup)
.dimension(tags)
.elasticX(true)
.transitionDuration(1000)
.colors(d3.scale.category10())
.label(function (d) { return d.key })
.filterHandler (function (dimension, filters) {
var fm = filters.map(Number)
dimension.filter(null);
if (fm.length === 0)
dimension.filter(null);
else
dimension.filterFunction(function (d) {
for (var i=0; i < fm.length; i++) {
if (d.indexOf(fm[i]) <0) return false;
}
return true;
});
return filters;
}
)
.xAxis().ticks(5);
It can be seen on http://jsfiddle.net/ewm76uru/24/
Nevertheless, the rowchart is not updated when I filter by one tag. For example, on jsfiddle, if you select tag '1', it filters items 1,3,6 and 7. Fine. But the rowchart is not updated... I Should have tag '3' count lowered to 2 for example.
Is there a way to have the rowchart tags counts updated each time I filter by tags ?
Thanks.
After a long struggle, I think I have finally gathered a working solution.
As said on crossfilter documentation : "a grouping intersects the crossfilter's current filters, except for the associated dimension's filter"
So, the tags dimension is not filtered when tag selection is modified, and there is no flag or function to force this reset. Nevertheless, there is a workaround (given here : https://github.com/square/crossfilter/issues/146).
The idea is to duplicate the 'tags' dimension, and to use it as the filtered dimension :
var tags = content.dimension(function (d) { return d.tags });
// duplicate the dimension
var tags2 = content.dimension(function (d) { return d.tags });
var groupall = tags.groupAll();
...
tagsChart
.group(tagsGroup)
.dimension(tags2) // and use this duplicated dimension
as it can been seen here :
http://jsfiddle.net/ewm76uru/30/
I hope this will help.

Resources