How to filter record by custom value from any dimension in dc.js? - dc.js

How to remove custom records from any dimension. In the below case how do I filter only category 'S' and allow rest of them in dimension ?
Example
let data = [
{category:'A',value:10},
{category:'B',value:11},
{category:'S',value:12},
{category:'A',value:14},
{category:'B',value:12},
]
let ndx = crossfilter(data);
let dim= ndx.dimension(function(d){
if(d.category != "S") return d.category;
})
This above code runs into loop and the application crashes. I don't want to create separate data for this dimension rather link it with other cross filters.

I guess its pretty simple, I did little research after posting the question.
Just manipulate the group parameter being passed to the chart. The code goes something like this.
Since I am trying to remove the value by key lets first write a function for further uses as well.
function removeByKey(source_group, value) {
return {
all: function() {
return source_group.all().filter(function(d) {
return d.key != value;
});
}
};
}
Once this is done the place where you call the group method for the charts call this method. The first parameter of removeByKey method is the group itself the second is the key value which is supposed to be removed from the chart.
chart
.dimension(dimension_data)
.group(removeByKey(dimension_data_group, 'S'))
Thanks :)

Related

Use full group record within title in dc-js geoChoropleth chart

I have a group for which elements after reduction look like this pseudocode :
{
key:"somevalue",
value: {
sum: the_total,
names:{
a: a_number,
b: b_number,
c:c_number
}
}
}
In my dc-js geoChoropleth graph the valueAccessor is (d) => d.value.sum
In my title, I would like to use the names component of my reduction. But when I use .title((d) => {...}), I can onjly access the key and the value resulting from the valueAccessor function instead of the original record.
Is that meant to be ?
This is a peculiarity of the geoChoropleth chart.
Most charts bind the group data directly to chart elements, but since the geoChoropleth chart has two sources of data, the map and the group, it binds the map data and hides the group data.
Here is the direct culprit:
_renderTitles (regionG, layerIndex, data) {
if (this.renderTitle()) {
regionG.selectAll('title').text(d => {
const key = this._getKey(layerIndex, d);
const value = data[key];
return this.title()({key: key, value: value});
});
}
}
It is creating key/value objects itself, and the value, as you deduced, comes from the valueAccessor:
_generateLayeredData () {
const data = {};
const groupAll = this.data();
for (let i = 0; i < groupAll.length; ++i) {
data[this.keyAccessor()(groupAll[i])] = this.valueAccessor()(groupAll[i]);
}
return data;
}
Sorry this is not a complete answer, but I would suggest adding a pretransition handler that replaces the titles, or alternately, using the key passed to the title accessor to lookup the data you need.
As I noted in the issue linked above, I think this is a pretty serious design bug.

check if d3.select or d3.selectAll

