UNPKG

dc

Version:

A multi-dimensional charting library built to work natively with crossfilter and rendered using d3.js

1,609 lines (1,461 loc) 472 kB
/*! * dc 3.1.4 * http://dc-js.github.io/dc.js/ * Copyright 2012-2019 Nick Zhu & the dc.js Developers * https://github.com/dc-js/dc.js/blob/master/AUTHORS * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ (function() { function _dc(d3, crossfilter) { 'use strict'; /** * The entire dc.js library is scoped under the **dc** name space. It does not introduce * anything else into the global name space. * * Most `dc` functions are designed to allow function chaining, meaning they return the current chart * instance whenever it is appropriate. The getter forms of functions do not participate in function * chaining because they return values that are not the chart, although some, * such as {@link dc.baseMixin#svg .svg} and {@link dc.coordinateGridMixin#xAxis .xAxis}, * return values that are themselves chainable d3 objects. * @namespace dc * @version 3.1.4 * @example * // Example chaining * chart.width(300) * .height(300) * .filter('sunday'); */ var dc = { version: '3.1.4', constants: { CHART_CLASS: 'dc-chart', DEBUG_GROUP_CLASS: 'debug', STACK_CLASS: 'stack', DESELECTED_CLASS: 'deselected', SELECTED_CLASS: 'selected', NODE_INDEX_NAME: '__index__', GROUP_INDEX_NAME: '__group_index__', DEFAULT_CHART_GROUP: '__default_chart_group__', EVENT_DELAY: 40, NEGLIGIBLE_NUMBER: 1e-10 }, _renderlet: null }; /** * The dc.chartRegistry object maintains sets of all instantiated dc.js charts under named groups * and the default group. * * A chart group often corresponds to a crossfilter instance. It specifies * the set of charts which should be updated when a filter changes on one of the charts or when the * global functions {@link dc.filterAll dc.filterAll}, {@link dc.refocusAll dc.refocusAll}, * {@link dc.renderAll dc.renderAll}, {@link dc.redrawAll dc.redrawAll}, or chart functions * {@link dc.baseMixin#renderGroup baseMixin.renderGroup}, * {@link dc.baseMixin#redrawGroup baseMixin.redrawGroup} are called. * * @namespace chartRegistry * @memberof dc * @type {{has, register, deregister, clear, list}} */ dc.chartRegistry = (function () { // chartGroup:string => charts:array var _chartMap = {}; function initializeChartGroup (group) { if (!group) { group = dc.constants.DEFAULT_CHART_GROUP; } if (!_chartMap[group]) { _chartMap[group] = []; } return group; } return { /** * Determine if a given chart instance resides in any group in the registry. * @method has * @memberof dc.chartRegistry * @param {Object} chart dc.js chart instance * @returns {Boolean} */ has: function (chart) { for (var e in _chartMap) { if (_chartMap[e].indexOf(chart) >= 0) { return true; } } return false; }, /** * Add given chart instance to the given group, creating the group if necessary. * If no group is provided, the default group `dc.constants.DEFAULT_CHART_GROUP` will be used. * @method register * @memberof dc.chartRegistry * @param {Object} chart dc.js chart instance * @param {String} [group] Group name * @return {undefined} */ register: function (chart, group) { group = initializeChartGroup(group); _chartMap[group].push(chart); }, /** * Remove given chart instance from the given group, creating the group if necessary. * If no group is provided, the default group `dc.constants.DEFAULT_CHART_GROUP` will be used. * @method deregister * @memberof dc.chartRegistry * @param {Object} chart dc.js chart instance * @param {String} [group] Group name * @return {undefined} */ deregister: function (chart, group) { group = initializeChartGroup(group); for (var i = 0; i < _chartMap[group].length; i++) { if (_chartMap[group][i].anchorName() === chart.anchorName()) { _chartMap[group].splice(i, 1); break; } } }, /** * Clear given group if one is provided, otherwise clears all groups. * @method clear * @memberof dc.chartRegistry * @param {String} group Group name * @return {undefined} */ clear: function (group) { if (group) { delete _chartMap[group]; } else { _chartMap = {}; } }, /** * Get an array of each chart instance in the given group. * If no group is provided, the charts in the default group are returned. * @method list * @memberof dc.chartRegistry * @param {String} [group] Group name * @returns {Array<Object>} */ list: function (group) { group = initializeChartGroup(group); return _chartMap[group]; } }; })(); /** * Add given chart instance to the given group, creating the group if necessary. * If no group is provided, the default group `dc.constants.DEFAULT_CHART_GROUP` will be used. * @memberof dc * @method registerChart * @param {Object} chart dc.js chart instance * @param {String} [group] Group name * @return {undefined} */ dc.registerChart = function (chart, group) { dc.chartRegistry.register(chart, group); }; /** * Remove given chart instance from the given group, creating the group if necessary. * If no group is provided, the default group `dc.constants.DEFAULT_CHART_GROUP` will be used. * @memberof dc * @method deregisterChart * @param {Object} chart dc.js chart instance * @param {String} [group] Group name * @return {undefined} */ dc.deregisterChart = function (chart, group) { dc.chartRegistry.deregister(chart, group); }; /** * Determine if a given chart instance resides in any group in the registry. * @memberof dc * @method hasChart * @param {Object} chart dc.js chart instance * @returns {Boolean} */ dc.hasChart = function (chart) { return dc.chartRegistry.has(chart); }; /** * Clear given group if one is provided, otherwise clears all groups. * @memberof dc * @method deregisterAllCharts * @param {String} group Group name * @return {undefined} */ dc.deregisterAllCharts = function (group) { dc.chartRegistry.clear(group); }; /** * Clear all filters on all charts within the given chart group. If the chart group is not given then * only charts that belong to the default chart group will be reset. * @memberof dc * @method filterAll * @param {String} [group] * @return {undefined} */ dc.filterAll = function (group) { var charts = dc.chartRegistry.list(group); for (var i = 0; i < charts.length; ++i) { charts[i].filterAll(); } }; /** * Reset zoom level / focus on all charts that belong to the given chart group. If the chart group is * not given then only charts that belong to the default chart group will be reset. * @memberof dc * @method refocusAll * @param {String} [group] * @return {undefined} */ dc.refocusAll = function (group) { var charts = dc.chartRegistry.list(group); for (var i = 0; i < charts.length; ++i) { if (charts[i].focus) { charts[i].focus(); } } }; /** * Re-render all charts belong to the given chart group. If the chart group is not given then only * charts that belong to the default chart group will be re-rendered. * @memberof dc * @method renderAll * @param {String} [group] * @return {undefined} */ dc.renderAll = function (group) { var charts = dc.chartRegistry.list(group); for (var i = 0; i < charts.length; ++i) { charts[i].render(); } if (dc._renderlet !== null) { dc._renderlet(group); } }; /** * Redraw all charts belong to the given chart group. If the chart group is not given then only charts * that belong to the default chart group will be re-drawn. Redraw is different from re-render since * when redrawing dc tries to update the graphic incrementally, using transitions, instead of starting * from scratch. * @memberof dc * @method redrawAll * @param {String} [group] * @return {undefined} */ dc.redrawAll = function (group) { var charts = dc.chartRegistry.list(group); for (var i = 0; i < charts.length; ++i) { charts[i].redraw(); } if (dc._renderlet !== null) { dc._renderlet(group); } }; /** * If this boolean is set truthy, all transitions will be disabled, and changes to the charts will happen * immediately. * @memberof dc * @member disableTransitions * @type {Boolean} * @default false */ dc.disableTransitions = false; /** * Start a transition on a selection if transitions are globally enabled * ({@link dc.disableTransitions} is false) and the duration is greater than zero; otherwise return * the selection. Since most operations are the same on a d3 selection and a d3 transition, this * allows a common code path for both cases. * @memberof dc * @method transition * @param {d3.selection} selection - the selection to be transitioned * @param {Number|Function} [duration=250] - the duration of the transition in milliseconds, a * function returning the duration, or 0 for no transition * @param {Number|Function} [delay] - the delay of the transition in milliseconds, or a function * returning the delay, or 0 for no delay * @param {String} [name] - the name of the transition (if concurrent transitions on the same * elements are needed) * @returns {d3.transition|d3.selection} */ dc.transition = function (selection, duration, delay, name) { if (dc.disableTransitions || duration <= 0) { return selection; } var s = selection.transition(name); if (duration >= 0 || duration !== undefined) { s = s.duration(duration); } if (delay >= 0 || delay !== undefined) { s = s.delay(delay); } return s; }; /* somewhat silly, but to avoid duplicating logic */ dc.optionalTransition = function (enable, duration, delay, name) { if (enable) { return function (selection) { return dc.transition(selection, duration, delay, name); }; } else { return function (selection) { return selection; }; } }; // See http://stackoverflow.com/a/20773846 dc.afterTransition = function (transition, callback) { if (transition.empty() || !transition.duration) { callback.call(transition); } else { var n = 0; transition .each(function () { ++n; }) .on('end', function () { if (!--n) { callback.call(transition); } }); } }; /** * @namespace units * @memberof dc * @type {{}} */ dc.units = {}; /** * The default value for {@link dc.coordinateGridMixin#xUnits .xUnits} for the * {@link dc.coordinateGridMixin Coordinate Grid Chart} and should * be used when the x values are a sequence of integers. * It is a function that counts the number of integers in the range supplied in its start and end parameters. * @method integers * @memberof dc.units * @see {@link dc.coordinateGridMixin#xUnits coordinateGridMixin.xUnits} * @example * chart.xUnits(dc.units.integers) // already the default * @param {Number} start * @param {Number} end * @returns {Number} */ dc.units.integers = function (start, end) { return Math.abs(end - start); }; /** * This argument can be passed to the {@link dc.coordinateGridMixin#xUnits .xUnits} function of a * coordinate grid chart to specify ordinal units for the x axis. Usually this parameter is used in * combination with passing * {@link https://github.com/d3/d3-scale/blob/master/README.md#ordinal-scales d3.scaleOrdinal} * to {@link dc.coordinateGridMixin#x .x}. * * As of dc.js 3.0, this is purely a placeholder or magic value which causes the chart to go into ordinal mode; the * function is not called. * @method ordinal * @memberof dc.units * @return {uncallable} * @see {@link https://github.com/d3/d3-scale/blob/master/README.md#ordinal-scales d3.scaleOrdinal} * @see {@link dc.coordinateGridMixin#xUnits coordinateGridMixin.xUnits} * @see {@link dc.coordinateGridMixin#x coordinateGridMixin.x} * @example * chart.xUnits(dc.units.ordinal) * .x(d3.scaleOrdinal()) */ dc.units.ordinal = function () { throw new Error('dc.units.ordinal should not be called - it is a placeholder'); }; /** * @namespace fp * @memberof dc.units * @type {{}} */ dc.units.fp = {}; /** * This function generates an argument for the {@link dc.coordinateGridMixin Coordinate Grid Chart} * {@link dc.coordinateGridMixin#xUnits .xUnits} function specifying that the x values are floating-point * numbers with the given precision. * The returned function determines how many values at the given precision will fit into the range * supplied in its start and end parameters. * @method precision * @memberof dc.units.fp * @see {@link dc.coordinateGridMixin#xUnits coordinateGridMixin.xUnits} * @example * // specify values (and ticks) every 0.1 units * chart.xUnits(dc.units.fp.precision(0.1) * // there are 500 units between 0.5 and 1 if the precision is 0.001 * var thousandths = dc.units.fp.precision(0.001); * thousandths(0.5, 1.0) // returns 500 * @param {Number} precision * @returns {Function} start-end unit function */ dc.units.fp.precision = function (precision) { var _f = function (s, e) { var d = Math.abs((e - s) / _f.resolution); if (dc.utils.isNegligible(d - Math.floor(d))) { return Math.floor(d); } else { return Math.ceil(d); } }; _f.resolution = precision; return _f; }; dc.round = {}; dc.round.floor = function (n) { return Math.floor(n); }; dc.round.ceil = function (n) { return Math.ceil(n); }; dc.round.round = function (n) { return Math.round(n); }; dc.override = function (obj, functionName, newFunction) { var existingFunction = obj[functionName]; obj['_' + functionName] = existingFunction; obj[functionName] = newFunction; }; dc.renderlet = function (_) { if (!arguments.length) { return dc._renderlet; } dc._renderlet = _; return dc; }; dc.instanceOfChart = function (o) { return o instanceof Object && o.__dcFlag__ && true; }; // polyfill for IE // from https://developer.mozilla.org/en-US/docs/Web/API/Element/matches#Polyfill if (!Element.prototype.matches) { Element.prototype.matches = Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector || function (s) { var matches = (this.document || this.ownerDocument).querySelectorAll(s), i = matches.length; do { --i; } while (i >= 0 && matches.item(i) !== this); return i > -1; }; } dc.errors = {}; dc.errors.Exception = function (msg) { var _msg = msg || 'Unexpected internal error'; this.message = _msg; this.toString = function () { return _msg; }; this.stack = (new Error()).stack; }; dc.errors.Exception.prototype = Object.create(Error.prototype); dc.errors.Exception.prototype.constructor = dc.errors.Exception; dc.errors.InvalidStateException = function () { dc.errors.Exception.apply(this, arguments); }; dc.errors.InvalidStateException.prototype = Object.create(dc.errors.Exception.prototype); dc.errors.InvalidStateException.prototype.constructor = dc.errors.InvalidStateException; dc.errors.BadArgumentException = function () { dc.errors.Exception.apply(this, arguments); }; dc.errors.BadArgumentException.prototype = Object.create(dc.errors.Exception.prototype); dc.errors.BadArgumentException.prototype.constructor = dc.errors.BadArgumentException; /** * The default date format for dc.js * @name dateFormat * @memberof dc * @type {Function} * @default d3.timeFormat('%m/%d/%Y') */ dc.dateFormat = d3.timeFormat('%m/%d/%Y'); /** * @namespace printers * @memberof dc * @type {{}} */ dc.printers = {}; /** * Converts a list of filters into a readable string. * @method filters * @memberof dc.printers * @param {Array<dc.filters>} filters * @returns {String} */ dc.printers.filters = function (filters) { var s = ''; for (var i = 0; i < filters.length; ++i) { if (i > 0) { s += ', '; } s += dc.printers.filter(filters[i]); } return s; }; /** * Converts a filter into a readable string. * @method filter * @memberof dc.printers * @param {dc.filters|any|Array<any>} filter * @returns {String} */ dc.printers.filter = function (filter) { var s = ''; if (typeof filter !== 'undefined' && filter !== null) { if (filter instanceof Array) { if (filter.length >= 2) { s = '[' + filter.map(function (e) { return dc.utils.printSingleValue(e); }).join(' -> ') + ']'; } else if (filter.length >= 1) { s = dc.utils.printSingleValue(filter[0]); } } else { s = dc.utils.printSingleValue(filter); } } return s; }; /** * Returns a function that given a string property name, can be used to pluck the property off an object. A function * can be passed as the second argument to also alter the data being returned. * * This can be a useful shorthand method to create accessor functions. * @method pluck * @memberof dc * @example * var xPluck = dc.pluck('x'); * var objA = {x: 1}; * xPluck(objA) // 1 * @example * var xPosition = dc.pluck('x', function (x, i) { * // `this` is the original datum, * // `x` is the x property of the datum, * // `i` is the position in the array * return this.radius + x; * }); * dc.selectAll('.circle').data(...).x(xPosition); * @param {String} n * @param {Function} [f] * @returns {Function} */ dc.pluck = function (n, f) { if (!f) { return function (d) { return d[n]; }; } return function (d, i) { return f.call(d, d[n], i); }; }; /** * @namespace utils * @memberof dc * @type {{}} */ dc.utils = {}; /** * Print a single value filter. * @method printSingleValue * @memberof dc.utils * @param {any} filter * @returns {String} */ dc.utils.printSingleValue = function (filter) { var s = '' + filter; if (filter instanceof Date) { s = dc.dateFormat(filter); } else if (typeof(filter) === 'string') { s = filter; } else if (dc.utils.isFloat(filter)) { s = dc.utils.printSingleValue.fformat(filter); } else if (dc.utils.isInteger(filter)) { s = Math.round(filter); } return s; }; dc.utils.printSingleValue.fformat = d3.format('.2f'); // convert 'day' to 'timeDay' and similar dc.utils.toTimeFunc = function (t) { return 'time' + t.charAt(0).toUpperCase() + t.slice(1); }; /** * Arbitrary add one value to another. * * If the value l is of type Date, adds r units to it. t becomes the unit. * For example dc.utils.add(dt, 3, 'week') will add 3 (r = 3) weeks (t= 'week') to dt. * * If l is of type numeric, t is ignored. In this case if r is of type string, * it is assumed to be percentage (whether or not it includes %). For example * dc.utils.add(30, 10) will give 40 and dc.utils.add(30, '10') will give 33. * * They also generate strange results if l is a string. * @method add * @memberof dc.utils * @param {Date|Number} l the value to modify * @param {String|Number} r the amount by which to modify the value * @param {Function|String} [t=d3.timeDay] if `l` is a `Date`, then this should be a * [d3 time interval](https://github.com/d3/d3-time/blob/master/README.md#_interval). * For backward compatibility with dc.js 2.0, it can also be the name of an interval, i.e. * 'millis', 'second', 'minute', 'hour', 'day', 'week', 'month', or 'year' * @returns {Date|Number} */ dc.utils.add = function (l, r, t) { if (typeof r === 'string') { r = r.replace('%', ''); } if (l instanceof Date) { if (typeof r === 'string') { r = +r; } if (t === 'millis') { return new Date(l.getTime() + r); } t = t || d3.timeDay; if (typeof t !== 'function') { t = d3[dc.utils.toTimeFunc(t)]; } return t.offset(l, r); } else if (typeof r === 'string') { var percentage = (+r / 100); return l > 0 ? l * (1 + percentage) : l * (1 - percentage); } else { return l + r; } }; /** * Arbitrary subtract one value from another. * * If the value l is of type Date, subtracts r units from it. t becomes the unit. * For example dc.utils.subtract(dt, 3, 'week') will subtract 3 (r = 3) weeks (t= 'week') from dt. * * If l is of type numeric, t is ignored. In this case if r is of type string, * it is assumed to be percentage (whether or not it includes %). For example * dc.utils.subtract(30, 10) will give 20 and dc.utils.subtract(30, '10') will give 27. * * They also generate strange results if l is a string. * @method subtract * @memberof dc.utils * @param {Date|Number} l the value to modify * @param {String|Number} r the amount by which to modify the value * @param {Function|String} [t=d3.timeDay] if `l` is a `Date`, then this should be a * [d3 time interval](https://github.com/d3/d3-time/blob/master/README.md#_interval). * For backward compatibility with dc.js 2.0, it can also be the name of an interval, i.e. * 'millis', 'second', 'minute', 'hour', 'day', 'week', 'month', or 'year' * @returns {Date|Number} */ dc.utils.subtract = function (l, r, t) { if (typeof r === 'string') { r = r.replace('%', ''); } if (l instanceof Date) { if (typeof r === 'string') { r = +r; } if (t === 'millis') { return new Date(l.getTime() - r); } t = t || d3.timeDay; if (typeof t !== 'function') { t = d3[dc.utils.toTimeFunc(t)]; } return t.offset(l, -r); } else if (typeof r === 'string') { var percentage = (+r / 100); return l < 0 ? l * (1 + percentage) : l * (1 - percentage); } else { return l - r; } }; /** * Is the value a number? * @method isNumber * @memberof dc.utils * @param {any} n * @returns {Boolean} */ dc.utils.isNumber = function (n) { return n === +n; }; /** * Is the value a float? * @method isFloat * @memberof dc.utils * @param {any} n * @returns {Boolean} */ dc.utils.isFloat = function (n) { return n === +n && n !== (n | 0); }; /** * Is the value an integer? * @method isInteger * @memberof dc.utils * @param {any} n * @returns {Boolean} */ dc.utils.isInteger = function (n) { return n === +n && n === (n | 0); }; /** * Is the value very close to zero? * @method isNegligible * @memberof dc.utils * @param {any} n * @returns {Boolean} */ dc.utils.isNegligible = function (n) { return !dc.utils.isNumber(n) || (n < dc.constants.NEGLIGIBLE_NUMBER && n > -dc.constants.NEGLIGIBLE_NUMBER); }; /** * Ensure the value is no greater or less than the min/max values. If it is return the boundary value. * @method clamp * @memberof dc.utils * @param {any} val * @param {any} min * @param {any} max * @returns {any} */ dc.utils.clamp = function (val, min, max) { return val < min ? min : (val > max ? max : val); }; /** * Given `x`, return a function that always returns `x`. * * {@link https://github.com/d3/d3/blob/master/CHANGES.md#internals `d3.functor` was removed in d3 version 4}. * This function helps to implement the replacement, * `typeof x === "function" ? x : dc.utils.constant(x)` * @method constant * @memberof dc.utils * @param {any} x * @returns {Function} */ dc.utils.constant = function (x) { return function () { return x; }; }; /** * Using a simple static counter, provide a unique integer id. * @method uniqueId * @memberof dc.utils * @returns {Number} */ var _idCounter = 0; dc.utils.uniqueId = function () { return ++_idCounter; }; /** * Convert a name to an ID. * @method nameToId * @memberof dc.utils * @param {String} name * @returns {String} */ dc.utils.nameToId = function (name) { return name.toLowerCase().replace(/[\s]/g, '_').replace(/[\.']/g, ''); }; /** * Append or select an item on a parent element. * @method appendOrSelect * @memberof dc.utils * @param {d3.selection} parent * @param {String} selector * @param {String} tag * @returns {d3.selection} */ dc.utils.appendOrSelect = function (parent, selector, tag) { tag = tag || selector; var element = parent.select(selector); if (element.empty()) { element = parent.append(tag); } return element; }; /** * Return the number if the value is a number; else 0. * @method safeNumber * @memberof dc.utils * @param {Number|any} n * @returns {Number} */ dc.utils.safeNumber = function (n) { return dc.utils.isNumber(+n) ? +n : 0;}; /** * Return true if both arrays are equal, if both array are null these are considered equal * @method arraysEqual * @memberof dc.utils * @param {Array|null} a1 * @param {Array|null} a2 * @returns {Boolean} */ dc.utils.arraysEqual = function (a1, a2) { if (!a1 && !a2) { return true; } if (!a1 || !a2) { return false; } return a1.length === a2.length && // If elements are not integers/strings, we hope that it will match because of toString // Test cases cover dates as well. a1.every(function (elem, i) { return elem.valueOf() === a2[i].valueOf(); }); }; // ******** Sunburst Chart ******** dc.utils.allChildren = function (node) { var paths = []; paths.push(node.path); console.log('currentNode', node); if (node.children) { for (var i = 0; i < node.children.length; i++) { paths = paths.concat(dc.utils.allChildren(node.children[i])); } } return paths; }; // builds a d3 Hierarchy from a collection // TODO: turn this monster method something better. dc.utils.toHierarchy = function (list, accessor) { var root = {'key': 'root', 'children': []}; for (var i = 0; i < list.length; i++) { var data = list[i]; var parts = data.key; var value = accessor(data); var currentNode = root; for (var j = 0; j < parts.length; j++) { var currentPath = parts.slice(0, j + 1); var children = currentNode.children; var nodeName = parts[j]; var childNode; if (j + 1 < parts.length) { // Not yet at the end of the sequence; move down the tree. childNode = findChild(children, nodeName); // If we don't already have a child node for this branch, create it. if (childNode === void 0) { childNode = {'key': nodeName, 'children': [], 'path': currentPath}; children.push(childNode); } currentNode = childNode; } else { // Reached the end of the sequence; create a leaf node. childNode = {'key': nodeName, 'value': value, 'data': data, 'path': currentPath}; children.push(childNode); } } } return root; }; function findChild (children, nodeName) { for (var k = 0; k < children.length; k++) { if (children[k].key === nodeName) { return children[k]; } } } dc.utils.getAncestors = function (node) { var path = []; var current = node; while (current.parent) { path.unshift(current.name); current = current.parent; } return path; }; dc.utils.arraysIdentical = function (a, b) { var i = a.length; if (i !== b.length) { return false; } while (i--) { if (a[i] !== b[i]) { return false; } } return true; }; if (typeof Object.assign !== 'function') { // Must be writable: true, enumerable: false, configurable: true Object.defineProperty(Object, 'assign', { value: function assign (target, varArgs) { // .length of function is 2 'use strict'; if (target === null) { // TypeError if undefined or null throw new TypeError('Cannot convert undefined or null to object'); } var to = Object(target); for (var index = 1; index < arguments.length; index++) { var nextSource = arguments[index]; if (nextSource !== null) { // Skip over if undefined or null for (var nextKey in nextSource) { // Avoid bugs when hasOwnProperty is shadowed if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; } } } } return to; }, writable: true, configurable: true }); } /** * Provides basis logging and deprecation utilities * @class logger * @memberof dc * @returns {dc.logger} */ dc.logger = (function () { var _logger = {}; /** * Enable debug level logging. Set to `false` by default. * @name enableDebugLog * @memberof dc.logger * @instance */ _logger.enableDebugLog = false; /** * Put a warning message to console * @method warn * @memberof dc.logger * @instance * @example * dc.logger.warn('Invalid use of .tension on CurveLinear'); * @param {String} [msg] * @returns {dc.logger} */ _logger.warn = function (msg) { if (console) { if (console.warn) { console.warn(msg); } else if (console.log) { console.log(msg); } } return _logger; }; var _alreadyWarned = {}; /** * Put a warning message to console. It will warn only on unique messages. * @method warnOnce * @memberof dc.logger * @instance * @example * dc.logger.warnOnce('Invalid use of .tension on CurveLinear'); * @param {String} [msg] * @returns {dc.logger} */ _logger.warnOnce = function (msg) { if (!_alreadyWarned[msg]) { _alreadyWarned[msg] = true; dc.logger.warn(msg); } return _logger; }; /** * Put a debug message to console. It is controlled by `dc.logger.enableDebugLog` * @method debug * @memberof dc.logger * @instance * @example * dc.logger.debug('Total number of slices: ' + numSlices); * @param {String} [msg] * @returns {dc.logger} */ _logger.debug = function (msg) { if (_logger.enableDebugLog && console) { if (console.debug) { console.debug(msg); } else if (console.log) { console.log(msg); } } return _logger; }; /** * Used to deprecate a function. It will return a wrapped version of the function, which will * will issue a warning when invoked. The warning will be issued only once. * * @method deprecate * @memberof dc.logger * @instance * @example * _chart.interpolate = dc.logger.deprecate(function (interpolate) { * if (!arguments.length) { * return _interpolate; * } * _interpolate = interpolate; * return _chart; * }, 'dc.lineChart.interpolate has been deprecated since version 3.0 use dc.lineChart.curve instead'); * @param {Function} [fn] * @param {String} [msg] * @returns {Function} */ _logger.deprecate = function (fn, msg) { // Allow logging of deprecation var warned = false; function deprecated () { if (!warned) { _logger.warn(msg); warned = true; } return fn.apply(this, arguments); } return deprecated; }; /** * Used to provide an informational message for a function. It will return a wrapped version of * the function, which will will issue a messsage with stack when invoked. The message will be * issued only once. * * @method annotate * @memberof dc.logger * @instance * @example * _chart.interpolate = dc.logger.annotate(function (interpolate) { * if (!arguments.length) { * return _interpolate; * } * _interpolate = interpolate; * return _chart; * }, 'dc.lineChart.interpolate has been annotated since version 3.0 use dc.lineChart.curve instead'); * @param {Function} [fn] * @param {String} [msg] * @returns {Function} */ _logger.annotate = function (fn, msg) { // Allow logging of deprecation var warned = false; function annotated () { if (!warned) { console.groupCollapsed(msg); console.trace(); console.groupEnd(); warned = true; } return fn.apply(this, arguments); } return annotated; }; return _logger; })(); /** * General configuration * * @class config * @memberof dc * @returns {dc.config} */ dc.config = (function () { var _config = {}; // D3v5 has removed schemeCategory20c, copied here for backward compatibility var _schemeCategory20c = [ '#3182bd', '#6baed6', '#9ecae1', '#c6dbef', '#e6550d', '#fd8d3c', '#fdae6b', '#fdd0a2', '#31a354', '#74c476', '#a1d99b', '#c7e9c0', '#756bb1', '#9e9ac8', '#bcbddc', '#dadaeb', '#636363', '#969696', '#bdbdbd', '#d9d9d9']; var _defaultColors = _schemeCategory20c; /** * Set the default color scheme for ordinal charts. Changing it will impact all ordinal charts. * * By default it is set to a copy of * `d3.schemeCategory20c` for backward compatibility. This color scheme has been * [removed from D3v5](https://github.com/d3/d3/blob/master/CHANGES.md#changes-in-d3-50). * In DC 3.1 release it will change to a more appropriate default. * * @example * dc.config.defaultColors(d3.schemeSet1) * @method defaultColors * @memberof dc.config * @instance * @param {Array} [colors] * @returns {Array|dc.config} */ _config.defaultColors = function (colors) { if (!arguments.length) { // Issue warning if it uses _schemeCategory20c if (_defaultColors === _schemeCategory20c) { dc.logger.warnOnce('You are using d3.schemeCategory20c, which has been removed in D3v5. ' + 'See the explanation at https://github.com/d3/d3/blob/master/CHANGES.md#changes-in-d3-50. ' + 'DC is using it for backward compatibility, however it will be changed in DCv3.1. ' + 'You can change it by calling dc.config.defaultColors(newScheme). ' + 'See https://github.com/d3/d3-scale-chromatic for some alternatives.'); } return _defaultColors; } _defaultColors = colors; return _config; }; return _config; })(); dc.events = { current: null }; /** * This function triggers a throttled event function with a specified delay (in milli-seconds). Events * that are triggered repetitively due to user interaction such brush dragging might flood the library * and invoke more renders than can be executed in time. Using this function to wrap your event * function allows the library to smooth out the rendering by throttling events and only responding to * the most recent event. * @name events.trigger * @memberof dc * @example * chart.on('renderlet', function(chart) { * // smooth the rendering through event throttling * dc.events.trigger(function(){ * // focus some other chart to the range selected by user on this chart * someOtherChart.focus(chart.filter()); * }); * }) * @param {Function} closure * @param {Number} [delay] * @return {undefined} */ dc.events.trigger = function (closure, delay) { if (!delay) { closure(); return; } dc.events.current = closure; setTimeout(function () { if (closure === dc.events.current) { closure(); } }, delay); }; /** * The dc.js filters are functions which are passed into crossfilter to chose which records will be * accumulated to produce values for the charts. In the crossfilter model, any filters applied on one * dimension will affect all the other dimensions but not that one. dc always applies a filter * function to the dimension; the function combines multiple filters and if any of them accept a * record, it is filtered in. * * These filter constructors are used as appropriate by the various charts to implement brushing. We * mention below which chart uses which filter. In some cases, many instances of a filter will be added. * * Each of the dc.js filters is an object with the following properties: * * `isFiltered` - a function that returns true if a value is within the filter * * `filterType` - a string identifying the filter, here the name of the constructor * * Currently these filter objects are also arrays, but this is not a requirement. Custom filters * can be used as long as they have the properties above. * @namespace filters * @memberof dc * @type {{}} */ dc.filters = {}; /** * RangedFilter is a filter which accepts keys between `low` and `high`. It is used to implement X * axis brushing for the {@link dc.coordinateGridMixin coordinate grid charts}. * * Its `filterType` is 'RangedFilter' * @name RangedFilter * @memberof dc.filters * @param {Number} low * @param {Number} high * @returns {Array<Number>} * @constructor */ dc.filters.RangedFilter = function (low, high) { var range = new Array(low, high); range.isFiltered = function (value) { return value >= this[0] && value < this[1]; }; range.filterType = 'RangedFilter'; return range; }; /** * TwoDimensionalFilter is a filter which accepts a single two-dimensional value. It is used by the * {@link dc.heatMap heat map chart} to include particular cells as they are clicked. (Rows and columns are * filtered by filtering all the cells in the row or column.) * * Its `filterType` is 'TwoDimensionalFilter' * @name TwoDimensionalFilter * @memberof dc.filters * @param {Array<Number>} filter * @returns {Array<Number>} * @constructor */ dc.filters.TwoDimensionalFilter = function (filter) { if (filter === null) { return null; } var f = filter; f.isFiltered = function (value) { return value.length && value.length === f.length && value[0] === f[0] && value[1] === f[1]; }; f.filterType = 'TwoDimensionalFilter'; return f; }; /** * The RangedTwoDimensionalFilter allows filtering all values which fit within a rectangular * region. It is used by the {@link dc.scatterPlot scatter plot} to implement rectangular brushing. * * It takes two two-dimensional points in the form `[[x1,y1],[x2,y2]]`, and normalizes them so that * `x1 <= x2` and `y1 <= y2`. It then returns a filter which accepts any points which are in the * rectangular range including the lower values but excluding the higher values. * * If an array of two values are given to the RangedTwoDimensionalFilter, it interprets the values as * two x coordinates `x1` and `x2` and returns a filter which accepts any points for which `x1 <= x < * x2`. * * Its `filterType` is 'RangedTwoDimensionalFilter' * @name RangedTwoDimensionalFilter * @memberof dc.filters * @param {Array<Array<Number>>} filter * @returns {Array<Array<Number>>} * @constructor */ dc.filters.RangedTwoDimensionalFilter = function (filter) { if (filter === null) { return null; } var f = filter; var fromBottomLeft; if (f[0] instanceof Array) { fromBottomLeft = [ [Math.min(filter[0][0], filter[1][0]), Math.min(filter[0][1], filter[1][1])], [Math.max(filter[0][0], filter[1][0]), Math.max(filter[0][1], filter[1][1])] ]; } else { fromBottomLeft = [[filter[0], -Infinity], [filter[1], Infinity]]; } f.isFiltered = function (value) { var x, y; if (value instanceof Array) { x = value[0]; y = value[1]; } else { x = value; y = fromBottomLeft[0][1]; } return x >= fromBottomLeft[0][0] && x < fromBottomLeft[1][0] && y >= fromBottomLeft[0][1] && y < fromBottomLeft[1][1]; }; f.filterType = 'RangedTwoDimensionalFilter'; return f; }; // ******** Sunburst Chart ******** /** * HierarchyFilter is a filter which accepts a key path as an array. It matches any node at, or * child of, the given path. It is used by the {@link dc.sunburstChart sunburst chart} to include particular cells and all * their children as they are clicked. * * @name HierarchyFilter * @memberof dc.filters * @param {String} path * @returns {Array<String>} * @constructor */ dc.filters.HierarchyFilter = function (path) { if (path === null) { return null; } var filter = path.slice(0); filter.isFiltered = function (value) { if (!(filter.length && value && value.length && value.length >= filter.length)) { return false; } for (var i = 0; i < filter.length; i++) { if (value[i] !== filter[i]) { return false; } } return true; }; return filter; }; /** * `dc.baseMixin` is an abstract functional object representing a basic `dc` chart object * for 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; } _minHei