dc.js Source: base-mixin.js (original) (raw)
/**
dc.baseMixin
is an abstract functional object representing a basicdc
chart objectfor all chart and widget implementations. Methods from the {@link #dc.baseMixin dc.baseMixin} are inherited
and available on all chart implementations in the
dc
library.@name baseMixin
@memberof dc
@mixin
@param {Object} _chart
@returns {dc.baseMixin} */ dc.baseMixin = function (_chart) { _chart.dcFlag = dc.utils.uniqueId();
var _dimension; var _group;
var _anchor; var _root; var _svg; var _isChild;
var _minWidth = 200; var _defaultWidthCalc = function (element) { var width = element && element.getBoundingClientRect && element.getBoundingClientRect().width; return (width && width > _minWidth) ? width : _minWidth; }; var _widthCalc = _defaultWidthCalc;
var _minHeight = 200; var _defaultHeightCalc = function (element) { var height = element && element.getBoundingClientRect && element.getBoundingClientRect().height; return (height && height > _minHeight) ? height : _minHeight; }; var _heightCalc = _defaultHeightCalc; var _width, _height; var _useViewBoxResizing = false;
var _keyAccessor = dc.pluck('key'); var _valueAccessor = dc.pluck('value'); var _label = dc.pluck('key');
var _ordering = dc.pluck('key');
var _renderLabel = false;
var _title = function (d) { return _chart.keyAccessor()(d) + ': ' + _chart.valueAccessor()(d); }; var _renderTitle = true; var _controlsUseVisibility = false;
var _transitionDuration = 750;
var _transitionDelay = 0;
var _filterPrinter = dc.printers.filters;
var _mandatoryAttributes = ['dimension', 'group'];
var _chartGroup = dc.constants.DEFAULT_CHART_GROUP;
var _listeners = d3.dispatch( 'preRender', 'postRender', 'preRedraw', 'postRedraw', 'filtered', 'zoomed', 'renderlet', 'pretransition'); var _legend; var _commitHandler;
var _filters = []; var _filterHandler = function (dimension, filters) { if (filters.length === 0) { dimension.filter(null); } else if (filters.length === 1 && !filters[0].isFiltered) { // single value and not a function-based filter dimension.filterExact(filters[0]); } else if (filters.length === 1 && filters[0].filterType === 'RangedFilter') { // single range-based filter dimension.filterRange(filters[0]); } else { dimension.filterFunction(function (d) { for (var i = 0; i < filters.length; i++) { var filter = filters[i]; if (filter.isFiltered && filter.isFiltered(d)) { return true; } else if (filter <= d && filter >= d) { return true; } } return false; }); } return filters; };
var _data = function (group) { return group.all(); };
/**
- Set or get the height attribute of a chart. The height is applied to the SVGElement generated by
- the chart when rendered (or re-rendered). If a value is given, then it will be used to calculate
- the new height and the chart returned for method chaining. The value can either be a numeric, a
- function, or falsy. If no value is specified then the value of the current height attribute will
- be returned.
- By default, without an explicit height being given, the chart will select the width of its
- anchor element. If that isn't possible it defaults to 200 (provided by the
- {@link dc.baseMixin#minHeight minHeight} property). Setting the value falsy will return
- the chart to the default behavior.
- @method height
- @memberof dc.baseMixin
- @instance
- @see {@link dc.baseMixin#minHeight minHeight}
- @example
- // Default height
- chart.height(function (element) {
var height = element && element.getBoundingClientRect && element.getBoundingClientRect().height;
return (height && height > chart.minHeight()) ? height : chart.minHeight();
- });
- chart.height(250); // Set the chart's height to 250px;
- chart.height(function(anchor) { return doSomethingWith(anchor); }); // set the chart's height with a function
- chart.height(null); // reset the height to the default auto calculation
- @param {Number|Function} [height]
- @returns {Number|dc.baseMixin} */ _chart.height = function (height) { if (!arguments.length) { if (!dc.utils.isNumber(_height)) { // only calculate once _height = _heightCalc(_root.node()); } return _height; } _heightCalc = height ? (typeof height === 'function' ? height : dc.utils.constant(height)) : _defaultHeightCalc; _height = undefined; return _chart; };
/**
- Set or get the width attribute of a chart.
- @method width
- @memberof dc.baseMixin
- @instance
- @see {@link dc.baseMixin#height height}
- @see {@link dc.baseMixin#minWidth minWidth}
- @example
- // Default width
- chart.width(function (element) {
var width = element && element.getBoundingClientRect && element.getBoundingClientRect().width;
return (width && width > chart.minWidth()) ? width : chart.minWidth();
- });
- @param {Number|Function} [width]
- @returns {Number|dc.baseMixin} */ _chart.width = function (width) { if (!arguments.length) { if (!dc.utils.isNumber(_width)) { // only calculate once _width = _widthCalc(_root.node()); } return _width; } _widthCalc = width ? (typeof width === 'function' ? width : dc.utils.constant(width)) : _defaultWidthCalc; _width = undefined; return _chart; };
/**
- Set or get the minimum width attribute of a chart. This only has effect when used with the default
- {@link dc.baseMixin#width width} function.
- @method minWidth
- @memberof dc.baseMixin
- @instance
- @see {@link dc.baseMixin#width width}
- @param {Number} [minWidth=200]
- @returns {Number|dc.baseMixin} */ _chart.minWidth = function (minWidth) { if (!arguments.length) { return _minWidth; } _minWidth = minWidth; return _chart; };
/**
- Set or get the minimum height attribute of a chart. This only has effect when used with the default
- {@link dc.baseMixin#height height} function.
- @method minHeight
- @memberof dc.baseMixin
- @instance
- @see {@link dc.baseMixin#height height}
- @param {Number} [minHeight=200]
- @returns {Number|dc.baseMixin} */ _chart.minHeight = function (minHeight) { if (!arguments.length) { return _minHeight; } _minHeight = minHeight; return _chart; };
/**
- Turn on/off using the SVG
- {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox
viewBox
attribute}. - When enabled,
viewBox
will be set on the svg root element instead ofwidth
andheight
. - Requires that the chart aspect ratio be defined using chart.width(w) and chart.height(h).
- This will maintain the aspect ratio while enabling the chart to resize responsively to the
- space given to the chart using CSS. For example, the chart can use `width: 100%; height:
- 100%` or absolute positioning to resize to its parent div.
- Since the text will be sized as if the chart is drawn according to the width and height, and
- will be resized if the chart is any other size, you need to set the chart width and height so
- that the text looks good. In practice, 600x400 seems to work pretty well for most charts.
- You can see examples of this resizing strategy in the [Chart Resizing
- Examples](http://dc-js.github.io/dc.js/resizing/); just add
?resize=viewbox
to any of the - one-chart examples to enable
useViewBoxResizing
. - @method useViewBoxResizing
- @memberof dc.baseMixin
- @instance
- @param {Boolean} [useViewBoxResizing=false]
- @returns {Boolean|dc.baseMixin} */ _chart.useViewBoxResizing = function (useViewBoxResizing) { if (!arguments.length) { return _useViewBoxResizing; } _useViewBoxResizing = useViewBoxResizing; return _chart; };
/**
- mandatory
- Set or get the dimension attribute of a chart. In
dc
, a dimension can be any valid - {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#dimension crossfilter dimension}
- If a value is given, then it will be used as the new dimension. If no value is specified then
- the current dimension will be returned.
- @method dimension
- @memberof dc.baseMixin
- @instance
- @see {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#dimension crossfilter.dimension}
- @example
- var index = crossfilter([]);
- var dimension = index.dimension(dc.pluck('key'));
- chart.dimension(dimension);
- @param {crossfilter.dimension} [dimension]
- @returns {crossfilter.dimension|dc.baseMixin} */ _chart.dimension = function (dimension) { if (!arguments.length) { return _dimension; } _dimension = dimension; _chart.expireCache(); return _chart; };
/**
- Set the data callback or retrieve the chart's data set. The data callback is passed the chart's
- group and by default will return
- {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group_all group.all}.
- This behavior may be modified to, for instance, return only the top 5 groups.
- @method data
- @memberof dc.baseMixin
- @instance
- @example
- // Default data function
- chart.data(function (group) { return group.all(); });
- chart.data(function (group) { return group.top(5); });
- @param {Function} [callback]
- @returns {*|dc.baseMixin} */ _chart.data = function (callback) { if (!arguments.length) { return _data.call(_chart, _group); } _data = typeof callback === 'function' ? callback : dc.utils.constant(callback); _chart.expireCache(); return _chart; };
/**
- mandatory
- Set or get the group attribute of a chart. In
dc
a group is a - {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group-map-reduce crossfilter group}.
- Usually the group should be created from the particular dimension associated with the same chart. If a value is
- given, then it will be used as the new group.
- If no value specified then the current group will be returned.
- If
name
is specified then it will be used to generate legend label. - @method group
- @memberof dc.baseMixin
- @instance
- @see {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group-map-reduce crossfilter.group}
- @example
- var index = crossfilter([]);
- var dimension = index.dimension(dc.pluck('key'));
- chart.dimension(dimension);
- chart.group(dimension.group().reduceSum());
- @param {crossfilter.group} [group]
- @param {String} [name]
- @returns {crossfilter.group|dc.baseMixin} */ _chart.group = function (group, name) { if (!arguments.length) { return _group; } _group = group; _chart._groupName = name; _chart.expireCache(); return _chart; };
/**
- Get or set an accessor to order ordinal dimensions. The chart uses
- {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort Array.sort}
- to sort elements; this accessor returns the value to order on.
- @method ordering
- @memberof dc.baseMixin
- @instance
- @example
- // Default ordering accessor
- _chart.ordering(dc.pluck('key'));
- @param {Function} [orderFunction]
- @returns {Function|dc.baseMixin} */ _chart.ordering = function (orderFunction) { if (!arguments.length) { return _ordering; } _ordering = orderFunction; _chart.expireCache(); return _chart; };
_chart._computeOrderedGroups = function (data) { // clone the array before sorting, otherwise Array.sort sorts in-place return data.slice().sort(function (a, b) { return _ordering(a) - _ordering(b) }); };
/**
- Clear all filters associated with this chart. The same effect can be achieved by calling
- {@link dc.baseMixin#filter chart.filter(null)}.
- @method filterAll
- @memberof dc.baseMixin
- @instance
- @returns {dc.baseMixin} */ _chart.filterAll = function () { return _chart.filter(null); };
/**
- Execute d3 single selection in the chart's scope using the given selector and return the d3
- selection.
- This function is not chainable since it does not return a chart instance; however the d3
- selection result can be chained to d3 function calls.
- @method select
- @memberof dc.baseMixin
- @instance
- @see {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3.select}
- @example
- // Has the same effect as d3.select('#chart-id').select(selector)
- chart.select(selector)
- @param {String} sel CSS selector string
- @returns {d3.selection} */ _chart.select = function (sel) { return _root.select(sel); };
/**
- Execute in scope d3 selectAll using the given selector and return d3 selection result.
- This function is not chainable since it does not return a chart instance; however the d3
- selection result can be chained to d3 function calls.
- @method selectAll
- @memberof dc.baseMixin
- @instance
- @see {@link https://github.com/d3/d3-selection/blob/master/README.md#selectAll d3.selectAll}
- @example
- // Has the same effect as d3.select('#chart-id').selectAll(selector)
- chart.selectAll(selector)
- @param {String} sel CSS selector string
- @returns {d3.selection} */ _chart.selectAll = function (sel) { return _root ? _root.selectAll(sel) : null; };
/**
- Set the root SVGElement to either be an existing chart's root; or any valid [d3 single
- selector](https://github.com/d3/d3-selection/blob/master/README.md#selecting-elements) specifying a dom
- block element such as a div; or a dom element or d3 selection. Optionally registers the chart
- within the chartGroup. This class is called internally on chart initialization, but be called
- again to relocate the chart. However, it will orphan any previously created SVGElements.
- @method anchor
- @memberof dc.baseMixin
- @instance
- @param {anchorChart|anchorSelector|anchorNode} [parent]
- @param {String} [chartGroup]
- @returns {String|node|d3.selection|dc.baseMixin} */ _chart.anchor = function (parent, chartGroup) { if (!arguments.length) { return _anchor; } if (dc.instanceOfChart(parent)) { _anchor = parent.anchor(); if (_anchor.children) { // is _anchor a div? _anchor = '#' + parent.anchorName(); } _root = parent.root(); _isChild = true; } else if (parent) { if (parent.select && parent.classed) { // detect d3 selection _anchor = parent.node(); } else { _anchor = parent; } _root = d3.select(_anchor); _root.classed(dc.constants.CHART_CLASS, true); dc.registerChart(_chart, chartGroup); _isChild = false; } else { throw new dc.errors.BadArgumentException('parent must be defined'); } _chartGroup = chartGroup; return _chart; };
/**
- Returns the DOM id for the chart's anchored location.
- @method anchorName
- @memberof dc.baseMixin
- @instance
- @returns {String} */ _chart.anchorName = function () { var a = _chart.anchor(); if (a && a.id) { return a.id; } if (a && a.replace) { return a.replace('#', ''); } return 'dc-chart' + _chart.chartID(); };
/**
- Returns the root element where a chart resides. Usually it will be the parent div element where
- the SVGElement was created. You can also pass in a new root element however this is usually handled by
- dc internally. Resetting the root element on a chart outside of dc internals may have
- unexpected consequences.
- @method root
- @memberof dc.baseMixin
- @instance
- @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement HTMLElement}
- @param {HTMLElement} [rootElement]
- @returns {HTMLElement|dc.baseMixin} */ _chart.root = function (rootElement) { if (!arguments.length) { return _root; } _root = rootElement; return _chart; };
/**
- Returns the top SVGElement for this specific chart. You can also pass in a new SVGElement,
- however this is usually handled by dc internally. Resetting the SVGElement on a chart outside
- of dc internals may have unexpected consequences.
- @method svg
- @memberof dc.baseMixin
- @instance
- @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGElement SVGElement}
- @param {SVGElement|d3.selection} [svgElement]
- @returns {SVGElement|d3.selection|dc.baseMixin} */ _chart.svg = function (svgElement) { if (!arguments.length) { return _svg; } _svg = svgElement; return _chart; };
/**
- Remove the chart's SVGElements from the dom and recreate the container SVGElement.
- @method resetSvg
- @memberof dc.baseMixin
- @instance
- @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGElement SVGElement}
- @returns {SVGElement} */ _chart.resetSvg = function () { _chart.select('svg').remove(); return generateSvg(); };
function sizeSvg () { if (_svg) { if (!_useViewBoxResizing) { _svg .attr('width', _chart.width()) .attr('height', _chart.height()); } else if (!_svg.attr('viewBox')) { _svg .attr('viewBox', '0 0 ' + _chart.width() + ' ' + _chart.height()); } } }
function generateSvg () { _svg = _chart.root().append('svg'); sizeSvg(); return _svg; }
/**
- Set or get the filter printer function. The filter printer function is used to generate human
- friendly text for filter value(s) associated with the chart instance. The text will get shown
- in the `.filter element; see {@link dc.baseMixin#turnOnControls turnOnControls}.
- By default dc charts use a default filter printer {@link dc.printers.filters dc.printers.filters}
- that provides simple printing support for both single value and ranged filters.
- @method filterPrinter
- @memberof dc.baseMixin
- @instance
- @example
- // for a chart with an ordinal brush, print the filters in upper case
- chart.filterPrinter(function(filters) {
- return filters.map(function(f) { return f.toUpperCase(); }).join(', ');
- });
- // for a chart with a range brush, print the filter as start and extent
- chart.filterPrinter(function(filters) {
- return 'start ' + dc.utils.printSingleValue(filters[0][0]) +
' extent ' + dc.utils.printSingleValue(filters[0][1] - filters[0][0]);
- });
- @param {Function} [filterPrinterFunction=dc.printers.filters]
- @returns {Function|dc.baseMixin} */ _chart.filterPrinter = function (filterPrinterFunction) { if (!arguments.length) { return _filterPrinter; } _filterPrinter = filterPrinterFunction; return _chart; };
/**
- If set, use the
visibility
attribute instead of thedisplay
attribute for showing/hiding - chart reset and filter controls, for less disruption to the layout.
- @method controlsUseVisibility
- @memberof dc.baseMixin
- @instance
- @param {Boolean} [controlsUseVisibility=false]
- @returns {Boolean|dc.baseMixin} **/ _chart.controlsUseVisibility = function (controlsUseVisibility) { if (!arguments.length) { return _controlsUseVisibility; } _controlsUseVisibility = controlsUseVisibility; return _chart; };
/**
- Turn on optional control elements within the root element. dc currently supports the
- following html control elements.
- root.selectAll('.reset') - elements are turned on if the chart has an active filter. This type
- of control element is usually used to store a reset link to allow user to reset filter on a
- certain chart. This element will be turned off automatically if the filter is cleared.
- root.selectAll('.filter') elements are turned on if the chart has an active filter. The text
- content of this element is then replaced with the current filter value using the filter printer
- function. This type of element will be turned off automatically if the filter is cleared.
- @method turnOnControls
- @memberof dc.baseMixin
- @instance
- @returns {dc.baseMixin} */ _chart.turnOnControls = function () { if (_root) { var attribute = _chart.controlsUseVisibility() ? 'visibility' : 'display'; _chart.selectAll('.reset').style(attribute, null); _chart.selectAll('.filter').text(_filterPrinter(_chart.filters())).style(attribute, null); } return _chart; };
/**
- Turn off optional control elements within the root element.
- @method turnOffControls
- @memberof dc.baseMixin
- @see {@link dc.baseMixin#turnOnControls turnOnControls}
- @instance
- @returns {dc.baseMixin} */ _chart.turnOffControls = function () { if (_root) { var attribute = _chart.controlsUseVisibility() ? 'visibility' : 'display'; var value = _chart.controlsUseVisibility() ? 'hidden' : 'none'; _chart.selectAll('.reset').style(attribute, value); _chart.selectAll('.filter').style(attribute, value).text(_chart.filter()); } return _chart; };
/**
- Set or get the animation transition duration (in milliseconds) for this chart instance.
- @method transitionDuration
- @memberof dc.baseMixin
- @instance
- @param {Number} [duration=750]
- @returns {Number|dc.baseMixin} */ _chart.transitionDuration = function (duration) { if (!arguments.length) { return _transitionDuration; } _transitionDuration = duration; return _chart; };
/**
- Set or get the animation transition delay (in milliseconds) for this chart instance.
- @method transitionDelay
- @memberof dc.baseMixin
- @instance
- @param {Number} [delay=0]
- @returns {Number|dc.baseMixin} */ _chart.transitionDelay = function (delay) { if (!arguments.length) { return _transitionDelay; } _transitionDelay = delay; return _chart; };
_chart.mandatoryAttributes = function () { if (!arguments.length) { return _mandatoryAttributes; } _mandatoryAttributes = _; return _chart; };
function checkForMandatoryAttributes (a) { if (!_chart[a] || !_charta) { throw new dc.errors.InvalidStateException('Mandatory attribute chart.' + a + ' is missing on chart[#' + _chart.anchorName() + ']'); } }
/**
Invoking this method will force the chart to re-render everything from scratch. Generally it
should only be used to render the chart for the first time on the page or if you want to make
sure everything is redrawn from scratch instead of relying on the default incremental redrawing
behaviour.
@method render
@memberof dc.baseMixin
@instance
@returns {dc.baseMixin} */ _chart.render = function () { _height = _width = undefined; // force recalculate _listeners.call('preRender', _chart, _chart);
if (_mandatoryAttributes) { _mandatoryAttributes.forEach(checkForMandatoryAttributes); }
var result = _chart._doRender();
if (_legend) { _legend.render(); }
_chart._activateRenderlets('postRender');
return result;
};
_chart._activateRenderlets = function (event) { _listeners.call('pretransition', _chart, _chart); if (_chart.transitionDuration() > 0 && _svg) { _svg.transition().duration(_chart.transitionDuration()).delay(_chart.transitionDelay()) .on('end', function () { _listeners.call('renderlet', _chart, _chart); if (event) { _listeners.call(event, _chart, _chart); } }); } else { _listeners.call('renderlet', _chart, _chart); if (event) { _listeners.call(event, _chart, _chart); } } };
/**
Calling redraw will cause the chart to re-render data changes incrementally. If there is no
change in the underlying data dimension then calling this method will have no effect on the
chart. Most chart interaction in dc will automatically trigger this method through internal
events (in particular {@link dc.redrawAll dc.redrawAll}); therefore, you only need to
manually invoke this function if data is manipulated outside of dc's control (for example if
data is loaded in the background using
{@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#crossfilter_add crossfilter.add}).
@method redraw
@memberof dc.baseMixin
@instance
@returns {dc.baseMixin} */ _chart.redraw = function () { sizeSvg(); _listeners.call('preRedraw', _chart, _chart);
var result = _chart._doRedraw();
if (_legend) { _legend.render(); }
_chart._activateRenderlets('postRedraw');
return result;
};
/**
- Gets/sets the commit handler. If the chart has a commit handler, the handler will be called when
- the chart's filters have changed, in order to send the filter data asynchronously to a server.
- Unlike other functions in dc.js, the commit handler is asynchronous. It takes two arguments:
- a flag indicating whether this is a render (true) or a redraw (false), and a callback to be
- triggered once the commit is done. The callback has the standard node.js continuation signature
- with error first and result second.
- @method commitHandler
- @param {Function} commitHandler
- @memberof dc.baseMixin
- @instance
- @returns {dc.baseMixin} */ _chart.commitHandler = function (commitHandler) { if (!arguments.length) { return _commitHandler; } _commitHandler = commitHandler; return _chart; };
/**
- Redraws all charts in the same group as this chart, typically in reaction to a filter
- change. If the chart has a {@link dc.baseMixin.commitFilter commitHandler}, it will
- be executed and waited for.
- @method redrawGroup
- @memberof dc.baseMixin
- @instance
- @returns {dc.baseMixin} */ _chart.redrawGroup = function () { if (_commitHandler) { _commitHandler(false, function (error, result) { if (error) { console.log(error); } else { dc.redrawAll(_chart.chartGroup()); } }); } else { dc.redrawAll(_chart.chartGroup()); } return _chart; };
/**
- Renders all charts in the same group as this chart. If the chart has a
- {@link dc.baseMixin.commitFilter commitHandler}, it will be executed and waited for
- @method renderGroup
- @memberof dc.baseMixin
- @instance
- @returns {dc.baseMixin} */ _chart.renderGroup = function () { if (_commitHandler) { _commitHandler(false, function (error, result) { if (error) { console.log(error); } else { dc.renderAll(_chart.chartGroup()); } }); } else { dc.renderAll(_chart.chartGroup()); } return _chart; };
_chart._invokeFilteredListener = function (f) { if (f !== undefined) { _listeners.call('filtered', _chart, _chart, f); } };
_chart._invokeZoomedListener = function () { _listeners.call('zoomed', _chart, _chart); };
var _hasFilterHandler = function (filters, filter) { if (filter === null || typeof(filter) === 'undefined') { return filters.length > 0; } return filters.some(function (f) { return filter <= f && filter >= f; }); };
/**
- Set or get the has-filter handler. The has-filter handler is a function that checks to see if
- the chart's current filters (first argument) include a specific filter (second argument). Using a custom has-filter handler allows
- you to change the way filters are checked for and replaced.
- @method hasFilterHandler
- @memberof dc.baseMixin
- @instance
- @example
- // default has-filter handler
- chart.hasFilterHandler(function (filters, filter) {
if (filter === null || typeof(filter) === 'undefined') {
return filters.length > 0;
}
return filters.some(function (f) {
return filter <= f && filter >= f;
});
- });
- // custom filter handler (no-op)
- chart.hasFilterHandler(function(filters, filter) {
return false;
- });
- @param {Function} [hasFilterHandler]
- @returns {Function|dc.baseMixin} */ _chart.hasFilterHandler = function (hasFilterHandler) { if (!arguments.length) { return _hasFilterHandler; } _hasFilterHandler = hasFilterHandler; return _chart; };
/**
- Check whether any active filter or a specific filter is associated with particular chart instance.
- This function is not chainable.
- @method hasFilter
- @memberof dc.baseMixin
- @instance
- @see {@link dc.baseMixin#hasFilterHandler hasFilterHandler}
- @param {*} [filter]
- @returns {Boolean} */ _chart.hasFilter = function (filter) { return _hasFilterHandler(_filters, filter); };
var _removeFilterHandler = function (filters, filter) { for (var i = 0; i < filters.length; i++) { if (filters[i] <= filter && filters[i] >= filter) { filters.splice(i, 1); break; } } return filters; };
/**
- Set or get the remove filter handler. The remove filter handler is a function that removes a
- filter from the chart's current filters. Using a custom remove filter handler allows you to
- change how filters are removed or perform additional work when removing a filter, e.g. when
- using a filter server other than crossfilter.
- The handler should return a new or modified array as the result.
- @method removeFilterHandler
- @memberof dc.baseMixin
- @instance
- @example
- // default remove filter handler
- chart.removeFilterHandler(function (filters, filter) {
for (var i = 0; i < filters.length; i++) {
if (filters[i] <= filter && filters[i] >= filter) {
filters.splice(i, 1);
break;
}
}
return filters;
- });
- // custom filter handler (no-op)
- chart.removeFilterHandler(function(filters, filter) {
return filters;
- });
- @param {Function} [removeFilterHandler]
- @returns {Function|dc.baseMixin} */ _chart.removeFilterHandler = function (removeFilterHandler) { if (!arguments.length) { return _removeFilterHandler; } _removeFilterHandler = removeFilterHandler; return _chart; };
var _addFilterHandler = function (filters, filter) { filters.push(filter); return filters; };
/**
- Set or get the add filter handler. The add filter handler is a function that adds a filter to
- the chart's filter list. Using a custom add filter handler allows you to change the way filters
- are added or perform additional work when adding a filter, e.g. when using a filter server other
- than crossfilter.
- The handler should return a new or modified array as the result.
- @method addFilterHandler
- @memberof dc.baseMixin
- @instance
- @example
- // default add filter handler
- chart.addFilterHandler(function (filters, filter) {
filters.push(filter);
return filters;
- });
- // custom filter handler (no-op)
- chart.addFilterHandler(function(filters, filter) {
return filters;
- });
- @param {Function} [addFilterHandler]
- @returns {Function|dc.baseMixin} */ _chart.addFilterHandler = function (addFilterHandler) { if (!arguments.length) { return _addFilterHandler; } _addFilterHandler = addFilterHandler; return _chart; };
var _resetFilterHandler = function (filters) { return []; };
/**
- Set or get the reset filter handler. The reset filter handler is a function that resets the
- chart's filter list by returning a new list. Using a custom reset filter handler allows you to
- change the way filters are reset, or perform additional work when resetting the filters,
- e.g. when using a filter server other than crossfilter.
- The handler should return a new or modified array as the result.
- @method resetFilterHandler
- @memberof dc.baseMixin
- @instance
- @example
- // default remove filter handler
- function (filters) {
return [];
- }
- // custom filter handler (no-op)
- chart.resetFilterHandler(function(filters) {
return filters;
- });
- @param {Function} [resetFilterHandler]
- @returns {dc.baseMixin} */ _chart.resetFilterHandler = function (resetFilterHandler) { if (!arguments.length) { return _resetFilterHandler; } _resetFilterHandler = resetFilterHandler; return _chart; };
function applyFilters (filters) { if (_chart.dimension() && _chart.dimension().filter) { var fs = _filterHandler(_chart.dimension(), filters); if (fs) { filters = fs; } } return filters; }
/**
- Replace the chart filter. This is equivalent to calling
chart.filter(null).filter(filter)
- but more efficient because the filter is only applied once.
- @method replaceFilter
- @memberof dc.baseMixin
- @instance
- @param {*} [filter]
- @returns {dc.baseMixin} **/ _chart.replaceFilter = function (filter) { _filters = _resetFilterHandler(_filters); _chart.filter(filter); return _chart; };
/**
Filter the chart by the given parameter, or return the current filter if no input parameter
is given.
The filter parameter can take one of these forms:
- A single value: the value will be toggled (added if it is not present in the current
filters, removed if it is present)
- An array containing a single array of values (
[[value,value,value]]
): each value is
- An array containing a single array of values (
toggled
- When appropriate for the chart, a {@link dc.filters dc filter object} such as
- {@link dc.filters.RangedFilter
dc.filters.RangedFilter
} for the
- {@link dc.filters.RangedFilter
{@link dc.coordinateGridMixin dc.coordinateGridMixin} charts
- {@link dc.filters.TwoDimensionalFilter
dc.filters.TwoDimensionalFilter
} for the
- {@link dc.filters.TwoDimensionalFilter
{@link dc.heatMap heat map}
- {@link dc.filters.RangedTwoDimensionalFilter
dc.filters.RangedTwoDimensionalFilter
}
- {@link dc.filters.RangedTwoDimensionalFilter
for the {@link dc.scatterPlot scatter plot}
null
: the filter will be reset using the
{@link dc.baseMixin#resetFilterHandler resetFilterHandler}
Note that this is always a toggle (even when it doesn't make sense for the filter type). If
you wish to replace the current filter, either call
chart.filter(null)
first - or it's moreefficient to call {@link dc.baseMixin#replaceFilter
chart.replaceFilter(filter)
} instead.Each toggle is executed by checking if the value is already present using the
{@link dc.baseMixin#hasFilterHandler hasFilterHandler}; if it is not present, it is added
using the {@link dc.baseMixin#addFilterHandler addFilterHandler}; if it is already present,
it is removed using the {@link dc.baseMixin#removeFilterHandler removeFilterHandler}.
Once the filters array has been updated, the filters are applied to the
crossfilter dimension, using the {@link dc.baseMixin#filterHandler filterHandler}.
Once you have set the filters, call {@link dc.baseMixin#redrawGroup
chart.redrawGroup()
}(or {@link dc.redrawAll
dc.redrawAll()
}) to redraw the chart's group.@method filter
@memberof dc.baseMixin
@instance
@see {@link dc.baseMixin#addFilterHandler addFilterHandler}
@see {@link dc.baseMixin#removeFilterHandler removeFilterHandler}
@see {@link dc.baseMixin#resetFilterHandler resetFilterHandler}
@see {@link dc.baseMixin#filterHandler filterHandler}
@example
// filter by a single string
chart.filter('Sunday');
// filter by a single age
chart.filter(18);
// filter by a set of states
chart.filter([['MA', 'TX', 'ND', 'WA']]);
// filter by range -- note the use of dc.filters.RangedFilter, which is different
// from the syntax for filtering a crossfilter dimension directly, dimension.filter([15,20])
chart.filter(dc.filters.RangedFilter(15,20));
@param {*} [filter]
@returns {dc.baseMixin} */ _chart.filter = function (filter) { if (!arguments.length) { return _filters.length > 0 ? _filters[0] : null; } var filters = _filters; if (filter instanceof Array && filter[0] instanceof Array && !filter.isFiltered) { // toggle each filter filter[0].forEach(function (f) { if (_hasFilterHandler(filters, f)) { filters = _removeFilterHandler(filters, f); } else { filters = _addFilterHandler(filters, f); } }); } else if (filter === null) { filters = _resetFilterHandler(filters); } else { if (_hasFilterHandler(filters, filter)) { filters = _removeFilterHandler(filters, filter); } else { filters = _addFilterHandler(filters, filter); } } _filters = applyFilters(filters); _chart._invokeFilteredListener(filter);
if (_root !== null && _chart.hasFilter()) { _chart.turnOnControls(); } else { _chart.turnOffControls(); }
return _chart;
};
/**
- Returns all current filters. This method does not perform defensive cloning of the internal
- filter array before returning, therefore any modification of the returned array will effect the
- chart's internal filter storage.
- @method filters
- @memberof dc.baseMixin
- @instance
- @returns {Array<*>} */ _chart.filters = function () { return _filters; };
_chart.highlightSelected = function (e) { d3.select(e).classed(dc.constants.SELECTED_CLASS, true); d3.select(e).classed(dc.constants.DESELECTED_CLASS, false); };
_chart.fadeDeselected = function (e) { d3.select(e).classed(dc.constants.SELECTED_CLASS, false); d3.select(e).classed(dc.constants.DESELECTED_CLASS, true); };
_chart.resetHighlight = function (e) { d3.select(e).classed(dc.constants.SELECTED_CLASS, false); d3.select(e).classed(dc.constants.DESELECTED_CLASS, false); };
/**
- This function is passed to d3 as the onClick handler for each chart. The default behavior is to
- filter on the clicked datum (passed to the callback) and redraw the chart group.
- This function can be replaced in order to change the click behavior (but first look at
- @method onClick
- @memberof dc.baseMixin
- @instance
- @example
- var oldHandler = chart.onClick;
- chart.onClick = function(datum) {
- // use datum.
- @param {*} datum
- @return {undefined} */ _chart.onClick = function (datum) { var filter = _chart.keyAccessor()(datum); dc.events.trigger(function () { _chart.filter(filter); _chart.redrawGroup(); }); };
/**
- Set or get the filter handler. The filter handler is a function that performs the filter action
- on a specific dimension. Using a custom filter handler allows you to perform additional logic
- before or after filtering.
- @method filterHandler
- @memberof dc.baseMixin
- @instance
- @see {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#dimension_filter crossfilter.dimension.filter}
- @example
- // the default filter handler handles all possible cases for the charts in dc.js
- // you can replace it with something more specialized for your own chart
- chart.filterHandler(function (dimension, filters) {
if (filters.length === 0) {
// the empty case (no filtering)
dimension.filter(null);
} else if (filters.length === 1 && !filters[0].isFiltered) {
// single value and not a function-based filter
dimension.filterExact(filters[0]);
} else if (filters.length === 1 && filters[0].filterType === 'RangedFilter') {
// single range-based filter
dimension.filterRange(filters[0]);
} else {
// an array of values, or an array of filter objects
dimension.filterFunction(function (d) {
for (var i = 0; i < filters.length; i++) {
var filter = filters[i];
if (filter.isFiltered && filter.isFiltered(d)) {
return true;
} else if (filter <= d && filter >= d) {
return true;
}
}
return false;
});
}
return filters;
- });
- // custom filter handler
- chart.filterHandler(function(dimension, filter){
var newFilter = filter + 10;
dimension.filter(newFilter);
return newFilter; // set the actual filter value to the new value
- });
- @param {Function} [filterHandler]
- @returns {Function|dc.baseMixin} */ _chart.filterHandler = function (filterHandler) { if (!arguments.length) { return _filterHandler; } _filterHandler = filterHandler; return _chart; };
// abstract function stub _chart._doRender = function () { // do nothing in base, should be overridden by sub-function return _chart; };
_chart._doRedraw = function () { // do nothing in base, should be overridden by sub-function return _chart; };
_chart.legendables = function () { // do nothing in base, should be overridden by sub-function return []; };
_chart.legendHighlight = function () { // do nothing in base, should be overridden by sub-function };
_chart.legendReset = function () { // do nothing in base, should be overridden by sub-function };
_chart.legendToggle = function () { // do nothing in base, should be overriden by sub-function };
_chart.isLegendableHidden = function () { // do nothing in base, should be overridden by sub-function return false; };
/**
- Set or get the key accessor function. The key accessor function is used to retrieve the key
- value from the crossfilter group. Key values are used differently in different charts, for
- example keys correspond to slices in a pie chart and x axis positions in a grid coordinate chart.
- @method keyAccessor
- @memberof dc.baseMixin
- @instance
- @example
- // default key accessor
- chart.keyAccessor(function(d) { return d.key; });
- // custom key accessor for a multi-value crossfilter reduction
- chart.keyAccessor(function(p) { return p.value.absGain; });
- @param {Function} [keyAccessor]
- @returns {Function|dc.baseMixin} */ _chart.keyAccessor = function (keyAccessor) { if (!arguments.length) { return _keyAccessor; } _keyAccessor = keyAccessor; return _chart; };
/**
- Set or get the value accessor function. The value accessor function is used to retrieve the
- value from the crossfilter group. Group values are used differently in different charts, for
- example values correspond to slice sizes in a pie chart and y axis positions in a grid
- coordinate chart.
- @method valueAccessor
- @memberof dc.baseMixin
- @instance
- @example
- // default value accessor
- chart.valueAccessor(function(d) { return d.value; });
- // custom value accessor for a multi-value crossfilter reduction
- chart.valueAccessor(function(p) { return p.value.percentageGain; });
- @param {Function} [valueAccessor]
- @returns {Function|dc.baseMixin} */ _chart.valueAccessor = function (valueAccessor) { if (!arguments.length) { return _valueAccessor; } _valueAccessor = valueAccessor; return _chart; };
/**
- Set or get the label function. The chart class will use this function to render labels for each
- child element in the chart, e.g. slices in a pie chart or bubbles in a bubble chart. Not every
- chart supports the label function, for example line chart does not use this function
- at all. By default, enables labels; pass false for the second parameter if this is not desired.
- @method label
- @memberof dc.baseMixin
- @instance
- @example
- // default label function just return the key
- chart.label(function(d) { return d.key; });
- // label function has access to the standard d3 data binding and can get quite complicated
- chart.label(function(d) { return d.data.key + '(' + Math.floor(d.data.value / all.value() * 100) + '%)'; });
- @param {Function} [labelFunction]
- @param {Boolean} [enableLabels=true]
- @returns {Function|dc.baseMixin} */ _chart.label = function (labelFunction, enableLabels) { if (!arguments.length) { return _label; } _label = labelFunction; if ((enableLabels === undefined) || enableLabels) { _renderLabel = true; } return _chart; };
/**
- Turn on/off label rendering
- @method renderLabel
- @memberof dc.baseMixin
- @instance
- @param {Boolean} [renderLabel=false]
- @returns {Boolean|dc.baseMixin} */ _chart.renderLabel = function (renderLabel) { if (!arguments.length) { return _renderLabel; } _renderLabel = renderLabel; return _chart; };
/**
- Set or get the title function. The chart class will use this function to render the SVGElement title
- (usually interpreted by browser as tooltips) for each child element in the chart, e.g. a slice
- in a pie chart or a bubble in a bubble chart. Almost every chart supports the title function;
- however in grid coordinate charts you need to turn off the brush in order to see titles, because
- otherwise the brush layer will block tooltip triggering.
- @method title
- @memberof dc.baseMixin
- @instance
- @example
- // default title function shows "key: value"
- chart.title(function(d) { return d.key + ': ' + d.value; });
- // title function has access to the standard d3 data binding and can get quite complicated
- chart.title(function(p) {
- return p.key.getFullYear()
+ '\n'
+ 'Index Gain: ' + numberFormat(p.value.absGain) + '\n'
+ 'Index Gain in Percentage: ' + numberFormat(p.value.percentageGain) + '%\n'
+ 'Fluctuation / Index Ratio: ' + numberFormat(p.value.fluctuationPercentage) + '%';
- });
- @param {Function} [titleFunction]
- @returns {Function|dc.baseMixin} */ _chart.title = function (titleFunction) { if (!arguments.length) { return _title; } _title = titleFunction; return _chart; };
/**
- Turn on/off title rendering, or return the state of the render title flag if no arguments are
- given.
- @method renderTitle
- @memberof dc.baseMixin
- @instance
- @param {Boolean} [renderTitle=true]
- @returns {Boolean|dc.baseMixin} */ _chart.renderTitle = function (renderTitle) { if (!arguments.length) { return _renderTitle; } _renderTitle = renderTitle; return _chart; };
/**
- A renderlet is similar to an event listener on rendering event. Multiple renderlets can be added
- to an individual chart. Each time a chart is rerendered or redrawn the renderlets are invoked
- right after the chart finishes its transitions, giving you a way to modify the SVGElements.
- Renderlet functions take the chart instance as the only input parameter and you can
- use the dc API or use raw d3 to achieve pretty much any effect.
- Use {@link dc.baseMixin#on on} with a 'renderlet' prefix.
- Generates a random key for the renderlet, which makes it hard to remove.
- @method renderlet
- @memberof dc.baseMixin
- @instance
- @deprecated
- @example
- // do this instead of .renderlet(function(chart) { ... })
- chart.on("renderlet", function(chart){
// mix of dc API and d3 manipulation
chart.select('g.y').style('display', 'none');
// its a closure so you can also access other chart variable available in the closure scope
moveChart.filter(chart.filter());
- });
- @param {Function} renderletFunction
- @returns {dc.baseMixin} */ _chart.renderlet = dc.logger.deprecate(function (renderletFunction) { _chart.on('renderlet.' + dc.utils.uniqueId(), renderletFunction); return _chart; }, 'chart.renderlet has been deprecated. Please use chart.on("renderlet.", renderletFunction)');
/**
- Get or set the chart group to which this chart belongs. Chart groups are rendered or redrawn
- together since it is expected they share the same underlying crossfilter data set.
- @method chartGroup
- @memberof dc.baseMixin
- @instance
- @param {String} [chartGroup]
- @returns {String|dc.baseMixin} */ _chart.chartGroup = function (chartGroup) { if (!arguments.length) { return _chartGroup; } if (!_isChild) { dc.deregisterChart(_chart, _chartGroup); } _chartGroup = chartGroup; if (!_isChild) { dc.registerChart(_chart, _chartGroup); } return _chart; };
/**
- Expire the internal chart cache. dc charts cache some data internally on a per chart basis to
- speed up rendering and avoid unnecessary calculation; however it might be useful to clear the
- cache if you have changed state which will affect rendering. For example, if you invoke
- {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#crossfilter_add crossfilter.add}
- function or reset group or dimension after rendering, it is a good idea to
- clear the cache to make sure charts are rendered properly.
- @method expireCache
- @memberof dc.baseMixin
- @instance
- @returns {dc.baseMixin} */ _chart.expireCache = function () { // do nothing in base, should be overridden by sub-function return _chart; };
/**
- Attach a dc.legend widget to this chart. The legend widget will automatically draw legend labels
- based on the color setting and names associated with each group.
- @method legend
- @memberof dc.baseMixin
- @instance
- @example
- chart.legend(dc.legend().x(400).y(10).itemHeight(13).gap(5))
- @param {dc.legend} [legend]
- @returns {dc.legend|dc.baseMixin} */ _chart.legend = function (legend) { if (!arguments.length) { return _legend; } _legend = legend; _legend.parent(_chart); return _chart; };
/**
- Returns the internal numeric ID of the chart.
- @method chartID
- @memberof dc.baseMixin
- @instance
- @returns {String} */ _chart.chartID = function () { return _chart.dcFlag; };
/**
Set chart options using a configuration object. Each key in the object will cause the method of
the same name to be called with the value to set that attribute for the chart.
@method options
@memberof dc.baseMixin
@instance
@example
chart.options({dimension: myDimension, group: myGroup});
@param {{}} opts
@returns {dc.baseMixin} */ _chart.options = function (opts) { var applyOptions = [ 'anchor', 'group', 'xAxisLabel', 'yAxisLabel', 'stack', 'title', 'point', 'getColor', 'overlayGeoJson' ];
for (var o in opts) { if (typeof(_chart[o]) === 'function') { if (opts[o] instanceof Array && applyOptions.indexOf(o) !== -1) { _chart[o].apply(_chart, opts[o]); } else { _chart[o].call(_chart, opts[o]); } } else { dc.logger.debug('Not a valid option setter name: ' + o); } } return _chart;
};
/**
- All dc chart instance supports the following listeners.
- Supports the following events:
renderlet
- This listener function will be invoked after transitions after redraw and render. Replaces the
- deprecated {@link dc.baseMixin#renderlet renderlet} method.
pretransition
- Like.on('renderlet', ...)
but the event is fired before transitions start.
preRender
- This listener function will be invoked before chart rendering.
postRender
- This listener function will be invoked after chart finish rendering including
- all renderlets' logic.
preRedraw
- This listener function will be invoked before chart redrawing.
postRedraw
- This listener function will be invoked after chart finish redrawing
- including all renderlets' logic.
filtered
- This listener function will be invoked after a filter is applied, added or removed.
zoomed
- This listener function will be invoked after a zoom is triggered.
- @method on
- @memberof dc.baseMixin
- @instance
- @see {@link https://github.com/d3/d3-dispatch/blob/master/README.md#dispatch_on d3.dispatch.on}
- @example
- .on('renderlet', function(chart, filter){...})
- .on('pretransition', function(chart, filter){...})
- .on('preRender', function(chart){...})
- .on('postRender', function(chart){...})
- .on('preRedraw', function(chart){...})
- .on('postRedraw', function(chart){...})
- .on('filtered', function(chart, filter){...})
- .on('zoomed', function(chart, filter){...})
- @param {String} event
- @param {Function} listener
- @returns {dc.baseMixin} */ _chart.on = function (event, listener) { _listeners.on(event, listener); return _chart; };
return _chart;
};