I have a method on a reusable chart that can be passed a selection and return a value if it is passed a d3.select('#id') selection or an array of values if it is passed a d3.selectAll('.class') selection. I'm currently interrogating the passed argument with context._groups[0] instanceof NodeList, but it feels a little fragile using an undocumented property, as that may change in future versions. Is there a more built in way of determining if a selection comes from select or selectAll?
selection.size() will not help here, as it only tells us the result of the selection, not how it was called.
EDIT:
Here's the context of the use. I'm using Mike Bostock's reusable chart pattern and this instance includes a method for getting/setting a label for a donut.
To me, this API usage follows the principle of least astonishment, as it's how I would expect the result to be returned.
var donut = APP.rotatingDonut();
// set label for one element
d3.select('#donut1.donut')
.call(donut.label, 'Donut 1')
d3.select('#donut2.donut')
.call(donut.label, 'Donut 2')
// set label for multiple elements
d3.selectAll('.donut.group-1')
.call(donut.label, 'Group 1 Donuts')
// get label for one donut
var donutOneLabel = d3.select('#donut1').call(donut.label)
// donutOnelabel === 'Donut 1'
// get label for multiple donuts
var donutLables = d3.selectAll('.donut').call(donut.label)
// donutLabels === ['Donut 1', 'Donut 2', 'Group 1 Donuts', 'Group 1 Donuts']
and the internal method definition:
App.rotatingDonut = function() {
var label = d3.local();
function donut() {}
donut.label = function(context, value) {
var returnArray;
var isList = context._groups[0] instanceof NodeList;
if (typeof value === 'undefined' ) {
// getter
returnArray = context.nodes()
.map(function (node) {return label.get(node);});
return isList ? returnArray : returnArray[0];
}
// settter
context.each(function() {label.set(this, value);});
// allows method chaining
return donut;
};
return donut
}
Well, sometimes a question here at S.O. simply doesn't have an answer (it has happened before).
That seems to be the case of this question of yours: "Is there a more built in way of determining if a selection comes from select or selectAll?". Probably no.
To prove that, let's see the source code for d3.select and d3.selectAll (important: those are not selection.select and selection.selectAll, which are very different from each other).
First, d3.select:
export default function(selector) {
return typeof selector === "string"
? new Selection([[document.querySelector(selector)]], [document.documentElement])
: new Selection([[selector]], root);
}
Now, d3.selectAll:
export default function(selector) {
return typeof selector === "string"
? new Selection([document.querySelectorAll(selector)], [document.documentElement])
: new Selection([selector == null ? [] : selector], root);
}
As you can see, we have only two differences here:
d3.selectAll accepts null. That will not help you.
d3.selectAll uses querySelectorAll, while d3.select uses querySelector.
That second difference is the only one that suits you, as you know by now, since querySelectorAll:
Returns a list of the elements within the document (using depth-first pre-order traversal of the document's nodes) that match the specified group of selectors. The object returned is a NodeList. (emphasis mine)
And querySelector only...:
Returns the first Element within the document that matches the specified selector, or group of selectors.
Therefore, the undocumented (and hacky, since you are using _groups, which is not a good idea) selection._groups[0] instanceof NodeList you are using right now seems to be the only way to tell a selection created by d3.select from a selection created by d3.selectAll.

dc.js add multiple filters at once

Is it possible to add a set of filters at once on a dc chart? For instance, say I have a pieChart, and an array of filter values to apply.
var osChart = dc.pieChart('#oschart');
And an input set of filters, say
var filters = ["linux", "mac osx", "windows", "solaris"]
How can I apply filters so that only one "filtered" event is generated?
I can do something like
for (var i=0; i < filters.length; i++) {
osChart.filter(filters[i]);
}
However that would generate 4 filtered event.
I am applying filters based on what a user typed on a text box. When a filter is applied, I am making some ajax calls, and this tends to slow down if I apply filters one by one. I can avoid extra ajax calls if the filters can be set at once.
crossfilter has a function filterFunction which can get this task done, but I am not sure how I can apply that on a dc chart. Apply filterFunction on osChart.dimension() did not work. With the latest dc release, I saw some functions like addFilterHandler and removeFilterHandler however I cannot test and deploy that version right now.
What other options do I have?
From looking at the code, which is somewhat convoluted, you can pass the array inside another array to .filter() (or the undocumented but unmagical .replaceFilter()) without a performance penalty, because it will apply all of the filters before invoking the filtered event.
From the latest .filter() source, which uses handlers but has the same behavior:
if (_ instanceof Array && _[0] instanceof Array && !_.isFiltered) {
_[0].forEach(function (d) {
if (_chart.hasFilter(d)) {
_removeFilterHandler(_filters, d);
} else {
_addFilterHandler(_filters, d);
}
});
} else if (_ === null) {
_filters = _resetFilterHandler(_filters);
} else {
if (_chart.hasFilter(_)) {
_removeFilterHandler(_filters, _);
} else {
_addFilterHandler(_filters, _);
}
}
applyFilters();
_chart._invokeFilteredListener(_);
So if it finds an array that does not have an isFiltered method, and that array's first element is also an array, it will iterate over the elements in the nested array.
For example, pass [["linux", "mac osx", "windows", "solaris"]] to filter on those four values. (Thanks #marcin for clarifying!)

Knockout validation issues

I have the following issues with my knockout model validations and not sure how to resolve them. Following is my model first of all, with the validation rules:
var Data = function (data) {
this.Val = data;
}
function ViewModel(item) {
var parse = JSON.parse(item.d);
var self = this;
this.Name = ko.observable(parse.Name);
this.UserType = ko.observable(parse.UserType);
this.ID = ko.observable(parse.ID).extend({ required: { params: true, message: "ID is required" }, decimal: { params: 2, message: "Should be decimal"} });
this.Username = ko.observable(parsed.Username).extend({ required: {
onlyIf: function () {
return self.UserType() > 1;
}
}
});
this.WeeklyData = ko.observableArray([]);
var records = $.map(parse.WeeklyData, function (data) { return new Data(data) });
this.WeeklyData(records);
this.WeeklyData2 = ko.observableArray([]);
var records = $.map(parse.WeeklyData2, function (data) { return new Data(data) });
this.WeeklyData2(records);
}
ko.extenders.numeric = function (target, precision) {
var result = ko.dependentObservable({
read: function () {
return target().toFixed(precision);
},
write: target
});
result.raw = target;
return result;
};
Here are my problems:
1) with the ID() observable, I want to restrict it to two decimal points, so I've created the validation extender 'numeric' but it's not working. Is there anything wrong with how I'm using it and how to correct it?
2) Also, if I want to restrict an observable to whole numbers, how can I do that?
3) when I define a rule with a condition, (i.e. Username()), how do I define a custom message for that? I was able to do it for default rules, but with the conditional rules, it's not working
4) I have two observable arrays WeeklyData1 and WeeklyData2 both of which contains Data() objects. I want to have separate min/max rules for these two, for example, min/max - 1,7 for WeeklyData1 and min/max - 1,150 for WeeklyData2. How can I get it done?
4) Right now my error messages appear right next to the data field, but I want all those to appear in a single validation summary, while displaying '*' against the field. I've been told to use Validation-bindings, but I'm not sure how to use it, can someone please give an example?
It's a lot of questions, I know, but I appreciate if someone could help.
Thanks in advance
Instead of diving in your code i have created a small-small demonstrations for your questions. Ok so here we go,
1) with the ID() observable, I want to restrict it to two decimal points.... and 2) Also, if I want to restrict an observable to whole numbers....
Your 1 and 2 question are pretty similar so i covered both of this in a single fiddle. Check this fiddle.
3) when I define a rule with a condition, (i.e. Username()), how do I define a custom message ....
You can use message property to set custom messages, Check this fiddle.
4) I have two observable arrays WeeklyData1 and WeeklyData2 both of which contains Data() objects
I am not clear which this question, what type of data both of these array contains and for what you want to set min/max rule ( array length or other ). So please clear this, than i will try to help on this.
5) Right now my error messages appear right next to the data field.....
This questions answer i already given in your how to? with knockout js validations question (Check update).
Let me know if it helps!

