Sounds good. Remember to destroy the SVG elements when rendered charts are not needed.
DC.renderAll can take a chart group id string so you can be very selective which charts get shown.
The group id is the second param after the element id when creating a chart.
Yeah, I suspected as much. I found that putting some logging on the filter function, and the functions that create range filters, 2d filters and ordinal filters gave a lot of insight to what was going on under the hood.
DC.js is VERY well written. You can see the quality of the code pretty quickly.
I will end up writing a driver layer for it, since I think there is some goodness to be had there. But the things I need to do now are more around resizing of components, I made changes to do that in 1.6, I'll see if they port nicely to 2.0, (mostly, it was around flushing out the cached values around bar sizes for the bar chart and the like.) I can see there is an invalidate cache call in 2.0, I'll see if all I have to do is call that then a component gets resized on the fly. If not, then hey - you will have some pull requests, since I got it working once, it should be easy to do it again :)
I do have some questions though... With a stacked chart, wouldn't have more sense to have the stacks be generated by another crossfilter group?
Take a thing for tracking vehicle warrant of fitness records... you may want to track how many vehicle where checked as a stack of vehicle types.
I CAN run a loop and add each vehicle type as a separate filter, and build the stack like that, (in fact, with the backend, we get the list, so it is easy) but it doesn't seem all that natural. Is a "stackBy" function possible? I'll look into it.
Either way, thanks so much for looking after this project :) I use it almost every day (seriously, in the last week, I have built 8 of them, roughly the same the week before - it is rapidly turning into my full time job now - not that I have ANY complaints.)
chart.
.dimension({ filter: function() {} })
.group({ all: function() {} })
.data(function () {
// return server data (cache per redraw, since this get's called more than once sometimes.)
})
.filterHandler(function (dimension, filters) {
// tell server what to do with filter values?
})
So let’s name a few things that we will probably need to replace.
1. chart.data()
2. chart.dimension()
3. chart.group()
4. chart.filterHandler()
5. chart.redrawGroup()
And how to do that:
2 & 3. Since we no longer need dimensions/groups from crossfilter, our dimension and group only need to be objects that don’t break anything…
var noop = function() {},
chart = dc.barChart(‘#chart1’, ‘chartGroup1').
.dimension({ filter: noop })
.group({ all: noop });
1. Our data function is going to be just be a function that returns the data from the server.
var serverData = {};
chart.data(function() { return serverData[chart.chartId()] || []; }); // Returns the array of data or empty array
4 & 5. Here is where the magic happens with the server. Typically in every chart, any time a filter happens:
It calls chart.filter(<someFilter>), which calls chart.applyFilters(), which calls chart.filterHandler(dimension, filters)
https://github.com/dc-js/dc.js/blob/master/src/coordinate-grid-mixin.js#L770
https://github.com/dc-js/dc.js/blob/master/src/base-mixin.js#L519
https://github.com/dc-js/dc.js/blob/master/src/base-mixin.js#L496
And after that has finished processing, it calls chart.redrawGroup();
https://github.com/dc-js/dc.js/blob/master/src/coordinate-grid-mixin.js#L771
So, what you need to do is override chart.filterHandler. The ultimate goal of our own filterHandler is figure out what to do with the filters. What I did:
// Create a map to maintain all filters of our chartGroup
var chartGroupFilters = d3.map();
// Convenience function to return all filters for a chart (these are filters provided by other charts being filtered)
function getChartFilters (chartId) {
var tmp = new d3.map(chartGroupFilters);
tmp.remove(chartId);
return d3.merge(tmp.values());
}
// Override the charts filter handler
chart.filterHandler(function(dimension, filters) {
// Here you will provide the way to map the array filters to a server friendly filter array
// Then you will push that object into the filters d3.map()
var mappedFilters = …; // Array of filters (could be only 1 filter)
chartGroupFilters.set(chart.chartId(), mappedFilters);
});
// Update the redrawGroup function of the chart to instead execute new queries for each chart and redraw them in callbacks.
chart.redrawGroup = function() {
// Loop any other charts and ajax call to the server for data on that chart.
var charts = dc.chartRegistry.list(‘chartGroup1’);
for ( var i = 0; i < charts.length; i++ ) {
var chartId = charts[i].chartId();
// The filters for those other charts can be returned by calling
var filters = getChartFilters(chartId);
// Perform ajax call to update that chart, with a callback of
var callback = function(serverData) {
serverData[‘chartId’] = serverData;
charts[i].redraw();
}
}
// One thing to note, you do not need to ajax call to the server for the chart that added a new filter. To keep similarity with Crossfilter, all you need to do is call
chart.redraw();
};
This is a lot to take in, but the overall flow is:
1. FilterHandler should locally update a maintained map of charts -> filters (filters are something your server understands)
2. RedrawGroup should execute the ajax call for each chart and update the data object referenced in the charts.data function
My code is a bit different so I haven’t really tested any of what I just wrote, but this is the general idea. If you have any more questions, feel free to ask!
Do you have some working demo of the implementation for a fake dimension and group?
ok, I've built a minimum version of "fakecrossfilter" so people can base their own code off it.
Excellent discussion, thank you Matt, Blair and everyone else; I've managed to move the filtering to the server side through your hints.Another question for those who are currently working with dc.js + crossfilter.js on the server side: how are you handling multiple session filtering? Create N crossfilters for N users would not scale well for me because loading a crossfilter is expensive.At first I tried to use a single crossfilter and reset its filters (calling filterAll on the dimensions) every time an user requests filtering on a chart. Since I have around 15 dimensions, this solution works fine only for small~medium size datasets. In my case, it starts to slow down for datasets with 70k or more records.I was wondering if it would be possible (and how difficult would it be) to create a 'multisession' crossfilter. I would keep a single dataset and save the default indexes to replicate for each new session created. Have you guys tried something like that? Am I going in the wrong direction here, perhaps replacing crossfilter with another library would be more appropriate?
I hate to say it but it really does sound like you are trying to fit a square peg into a round hole.
I would suggest a two tier approach. One where you know the client is using a PC and can handle the complete dataset and another that abstracts the data to a high enough level to be able to cope with it on a tablet but in effect gives a reduced set of charts.
The only server side solution I could see working is to have a crossfilter held in server memory and dimensions held inside each session.
--
You received this message because you are subscribed to a topic in the Google Groups "dc-js user group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/dc-js-user-group/fkRoFHuqg4k/unsubscribe.
To unsubscribe from this group and all its topics, send an email to dc-js-user-gro...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dc-js-user-group/07666afc-743a-443c-963b-6efba3253749%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/dc-js-user-group/87d77723-4d7f-4b83-9cb3-ccc1f32c1c80%40googlegroups.com.