/**
* A filter provides a chart with an interface to the data.
* The filter contains a number of `Partition`s and `Aggregate`s.
* It takes care of calling the relevant functions provided by a `Dataset`.
*
* Basic usage for a Chart is:
* 1. The chart renders using `Filter.data` if available
* 2. It can add or remove partitions and aggregates to the filter
* 3. It calls `Filter.updateSelection(..)` when the user makes a selection
* 4. To apply the new selection, and also filter the other charts, the chart calls `Filter.updateDataFilter()`
* 5. The charts redraws on 'newData' events on the filter
*
* The filter does the following:
* 1. It adds or removes paritions and aggregates on request
* 2. When it has the right number of partitions and aggregates, `Filter.isConfigured` becomes true
* and `Filter.initDataFilter()` is called
* 3. This in turn creates a `Filter.getData` function
* 4. As the new filter could affect all plots `Dataset.getAllData` called
*
* `Filter.getData` does the following:
* 1. It arranges for the `Filter.data` array to be filled
* 2. A `newData` event is triggerd, and the chart's callback function is executed
*
* @class Filter
* @extends Base
*/
/**
* newData event
* Indicates new data is available at Filter.data for plotting.
*
* @event Filter#newData
*/
/**
* @typedef {Object} DataRecord - Object holding the plot data, partitions are labelled with a single small letter, aggregates with a double small letter
* @property {string} DataRecord.a Value of first partition
* @property {string} DataRecord.b Value of second partition
* @property {string} DataRecord.c Value of third partition, etc.
* @property {string} DataRecord.aa Value of first aggregate
* @property {string} DataRecord.bb Value of second aggregate, etc.
*/
/**
* @typedef {DataRecord[]} Data - Array of DataRecords
*/
var Base = require('./base');
var Aggregates = require('./aggregate-collection');
var Partitions = require('./partition-collection');
module.exports = Base.extend({
props: {
chartType: {
type: 'string',
required: true,
default: 'barchart',
values: ['piechart', 'horizontalbarchart', 'barchart', 'linechart', 'radarchart', 'polarareachart', 'bubbleplot']
},
/**
* Title for displaying purposes
* @memberof! Filter
* @type {string}
*/
title: ['string', true, '']
},
collections: {
/**
* @memberof! Filter
* @type {Partitions[]}
*/
partitions: Partitions,
/**
* @memberof! Filter
* @type {Aggregate[]}
*/
aggregates: Aggregates
},
// Session properties are not typically persisted to the server,
// and are not returned by calls to toJSON() or serialize().
session: {
/**
* Array containing the data to plot
* @memberof! Filter
* @type {Data}
*/
data: {
type: 'array',
default: null
},
/**
* Call this function to request new data.
* The dataset backing the facet will copy the data to Filter.data.
* A newData event is fired when the data is ready to be plotted.
* @function
* @virtual
* @memberof! Filter
* @emits newData
*/
getData: {
type: 'any',
default: null
},
/*
* Minimum number of partitions required
* @memberof! Chart
* @type {number}
*/
minPartitions: 'number',
/*
* Maximum number of partitions required
* @memberof! Chart
* @type {number}
*/
maxPartitions: 'number'
},
derived: {
isConfigured: {
deps: ['minPartitions', 'maxPartitions', 'partitions', 'minAggregates', 'maxAggregates', 'aggregates'],
cache: false,
fn: function () {
var partitionsOk = (this.minPartitions <= this.partitions.length <= this.maxPartitions);
var aggregatesOk = (this.minAggregates <= this.aggregates.length <= this.maxAggregates);
return partitionsOk && aggregatesOk;
}
}
},
// Initialize the Filter:
// * set up callback to free internal state on remove
initialize: function () {
this.partitions.on('change', function (partition, options) {
if (this.isConfigured) {
// categorial partitions manage their own groups
if (partition.type !== 'categorial') {
partition.setGroups();
}
partition.updateSelection();
this.initDataFilter();
} else {
this.releaseDataFilter();
}
}, this);
this.partitions.on('add remove', function (partition, partitionsb, options) {
if (this.isConfigured) {
this.initDataFilter();
} else {
this.releaseDataFilter();
}
}, this);
this.on('remove', function () {
this.releaseDataFilter();
});
},
// Apply the separate filterFunctions from each partition in a single function
filterFunction: function () {
var fs = [];
this.partitions.forEach(function (partition) {
fs.push(partition.filterFunction());
});
return function (d) {
var groups = d.split('|');
return fs.every(function (f, i) { return f(groups[i]); });
};
}
});