How to use JQUERY to filter table rows dynamically using multiple form inputs

I'm displaying a table with multiple rows and columns. I'm using a JQUERY plugin called uiTableFilter which uses a text field input and filters (shows/hides) the table rows based on the input you provide. All you do is specify a column you want to filter on, and it will display only rows that have the text field input in that column. Simple and works fine.
I want to add a SECOND text input field that will help me narrow the results down even further. So, for instance if I had a PETS table and one column was petType and one was petColor -- I could type in CAT into the first text field, to show ALL cats, and then in the 2nd text field, I could type black, and the resulting table would display only rows where BLACK CATS were found. Basically, a subset.
Here is the JQUERY I'm using:
$("#typeFilter").live('keyup', function() {
if ($(this).val().length > 2 || $(this).val().length == 0)
{
var newTable = $('#pets');
$.uiTableFilter( theTable, this.value, "petType" );
}
}) // end typefilter
$("#colorFilter").live('keyup', function() {
if ($(this).val().length > 2 || $(this).val().length == 0)
{
var newTable = $('#pets');
$.uiTableFilter( newTable, this.value, "petColor" );
}
}) // end colorfilter
Problem is, I can use one filter, and it will display the correct subset of table rows, but when I provide input for the other filter, it doesn't seem to recognize the visible table rows that are remaining from the previous column, but instead it appears that it does an entirely new filtering of the original table. If 10 rows are returned after applying one filter, the 2nd filter should only apply to THOSE 10 rows. I've tried LIVE and BIND, but not working.
Can anyone shed some light on where I'm going wrong? Thanks!
The uiTableFilter plugin doesn't support what you're trying to do. A quick look at the source reveals this:
elems.each(function(){
var elem = jQuery(this);
jQuery.uiTableFilter.has_words(getText(elem), words, false)
? matches(elem)
: noMatch(elem);
});
and that expands to (essentially) this:
elems.each(function(){
var elem = jQuery(this);
jQuery.uiTableFilter.has_words(getText(elem), words, false)
? elem.show()
: elem.hide();
});
So all it does is spin through all the rows, .show() those that match, and .hide() those that don't; uiTableSorter doesn't pay attention to the current shown/hidden state of the rows and there's no way to tell it to filter on multiple columns.
If you really need your desired functionality then you can modify the plugin's behavior (the code is pretty small and simple) or just write your own. Here's a stripped down and simplified version that supports multiple filters and is a more conventional jQuery plugin than uiTableFilter:
(function($) {
$.fn.multiFilter = function(filters) {
var $table = $(this);
return $table.find('tbody > tr').each(function() {
var tr = $(this);
// Make it an array to avoid special cases later.
if(!$.isArray(filters))
filters = [ filters ];
howMany = 0;
for(i = 0, f = filters[0]; i < filters.length; f = filters[++i]) {
var index = 0;
$table.find('thead > tr > th').each(function(i) {
if($(this).text() == f.column) {
index = i;
return false;
}
});
var text = tr.find('td:eq(' + index + ')').text();
if(text.toLowerCase().indexOf(f.word.toLowerCase()) != -1)
++howMany;
}
if(howMany == filters.length)
tr.show();
else
tr.hide();
});
};
})(jQuery);
I'll leave error handling and performance as an exercise for the reader, this is just an illustrative example and I wouldn't want to get in the way of your learning. You could wire it up something like this:
$('#type').keyup(function() {
$('#leeLooDallas').multiFilter({ column: 'petType', word: this.value });
});
$('#color').keyup(function() {
$('#leeLooDallas').multiFilter([
{ column: 'petType', word: $('#type').val() },
{ column: 'petColor', word: this.value }
]);
});
And here's a live example (which assumes that you're going to enter something in "type" before "color"): http://jsfiddle.net/ambiguous/hdFDt/1/

Resources