{"version":3,"file":"dc.min.js","sources":["../src/core/bad-argument-exception.js","../src/core/constants.js","../src/core/logger.js","../src/core/config.js","../src/core/chart-registry.js","../src/core/core.js","../src/core/events.js","../src/core/filters.js","../src/core/invalid-state-exception.js","../src/core/utils.js","../src/core/printers.js","../src/core/units.js","../src/base/base-mixin.js","../src/base/color-mixin.js","../src/base/bubble-mixin.js","../src/base/cap-mixin.js","../src/base/margin-mixin.js","../src/base/coordinate-grid-mixin.js","../src/base/d3.box.js","../src/base/stack-mixin.js","../src/charts/bar-chart.js","../src/charts/box-plot.js","../src/charts/bubble-chart.js","../src/charts/bubble-overlay.js","../src/charts/cbox-menu.js","../src/charts/composite-chart.js","../src/charts/data-count.js","../src/charts/data-grid.js","../src/charts/data-table.js","../src/charts/geo-choropleth-chart.js","../src/charts/heatmap.js","../src/charts/html-legend.js","../src/charts/legend.js","../src/charts/line-chart.js","../src/charts/number-display.js","../src/charts/pie-chart.js","../src/charts/row-chart.js","../src/charts/scatter-plot.js","../src/charts/select-menu.js","../src/charts/series-chart.js","../src/charts/sunburst-chart.js","../src/charts/text-filter-widget.js","../src/compat/d3v5.js","../src/compat/d3v6.js"],"sourcesContent":["\nexport class BadArgumentException extends Error { }\n","export const constants = {\n CHART_CLASS: 'dc-chart',\n DEBUG_GROUP_CLASS: 'debug',\n STACK_CLASS: 'stack',\n DESELECTED_CLASS: 'deselected',\n SELECTED_CLASS: 'selected',\n NODE_INDEX_NAME: '__index__',\n GROUP_INDEX_NAME: '__group_index__',\n DEFAULT_CHART_GROUP: '__default_chart_group__',\n EVENT_DELAY: 40,\n NEGLIGIBLE_NUMBER: 1e-10\n};\n","/**\n * Provides basis logging and deprecation utilities\n */\nexport class Logger {\n\n constructor () {\n /**\n * Enable debug level logging. Set to `false` by default.\n * @name enableDebugLog\n * @memberof Logger\n * @instance\n */\n this.enableDebugLog = false;\n\n this._alreadyWarned = {};\n }\n\n /**\n * Put a warning message to console\n * @example\n * logger.warn('Invalid use of .tension on CurveLinear');\n * @param {String} [msg]\n * @returns {Logger}\n */\n warn (msg) {\n if (console) {\n if (console.warn) {\n console.warn(msg);\n } else if (console.log) {\n console.log(msg);\n }\n }\n\n return this;\n }\n\n /**\n * Put a warning message to console. It will warn only on unique messages.\n * @example\n * logger.warnOnce('Invalid use of .tension on CurveLinear');\n * @param {String} [msg]\n * @returns {Logger}\n */\n warnOnce (msg) {\n if (!this._alreadyWarned[msg]) {\n this._alreadyWarned[msg] = true;\n\n logger.warn(msg);\n }\n\n return this;\n }\n\n /**\n * Put a debug message to console. It is controlled by `logger.enableDebugLog`\n * @example\n * logger.debug('Total number of slices: ' + numSlices);\n * @param {String} [msg]\n * @returns {Logger}\n */\n debug (msg) {\n if (this.enableDebugLog && console) {\n if (console.debug) {\n console.debug(msg);\n } else if (console.log) {\n console.log(msg);\n }\n }\n\n return this;\n }\n}\n\nexport const logger = new Logger();\n","import {timeFormat} from 'd3-time-format';\n\nimport {logger} from './logger';\n\n/**\n * General configuration\n */\nexport class Config {\n constructor () {\n this._defaultColors = Config._schemeCategory20c;\n\n /**\n * The default date format for dc.js\n * @type {Function}\n * @default d3.timeFormat('%m/%d/%Y')\n */\n this.dateFormat = timeFormat('%m/%d/%Y');\n\n this._renderlet = null;\n\n /**\n * If this boolean is set truthy, all transitions will be disabled, and changes to the charts will happen\n * immediately.\n * @type {Boolean}\n * @default false\n */\n this.disableTransitions = false;\n }\n\n /**\n * Set the default color scheme for ordinal charts. Changing it will impact all ordinal charts.\n *\n * By default it is set to a copy of\n * `d3.schemeCategory20c` for backward compatibility. This color scheme has been\n * [removed from D3v5](https://github.com/d3/d3/blob/master/CHANGES.md#changes-in-d3-50).\n * In DC 3.1 release it will change to a more appropriate default.\n *\n * @example\n * config.defaultColors(d3.schemeSet1)\n * @param {Array} [colors]\n * @returns {Array|config}\n */\n defaultColors (colors) {\n if (!arguments.length) {\n // Issue warning if it uses _schemeCategory20c\n if (this._defaultColors === Config._schemeCategory20c) {\n logger.warnOnce('You are using d3.schemeCategory20c, which has been removed in D3v5. ' +\n 'See the explanation at https://github.com/d3/d3/blob/master/CHANGES.md#changes-in-d3-50. ' +\n 'DC is using it for backward compatibility, however it will be changed in DCv3.1. ' +\n 'You can change it by calling dc.config.defaultColors(newScheme). ' +\n 'See https://github.com/d3/d3-scale-chromatic for some alternatives.');\n }\n return this._defaultColors;\n }\n this._defaultColors = colors;\n return this;\n }\n\n}\n\n// D3v5 has removed schemeCategory20c, copied here for backward compatibility\nConfig._schemeCategory20c = [\n '#3182bd', '#6baed6', '#9ecae1', '#c6dbef', '#e6550d',\n '#fd8d3c', '#fdae6b', '#fdd0a2', '#31a354', '#74c476',\n '#a1d99b', '#c7e9c0', '#756bb1', '#9e9ac8', '#bcbddc',\n '#dadaeb', '#636363', '#969696', '#bdbdbd', '#d9d9d9'];\n\n/**\n * General configuration object; see {@link Config} for members.\n */\nexport const config = new Config();\n\n/**\n * d3.js compatiblity layer\n */\nexport const d3compat = {\n eventHandler: handler => function eventHandler (a, b) {\n console.warn('No d3.js compatbility event handler registered, defaulting to v6 behavior.');\n handler.call(this, b, a);\n },\n nester: ({key, sortKeys, sortValues, entries}) => {\n throw new Error('No d3.js compatbility nester registered, load v5 or v6 compability layer.');\n },\n pointer: () => { throw new Error('No d3.js compatbility pointer registered, load v5 or v6 compability layer.'); }\n};\n","import {constants} from './constants';\nimport {config} from './config';\n\n/**\n * The ChartRegistry maintains sets of all instantiated dc.js charts under named groups\n * and the default group. There is a single global ChartRegistry object named `chartRegistry`\n *\n * A chart group often corresponds to a crossfilter instance. It specifies\n * the set of charts which should be updated when a filter changes on one of the charts or when the\n * global functions {@link filterAll filterAll}, {@link refocusAll refocusAll},\n * {@link renderAll renderAll}, {@link redrawAll redrawAll}, or chart functions\n * {@link baseMixin#renderGroup baseMixin.renderGroup},\n * {@link baseMixin#redrawGroup baseMixin.redrawGroup} are called.\n */\nclass ChartRegistry {\n constructor () {\n // chartGroup:string => charts:array\n this._chartMap = {};\n }\n\n _initializeChartGroup (group) {\n if (!group) {\n group = constants.DEFAULT_CHART_GROUP;\n }\n\n if (!(this._chartMap)[group]) {\n (this._chartMap)[group] = [];\n }\n\n return group;\n }\n\n /**\n * Determine if a given chart instance resides in any group in the registry.\n * @param {Object} chart dc.js chart instance\n * @returns {Boolean}\n */\n has (chart) {\n for (const e in this._chartMap) {\n if ((this._chartMap)[e].indexOf(chart) >= 0) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Add given chart instance to the given group, creating the group if necessary.\n * If no group is provided, the default group `constants.DEFAULT_CHART_GROUP` will be used.\n * @param {Object} chart dc.js chart instance\n * @param {String} [group] Group name\n * @return {undefined}\n */\n register (chart, group) {\n const _chartMap = this._chartMap;\n group = this._initializeChartGroup(group);\n _chartMap[group].push(chart);\n }\n\n /**\n * Remove given chart instance from the given group, creating the group if necessary.\n * If no group is provided, the default group `constants.DEFAULT_CHART_GROUP` will be used.\n * @param {Object} chart dc.js chart instance\n * @param {String} [group] Group name\n * @return {undefined}\n */\n deregister (chart, group) {\n group = this._initializeChartGroup(group);\n for (let i = 0; i < (this._chartMap)[group].length; i++) {\n if ((this._chartMap)[group][i].anchorName() === chart.anchorName()) {\n (this._chartMap)[group].splice(i, 1);\n break;\n }\n }\n }\n\n /**\n * Clear given group if one is provided, otherwise clears all groups.\n * @param {String} group Group name\n * @return {undefined}\n */\n clear (group) {\n if (group) {\n delete (this._chartMap)[group];\n } else {\n this._chartMap = {};\n }\n }\n\n /**\n * Get an array of each chart instance in the given group.\n * If no group is provided, the charts in the default group are returned.\n * @param {String} [group] Group name\n * @returns {Array}\n */\n list (group) {\n group = this._initializeChartGroup(group);\n return (this._chartMap)[group];\n }\n}\n\n/**\n * The chartRegistry object maintains sets of all instantiated dc.js charts under named groups\n * and the default group. See {@link ChartRegistry ChartRegistry} for its methods.\n */\nexport const chartRegistry = new ChartRegistry();\n\n/**\n * Add given chart instance to the given group, creating the group if necessary.\n * If no group is provided, the default group `constants.DEFAULT_CHART_GROUP` will be used.\n * @function registerChart\n * @param {Object} chart dc.js chart instance\n * @param {String} [group] Group name\n * @return {undefined}\n */\nexport const registerChart = function (chart, group) {\n chartRegistry.register(chart, group);\n};\n\n/**\n * Remove given chart instance from the given group, creating the group if necessary.\n * If no group is provided, the default group `constants.DEFAULT_CHART_GROUP` will be used.\n * @function deregisterChart\n * @param {Object} chart dc.js chart instance\n * @param {String} [group] Group name\n * @return {undefined}\n */\nexport const deregisterChart = function (chart, group) {\n chartRegistry.deregister(chart, group);\n};\n\n/**\n * Determine if a given chart instance resides in any group in the registry.\n * @function hasChart\n * @param {Object} chart dc.js chart instance\n * @returns {Boolean}\n */\nexport const hasChart = function (chart) {\n return chartRegistry.has(chart);\n};\n\n/**\n * Clear given group if one is provided, otherwise clears all groups.\n * @function deregisterAllCharts\n * @param {String} group Group name\n * @return {undefined}\n */\nexport const deregisterAllCharts = function (group) {\n chartRegistry.clear(group);\n};\n\n/**\n * Clear all filters on all charts within the given chart group. If the chart group is not given then\n * only charts that belong to the default chart group will be reset.\n * @function filterAll\n * @param {String} [group]\n * @return {undefined}\n */\nexport const filterAll = function (group) {\n const charts = chartRegistry.list(group);\n for (let i = 0; i < charts.length; ++i) {\n charts[i].filterAll();\n }\n};\n\n/**\n * Reset zoom level / focus on all charts that belong to the given chart group. If the chart group is\n * not given then only charts that belong to the default chart group will be reset.\n * @function refocusAll\n * @param {String} [group]\n * @return {undefined}\n */\nexport const refocusAll = function (group) {\n const charts = chartRegistry.list(group);\n for (let i = 0; i < charts.length; ++i) {\n if (charts[i].focus) {\n charts[i].focus();\n }\n }\n};\n\n/**\n * Re-render all charts belong to the given chart group. If the chart group is not given then only\n * charts that belong to the default chart group will be re-rendered.\n * @function renderAll\n * @param {String} [group]\n * @return {undefined}\n */\nexport const renderAll = function (group) {\n const charts = chartRegistry.list(group);\n for (let i = 0; i < charts.length; ++i) {\n charts[i].render();\n }\n\n if (config._renderlet !== null) {\n config._renderlet(group);\n }\n};\n\n/**\n * Redraw all charts belong to the given chart group. If the chart group is not given then only charts\n * that belong to the default chart group will be re-drawn. Redraw is different from re-render since\n * when redrawing dc tries to update the graphic incrementally, using transitions, instead of starting\n * from scratch.\n * @function redrawAll\n * @param {String} [group]\n * @return {undefined}\n */\nexport const redrawAll = function (group) {\n const charts = chartRegistry.list(group);\n for (let i = 0; i < charts.length; ++i) {\n charts[i].redraw();\n }\n\n if (config._renderlet !== null) {\n config._renderlet(group);\n }\n};\n","import {config} from './config';\n\n/**\n * Start a transition on a selection if transitions are globally enabled\n * ({@link disableTransitions} is false) and the duration is greater than zero; otherwise return\n * the selection. Since most operations are the same on a d3 selection and a d3 transition, this\n * allows a common code path for both cases.\n * @function transition\n * @param {d3.selection} selection - the selection to be transitioned\n * @param {Number|Function} [duration=250] - the duration of the transition in milliseconds, a\n * function returning the duration, or 0 for no transition\n * @param {Number|Function} [delay] - the delay of the transition in milliseconds, or a function\n * returning the delay, or 0 for no delay\n * @param {String} [name] - the name of the transition (if concurrent transitions on the same\n * elements are needed)\n * @returns {d3.transition|d3.selection}\n */\nexport const transition = function (selection, duration, delay, name) {\n if (config.disableTransitions || duration <= 0) {\n return selection;\n }\n\n let s = selection.transition(name);\n\n if (duration >= 0 || duration !== undefined) {\n s = s.duration(duration);\n }\n if (delay >= 0 || delay !== undefined) {\n s = s.delay(delay);\n }\n\n return s;\n};\n\n/* somewhat silly, but to avoid duplicating logic */\nexport const optionalTransition = function (enable, duration, delay, name) {\n if (enable) {\n return function (selection) {\n return transition(selection, duration, delay, name);\n };\n } else {\n return function (selection) {\n return selection;\n };\n }\n};\n\n// See http://stackoverflow.com/a/20773846\nexport const afterTransition = function (_transition, callback) {\n if (_transition.empty() || !_transition.duration) {\n callback.call(_transition);\n } else {\n let n = 0;\n _transition\n .each(() => { ++n; })\n .on('end', () => {\n if (!--n) {\n callback.call(_transition);\n }\n });\n }\n};\n\nexport const renderlet = function (_) {\n if (!arguments.length) {\n return config._renderlet;\n }\n config._renderlet = _;\n return null;\n};\n\nexport const instanceOfChart = function (o) {\n return o instanceof Object && o.__dcFlag__ && true;\n};\n","export const events = {\n current: null\n};\n\n/**\n * This function triggers a throttled event function with a specified delay (in milli-seconds). Events\n * that are triggered repetitively due to user interaction such brush dragging might flood the library\n * and invoke more renders than can be executed in time. Using this function to wrap your event\n * function allows the library to smooth out the rendering by throttling events and only responding to\n * the most recent event.\n * @name events.trigger\n * @example\n * chart.on('renderlet', function(chart) {\n * // smooth the rendering through event throttling\n * events.trigger(function(){\n * // focus some other chart to the range selected by user on this chart\n * someOtherChart.focus(chart.filter());\n * });\n * })\n * @param {Function} closure\n * @param {Number} [delay]\n * @return {undefined}\n */\nevents.trigger = function (closure, delay) {\n if (!delay) {\n closure();\n return;\n }\n\n events.current = closure;\n\n setTimeout(() => {\n if (closure === events.current) {\n closure();\n }\n }, delay);\n};\n","/**\n * The dc.js filters are functions which are passed into crossfilter to chose which records will be\n * accumulated to produce values for the charts. In the crossfilter model, any filters applied on one\n * dimension will affect all the other dimensions but not that one. dc always applies a filter\n * function to the dimension; the function combines multiple filters and if any of them accept a\n * record, it is filtered in.\n *\n * These filter constructors are used as appropriate by the various charts to implement brushing. We\n * mention below which chart uses which filter. In some cases, many instances of a filter will be added.\n *\n * Each of the dc.js filters is an object with the following properties:\n * * `isFiltered` - a function that returns true if a value is within the filter\n * * `filterType` - a string identifying the filter, here the name of the constructor\n *\n * Currently these filter objects are also arrays, but this is not a requirement. Custom filters\n * can be used as long as they have the properties above.\n * @namespace filters\n * @type {{}}\n */\nexport const filters = {};\n\n/**\n * RangedFilter is a filter which accepts keys between `low` and `high`. It is used to implement X\n * axis brushing for the {@link CoordinateGridMixin coordinate grid charts}.\n *\n * Its `filterType` is 'RangedFilter'\n * @name RangedFilter\n * @memberof filters\n * @param {Number} low\n * @param {Number} high\n * @returns {Array}\n * @constructor\n */\nfilters.RangedFilter = function (low, high) {\n const range = new Array(low, high);\n range.isFiltered = function (value) {\n return value >= this[0] && value < this[1];\n };\n range.filterType = 'RangedFilter';\n\n return range;\n};\n\n/**\n * TwoDimensionalFilter is a filter which accepts a single two-dimensional value. It is used by the\n * {@link HeatMap heat map chart} to include particular cells as they are clicked. (Rows and columns are\n * filtered by filtering all the cells in the row or column.)\n *\n * Its `filterType` is 'TwoDimensionalFilter'\n * @name TwoDimensionalFilter\n * @memberof filters\n * @param {Array} filter\n * @returns {Array}\n * @constructor\n */\nfilters.TwoDimensionalFilter = function (filter) {\n if (filter === null) { return null; }\n\n const f = filter;\n f.isFiltered = function (value) {\n return value.length && value.length === f.length &&\n value[0] === f[0] && value[1] === f[1];\n };\n f.filterType = 'TwoDimensionalFilter';\n\n return f;\n};\n\n/**\n * The RangedTwoDimensionalFilter allows filtering all values which fit within a rectangular\n * region. It is used by the {@link ScatterPlot scatter plot} to implement rectangular brushing.\n *\n * It takes two two-dimensional points in the form `[[x1,y1],[x2,y2]]`, and normalizes them so that\n * `x1 <= x2` and `y1 <= y2`. It then returns a filter which accepts any points which are in the\n * rectangular range including the lower values but excluding the higher values.\n *\n * If an array of two values are given to the RangedTwoDimensionalFilter, it interprets the values as\n * two x coordinates `x1` and `x2` and returns a filter which accepts any points for which `x1 <= x <\n * x2`.\n *\n * Its `filterType` is 'RangedTwoDimensionalFilter'\n * @name RangedTwoDimensionalFilter\n * @memberof filters\n * @param {Array>} filter\n * @returns {Array>}\n * @constructor\n */\nfilters.RangedTwoDimensionalFilter = function (filter) {\n if (filter === null) { return null; }\n\n const f = filter;\n let fromBottomLeft;\n\n if (f[0] instanceof Array) {\n fromBottomLeft = [\n [Math.min(filter[0][0], filter[1][0]), Math.min(filter[0][1], filter[1][1])],\n [Math.max(filter[0][0], filter[1][0]), Math.max(filter[0][1], filter[1][1])]\n ];\n } else {\n fromBottomLeft = [[filter[0], -Infinity], [filter[1], Infinity]];\n }\n\n f.isFiltered = function (value) {\n let x, y;\n\n if (value instanceof Array) {\n x = value[0];\n y = value[1];\n } else {\n x = value;\n y = fromBottomLeft[0][1];\n }\n\n return x >= fromBottomLeft[0][0] && x < fromBottomLeft[1][0] &&\n y >= fromBottomLeft[0][1] && y < fromBottomLeft[1][1];\n };\n f.filterType = 'RangedTwoDimensionalFilter';\n\n return f;\n};\n\n// ******** Sunburst Chart ********\n\n/**\n * HierarchyFilter is a filter which accepts a key path as an array. It matches any node at, or\n * child of, the given path. It is used by the {@link SunburstChart sunburst chart} to include particular cells and all\n * their children as they are clicked.\n *\n * @name HierarchyFilter\n * @memberof filters\n * @param {String} path\n * @returns {Array}\n * @constructor\n */\nfilters.HierarchyFilter = function (path) {\n if (path === null) {\n return null;\n }\n\n const filter = path.slice(0);\n filter.isFiltered = function (value) {\n if (!(filter.length && value && value.length && value.length >= filter.length)) {\n return false;\n }\n\n for (let i = 0; i < filter.length; i++) {\n if (value[i] !== filter[i]) {\n return false;\n }\n }\n\n return true;\n };\n return filter;\n};\n","\nexport class InvalidStateException extends Error { }\n","import {timeDay, timeHour, timeMinute, timeMonth, timeSecond, timeWeek, timeYear} from 'd3-time';\nimport {format} from 'd3-format';\n\nimport {constants} from './constants';\nimport {config} from './config';\n\n/**\n * Returns a function that given a string property name, can be used to pluck the property off an object. A function\n * can be passed as the second argument to also alter the data being returned.\n *\n * This can be a useful shorthand method to create accessor functions.\n * @example\n * var xPluck = pluck('x');\n * var objA = {x: 1};\n * xPluck(objA) // 1\n * @example\n * var xPosition = pluck('x', function (x, i) {\n * // `this` is the original datum,\n * // `x` is the x property of the datum,\n * // `i` is the position in the array\n * return this.radius + x;\n * });\n * selectAll('.circle').data(...).x(xPosition);\n * @function pluck\n * @param {String} n\n * @param {Function} [f]\n * @returns {Function}\n */\nexport const pluck = function (n, f) {\n if (!f) {\n return function (d) { return d[n]; };\n }\n return function (d, i) { return f.call(d, d[n], i); };\n};\n\n/**\n * @namespace utils\n * @type {{}}\n */\nexport const utils = {};\n\n/**\n * Print a single value filter.\n * @method printSingleValue\n * @memberof utils\n * @param {any} filter\n * @returns {String}\n */\nutils.printSingleValue = function (filter) {\n let s = `${filter}`;\n\n if (filter instanceof Date) {\n s = config.dateFormat(filter);\n } else if (typeof (filter) === 'string') {\n s = filter;\n } else if (utils.isFloat(filter)) {\n s = utils.printSingleValue.fformat(filter);\n } else if (utils.isInteger(filter)) {\n s = Math.round(filter);\n }\n\n return s;\n};\nutils.printSingleValue.fformat = format('.2f');\n\n// convert 'day' to d3.timeDay and similar\nutils._toTimeFunc = function (t) {\n const mappings = {\n 'second': timeSecond,\n 'minute': timeMinute,\n 'hour': timeHour,\n 'day': timeDay,\n 'week': timeWeek,\n 'month': timeMonth,\n 'year': timeYear\n };\n return mappings[t];\n};\n\n/**\n * Arbitrary add one value to another.\n *\n * If the value l is of type Date, adds r units to it. t becomes the unit.\n * For example utils.add(dt, 3, 'week') will add 3 (r = 3) weeks (t= 'week') to dt.\n *\n * If l is of type numeric, t is ignored. In this case if r is of type string,\n * it is assumed to be percentage (whether or not it includes %). For example\n * utils.add(30, 10) will give 40 and utils.add(30, '10') will give 33.\n *\n * They also generate strange results if l is a string.\n * @method add\n * @memberof utils\n * @param {Date|Number} l the value to modify\n * @param {String|Number} r the amount by which to modify the value\n * @param {Function|String} [t=d3.timeDay] if `l` is a `Date`, then this should be a\n * [d3 time interval](https://github.com/d3/d3-time/blob/master/README.md#_interval).\n * For backward compatibility with dc.js 2.0, it can also be the name of an interval, i.e.\n * 'millis', 'second', 'minute', 'hour', 'day', 'week', 'month', or 'year'\n * @returns {Date|Number}\n */\nutils.add = function (l, r, t) {\n if (typeof r === 'string') {\n r = r.replace('%', '');\n }\n\n if (l instanceof Date) {\n if (typeof r === 'string') {\n r = +r;\n }\n if (t === 'millis') {\n return new Date(l.getTime() + r);\n }\n t = t || timeDay;\n if (typeof t !== 'function') {\n t = utils._toTimeFunc(t);\n }\n return t.offset(l, r);\n } else if (typeof r === 'string') {\n const percentage = (+r / 100);\n return l > 0 ? l * (1 + percentage) : l * (1 - percentage);\n } else {\n return l + r;\n }\n};\n\n/**\n * Arbitrary subtract one value from another.\n *\n * If the value l is of type Date, subtracts r units from it. t becomes the unit.\n * For example utils.subtract(dt, 3, 'week') will subtract 3 (r = 3) weeks (t= 'week') from dt.\n *\n * If l is of type numeric, t is ignored. In this case if r is of type string,\n * it is assumed to be percentage (whether or not it includes %). For example\n * utils.subtract(30, 10) will give 20 and utils.subtract(30, '10') will give 27.\n *\n * They also generate strange results if l is a string.\n * @method subtract\n * @memberof utils\n * @param {Date|Number} l the value to modify\n * @param {String|Number} r the amount by which to modify the value\n * @param {Function|String} [t=d3.timeDay] if `l` is a `Date`, then this should be a\n * [d3 time interval](https://github.com/d3/d3-time/blob/master/README.md#_interval).\n * For backward compatibility with dc.js 2.0, it can also be the name of an interval, i.e.\n * 'millis', 'second', 'minute', 'hour', 'day', 'week', 'month', or 'year'\n * @returns {Date|Number}\n */\nutils.subtract = function (l, r, t) {\n if (typeof r === 'string') {\n r = r.replace('%', '');\n }\n\n if (l instanceof Date) {\n if (typeof r === 'string') {\n r = +r;\n }\n if (t === 'millis') {\n return new Date(l.getTime() - r);\n }\n t = t || timeDay;\n if (typeof t !== 'function') {\n t = utils._toTimeFunc(t);\n }\n return t.offset(l, -r);\n } else if (typeof r === 'string') {\n const percentage = (+r / 100);\n return l < 0 ? l * (1 + percentage) : l * (1 - percentage);\n } else {\n return l - r;\n }\n};\n\n/**\n * Is the value a number?\n * @method isNumber\n * @memberof utils\n * @param {any} n\n * @returns {Boolean}\n */\nutils.isNumber = function (n) {\n return n === +n;\n};\n\n/**\n * Is the value a float?\n * @method isFloat\n * @memberof utils\n * @param {any} n\n * @returns {Boolean}\n */\nutils.isFloat = function (n) {\n return n === +n && n !== (n | 0);\n};\n\n/**\n * Is the value an integer?\n * @method isInteger\n * @memberof utils\n * @param {any} n\n * @returns {Boolean}\n */\nutils.isInteger = function (n) {\n return n === +n && n === (n | 0);\n};\n\n/**\n * Is the value very close to zero?\n * @method isNegligible\n * @memberof utils\n * @param {any} n\n * @returns {Boolean}\n */\nutils.isNegligible = function (n) {\n return !utils.isNumber(n) || (n < constants.NEGLIGIBLE_NUMBER && n > -constants.NEGLIGIBLE_NUMBER);\n};\n\n/**\n * Ensure the value is no greater or less than the min/max values. If it is return the boundary value.\n * @method clamp\n * @memberof utils\n * @param {any} val\n * @param {any} min\n * @param {any} max\n * @returns {any}\n */\nutils.clamp = function (val, min, max) {\n return val < min ? min : (val > max ? max : val);\n};\n\n/**\n * Given `x`, return a function that always returns `x`.\n *\n * {@link https://github.com/d3/d3/blob/master/CHANGES.md#internals `d3.functor` was removed in d3 version 4}.\n * This function helps to implement the replacement,\n * `typeof x === \"function\" ? x : utils.constant(x)`\n * @method constant\n * @memberof utils\n * @param {any} x\n * @returns {Function}\n */\nutils.constant = function (x) {\n return function () {\n return x;\n };\n};\n\n/**\n * Using a simple static counter, provide a unique integer id.\n * @method uniqueId\n * @memberof utils\n * @returns {Number}\n */\nlet _idCounter = 0;\nutils.uniqueId = function () {\n return ++_idCounter;\n};\n\n/**\n * Convert a name to an ID.\n * @method nameToId\n * @memberof utils\n * @param {String} name\n * @returns {String}\n */\nutils.nameToId = function (name) {\n return name.toLowerCase().replace(/[\\s]/g, '_').replace(/[\\.']/g, '');\n};\n\n/**\n * Append or select an item on a parent element.\n * @method appendOrSelect\n * @memberof utils\n * @param {d3.selection} parent\n * @param {String} selector\n * @param {String} tag\n * @returns {d3.selection}\n */\nutils.appendOrSelect = function (parent, selector, tag) {\n tag = tag || selector;\n let element = parent.select(selector);\n if (element.empty()) {\n element = parent.append(tag);\n }\n return element;\n};\n\n/**\n * Return the number if the value is a number; else 0.\n * @method safeNumber\n * @memberof utils\n * @param {Number|any} n\n * @returns {Number}\n */\nutils.safeNumber = function (n) { return utils.isNumber(+n) ? +n : 0;};\n\n/**\n * Return true if both arrays are equal, if both array are null these are considered equal\n * @method arraysEqual\n * @memberof utils\n * @param {Array|null} a1\n * @param {Array|null} a2\n * @returns {Boolean}\n */\nutils.arraysEqual = function (a1, a2) {\n if (!a1 && !a2) {\n return true;\n }\n\n if (!a1 || !a2) {\n return false;\n }\n\n return a1.length === a2.length &&\n // If elements are not integers/strings, we hope that it will match because of toString\n // Test cases cover dates as well.\n a1.every((elem, i) => elem.valueOf() === a2[i].valueOf());\n};\n\n// ******** Sunburst Chart ********\nutils.allChildren = function (node) {\n let paths = [];\n paths.push(node.path);\n console.log('currentNode', node);\n if (node.children) {\n for (let i = 0; i < node.children.length; i++) {\n paths = paths.concat(utils.allChildren(node.children[i]));\n }\n }\n return paths;\n};\n\n// builds a d3 Hierarchy from a collection\n// TODO: turn this monster method something better.\nutils.toHierarchy = function (list, accessor) {\n const root = {'key': 'root', 'children': []};\n for (let i = 0; i < list.length; i++) {\n const data = list[i];\n const parts = data.key;\n const value = accessor(data);\n let currentNode = root;\n for (let j = 0; j < parts.length; j++) {\n const currentPath = parts.slice(0, j + 1);\n const children = currentNode.children;\n const nodeName = parts[j];\n let childNode;\n if (j + 1 < parts.length) {\n // Not yet at the end of the sequence; move down the tree.\n childNode = findChild(children, nodeName);\n\n // If we don't already have a child node for this branch, create it.\n if (childNode === void 0) {\n childNode = {'key': nodeName, 'children': [], 'path': currentPath};\n children.push(childNode);\n }\n currentNode = childNode;\n } else {\n // Reached the end of the sequence; create a leaf node.\n childNode = {'key': nodeName, 'value': value, 'data': data, 'path': currentPath};\n children.push(childNode);\n }\n }\n }\n return root;\n};\n\nfunction findChild (children, nodeName) {\n for (let k = 0; k < children.length; k++) {\n if (children[k].key === nodeName) {\n return children[k];\n }\n }\n}\n\nutils.getAncestors = function (node) {\n const path = [];\n let current = node;\n while (current.parent) {\n path.unshift(current.name);\n current = current.parent;\n }\n return path;\n};\n\nutils.arraysIdentical = function (a, b) {\n let i = a.length;\n if (i !== b.length) {\n return false;\n }\n while (i--) {\n if (a[i] !== b[i]) {\n return false;\n }\n }\n return true;\n};\n","import {utils} from './utils';\n\n/**\n * @namespace printers\n * @type {{}}\n */\nexport const printers = {};\n\n/**\n * Converts a list of filters into a readable string.\n * @method filters\n * @memberof printers\n * @param {Array} filters\n * @returns {String}\n */\nprinters.filters = function (filters) {\n let s = '';\n\n for (let i = 0; i < filters.length; ++i) {\n if (i > 0) {\n s += ', ';\n }\n s += printers.filter(filters[i]);\n }\n\n return s;\n};\n\n/**\n * Converts a filter into a readable string.\n * @method filter\n * @memberof printers\n * @param {filters|any|Array} filter\n * @returns {String}\n */\nprinters.filter = function (filter) {\n let s = '';\n\n if (typeof filter !== 'undefined' && filter !== null) {\n if (filter instanceof Array) {\n if (filter.length >= 2) {\n s = `[${filter.map(e => utils.printSingleValue(e)).join(' -> ')}]`;\n } else if (filter.length >= 1) {\n s = utils.printSingleValue(filter[0]);\n }\n } else {\n s = utils.printSingleValue(filter);\n }\n }\n\n return s;\n};\n","import {utils} from './utils';\n\n/**\n * @namespace units\n * @type {{}}\n */\nexport const units = {};\n\n/**\n * The default value for {@link CoordinateGridMixin#xUnits .xUnits} for the\n * {@link CoordinateGridMixin Coordinate Grid Chart} and should\n * be used when the x values are a sequence of integers.\n * It is a function that counts the number of integers in the range supplied in its start and end parameters.\n * @method integers\n * @memberof units\n * @see {@link CoordinateGridMixin#xUnits coordinateGridMixin.xUnits}\n * @example\n * chart.xUnits(units.integers) // already the default\n * @param {Number} start\n * @param {Number} end\n * @returns {Number}\n */\nunits.integers = function (start, end) {\n return Math.abs(end - start);\n};\n\n/**\n * This argument can be passed to the {@link CoordinateGridMixin#xUnits .xUnits} function of a\n * coordinate grid chart to specify ordinal units for the x axis. Usually this parameter is used in\n * combination with passing\n * {@link https://github.com/d3/d3-scale/blob/master/README.md#ordinal-scales d3.scaleOrdinal}\n * to {@link CoordinateGridMixin#x .x}.\n *\n * As of dc.js 3.0, this is purely a placeholder or magic value which causes the chart to go into ordinal mode; the\n * function is not called.\n * @method ordinal\n * @memberof units\n * @return {uncallable}\n * @see {@link https://github.com/d3/d3-scale/blob/master/README.md#ordinal-scales d3.scaleOrdinal}\n * @see {@link CoordinateGridMixin#xUnits coordinateGridMixin.xUnits}\n * @see {@link CoordinateGridMixin#x coordinateGridMixin.x}\n * @example\n * chart.xUnits(units.ordinal)\n * .x(d3.scaleOrdinal())\n */\nunits.ordinal = function () {\n throw new Error('dc.units.ordinal should not be called - it is a placeholder');\n};\n\n/**\n * @namespace fp\n * @memberof units\n * @type {{}}\n */\nunits.fp = {};\n/**\n * This function generates an argument for the {@link CoordinateGridMixin Coordinate Grid Chart}\n * {@link CoordinateGridMixin#xUnits .xUnits} function specifying that the x values are floating-point\n * numbers with the given precision.\n * The returned function determines how many values at the given precision will fit into the range\n * supplied in its start and end parameters.\n * @method precision\n * @memberof units.fp\n * @see {@link CoordinateGridMixin#xUnits coordinateGridMixin.xUnits}\n * @example\n * // specify values (and ticks) every 0.1 units\n * chart.xUnits(units.fp.precision(0.1)\n * // there are 500 units between 0.5 and 1 if the precision is 0.001\n * var thousandths = units.fp.precision(0.001);\n * thousandths(0.5, 1.0) // returns 500\n * @param {Number} precision\n * @returns {Function} start-end unit function\n */\nunits.fp.precision = function (precision) {\n const _f = function (s, e) {\n const d = Math.abs((e - s) / _f.resolution);\n if (utils.isNegligible(d - Math.floor(d))) {\n return Math.floor(d);\n } else {\n return Math.ceil(d);\n }\n };\n _f.resolution = precision;\n return _f;\n};\n","import {select} from 'd3-selection';\nimport {dispatch} from 'd3-dispatch';\nimport {ascending} from 'd3-array';\n\nimport {pluck, utils} from '../core/utils';\nimport {instanceOfChart} from '../core/core';\nimport {deregisterChart, redrawAll, registerChart, renderAll} from '../core/chart-registry';\nimport {constants} from '../core/constants';\nimport {events} from '../core/events';\nimport {logger} from '../core/logger';\nimport {printers} from '../core/printers';\nimport {InvalidStateException} from '../core/invalid-state-exception';\nimport {BadArgumentException} from '../core/bad-argument-exception';\nimport {d3compat} from '../core/config';\n\nconst _defaultFilterHandler = (dimension, filters) => {\n if (filters.length === 0) {\n dimension.filter(null);\n } else if (filters.length === 1 && !filters[0].isFiltered) {\n // single value and not a function-based filter\n dimension.filterExact(filters[0]);\n } else if (filters.length === 1 && filters[0].filterType === 'RangedFilter') {\n // single range-based filter\n dimension.filterRange(filters[0]);\n } else {\n dimension.filterFunction(d => {\n for (let i = 0; i < filters.length; i++) {\n const filter = filters[i];\n if (filter.isFiltered) {\n if(filter.isFiltered(d)) {\n return true;\n }\n } else if (filter <= d && filter >= d) {\n return true;\n }\n }\n return false;\n });\n }\n return filters;\n};\n\nconst _defaultHasFilterHandler = (filters, filter) => {\n if (filter === null || typeof (filter) === 'undefined') {\n return filters.length > 0;\n }\n return filters.some(f => filter <= f && filter >= f);\n};\n\nconst _defaultRemoveFilterHandler = (filters, filter) => {\n for (let i = 0; i < filters.length; i++) {\n if (filters[i] <= filter && filters[i] >= filter) {\n filters.splice(i, 1);\n break;\n }\n }\n return filters;\n};\n\nconst _defaultAddFilterHandler = (filters, filter) => {\n filters.push(filter);\n return filters;\n};\n\nconst _defaultResetFilterHandler = filters => [];\n\n/**\n * `BaseMixin` is an abstract functional object representing a basic `dc` chart object\n * for all chart and widget implementations. Methods from the {@link #BaseMixin BaseMixin} are inherited\n * and available on all chart implementations in the `dc` library.\n * @mixin BaseMixin\n */\nexport class BaseMixin {\n constructor () {\n this.__dcFlag__ = utils.uniqueId();\n this._svgDescription = null\n this._keyboardAccessible = false;\n\n this._dimension = undefined;\n this._group = undefined;\n\n this._anchor = undefined;\n this._root = undefined;\n this._svg = undefined;\n this._isChild = undefined;\n\n this._minWidth = 200;\n this._defaultWidthCalc = element => {\n const width = element && element.getBoundingClientRect && element.getBoundingClientRect().width;\n return (width && width > this._minWidth) ? width : this._minWidth;\n };\n this._widthCalc = this._defaultWidthCalc;\n\n this._minHeight = 200;\n this._defaultHeightCalc = element => {\n const height = element && element.getBoundingClientRect && element.getBoundingClientRect().height;\n return (height && height > this._minHeight) ? height : this._minHeight;\n };\n this._heightCalc = this._defaultHeightCalc;\n this._width = undefined;\n this._height = undefined;\n this._useViewBoxResizing = false;\n\n this._keyAccessor = pluck('key');\n this._valueAccessor = pluck('value');\n this._label = pluck('key');\n\n this._ordering = pluck('key');\n\n this._renderLabel = false;\n\n this._title = d => `${this.keyAccessor()(d)}: ${this.valueAccessor()(d)}`;\n this._renderTitle = true;\n this._controlsUseVisibility = false;\n\n this._transitionDuration = 750;\n\n this._transitionDelay = 0;\n\n this._filterPrinter = printers.filters;\n\n this._mandatoryAttributesList = ['dimension', 'group'];\n\n this._chartGroup = constants.DEFAULT_CHART_GROUP;\n\n this._listeners = dispatch(\n 'preRender',\n 'postRender',\n 'preRedraw',\n 'postRedraw',\n 'filtered',\n 'zoomed',\n 'renderlet',\n 'pretransition');\n\n this._legend = undefined;\n this._commitHandler = undefined;\n\n this._defaultData = group => group.all();\n this._data = this._defaultData;\n\n this._filters = [];\n\n this._filterHandler = _defaultFilterHandler;\n this._hasFilterHandler = _defaultHasFilterHandler;\n this._removeFilterHandler = _defaultRemoveFilterHandler;\n this._addFilterHandler = _defaultAddFilterHandler;\n this._resetFilterHandler = _defaultResetFilterHandler;\n }\n\n /**\n * Set or get the height attribute of a chart. The height is applied to the SVGElement generated by\n * the chart when rendered (or re-rendered). If a value is given, then it will be used to calculate\n * the new height and the chart returned for method chaining. The value can either be a numeric, a\n * function, or falsy. If no value is specified then the value of the current height attribute will\n * be returned.\n *\n * By default, without an explicit height being given, the chart will select the width of its\n * anchor element. If that isn't possible it defaults to 200 (provided by the\n * {@link BaseMixin#minHeight minHeight} property). Setting the value falsy will return\n * the chart to the default behavior.\n * @see {@link BaseMixin#minHeight minHeight}\n * @example\n * // Default height\n * chart.height(function (element) {\n * var height = element && element.getBoundingClientRect && element.getBoundingClientRect().height;\n * return (height && height > chart.minHeight()) ? height : chart.minHeight();\n * });\n *\n * chart.height(250); // Set the chart's height to 250px;\n * chart.height(function(anchor) { return doSomethingWith(anchor); }); // set the chart's height with a function\n * chart.height(null); // reset the height to the default auto calculation\n * @param {Number|Function} [height]\n * @returns {Number|BaseMixin}\n */\n height (height) {\n if (!arguments.length) {\n if (!utils.isNumber(this._height)) {\n // only calculate once\n this._height = this._heightCalc(this._root.node());\n }\n return this._height;\n }\n this._heightCalc = height ? (typeof height === 'function' ? height : utils.constant(height)) : this._defaultHeightCalc;\n this._height = undefined;\n return this;\n }\n\n /**\n * Set or get the width attribute of a chart.\n * @see {@link BaseMixin#height height}\n * @see {@link BaseMixin#minWidth minWidth}\n * @example\n * // Default width\n * chart.width(function (element) {\n * var width = element && element.getBoundingClientRect && element.getBoundingClientRect().width;\n * return (width && width > chart.minWidth()) ? width : chart.minWidth();\n * });\n * @param {Number|Function} [width]\n * @returns {Number|BaseMixin}\n */\n width (width) {\n if (!arguments.length) {\n if (!utils.isNumber(this._width)) {\n // only calculate once\n this._width = this._widthCalc(this._root.node());\n }\n return this._width;\n }\n this._widthCalc = width ? (typeof width === 'function' ? width : utils.constant(width)) : this._defaultWidthCalc;\n this._width = undefined;\n return this;\n }\n\n /**\n * Set or get the minimum width attribute of a chart. This only has effect when used with the default\n * {@link BaseMixin#width width} function.\n * @see {@link BaseMixin#width width}\n * @param {Number} [minWidth=200]\n * @returns {Number|BaseMixin}\n */\n minWidth (minWidth) {\n if (!arguments.length) {\n return this._minWidth;\n }\n this._minWidth = minWidth;\n return this;\n }\n\n /**\n * Set or get the minimum height attribute of a chart. This only has effect when used with the default\n * {@link BaseMixin#height height} function.\n * @see {@link BaseMixin#height height}\n * @param {Number} [minHeight=200]\n * @returns {Number|BaseMixin}\n */\n minHeight (minHeight) {\n if (!arguments.length) {\n return this._minHeight;\n }\n this._minHeight = minHeight;\n return this;\n }\n\n /**\n * Turn on/off using the SVG\n * {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox `viewBox` attribute}.\n * When enabled, `viewBox` will be set on the svg root element instead of `width` and `height`.\n * Requires that the chart aspect ratio be defined using chart.width(w) and chart.height(h).\n *\n * This will maintain the aspect ratio while enabling the chart to resize responsively to the\n * space given to the chart using CSS. For example, the chart can use `width: 100%; height:\n * 100%` or absolute positioning to resize to its parent div.\n *\n * Since the text will be sized as if the chart is drawn according to the width and height, and\n * will be resized if the chart is any other size, you need to set the chart width and height so\n * that the text looks good. In practice, 600x400 seems to work pretty well for most charts.\n *\n * You can see examples of this resizing strategy in the [Chart Resizing\n * Examples](http://dc-js.github.io/dc.js/resizing/); just add `?resize=viewbox` to any of the\n * one-chart examples to enable `useViewBoxResizing`.\n * @param {Boolean} [useViewBoxResizing=false]\n * @returns {Boolean|BaseMixin}\n */\n useViewBoxResizing (useViewBoxResizing) {\n if (!arguments.length) {\n return this._useViewBoxResizing;\n }\n this._useViewBoxResizing = useViewBoxResizing;\n return this;\n }\n\n /**\n * **mandatory**\n *\n * Set or get the dimension attribute of a chart. In `dc`, a dimension can be any valid\n * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#dimension crossfilter dimension}\n *\n * If a value is given, then it will be used as the new dimension. If no value is specified then\n * the current dimension will be returned.\n * @see {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#dimension crossfilter.dimension}\n * @example\n * var index = crossfilter([]);\n * var dimension = index.dimension(pluck('key'));\n * chart.dimension(dimension);\n * @param {crossfilter.dimension} [dimension]\n * @returns {crossfilter.dimension|BaseMixin}\n */\n dimension (dimension) {\n if (!arguments.length) {\n return this._dimension;\n }\n this._dimension = dimension;\n this.expireCache();\n return this;\n }\n\n /**\n * Set the data callback or retrieve the chart's data set. The data callback is passed the chart's\n * group and by default will return\n * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group_all group.all}.\n * This behavior may be modified to, for instance, return only the top 5 groups.\n * @example\n * // Default data function\n * chart.data(function (group) { return group.all(); });\n *\n * chart.data(function (group) { return group.top(5); });\n * @param {Function} [callback]\n * @returns {*|BaseMixin}\n */\n data (callback) {\n if (!arguments.length) {\n return this._data(this._group);\n }\n this._data = typeof callback === 'function' ? callback : utils.constant(callback);\n this.expireCache();\n return this;\n }\n\n /**\n * **mandatory**\n *\n * Set or get the group attribute of a chart. In `dc` a group is a\n * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group-map-reduce crossfilter group}.\n * Usually the group should be created from the particular dimension associated with the same chart. If a value is\n * given, then it will be used as the new group.\n *\n * If no value specified then the current group will be returned.\n * If `name` is specified then it will be used to generate legend label.\n * @see {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group-map-reduce crossfilter.group}\n * @example\n * var index = crossfilter([]);\n * var dimension = index.dimension(pluck('key'));\n * chart.dimension(dimension);\n * chart.group(dimension.group().reduceSum());\n * @param {crossfilter.group} [group]\n * @param {String} [name]\n * @returns {crossfilter.group|BaseMixin}\n */\n group (group, name) {\n if (!arguments.length) {\n return this._group;\n }\n this._group = group;\n this._groupName = name;\n this.expireCache();\n return this;\n }\n\n /**\n * Get or set an accessor to order ordinal dimensions. The chart uses\n * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort Array.sort}\n * to sort elements; this accessor returns the value to order on.\n * @example\n * // Default ordering accessor\n * _chart.ordering(pluck('key'));\n * @param {Function} [orderFunction]\n * @returns {Function|BaseMixin}\n */\n ordering (orderFunction) {\n if (!arguments.length) {\n return this._ordering;\n }\n this._ordering = orderFunction;\n this.expireCache();\n return this;\n }\n\n _computeOrderedGroups (data) {\n // clone the array before sorting, otherwise Array.sort sorts in-place\n return Array.from(data).sort((a, b) => ascending(this._ordering(a), this._ordering(b)));\n }\n\n /**\n * Clear all filters associated with this chart. The same effect can be achieved by calling\n * {@link BaseMixin#filter chart.filter(null)}.\n * @returns {BaseMixin}\n */\n filterAll () {\n return this.filter(null);\n }\n\n /**\n * Execute d3 single selection in the chart's scope using the given selector and return the d3\n * selection.\n *\n * This function is **not chainable** since it does not return a chart instance; however the d3\n * selection result can be chained to d3 function calls.\n * @see {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3.select}\n * @example\n * // Has the same effect as d3.select('#chart-id').select(selector)\n * chart.select(selector)\n * @param {String} sel CSS selector string\n * @returns {d3.selection}\n */\n select (sel) {\n return this._root.select(sel);\n }\n\n /**\n * Execute in scope d3 selectAll using the given selector and return d3 selection result.\n *\n * This function is **not chainable** since it does not return a chart instance; however the d3\n * selection result can be chained to d3 function calls.\n * @see {@link https://github.com/d3/d3-selection/blob/master/README.md#selectAll d3.selectAll}\n * @example\n * // Has the same effect as d3.select('#chart-id').selectAll(selector)\n * chart.selectAll(selector)\n * @param {String} sel CSS selector string\n * @returns {d3.selection}\n */\n selectAll (sel) {\n return this._root ? this._root.selectAll(sel) : null;\n }\n\n /**\n * Set the root SVGElement to either be an existing chart's root; or any valid [d3 single\n * selector](https://github.com/d3/d3-selection/blob/master/README.md#selecting-elements) specifying a dom\n * block element such as a div; or a dom element or d3 selection. Optionally registers the chart\n * within the chartGroup. This class is called internally on chart initialization, but be called\n * again to relocate the chart. However, it will orphan any previously created SVGElements.\n * @param {anchorChart|anchorSelector|anchorNode} [parent]\n * @param {String} [chartGroup]\n * @returns {String|node|d3.selection|BaseMixin}\n */\n anchor (parent, chartGroup) {\n if (!arguments.length) {\n return this._anchor;\n }\n if (instanceOfChart(parent)) {\n this._anchor = parent.anchor();\n if (this._anchor.children) { // is _anchor a div?\n this._anchor = `#${parent.anchorName()}`;\n }\n this._root = parent.root();\n this._isChild = true;\n } else if (parent) {\n if (parent.select && parent.classed) { // detect d3 selection\n this._anchor = parent.node();\n } else {\n this._anchor = parent;\n }\n this._root = select(this._anchor);\n this._root.classed(constants.CHART_CLASS, true);\n registerChart(this, chartGroup);\n this._isChild = false;\n } else {\n throw new BadArgumentException('parent must be defined');\n }\n this._chartGroup = chartGroup;\n return this;\n }\n\n /**\n * Returns the DOM id for the chart's anchored location.\n * @returns {String}\n */\n anchorName () {\n const a = this.anchor();\n if (a && a.id) {\n return a.id;\n }\n if (a && a.replace) {\n return a.replace('#', '');\n }\n return `dc-chart${this.chartID()}`;\n }\n\n /**\n * Returns the root element where a chart resides. Usually it will be the parent div element where\n * the SVGElement was created. You can also pass in a new root element however this is usually handled by\n * dc internally. Resetting the root element on a chart outside of dc internals may have\n * unexpected consequences.\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement HTMLElement}\n * @param {HTMLElement} [rootElement]\n * @returns {HTMLElement|BaseMixin}\n */\n root (rootElement) {\n if (!arguments.length) {\n return this._root;\n }\n this._root = rootElement;\n return this;\n }\n\n /**\n * Returns the top SVGElement for this specific chart. You can also pass in a new SVGElement,\n * however this is usually handled by dc internally. Resetting the SVGElement on a chart outside\n * of dc internals may have unexpected consequences.\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGElement SVGElement}\n * @param {SVGElement|d3.selection} [svgElement]\n * @returns {SVGElement|d3.selection|BaseMixin}\n */\n svg (svgElement) {\n if (!arguments.length) {\n return this._svg;\n }\n this._svg = svgElement;\n return this;\n }\n\n /**\n * Remove the chart's SVGElements from the dom and recreate the container SVGElement.\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGElement SVGElement}\n * @returns {SVGElement}\n */\n resetSvg () {\n this.select('svg').remove();\n return this.generateSvg();\n }\n\n sizeSvg () {\n if (this._svg) {\n if (!this._useViewBoxResizing) {\n this._svg\n .attr('width', this.width())\n .attr('height', this.height());\n } else if (!this._svg.attr('viewBox')) {\n this._svg\n .attr('viewBox', `0 0 ${this.width()} ${this.height()}`);\n }\n }\n }\n\n generateSvg () {\n this._svg = this.root().append('svg');\n \n if (this._svgDescription || this._keyboardAccessible) {\n\n this._svg.append('desc')\n .attr('id', `desc-id-${this.__dcFlag__}`)\n .html(`${this.svgDescription()}`);\n\n this._svg\n .attr('tabindex', '0')\n .attr('role', 'img')\n .attr('aria-labelledby', `desc-id-${this.__dcFlag__}`);\n }\n\n this.sizeSvg();\n return this._svg;\n }\n\n /**\n * Set or get description text for the entire SVG graphic. If set, will create a `` element as the first\n * child of the SVG with the description text and also make the SVG focusable from keyboard.\n * @param {String} [description]\n * @returns {String|BaseMixin}\n */\n svgDescription (description) {\n if (!arguments.length) {\n return this._svgDescription || this.constructor.name;\n }\n\n this._svgDescription = description;\n return this;\n }\n\n /**\n * If set, interactive chart elements like individual bars in a bar chart or symbols in a scatter plot\n * will be focusable from keyboard and on pressing Enter or Space will behave as if clicked on.\n * \n * If `svgDescription` has not been explicitly set, will also set SVG description text to the class\n * constructor name, like BarChart or HeatMap, and make the entire SVG focusable.\n * @param {Boolean} [keyboardAccessible=false]\n * @returns {Boolean|BarChart}\n */\n keyboardAccessible (keyboardAccessible) {\n if (!arguments.length) {\n return this._keyboardAccessible;\n }\n this._keyboardAccessible = keyboardAccessible;\n return this;\n }\n\n /**\n * Set or get the filter printer function. The filter printer function is used to generate human\n * friendly text for filter value(s) associated with the chart instance. The text will get shown\n * in the `.filter element; see {@link BaseMixin#turnOnControls turnOnControls}.\n *\n * By default dc charts use a default filter printer {@link printers.filters printers.filters}\n * that provides simple printing support for both single value and ranged filters.\n * @example\n * // for a chart with an ordinal brush, print the filters in upper case\n * chart.filterPrinter(function(filters) {\n * return filters.map(function(f) { return f.toUpperCase(); }).join(', ');\n * });\n * // for a chart with a range brush, print the filter as start and extent\n * chart.filterPrinter(function(filters) {\n * return 'start ' + utils.printSingleValue(filters[0][0]) +\n * ' extent ' + utils.printSingleValue(filters[0][1] - filters[0][0]);\n * });\n * @param {Function} [filterPrinterFunction=printers.filters]\n * @returns {Function|BaseMixin}\n */\n filterPrinter (filterPrinterFunction) {\n if (!arguments.length) {\n return this._filterPrinter;\n }\n this._filterPrinter = filterPrinterFunction;\n return this;\n }\n\n /**\n * If set, use the `visibility` attribute instead of the `display` attribute for showing/hiding\n * chart reset and filter controls, for less disruption to the layout.\n * @param {Boolean} [controlsUseVisibility=false]\n * @returns {Boolean|BaseMixin}\n */\n controlsUseVisibility (controlsUseVisibility) {\n if (!arguments.length) {\n return this._controlsUseVisibility;\n }\n this._controlsUseVisibility = controlsUseVisibility;\n return this;\n }\n\n /**\n * Turn on optional control elements within the root element. dc currently supports the\n * following html control elements.\n * * root.selectAll('.reset') - elements are turned on if the chart has an active filter. This type\n * of control element is usually used to store a reset link to allow user to reset filter on a\n * certain chart. This element will be turned off automatically if the filter is cleared.\n * * root.selectAll('.filter') elements are turned on if the chart has an active filter. The text\n * content of this element is then replaced with the current filter value using the filter printer\n * function. This type of element will be turned off automatically if the filter is cleared.\n * @returns {BaseMixin}\n */\n turnOnControls () {\n if (this._root) {\n const attribute = this.controlsUseVisibility() ? 'visibility' : 'display';\n this.selectAll('.reset').style(attribute, null);\n this.selectAll('.filter').text(this._filterPrinter(this.filters())).style(attribute, null);\n }\n return this;\n }\n\n /**\n * Turn off optional control elements within the root element.\n * @see {@link BaseMixin#turnOnControls turnOnControls}\n * @returns {BaseMixin}\n */\n turnOffControls () {\n if (this._root) {\n const attribute = this.controlsUseVisibility() ? 'visibility' : 'display';\n const value = this.controlsUseVisibility() ? 'hidden' : 'none';\n this.selectAll('.reset').style(attribute, value);\n this.selectAll('.filter').style(attribute, value).text(this.filter());\n }\n return this;\n }\n\n /**\n * Set or get the animation transition duration (in milliseconds) for this chart instance.\n * @param {Number} [duration=750]\n * @returns {Number|BaseMixin}\n */\n transitionDuration (duration) {\n if (!arguments.length) {\n return this._transitionDuration;\n }\n this._transitionDuration = duration;\n return this;\n }\n\n /**\n * Set or get the animation transition delay (in milliseconds) for this chart instance.\n * @param {Number} [delay=0]\n * @returns {Number|BaseMixin}\n */\n transitionDelay (delay) {\n if (!arguments.length) {\n return this._transitionDelay;\n }\n this._transitionDelay = delay;\n return this;\n }\n\n _mandatoryAttributes (_) {\n if (!arguments.length) {\n return this._mandatoryAttributesList;\n }\n this._mandatoryAttributesList = _;\n return this;\n }\n\n checkForMandatoryAttributes (a) {\n if (!this[a] || !this[a]()) {\n throw new InvalidStateException(`Mandatory attribute chart.${a} is missing on chart[#${this.anchorName()}]`);\n }\n }\n\n /**\n * Invoking this method will force the chart to re-render everything from scratch. Generally it\n * should only be used to render the chart for the first time on the page or if you want to make\n * sure everything is redrawn from scratch instead of relying on the default incremental redrawing\n * behaviour.\n * @returns {BaseMixin}\n */\n render () {\n this._height = this._width = undefined; // force recalculate\n this._listeners.call('preRender', this, this);\n\n if (this._mandatoryAttributesList) {\n this._mandatoryAttributesList.forEach(e => this.checkForMandatoryAttributes(e));\n }\n\n const result = this._doRender();\n\n if (this._legend) {\n this._legend.render();\n }\n\n this._activateRenderlets('postRender');\n\n return result;\n }\n\n _makeKeyboardAccessible (onClickFunction, ...onClickArgs) {\n // called from each chart module's render and redraw methods\n const tabElements = this._svg\n .selectAll('.dc-tabbable')\n .attr('tabindex', 0);\n \n if (onClickFunction) {\n tabElements.on('keydown', d3compat.eventHandler((d, event) => {\n // trigger only if d is an object undestood by KeyAccessor()\n if (event.keyCode === 13 && typeof d === 'object') {\n onClickFunction.call(this, d, ...onClickArgs)\n } \n // special case for space key press - prevent scrolling\n if (event.keyCode === 32 && typeof d === 'object') {\n onClickFunction.call(this, d, ...onClickArgs)\n event.preventDefault(); \n }\n \n }));\n }\n }\n\n _activateRenderlets (event) {\n this._listeners.call('pretransition', this, this);\n if (this.transitionDuration() > 0 && this._svg) {\n this._svg.transition().duration(this.transitionDuration()).delay(this.transitionDelay())\n .on('end', () => {\n this._listeners.call('renderlet', this, this);\n if (event) {\n this._listeners.call(event, this, this);\n }\n });\n } else {\n this._listeners.call('renderlet', this, this);\n if (event) {\n this._listeners.call(event, this, this);\n }\n }\n }\n\n /**\n * Calling redraw will cause the chart to re-render data changes incrementally. If there is no\n * change in the underlying data dimension then calling this method will have no effect on the\n * chart. Most chart interaction in dc will automatically trigger this method through internal\n * events (in particular {@link redrawAll redrawAll}); therefore, you only need to\n * manually invoke this function if data is manipulated outside of dc's control (for example if\n * data is loaded in the background using\n * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#crossfilter_add crossfilter.add}).\n * @returns {BaseMixin}\n */\n redraw () {\n this.sizeSvg();\n this._listeners.call('preRedraw', this, this);\n\n const result = this._doRedraw();\n\n if (this._legend) {\n this._legend.render();\n }\n\n this._activateRenderlets('postRedraw');\n\n return result;\n }\n\n /**\n * Gets/sets the commit handler. If the chart has a commit handler, the handler will be called when\n * the chart's filters have changed, in order to send the filter data asynchronously to a server.\n *\n * Unlike other functions in dc.js, the commit handler is asynchronous. It takes two arguments:\n * a flag indicating whether this is a render (true) or a redraw (false), and a callback to be\n * triggered once the commit is done. The callback has the standard node.js continuation signature\n * with error first and result second.\n * @param {Function} commitHandler\n * @returns {BaseMixin}\n */\n commitHandler (commitHandler) {\n if (!arguments.length) {\n return this._commitHandler;\n }\n this._commitHandler = commitHandler;\n return this;\n }\n\n /**\n * Redraws all charts in the same group as this chart, typically in reaction to a filter\n * change. If the chart has a {@link BaseMixin.commitFilter commitHandler}, it will\n * be executed and waited for.\n * @returns {BaseMixin}\n */\n redrawGroup () {\n if (this._commitHandler) {\n this._commitHandler(false, (error, result) => {\n if (error) {\n console.log(error);\n } else {\n redrawAll(this.chartGroup());\n }\n });\n } else {\n redrawAll(this.chartGroup());\n }\n return this;\n }\n\n /**\n * Renders all charts in the same group as this chart. If the chart has a\n * {@link BaseMixin.commitFilter commitHandler}, it will be executed and waited for\n * @returns {BaseMixin}\n */\n renderGroup () {\n if (this._commitHandler) {\n this._commitHandler(false, (error, result) => {\n if (error) {\n console.log(error);\n } else {\n renderAll(this.chartGroup());\n }\n });\n } else {\n renderAll(this.chartGroup());\n }\n return this;\n }\n\n _invokeFilteredListener (f) {\n if (f !== undefined) {\n this._listeners.call('filtered', this, this, f);\n }\n }\n\n _invokeZoomedListener () {\n this._listeners.call('zoomed', this, this);\n }\n\n /**\n * Set or get the has-filter handler. The has-filter handler is a function that checks to see if\n * the chart's current filters (first argument) include a specific filter (second argument). Using a custom has-filter handler allows\n * you to change the way filters are checked for and replaced.\n * @example\n * // default has-filter handler\n * chart.hasFilterHandler(function (filters, filter) {\n * if (filter === null || typeof(filter) === 'undefined') {\n * return filters.length > 0;\n * }\n * return filters.some(function (f) {\n * return filter <= f && filter >= f;\n * });\n * });\n *\n * // custom filter handler (no-op)\n * chart.hasFilterHandler(function(filters, filter) {\n * return false;\n * });\n * @param {Function} [hasFilterHandler]\n * @returns {Function|BaseMixin}\n */\n hasFilterHandler (hasFilterHandler) {\n if (!arguments.length) {\n return this._hasFilterHandler;\n }\n this._hasFilterHandler = hasFilterHandler;\n return this;\n }\n\n /**\n * Check whether any active filter or a specific filter is associated with particular chart instance.\n * This function is **not chainable**.\n * @see {@link BaseMixin#hasFilterHandler hasFilterHandler}\n * @param {*} [filter]\n * @returns {Boolean}\n */\n hasFilter (filter) {\n return this._hasFilterHandler(this._filters, filter);\n }\n\n /**\n * Set or get the remove filter handler. The remove filter handler is a function that removes a\n * filter from the chart's current filters. Using a custom remove filter handler allows you to\n * change how filters are removed or perform additional work when removing a filter, e.g. when\n * using a filter server other than crossfilter.\n *\n * The handler should return a new or modified array as the result.\n * @example\n * // default remove filter handler\n * chart.removeFilterHandler(function (filters, filter) {\n * for (var i = 0; i < filters.length; i++) {\n * if (filters[i] <= filter && filters[i] >= filter) {\n * filters.splice(i, 1);\n * break;\n * }\n * }\n * return filters;\n * });\n *\n * // custom filter handler (no-op)\n * chart.removeFilterHandler(function(filters, filter) {\n * return filters;\n * });\n * @param {Function} [removeFilterHandler]\n * @returns {Function|BaseMixin}\n */\n removeFilterHandler (removeFilterHandler) {\n if (!arguments.length) {\n return this._removeFilterHandler;\n }\n this._removeFilterHandler = removeFilterHandler;\n return this;\n }\n\n /**\n * Set or get the add filter handler. The add filter handler is a function that adds a filter to\n * the chart's filter list. Using a custom add filter handler allows you to change the way filters\n * are added or perform additional work when adding a filter, e.g. when using a filter server other\n * than crossfilter.\n *\n * The handler should return a new or modified array as the result.\n * @example\n * // default add filter handler\n * chart.addFilterHandler(function (filters, filter) {\n * filters.push(filter);\n * return filters;\n * });\n *\n * // custom filter handler (no-op)\n * chart.addFilterHandler(function(filters, filter) {\n * return filters;\n * });\n * @param {Function} [addFilterHandler]\n * @returns {Function|BaseMixin}\n */\n addFilterHandler (addFilterHandler) {\n if (!arguments.length) {\n return this._addFilterHandler;\n }\n this._addFilterHandler = addFilterHandler;\n return this;\n }\n\n /**\n * Set or get the reset filter handler. The reset filter handler is a function that resets the\n * chart's filter list by returning a new list. Using a custom reset filter handler allows you to\n * change the way filters are reset, or perform additional work when resetting the filters,\n * e.g. when using a filter server other than crossfilter.\n *\n * The handler should return a new or modified array as the result.\n * @example\n * // default remove filter handler\n * function (filters) {\n * return [];\n * }\n *\n * // custom filter handler (no-op)\n * chart.resetFilterHandler(function(filters) {\n * return filters;\n * });\n * @param {Function} [resetFilterHandler]\n * @returns {BaseMixin}\n */\n resetFilterHandler (resetFilterHandler) {\n if (!arguments.length) {\n return this._resetFilterHandler;\n }\n this._resetFilterHandler = resetFilterHandler;\n return this;\n }\n\n applyFilters (filters) {\n if (this.dimension() && this.dimension().filter) {\n const fs = this._filterHandler(this.dimension(), filters);\n if (fs) {\n filters = fs;\n }\n }\n return filters;\n }\n\n /**\n * Replace the chart filter. This is equivalent to calling `chart.filter(null).filter(filter)`\n * but more efficient because the filter is only applied once.\n *\n * @param {*} [filter]\n * @returns {BaseMixin}\n */\n replaceFilter (filter) {\n this._filters = this._resetFilterHandler(this._filters);\n this.filter(filter);\n return this;\n }\n\n /**\n * Filter the chart by the given parameter, or return the current filter if no input parameter\n * is given.\n *\n * The filter parameter can take one of these forms:\n * * A single value: the value will be toggled (added if it is not present in the current\n * filters, removed if it is present)\n * * An array containing a single array of values (`[[value,value,value]]`): each value is\n * toggled\n * * When appropriate for the chart, a {@link filters dc filter object} such as\n * * {@link filters.RangedFilter `filters.RangedFilter`} for the\n * {@link CoordinateGridMixin CoordinateGridMixin} charts\n * * {@link filters.TwoDimensionalFilter `filters.TwoDimensionalFilter`} for the\n * {@link HeatMap heat map}\n * * {@link filters.RangedTwoDimensionalFilter `filters.RangedTwoDimensionalFilter`}\n * for the {@link ScatterPlot scatter plot}\n * * `null`: the filter will be reset using the\n * {@link BaseMixin#resetFilterHandler resetFilterHandler}\n *\n * Note that this is always a toggle (even when it doesn't make sense for the filter type). If\n * you wish to replace the current filter, either call `chart.filter(null)` first - or it's more\n * efficient to call {@link BaseMixin#replaceFilter `chart.replaceFilter(filter)`} instead.\n *\n * Each toggle is executed by checking if the value is already present using the\n * {@link BaseMixin#hasFilterHandler hasFilterHandler}; if it is not present, it is added\n * using the {@link BaseMixin#addFilterHandler addFilterHandler}; if it is already present,\n * it is removed using the {@link BaseMixin#removeFilterHandler removeFilterHandler}.\n *\n * Once the filters array has been updated, the filters are applied to the\n * crossfilter dimension, using the {@link BaseMixin#filterHandler filterHandler}.\n *\n * Once you have set the filters, call {@link BaseMixin#redrawGroup `chart.redrawGroup()`}\n * (or {@link redrawAll `redrawAll()`}) to redraw the chart's group.\n * @see {@link BaseMixin#addFilterHandler addFilterHandler}\n * @see {@link BaseMixin#removeFilterHandler removeFilterHandler}\n * @see {@link BaseMixin#resetFilterHandler resetFilterHandler}\n * @see {@link BaseMixin#filterHandler filterHandler}\n * @example\n * // filter by a single string\n * chart.filter('Sunday');\n * // filter by a single age\n * chart.filter(18);\n * // filter by a set of states\n * chart.filter([['MA', 'TX', 'ND', 'WA']]);\n * // filter by range -- note the use of filters.RangedFilter, which is different\n * // from the syntax for filtering a crossfilter dimension directly, dimension.filter([15,20])\n * chart.filter(filters.RangedFilter(15,20));\n * @param {*} [filter]\n * @returns {BaseMixin}\n */\n filter (filter) {\n if (!arguments.length) {\n return this._filters.length > 0 ? this._filters[0] : null;\n }\n let filters = this._filters;\n if (filter instanceof Array && filter[0] instanceof Array && !filter.isFiltered) {\n // toggle each filter\n filter[0].forEach(f => {\n if (this._hasFilterHandler(filters, f)) {\n filters = this._removeFilterHandler(filters, f);\n } else {\n filters = this._addFilterHandler(filters, f);\n }\n });\n } else if (filter === null) {\n filters = this._resetFilterHandler(filters);\n } else {\n if (this._hasFilterHandler(filters, filter)) {\n filters = this._removeFilterHandler(filters, filter);\n } else {\n filters = this._addFilterHandler(filters, filter);\n }\n }\n this._filters = this.applyFilters(filters);\n this._invokeFilteredListener(filter);\n\n if (this._root !== null && this.hasFilter()) {\n this.turnOnControls();\n } else {\n this.turnOffControls();\n }\n\n return this;\n }\n\n /**\n * Returns all current filters. This method does not perform defensive cloning of the internal\n * filter array before returning, therefore any modification of the returned array will effect the\n * chart's internal filter storage.\n * @returns {Array<*>}\n */\n filters () {\n return this._filters;\n }\n\n highlightSelected (e) {\n select(e).classed(constants.SELECTED_CLASS, true);\n select(e).classed(constants.DESELECTED_CLASS, false);\n }\n\n fadeDeselected (e) {\n select(e).classed(constants.SELECTED_CLASS, false);\n select(e).classed(constants.DESELECTED_CLASS, true);\n }\n\n resetHighlight (e) {\n select(e).classed(constants.SELECTED_CLASS, false);\n select(e).classed(constants.DESELECTED_CLASS, false);\n }\n\n /**\n * This function is passed to d3 as the onClick handler for each chart. The default behavior is to\n * filter on the clicked datum (passed to the callback) and redraw the chart group.\n *\n * This function can be replaced in order to change the click behavior (but first look at\n * @example\n * var oldHandler = chart.onClick;\n * chart.onClick = function(datum) {\n * // use datum.\n * @param {*} datum\n * @return {undefined}\n */\n onClick (datum) {\n const filter = this.keyAccessor()(datum);\n events.trigger(() => {\n this.filter(filter);\n this.redrawGroup();\n });\n }\n\n /**\n * Set or get the filter handler. The filter handler is a function that performs the filter action\n * on a specific dimension. Using a custom filter handler allows you to perform additional logic\n * before or after filtering.\n * @see {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#dimension_filter crossfilter.dimension.filter}\n * @example\n * // the default filter handler handles all possible cases for the charts in dc.js\n * // you can replace it with something more specialized for your own chart\n * chart.filterHandler(function (dimension, filters) {\n * if (filters.length === 0) {\n * // the empty case (no filtering)\n * dimension.filter(null);\n * } else if (filters.length === 1 && !filters[0].isFiltered) {\n * // single value and not a function-based filter\n * dimension.filterExact(filters[0]);\n * } else if (filters.length === 1 && filters[0].filterType === 'RangedFilter') {\n * // single range-based filter\n * dimension.filterRange(filters[0]);\n * } else {\n * // an array of values, or an array of filter objects\n * dimension.filterFunction(function (d) {\n * for (var i = 0; i < filters.length; i++) {\n * var filter = filters[i];\n * if (filter.isFiltered && filter.isFiltered(d)) {\n * return true;\n * } else if (filter <= d && filter >= d) {\n * return true;\n * }\n * }\n * return false;\n * });\n * }\n * return filters;\n * });\n *\n * // custom filter handler\n * chart.filterHandler(function(dimension, filter){\n * var newFilter = filter + 10;\n * dimension.filter(newFilter);\n * return newFilter; // set the actual filter value to the new value\n * });\n * @param {Function} [filterHandler]\n * @returns {Function|BaseMixin}\n */\n filterHandler (filterHandler) {\n if (!arguments.length) {\n return this._filterHandler;\n }\n this._filterHandler = filterHandler;\n return this;\n }\n\n // abstract function stub\n _doRender () {\n // do nothing in base, should be overridden by sub-function\n return this;\n }\n\n _doRedraw () {\n // do nothing in base, should be overridden by sub-function\n return this;\n }\n\n legendables () {\n // do nothing in base, should be overridden by sub-function\n return [];\n }\n\n legendHighlight () {\n // do nothing in base, should be overridden by sub-function\n }\n\n legendReset () {\n // do nothing in base, should be overridden by sub-function\n }\n\n legendToggle () {\n // do nothing in base, should be overriden by sub-function\n }\n\n isLegendableHidden () {\n // do nothing in base, should be overridden by sub-function\n return false;\n }\n\n /**\n * Set or get the key accessor function. The key accessor function is used to retrieve the key\n * value from the crossfilter group. Key values are used differently in different charts, for\n * example keys correspond to slices in a pie chart and x axis positions in a grid coordinate chart.\n * @example\n * // default key accessor\n * chart.keyAccessor(function(d) { return d.key; });\n * // custom key accessor for a multi-value crossfilter reduction\n * chart.keyAccessor(function(p) { return p.value.absGain; });\n * @param {Function} [keyAccessor]\n * @returns {Function|BaseMixin}\n */\n keyAccessor (keyAccessor) {\n if (!arguments.length) {\n return this._keyAccessor;\n }\n this._keyAccessor = keyAccessor;\n return this;\n }\n\n /**\n * Set or get the value accessor function. The value accessor function is used to retrieve the\n * value from the crossfilter group. Group values are used differently in different charts, for\n * example values correspond to slice sizes in a pie chart and y axis positions in a grid\n * coordinate chart.\n * @example\n * // default value accessor\n * chart.valueAccessor(function(d) { return d.value; });\n * // custom value accessor for a multi-value crossfilter reduction\n * chart.valueAccessor(function(p) { return p.value.percentageGain; });\n * @param {Function} [valueAccessor]\n * @returns {Function|BaseMixin}\n */\n valueAccessor (valueAccessor) {\n if (!arguments.length) {\n return this._valueAccessor;\n }\n this._valueAccessor = valueAccessor;\n return this;\n }\n\n /**\n * Set or get the label function. The chart class will use this function to render labels for each\n * child element in the chart, e.g. slices in a pie chart or bubbles in a bubble chart. Not every\n * chart supports the label function, for example line chart does not use this function\n * at all. By default, enables labels; pass false for the second parameter if this is not desired.\n * @example\n * // default label function just return the key\n * chart.label(function(d) { return d.key; });\n * // label function has access to the standard d3 data binding and can get quite complicated\n * chart.label(function(d) { return d.data.key + '(' + Math.floor(d.data.value / all.value() * 100) + '%)'; });\n * @param {Function} [labelFunction]\n * @param {Boolean} [enableLabels=true]\n * @returns {Function|BaseMixin}\n */\n label (labelFunction, enableLabels) {\n if (!arguments.length) {\n return this._label;\n }\n this._label = labelFunction;\n if ((enableLabels === undefined) || enableLabels) {\n this._renderLabel = true;\n }\n return this;\n }\n\n /**\n * Turn on/off label rendering\n * @param {Boolean} [renderLabel=false]\n * @returns {Boolean|BaseMixin}\n */\n renderLabel (renderLabel) {\n if (!arguments.length) {\n return this._renderLabel;\n }\n this._renderLabel = renderLabel;\n return this;\n }\n\n /**\n * Set or get the title function. The chart class will use this function to render the SVGElement title\n * (usually interpreted by browser as tooltips) for each child element in the chart, e.g. a slice\n * in a pie chart or a bubble in a bubble chart. Almost every chart supports the title function;\n * however in grid coordinate charts you need to turn off the brush in order to see titles, because\n * otherwise the brush layer will block tooltip triggering.\n * @example\n * // default title function shows \"key: value\"\n * chart.title(function(d) { return d.key + ': ' + d.value; });\n * // title function has access to the standard d3 data binding and can get quite complicated\n * chart.title(function(p) {\n * return p.key.getFullYear()\n * + '\\n'\n * + 'Index Gain: ' + numberFormat(p.value.absGain) + '\\n'\n * + 'Index Gain in Percentage: ' + numberFormat(p.value.percentageGain) + '%\\n'\n * + 'Fluctuation / Index Ratio: ' + numberFormat(p.value.fluctuationPercentage) + '%';\n * });\n * @param {Function} [titleFunction]\n * @returns {Function|BaseMixin}\n */\n title (titleFunction) {\n if (!arguments.length) {\n return this._title;\n }\n this._title = titleFunction;\n return this;\n }\n\n /**\n * Turn on/off title rendering, or return the state of the render title flag if no arguments are\n * given.\n * @param {Boolean} [renderTitle=true]\n * @returns {Boolean|BaseMixin}\n */\n renderTitle (renderTitle) {\n if (!arguments.length) {\n return this._renderTitle;\n }\n this._renderTitle = renderTitle;\n return this;\n }\n\n /**\n * Get or set the chart group to which this chart belongs. Chart groups are rendered or redrawn\n * together since it is expected they share the same underlying crossfilter data set.\n * @param {String} [chartGroup]\n * @returns {String|BaseMixin}\n */\n chartGroup (chartGroup) {\n if (!arguments.length) {\n return this._chartGroup;\n }\n if (!this._isChild) {\n deregisterChart(this, this._chartGroup);\n }\n this._chartGroup = chartGroup;\n if (!this._isChild) {\n registerChart(this, this._chartGroup);\n }\n return this;\n }\n\n /**\n * Expire the internal chart cache. dc charts cache some data internally on a per chart basis to\n * speed up rendering and avoid unnecessary calculation; however it might be useful to clear the\n * cache if you have changed state which will affect rendering. For example, if you invoke\n * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#crossfilter_add crossfilter.add}\n * function or reset group or dimension after rendering, it is a good idea to\n * clear the cache to make sure charts are rendered properly.\n * @returns {BaseMixin}\n */\n expireCache () {\n // do nothing in base, should be overridden by sub-function\n return this;\n }\n\n /**\n * Attach a Legend widget to this chart. The legend widget will automatically draw legend labels\n * based on the color setting and names associated with each group.\n * @example\n * chart.legend(new Legend().x(400).y(10).itemHeight(13).gap(5))\n * @param {Legend} [legend]\n * @returns {Legend|BaseMixin}\n */\n legend (legend) {\n if (!arguments.length) {\n return this._legend;\n }\n this._legend = legend;\n this._legend.parent(this);\n return this;\n }\n\n /**\n * Returns the internal numeric ID of the chart.\n * @returns {String}\n */\n chartID () {\n return this.__dcFlag__;\n }\n\n /**\n * Set chart options using a configuration object. Each key in the object will cause the method of\n * the same name to be called with the value to set that attribute for the chart.\n * @example\n * chart.options({dimension: myDimension, group: myGroup});\n * @param {{}} opts\n * @returns {BaseMixin}\n */\n options (opts) {\n const applyOptions = [\n 'anchor',\n 'group',\n 'xAxisLabel',\n 'yAxisLabel',\n 'stack',\n 'title',\n 'point',\n 'getColor',\n 'overlayGeoJson'\n ];\n\n for (const o in opts) {\n if (typeof (this[o]) === 'function') {\n if (opts[o] instanceof Array && applyOptions.indexOf(o) !== -1) {\n this[o].apply(this, opts[o]);\n } else {\n this[o].call(this, opts[o]);\n }\n } else {\n logger.debug(`Not a valid option setter name: ${o}`);\n }\n }\n return this;\n }\n\n /**\n * All dc chart instance supports the following listeners.\n * Supports the following events:\n * * `renderlet` - This listener function will be invoked after transitions after redraw and render. Replaces the\n * deprecated {@link BaseMixin#renderlet renderlet} method.\n * * `pretransition` - Like `.on('renderlet', ...)` but the event is fired before transitions start.\n * * `preRender` - This listener function will be invoked before chart rendering.\n * * `postRender` - This listener function will be invoked after chart finish rendering including\n * all renderlets' logic.\n * * `preRedraw` - This listener function will be invoked before chart redrawing.\n * * `postRedraw` - This listener function will be invoked after chart finish redrawing\n * including all renderlets' logic.\n * * `filtered` - This listener function will be invoked after a filter is applied, added or removed.\n * * `zoomed` - This listener function will be invoked after a zoom is triggered.\n * @see {@link https://github.com/d3/d3-dispatch/blob/master/README.md#dispatch_on d3.dispatch.on}\n * @example\n * .on('renderlet', function(chart, filter){...})\n * .on('pretransition', function(chart, filter){...})\n * .on('preRender', function(chart){...})\n * .on('postRender', function(chart){...})\n * .on('preRedraw', function(chart){...})\n * .on('postRedraw', function(chart){...})\n * .on('filtered', function(chart, filter){...})\n * .on('zoomed', function(chart, filter){...})\n * @param {String} event\n * @param {Function} listener\n * @returns {BaseMixin}\n */\n on (event, listener) {\n this._listeners.on(event, listener);\n return this;\n }\n\n /**\n * A renderlet is similar to an event listener on rendering event. Multiple renderlets can be added\n * to an individual chart. Each time a chart is rerendered or redrawn the renderlets are invoked\n * right after the chart finishes its transitions, giving you a way to modify the SVGElements.\n * Renderlet functions take the chart instance as the only input parameter and you can\n * use the dc API or use raw d3 to achieve pretty much any effect.\n *\n * Use {@link BaseMixin#on on} with a 'renderlet' prefix.\n * Generates a random key for the renderlet, which makes it hard to remove.\n * @deprecated chart.renderlet has been deprecated. Please use chart.on(\"renderlet.\", renderletFunction)\n * @example\n * // do this instead of .renderlet(function(chart) { ... })\n * chart.on(\"renderlet\", function(chart){\n * // mix of dc API and d3 manipulation\n * chart.select('g.y').style('display', 'none');\n * // its a closure so you can also access other chart variable available in the closure scope\n * moveChart.filter(chart.filter());\n * });\n * @param {Function} renderletFunction\n * @returns {BaseMixin}\n */\n renderlet (renderletFunction) {\n logger.warnOnce('chart.renderlet has been deprecated. Please use chart.on(\"renderlet.\", renderletFunction)');\n this.on(`renderlet.${utils.uniqueId()}`, renderletFunction);\n return this;\n }\n}\n\nexport const baseMixin = () => new BaseMixin();\n","import {scaleLinear, scaleOrdinal, scaleQuantize} from 'd3-scale';\nimport {interpolateHcl} from 'd3-interpolate';\nimport {max, min} from 'd3-array';\n\nimport {config} from '../core/config';\nimport {utils} from '../core/utils';\n\n/**\n * The Color Mixin is an abstract chart functional class providing universal coloring support\n * as a mix-in for any concrete chart implementation.\n * @mixin ColorMixin\n * @param {Object} Base\n * @returns {ColorMixin}\n */\nexport const ColorMixin = Base => class extends Base {\n constructor () {\n super();\n\n this._colors = scaleOrdinal(config.defaultColors());\n\n this._colorAccessor = d => this.keyAccessor()(d);\n this._colorCalculator = undefined;\n\n {\n const chart = this;\n // ES6: this method is called very differently from stack-mixin and derived charts\n // Removing and placing it as a member method is tricky\n\n /**\n * Get the color for the datum d and counter i. This is used internally by charts to retrieve a color.\n * @method getColor\n * @memberof ColorMixin\n * @instance\n * @param {*} d\n * @param {Number} [i]\n * @returns {String}\n */\n chart.getColor = function (d, i) {\n return chart._colorCalculator ?\n chart._colorCalculator.call(this, d, i) :\n chart._colors(chart._colorAccessor.call(this, d, i));\n };\n }\n }\n\n /**\n * Set the domain by determining the min and max values as retrieved by\n * {@link ColorMixin#colorAccessor .colorAccessor} over the chart's dataset.\n * @memberof ColorMixin\n * @instance\n * @returns {ColorMixin}\n */\n calculateColorDomain () {\n const newDomain = [min(this.data(), this.colorAccessor()),\n max(this.data(), this.colorAccessor())];\n this._colors.domain(newDomain);\n return this;\n }\n\n /**\n * Retrieve current color scale or set a new color scale. This methods accepts any function that\n * operates like a d3 scale.\n * @memberof ColorMixin\n * @instance\n * @see {@link https://github.com/d3/d3-scale/blob/master/README.md d3.scale}\n * @example\n * // alternate categorical scale\n * chart.colors(d3.scale.category20b());\n * // ordinal scale\n * chart.colors(d3.scaleOrdinal().range(['red','green','blue']));\n * // convenience method, the same as above\n * chart.ordinalColors(['red','green','blue']);\n * // set a linear scale\n * chart.linearColors([\"#4575b4\", \"#ffffbf\", \"#a50026\"]);\n * @param {d3.scale} [colorScale=d3.scaleOrdinal(d3.schemeCategory20c)]\n * @returns {d3.scale|ColorMixin}\n */\n colors (colorScale) {\n if (!arguments.length) {\n return this._colors;\n }\n if (colorScale instanceof Array) {\n this._colors = scaleQuantize().range(colorScale); // deprecated legacy support, note: this fails for ordinal domains\n } else {\n this._colors = typeof colorScale === 'function' ? colorScale : utils.constant(colorScale);\n }\n return this;\n }\n\n /**\n * Convenience method to set the color scale to\n * {@link https://github.com/d3/d3-scale/blob/master/README.md#ordinal-scales d3.scaleOrdinal} with\n * range `r`.\n * @memberof ColorMixin\n * @instance\n * @param {Array} r\n * @returns {ColorMixin}\n */\n ordinalColors (r) {\n return this.colors(scaleOrdinal().range(r));\n }\n\n /**\n * Convenience method to set the color scale to an Hcl interpolated linear scale with range `r`.\n * @memberof ColorMixin\n * @instance\n * @param {Array} r\n * @returns {ColorMixin}\n */\n linearColors (r) {\n return this.colors(scaleLinear()\n .range(r)\n .interpolate(interpolateHcl));\n }\n\n /**\n * Set or the get color accessor function. This function will be used to map a data point in a\n * crossfilter group to a color value on the color scale. The default function uses the key\n * accessor.\n * @memberof ColorMixin\n * @instance\n * @example\n * // default index based color accessor\n * .colorAccessor(function (d, i){return i;})\n * // color accessor for a multi-value crossfilter reduction\n * .colorAccessor(function (d){return d.value.absGain;})\n * @param {Function} [colorAccessor]\n * @returns {Function|ColorMixin}\n */\n colorAccessor (colorAccessor) {\n if (!arguments.length) {\n return this._colorAccessor;\n }\n this._colorAccessor = colorAccessor;\n return this;\n }\n\n /**\n * Set or get the current domain for the color mapping function. The domain must be supplied as an\n * array.\n *\n * Note: previously this method accepted a callback function. Instead you may use a custom scale\n * set by {@link ColorMixin#colors .colors}.\n * @memberof ColorMixin\n * @instance\n * @param {Array} [domain]\n * @returns {Array|ColorMixin}\n */\n colorDomain (domain) {\n if (!arguments.length) {\n return this._colors.domain();\n }\n this._colors.domain(domain);\n return this;\n }\n\n /**\n * Overrides the color selection algorithm, replacing it with a simple function.\n *\n * Normally colors will be determined by calling the `colorAccessor` to get a value, and then passing that\n * value through the `colorScale`.\n *\n * But sometimes it is difficult to get a color scale to produce the desired effect. The `colorCalculator`\n * takes the datum and index and returns a color directly.\n * @memberof ColorMixin\n * @instance\n * @param {*} [colorCalculator]\n * @returns {Function|ColorMixin}\n */\n colorCalculator (colorCalculator) {\n if (!arguments.length) {\n return this._colorCalculator || this.getColor;\n }\n this._colorCalculator = colorCalculator;\n return this;\n }\n};\n","import { ascending, descending, min, max } from 'd3-array';\nimport { scaleLinear } from 'd3-scale';\n\nimport {ColorMixin} from './color-mixin';\nimport {transition} from '../core/core';\nimport {events} from '../core/events';\nimport {d3compat} from '../core/config';\n\n/**\n * This Mixin provides reusable functionalities for any chart that needs to visualize data using bubbles.\n * @mixin BubbleMixin\n * @mixes ColorMixin\n * @param {Object} Base\n * @returns {BubbleMixin}\n */\nexport const BubbleMixin = Base => class extends ColorMixin(Base) {\n constructor () {\n super();\n\n this._maxBubbleRelativeSize = 0.3;\n this._minRadiusWithLabel = 10;\n this._sortBubbleSize = false;\n this._elasticRadius = false;\n this._excludeElasticZero = true;\n\n // These cane be used by derived classes as well, so member status\n this.BUBBLE_NODE_CLASS = 'node';\n this.BUBBLE_CLASS = 'bubble';\n this.MIN_RADIUS = 10;\n\n this.renderLabel(true);\n\n this.data(group => {\n const data = group.all();\n\n if (this._keyboardAccessible) {\n // sort based on the x value (key)\n data.sort((a, b) => ascending(this.keyAccessor()(a), this.keyAccessor()(b)));\n }\n\n if (this._sortBubbleSize) {\n // sort descending so smaller bubbles are on top\n const radiusAccessor = this.radiusValueAccessor();\n data.sort((a, b) => descending(radiusAccessor(a), radiusAccessor(b)));\n }\n return data;\n });\n\n this._r = scaleLinear().domain([0, 100]);\n }\n\n _rValueAccessor (d) {\n return d.r;\n }\n\n /**\n * Get or set the bubble radius scale. By default the bubble chart uses\n * {@link https://github.com/d3/d3-scale/blob/master/README.md#scaleLinear d3.scaleLinear().domain([0, 100])}\n * as its radius scale.\n * @memberof BubbleMixin\n * @instance\n * @see {@link https://github.com/d3/d3-scale/blob/master/README.md d3.scale}\n * @param {d3.scale} [bubbleRadiusScale=d3.scaleLinear().domain([0, 100])]\n * @returns {d3.scale|BubbleMixin}\n */\n r (bubbleRadiusScale) {\n if (!arguments.length) {\n return this._r;\n }\n this._r = bubbleRadiusScale;\n return this;\n }\n\n /**\n * Turn on or off the elastic bubble radius feature, or return the value of the flag. If this\n * feature is turned on, then bubble radii will be automatically rescaled to fit the chart better.\n * @memberof BubbleMixin\n * @instance\n * @param {Boolean} [elasticRadius=false]\n * @returns {Boolean|BubbleChart}\n */\n elasticRadius (elasticRadius) {\n if (!arguments.length) {\n return this._elasticRadius;\n }\n this._elasticRadius = elasticRadius;\n return this;\n }\n\n calculateRadiusDomain () {\n if (this._elasticRadius) {\n this.r().domain([this.rMin(), this.rMax()]);\n }\n }\n\n /**\n * Get or set the radius value accessor function. If set, the radius value accessor function will\n * be used to retrieve a data value for each bubble. The data retrieved then will be mapped using\n * the r scale to the actual bubble radius. This allows you to encode a data dimension using bubble\n * size.\n * @memberof BubbleMixin\n * @instance\n * @param {Function} [radiusValueAccessor]\n * @returns {Function|BubbleMixin}\n */\n radiusValueAccessor (radiusValueAccessor) {\n if (!arguments.length) {\n return this._rValueAccessor;\n }\n this._rValueAccessor = radiusValueAccessor;\n return this;\n }\n\n rMin () {\n let values = this.data().map(this.radiusValueAccessor());\n if(this._excludeElasticZero) {\n values = values.filter(value => value > 0);\n }\n return min(values);\n }\n\n rMax () {\n return max(this.data(), e => this.radiusValueAccessor()(e));\n }\n\n bubbleR (d) {\n const value = this.radiusValueAccessor()(d);\n let r = this.r()(value);\n if (isNaN(r) || value <= 0) {\n r = 0;\n }\n return r;\n }\n\n _labelFunction (d) {\n return this.label()(d);\n }\n\n _shouldLabel (d) {\n return (this.bubbleR(d) > this._minRadiusWithLabel);\n }\n\n _labelOpacity (d) {\n return this._shouldLabel(d) ? 1 : 0;\n }\n\n _labelPointerEvent (d) {\n return this._shouldLabel(d) ? 'all' : 'none';\n }\n\n _doRenderLabel (bubbleGEnter) {\n if (this.renderLabel()) {\n let label = bubbleGEnter.select('text');\n\n if (label.empty()) {\n label = bubbleGEnter.append('text')\n .attr('text-anchor', 'middle')\n .attr('dy', '.3em')\n .on('click', d3compat.eventHandler(d => this.onClick(d)));\n }\n\n label\n .attr('opacity', 0)\n .attr('pointer-events', d => this._labelPointerEvent(d))\n .text(d => this._labelFunction(d));\n transition(label, this.transitionDuration(), this.transitionDelay())\n .attr('opacity', d => this._labelOpacity(d));\n }\n }\n\n doUpdateLabels (bubbleGEnter) {\n if (this.renderLabel()) {\n const labels = bubbleGEnter.select('text')\n .attr('pointer-events', d => this._labelPointerEvent(d))\n .text(d => this._labelFunction(d));\n transition(labels, this.transitionDuration(), this.transitionDelay())\n .attr('opacity', d => this._labelOpacity(d));\n }\n }\n\n _titleFunction (d) {\n return this.title()(d);\n }\n\n _doRenderTitles (g) {\n if (this.renderTitle()) {\n const title = g.select('title');\n\n if (title.empty()) {\n g.append('title').text(d => this._titleFunction(d));\n }\n }\n }\n\n doUpdateTitles (g) {\n if (this.renderTitle()) {\n g.select('title').text(d => this._titleFunction(d));\n }\n }\n\n /**\n * Turn on or off the bubble sorting feature, or return the value of the flag. If enabled,\n * bubbles will be sorted by their radius, with smaller bubbles in front.\n * @memberof BubbleChart\n * @instance\n * @param {Boolean} [sortBubbleSize=false]\n * @returns {Boolean|BubbleChart}\n */\n sortBubbleSize (sortBubbleSize) {\n if (!arguments.length) {\n return this._sortBubbleSize;\n }\n this._sortBubbleSize = sortBubbleSize;\n return this;\n }\n\n /**\n * Get or set the minimum radius. This will be used to initialize the radius scale's range.\n * @memberof BubbleMixin\n * @instance\n * @param {Number} [radius=10]\n * @returns {Number|BubbleMixin}\n */\n minRadius (radius) {\n if (!arguments.length) {\n return this.MIN_RADIUS;\n }\n this.MIN_RADIUS = radius;\n return this;\n }\n\n /**\n * Get or set the minimum radius for label rendering. If a bubble's radius is less than this value\n * then no label will be rendered.\n * @memberof BubbleMixin\n * @instance\n * @param {Number} [radius=10]\n * @returns {Number|BubbleMixin}\n */\n\n minRadiusWithLabel (radius) {\n if (!arguments.length) {\n return this._minRadiusWithLabel;\n }\n this._minRadiusWithLabel = radius;\n return this;\n }\n\n /**\n * Get or set the maximum relative size of a bubble to the length of x axis. This value is useful\n * when the difference in radius between bubbles is too great.\n * @memberof BubbleMixin\n * @instance\n * @param {Number} [relativeSize=0.3]\n * @returns {Number|BubbleMixin}\n */\n maxBubbleRelativeSize (relativeSize) {\n if (!arguments.length) {\n return this._maxBubbleRelativeSize;\n }\n this._maxBubbleRelativeSize = relativeSize;\n return this;\n }\n\n /**\n * Should the chart exclude zero when calculating elastic bubble radius?\n * @memberof BubbleMixin\n * @instance\n * @param {Boolean} [excludeZero=true]\n * @returns {Boolean|BubbleMixin}\n */\n excludeElasticZero (excludeZero) {\n if (!arguments.length) {\n return this._excludeElasticZero;\n }\n this._excludeElasticZero = excludeZero;\n return this;\n }\n\n fadeDeselectedArea (selection) {\n if (this.hasFilter()) {\n const chart = this;\n this.selectAll(`g.${chart.BUBBLE_NODE_CLASS}`).each(function (d) {\n if (chart.isSelectedNode(d)) {\n chart.highlightSelected(this);\n } else {\n chart.fadeDeselected(this);\n }\n });\n } else {\n const chart = this;\n this.selectAll(`g.${chart.BUBBLE_NODE_CLASS}`).each(function () {\n chart.resetHighlight(this);\n });\n }\n }\n\n isSelectedNode (d) {\n return this.hasFilter(d.key);\n }\n\n onClick (d) {\n const filter = d.key;\n events.trigger(() => {\n this.filter(filter);\n this.redrawGroup();\n });\n }\n};\n","import {sum} from 'd3-array';\n\n/**\n * Cap is a mixin that groups small data elements below a _cap_ into an *others* grouping for both the\n * Row and Pie Charts.\n *\n * The top ordered elements in the group up to the cap amount will be kept in the chart, and the rest\n * will be replaced with an *others* element, with value equal to the sum of the replaced values. The\n * keys of the elements below the cap limit are recorded in order to filter by those keys when the\n * others* element is clicked.\n * @mixin CapMixin\n * @param {Object} Base\n * @returns {CapMixin}\n */\nexport const CapMixin = Base => class extends Base {\n constructor () {\n super();\n\n this._cap = Infinity;\n this._takeFront = true;\n this._othersLabel = 'Others';\n\n this._othersGrouper = (topItems, restItems) => {\n const restItemsSum = sum(restItems, this.valueAccessor()),\n restKeys = restItems.map(this.keyAccessor());\n if (restItemsSum > 0) {\n return topItems.concat([{\n others: restKeys,\n key: this.othersLabel(),\n value: restItemsSum\n }]);\n }\n return topItems;\n };\n\n // emulate old group.top(N) ordering\n this.ordering(kv => -kv.value);\n\n // return N \"top\" groups, where N is the cap, sorted by baseMixin.ordering\n // whether top means front or back depends on takeFront\n this.data(group => {\n if (this._cap === Infinity) {\n return this._computeOrderedGroups(group.all());\n } else {\n let items = group.all(), rest;\n items = this._computeOrderedGroups(items); // sort by baseMixin.ordering\n\n if (this._cap) {\n if (this._takeFront) {\n rest = items.slice(this._cap);\n items = items.slice(0, this._cap);\n } else {\n const start = Math.max(0, items.length - this._cap);\n rest = items.slice(0, start);\n items = items.slice(start);\n }\n }\n\n if (this._othersGrouper) {\n return this._othersGrouper(items, rest);\n }\n return items;\n }\n });\n }\n\n cappedKeyAccessor (d, i) {\n if (d.others) {\n return d.key;\n }\n return this.keyAccessor()(d, i);\n }\n\n cappedValueAccessor (d, i) {\n if (d.others) {\n return d.value;\n }\n return this.valueAccessor()(d, i);\n }\n\n /**\n * Get or set the count of elements to that will be included in the cap. If there is an\n * {@link CapMixin#othersGrouper othersGrouper}, any further elements will be combined in an\n * extra element with its name determined by {@link CapMixin#othersLabel othersLabel}.\n *\n * As of dc.js 2.1 and onward, the capped charts use\n * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group_all group.all()}\n * and {@link BaseMixin#ordering BaseMixin.ordering()} to determine the order of\n * elements. Then `cap` and {@link CapMixin#takeFront takeFront} determine how many elements\n * to keep, from which end of the resulting array.\n *\n * **Migration note:** Up through dc.js 2.0.*, capping used\n * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group_top group.top(N)},\n * which selects the largest items according to\n * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group_order group.order()}.\n * The chart then sorted the items according to {@link BaseMixin#ordering baseMixin.ordering()}.\n * So the two values essentially had to agree, but if the `group.order()` was incorrect (it's\n * easy to forget about), the wrong rows or slices would be displayed, in the correct order.\n *\n * If your chart previously relied on `group.order()`, use `chart.ordering()` instead. As of\n * 2.1.5, the ordering defaults to sorting from greatest to least like `group.top(N)` did.\n *\n * If you want to cap by one ordering but sort by another, you can still do this by\n * specifying your own {@link BaseMixin#data `.data()`} callback. For details, see the example\n * {@link https://dc-js.github.io/dc.js/examples/cap-and-sort-differently.html Cap and Sort Differently}.\n * @memberof CapMixin\n * @instance\n * @param {Number} [count=Infinity]\n * @returns {Number|CapMixin}\n */\n cap (count) {\n if (!arguments.length) {\n return this._cap;\n }\n this._cap = count;\n return this;\n }\n\n /**\n * Get or set the direction of capping. If set, the chart takes the first\n * {@link CapMixin#cap cap} elements from the sorted array of elements; otherwise\n * it takes the last `cap` elements.\n * @memberof CapMixin\n * @instance\n * @param {Boolean} [takeFront=true]\n * @returns {Boolean|CapMixin}\n */\n takeFront (takeFront) {\n if (!arguments.length) {\n return this._takeFront;\n }\n this._takeFront = takeFront;\n return this;\n }\n\n /**\n * Get or set the label for *Others* slice when slices cap is specified.\n * @memberof CapMixin\n * @instance\n * @param {String} [label=\"Others\"]\n * @returns {String|CapMixin}\n */\n othersLabel (label) {\n if (!arguments.length) {\n return this._othersLabel;\n }\n this._othersLabel = label;\n return this;\n }\n\n /**\n * Get or set the grouper function that will perform the insertion of data for the *Others* slice\n * if the slices cap is specified. If set to a falsy value, no others will be added.\n *\n * The grouper function takes an array of included (\"top\") items, and an array of the rest of\n * the items. By default the grouper function computes the sum of the rest.\n * @memberof CapMixin\n * @instance\n * @example\n * // Do not show others\n * chart.othersGrouper(null);\n * // Default others grouper\n * chart.othersGrouper(function (topItems, restItems) {\n * var restItemsSum = d3.sum(restItems, _chart.valueAccessor()),\n * restKeys = restItems.map(_chart.keyAccessor());\n * if (restItemsSum > 0) {\n * return topItems.concat([{\n * others: restKeys,\n * key: _chart.othersLabel(),\n * value: restItemsSum\n * }]);\n * }\n * return topItems;\n * });\n * @param {Function} [grouperFunction]\n * @returns {Function|CapMixin}\n */\n othersGrouper (grouperFunction) {\n if (!arguments.length) {\n return this._othersGrouper;\n }\n this._othersGrouper = grouperFunction;\n return this;\n }\n\n onClick (d) {\n if (d.others) {\n this.filter([d.others]);\n }\n super.onClick(d);\n }\n};\n","import {BaseMixin} from './base-mixin';\n\n/**\n * Margin is a mixin that provides margin utility functions for both the Row Chart and Coordinate Grid\n * Charts.\n * @mixin MarginMixin\n * @param {Object} Base\n * @returns {MarginMixin}\n */\nexport class MarginMixin extends BaseMixin {\n constructor () {\n super();\n\n this._margin = {top: 10, right: 50, bottom: 30, left: 30};\n }\n\n /**\n * Get or set the margins for a particular coordinate grid chart instance. The margins is stored as\n * an associative Javascript array.\n * @memberof MarginMixin\n * @instance\n * @example\n * var leftMargin = chart.margins().left; // 30 by default\n * chart.margins().left = 50;\n * leftMargin = chart.margins().left; // now 50\n * @param {{top: Number, right: Number, left: Number, bottom: Number}} [margins={top: 10, right: 50, bottom: 30, left: 30}]\n * @returns {{top: Number, right: Number, left: Number, bottom: Number}|MarginMixin}\n */\n margins (margins) {\n if (!arguments.length) {\n return this._margin;\n }\n this._margin = margins;\n return this;\n }\n\n /**\n * Effective width of the chart excluding margins (in pixels).\n *\n * @returns {number}\n */\n effectiveWidth () {\n return this.width() - this.margins().left - this.margins().right;\n }\n\n /**\n * Effective height of the chart excluding margins (in pixels).\n *\n * @returns {number}\n */\n effectiveHeight () {\n return this.height() - this.margins().top - this.margins().bottom;\n }\n}\n","import {schemeCategory10} from 'd3-scale-chromatic';\nimport {timeDay} from 'd3-time';\nimport {max, min} from 'd3-array';\nimport {scaleBand, scaleLinear, scaleOrdinal} from 'd3-scale';\nimport {axisTop, axisBottom, axisLeft, axisRight} from 'd3-axis';\nimport {zoom, zoomIdentity} from 'd3-zoom';\nimport {brushX} from 'd3-brush';\n\nimport {ColorMixin} from './color-mixin';\nimport {MarginMixin} from './margin-mixin';\nimport {optionalTransition, transition} from '../core/core';\nimport {units} from '../core/units';\nimport {constants} from '../core/constants';\nimport {utils} from '../core/utils';\nimport {d3compat} from '../core/config';\nimport {logger} from '../core/logger';\nimport {filters} from '../core/filters';\nimport {events} from '../core/events';\n\nconst GRID_LINE_CLASS = 'grid-line';\nconst HORIZONTAL_CLASS = 'horizontal';\nconst VERTICAL_CLASS = 'vertical';\nconst Y_AXIS_LABEL_CLASS = 'y-axis-label';\nconst X_AXIS_LABEL_CLASS = 'x-axis-label';\nconst CUSTOM_BRUSH_HANDLE_CLASS = 'custom-brush-handle';\nconst DEFAULT_AXIS_LABEL_PADDING = 12;\n\n/**\n * Coordinate Grid is an abstract base chart designed to support a number of coordinate grid based\n * concrete chart types, e.g. bar chart, line chart, and bubble chart.\n * @mixin CoordinateGridMixin\n * @mixes ColorMixin\n * @mixes MarginMixin\n */\nexport class CoordinateGridMixin extends ColorMixin(MarginMixin) {\n constructor () {\n super();\n\n this.colors(scaleOrdinal(schemeCategory10));\n this._mandatoryAttributes().push('x');\n this._parent = undefined;\n this._g = undefined;\n this._chartBodyG = undefined;\n\n this._x = undefined;\n this._origX = undefined; // Will hold original scale in case of zoom\n this._xOriginalDomain = undefined;\n this._xAxis = null;\n this._xUnits = units.integers;\n this._xAxisPadding = 0;\n this._xAxisPaddingUnit = timeDay;\n this._xElasticity = false;\n this._xAxisLabel = undefined;\n this._xAxisLabelPadding = 0;\n this._lastXDomain = undefined;\n\n this._y = undefined;\n this._yAxis = null;\n this._yAxisPadding = 0;\n this._yElasticity = false;\n this._yAxisLabel = undefined;\n this._yAxisLabelPadding = 0;\n\n this._brush = brushX();\n\n this._gBrush = undefined;\n this._brushOn = true;\n this._parentBrushOn = false;\n this._round = undefined;\n this._ignoreBrushEvents = false; // ignore when carrying out programmatic brush operations\n\n this._renderHorizontalGridLine = false;\n this._renderVerticalGridLine = false;\n\n this._resizing = false;\n this._unitCount = undefined;\n\n this._zoomScale = [1, Infinity];\n this._zoomOutRestrict = true;\n\n this._zoom = zoom().on('zoom', d3compat.eventHandler((d, evt) => this._onZoom(evt)));\n this._nullZoom = zoom().on('zoom', null);\n this._hasBeenMouseZoomable = false;\n this._ignoreZoomEvents = false; // ignore when carrying out programmatic zoom operations\n\n this._rangeChart = undefined;\n this._focusChart = undefined;\n\n this._mouseZoomable = false;\n this._clipPadding = 0;\n\n this._fOuterRangeBandPadding = 0.5;\n this._fRangeBandPadding = 0;\n\n this._useRightYAxis = false;\n this._useTopXAxis = false;\n }\n\n /**\n * When changing the domain of the x or y scale, it is necessary to tell the chart to recalculate\n * and redraw the axes. (`.rescale()` is called automatically when the x or y scale is replaced\n * with {@link CoordinateGridMixin+x .x()} or {@link CoordinateGridMixin#y .y()}, and has\n * no effect on elastic scales.)\n * @returns {CoordinateGridMixin}\n */\n rescale () {\n this._unitCount = undefined;\n this._resizing = true;\n return this;\n }\n\n resizing (resizing) {\n if (!arguments.length) {\n return this._resizing;\n }\n this._resizing = resizing;\n return this;\n }\n\n /**\n * Get or set the range selection chart associated with this instance. Setting the range selection\n * chart using this function will automatically update its selection brush when the current chart\n * zooms in. In return the given range chart will also automatically attach this chart as its focus\n * chart hence zoom in when range brush updates.\n *\n * Usually the range and focus charts will share a dimension. The range chart will set the zoom\n * boundaries for the focus chart, so its dimension values must be compatible with the domain of\n * the focus chart.\n *\n * See the [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) example for this effect in action.\n * @param {CoordinateGridMixin} [rangeChart]\n * @returns {CoordinateGridMixin}\n */\n rangeChart (rangeChart) {\n if (!arguments.length) {\n return this._rangeChart;\n }\n this._rangeChart = rangeChart;\n this._rangeChart.focusChart(this);\n return this;\n }\n\n /**\n * Get or set the scale extent for mouse zooms.\n * @param {Array} [extent=[1, Infinity]]\n * @returns {Array|CoordinateGridMixin}\n */\n zoomScale (extent) {\n if (!arguments.length) {\n return this._zoomScale;\n }\n this._zoomScale = extent;\n return this;\n }\n\n /**\n * Get or set the zoom restriction for the chart. If true limits the zoom to origional domain of the chart.\n * @param {Boolean} [zoomOutRestrict=true]\n * @returns {Boolean|CoordinateGridMixin}\n */\n zoomOutRestrict (zoomOutRestrict) {\n if (!arguments.length) {\n return this._zoomOutRestrict;\n }\n this._zoomOutRestrict = zoomOutRestrict;\n return this;\n }\n\n _generateG (parent) {\n if (parent === undefined) {\n this._parent = this.svg();\n } else {\n this._parent = parent;\n }\n\n const href = window.location.href.split('#')[0];\n\n this._g = this._parent.append('g');\n\n this._chartBodyG = this._g.append('g').attr('class', 'chart-body')\n .attr('transform', `translate(${this.margins().left}, ${this.margins().top})`)\n .attr('clip-path', `url(${href}#${this._getClipPathId()})`);\n\n return this._g;\n }\n\n /**\n * Get or set the root g element. This method is usually used to retrieve the g element in order to\n * overlay custom svg drawing programatically. **Caution**: The root g element is usually generated\n * by dc.js internals, and resetting it might produce unpredictable result.\n * @param {SVGElement} [gElement]\n * @returns {SVGElement|CoordinateGridMixin}\n */\n g (gElement) {\n if (!arguments.length) {\n return this._g;\n }\n this._g = gElement;\n return this;\n }\n\n /**\n * Set or get mouse zoom capability flag (default: false). When turned on the chart will be\n * zoomable using the mouse wheel. If the range selector chart is attached zooming will also update\n * the range selection brush on the associated range selector chart.\n * @param {Boolean} [mouseZoomable=false]\n * @returns {Boolean|CoordinateGridMixin}\n */\n mouseZoomable (mouseZoomable) {\n if (!arguments.length) {\n return this._mouseZoomable;\n }\n this._mouseZoomable = mouseZoomable;\n return this;\n }\n\n /**\n * Retrieve the svg group for the chart body.\n * @param {SVGElement} [chartBodyG]\n * @returns {SVGElement}\n */\n chartBodyG (chartBodyG) {\n if (!arguments.length) {\n return this._chartBodyG;\n }\n this._chartBodyG = chartBodyG;\n return this;\n }\n\n /**\n * **mandatory**\n *\n * Get or set the x scale. The x scale can be any d3\n * {@link https://github.com/d3/d3-scale/blob/master/README.md d3.scale} or\n * {@link https://github.com/d3/d3-scale/blob/master/README.md#ordinal-scales ordinal scale}\n * @see {@link https://github.com/d3/d3-scale/blob/master/README.md d3.scale}\n * @example\n * // set x to a linear scale\n * chart.x(d3.scaleLinear().domain([-2500, 2500]))\n * // set x to a time scale to generate histogram\n * chart.x(d3.scaleTime().domain([new Date(1985, 0, 1), new Date(2012, 11, 31)]))\n * @param {d3.scale} [xScale]\n * @returns {d3.scale|CoordinateGridMixin}\n */\n x (xScale) {\n if (!arguments.length) {\n return this._x;\n }\n this._x = xScale;\n this._xOriginalDomain = this._x.domain();\n this.rescale();\n return this;\n }\n\n xOriginalDomain () {\n return this._xOriginalDomain;\n }\n\n /**\n * Set or get the xUnits function. The coordinate grid chart uses the xUnits function to calculate\n * the number of data projections on the x axis such as the number of bars for a bar chart or the\n * number of dots for a line chart.\n *\n * This function is expected to return a Javascript array of all data points on the x axis, or\n * the number of points on the axis. d3 time range functions [d3.timeDays, d3.timeMonths, and\n * d3.timeYears](https://github.com/d3/d3-time/blob/master/README.md#intervals) are all valid\n * xUnits functions.\n *\n * dc.js also provides a few units function, see the {@link units Units Namespace} for\n * a list of built-in units functions.\n *\n * Note that as of dc.js 3.0, `units.ordinal` is not a real function, because it is not\n * possible to define this function compliant with the d3 range functions. It was already a\n * magic value which caused charts to behave differently, and now it is completely so.\n * @example\n * // set x units to count days\n * chart.xUnits(d3.timeDays);\n * // set x units to count months\n * chart.xUnits(d3.timeMonths);\n *\n * // A custom xUnits function can be used as long as it follows the following interface:\n * // units in integer\n * function(start, end) {\n * // simply calculates how many integers in the domain\n * return Math.abs(end - start);\n * }\n *\n * // fixed units\n * function(start, end) {\n * // be aware using fixed units will disable the focus/zoom ability on the chart\n * return 1000;\n * }\n * @param {Function} [xUnits=units.integers]\n * @returns {Function|CoordinateGridMixin}\n */\n xUnits (xUnits) {\n if (!arguments.length) {\n return this._xUnits;\n }\n this._xUnits = xUnits;\n return this;\n }\n\n /**\n * Set or get the x axis used by a particular coordinate grid chart instance. This function is most\n * useful when x axis customization is required. The x axis in dc.js is an instance of a\n * {@link https://github.com/d3/d3-axis/blob/master/README.md#axisBottom d3 bottom axis object};\n * therefore it supports any valid d3 axisBottom manipulation.\n *\n * **Caution**: The x axis is usually generated internally by dc; resetting it may cause\n * unexpected results. Note also that when used as a getter, this function is not chainable:\n * it returns the axis, not the chart,\n * {@link https://github.com/dc-js/dc.js/wiki/FAQ#why-does-everything-break-after-a-call-to-xaxis-or-yaxis\n * so attempting to call chart functions after calling `.xAxis()` will fail}.\n * @see {@link https://github.com/d3/d3-axis/blob/master/README.md#axisBottom d3.axisBottom}\n * @example\n * // customize x axis tick format\n * chart.xAxis().tickFormat(function(v) {return v + '%';});\n * // customize x axis tick values\n * chart.xAxis().tickValues([0, 100, 200, 300]);\n * @param {d3.axis} [xAxis=d3.axisBottom()]\n * @returns {d3.axis|CoordinateGridMixin}\n */\n xAxis (xAxis) {\n if (!arguments.length) {\n if (!this._xAxis) {\n this._xAxis = this._createXAxis();\n }\n return this._xAxis;\n }\n this._xAxis = xAxis;\n return this;\n }\n\n /**\n * Turn on/off elastic x axis behavior. If x axis elasticity is turned on, then the grid chart will\n * attempt to recalculate the x axis range whenever a redraw event is triggered.\n * @param {Boolean} [elasticX=false]\n * @returns {Boolean|CoordinateGridMixin}\n */\n elasticX (elasticX) {\n if (!arguments.length) {\n return this._xElasticity;\n }\n this._xElasticity = elasticX;\n return this;\n }\n\n /**\n * Set or get x axis padding for the elastic x axis. The padding will be added to both end of the x\n * axis if elasticX is turned on; otherwise it is ignored.\n *\n * Padding can be an integer or percentage in string (e.g. '10%'). Padding can be applied to\n * number or date x axes. When padding a date axis, an integer represents number of units being padded\n * and a percentage string will be treated the same as an integer. The unit will be determined by the\n * xAxisPaddingUnit variable.\n * @param {Number|String} [padding=0]\n * @returns {Number|String|CoordinateGridMixin}\n */\n xAxisPadding (padding) {\n if (!arguments.length) {\n return this._xAxisPadding;\n }\n this._xAxisPadding = padding;\n return this;\n }\n\n /**\n * Set or get x axis padding unit for the elastic x axis. The padding unit will determine which unit to\n * use when applying xAxis padding if elasticX is turned on and if x-axis uses a time dimension;\n * otherwise it is ignored.\n *\n * The padding unit should be a\n * [d3 time interval](https://github.com/d3/d3-time/blob/master/README.md#self._interval).\n * For backward compatibility with dc.js 2.0, it can also be the name of a d3 time interval\n * ('day', 'hour', etc). Available arguments are the\n * [d3 time intervals](https://github.com/d3/d3-time/blob/master/README.md#intervals d3.timeInterval).\n * @param {String} [unit=d3.timeDay]\n * @returns {String|CoordinateGridMixin}\n */\n xAxisPaddingUnit (unit) {\n if (!arguments.length) {\n return this._xAxisPaddingUnit;\n }\n this._xAxisPaddingUnit = unit;\n return this;\n }\n\n /**\n * Returns the number of units displayed on the x axis. If the x axis is ordinal (`xUnits` is\n * `units.ordinal`), this is the number of items in the domain of the x scale. Otherwise, the\n * x unit count is calculated using the {@link CoordinateGridMixin#xUnits xUnits} function.\n * @returns {Number}\n */\n xUnitCount () {\n if (this._unitCount === undefined) {\n if (this.isOrdinal()) {\n // In this case it number of items in domain\n this._unitCount = this.x().domain().length;\n } else {\n this._unitCount = this.xUnits()(this.x().domain()[0], this.x().domain()[1]);\n\n // Sometimes xUnits() may return an array while sometimes directly the count\n if (this._unitCount instanceof Array) {\n this._unitCount = this._unitCount.length;\n }\n }\n }\n\n return this._unitCount;\n }\n\n /**\n * Gets or sets whether the chart should be drawn with a right axis instead of a left axis. When\n * used with a chart in a composite chart, allows both left and right Y axes to be shown on a\n * chart.\n * @param {Boolean} [useRightYAxis=false]\n * @returns {Boolean|CoordinateGridMixin}\n */\n useRightYAxis (useRightYAxis) {\n if (!arguments.length) {\n return this._useRightYAxis;\n }\n\n // We need to warn if value is changing after self._yAxis was created\n if (this._useRightYAxis !== useRightYAxis && this._yAxis) {\n logger.warn('Value of useRightYAxis has been altered, after yAxis was created. ' +\n 'You might get unexpected yAxis behavior. ' +\n 'Make calls to useRightYAxis sooner in your chart creation process.');\n }\n\n this._useRightYAxis = useRightYAxis;\n return this;\n }\n\n /**\n * Gets or sets whether the chart should be drawn with a top axis instead of a bottom axis. When\n * used with a chart in a composite chart, allows both top and bottom X axes to be shown on a\n * chart.\n * @param {Boolean} [useTopXAxis=false]\n * @returns {Boolean|CoordinateGridMixin}\n */\n useTopXAxis (useTopXAxis) {\n if (!arguments.length) {\n return this._useTopXAxis;\n }\n\n // We need to warn if value is changing after self._yAxis was created\n if (this._useTopXAxis !== useTopXAxis && this._xAxis) {\n logger.warn('Value of useTopXAxis has been altered, after xAxis was created. ' +\n 'You might get unexpected yAxis behavior. ' +\n 'Make calls to useTopXAxis sooner in your chart creation process.');\n }\n\n this._useTopXAxis = useTopXAxis;\n return this;\n }\n\n /**\n * Returns true if the chart is using ordinal xUnits ({@link units.ordinal units.ordinal}, or false\n * otherwise. Most charts behave differently with ordinal data and use the result of this method to\n * trigger the appropriate logic.\n * @returns {Boolean}\n */\n isOrdinal () {\n return this.xUnits() === units.ordinal;\n }\n\n _useOuterPadding () {\n return true;\n }\n\n _ordinalXDomain () {\n const groups = this._computeOrderedGroups(this.data());\n return groups.map(this.keyAccessor());\n }\n\n _createXAxis () {\n return this._useTopXAxis ? axisTop() : axisBottom();\n }\n\n // eslint-disable-next-line complexity\n _prepareXAxis (g, render) {\n if (!this.isOrdinal()) {\n if (this.elasticX()) {\n this._x.domain([this.xAxisMin(), this.xAxisMax()]);\n }\n } else { // self._chart.isOrdinal()\n // D3v4 - Ordinal charts would need scaleBand\n // bandwidth is a method in scaleBand\n // (https://github.com/d3/d3-scale/blob/master/README.md#scaleBand)\n if (!this._x.bandwidth) {\n // If self._x is not a scaleBand create a new scale and\n // copy the original domain to the new scale\n logger.warn('For compatibility with d3v4+, dc.js d3.0 ordinal bar/line/bubble charts need ' +\n 'd3.scaleBand() for the x scale, instead of d3.scaleOrdinal(). ' +\n 'Replacing .x() with a d3.scaleBand with the same domain - ' +\n 'make the same change in your code to avoid this warning!');\n this._x = scaleBand().domain(this._x.domain());\n }\n\n if (this.elasticX() || this._x.domain().length === 0) {\n this._x.domain(this._ordinalXDomain());\n }\n }\n\n // has the domain changed?\n const xdom = this._x.domain();\n if (render || !utils.arraysEqual(this._lastXDomain, xdom)) {\n this.rescale();\n }\n this._lastXDomain = xdom;\n\n // please can't we always use rangeBands for bar charts?\n if (this.isOrdinal()) {\n this._x.range([0, this.xAxisLength()])\n .paddingInner(this._fRangeBandPadding)\n .paddingOuter(this._useOuterPadding() ? this._fOuterRangeBandPadding : 0);\n } else {\n this._x.range([0, this.xAxisLength()]);\n }\n\n if (!this._xAxis) {\n this._xAxis = this._createXAxis()\n }\n\n this._xAxis = this._xAxis.scale(this.x());\n\n this._renderVerticalGridLines(g);\n }\n\n renderXAxis (g) {\n let axisXG = g.select('g.x');\n\n if (axisXG.empty()) {\n axisXG = g.append('g')\n .attr('class', 'axis x')\n .attr('transform', `translate(${this.margins().left},${this._xAxisY()})`);\n }\n\n let axisXLab = g.select(`text.${X_AXIS_LABEL_CLASS}`);\n const axisXLabY = this._useTopXAxis ? this._xAxisLabelPadding : (this.height() - this._xAxisLabelPadding);\n if (axisXLab.empty() && this.xAxisLabel()) {\n axisXLab = g.append('text')\n .attr('class', X_AXIS_LABEL_CLASS)\n .attr('transform', `translate(${this.margins().left + this.xAxisLength() / 2},${axisXLabY})`)\n .attr('text-anchor', 'middle');\n }\n if (this.xAxisLabel() && axisXLab.text() !== this.xAxisLabel()) {\n axisXLab.text(this.xAxisLabel());\n }\n\n transition(axisXG, this.transitionDuration(), this.transitionDelay())\n .attr('transform', `translate(${this.margins().left},${this._xAxisY()})`)\n .call(this._xAxis);\n transition(axisXLab, this.transitionDuration(), this.transitionDelay())\n .attr('transform', `translate(${this.margins().left + this.xAxisLength() / 2},${axisXLabY})`);\n }\n\n _renderVerticalGridLines (g) {\n let gridLineG = g.select(`g.${VERTICAL_CLASS}`);\n\n if (this._renderVerticalGridLine) {\n if (gridLineG.empty()) {\n gridLineG = g.insert('g', ':first-child')\n .attr('class', `${GRID_LINE_CLASS} ${VERTICAL_CLASS}`)\n .attr('transform', `translate(${this.margins().left},${this.margins().top})`);\n }\n\n const ticks = this._xAxis.tickValues() ? this._xAxis.tickValues() :\n (typeof this._x.ticks === 'function' ? this._x.ticks.apply(this._x, this._xAxis.tickArguments()) : this._x.domain());\n\n const lines = gridLineG.selectAll('line')\n .data(ticks);\n\n // enter\n const linesGEnter = lines.enter()\n .append('line')\n .attr('x1', d => this._x(d))\n .attr('y1', this._xAxisY() - this.margins().top)\n .attr('x2', d => this._x(d))\n .attr('y2', 0)\n .attr('opacity', 0);\n transition(linesGEnter, this.transitionDuration(), this.transitionDelay())\n .attr('opacity', 0.5);\n\n // update\n transition(lines, this.transitionDuration(), this.transitionDelay())\n .attr('x1', d => this._x(d))\n .attr('y1', this._xAxisY() - this.margins().top)\n .attr('x2', d => this._x(d))\n .attr('y2', 0);\n\n // exit\n lines.exit().remove();\n } else {\n gridLineG.selectAll('line').remove();\n }\n }\n\n _xAxisY () {\n return this._useTopXAxis ? this.margins().top : this.height() - this.margins().bottom;\n }\n\n xAxisLength () {\n return this.effectiveWidth();\n }\n\n /**\n * Set or get the x axis label. If setting the label, you may optionally include additional padding to\n * the margin to make room for the label. By default the padded is set to 12 to accomodate the text height.\n * @param {String} [labelText]\n * @param {Number} [padding=12]\n * @returns {String}\n */\n xAxisLabel (labelText, padding) {\n if (!arguments.length) {\n return this._xAxisLabel;\n }\n this._xAxisLabel = labelText;\n this.margins().bottom -= this._xAxisLabelPadding;\n this._xAxisLabelPadding = (padding === undefined) ? DEFAULT_AXIS_LABEL_PADDING : padding;\n this.margins().bottom += this._xAxisLabelPadding;\n return this;\n }\n\n _createYAxis () {\n return this._useRightYAxis ? axisRight() : axisLeft();\n }\n\n _prepareYAxis (g) {\n if (this._y === undefined || this.elasticY()) {\n if (this._y === undefined) {\n this._y = scaleLinear();\n }\n const _min = this.yAxisMin() || 0;\n const _max = this.yAxisMax() || 0;\n this._y.domain([_min, _max]).rangeRound([this.yAxisHeight(), 0]);\n }\n\n this._y.range([this.yAxisHeight(), 0]);\n\n if (!this._yAxis) {\n this._yAxis = this._createYAxis();\n }\n\n this._yAxis.scale(this._y);\n\n this._renderHorizontalGridLinesForAxis(g, this._y, this._yAxis);\n }\n\n renderYAxisLabel (axisClass, text, rotation, labelXPosition) {\n labelXPosition = labelXPosition || this._yAxisLabelPadding;\n\n let axisYLab = this.g().select(`text.${Y_AXIS_LABEL_CLASS}.${axisClass}-label`);\n const labelYPosition = (this.margins().top + this.yAxisHeight() / 2);\n if (axisYLab.empty() && text) {\n axisYLab = this.g().append('text')\n .attr('transform', `translate(${labelXPosition},${labelYPosition}),rotate(${rotation})`)\n .attr('class', `${Y_AXIS_LABEL_CLASS} ${axisClass}-label`)\n .attr('text-anchor', 'middle')\n .text(text);\n }\n if (text && axisYLab.text() !== text) {\n axisYLab.text(text);\n }\n transition(axisYLab, this.transitionDuration(), this.transitionDelay())\n .attr('transform', `translate(${labelXPosition},${labelYPosition}),rotate(${rotation})`);\n }\n\n renderYAxisAt (axisClass, axis, position) {\n let axisYG = this.g().select(`g.${axisClass}`);\n if (axisYG.empty()) {\n axisYG = this.g().append('g')\n .attr('class', `axis ${axisClass}`)\n .attr('transform', `translate(${position},${this.margins().top})`);\n }\n\n transition(axisYG, this.transitionDuration(), this.transitionDelay())\n .attr('transform', `translate(${position},${this.margins().top})`)\n .call(axis);\n }\n\n renderYAxis () {\n const axisPosition = this._useRightYAxis ? (this.width() - this.margins().right) : this._yAxisX();\n this.renderYAxisAt('y', this._yAxis, axisPosition);\n const labelPosition = this._useRightYAxis ? (this.width() - this._yAxisLabelPadding) : this._yAxisLabelPadding;\n const rotation = this._useRightYAxis ? 90 : -90;\n this.renderYAxisLabel('y', this.yAxisLabel(), rotation, labelPosition);\n }\n\n _renderHorizontalGridLinesForAxis (g, scale, axis) {\n let gridLineG = g.select(`g.${HORIZONTAL_CLASS}`);\n\n if (this._renderHorizontalGridLine) {\n // see https://github.com/d3/d3-axis/blob/master/src/axis.js#L48\n const ticks = axis.tickValues() ? axis.tickValues() :\n (scale.ticks ? scale.ticks.apply(scale, axis.tickArguments()) : scale.domain());\n\n if (gridLineG.empty()) {\n gridLineG = g.insert('g', ':first-child')\n .attr('class', `${GRID_LINE_CLASS} ${HORIZONTAL_CLASS}`)\n .attr('transform', `translate(${this.margins().left},${this.margins().top})`);\n }\n\n const lines = gridLineG.selectAll('line')\n .data(ticks);\n\n // enter\n const linesGEnter = lines.enter()\n .append('line')\n .attr('x1', 1)\n .attr('y1', d => scale(d))\n .attr('x2', this.xAxisLength())\n .attr('y2', d => scale(d))\n .attr('opacity', 0);\n transition(linesGEnter, this.transitionDuration(), this.transitionDelay())\n .attr('opacity', 0.5);\n\n // update\n transition(lines, this.transitionDuration(), this.transitionDelay())\n .attr('x1', 1)\n .attr('y1', d => scale(d))\n .attr('x2', this.xAxisLength())\n .attr('y2', d => scale(d));\n\n // exit\n lines.exit().remove();\n } else {\n gridLineG.selectAll('line').remove();\n }\n }\n\n _yAxisX () {\n return this.useRightYAxis() ? this.width() - this.margins().right : this.margins().left;\n }\n\n /**\n * Set or get the y axis label. If setting the label, you may optionally include additional padding\n * to the margin to make room for the label. By default the padding is set to 12 to accommodate the\n * text height.\n * @param {String} [labelText]\n * @param {Number} [padding=12]\n * @returns {String|CoordinateGridMixin}\n */\n yAxisLabel (labelText, padding) {\n if (!arguments.length) {\n return this._yAxisLabel;\n }\n this._yAxisLabel = labelText;\n this.margins().left -= this._yAxisLabelPadding;\n this._yAxisLabelPadding = (padding === undefined) ? DEFAULT_AXIS_LABEL_PADDING : padding;\n this.margins().left += this._yAxisLabelPadding;\n return this;\n }\n\n /**\n * Get or set the y scale. The y scale is typically automatically determined by the chart implementation.\n * @see {@link https://github.com/d3/d3-scale/blob/master/README.md d3.scale}\n * @param {d3.scale} [yScale]\n * @returns {d3.scale|CoordinateGridMixin}\n */\n y (yScale) {\n if (!arguments.length) {\n return this._y;\n }\n this._y = yScale;\n this.rescale();\n return this;\n }\n\n /**\n * Set or get the y axis used by the coordinate grid chart instance. This function is most useful\n * when y axis customization is required. Depending on `useRightYAxis` the y axis in dc.js is an instance of\n * either [d3.axisLeft](https://github.com/d3/d3-axis/blob/master/README.md#axisLeft) or\n * [d3.axisRight](https://github.com/d3/d3-axis/blob/master/README.md#axisRight); therefore it supports any\n * valid d3 axis manipulation.\n *\n * **Caution**: The y axis is usually generated internally by dc; resetting it may cause\n * unexpected results. Note also that when used as a getter, this function is not chainable: it\n * returns the axis, not the chart,\n * {@link https://github.com/dc-js/dc.js/wiki/FAQ#why-does-everything-break-after-a-call-to-xaxis-or-yaxis\n * so attempting to call chart functions after calling `.yAxis()` will fail}.\n * In addition, depending on whether you are going to use the axis on left or right\n * you need to appropriately pass [d3.axisLeft](https://github.com/d3/d3-axis/blob/master/README.md#axisLeft)\n * or [d3.axisRight](https://github.com/d3/d3-axis/blob/master/README.md#axisRight)\n * @see {@link https://github.com/d3/d3-axis/blob/master/README.md d3.axis}\n * @example\n * // customize y axis tick format\n * chart.yAxis().tickFormat(function(v) {return v + '%';});\n * // customize y axis tick values\n * chart.yAxis().tickValues([0, 100, 200, 300]);\n * @param {d3.axisLeft|d3.axisRight} [yAxis]\n * @returns {d3.axisLeft|d3.axisRight|CoordinateGridMixin}\n */\n yAxis (yAxis) {\n if (!arguments.length) {\n if (!this._yAxis) {\n this._yAxis = this._createYAxis();\n }\n return this._yAxis;\n }\n this._yAxis = yAxis;\n return this;\n }\n\n /**\n * Turn on/off elastic y axis behavior. If y axis elasticity is turned on, then the grid chart will\n * attempt to recalculate the y axis range whenever a redraw event is triggered.\n * @param {Boolean} [elasticY=false]\n * @returns {Boolean|CoordinateGridMixin}\n */\n elasticY (elasticY) {\n if (!arguments.length) {\n return this._yElasticity;\n }\n this._yElasticity = elasticY;\n return this;\n }\n\n /**\n * Turn on/off horizontal grid lines.\n * @param {Boolean} [renderHorizontalGridLines=false]\n * @returns {Boolean|CoordinateGridMixin}\n */\n renderHorizontalGridLines (renderHorizontalGridLines) {\n if (!arguments.length) {\n return this._renderHorizontalGridLine;\n }\n this._renderHorizontalGridLine = renderHorizontalGridLines;\n return this;\n }\n\n /**\n * Turn on/off vertical grid lines.\n * @param {Boolean} [renderVerticalGridLines=false]\n * @returns {Boolean|CoordinateGridMixin}\n */\n renderVerticalGridLines (renderVerticalGridLines) {\n if (!arguments.length) {\n return this._renderVerticalGridLine;\n }\n this._renderVerticalGridLine = renderVerticalGridLines;\n return this;\n }\n\n /**\n * Calculates the minimum x value to display in the chart. Includes xAxisPadding if set.\n * @returns {*}\n */\n xAxisMin () {\n const m = min(this.data(), e => this.keyAccessor()(e));\n return utils.subtract(m, this._xAxisPadding, this._xAxisPaddingUnit);\n }\n\n /**\n * Calculates the maximum x value to display in the chart. Includes xAxisPadding if set.\n * @returns {*}\n */\n xAxisMax () {\n const m = max(this.data(), e => this.keyAccessor()(e));\n return utils.add(m, this._xAxisPadding, this._xAxisPaddingUnit);\n }\n\n /**\n * Calculates the minimum y value to display in the chart. Includes yAxisPadding if set.\n * @returns {*}\n */\n yAxisMin () {\n const m = min(this.data(), e => this.valueAccessor()(e));\n return utils.subtract(m, this._yAxisPadding);\n }\n\n /**\n * Calculates the maximum y value to display in the chart. Includes yAxisPadding if set.\n * @returns {*}\n */\n yAxisMax () {\n const m = max(this.data(), e => this.valueAccessor()(e));\n return utils.add(m, this._yAxisPadding);\n }\n\n /**\n * Set or get y axis padding for the elastic y axis. The padding will be added to the top and\n * bottom of the y axis if elasticY is turned on; otherwise it is ignored.\n *\n * Padding can be an integer or percentage in string (e.g. '10%'). Padding can be applied to\n * number or date axes. When padding a date axis, an integer represents number of days being padded\n * and a percentage string will be treated the same as an integer.\n * @param {Number|String} [padding=0]\n * @returns {Number|CoordinateGridMixin}\n */\n yAxisPadding (padding) {\n if (!arguments.length) {\n return this._yAxisPadding;\n }\n this._yAxisPadding = padding;\n return this;\n }\n\n yAxisHeight () {\n return this.effectiveHeight();\n }\n\n /**\n * Set or get the rounding function used to quantize the selection when brushing is enabled.\n * @example\n * // set x unit round to by month, this will make sure range selection brush will\n * // select whole months\n * chart.round(d3.timeMonth.round);\n * @param {Function} [round]\n * @returns {Function|CoordinateGridMixin}\n */\n round (round) {\n if (!arguments.length) {\n return this._round;\n }\n this._round = round;\n return this;\n }\n\n _rangeBandPadding (_) {\n if (!arguments.length) {\n return this._fRangeBandPadding;\n }\n this._fRangeBandPadding = _;\n return this;\n }\n\n _outerRangeBandPadding (_) {\n if (!arguments.length) {\n return this._fOuterRangeBandPadding;\n }\n this._fOuterRangeBandPadding = _;\n return this;\n }\n\n filter (_) {\n if (!arguments.length) {\n return super.filter();\n }\n\n super.filter(_);\n\n this.redrawBrush(_, false);\n\n return this;\n }\n\n /**\n * Get or set the brush. Brush must be an instance of d3 brushes\n * https://github.com/d3/d3-brush/blob/master/README.md\n * You will use this only if you are writing a new chart type that supports brushing.\n *\n * **Caution**: dc creates and manages brushes internally. Go through and understand the source code\n * if you want to pass a new brush object. Even if you are only using the getter,\n * the brush object may not behave the way you expect.\n *\n * @param {d3.brush} [_]\n * @returns {d3.brush|CoordinateGridMixin}\n */\n brush (_) {\n if (!arguments.length) {\n return this._brush;\n }\n this._brush = _;\n return this;\n }\n\n renderBrush (g, doTransition) {\n if (this._brushOn) {\n this._brush.on('start brush end', d3compat.eventHandler((d, evt) => this._brushing(evt)));\n\n // To retrieve selection we need self._gBrush\n this._gBrush = g.append('g')\n .attr('class', 'brush')\n .attr('transform', `translate(${this.margins().left},${this.margins().top})`);\n\n this.setBrushExtents();\n\n this.createBrushHandlePaths(this._gBrush, doTransition);\n\n this.redrawBrush(this.filter(), doTransition);\n }\n }\n\n createBrushHandlePaths (gBrush) {\n let brushHandles = gBrush.selectAll(`path.${CUSTOM_BRUSH_HANDLE_CLASS}`).data([{type: 'w'}, {type: 'e'}]);\n\n brushHandles = brushHandles\n .enter()\n .append('path')\n .attr('class', CUSTOM_BRUSH_HANDLE_CLASS)\n .merge(brushHandles);\n\n brushHandles\n .attr('d', d => this.resizeHandlePath(d));\n }\n\n extendBrush (brushSelection) {\n if (brushSelection && this.round()) {\n brushSelection[0] = this.round()(brushSelection[0]);\n brushSelection[1] = this.round()(brushSelection[1]);\n }\n return brushSelection;\n }\n\n brushIsEmpty (brushSelection) {\n return !brushSelection || brushSelection[1] <= brushSelection[0];\n }\n\n _brushing (evt) {\n if (this._ignoreBrushEvents) {\n return;\n }\n\n let brushSelection = evt.selection;\n if (brushSelection) {\n brushSelection = brushSelection.map(this.x().invert);\n }\n\n brushSelection = this.extendBrush(brushSelection);\n\n this.redrawBrush(brushSelection, false);\n\n const rangedFilter = this.brushIsEmpty(brushSelection) ? null : filters.RangedFilter(brushSelection[0], brushSelection[1]);\n\n events.trigger(() => {\n this.applyBrushSelection(rangedFilter);\n }, constants.EVENT_DELAY);\n }\n\n // This can be overridden in a derived chart. For example Composite chart overrides it\n applyBrushSelection (rangedFilter) {\n this.replaceFilter(rangedFilter);\n this.redrawGroup();\n }\n\n _withoutBrushEvents (closure) {\n const oldValue = this._ignoreBrushEvents;\n this._ignoreBrushEvents = true;\n\n try {\n closure();\n } finally {\n this._ignoreBrushEvents = oldValue;\n }\n }\n\n setBrushExtents (doTransition) {\n this._withoutBrushEvents(() => {\n // Set boundaries of the brush, must set it before applying to self._gBrush\n this._brush.extent([[0, 0], [this.effectiveWidth(), this.effectiveHeight()]]);\n });\n\n this._gBrush\n .call(this._brush);\n }\n\n redrawBrush (brushSelection, doTransition) {\n if (this._brushOn && this._gBrush) {\n if (this._resizing) {\n this.setBrushExtents(doTransition);\n }\n\n if (!brushSelection) {\n this._withoutBrushEvents(() => {\n this._gBrush\n .call(this._brush.move, null);\n })\n\n this._gBrush.selectAll(`path.${CUSTOM_BRUSH_HANDLE_CLASS}`)\n .attr('display', 'none');\n } else {\n const scaledSelection = [this._x(brushSelection[0]), this._x(brushSelection[1])];\n\n const gBrush =\n optionalTransition(doTransition, this.transitionDuration(), this.transitionDelay())(this._gBrush);\n\n this._withoutBrushEvents(() => {\n gBrush\n .call(this._brush.move, scaledSelection);\n });\n\n gBrush.selectAll(`path.${CUSTOM_BRUSH_HANDLE_CLASS}`)\n .attr('display', null)\n .attr('transform', (d, i) => `translate(${this._x(brushSelection[i])}, 0)`)\n .attr('d', d => this.resizeHandlePath(d));\n }\n }\n this.fadeDeselectedArea(brushSelection);\n }\n\n fadeDeselectedArea (brushSelection) {\n // do nothing, sub-chart should override this function\n }\n\n // borrowed from Crossfilter example\n resizeHandlePath (d) {\n d = d.type;\n const e = +(d === 'e'), x = e ? 1 : -1, y = this.effectiveHeight() / 3;\n return `M${0.5 * x},${y \n }A6,6 0 0 ${e} ${6.5 * x},${y + 6 \n }V${2 * y - 6 \n }A6,6 0 0 ${e} ${0.5 * x},${2 * y \n }Z` +\n `M${2.5 * x},${y + 8 \n }V${2 * y - 8 \n }M${4.5 * x},${y + 8 \n }V${2 * y - 8}`;\n }\n\n _getClipPathId () {\n return `${this.anchorName().replace(/[ .#=\\[\\]\"]/g, '-')}-clip`;\n }\n\n /**\n * Get or set the padding in pixels for the clip path. Once set padding will be applied evenly to\n * the top, left, right, and bottom when the clip path is generated. If set to zero, the clip area\n * will be exactly the chart body area minus the margins.\n * @param {Number} [padding=5]\n * @returns {Number|CoordinateGridMixin}\n */\n clipPadding (padding) {\n if (!arguments.length) {\n return this._clipPadding;\n }\n this._clipPadding = padding;\n return this;\n }\n\n _generateClipPath () {\n const defs = utils.appendOrSelect(this._parent, 'defs');\n // cannot select elements; bug in WebKit, must select by id\n // https://groups.google.com/forum/#!topic/d3-js/6EpAzQ2gU9I\n const id = this._getClipPathId();\n const chartBodyClip = utils.appendOrSelect(defs, `#${id}`, 'clipPath').attr('id', id);\n\n const padding = this._clipPadding * 2;\n\n utils.appendOrSelect(chartBodyClip, 'rect')\n .attr('width', this.xAxisLength() + padding)\n .attr('height', this.yAxisHeight() + padding)\n .attr('transform', `translate(-${this._clipPadding}, -${this._clipPadding})`);\n }\n\n _preprocessData () {\n }\n\n _doRender () {\n this.resetSvg();\n\n this._preprocessData();\n\n this._generateG();\n this._generateClipPath();\n\n this._drawChart(true);\n\n this._configureMouseZoom();\n\n return this;\n }\n\n _doRedraw () {\n this._preprocessData();\n\n this._drawChart(false);\n this._generateClipPath();\n\n return this;\n }\n\n _drawChart (render) {\n if (this.isOrdinal()) {\n this._brushOn = false;\n }\n\n this._prepareXAxis(this.g(), render);\n this._prepareYAxis(this.g());\n\n this.plotData();\n\n if (this.elasticX() || this._resizing || render) {\n this.renderXAxis(this.g());\n }\n\n if (this.elasticY() || this._resizing || render) {\n this.renderYAxis(this.g());\n }\n\n if (render) {\n this.renderBrush(this.g(), false);\n } else {\n // Animate the brush only while resizing\n this.redrawBrush(this.filter(), this._resizing);\n }\n this.fadeDeselectedArea(this.filter());\n this.resizing(false);\n }\n\n _configureMouseZoom () {\n // Save a copy of original x scale\n this._origX = this._x.copy();\n\n if (this._mouseZoomable) {\n this._enableMouseZoom();\n } else if (this._hasBeenMouseZoomable) {\n this._disableMouseZoom();\n }\n }\n\n _enableMouseZoom () {\n this._hasBeenMouseZoomable = true;\n\n const extent = [[0, 0], [this.effectiveWidth(), this.effectiveHeight()]];\n\n this._zoom\n .scaleExtent(this._zoomScale)\n .extent(extent)\n .duration(this.transitionDuration());\n\n if (this._zoomOutRestrict) {\n // Ensure minimum zoomScale is at least 1\n const zoomScaleMin = Math.max(this._zoomScale[0], 1);\n this._zoom\n .translateExtent(extent)\n .scaleExtent([zoomScaleMin, this._zoomScale[1]]);\n }\n\n this.root().call(this._zoom);\n\n // Tell D3 zoom our current zoom/pan status\n this._updateD3zoomTransform();\n }\n\n _disableMouseZoom () {\n this.root().call(this._nullZoom);\n }\n\n _zoomHandler (newDomain, noRaiseEvents) {\n let domFilter;\n\n if (this._hasRangeSelected(newDomain)) {\n this.x().domain(newDomain);\n domFilter = filters.RangedFilter(newDomain[0], newDomain[1]);\n } else {\n this.x().domain(this._xOriginalDomain);\n domFilter = null;\n }\n\n this.replaceFilter(domFilter);\n this.rescale();\n this.redraw();\n\n if (!noRaiseEvents) {\n if (this._rangeChart && !utils.arraysEqual(this.filter(), this._rangeChart.filter())) {\n events.trigger(() => {\n this._rangeChart.replaceFilter(domFilter);\n this._rangeChart.redraw();\n });\n }\n\n this._invokeZoomedListener();\n events.trigger(() => {\n this.redrawGroup();\n }, constants.EVENT_DELAY);\n }\n }\n\n // event.transform.rescaleX(self._origX).domain() should give back newDomain\n _domainToZoomTransform (newDomain, origDomain, xScale) {\n const k = (origDomain[1] - origDomain[0]) / (newDomain[1] - newDomain[0]);\n const xt = -1 * xScale(newDomain[0]);\n\n return zoomIdentity.scale(k).translate(xt, 0);\n }\n\n // If we changing zoom status (for example by calling focus), tell D3 zoom about it\n _updateD3zoomTransform () {\n if (this._zoom) {\n this._withoutZoomEvents(() => {\n this._zoom.transform(this.root(), this._domainToZoomTransform(this.x().domain(), this._xOriginalDomain, this._origX));\n });\n }\n }\n\n _withoutZoomEvents (closure) {\n const oldValue = this._ignoreZoomEvents;\n this._ignoreZoomEvents = true;\n\n try {\n closure();\n } finally {\n this._ignoreZoomEvents = oldValue;\n }\n }\n\n _onZoom (evt) {\n // ignore zoom events if it was caused by a programmatic change\n if (this._ignoreZoomEvents) {\n return;\n }\n\n const newDomain = evt.transform.rescaleX(this._origX).domain();\n this.focus(newDomain, false);\n }\n\n _checkExtents (ext, outerLimits) {\n if (!ext || ext.length !== 2 || !outerLimits || outerLimits.length !== 2) {\n return ext;\n }\n\n if (ext[0] > outerLimits[1] || ext[1] < outerLimits[0]) {\n console.warn('Could not intersect extents, will reset');\n }\n // Math.max does not work (as the values may be dates as well)\n return [ext[0] > outerLimits[0] ? ext[0] : outerLimits[0], ext[1] < outerLimits[1] ? ext[1] : outerLimits[1]];\n }\n\n /**\n * Zoom this chart to focus on the given range. The given range should be an array containing only\n * 2 elements (`[start, end]`) defining a range in the x domain. If the range is not given or set\n * to null, then the zoom will be reset. _For focus to work elasticX has to be turned off;\n * otherwise focus will be ignored.\n *\n * To avoid ping-pong volley of events between a pair of range and focus charts please set\n * `noRaiseEvents` to `true`. In that case it will update this chart but will not fire `zoom` event\n * and not try to update back the associated range chart.\n * If you are calling it manually - typically you will leave it to `false` (the default).\n *\n * @example\n * chart.on('renderlet', function(chart) {\n * // smooth the rendering through event throttling\n * events.trigger(function(){\n * // focus some other chart to the range selected by user on this chart\n * someOtherChart.focus(chart.filter());\n * });\n * })\n * @param {Array} [range]\n * @param {Boolean} [noRaiseEvents = false]\n * @return {undefined}\n */\n focus (range, noRaiseEvents) {\n if (this._zoomOutRestrict) {\n // ensure range is within self._xOriginalDomain\n range = this._checkExtents(range, this._xOriginalDomain);\n\n // If it has an associated range chart ensure range is within domain of that rangeChart\n if (this._rangeChart) {\n range = this._checkExtents(range, this._rangeChart.x().domain());\n }\n }\n\n this._zoomHandler(range, noRaiseEvents);\n this._updateD3zoomTransform();\n }\n\n refocused () {\n return !utils.arraysEqual(this.x().domain(), this._xOriginalDomain);\n }\n\n focusChart (c) {\n if (!arguments.length) {\n return this._focusChart;\n }\n this._focusChart = c;\n this.on('filtered.dcjs-range-chart', chart => {\n if (!chart.filter()) {\n events.trigger(() => {\n this._focusChart.x().domain(this._focusChart.xOriginalDomain(), true);\n });\n } else if (!utils.arraysEqual(chart.filter(), this._focusChart.filter())) {\n events.trigger(() => {\n this._focusChart.focus(chart.filter(), true);\n });\n }\n });\n return this;\n }\n\n /**\n * Turn on/off the brush-based range filter. When brushing is on then user can drag the mouse\n * across a chart with a quantitative scale to perform range filtering based on the extent of the\n * brush, or click on the bars of an ordinal bar chart or slices of a pie chart to filter and\n * un-filter them. However turning on the brush filter will disable other interactive elements on\n * the chart such as highlighting, tool tips, and reference lines. Zooming will still be possible\n * if enabled, but only via scrolling (panning will be disabled.)\n * @param {Boolean} [brushOn=true]\n * @returns {Boolean|CoordinateGridMixin}\n */\n brushOn (brushOn) {\n if (!arguments.length) {\n return this._brushOn;\n }\n this._brushOn = brushOn;\n return this;\n }\n\n /**\n * This will be internally used by composite chart onto children. Please go not invoke directly.\n *\n * @protected\n * @param {Boolean} [brushOn=false]\n * @returns {Boolean|CoordinateGridMixin}\n */\n parentBrushOn (brushOn) {\n if (!arguments.length) {\n return this._parentBrushOn;\n }\n this._parentBrushOn = brushOn;\n return this;\n }\n\n // Get the SVG rendered brush\n gBrush () {\n return this._gBrush;\n }\n\n _hasRangeSelected (range) {\n return range instanceof Array && range.length > 1;\n }\n}\n","// https://github.com/d3/d3-plugins/blob/master/box/box.js\n// * Original source March 22, 2013\n// * Enhancements integrated on May 13, 2018 for dc.js library only\n\n// https://github.com/d3/d3-plugins/blob/master/LICENSE\n// Copyright (c) 2012-2015, Michael Bostock\n// All rights reserved.\n\n// Redistribution and use in source and binary forms, with or without\n// modification, are permitted provided that the following conditions are met:\n//\n// * Redistributions of source code must retain the above copyright notice, this\n// list of conditions and the following disclaimer.\n//\n// * Redistributions in binary form must reproduce the above copyright notice,\n// this list of conditions and the following disclaimer in the documentation\n// and/or other materials provided with the distribution.\n//\n// * The name Michael Bostock may not be used to endorse or promote products\n// derived from this software without specific prior written permission.\n//\n// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\n// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\n// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n// DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,\n// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,\n// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY\n// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,\n// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n/*eslint complexity: 0*/\n\n// Inspired by http://informationandvisualization.de/blog/box-plot\n\nimport {ascending, quantile, range} from 'd3-array';\nimport {select} from 'd3-selection';\nimport {scaleLinear} from 'd3-scale';\nimport {timerFlush} from 'd3-timer';\n\nimport {utils} from '../core/utils';\n\nexport const d3Box = function () {\n let width = 1;\n let height = 1;\n let duration = 0;\n const delay = 0;\n let domain = null;\n let value = Number;\n let whiskers = boxWhiskers;\n let quartiles = boxQuartiles;\n let tickFormat = null;\n\n // Enhanced attributes\n let renderDataPoints = false;\n const dataRadius = 3;\n let dataOpacity = 0.3;\n let dataWidthPortion = 0.8;\n let renderTitle = false;\n let showOutliers = true;\n let boldOutlier = false;\n\n\n // For each small multiple…\n function box (g) {\n g.each(function (data, index) {\n data = data.map(value).sort(ascending);\n const _g = select(this);\n const n = data.length;\n let min;\n let max;\n\n // Leave if there are no items.\n if (n === 0) {return;}\n\n // Compute quartiles. Must return exactly 3 elements.\n const quartileData = data.quartiles = quartiles(data);\n\n // Compute whiskers. Must return exactly 2 elements, or null.\n const whiskerIndices = whiskers && whiskers.call(this, data, index),\n whiskerData = whiskerIndices && whiskerIndices.map(_i => data[_i]);\n\n // Compute outliers. If no whiskers are specified, all data are 'outliers'.\n // We compute the outliers as indices, so that we can join across transitions!\n const outlierIndices = whiskerIndices ?\n range(0, whiskerIndices[0]).concat(range(whiskerIndices[1] + 1, n)) : range(n);\n\n // Determine the maximum value based on if outliers are shown\n if (showOutliers) {\n min = data[0];\n max = data[n - 1];\n } else {\n min = data[whiskerIndices[0]];\n max = data[whiskerIndices[1]];\n }\n const pointIndices = range(whiskerIndices[0], whiskerIndices[1] + 1);\n\n // Compute the new x-scale.\n const x1 = scaleLinear()\n .domain(domain && domain.call(this, data, index) || [min, max])\n .range([height, 0]);\n\n // Retrieve the old x-scale, if this is an update.\n const x0 = this.__chart__ || scaleLinear()\n .domain([0, Infinity])\n .range(x1.range());\n\n // Stash the new scale.\n this.__chart__ = x1;\n\n // Note: the box, median, and box tick elements are fixed in number,\n // so we only have to handle enter and update. In contrast, the outliers\n // and other elements are variable, so we need to exit them! Variable\n // elements also fade in and out.\n\n // Update center line: the vertical line spanning the whiskers.\n const center = _g.selectAll('line.center')\n .data(whiskerData ? [whiskerData] : []);\n\n center.enter().insert('line', 'rect')\n .attr('class', 'center')\n .attr('x1', width / 2)\n .attr('y1', d => x0(d[0]))\n .attr('x2', width / 2)\n .attr('y2', d => x0(d[1]))\n .style('opacity', 1e-6)\n .transition()\n .duration(duration)\n .delay(delay)\n .style('opacity', 1)\n .attr('y1', d => x1(d[0]))\n .attr('y2', d => x1(d[1]));\n\n center.transition()\n .duration(duration)\n .delay(delay)\n .style('opacity', 1)\n .attr('x1', width / 2)\n .attr('x2', width / 2)\n .attr('y1', d => x1(d[0]))\n .attr('y2', d => x1(d[1]));\n\n center.exit().transition()\n .duration(duration)\n .delay(delay)\n .style('opacity', 1e-6)\n .attr('y1', d => x1(d[0]))\n .attr('y2', d => x1(d[1]))\n .remove();\n\n // Update innerquartile box.\n const _box = _g.selectAll('rect.box')\n .data([quartileData]);\n\n _box.enter().append('rect')\n .attr('class', 'box')\n .attr('x', 0)\n .attr('y', d => x0(d[2]))\n .attr('width', width)\n .attr('height', d => x0(d[0]) - x0(d[2]))\n .style('fill-opacity', (renderDataPoints) ? 0.1 : 1)\n .transition()\n .duration(duration)\n .delay(delay)\n .attr('y', d => x1(d[2]))\n .attr('height', d => x1(d[0]) - x1(d[2]));\n\n _box.transition()\n .duration(duration)\n .delay(delay)\n .attr('width', width)\n .attr('y', d => x1(d[2]))\n .attr('height', d => x1(d[0]) - x1(d[2]));\n\n // Update median line.\n const medianLine = _g.selectAll('line.median')\n .data([quartileData[1]]);\n\n medianLine.enter().append('line')\n .attr('class', 'median')\n .attr('x1', 0)\n .attr('y1', x0)\n .attr('x2', width)\n .attr('y2', x0)\n .transition()\n .duration(duration)\n .delay(delay)\n .attr('y1', x1)\n .attr('y2', x1);\n\n medianLine.transition()\n .duration(duration)\n .delay(delay)\n .attr('x1', 0)\n .attr('x2', width)\n .attr('y1', x1)\n .attr('y2', x1);\n\n // Update whiskers.\n const whisker = _g.selectAll('line.whisker')\n .data(whiskerData || []);\n\n whisker.enter().insert('line', 'circle, text')\n .attr('class', 'whisker')\n .attr('x1', 0)\n .attr('y1', x0)\n .attr('x2', width)\n .attr('y2', x0)\n .style('opacity', 1e-6)\n .transition()\n .duration(duration)\n .delay(delay)\n .attr('y1', x1)\n .attr('y2', x1)\n .style('opacity', 1);\n\n whisker.transition()\n .duration(duration)\n .delay(delay)\n .attr('x1', 0)\n .attr('x2', width)\n .attr('y1', x1)\n .attr('y2', x1)\n .style('opacity', 1);\n\n whisker.exit().transition()\n .duration(duration)\n .delay(delay)\n .attr('y1', x1)\n .attr('y2', x1)\n .style('opacity', 1e-6)\n .remove();\n\n // Update outliers.\n if (showOutliers) {\n const outlierClass = boldOutlier ? 'outlierBold' : 'outlier';\n const outlierSize = boldOutlier ? 3 : 5;\n const outlierX = boldOutlier ?\n function () {\n return Math.floor(Math.random() *\n (width * dataWidthPortion) +\n 1 + ((width - (width * dataWidthPortion)) / 2));\n } :\n function () {\n return width / 2;\n };\n\n const outlier = _g.selectAll(`circle.${outlierClass}`)\n .data(outlierIndices, Number);\n\n outlier.enter().insert('circle', 'text')\n .attr('class', outlierClass)\n .attr('r', outlierSize)\n .attr('cx', outlierX)\n .attr('cy', i => x0(data[i]))\n .style('opacity', 1e-6)\n .transition()\n .duration(duration)\n .delay(delay)\n .attr('cy', i => x1(data[i]))\n .style('opacity', 0.6);\n\n if (renderTitle) {\n outlier.selectAll('title').remove();\n outlier.append('title').text(i => data[i]);\n }\n\n outlier.transition()\n .duration(duration)\n .delay(delay)\n .attr('cx', outlierX)\n .attr('cy', i => x1(data[i]))\n .style('opacity', 0.6);\n\n outlier.exit().transition()\n .duration(duration)\n .delay(delay)\n .attr('cy', 0) //function (i) { return x1(d[i]); })\n .style('opacity', 1e-6)\n .remove();\n }\n\n // Update Values\n if (renderDataPoints) {\n const point = _g.selectAll('circle.data')\n .data(pointIndices);\n\n point.enter().insert('circle', 'text')\n .attr('class', 'data')\n .attr('r', dataRadius)\n .attr('cx', () => Math.floor(Math.random() *\n (width * dataWidthPortion) +\n 1 + ((width - (width * dataWidthPortion)) / 2)))\n .attr('cy', i => x0(data[i]))\n .style('opacity', 1e-6)\n .transition()\n .duration(duration)\n .delay(delay)\n .attr('cy', i => x1(data[i]))\n .style('opacity', dataOpacity);\n\n if (renderTitle) {\n point.selectAll('title').remove();\n point.append('title').text(i => data[i]);\n }\n\n point.transition()\n .duration(duration)\n .delay(delay)\n .attr('cx', () => Math.floor(Math.random() *\n (width * dataWidthPortion) +\n 1 + ((width - (width * dataWidthPortion)) / 2)))\n .attr('cy', i => x1(data[i]))\n .style('opacity', dataOpacity);\n\n point.exit().transition()\n .duration(duration)\n .delay(delay)\n .attr('cy', 0)\n .style('opacity', 1e-6)\n .remove();\n }\n\n // Compute the tick format.\n const format = tickFormat || x1.tickFormat(8);\n\n // Update box ticks.\n const boxTick = _g.selectAll('text.box')\n .data(quartileData);\n\n boxTick.enter().append('text')\n .attr('class', 'box')\n .attr('dy', '.3em')\n .attr('dx', (d, i) => i & 1 ? 6 : -6)\n .attr('x', (d, i) => i & 1 ? width : 0)\n .attr('y', x0)\n .attr('text-anchor', (d, i) => i & 1 ? 'start' : 'end')\n .text(format)\n .transition()\n .duration(duration)\n .delay(delay)\n .attr('y', x1);\n\n boxTick.transition()\n .duration(duration)\n .delay(delay)\n .text(format)\n .attr('x', (d, i) => i & 1 ? width : 0)\n .attr('y', x1);\n\n // Update whisker ticks. These are handled separately from the box\n // ticks because they may or may not exist, and we want don't want\n // to join box ticks pre-transition with whisker ticks post-.\n const whiskerTick = _g.selectAll('text.whisker')\n .data(whiskerData || []);\n\n whiskerTick.enter().append('text')\n .attr('class', 'whisker')\n .attr('dy', '.3em')\n .attr('dx', 6)\n .attr('x', width)\n .attr('y', x0)\n .text(format)\n .style('opacity', 1e-6)\n .transition()\n .duration(duration)\n .delay(delay)\n .attr('y', x1)\n .style('opacity', 1);\n\n whiskerTick.transition()\n .duration(duration)\n .delay(delay)\n .text(format)\n .attr('x', width)\n .attr('y', x1)\n .style('opacity', 1);\n\n whiskerTick.exit().transition()\n .duration(duration)\n .delay(delay)\n .attr('y', x1)\n .style('opacity', 1e-6)\n .remove();\n\n // Remove temporary quartiles element from within data array.\n delete data.quartiles;\n });\n timerFlush();\n }\n\n box.width = function (x) {\n if (!arguments.length) {\n return width;\n }\n width = x;\n return box;\n };\n\n box.height = function (x) {\n if (!arguments.length) {\n return height;\n }\n height = x;\n return box;\n };\n\n box.tickFormat = function (x) {\n if (!arguments.length) {\n return tickFormat;\n }\n tickFormat = x;\n return box;\n };\n\n box.showOutliers = function (x) {\n if (!arguments.length) {\n return showOutliers;\n }\n showOutliers = x;\n return box;\n };\n\n box.boldOutlier = function (x) {\n if (!arguments.length) {\n return boldOutlier;\n }\n boldOutlier = x;\n return box;\n };\n\n box.renderDataPoints = function (x) {\n if (!arguments.length) {\n return renderDataPoints;\n }\n renderDataPoints = x;\n return box;\n };\n\n box.renderTitle = function (x) {\n if (!arguments.length) {\n return renderTitle;\n }\n renderTitle = x;\n return box;\n };\n\n box.dataOpacity = function (x) {\n if (!arguments.length) {\n return dataOpacity;\n }\n dataOpacity = x;\n return box;\n };\n\n box.dataWidthPortion = function (x) {\n if (!arguments.length) {\n return dataWidthPortion;\n }\n dataWidthPortion = x;\n return box;\n };\n\n box.duration = function (x) {\n if (!arguments.length) {\n return duration;\n }\n duration = x;\n return box;\n };\n\n box.domain = function (x) {\n if (!arguments.length) {\n return domain;\n }\n domain = x === null ? x : typeof x === 'function' ? x : utils.constant(x);\n return box;\n };\n\n box.value = function (x) {\n if (!arguments.length) {\n return value;\n }\n value = x;\n return box;\n };\n\n box.whiskers = function (x) {\n if (!arguments.length) {\n return whiskers;\n }\n whiskers = x;\n return box;\n };\n\n box.quartiles = function (x) {\n if (!arguments.length) {\n return quartiles;\n }\n quartiles = x;\n return box;\n };\n\n return box;\n};\n\nfunction boxWhiskers (d) {\n return [0, d.length - 1];\n}\n\nfunction boxQuartiles (d) {\n return [\n quantile(d, 0.25),\n quantile(d, 0.5),\n quantile(d, 0.75)\n ];\n}\n","import {stack} from 'd3-shape';\nimport {max, min} from 'd3-array';\n\nimport {pluck, utils} from '../core/utils';\nimport {CoordinateGridMixin} from './coordinate-grid-mixin';\n\n/**\n * Stack Mixin is an mixin that provides cross-chart support of stackability using d3.stack.\n * @mixin StackMixin\n * @mixes CoordinateGridMixin\n */\nexport class StackMixin extends CoordinateGridMixin {\n constructor () {\n super();\n\n this._stackLayout = stack();\n\n this._stack = [];\n this._titles = {};\n\n this._hidableStacks = false;\n this._evadeDomainFilter = false;\n\n this.data(() => {\n const layers = this._stack.filter(this._visibility);\n if (!layers.length) {\n return [];\n }\n layers.forEach((l, i) => this._prepareValues(l, i));\n const v4data = layers[0].values.map((v, i) => {\n const col = {x: v.x};\n layers.forEach(layer => {\n col[layer.name] = layer.values[i].y;\n });\n return col;\n });\n const keys = layers.map(layer => layer.name);\n const v4result = this.stackLayout().keys(keys)(v4data);\n v4result.forEach((series, i) => {\n series.forEach((ys, j) => {\n layers[i].values[j].y0 = ys[0];\n layers[i].values[j].y1 = ys[1];\n });\n });\n return layers;\n });\n\n this.colorAccessor(function (d) {\n return this.layer || this.name || d.name || d.layer;\n });\n }\n\n _prepareValues (layer, layerIdx) {\n const valAccessor = layer.accessor || this.valueAccessor();\n layer.name = String(layer.name || layerIdx);\n const allValues = layer.group.all().map((d, i) => ({\n x: this.keyAccessor()(d, i),\n y: layer.hidden ? null : valAccessor(d, i),\n data: d,\n layer: layer.name,\n hidden: layer.hidden\n }));\n\n layer.domainValues = allValues.filter(l => this._domainFilter()(l));\n layer.values = this.evadeDomainFilter() ? allValues : layer.domainValues;\n }\n\n _domainFilter () {\n if (!this.x()) {\n return utils.constant(true);\n }\n const xDomain = this.x().domain();\n if (this.isOrdinal()) {\n // TODO #416\n //var domainSet = d3.set(xDomain);\n return () => true //domainSet.has(p.x);\n ;\n }\n if (this.elasticX()) {\n return () => true;\n }\n return p => p.x >= xDomain[0] && p.x <= xDomain[xDomain.length - 1];\n }\n\n /**\n * Stack a new crossfilter group onto this chart with an optional custom value accessor. All stacks\n * in the same chart will share the same key accessor and therefore the same set of keys.\n *\n * For example, in a stacked bar chart, the bars of each stack will be positioned using the same set\n * of keys on the x axis, while stacked vertically. If name is specified then it will be used to\n * generate the legend label.\n * @see {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group-map-reduce crossfilter.group}\n * @example\n * // stack group using default accessor\n * chart.stack(valueSumGroup)\n * // stack group using custom accessor\n * .stack(avgByDayGroup, function(d){return d.value.avgByDay;});\n * @param {crossfilter.group} group\n * @param {String} [name]\n * @param {Function} [accessor]\n * @returns {Array<{group: crossfilter.group, name: String, accessor: Function}>|StackMixin}\n */\n stack (group, name, accessor) {\n if (!arguments.length) {\n return this._stack;\n }\n\n if (arguments.length <= 2) {\n accessor = name;\n }\n\n const layer = {group: group};\n if (typeof name === 'string') {\n layer.name = name;\n }\n if (typeof accessor === 'function') {\n layer.accessor = accessor;\n }\n this._stack.push(layer);\n\n return this;\n }\n\n group (g, n, f) {\n if (!arguments.length) {\n return super.group();\n }\n this._stack = [];\n this._titles = {};\n this.stack(g, n);\n if (f) {\n this.valueAccessor(f);\n }\n return super.group(g, n);\n }\n\n /**\n * Allow named stacks to be hidden or shown by clicking on legend items.\n * This does not affect the behavior of hideStack or showStack.\n * @param {Boolean} [hidableStacks=false]\n * @returns {Boolean|StackMixin}\n */\n hidableStacks (hidableStacks) {\n if (!arguments.length) {\n return this._hidableStacks;\n }\n this._hidableStacks = hidableStacks;\n return this;\n }\n\n _findLayerByName (n) {\n const i = this._stack.map(pluck('name')).indexOf(n);\n return this._stack[i];\n }\n\n /**\n * Hide all stacks on the chart with the given name.\n * The chart must be re-rendered for this change to appear.\n * @param {String} stackName\n * @returns {StackMixin}\n */\n hideStack (stackName) {\n const layer = this._findLayerByName(stackName);\n if (layer) {\n layer.hidden = true;\n }\n return this;\n }\n\n /**\n * Show all stacks on the chart with the given name.\n * The chart must be re-rendered for this change to appear.\n * @param {String} stackName\n * @returns {StackMixin}\n */\n showStack (stackName) {\n const layer = this._findLayerByName(stackName);\n if (layer) {\n layer.hidden = false;\n }\n return this;\n }\n\n getValueAccessorByIndex (index) {\n return this._stack[index].accessor || this.valueAccessor();\n }\n\n yAxisMin () {\n const m = min(this._flattenStack(), p => (p.y < 0) ? (p.y + p.y0) : p.y0);\n return utils.subtract(m, this.yAxisPadding());\n }\n\n yAxisMax () {\n const m = max(this._flattenStack(), p => (p.y > 0) ? (p.y + p.y0) : p.y0);\n return utils.add(m, this.yAxisPadding());\n }\n\n _flattenStack () {\n // A round about way to achieve flatMap\n // When target browsers support flatMap, just replace map -> flatMap, no concat needed\n const values = this.data().map(layer => layer.domainValues);\n return [].concat(...values);\n }\n\n xAxisMin () {\n const m = min(this._flattenStack(), pluck('x'));\n return utils.subtract(m, this.xAxisPadding(), this.xAxisPaddingUnit());\n }\n\n xAxisMax () {\n const m = max(this._flattenStack(), pluck('x'));\n return utils.add(m, this.xAxisPadding(), this.xAxisPaddingUnit());\n }\n\n /**\n * Set or get the title function. Chart class will use this function to render svg title (usually interpreted by\n * browser as tooltips) for each child element in the chart, i.e. a slice in a pie chart or a bubble in a bubble chart.\n * Almost every chart supports title function however in grid coordinate chart you need to turn off brush in order to\n * use title otherwise the brush layer will block tooltip trigger.\n *\n * If the first argument is a stack name, the title function will get or set the title for that stack. If stackName\n * is not provided, the first stack is implied.\n * @example\n * // set a title function on 'first stack'\n * chart.title('first stack', function(d) { return d.key + ': ' + d.value; });\n * // get a title function from 'second stack'\n * var secondTitleFunction = chart.title('second stack');\n * @param {String} [stackName]\n * @param {Function} [titleAccessor]\n * @returns {String|StackMixin}\n */\n title (stackName, titleAccessor) {\n if (!stackName) {\n return super.title();\n }\n\n if (typeof stackName === 'function') {\n return super.title(stackName);\n }\n if (stackName === this._groupName && typeof titleAccessor === 'function') {\n return super.title(titleAccessor);\n }\n\n if (typeof titleAccessor !== 'function') {\n return this._titles[stackName] || super.title();\n }\n\n this._titles[stackName] = titleAccessor;\n\n return this;\n }\n\n /**\n * Gets or sets the stack layout algorithm, which computes a baseline for each stack and\n * propagates it to the next.\n * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Stack-Layout.md d3.stackD3v3}\n * @param {Function} [_stack=d3.stackD3v3]\n * @returns {Function|StackMixin}\n */\n stackLayout (_stack) {\n if (!arguments.length) {\n return this._stackLayout;\n }\n this._stackLayout = _stack;\n return this;\n }\n\n /**\n * Since dc.js 2.0, there has been {@link https://github.com/dc-js/dc.js/issues/949 an issue}\n * where points are filtered to the current domain. While this is a useful optimization, it is\n * incorrectly implemented: the next point outside the domain is required in order to draw lines\n * that are clipped to the bounds, as well as bars that are partly clipped.\n *\n * A fix will be included in dc.js 2.1.x, but a workaround is needed for dc.js 2.0 and until\n * that fix is published, so set this flag to skip any filtering of points.\n *\n * Once the bug is fixed, this flag will have no effect, and it will be deprecated.\n * @param {Boolean} [evadeDomainFilter=false]\n * @returns {Boolean|StackMixin}\n */\n evadeDomainFilter (evadeDomainFilter) {\n if (!arguments.length) {\n return this._evadeDomainFilter;\n }\n this._evadeDomainFilter = evadeDomainFilter;\n return this;\n }\n\n _visibility (l) {\n return !l.hidden;\n }\n\n _ordinalXDomain () {\n const flat = this._flattenStack().map(pluck('data'));\n const ordered = this._computeOrderedGroups(flat);\n return ordered.map(this.keyAccessor());\n }\n\n legendables () {\n return this._stack.map((layer, i) => ({\n chart: this,\n name: layer.name,\n hidden: layer.hidden || false,\n color: this.getColor.call(layer, layer.values, i)\n }));\n }\n\n isLegendableHidden (d) {\n const layer = this._findLayerByName(d.name);\n return layer ? layer.hidden : false;\n }\n\n legendToggle (d) {\n if (this._hidableStacks) {\n if (this.isLegendableHidden(d)) {\n this.showStack(d.name);\n } else {\n this.hideStack(d.name);\n }\n //_chart.redraw();\n this.renderGroup();\n }\n }\n}\n","import {select} from 'd3-selection';\n\nimport {StackMixin} from '../base/stack-mixin';\nimport {transition} from '../core/core';\nimport {constants} from '../core/constants';\nimport {logger} from '../core/logger';\nimport {pluck, utils} from '../core/utils';\nimport {d3compat} from '../core/config';\n\nconst MIN_BAR_WIDTH = 1;\nconst DEFAULT_GAP_BETWEEN_BARS = 2;\nconst LABEL_PADDING = 3;\n\n/**\n * Concrete bar chart/histogram implementation.\n *\n * Examples:\n * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}\n * - {@link http://dc-js.github.com/dc.js/crime/index.html Canadian City Crime Stats}\n * @mixes StackMixin\n */\nexport class BarChart extends StackMixin {\n /**\n * Create a Bar Chart\n * @example\n * // create a bar chart under #chart-container1 element using the default global chart group\n * var chart1 = new BarChart('#chart-container1');\n * // create a bar chart under #chart-container2 element using chart group A\n * var chart2 = new BarChart('#chart-container2', 'chartGroupA');\n * // create a sub-chart under a composite parent chart\n * var chart3 = new BarChart(compositeChart);\n * @param {String|node|d3.selection|CompositeChart} parent - Any valid\n * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector}\n * specifying a dom block element such as a div; or a dom element or d3 selection. If the bar\n * chart is a sub-chart in a {@link CompositeChart Composite Chart} then pass in the parent\n * composite chart instance instead.\n * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.\n * Interaction with a chart will only trigger events and redraws within the chart's group.\n */\n constructor (parent, chartGroup) {\n super();\n\n this._gap = DEFAULT_GAP_BETWEEN_BARS;\n this._centerBar = false;\n this._alwaysUseRounding = false;\n\n this._barWidth = undefined;\n\n this.label(d => utils.printSingleValue(d.y0 + d.y), false);\n\n this.anchor(parent, chartGroup);\n }\n\n /**\n * Get or set the outer padding on an ordinal bar chart. This setting has no effect on non-ordinal charts.\n * Will pad the width by `padding * barWidth` on each side of the chart.\n * @param {Number} [padding=0.5]\n * @returns {Number|BarChart}\n */\n outerPadding (padding) {\n if (!arguments.length) {\n return this._outerRangeBandPadding();\n }\n return this._outerRangeBandPadding(padding);\n }\n\n rescale () {\n super.rescale();\n this._barWidth = undefined;\n return this;\n }\n\n render () {\n if (this.round() && this._centerBar && !this._alwaysUseRounding) {\n logger.warn('By default, brush rounding is disabled if bars are centered. ' +\n 'See dc.js bar chart API documentation for details.');\n }\n\n return super.render();\n }\n\n plotData () {\n let layers = this.chartBodyG().selectAll('g.stack')\n .data(this.data());\n\n this._calculateBarWidth();\n\n layers = layers\n .enter()\n .append('g')\n .attr('class', (d, i) => `stack _${i}`)\n .merge(layers);\n\n const last = layers.size() - 1;\n {\n const chart = this;\n layers.each(function (d, i) {\n const layer = select(this);\n\n chart._renderBars(layer, i, d);\n\n if (chart.renderLabel() && last === i) {\n chart._renderLabels(layer, i, d);\n }\n });\n }\n }\n\n _barHeight (d) {\n return utils.safeNumber(Math.abs(this.y()(d.y + d.y0) - this.y()(d.y0)));\n }\n\n _labelXPos (d) {\n let x = this.x()(d.x);\n if (!this._centerBar) {\n x += this._barWidth / 2;\n }\n if (this.isOrdinal() && this._gap !== undefined) {\n x += this._gap / 2;\n }\n return utils.safeNumber(x);\n }\n\n _labelYPos (d) {\n let y = this.y()(d.y + d.y0);\n\n if (d.y < 0) {\n y -= this._barHeight(d);\n }\n\n return utils.safeNumber(y - LABEL_PADDING);\n }\n\n _renderLabels (layer, layerIndex, data) {\n const labels = layer.selectAll('text.barLabel')\n .data(data.values, pluck('x'));\n\n const labelsEnterUpdate = labels\n .enter()\n .append('text')\n .attr('class', 'barLabel')\n .attr('text-anchor', 'middle')\n .attr('x', d => this._labelXPos(d))\n .attr('y', d => this._labelYPos(d))\n .merge(labels);\n\n if (this.isOrdinal()) {\n labelsEnterUpdate.on('click', d3compat.eventHandler(d => this.onClick(d)));\n labelsEnterUpdate.attr('cursor', 'pointer');\n }\n\n transition(labelsEnterUpdate, this.transitionDuration(), this.transitionDelay())\n .attr('x', d => this._labelXPos(d))\n .attr('y', d => this._labelYPos(d))\n .text(d => this.label()(d));\n\n transition(labels.exit(), this.transitionDuration(), this.transitionDelay())\n .attr('height', 0)\n .remove();\n }\n\n _barXPos (d) {\n let x = this.x()(d.x);\n if (this._centerBar) {\n x -= this._barWidth / 2;\n }\n if (this.isOrdinal() && this._gap !== undefined) {\n x += this._gap / 2;\n }\n return utils.safeNumber(x);\n }\n\n _renderBars (layer, layerIndex, data) {\n const bars = layer.selectAll('rect.bar')\n .data(data.values, pluck('x'));\n\n const enter = bars.enter()\n .append('rect')\n .attr('class', 'bar')\n .classed('dc-tabbable', this._keyboardAccessible)\n .attr('fill', pluck('data', this.getColor))\n .attr('x', d => this._barXPos(d))\n .attr('y', this.yAxisHeight())\n .attr('height', 0);\n\n const barsEnterUpdate = enter.merge(bars);\n\n if (this.renderTitle()) {\n enter.append('title').text(pluck('data', this.title(data.name)));\n }\n\n if (this.isOrdinal()) {\n barsEnterUpdate.on('click', d3compat.eventHandler(d => this.onClick(d)));\n }\n\n if (this._keyboardAccessible) {\n this._makeKeyboardAccessible(this.onClick);\n }\n\n transition(barsEnterUpdate, this.transitionDuration(), this.transitionDelay())\n .attr('x', d => this._barXPos(d))\n .attr('y', d => {\n let y = this.y()(d.y + d.y0);\n\n if (d.y < 0) {\n y -= this._barHeight(d);\n }\n\n return utils.safeNumber(y);\n })\n .attr('width', this._barWidth)\n .attr('height', d => this._barHeight(d))\n .attr('fill', pluck('data', this.getColor))\n .select('title').text(pluck('data', this.title(data.name)));\n\n transition(bars.exit(), this.transitionDuration(), this.transitionDelay())\n .attr('x', d => this.x()(d.x))\n .attr('width', this._barWidth * 0.9)\n .remove();\n }\n\n _calculateBarWidth () {\n if (this._barWidth === undefined) {\n const numberOfBars = this.xUnitCount();\n\n // please can't we always use rangeBands for bar charts?\n if (this.isOrdinal() && this._gap === undefined) {\n this._barWidth = Math.floor(this.x().bandwidth());\n } else if (this._gap) {\n this._barWidth = Math.floor((this.xAxisLength() - (numberOfBars - 1) * this._gap) / numberOfBars);\n } else {\n this._barWidth = Math.floor(this.xAxisLength() / (1 + this.barPadding()) / numberOfBars);\n }\n\n if (this._barWidth === Infinity || isNaN(this._barWidth) || this._barWidth < MIN_BAR_WIDTH) {\n this._barWidth = MIN_BAR_WIDTH;\n }\n }\n }\n\n fadeDeselectedArea (brushSelection) {\n const bars = this.chartBodyG().selectAll('rect.bar');\n\n if (this.isOrdinal()) {\n if (this.hasFilter()) {\n bars.classed(constants.SELECTED_CLASS, d => this.hasFilter(d.x));\n bars.classed(constants.DESELECTED_CLASS, d => !this.hasFilter(d.x));\n } else {\n bars.classed(constants.SELECTED_CLASS, false);\n bars.classed(constants.DESELECTED_CLASS, false);\n }\n } else if (this.brushOn() || this.parentBrushOn()) {\n if (!this.brushIsEmpty(brushSelection)) {\n const start = brushSelection[0];\n const end = brushSelection[1];\n\n bars.classed(constants.DESELECTED_CLASS, d => d.x < start || d.x >= end);\n } else {\n bars.classed(constants.DESELECTED_CLASS, false);\n }\n }\n }\n\n /**\n * Whether the bar chart will render each bar centered around the data position on the x-axis.\n * @param {Boolean} [centerBar=false]\n * @returns {Boolean|BarChart}\n */\n centerBar (centerBar) {\n if (!arguments.length) {\n return this._centerBar;\n }\n this._centerBar = centerBar;\n return this;\n }\n\n onClick (d) {\n super.onClick(d.data);\n }\n\n /**\n * Get or set the spacing between bars as a fraction of bar size. Valid values are between 0-1.\n * Setting this value will also remove any previously set {@link BarChart#gap gap}. See the\n * {@link https://github.com/d3/d3-scale/blob/master/README.md#scaleBand d3 docs}\n * for a visual description of how the padding is applied.\n * @param {Number} [barPadding=0]\n * @returns {Number|BarChart}\n */\n barPadding (barPadding) {\n if (!arguments.length) {\n return this._rangeBandPadding();\n }\n this._rangeBandPadding(barPadding);\n this._gap = undefined;\n return this;\n }\n\n _useOuterPadding () {\n return this._gap === undefined;\n }\n\n /**\n * Manually set fixed gap (in px) between bars instead of relying on the default auto-generated\n * gap. By default the bar chart implementation will calculate and set the gap automatically\n * based on the number of data points and the length of the x axis.\n * @param {Number} [gap=2]\n * @returns {Number|BarChart}\n */\n gap (gap) {\n if (!arguments.length) {\n return this._gap;\n }\n this._gap = gap;\n return this;\n }\n\n extendBrush (brushSelection) {\n if (brushSelection && this.round() && (!this._centerBar || this._alwaysUseRounding)) {\n brushSelection[0] = this.round()(brushSelection[0]);\n brushSelection[1] = this.round()(brushSelection[1]);\n }\n return brushSelection;\n }\n\n /**\n * Set or get whether rounding is enabled when bars are centered. If false, using\n * rounding with centered bars will result in a warning and rounding will be ignored. This flag\n * has no effect if bars are not {@link BarChart#centerBar centered}.\n * When using standard d3.js rounding methods, the brush often doesn't align correctly with\n * centered bars since the bars are offset. The rounding function must add an offset to\n * compensate, such as in the following example.\n * @example\n * chart.round(function(n) { return Math.floor(n) + 0.5; });\n * @param {Boolean} [alwaysUseRounding=false]\n * @returns {Boolean|BarChart}\n */\n alwaysUseRounding (alwaysUseRounding) {\n if (!arguments.length) {\n return this._alwaysUseRounding;\n }\n this._alwaysUseRounding = alwaysUseRounding;\n return this;\n }\n\n legendHighlight (d) {\n const colorFilter = (color, inv) => function () {\n const item = select(this);\n const match = item.attr('fill') === color;\n return inv ? !match : match;\n };\n\n if (!this.isLegendableHidden(d)) {\n this.g().selectAll('rect.bar')\n .classed('highlight', colorFilter(d.color))\n .classed('fadeout', colorFilter(d.color, true));\n }\n }\n\n legendReset () {\n this.g().selectAll('rect.bar')\n .classed('highlight', false)\n .classed('fadeout', false);\n }\n\n xAxisMax () {\n let max = super.xAxisMax();\n if ('resolution' in this.xUnits()) {\n const res = this.xUnits().resolution;\n max += res;\n }\n return max;\n }\n}\n\nexport const barChart = (parent, chartGroup) => new BarChart(parent, chartGroup);\n","import {scaleBand} from 'd3-scale';\nimport {select} from 'd3-selection';\nimport {min, max} from 'd3-array';\n\nimport {d3Box} from '../base/d3.box'\nimport {CoordinateGridMixin} from '../base/coordinate-grid-mixin';\nimport {transition} from '../core/core';\nimport {units} from '../core/units';\nimport {utils} from '../core/utils';\nimport {d3compat} from '../core/config';\n\n// Returns a function to compute the interquartile range.\nfunction defaultWhiskersIQR (k) {\n return d => {\n const q1 = d.quartiles[0];\n const q3 = d.quartiles[2];\n const iqr = (q3 - q1) * k;\n\n let i = -1;\n let j = d.length;\n\n do {\n ++i;\n } while (d[i] < q1 - iqr);\n\n do {\n --j;\n } while (d[j] > q3 + iqr);\n\n return [i, j];\n };\n}\n\n/**\n * A box plot is a chart that depicts numerical data via their quartile ranges.\n *\n * Examples:\n * - {@link http://dc-js.github.io/dc.js/examples/boxplot-basic.html Boxplot Basic example}\n * - {@link http://dc-js.github.io/dc.js/examples/boxplot-enhanced.html Boxplot Enhanced example}\n * - {@link http://dc-js.github.io/dc.js/examples/boxplot-render-data.html Boxplot Render Data example}\n * - {@link http://dc-js.github.io/dc.js/examples/boxplot-time.html Boxplot time example}\n * @mixes CoordinateGridMixin\n */\nexport class BoxPlot extends CoordinateGridMixin {\n /**\n * Create a Box Plot.\n *\n * @example\n * // create a box plot under #chart-container1 element using the default global chart group\n * var boxPlot1 = new BoxPlot('#chart-container1');\n * // create a box plot under #chart-container2 element using chart group A\n * var boxPlot2 = new BoxPlot('#chart-container2', 'chartGroupA');\n * @param {String|node|d3.selection} parent - Any valid\n * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying\n * a dom block element such as a div; or a dom element or d3 selection.\n * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.\n * Interaction with a chart will only trigger events and redraws within the chart's group.\n */\n constructor (parent, chartGroup) {\n super();\n\n this._whiskerIqrFactor = 1.5;\n this._whiskersIqr = defaultWhiskersIQR;\n this._whiskers = this._whiskersIqr(this._whiskerIqrFactor);\n\n this._box = d3Box();\n this._tickFormat = null;\n this._renderDataPoints = false;\n this._dataOpacity = 0.3;\n this._dataWidthPortion = 0.8;\n this._showOutliers = true;\n this._boldOutlier = false;\n\n // Used in yAxisMin and yAxisMax to add padding in pixel coordinates\n // so the min and max data points/whiskers are within the chart\n this._yRangePadding = 8;\n\n this._boxWidth = (innerChartWidth, xUnits) => {\n if (this.isOrdinal()) {\n return this.x().bandwidth();\n } else {\n return innerChartWidth / (1 + this.boxPadding()) / xUnits;\n }\n };\n\n // default to ordinal\n this.x(scaleBand());\n this.xUnits(units.ordinal);\n\n // valueAccessor should return an array of values that can be coerced into numbers\n // or if data is overloaded for a static array of arrays, it should be `Number`.\n // Empty arrays are not included.\n this.data(group => group.all().map(d => {\n d.map = accessor => accessor.call(d, d);\n return d;\n }).filter(d => {\n const values = this.valueAccessor()(d);\n return values.length !== 0;\n }));\n\n this.boxPadding(0.8);\n this.outerPadding(0.5);\n\n this.anchor(parent, chartGroup);\n }\n\n /**\n * Get or set the spacing between boxes as a fraction of box size. Valid values are within 0-1.\n * See the {@link https://github.com/d3/d3-scale/blob/master/README.md#scaleBand d3 docs}\n * for a visual description of how the padding is applied.\n * @see {@link https://github.com/d3/d3-scale/blob/master/README.md#scaleBand d3.scaleBand}\n * @param {Number} [padding=0.8]\n * @returns {Number|BoxPlot}\n */\n boxPadding (padding) {\n if (!arguments.length) {\n return this._rangeBandPadding();\n }\n return this._rangeBandPadding(padding);\n }\n\n /**\n * Get or set the outer padding on an ordinal box chart. This setting has no effect on non-ordinal charts\n * or on charts with a custom {@link BoxPlot#boxWidth .boxWidth}. Will pad the width by\n * `padding * barWidth` on each side of the chart.\n * @param {Number} [padding=0.5]\n * @returns {Number|BoxPlot}\n */\n outerPadding (padding) {\n if (!arguments.length) {\n return this._outerRangeBandPadding();\n }\n return this._outerRangeBandPadding(padding);\n }\n\n /**\n * Get or set the numerical width of the boxplot box. The width may also be a function taking as\n * parameters the chart width excluding the right and left margins, as well as the number of x\n * units.\n * @example\n * // Using numerical parameter\n * chart.boxWidth(10);\n * // Using function\n * chart.boxWidth((innerChartWidth, xUnits) { ... });\n * @param {Number|Function} [boxWidth=0.5]\n * @returns {Number|Function|BoxPlot}\n */\n boxWidth (boxWidth) {\n if (!arguments.length) {\n return this._boxWidth;\n }\n this._boxWidth = typeof boxWidth === 'function' ? boxWidth : utils.constant(boxWidth);\n return this;\n }\n\n _boxTransform (d, i) {\n const xOffset = this.x()(this.keyAccessor()(d, i));\n return `translate(${xOffset}, 0)`;\n }\n\n _preprocessData () {\n if (this.elasticX()) {\n this.x().domain([]);\n }\n }\n\n plotData () {\n this._calculatedBoxWidth = this._boxWidth(this.effectiveWidth(), this.xUnitCount());\n\n this._box.whiskers(this._whiskers)\n .width(this._calculatedBoxWidth)\n .height(this.effectiveHeight())\n .value(this.valueAccessor())\n .domain(this.y().domain())\n .duration(this.transitionDuration())\n .tickFormat(this._tickFormat)\n .renderDataPoints(this._renderDataPoints)\n .dataOpacity(this._dataOpacity)\n .dataWidthPortion(this._dataWidthPortion)\n .renderTitle(this.renderTitle())\n .showOutliers(this._showOutliers)\n .boldOutlier(this._boldOutlier);\n\n const boxesG = this.chartBodyG().selectAll('g.box').data(this.data(), this.keyAccessor());\n\n const boxesGEnterUpdate = this._renderBoxes(boxesG);\n this._updateBoxes(boxesGEnterUpdate);\n this._removeBoxes(boxesG);\n\n this.fadeDeselectedArea(this.filter());\n }\n\n _renderBoxes (boxesG) {\n const boxesGEnter = boxesG.enter().append('g');\n\n boxesGEnter\n .attr('class', 'box')\n .classed('dc-tabbable', this._keyboardAccessible)\n .attr('transform', (d, i) => this._boxTransform(d, i))\n .call(this._box)\n .on('click', d3compat.eventHandler(d => {\n this.filter(this.keyAccessor()(d));\n this.redrawGroup();\n }))\n .selectAll('circle')\n .classed('dc-tabbable', this._keyboardAccessible);\n\n if (this._keyboardAccessible) {\n this._makeKeyboardAccessible(this.onClick);\n }\n\n return boxesGEnter.merge(boxesG);\n }\n\n _updateBoxes (boxesG) {\n const chart = this;\n transition(boxesG, this.transitionDuration(), this.transitionDelay())\n .attr('transform', (d, i) => this._boxTransform(d, i))\n .call(this._box)\n .each(function (d) {\n const color = chart.getColor(d, 0);\n select(this).select('rect.box').attr('fill', color);\n select(this).selectAll('circle.data').attr('fill', color);\n });\n }\n\n _removeBoxes (boxesG) {\n boxesG.exit().remove().call(this._box);\n }\n\n _minDataValue () {\n return min(this.data(), e => min(this.valueAccessor()(e)));\n }\n\n _maxDataValue () {\n return max(this.data(), e => max(this.valueAccessor()(e)));\n }\n\n _yAxisRangeRatio () {\n return ((this._maxDataValue() - this._minDataValue()) / this.effectiveHeight());\n }\n\n onClick (d) {\n this.filter(this.keyAccessor()(d));\n this.redrawGroup();\n }\n\n fadeDeselectedArea (brushSelection) {\n const chart = this;\n if (this.hasFilter()) {\n if (this.isOrdinal()) {\n this.g().selectAll('g.box').each(function (d) {\n if (chart.isSelectedNode(d)) {\n chart.highlightSelected(this);\n } else {\n chart.fadeDeselected(this);\n }\n });\n } else {\n if (!(this.brushOn() || this.parentBrushOn())) {\n return;\n }\n const start = brushSelection[0];\n const end = brushSelection[1];\n this.g().selectAll('g.box').each(function (d) {\n const key = chart.keyAccessor()(d);\n if (key < start || key >= end) {\n chart.fadeDeselected(this);\n } else {\n chart.highlightSelected(this);\n }\n });\n }\n } else {\n this.g().selectAll('g.box').each(function () {\n chart.resetHighlight(this);\n });\n }\n }\n\n isSelectedNode (d) {\n return this.hasFilter(this.keyAccessor()(d));\n }\n\n yAxisMin () {\n const padding = this._yRangePadding * this._yAxisRangeRatio();\n return utils.subtract(this._minDataValue() - padding, this.yAxisPadding());\n }\n\n yAxisMax () {\n const padding = this._yRangePadding * this._yAxisRangeRatio();\n return utils.add(this._maxDataValue() + padding, this.yAxisPadding());\n }\n\n /**\n * Get or set the numerical format of the boxplot median, whiskers and quartile labels. Defaults\n * to integer formatting.\n * @example\n * // format ticks to 2 decimal places\n * chart.tickFormat(d3.format('.2f'));\n * @param {Function} [tickFormat]\n * @returns {Number|Function|BoxPlot}\n */\n tickFormat (tickFormat) {\n if (!arguments.length) {\n return this._tickFormat;\n }\n this._tickFormat = tickFormat;\n return this;\n }\n\n /**\n * Get or set the amount of padding to add, in pixel coordinates, to the top and\n * bottom of the chart to accommodate box/whisker labels.\n * @example\n * // allow more space for a bigger whisker font\n * chart.yRangePadding(12);\n * @param {Function} [yRangePadding = 8]\n * @returns {Number|Function|BoxPlot}\n */\n yRangePadding (yRangePadding) {\n if (!arguments.length) {\n return this._yRangePadding;\n }\n this._yRangePadding = yRangePadding;\n return this;\n }\n\n /**\n * Get or set whether individual data points will be rendered.\n * @example\n * // Enable rendering of individual data points\n * chart.renderDataPoints(true);\n * @param {Boolean} [show=false]\n * @returns {Boolean|BoxPlot}\n */\n renderDataPoints (show) {\n if (!arguments.length) {\n return this._renderDataPoints;\n }\n this._renderDataPoints = show;\n return this;\n }\n\n /**\n * Get or set the opacity when rendering data.\n * @example\n * // If individual data points are rendered increase the opacity.\n * chart.dataOpacity(0.7);\n * @param {Number} [opacity=0.3]\n * @returns {Number|BoxPlot}\n */\n dataOpacity (opacity) {\n if (!arguments.length) {\n return this._dataOpacity;\n }\n this._dataOpacity = opacity;\n return this;\n }\n\n /**\n * Get or set the portion of the width of the box to show data points.\n * @example\n * // If individual data points are rendered increase the data box.\n * chart.dataWidthPortion(0.9);\n * @param {Number} [percentage=0.8]\n * @returns {Number|BoxPlot}\n */\n dataWidthPortion (percentage) {\n if (!arguments.length) {\n return this._dataWidthPortion;\n }\n this._dataWidthPortion = percentage;\n return this;\n }\n\n /**\n * Get or set whether outliers will be rendered.\n * @example\n * // Disable rendering of outliers\n * chart.showOutliers(false);\n * @param {Boolean} [show=true]\n * @returns {Boolean|BoxPlot}\n */\n showOutliers (show) {\n if (!arguments.length) {\n return this._showOutliers;\n }\n this._showOutliers = show;\n return this;\n }\n\n /**\n * Get or set whether outliers will be drawn bold.\n * @example\n * // If outliers are rendered display as bold\n * chart.boldOutlier(true);\n * @param {Boolean} [show=false]\n * @returns {Boolean|BoxPlot}\n */\n boldOutlier (show) {\n if (!arguments.length) {\n return this._boldOutlier;\n }\n this._boldOutlier = show;\n return this;\n }\n}\n\nexport const boxPlot = (parent, chartGroup) => new BoxPlot(parent, chartGroup);\n","import {BubbleMixin} from '../base/bubble-mixin';\nimport {CoordinateGridMixin} from '../base/coordinate-grid-mixin';\nimport {transition} from '../core/core';\nimport {d3compat} from '../core/config';\n\n/**\n * A concrete implementation of a general purpose bubble chart that allows data visualization using the\n * following dimensions:\n * - x axis position\n * - y axis position\n * - bubble radius\n * - color\n *\n * Examples:\n * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}\n * - {@link http://dc-js.github.com/dc.js/vc/index.html US Venture Capital Landscape 2011}\n * @mixes BubbleMixin\n * @mixes CoordinateGridMixin\n */\nexport class BubbleChart extends BubbleMixin(CoordinateGridMixin) {\n /**\n * Create a Bubble Chart.\n *\n * @example\n * // create a bubble chart under #chart-container1 element using the default global chart group\n * var bubbleChart1 = new BubbleChart('#chart-container1');\n * // create a bubble chart under #chart-container2 element using chart group A\n * var bubbleChart2 = new BubbleChart('#chart-container2', 'chartGroupA');\n * @param {String|node|d3.selection} parent - Any valid\n * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying\n * a dom block element such as a div; or a dom element or d3 selection.\n * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.\n * Interaction with a chart will only trigger events and redraws within the chart's group.\n */\n constructor (parent, chartGroup) {\n super();\n\n this.transitionDuration(750);\n\n this.transitionDelay(0);\n\n this.anchor(parent, chartGroup);\n }\n\n _bubbleLocator (d) {\n return `translate(${this._bubbleX(d)},${this._bubbleY(d)})`;\n }\n\n plotData () {\n this.calculateRadiusDomain();\n this.r().range([this.MIN_RADIUS, this.xAxisLength() * this.maxBubbleRelativeSize()]);\n\n const data = this.data();\n let bubbleG = this.chartBodyG().selectAll(`g.${this.BUBBLE_NODE_CLASS}`)\n .data(data, d => d.key);\n if (this.sortBubbleSize() || this.keyboardAccessible()) {\n // update dom order based on sort\n bubbleG.order();\n }\n\n this._removeNodes(bubbleG);\n\n bubbleG = this._renderNodes(bubbleG);\n\n this._updateNodes(bubbleG);\n\n this.fadeDeselectedArea(this.filter());\n }\n\n _renderNodes (bubbleG) {\n const bubbleGEnter = bubbleG.enter().append('g');\n\n bubbleGEnter\n .attr('class', this.BUBBLE_NODE_CLASS)\n .attr('transform', d => this._bubbleLocator(d))\n .append('circle').attr('class', (d, i) => `${this.BUBBLE_CLASS} _${i}`)\n .on('click', d3compat.eventHandler(d => this.onClick(d)))\n .classed('dc-tabbable', this._keyboardAccessible)\n .attr('fill', this.getColor)\n .attr('r', 0);\n\n bubbleG = bubbleGEnter.merge(bubbleG);\n\n transition(bubbleG, this.transitionDuration(), this.transitionDelay())\n .select(`circle.${this.BUBBLE_CLASS}`)\n .attr('r', d => this.bubbleR(d))\n .attr('opacity', d => (this.bubbleR(d) > 0) ? 1 : 0);\n\n if (this._keyboardAccessible) {\n this._makeKeyboardAccessible(this.onClick);\n }\n\n this._doRenderLabel(bubbleGEnter);\n\n this._doRenderTitles(bubbleGEnter);\n\n return bubbleG;\n }\n\n _updateNodes (bubbleG) {\n transition(bubbleG, this.transitionDuration(), this.transitionDelay())\n .attr('transform', d => this._bubbleLocator(d))\n .select(`circle.${this.BUBBLE_CLASS}`)\n .attr('fill', this.getColor)\n .attr('r', d => this.bubbleR(d))\n .attr('opacity', d => (this.bubbleR(d) > 0) ? 1 : 0);\n\n this.doUpdateLabels(bubbleG);\n this.doUpdateTitles(bubbleG);\n }\n\n _removeNodes (bubbleG) {\n bubbleG.exit().remove();\n }\n\n _bubbleX (d) {\n let x = this.x()(this.keyAccessor()(d));\n if (isNaN(x) || !isFinite(x)) {\n x = 0;\n }\n return x;\n }\n\n _bubbleY (d) {\n let y = this.y()(this.valueAccessor()(d));\n if (isNaN(y) || !isFinite(y)) {\n y = 0;\n }\n return y;\n }\n\n renderBrush () {\n // override default x axis brush from parent chart\n }\n\n redrawBrush (brushSelection, doTransition) {\n // override default x axis brush from parent chart\n this.fadeDeselectedArea(brushSelection);\n }\n}\n\nexport const bubbleChart = (parent, chartGroup) => new BubbleChart(parent, chartGroup);\n","import {BaseMixin} from '../base/base-mixin';\nimport {BubbleMixin} from '../base/bubble-mixin';\nimport {transition} from '../core/core';\nimport {constants} from '../core/constants';\nimport {utils} from '../core/utils';\nimport {d3compat} from '../core/config';\n\nconst BUBBLE_OVERLAY_CLASS = 'bubble-overlay';\nconst BUBBLE_NODE_CLASS = 'node';\nconst BUBBLE_CLASS = 'bubble';\n\n/**\n * The bubble overlay chart is quite different from the typical bubble chart. With the bubble overlay\n * chart you can arbitrarily place bubbles on an existing svg or bitmap image, thus changing the\n * typical x and y positioning while retaining the capability to visualize data using bubble radius\n * and coloring.\n *\n * Examples:\n * - {@link http://dc-js.github.com/dc.js/crime/index.html Canadian City Crime Stats}\n * @mixes BubbleMixin\n * @mixes BaseMixin\n */\nexport class BubbleOverlay extends BubbleMixin(BaseMixin) {\n /**\n * Create a Bubble Overlay.\n *\n * @example\n * // create a bubble overlay chart on top of the '#chart-container1 svg' element using the default global chart group\n * var bubbleChart1 = BubbleOverlayChart('#chart-container1').svg(d3.select('#chart-container1 svg'));\n * // create a bubble overlay chart on top of the '#chart-container2 svg' element using chart group A\n * var bubbleChart2 = new CompositeChart('#chart-container2', 'chartGroupA').svg(d3.select('#chart-container2 svg'));\n * @param {String|node|d3.selection} parent - Any valid\n * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying\n * a dom block element such as a div; or a dom element or d3 selection.\n * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.\n * Interaction with a chart will only trigger events and redraws within the chart's group.\n */\n constructor (parent, chartGroup) {\n super();\n\n /**\n * **mandatory**\n *\n * Set the underlying svg image element. Unlike other dc charts this chart will not generate a svg\n * element; therefore the bubble overlay chart will not work if this function is not invoked. If the\n * underlying image is a bitmap, then an empty svg will need to be created on top of the image.\n * @example\n * // set up underlying svg element\n * chart.svg(d3.select('#chart svg'));\n * @param {SVGElement|d3.selection} [imageElement]\n * @returns {BubbleOverlay}\n */\n this._g = undefined;\n this._points = [];\n this._keyboardAccessible = false;\n\n this.transitionDuration(750);\n\n this.transitionDelay(0);\n\n this.radiusValueAccessor(d => d.value);\n\n this.anchor(parent, chartGroup);\n }\n\n /**\n * **mandatory**\n *\n * Set up a data point on the overlay. The name of a data point should match a specific 'key' among\n * data groups generated using keyAccessor. If a match is found (point name <-> data group key)\n * then a bubble will be generated at the position specified by the function. x and y\n * value specified here are relative to the underlying svg.\n * @param {String} name\n * @param {Number} x\n * @param {Number} y\n * @returns {BubbleOverlay}\n */\n point (name, x, y) {\n this._points.push({name: name, x: x, y: y});\n return this;\n }\n\n _doRender () {\n this._g = this._initOverlayG();\n\n this.r().range([this.MIN_RADIUS, this.width() * this.maxBubbleRelativeSize()]);\n\n this._initializeBubbles();\n\n this.fadeDeselectedArea(this.filter());\n\n return this;\n }\n\n _initOverlayG () {\n this._g = this.select(`g.${BUBBLE_OVERLAY_CLASS}`);\n if (this._g.empty()) {\n this._g = this.svg().append('g').attr('class', BUBBLE_OVERLAY_CLASS);\n }\n return this._g;\n }\n\n _initializeBubbles () {\n const data = this._mapData();\n this.calculateRadiusDomain();\n\n this._points.forEach(point => {\n const nodeG = this._getNodeG(point, data);\n\n let circle = nodeG.select(`circle.${BUBBLE_CLASS}`);\n\n if (circle.empty()) {\n circle = nodeG.append('circle')\n .attr('class', BUBBLE_CLASS)\n .classed('dc-tabbable', this._keyboardAccessible)\n .attr('r', 0)\n .attr('fill', this.getColor)\n .on('click', d3compat.eventHandler(d => this.onClick(d)));\n }\n\n if (this._keyboardAccessible) {\n this._makeKeyboardAccessible(this.onClick);\n }\n\n transition(circle, this.transitionDuration(), this.transitionDelay())\n .attr('r', d => this.bubbleR(d));\n\n this._doRenderLabel(nodeG);\n\n this._doRenderTitles(nodeG);\n });\n }\n\n _mapData () {\n const data = {};\n this.data().forEach(datum => {\n data[this.keyAccessor()(datum)] = datum;\n });\n return data;\n }\n\n _getNodeG (point, data) {\n const bubbleNodeClass = `${BUBBLE_NODE_CLASS} ${utils.nameToId(point.name)}`;\n\n let nodeG = this._g.select(`g.${utils.nameToId(point.name)}`);\n\n if (nodeG.empty()) {\n nodeG = this._g.append('g')\n .attr('class', bubbleNodeClass)\n .attr('transform', `translate(${point.x},${point.y})`);\n }\n\n nodeG.datum(data[point.name]);\n\n return nodeG;\n }\n\n _doRedraw () {\n this._updateBubbles();\n\n this.fadeDeselectedArea(this.filter());\n\n return this;\n }\n\n _updateBubbles () {\n const data = this._mapData();\n this.calculateRadiusDomain();\n\n this._points.forEach(point => {\n const nodeG = this._getNodeG(point, data);\n\n const circle = nodeG.select(`circle.${BUBBLE_CLASS}`);\n\n transition(circle, this.transitionDuration(), this.transitionDelay())\n .attr('r', d => this.bubbleR(d))\n .attr('fill', this.getColor);\n\n this.doUpdateLabels(nodeG);\n\n this.doUpdateTitles(nodeG);\n });\n }\n\n debug (flag) {\n if (flag) {\n let debugG = this.select(`g.${constants.DEBUG_GROUP_CLASS}`);\n\n if (debugG.empty()) {\n debugG = this.svg()\n .append('g')\n .attr('class', constants.DEBUG_GROUP_CLASS);\n }\n\n const debugText = debugG.append('text')\n .attr('x', 10)\n .attr('y', 20);\n\n debugG\n .append('rect')\n .attr('width', this.width())\n .attr('height', this.height())\n .on('mousemove', d3compat.eventHandler((d, evt) => {\n const position = d3compat.pointer(evt, debugG.node());\n const msg = `${position[0]}, ${position[1]}`;\n debugText.text(msg);\n }));\n } else {\n this.selectAll('.debug').remove();\n }\n\n return this;\n }\n\n}\n\nexport const bubbleOverlay = (parent, chartGroup) => new BubbleOverlay(parent, chartGroup);\n","import {select} from 'd3-selection';\n\nimport {events} from '../core/events';\nimport {BaseMixin} from '../base/base-mixin';\nimport {utils} from '../core/utils'\nimport {d3compat} from '../core/config';\n\nconst GROUP_CSS_CLASS = 'dc-cbox-group';\nconst ITEM_CSS_CLASS = 'dc-cbox-item';\n\n/**\n * The CboxMenu is a simple widget designed to filter a dimension by\n * selecting option(s) from a set of HTML `` elements. The menu can be\n * made into a set of radio buttons (single select) or checkboxes (multiple).\n * @mixes BaseMixin\n */\nexport class CboxMenu extends BaseMixin {\n /**\n * Create a Cbox Menu.\n *\n * @example\n * // create a cboxMenu under #cbox-container using the default global chart group\n * var cbox = new CboxMenu('#cbox-container')\n * .dimension(states)\n * .group(stateGroup);\n * // the option text can be set via the title() function\n * // by default the option text is '`key`: `value`'\n * cbox.title(function (d){\n * return 'STATE: ' + d.key;\n * })\n * @param {String|node|d3.selection|CompositeChart} parent - Any valid\n * [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying\n * a dom block element such as a div; or a dom element or d3 selection.\n * @param {String} [chartGroup] - The name of the chart group this widget should be placed in.\n * Interaction with the widget will only trigger events and redraws within its group.\n */\n constructor (parent, chartGroup) {\n super();\n\n this._cbox = undefined;\n this._promptText = 'Select all';\n this._multiple = false;\n this._inputType = 'radio';\n this._promptValue = null;\n\n this._uniqueId = utils.uniqueId();\n\n this.data(group => group.all().filter(this._filterDisplayed));\n\n // There is an accessor for this attribute, initialized with default value\n this._filterDisplayed = d => this.valueAccessor()(d) > 0;\n\n this._order = (a, b) => {\n if (this.keyAccessor()(a) > this.keyAccessor()(b)) {\n return 1;\n }\n if (this.keyAccessor()(a) < this.keyAccessor()(b)) {\n return -1;\n }\n return 0;\n };\n\n this.anchor(parent, chartGroup);\n }\n\n _doRender () {\n return this._doRedraw();\n }\n\n _doRedraw () {\n this.select('ul').remove();\n this._cbox = this.root()\n .append('ul')\n .classed(GROUP_CSS_CLASS, true);\n this._renderOptions();\n\n if (this.hasFilter() && this._multiple) {\n this._cbox.selectAll('input')\n // adding `false` avoids failing test cases in phantomjs\n .property('checked', d => d && this.filters().indexOf(String(this.keyAccessor()(d))) >= 0 || false);\n } else if (this.hasFilter()) {\n this._cbox.selectAll('input')\n .property('checked', d => {\n if (!d) {\n return false;\n }\n return this.keyAccessor()(d) === this.filter();\n });\n }\n return this;\n }\n\n _renderOptions () {\n let options = this._cbox\n .selectAll(`li.${ITEM_CSS_CLASS}`)\n .data(this.data(), d => this.keyAccessor()(d));\n\n options.exit().remove();\n\n options = options.enter()\n .append('li')\n .classed(ITEM_CSS_CLASS, true)\n .merge(options);\n\n options\n .append('input')\n .attr('type', this._inputType)\n .attr('value', d => this.keyAccessor()(d))\n .attr('name', `domain_${this._uniqueId}`)\n .attr('id', (d, i) => `input_${this._uniqueId}_${i}`);\n options\n .append('label')\n .attr('for', (d, i) => `input_${this._uniqueId}_${i}`)\n .text(this.title());\n\n const chart = this;\n // 'all' option\n if (this._multiple) {\n this._cbox\n .append('li')\n .append('input')\n .attr('type', 'reset')\n .text(this._promptText)\n .on('click', d3compat.eventHandler(function (d, evt) {\n return chart._onChange(d, evt, this);\n }));\n } else {\n const li = this._cbox.append('li');\n li.append('input')\n .attr('type', this._inputType)\n .attr('value', this._promptValue)\n .attr('name', `domain_${this._uniqueId}`)\n .attr('id', (d, i) => `input_${this._uniqueId}_all`)\n .property('checked', true);\n li.append('label')\n .attr('for', (d, i) => `input_${this._uniqueId}_all`)\n .text(this._promptText);\n }\n\n this._cbox\n .selectAll(`li.${ITEM_CSS_CLASS}`)\n .sort(this._order);\n\n this._cbox.on('change', d3compat.eventHandler(function (d, evt) {\n return chart._onChange(d, evt, this);\n }));\n return options;\n }\n\n _onChange (d, evt, element) {\n let values;\n\n const target = select(evt.target);\n let options;\n\n if (!target.datum()) {\n values = this._promptValue || null;\n } else {\n options = select(element).selectAll('input')\n .filter(function (o) {\n if (o) {\n return this.checked;\n }\n });\n values = options.nodes().map(option => option.value);\n // check if only prompt option is selected\n if (!this._multiple && values.length === 1) {\n values = values[0];\n }\n }\n this.onChange(values);\n }\n\n onChange (val) {\n if (val && this._multiple) {\n this.replaceFilter([val]);\n } else if (val) {\n this.replaceFilter(val);\n } else {\n this.filterAll();\n }\n events.trigger(() => {\n this.redrawGroup();\n });\n }\n\n /**\n * Get or set the function that controls the ordering of option tags in the\n * cbox menu. By default options are ordered by the group key in ascending\n * order.\n * @param {Function} [order]\n * @returns {Function|CboxMenu}\n * @example\n * // order by the group's value\n * chart.order(function (a,b) {\n * return a.value > b.value ? 1 : b.value > a.value ? -1 : 0;\n * });\n */\n order (order) {\n if (!arguments.length) {\n return this._order;\n }\n this._order = order;\n return this;\n }\n\n /**\n * Get or set the text displayed in the options used to prompt selection.\n * @param {String} [promptText='Select all']\n * @returns {String|CboxMenu}\n * @example\n * chart.promptText('All states');\n */\n promptText (promptText) {\n if (!arguments.length) {\n return this._promptText;\n }\n this._promptText = promptText;\n return this;\n }\n\n /**\n * Get or set the function that filters options prior to display. By default options\n * with a value of < 1 are not displayed.\n * @param {function} [filterDisplayed]\n * @returns {Function|CboxMenu}\n * @example\n * // display all options override the `filterDisplayed` function:\n * chart.filterDisplayed(function () {\n * return true;\n * });\n */\n filterDisplayed (filterDisplayed) {\n if (!arguments.length) {\n return this._filterDisplayed;\n }\n this._filterDisplayed = filterDisplayed;\n return this;\n }\n\n /**\n * Controls the type of input element. Setting it to true converts\n * the HTML `input` tags from radio buttons to checkboxes.\n * @param {boolean} [multiple=false]\n * @returns {Boolean|CboxMenu}\n * @example\n * chart.multiple(true);\n */\n multiple (multiple) {\n if (!arguments.length) {\n return this._multiple;\n }\n this._multiple = multiple;\n if (this._multiple) {\n this._inputType = 'checkbox';\n } else {\n this._inputType = 'radio';\n }\n return this;\n }\n\n /**\n * Controls the default value to be used for\n * [dimension.filter](https://github.com/crossfilter/crossfilter/wiki/API-Reference#dimension_filter)\n * when only the prompt value is selected. If `null` (the default), no filtering will occur when\n * just the prompt is selected.\n * @param {?*} [promptValue=null]\n * @returns {*|CboxMenu}\n */\n promptValue (promptValue) {\n if (!arguments.length) {\n return this._promptValue;\n }\n this._promptValue = promptValue;\n\n return this;\n }\n}\n\nexport const cboxMenu = (parent, chartGroup) => new CboxMenu(parent, chartGroup);\n","import {min, max} from 'd3-array';\nimport {scaleLinear} from 'd3-scale';\nimport {axisRight} from 'd3-axis';\n\nimport {utils} from '../core/utils';\nimport {CoordinateGridMixin} from '../base/coordinate-grid-mixin';\n\nconst SUB_CHART_CLASS = 'sub';\nconst DEFAULT_RIGHT_Y_AXIS_LABEL_PADDING = 12;\n\n/**\n * Composite charts are a special kind of chart that render multiple charts on the same Coordinate\n * Grid. You can overlay (compose) different bar/line/area charts in a single composite chart to\n * achieve some quite flexible charting effects.\n * @mixes CoordinateGridMixin\n */\nexport class CompositeChart extends CoordinateGridMixin {\n /**\n * Create a Composite Chart.\n * @example\n * // create a composite chart under #chart-container1 element using the default global chart group\n * var compositeChart1 = new CompositeChart('#chart-container1');\n * // create a composite chart under #chart-container2 element using chart group A\n * var compositeChart2 = new CompositeChart('#chart-container2', 'chartGroupA');\n * @param {String|node|d3.selection} parent - Any valid\n * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying\n * a dom block element such as a div; or a dom element or d3 selection.\n * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.\n * Interaction with a chart will only trigger events and redraws within the chart's group.\n */\n constructor (parent, chartGroup) {\n super();\n\n this._children = [];\n\n this._childOptions = {};\n\n this._shareColors = false;\n this._shareTitle = true;\n this._alignYAxes = false;\n\n this._rightYAxis = axisRight();\n this._rightYAxisLabel = 0;\n this._rightYAxisLabelPadding = DEFAULT_RIGHT_Y_AXIS_LABEL_PADDING;\n this._rightY = undefined;\n this._rightAxisGridLines = false;\n\n this._mandatoryAttributes([]);\n this.transitionDuration(500);\n this.transitionDelay(0);\n\n this.on('filtered.dcjs-composite-chart', chart => {\n // Propagate the filters onto the children\n // Notice that on children the call is .replaceFilter and not .filter\n // the reason is that _chart.filter() returns the entire current set of filters not just the last added one\n for (let i = 0; i < this._children.length; ++i) {\n this._children[i].replaceFilter(this.filter());\n }\n });\n\n this.anchor(parent, chartGroup);\n }\n\n _generateG () {\n const g = super._generateG();\n\n for (let i = 0; i < this._children.length; ++i) {\n const child = this._children[i];\n\n this._generateChildG(child, i);\n\n if (!child.dimension()) {\n child.dimension(this.dimension());\n }\n if (!child.group()) {\n child.group(this.group());\n }\n\n child.chartGroup(this.chartGroup());\n child.svg(this.svg());\n child.xUnits(this.xUnits());\n child.transitionDuration(this.transitionDuration(), this.transitionDelay());\n child.parentBrushOn(this.brushOn());\n child.brushOn(false);\n child.renderTitle(this.renderTitle());\n child.elasticX(this.elasticX());\n }\n\n return g;\n }\n\n rescale () {\n super.rescale();\n\n this._children.forEach(child => {\n child.rescale();\n });\n\n return this;\n }\n\n resizing (resizing) {\n if (!arguments.length) {\n return super.resizing();\n }\n super.resizing(resizing);\n\n this._children.forEach(child => {\n child.resizing(resizing);\n });\n\n return this;\n }\n\n _prepareYAxis () {\n const left = (this._leftYAxisChildren().length !== 0);\n const right = (this._rightYAxisChildren().length !== 0);\n const ranges = this._calculateYAxisRanges(left, right);\n\n if (left) {\n this._prepareLeftYAxis(ranges);\n }\n if (right) {\n this._prepareRightYAxis(ranges);\n }\n\n if (this._leftYAxisChildren().length > 0 && !this._rightAxisGridLines) {\n this._renderHorizontalGridLinesForAxis(this.g(), this.y(), this.yAxis());\n } else if (this._rightYAxisChildren().length > 0) {\n this._renderHorizontalGridLinesForAxis(this.g(), this._rightY, this._rightYAxis);\n }\n }\n\n renderYAxis () {\n if (this._leftYAxisChildren().length !== 0) {\n this.renderYAxisAt('y', this.yAxis(), this.margins().left);\n this.renderYAxisLabel('y', this.yAxisLabel(), -90);\n }\n\n if (this._rightYAxisChildren().length !== 0) {\n this.renderYAxisAt('yr', this.rightYAxis(), this.width() - this.margins().right);\n this.renderYAxisLabel('yr', this.rightYAxisLabel(), 90, this.width() - this._rightYAxisLabelPadding);\n }\n }\n\n _calculateYAxisRanges (left, right) {\n let lyAxisMin, lyAxisMax, ryAxisMin, ryAxisMax;\n let ranges;\n\n if (left) {\n lyAxisMin = this._yAxisMin();\n lyAxisMax = this._yAxisMax();\n }\n\n if (right) {\n ryAxisMin = this._rightYAxisMin();\n ryAxisMax = this._rightYAxisMax();\n }\n\n if (this.alignYAxes() && left && right) {\n ranges = this._alignYAxisRanges(lyAxisMin, lyAxisMax, ryAxisMin, ryAxisMax);\n }\n\n return ranges || {\n lyAxisMin: lyAxisMin,\n lyAxisMax: lyAxisMax,\n ryAxisMin: ryAxisMin,\n ryAxisMax: ryAxisMax\n };\n }\n\n _alignYAxisRanges (lyAxisMin, lyAxisMax, ryAxisMin, ryAxisMax) {\n // since the two series will share a zero, each Y is just a multiple\n // of the other. and the ratio should be the ratio of the ranges of the\n // input data, so that they come out the same height. so we just min/max\n\n // note: both ranges already include zero due to the stack mixin (#667)\n // if #667 changes, we can reconsider whether we want data height or\n // height from zero to be equal. and it will be possible for the axes\n // to be aligned but not visible.\n const extentRatio = (ryAxisMax - ryAxisMin) / (lyAxisMax - lyAxisMin);\n\n return {\n lyAxisMin: Math.min(lyAxisMin, ryAxisMin / extentRatio),\n lyAxisMax: Math.max(lyAxisMax, ryAxisMax / extentRatio),\n ryAxisMin: Math.min(ryAxisMin, lyAxisMin * extentRatio),\n ryAxisMax: Math.max(ryAxisMax, lyAxisMax * extentRatio)\n };\n }\n\n _prepareRightYAxis (ranges) {\n const needDomain = this.rightY() === undefined || this.elasticY(),\n needRange = needDomain || this.resizing();\n if (this.rightY() === undefined) {\n this.rightY(scaleLinear());\n }\n if (needDomain) {\n this.rightY().domain([ranges.ryAxisMin, ranges.ryAxisMax]);\n }\n if (needRange) {\n this.rightY().rangeRound([this.yAxisHeight(), 0]);\n }\n\n this.rightY().range([this.yAxisHeight(), 0]);\n this.rightYAxis(this.rightYAxis().scale(this.rightY()));\n\n // In D3v4 create a RightAxis\n // _chart.rightYAxis().orient('right');\n }\n\n _prepareLeftYAxis (ranges) {\n const needDomain = this.y() === undefined || this.elasticY(),\n needRange = needDomain || this.resizing();\n if (this.y() === undefined) {\n this.y(scaleLinear());\n }\n if (needDomain) {\n this.y().domain([ranges.lyAxisMin, ranges.lyAxisMax]);\n }\n if (needRange) {\n this.y().rangeRound([this.yAxisHeight(), 0]);\n }\n\n this.y().range([this.yAxisHeight(), 0]);\n this.yAxis(this.yAxis().scale(this.y()));\n\n // In D3v4 create a LeftAxis\n // _chart.yAxis().orient('left');\n }\n\n _generateChildG (child, i) {\n child._generateG(this.g());\n child.g().attr('class', `${SUB_CHART_CLASS} _${i}`);\n }\n\n plotData () {\n for (let i = 0; i < this._children.length; ++i) {\n const child = this._children[i];\n\n if (!child.g()) {\n this._generateChildG(child, i);\n }\n\n if (this._shareColors) {\n child.colors(this.colors());\n }\n\n child.x(this.x());\n\n child.xAxis(this.xAxis());\n\n if (child.useRightYAxis()) {\n child.y(this.rightY());\n child.yAxis(this.rightYAxis());\n } else {\n child.y(this.y());\n child.yAxis(this.yAxis());\n }\n\n child.plotData();\n\n child._activateRenderlets();\n }\n }\n\n /**\n * Get or set whether to draw gridlines from the right y axis. Drawing from the left y axis is the\n * default behavior. This option is only respected when subcharts with both left and right y-axes\n * are present.\n * @param {Boolean} [useRightAxisGridLines=false]\n * @returns {Boolean|CompositeChart}\n */\n useRightAxisGridLines (useRightAxisGridLines) {\n if (!arguments) {\n return this._rightAxisGridLines;\n }\n\n this._rightAxisGridLines = useRightAxisGridLines;\n return this;\n }\n\n /**\n * Get or set chart-specific options for all child charts. This is equivalent to calling\n * {@link BaseMixin#options .options} on each child chart.\n * @param {Object} [childOptions]\n * @returns {Object|CompositeChart}\n */\n childOptions (childOptions) {\n if (!arguments.length) {\n return this._childOptions;\n }\n this._childOptions = childOptions;\n this._children.forEach(child => {\n child.options(this._childOptions);\n });\n return this;\n }\n\n fadeDeselectedArea (brushSelection) {\n if (this.brushOn()) {\n for (let i = 0; i < this._children.length; ++i) {\n const child = this._children[i];\n child.fadeDeselectedArea(brushSelection);\n }\n }\n }\n\n /**\n * Set or get the right y axis label.\n * @param {String} [rightYAxisLabel]\n * @param {Number} [padding]\n * @returns {String|CompositeChart}\n */\n rightYAxisLabel (rightYAxisLabel, padding) {\n if (!arguments.length) {\n return this._rightYAxisLabel;\n }\n this._rightYAxisLabel = rightYAxisLabel;\n this.margins().right -= this._rightYAxisLabelPadding;\n this._rightYAxisLabelPadding = (padding === undefined) ? DEFAULT_RIGHT_Y_AXIS_LABEL_PADDING : padding;\n this.margins().right += this._rightYAxisLabelPadding;\n return this;\n }\n\n /**\n * Combine the given charts into one single composite coordinate grid chart.\n * @example\n * moveChart.compose([\n * // when creating sub-chart you need to pass in the parent chart\n * new LineChart(moveChart)\n * .group(indexAvgByMonthGroup) // if group is missing then parent's group will be used\n * .valueAccessor(function (d){return d.value.avg;})\n * // most of the normal functions will continue to work in a composed chart\n * .renderArea(true)\n * .stack(monthlyMoveGroup, function (d){return d.value;})\n * .title(function (d){\n * var value = d.value.avg?d.value.avg:d.value;\n * if(isNaN(value)) value = 0;\n * return dateFormat(d.key) + '\\n' + numberFormat(value);\n * }),\n * new BarChart(moveChart)\n * .group(volumeByMonthGroup)\n * .centerBar(true)\n * ]);\n * @param {Array} [subChartArray]\n * @returns {CompositeChart}\n */\n compose (subChartArray) {\n this._children = subChartArray;\n this._children.forEach(child => {\n child.height(this.height());\n child.width(this.width());\n child.margins(this.margins());\n\n if (this._shareTitle) {\n child.title(this.title());\n }\n\n child.options(this._childOptions);\n });\n this.rescale();\n return this;\n }\n\n _setChildrenProperty (prop, value) {\n this._children.forEach(child => {\n child[prop](value);\n });\n }\n\n // properties passed through in compose()\n height (height) {\n if(!arguments.length) {\n return super.height();\n }\n super.height(height);\n this._setChildrenProperty('height', height);\n return this;\n }\n\n width (width) {\n if(!arguments.length) {\n return super.width();\n }\n super.width(width);\n this._setChildrenProperty('width', width);\n return this;\n }\n\n margins (margins) {\n if(!arguments.length) {\n return super.margins();\n }\n super.margins(margins);\n this._setChildrenProperty('margins', margins);\n return this;\n }\n\n /**\n * Returns the child charts which are composed into the composite chart.\n * @returns {Array}\n */\n children () {\n return this._children;\n }\n\n /**\n * Get or set color sharing for the chart. If set, the {@link ColorMixin#colors .colors()} value from this chart\n * will be shared with composed children. Additionally if the child chart implements\n * Stackable and has not set a custom .colorAccessor, then it will generate a color\n * specific to its order in the composition.\n * @param {Boolean} [shareColors=false]\n * @returns {Boolean|CompositeChart}\n */\n shareColors (shareColors) {\n if (!arguments.length) {\n return this._shareColors;\n }\n this._shareColors = shareColors;\n return this;\n }\n\n /**\n * Get or set title sharing for the chart. If set, the {@link BaseMixin#title .title()} value from\n * this chart will be shared with composed children.\n *\n * Note: currently you must call this before `compose` or the child will still get the parent's\n * `title` function!\n * @param {Boolean} [shareTitle=true]\n * @returns {Boolean|CompositeChart}\n */\n shareTitle (shareTitle) {\n if (!arguments.length) {\n return this._shareTitle;\n }\n this._shareTitle = shareTitle;\n return this;\n }\n\n /**\n * Get or set the y scale for the right axis. The right y scale is typically automatically\n * generated by the chart implementation.\n * @see {@link https://github.com/d3/d3-scale/blob/master/README.md d3.scale}\n * @param {d3.scale} [yScale]\n * @returns {d3.scale|CompositeChart}\n */\n rightY (yScale) {\n if (!arguments.length) {\n return this._rightY;\n }\n this._rightY = yScale;\n this.rescale();\n return this;\n }\n\n /**\n * Get or set alignment between left and right y axes. A line connecting '0' on both y axis\n * will be parallel to x axis. This only has effect when {@link CoordinateGridMixin#elasticY elasticY} is true.\n * @param {Boolean} [alignYAxes=false]\n * @returns {Chart}\n */\n alignYAxes (alignYAxes) {\n if (!arguments.length) {\n return this._alignYAxes;\n }\n this._alignYAxes = alignYAxes;\n this.rescale();\n return this;\n }\n\n _leftYAxisChildren () {\n return this._children.filter(child => !child.useRightYAxis());\n }\n\n _rightYAxisChildren () {\n return this._children.filter(child => child.useRightYAxis());\n }\n\n _getYAxisMin (charts) {\n return charts.map(c => c.yAxisMin());\n }\n\n _yAxisMin () {\n return min(this._getYAxisMin(this._leftYAxisChildren()));\n }\n\n _rightYAxisMin () {\n return min(this._getYAxisMin(this._rightYAxisChildren()));\n }\n\n _getYAxisMax (charts) {\n return charts.map(c => c.yAxisMax());\n }\n\n _yAxisMax () {\n return utils.add(max(this._getYAxisMax(this._leftYAxisChildren())), this.yAxisPadding());\n }\n\n _rightYAxisMax () {\n return utils.add(max(this._getYAxisMax(this._rightYAxisChildren())), this.yAxisPadding());\n }\n\n _getAllXAxisMinFromChildCharts () {\n return this._children.map(c => c.xAxisMin());\n }\n\n xAxisMin () {\n return utils.subtract(min(this._getAllXAxisMinFromChildCharts()), this.xAxisPadding(), this.xAxisPaddingUnit());\n }\n\n _getAllXAxisMaxFromChildCharts () {\n return this._children.map(c => c.xAxisMax());\n }\n\n xAxisMax () {\n return utils.add(max(this._getAllXAxisMaxFromChildCharts()), this.xAxisPadding(), this.xAxisPaddingUnit());\n }\n\n legendables () {\n return this._children.reduce((items, child) => {\n if (this._shareColors) {\n child.colors(this.colors());\n }\n items.push.apply(items, child.legendables());\n return items;\n }, []);\n }\n\n legendHighlight (d) {\n for (let j = 0; j < this._children.length; ++j) {\n const child = this._children[j];\n child.legendHighlight(d);\n }\n }\n\n legendReset (d) {\n for (let j = 0; j < this._children.length; ++j) {\n const child = this._children[j];\n child.legendReset(d);\n }\n }\n\n legendToggle () {\n console.log('composite should not be getting legendToggle itself');\n }\n\n /**\n * Set or get the right y axis used by the composite chart. This function is most useful when y\n * axis customization is required. The y axis in dc.js is an instance of a\n * [d3.axisRight](https://github.com/d3/d3-axis/blob/master/README.md#axisRight) therefore it supports any valid\n * d3 axis manipulation.\n *\n * **Caution**: The right y axis is usually generated internally by dc; resetting it may cause\n * unexpected results. Note also that when used as a getter, this function is not chainable: it\n * returns the axis, not the chart,\n * {@link https://github.com/dc-js/dc.js/wiki/FAQ#why-does-everything-break-after-a-call-to-xaxis-or-yaxis\n * so attempting to call chart functions after calling `.yAxis()` will fail}.\n * @see {@link https://github.com/d3/d3-axis/blob/master/README.md#axisRight}\n * @example\n * // customize y axis tick format\n * chart.rightYAxis().tickFormat(function (v) {return v + '%';});\n * // customize y axis tick values\n * chart.rightYAxis().tickValues([0, 100, 200, 300]);\n * @param {d3.axisRight} [rightYAxis]\n * @returns {d3.axisRight|CompositeChart}\n */\n rightYAxis (rightYAxis) {\n if (!arguments.length) {\n return this._rightYAxis;\n }\n this._rightYAxis = rightYAxis;\n return this;\n }\n\n yAxisMin () {\n throw new Error('Not supported for this chart type');\n }\n\n yAxisMax () {\n throw new Error('Not supported for this chart type');\n }\n}\n\nexport const compositeChart = (parent, chartGroup) => new CompositeChart(parent, chartGroup);\n","import {format} from 'd3-format';\n\nimport {logger} from '../core/logger';\nimport {BaseMixin} from '../base/base-mixin';\n\n/**\n * The data count widget is a simple widget designed to display the number of records selected by the\n * current filters out of the total number of records in the data set. Once created the data count widget\n * will automatically update the text content of child elements with the following classes:\n *\n * * `.total-count` - total number of records\n * * `.filter-count` - number of records matched by the current filters\n *\n * Note: this widget works best for the specific case of showing the number of records out of a\n * total. If you want a more general-purpose numeric display, please use the\n * {@link NumberDisplay} widget instead.\n *\n * Examples:\n * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}\n * @mixes BaseMixin\n */\nexport class DataCount extends BaseMixin {\n /**\n * Create a Data Count widget.\n * @example\n * var ndx = crossfilter(data);\n * var all = ndx.groupAll();\n *\n * new DataCount('.dc-data-count')\n * .crossfilter(ndx)\n * .groupAll(all);\n * @param {String|node|d3.selection} parent - Any valid\n * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying\n * a dom block element such as a div; or a dom element or d3 selection.\n * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.\n * Interaction with a chart will only trigger events and redraws within the chart's group.\n */\n constructor (parent, chartGroup) {\n super();\n\n this._formatNumber = format(',d');\n this._crossfilter = null;\n this._groupAll = null;\n this._html = {some: '', all: ''};\n\n this._mandatoryAttributes(['crossfilter', 'groupAll']);\n\n this.anchor(parent, chartGroup);\n }\n\n /**\n * Gets or sets an optional object specifying HTML templates to use depending how many items are\n * selected. The text `%total-count` will replaced with the total number of records, and the text\n * `%filter-count` will be replaced with the number of selected records.\n * - all: HTML template to use if all items are selected\n * - some: HTML template to use if not all items are selected\n * @example\n * counter.html({\n * some: '%filter-count out of %total-count records selected',\n * all: 'All records selected. Click on charts to apply filters'\n * })\n * @param {{some:String, all: String}} [options]\n * @returns {{some:String, all: String}|DataCount}\n */\n html (options) {\n if (!arguments.length) {\n return this._html;\n }\n if (options.all) {\n this._html.all = options.all;\n }\n if (options.some) {\n this._html.some = options.some;\n }\n return this;\n }\n\n /**\n * Gets or sets an optional function to format the filter count and total count.\n * @see {@link https://github.com/d3/d3-format/blob/master/README.md#format d3.format}\n * @example\n * counter.formatNumber(d3.format('.2g'))\n * @param {Function} [formatter=d3.format('.2g')]\n * @returns {Function|DataCount}\n */\n formatNumber (formatter) {\n if (!arguments.length) {\n return this._formatNumber;\n }\n this._formatNumber = formatter;\n return this;\n }\n\n _doRender () {\n const tot = this.crossfilter().size(),\n val = this.groupAll().value();\n const all = this._formatNumber(tot);\n const selected = this._formatNumber(val);\n\n if ((tot === val) && (this._html.all !== '')) {\n this.root().html(this._html.all.replace('%total-count', all).replace('%filter-count', selected));\n } else if (this._html.some !== '') {\n this.root().html(this._html.some.replace('%total-count', all).replace('%filter-count', selected));\n } else {\n this.selectAll('.total-count').text(all);\n this.selectAll('.filter-count').text(selected);\n }\n return this;\n }\n\n _doRedraw () {\n return this._doRender();\n }\n\n crossfilter (cf) {\n if (!arguments.length) {\n return this._crossfilter;\n }\n this._crossfilter = cf;\n return this;\n }\n\n dimension (cf) {\n logger.warnOnce('consider using dataCount.crossfilter instead of dataCount.dimension for clarity');\n if (!arguments.length) {\n return this.crossfilter();\n }\n return this.crossfilter(cf);\n }\n\n groupAll (groupAll) {\n if (!arguments.length) {\n return this._groupAll;\n }\n this._groupAll = groupAll;\n return this;\n }\n\n group (groupAll) {\n logger.warnOnce('consider using dataCount.groupAll instead of dataCount.group for clarity');\n if (!arguments.length) {\n return this.groupAll();\n }\n return this.groupAll(groupAll);\n }\n}\n\nexport const dataCount = (parent, chartGroup) => new DataCount(parent, chartGroup);\n","import {ascending} from 'd3-array';\n\nimport {logger} from '../core/logger';\nimport {BaseMixin} from '../base/base-mixin';\nimport {d3compat} from '../core/config';\n\nconst LABEL_CSS_CLASS = 'dc-grid-label';\nconst ITEM_CSS_CLASS = 'dc-grid-item';\nconst SECTION_CSS_CLASS = 'dc-grid-section dc-grid-group';\nconst GRID_CSS_CLASS = 'dc-grid-top';\n\n/**\n * Data grid is a simple widget designed to list the filtered records, providing\n * a simple way to define how the items are displayed.\n *\n * Note: Formerly the data grid chart (and data table) used the {@link DataGrid#group group} attribute as a\n * keying function for {@link https://github.com/d3/d3-collection/blob/master/README.md#nest nesting} the data\n * together in sections. This was confusing so it has been renamed to `section`, although `group` still works.\n *\n * Examples:\n * - {@link https://dc-js.github.io/dc.js/ep/ List of members of the european parliament}\n * @mixes BaseMixin\n */\nexport class DataGrid extends BaseMixin {\n /**\n * Create a Data Grid.\n * @param {String|node|d3.selection} parent - Any valid\n * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying\n * a dom block element such as a div; or a dom element or d3 selection.\n * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.\n * Interaction with a chart will only trigger events and redraws within the chart's group.\n */\n constructor (parent, chartGroup) {\n super();\n\n this._section = null;\n this._size = 999; // shouldn't be needed, but you might\n this._html = function (d) {\n return `you need to provide an html() handling param: ${JSON.stringify(d)}`;\n };\n this._sortBy = function (d) {\n return d;\n };\n this._order = ascending;\n this._beginSlice = 0;\n this._endSlice = undefined;\n\n this._htmlSection = d => `

${ \n this.keyAccessor()(d)}

`;\n\n this._mandatoryAttributes(['dimension', 'section']);\n\n this.anchor(parent, chartGroup);\n }\n\n _doRender () {\n this.selectAll(`div.${GRID_CSS_CLASS}`).remove();\n\n this._renderItems(this._renderSections());\n\n return this;\n }\n\n _renderSections () {\n const sections = this.root().selectAll(`div.${GRID_CSS_CLASS}`)\n .data(this._nestEntries(), d => this.keyAccessor()(d));\n\n const itemSection = sections\n .enter()\n .append('div')\n .attr('class', GRID_CSS_CLASS);\n\n if (this._htmlSection) {\n itemSection\n .html(d => this._htmlSection(d));\n }\n\n sections.exit().remove();\n return itemSection;\n }\n\n _nestEntries () {\n let entries = this.dimension().top(this._size);\n\n entries = entries\n .sort((a, b) => this._order(this._sortBy(a), this._sortBy(b)))\n .slice(this._beginSlice, this._endSlice)\n\n return d3compat.nester({\n key: this.section(),\n sortKeys: this._order,\n entries\n });\n }\n\n _renderItems (sections) {\n let items = sections.order()\n .selectAll(`div.${ITEM_CSS_CLASS}`)\n .data(d => d.values);\n\n items.exit().remove();\n\n items = items\n .enter()\n .append('div')\n .attr('class', ITEM_CSS_CLASS)\n .html(d => this._html(d))\n .merge(items);\n\n return items;\n }\n\n _doRedraw () {\n return this._doRender();\n }\n\n /**\n * Get or set the section function for the data grid. The section function takes a data row and\n * returns the key to specify to {@link https://github.com/d3/d3-collection/blob/master/README.md#nest d3.nest}\n * to split rows into sections.\n *\n * Do not pass in a crossfilter section as this will not work.\n * @example\n * // section rows by the value of their field\n * chart\n * .section(function(d) { return d.field; })\n * @param {Function} section Function taking a row of data and returning the nest key.\n * @returns {Function|DataGrid}\n */\n section (section) {\n if (!arguments.length) {\n return this._section;\n }\n this._section = section;\n return this;\n }\n\n /**\n * Backward-compatible synonym for {@link DataGrid#section section}.\n *\n * @param {Function} section Function taking a row of data and returning the nest key.\n * @returns {Function|DataGrid}\n */\n group (section) {\n logger.warnOnce('consider using dataGrid.section instead of dataGrid.group for clarity');\n if (!arguments.length) {\n return this.section();\n }\n return this.section(section);\n }\n\n /**\n * Get or set the index of the beginning slice which determines which entries get displayed by the widget.\n * Useful when implementing pagination.\n * @param {Number} [beginSlice=0]\n * @returns {Number|DataGrid}\n */\n beginSlice (beginSlice) {\n if (!arguments.length) {\n return this._beginSlice;\n }\n this._beginSlice = beginSlice;\n return this;\n }\n\n /**\n * Get or set the index of the end slice which determines which entries get displayed by the widget.\n * Useful when implementing pagination.\n * @param {Number} [endSlice]\n * @returns {Number|DataGrid}\n */\n endSlice (endSlice) {\n if (!arguments.length) {\n return this._endSlice;\n }\n this._endSlice = endSlice;\n return this;\n }\n\n /**\n * Get or set the grid size which determines the number of items displayed by the widget.\n * @param {Number} [size=999]\n * @returns {Number|DataGrid}\n */\n size (size) {\n if (!arguments.length) {\n return this._size;\n }\n this._size = size;\n return this;\n }\n\n /**\n * Get or set the function that formats an item. The data grid widget uses a\n * function to generate dynamic html. Use your favourite templating engine or\n * generate the string directly.\n * @example\n * chart.html(function (d) { return '
'+data.exampleString+'
';});\n * @param {Function} [html]\n * @returns {Function|DataGrid}\n */\n html (html) {\n if (!arguments.length) {\n return this._html;\n }\n this._html = html;\n return this;\n }\n\n /**\n * Get or set the function that formats a section label.\n * @example\n * chart.htmlSection (function (d) { return '

'.d.key . 'with ' . d.values.length .' items

'});\n * @param {Function} [htmlSection]\n * @returns {Function|DataGrid}\n */\n htmlSection (htmlSection) {\n if (!arguments.length) {\n return this._htmlSection;\n }\n this._htmlSection = htmlSection;\n return this;\n }\n\n /**\n * Backward-compatible synonym for {@link DataGrid#htmlSection htmlSection}.\n * @param {Function} [htmlSection]\n * @returns {Function|DataGrid}\n */\n htmlGroup (htmlSection) {\n logger.warnOnce('consider using dataGrid.htmlSection instead of dataGrid.htmlGroup for clarity');\n if (!arguments.length) {\n return this.htmlSection();\n }\n return this.htmlSection(htmlSection);\n }\n\n /**\n * Get or set sort-by function. This function works as a value accessor at the item\n * level and returns a particular field to be sorted.\n * @example\n * chart.sortBy(function(d) {\n * return d.date;\n * });\n * @param {Function} [sortByFunction]\n * @returns {Function|DataGrid}\n */\n sortBy (sortByFunction) {\n if (!arguments.length) {\n return this._sortBy;\n }\n this._sortBy = sortByFunction;\n return this;\n }\n\n /**\n * Get or set sort the order function.\n * @see {@link https://github.com/d3/d3-array/blob/master/README.md#ascending d3.ascending}\n * @see {@link https://github.com/d3/d3-array/blob/master/README.md#descending d3.descending}\n * @example\n * chart.order(d3.descending);\n * @param {Function} [order=d3.ascending]\n * @returns {Function|DataGrid}\n */\n order (order) {\n if (!arguments.length) {\n return this._order;\n }\n this._order = order;\n return this;\n }\n}\n\nexport const dataGrid = (parent, chartGroup) => new DataGrid(parent, chartGroup);\n","import {ascending} from 'd3-array';\n\nimport {logger} from '../core/logger';\nimport {BaseMixin} from '../base/base-mixin';\nimport {d3compat} from '../core/config';\n\nconst LABEL_CSS_CLASS = 'dc-table-label';\nconst ROW_CSS_CLASS = 'dc-table-row';\nconst COLUMN_CSS_CLASS = 'dc-table-column';\nconst SECTION_CSS_CLASS = 'dc-table-section dc-table-group';\nconst HEAD_CSS_CLASS = 'dc-table-head';\n\n/**\n * The data table is a simple widget designed to list crossfilter focused data set (rows being\n * filtered) in a good old tabular fashion.\n *\n * An interesting feature of the data table is that you can pass a crossfilter group to the\n * `dimension`, if you want to show aggregated data instead of raw data rows. This requires no\n * special code as long as you specify the {@link DataTable#order order} as `d3.descending`,\n * since the data table will use `dimension.top()` to fetch the data in that case, and the method is\n * equally supported on the crossfilter group as the crossfilter dimension.\n *\n * If you want to display aggregated data in ascending order, you will need to wrap the group\n * in a [fake dimension](https://github.com/dc-js/dc.js/wiki/FAQ#fake-dimensions) to support the\n * `.bottom()` method. See the example linked below for more details.\n *\n * Note: Formerly the data table (and data grid chart) used the {@link DataTable#group group} attribute as a\n * keying function for {@link https://github.com/d3/d3-collection/blob/master/README.md#nest nesting} the data\n * together in sections. This was confusing so it has been renamed to `section`, although `group` still works.\n * Examples:\n * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}\n * - {@link http://dc-js.github.io/dc.js/examples/table-on-aggregated-data.html dataTable on a crossfilter group}\n * ({@link https://github.com/dc-js/dc.js/blob/master/web-src/examples/table-on-aggregated-data.html source})\n *\n * @mixes BaseMixin\n */\nexport class DataTable extends BaseMixin {\n /**\n * Create a Data Table.\n *\n * @param {String|node|d3.selection} parent - Any valid\n * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying\n * a dom block element such as a div; or a dom element or d3 selection.\n * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.\n * Interaction with a chart will only trigger events and redraws within the chart's group.\n */\n constructor (parent, chartGroup) {\n super();\n\n this._size = 25;\n this._columns = [];\n this._sortBy = d => d;\n this._order = ascending;\n this._beginSlice = 0;\n this._endSlice = undefined;\n this._showSections = true;\n this._section = () => ''; // all in one section\n\n this._mandatoryAttributes(['dimension']);\n\n this.anchor(parent, chartGroup);\n }\n\n _doRender () {\n this.selectAll('tbody').remove();\n\n this._renderRows(this._renderSections());\n\n return this;\n }\n\n _doColumnValueFormat (v, d) {\n return (typeof v === 'function') ? v(d) : // v as function\n (typeof v === 'string') ? d[v] : // v is field name string\n v.format(d); // v is Object, use fn (element 2)\n }\n\n _doColumnHeaderFormat (d) {\n // if 'function', convert to string representation\n // show a string capitalized\n // if an object then display its label string as-is.\n return (typeof d === 'function') ? this._doColumnHeaderFnToString(d) :\n (typeof d === 'string') ? this._doColumnHeaderCapitalize(d) :\n String(d.label);\n }\n\n _doColumnHeaderCapitalize (s) {\n // capitalize\n return s.charAt(0).toUpperCase() + s.slice(1);\n }\n\n _doColumnHeaderFnToString (f) {\n // columnString(f) {\n let s = String(f);\n const i1 = s.indexOf('return ');\n if (i1 >= 0) {\n const i2 = s.lastIndexOf(';');\n if (i2 >= 0) {\n s = s.substring(i1 + 7, i2);\n const i3 = s.indexOf('numberFormat');\n if (i3 >= 0) {\n s = s.replace('numberFormat', '');\n }\n }\n }\n return s;\n }\n\n _renderSections () {\n // The 'original' example uses all 'functions'.\n // If all 'functions' are used, then don't remove/add a header, and leave\n // the html alone. This preserves the functionality of earlier releases.\n // A 2nd option is a string representing a field in the data.\n // A third option is to supply an Object such as an array of 'information', and\n // supply your own _doColumnHeaderFormat and _doColumnValueFormat functions to\n // create what you need.\n let bAllFunctions = true;\n this._columns.forEach(f => {\n bAllFunctions = bAllFunctions & (typeof f === 'function');\n });\n\n if (!bAllFunctions) {\n // ensure one thead\n let thead = this.selectAll('thead').data([0]);\n thead.exit().remove();\n thead = thead.enter()\n .append('thead')\n .merge(thead);\n\n // with one tr\n let headrow = thead.selectAll('tr').data([0]);\n headrow.exit().remove();\n headrow = headrow.enter()\n .append('tr')\n .merge(headrow);\n\n // with a th for each column\n const headcols = headrow.selectAll('th')\n .data(this._columns);\n headcols.exit().remove();\n headcols.enter().append('th')\n .merge(headcols)\n .attr('class', HEAD_CSS_CLASS)\n .html(d => (this._doColumnHeaderFormat(d)));\n }\n\n const sections = this.root().selectAll('tbody')\n .data(this._nestEntries(), d => this.keyAccessor()(d));\n\n const rowSection = sections\n .enter()\n .append('tbody');\n\n if (this._showSections === true) {\n rowSection\n .append('tr')\n .attr('class', SECTION_CSS_CLASS)\n .append('td')\n .attr('class', LABEL_CSS_CLASS)\n .attr('colspan', this._columns.length)\n .html(d => this.keyAccessor()(d));\n }\n\n sections.exit().remove();\n\n return rowSection;\n }\n\n _nestEntries () {\n let entries;\n if (this._order === ascending) {\n entries = this.dimension().bottom(this._size);\n } else {\n entries = this.dimension().top(this._size);\n }\n\n entries = entries.sort((a, b) => this._order(this._sortBy(a), this._sortBy(b))).slice(this._beginSlice, this._endSlice)\n\n return d3compat.nester({\n key: this.section(),\n sortKeys: this._order,\n entries\n });\n }\n\n _renderRows (sections) {\n const rows = sections.order()\n .selectAll(`tr.${ROW_CSS_CLASS}`)\n .data(d => d.values);\n\n const rowEnter = rows.enter()\n .append('tr')\n .attr('class', ROW_CSS_CLASS);\n\n this._columns.forEach((v, i) => {\n rowEnter.append('td')\n .attr('class', `${COLUMN_CSS_CLASS} _${i}`)\n .html(d => this._doColumnValueFormat(v, d));\n });\n\n rows.exit().remove();\n\n return rows;\n }\n\n _doRedraw () {\n return this._doRender();\n }\n\n /**\n * Get or set the section function for the data table. The section function takes a data row and\n * returns the key to specify to {@link https://github.com/d3/d3-collection/blob/master/README.md#nest d3.nest}\n * to split rows into sections. By default there will be only one section with no name.\n *\n * Set {@link DataTable#showSections showSections} to false to hide the section headers\n *\n * @example\n * // section rows by the value of their field\n * chart\n * .section(function(d) { return d.field; })\n * @param {Function} section Function taking a row of data and returning the nest key.\n * @returns {Function|DataTable}\n */\n section (section) {\n if (!arguments.length) {\n return this._section;\n }\n this._section = section;\n return this;\n }\n\n /**\n * Backward-compatible synonym for {@link DataTable#section section}.\n *\n * @param {Function} section Function taking a row of data and returning the nest key.\n * @returns {Function|DataTable}\n */\n group (section) {\n logger.warnOnce('consider using dataTable.section instead of dataTable.group for clarity');\n if (!arguments.length) {\n return this.section();\n }\n return this.section(section);\n }\n\n /**\n * Get or set the table size which determines the number of rows displayed by the widget.\n * @param {Number} [size=25]\n * @returns {Number|DataTable}\n */\n size (size) {\n if (!arguments.length) {\n return this._size;\n }\n this._size = size;\n return this;\n }\n\n /**\n * Get or set the index of the beginning slice which determines which entries get displayed\n * by the widget. Useful when implementing pagination.\n *\n * Note: the sortBy function will determine how the rows are ordered for pagination purposes.\n\n * See the {@link http://dc-js.github.io/dc.js/examples/table-pagination.html table pagination example}\n * to see how to implement the pagination user interface using `beginSlice` and `endSlice`.\n * @param {Number} [beginSlice=0]\n * @returns {Number|DataTable}\n */\n beginSlice (beginSlice) {\n if (!arguments.length) {\n return this._beginSlice;\n }\n this._beginSlice = beginSlice;\n return this;\n }\n\n /**\n * Get or set the index of the end slice which determines which entries get displayed by the\n * widget. Useful when implementing pagination. See {@link DataTable#beginSlice `beginSlice`} for more information.\n * @param {Number|undefined} [endSlice=undefined]\n * @returns {Number|DataTable}\n */\n endSlice (endSlice) {\n if (!arguments.length) {\n return this._endSlice;\n }\n this._endSlice = endSlice;\n return this;\n }\n\n /**\n * Get or set column functions. The data table widget supports several methods of specifying the\n * columns to display.\n *\n * The original method uses an array of functions to generate dynamic columns. Column functions\n * are simple javascript functions with only one input argument `d` which represents a row in\n * the data set. The return value of these functions will be used to generate the content for\n * each cell. However, this method requires the HTML for the table to have a fixed set of column\n * headers.\n *\n *
chart.columns([\n     *     function(d) { return d.date; },\n     *     function(d) { return d.open; },\n     *     function(d) { return d.close; },\n     *     function(d) { return numberFormat(d.close - d.open); },\n     *     function(d) { return d.volume; }\n     * ]);\n     * 
\n *\n * In the second method, you can list the columns to read from the data without specifying it as\n * a function, except where necessary (ie, computed columns). Note the data element name is\n * capitalized when displayed in the table header. You can also mix in functions as necessary,\n * using the third `{label, format}` form, as shown below.\n *\n *
chart.columns([\n     *     \"date\",    // d[\"date\"], ie, a field accessor; capitalized automatically\n     *     \"open\",    // ...\n     *     \"close\",   // ...\n     *     {\n     *         label: \"Change\",\n     *         format: function (d) {\n     *             return numberFormat(d.close - d.open);\n     *         }\n     *     },\n     *     \"volume\"   // d[\"volume\"], ie, a field accessor; capitalized automatically\n     * ]);\n     * 
\n *\n * In the third example, we specify all fields using the `{label, format}` method:\n *
chart.columns([\n     *     {\n     *         label: \"Date\",\n     *         format: function (d) { return d.date; }\n     *     },\n     *     {\n     *         label: \"Open\",\n     *         format: function (d) { return numberFormat(d.open); }\n     *     },\n     *     {\n     *         label: \"Close\",\n     *         format: function (d) { return numberFormat(d.close); }\n     *     },\n     *     {\n     *         label: \"Change\",\n     *         format: function (d) { return numberFormat(d.close - d.open); }\n     *     },\n     *     {\n     *         label: \"Volume\",\n     *         format: function (d) { return d.volume; }\n     *     }\n     * ]);\n     * 
\n *\n * You may wish to override the dataTable functions `_doColumnHeaderCapitalize` and\n * `_doColumnHeaderFnToString`, which are used internally to translate the column information or\n * function into a displayed header. The first one is used on the \"string\" column specifier; the\n * second is used to transform a stringified function into something displayable. For the Stock\n * example, the function for Change becomes the table header **d.close - d.open**.\n *\n * Finally, you can even specify a completely different form of column definition. To do this,\n * override `_chart._doColumnHeaderFormat` and `_chart._doColumnValueFormat` Be aware that\n * fields without numberFormat specification will be displayed just as they are stored in the\n * data, unformatted.\n * @param {Array} [columns=[]]\n * @returns {Array}|DataTable}\n */\n columns (columns) {\n if (!arguments.length) {\n return this._columns;\n }\n this._columns = columns;\n return this;\n }\n\n /**\n * Get or set sort-by function. This function works as a value accessor at row level and returns a\n * particular field to be sorted by.\n * @example\n * chart.sortBy(function(d) {\n * return d.date;\n * });\n * @param {Function} [sortBy=identity function]\n * @returns {Function|DataTable}\n */\n sortBy (sortBy) {\n if (!arguments.length) {\n return this._sortBy;\n }\n this._sortBy = sortBy;\n return this;\n }\n\n /**\n * Get or set sort order. If the order is `d3.ascending`, the data table will use\n * `dimension().bottom()` to fetch the data; otherwise it will use `dimension().top()`\n * @see {@link https://github.com/d3/d3-array/blob/master/README.md#ascending d3.ascending}\n * @see {@link https://github.com/d3/d3-array/blob/master/README.md#descending d3.descending}\n * @example\n * chart.order(d3.descending);\n * @param {Function} [order=d3.ascending]\n * @returns {Function|DataTable}\n */\n order (order) {\n if (!arguments.length) {\n return this._order;\n }\n this._order = order;\n return this;\n }\n\n /**\n * Get or set if section header rows will be shown.\n * @example\n * chart\n * .section([value], [name])\n * .showSections(true|false);\n * @param {Boolean} [showSections=true]\n * @returns {Boolean|DataTable}\n */\n showSections (showSections) {\n if (!arguments.length) {\n return this._showSections;\n }\n this._showSections = showSections;\n return this;\n }\n\n /**\n * Backward-compatible synonym for {@link DataTable#showSections showSections}.\n * @param {Boolean} [showSections=true]\n * @returns {Boolean|DataTable}\n */\n showGroups (showSections) {\n logger.warnOnce('consider using dataTable.showSections instead of dataTable.showGroups for clarity');\n if (!arguments.length) {\n return this.showSections();\n }\n return this.showSections(showSections);\n }\n}\n\nexport const dataTable = (parent, chartGroup) => new DataTable(parent, chartGroup);\n","import {geoPath, geoAlbersUsa} from 'd3-geo';\nimport {select} from 'd3-selection';\n\nimport {BaseMixin} from '../base/base-mixin';\nimport {ColorMixin} from '../base/color-mixin';\nimport {transition} from '../core/core';\nimport {logger} from '../core/logger';\nimport {events} from '../core/events';\nimport {utils} from '../core/utils';\nimport {d3compat} from '../core/config';\n\n/**\n * The geo choropleth chart is designed as an easy way to create a crossfilter driven choropleth map\n * from GeoJson data. This chart implementation was inspired by\n * {@link http://bl.ocks.org/4060606 the great d3 choropleth example}.\n *\n * Examples:\n * - {@link http://dc-js.github.com/dc.js/vc/index.html US Venture Capital Landscape 2011}\n * @mixes ColorMixin\n * @mixes BaseMixin\n */\nexport class GeoChoroplethChart extends ColorMixin(BaseMixin) {\n /**\n * Create a Geo Choropleth Chart.\n * @example\n * // create a choropleth chart under '#us-chart' element using the default global chart group\n * var chart1 = new GeoChoroplethChart('#us-chart');\n * // create a choropleth chart under '#us-chart2' element using chart group A\n * var chart2 = new CompositeChart('#us-chart2', 'chartGroupA');\n * @param {String|node|d3.selection} parent - Any valid\n * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying\n * a dom block element such as a div; or a dom element or d3 selection.\n * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.\n * Interaction with a chart will only trigger events and redraws within the chart's group.\n */\n constructor (parent, chartGroup) {\n super();\n\n this.colorAccessor(d => d || 0);\n\n this._geoPath = geoPath();\n this._projectionFlag = undefined;\n this._projection = undefined;\n\n this._geoJsons = [];\n\n this.anchor(parent, chartGroup);\n }\n\n _doRender () {\n this.resetSvg();\n for (let layerIndex = 0; layerIndex < this._geoJsons.length; ++layerIndex) {\n const states = this.svg().append('g')\n .attr('class', `layer${layerIndex}`);\n\n let regionG = states.selectAll(`g.${this._geoJson(layerIndex).name}`)\n .data(this._geoJson(layerIndex).data);\n\n regionG = regionG.enter()\n .append('g')\n .attr('class', this._geoJson(layerIndex).name)\n .merge(regionG);\n\n regionG\n .append('path')\n .classed('dc-tabbable', this._keyboardAccessible)\n .attr('fill', 'white')\n .attr('d', this._getGeoPath());\n\n regionG.append('title');\n\n this._plotData(layerIndex);\n }\n this._projectionFlag = false;\n }\n\n _plotData (layerIndex) {\n const data = this._generateLayeredData();\n\n if (this._isDataLayer(layerIndex)) {\n const regionG = this._renderRegionG(layerIndex);\n\n this._renderPaths(regionG, layerIndex, data);\n\n this._renderTitles(regionG, layerIndex, data);\n }\n }\n\n _generateLayeredData () {\n const data = {};\n const groupAll = this.data();\n for (let i = 0; i < groupAll.length; ++i) {\n data[this.keyAccessor()(groupAll[i])] = this.valueAccessor()(groupAll[i]);\n }\n return data;\n }\n\n _isDataLayer (layerIndex) {\n return this._geoJson(layerIndex).keyAccessor;\n }\n\n _renderRegionG (layerIndex) {\n const regionG = this.svg()\n .selectAll(this._layerSelector(layerIndex))\n .classed('selected', d => this._isSelected(layerIndex, d))\n .classed('deselected', d => this._isDeselected(layerIndex, d))\n .attr('class', d => {\n const layerNameClass = this._geoJson(layerIndex).name;\n const regionClass = utils.nameToId(this._geoJson(layerIndex).keyAccessor(d));\n let baseClasses = `${layerNameClass} ${regionClass}`;\n if (this._isSelected(layerIndex, d)) {\n baseClasses += ' selected';\n }\n if (this._isDeselected(layerIndex, d)) {\n baseClasses += ' deselected';\n }\n return baseClasses;\n });\n return regionG;\n }\n\n _layerSelector (layerIndex) {\n return `g.layer${layerIndex} g.${this._geoJson(layerIndex).name}`;\n }\n\n _isSelected (layerIndex, d) {\n return this.hasFilter() && this.hasFilter(this._getKey(layerIndex, d));\n }\n\n _isDeselected (layerIndex, d) {\n return this.hasFilter() && !this.hasFilter(this._getKey(layerIndex, d));\n }\n\n _getKey (layerIndex, d) {\n return this._geoJson(layerIndex).keyAccessor(d);\n }\n\n _geoJson (index) {\n return this._geoJsons[index];\n }\n\n _renderPaths (regionG, layerIndex, data) {\n const paths = regionG\n .select('path')\n .attr('fill', function () {\n const currentFill = select(this).attr('fill');\n if (currentFill) {\n return currentFill;\n }\n return 'none';\n })\n .on('click', d3compat.eventHandler(d => this.onClick(d, layerIndex)));\n\n if (this._keyboardAccessible) {\n this._makeKeyboardAccessible(this.onClick, layerIndex);\n }\n\n transition(paths, this.transitionDuration(),\n this.transitionDelay()).attr('fill', (d, i) => this.getColor(data[this._geoJson(layerIndex).keyAccessor(d)], i));\n }\n\n onClick (d, layerIndex) {\n const selectedRegion = this._geoJson(layerIndex).keyAccessor(d);\n events.trigger(() => {\n this.filter(selectedRegion);\n this.redrawGroup();\n });\n }\n\n _renderTitles (regionG, layerIndex, data) {\n if (this.renderTitle()) {\n regionG.selectAll('title').text(d => {\n const key = this._getKey(layerIndex, d);\n const value = data[key];\n return this.title()({key: key, value: value});\n });\n }\n }\n\n _doRedraw () {\n for (let layerIndex = 0; layerIndex < this._geoJsons.length; ++layerIndex) {\n this._plotData(layerIndex);\n if (this._projectionFlag) {\n this.svg().selectAll(`g.${this._geoJson(layerIndex).name} path`).attr('d', this._getGeoPath());\n }\n }\n this._projectionFlag = false;\n }\n\n /**\n * **mandatory**\n *\n * Use this function to insert a new GeoJson map layer. This function can be invoked multiple times\n * if you have multiple GeoJson data layers to render on top of each other. If you overlay multiple\n * layers with the same name the new overlay will override the existing one.\n * @see {@link http://geojson.org/ GeoJSON}\n * @see {@link https://github.com/topojson/topojson/wiki TopoJSON}\n * @see {@link https://github.com/topojson/topojson-1.x-api-reference/blob/master/API-Reference.md#wiki-feature topojson.feature}\n * @example\n * // insert a layer for rendering US states\n * chart.overlayGeoJson(statesJson.features, 'state', function(d) {\n * return d.properties.name;\n * });\n * @param {_geoJson} json - a geojson feed\n * @param {String} name - name of the layer\n * @param {Function} keyAccessor - accessor function used to extract 'key' from the GeoJson data. The key extracted by\n * this function should match the keys returned by the crossfilter groups.\n * @returns {GeoChoroplethChart}\n */\n overlayGeoJson (json, name, keyAccessor) {\n for (let i = 0; i < this._geoJsons.length; ++i) {\n if (this._geoJsons[i].name === name) {\n this._geoJsons[i].data = json;\n this._geoJsons[i].keyAccessor = keyAccessor;\n return this;\n }\n }\n this._geoJsons.push({name: name, data: json, keyAccessor: keyAccessor});\n return this;\n }\n\n /**\n * Gets or sets a custom geo projection function. See the available\n * {@link https://github.com/d3/d3-geo/blob/master/README.md#projections d3 geo projection functions}.\n *\n * Starting version 3.0 it has been deprecated to rely on the default projection being\n * {@link https://github.com/d3/d3-geo/blob/master/README.md#geoAlbersUsa d3.geoAlbersUsa()}. Please\n * set it explicitly. {@link https://bl.ocks.org/mbostock/5557726\n * Considering that `null` is also a valid value for projection}, if you need\n * projection to be `null` please set it explicitly to `null`.\n * @see {@link https://github.com/d3/d3-geo/blob/master/README.md#projections d3.projection}\n * @see {@link https://github.com/d3/d3-geo-projection d3-geo-projection}\n * @param {d3.projection} [projection=d3.geoAlbersUsa()]\n * @returns {d3.projection|GeoChoroplethChart}\n */\n projection (projection) {\n if (!arguments.length) {\n return this._projection;\n }\n\n this._projection = projection;\n this._projectionFlag = true;\n return this;\n }\n\n _getGeoPath () {\n if (this._projection === undefined) {\n logger.warn('choropleth projection default of geoAlbers is deprecated,' +\n ' in next version projection will need to be set explicitly');\n return this._geoPath.projection(geoAlbersUsa());\n }\n\n return this._geoPath.projection(this._projection);\n }\n\n /**\n * Returns all GeoJson layers currently registered with this chart. The returned array is a\n * reference to this chart's internal data structure, so any modification to this array will also\n * modify this chart's internal registration.\n * @returns {Array<{name:String, data: Object, accessor: Function}>}\n */\n geoJsons () {\n return this._geoJsons;\n }\n\n /**\n * Returns the {@link https://github.com/d3/d3-geo/blob/master/README.md#paths d3.geoPath} object used to\n * render the projection and features. Can be useful for figuring out the bounding box of the\n * feature set and thus a way to calculate scale and translation for the projection.\n * @see {@link https://github.com/d3/d3-geo/blob/master/README.md#paths d3.geoPath}\n * @returns {d3.geoPath}\n */\n geoPath () {\n return this._geoPath;\n }\n\n /**\n * Remove a GeoJson layer from this chart by name\n * @param {String} name\n * @returns {GeoChoroplethChart}\n */\n removeGeoJson (name) {\n const geoJsons = [];\n\n for (let i = 0; i < this._geoJsons.length; ++i) {\n const layer = this._geoJsons[i];\n if (layer.name !== name) {\n geoJsons.push(layer);\n }\n }\n\n this._geoJsons = geoJsons;\n\n return this;\n }\n}\n\nexport const geoChoroplethChart = (parent, chartGroup) => new GeoChoroplethChart(parent, chartGroup);\n","import {ascending} from 'd3-array';\nimport {scaleBand} from 'd3-scale';\n\nimport {transition} from '../core/core';\nimport {logger} from '../core/logger';\nimport {filters} from '../core/filters';\nimport {events} from '../core/events';\nimport {ColorMixin} from '../base/color-mixin';\nimport {MarginMixin} from '../base/margin-mixin';\nimport {d3compat} from '../core/config';\n\nconst DEFAULT_BORDER_RADIUS = 6.75;\n\n/**\n * A heat map is matrix that represents the values of two dimensions of data using colors.\n * @mixes ColorMixin\n * @mixes MarginMixin\n * @mixes BaseMixin\n */\nexport class HeatMap extends ColorMixin(MarginMixin) {\n /**\n * Create a Heat Map\n * @example\n * // create a heat map under #chart-container1 element using the default global chart group\n * var heatMap1 = new HeatMap('#chart-container1');\n * // create a heat map under #chart-container2 element using chart group A\n * var heatMap2 = new HeatMap('#chart-container2', 'chartGroupA');\n * @param {String|node|d3.selection} parent - Any valid\n * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying\n * a dom block element such as a div; or a dom element or d3 selection.\n * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.\n * Interaction with a chart will only trigger events and redraws within the chart's group.\n */\n constructor (parent, chartGroup) {\n super();\n\n this._chartBody = undefined;\n\n this._cols = undefined;\n this._rows = undefined;\n this._colOrdering = ascending;\n this._rowOrdering = ascending;\n this._colScale = scaleBand();\n this._rowScale = scaleBand();\n\n this._xBorderRadius = DEFAULT_BORDER_RADIUS;\n this._yBorderRadius = DEFAULT_BORDER_RADIUS;\n\n this._mandatoryAttributes(['group']);\n this.title(this.colorAccessor());\n\n this._colsLabel = d => d;\n this._rowsLabel = d => d;\n\n this._xAxisOnClick = d => {\n this._filterAxis(0, d);\n };\n this._yAxisOnClick = d => {\n this._filterAxis(1, d);\n };\n this._boxOnClick = d => {\n const filter = d.key;\n events.trigger(() => {\n this.filter(filters.TwoDimensionalFilter(filter));\n this.redrawGroup();\n });\n };\n\n this.anchor(parent, chartGroup);\n }\n\n /**\n * Set or get the column label function. The chart class uses this function to render\n * column labels on the X axis. It is passed the column name.\n * @example\n * // the default label function just returns the name\n * chart.colsLabel(function(d) { return d; });\n * @param {Function} [labelFunction=function(d) { return d; }]\n * @returns {Function|HeatMap}\n */\n colsLabel (labelFunction) {\n if (!arguments.length) {\n return this._colsLabel;\n }\n this._colsLabel = labelFunction;\n return this;\n }\n\n /**\n * Set or get the row label function. The chart class uses this function to render\n * row labels on the Y axis. It is passed the row name.\n * @example\n * // the default label function just returns the name\n * chart.rowsLabel(function(d) { return d; });\n * @param {Function} [labelFunction=function(d) { return d; }]\n * @returns {Function|HeatMap}\n */\n rowsLabel (labelFunction) {\n if (!arguments.length) {\n return this._rowsLabel;\n }\n this._rowsLabel = labelFunction;\n return this;\n }\n\n _filterAxis (axis, value) {\n const cellsOnAxis = this.selectAll('.box-group').filter(d => d.key[axis] === value);\n const unfilteredCellsOnAxis = cellsOnAxis.filter(d => !this.hasFilter(d.key));\n events.trigger(() => {\n const selection = unfilteredCellsOnAxis.empty() ? cellsOnAxis : unfilteredCellsOnAxis;\n const filtersList = selection.data().map(kv => filters.TwoDimensionalFilter(kv.key));\n this.filter([filtersList]);\n this.redrawGroup();\n });\n }\n\n filter (filter) {\n const nonstandardFilter = f => {\n logger.warnOnce('heatmap.filter taking a coordinate is deprecated - please pass dc.filters.TwoDimensionalFilter instead');\n return this._filter(filters.TwoDimensionalFilter(f));\n };\n\n if (!arguments.length) {\n return super.filter();\n }\n if (filter !== null && filter.filterType !== 'TwoDimensionalFilter' &&\n !(Array.isArray(filter) && Array.isArray(filter[0]) && filter[0][0].filterType === 'TwoDimensionalFilter')) {\n return nonstandardFilter(filter);\n }\n return super.filter(filter);\n }\n\n /**\n * Gets or sets the values used to create the rows of the heatmap, as an array. By default, all\n * the values will be fetched from the data using the value accessor.\n * @param {Array} [rows]\n * @returns {Array|HeatMap}\n */\n\n rows (rows) {\n if (!arguments.length) {\n return this._rows;\n }\n this._rows = rows;\n return this;\n }\n\n /**\n * Get or set a comparator to order the rows.\n * Default is {@link https://github.com/d3/d3-array#ascending d3.ascending}.\n * @param {Function} [rowOrdering]\n * @returns {Function|HeatMap}\n */\n rowOrdering (rowOrdering) {\n if (!arguments.length) {\n return this._rowOrdering;\n }\n this._rowOrdering = rowOrdering;\n return this;\n }\n\n /**\n * Gets or sets the keys used to create the columns of the heatmap, as an array. By default, all\n * the values will be fetched from the data using the key accessor.\n * @param {Array} [cols]\n * @returns {Array|HeatMap}\n */\n cols (cols) {\n if (!arguments.length) {\n return this._cols;\n }\n this._cols = cols;\n return this;\n }\n\n /**\n * Get or set a comparator to order the columns.\n * Default is {@link https://github.com/d3/d3-array#ascending d3.ascending}.\n * @param {Function} [colOrdering]\n * @returns {Function|HeatMap}\n */\n colOrdering (colOrdering) {\n if (!arguments.length) {\n return this._colOrdering;\n }\n this._colOrdering = colOrdering;\n return this;\n }\n\n _doRender () {\n this.resetSvg();\n\n this._chartBody = this.svg()\n .append('g')\n .attr('class', 'heatmap')\n .attr('transform', `translate(${this.margins().left},${this.margins().top})`);\n\n return this._doRedraw();\n }\n\n _doRedraw () {\n const data = this.data();\n let rows = this.rows() || data.map(this.valueAccessor()),\n cols = this.cols() || data.map(this.keyAccessor());\n if (this._rowOrdering) {\n rows = rows.sort(this._rowOrdering);\n }\n if (this._colOrdering) {\n cols = cols.sort(this._colOrdering);\n }\n rows = this._rowScale.domain(rows);\n cols = this._colScale.domain(cols);\n\n const rowCount = rows.domain().length,\n colCount = cols.domain().length,\n boxWidth = Math.floor(this.effectiveWidth() / colCount),\n boxHeight = Math.floor(this.effectiveHeight() / rowCount);\n\n cols.rangeRound([0, this.effectiveWidth()]);\n rows.rangeRound([this.effectiveHeight(), 0]);\n\n let boxes = this._chartBody.selectAll('g.box-group').data(this.data(),\n (d, i) => `${this.keyAccessor()(d, i)}\\0${this.valueAccessor()(d, i)}`);\n\n boxes.exit().remove();\n\n const gEnter = boxes.enter().append('g')\n .attr('class', 'box-group');\n\n gEnter.append('rect')\n .attr('class', 'heat-box')\n .classed('dc-tabbable', this._keyboardAccessible)\n .attr('fill', 'white')\n .attr('x', (d, i) => cols(this.keyAccessor()(d, i)))\n .attr('y', (d, i) => rows(this.valueAccessor()(d, i)))\n .on('click', d3compat.eventHandler(this.boxOnClick()));\n\n if (this._keyboardAccessible) {\n this._makeKeyboardAccessible(this.boxOnClick);\n }\n\n boxes = gEnter.merge(boxes);\n\n if (this.renderTitle()) {\n gEnter.append('title');\n boxes.select('title').text(this.title());\n }\n\n transition(boxes.select('rect'), this.transitionDuration(), this.transitionDelay())\n .attr('x', (d, i) => cols(this.keyAccessor()(d, i)))\n .attr('y', (d, i) => rows(this.valueAccessor()(d, i)))\n .attr('rx', this._xBorderRadius)\n .attr('ry', this._yBorderRadius)\n .attr('fill', this.getColor)\n .attr('width', boxWidth)\n .attr('height', boxHeight);\n\n let gCols = this._chartBody.select('g.cols');\n if (gCols.empty()) {\n gCols = this._chartBody.append('g').attr('class', 'cols axis');\n }\n let gColsText = gCols.selectAll('text').data(cols.domain());\n\n gColsText.exit().remove();\n\n gColsText = gColsText\n .enter()\n .append('text')\n .attr('x', d => cols(d) + boxWidth / 2)\n .style('text-anchor', 'middle')\n .attr('y', this.effectiveHeight())\n .attr('dy', 12)\n .on('click', d3compat.eventHandler(this.xAxisOnClick()))\n .text(this.colsLabel())\n .merge(gColsText);\n\n transition(gColsText, this.transitionDuration(), this.transitionDelay())\n .text(this.colsLabel())\n .attr('x', d => cols(d) + boxWidth / 2)\n .attr('y', this.effectiveHeight());\n\n let gRows = this._chartBody.select('g.rows');\n if (gRows.empty()) {\n gRows = this._chartBody.append('g').attr('class', 'rows axis');\n }\n\n let gRowsText = gRows.selectAll('text').data(rows.domain());\n\n gRowsText.exit().remove();\n\n gRowsText = gRowsText\n .enter()\n .append('text')\n .style('text-anchor', 'end')\n .attr('x', 0)\n .attr('dx', -2)\n .attr('y', d => rows(d) + boxHeight / 2)\n .attr('dy', 6)\n .on('click', d3compat.eventHandler(this.yAxisOnClick()))\n .text(this.rowsLabel())\n .merge(gRowsText);\n\n transition(gRowsText, this.transitionDuration(), this.transitionDelay())\n .text(this.rowsLabel())\n .attr('y', d => rows(d) + boxHeight / 2);\n\n if (this.hasFilter()) {\n const chart = this;\n this.selectAll('g.box-group').each(function (d) {\n if (chart.isSelectedNode(d)) {\n chart.highlightSelected(this);\n } else {\n chart.fadeDeselected(this);\n }\n });\n } else {\n const chart = this;\n this.selectAll('g.box-group').each(function () {\n chart.resetHighlight(this);\n });\n }\n return this;\n }\n\n /**\n * Gets or sets the handler that fires when an individual cell is clicked in the heatmap.\n * By default, filtering of the cell will be toggled.\n * @example\n * // default box on click handler\n * chart.boxOnClick(function (d) {\n * var filter = d.key;\n * events.trigger(function () {\n * _chart.filter(filter);\n * _chart.redrawGroup();\n * });\n * });\n * @param {Function} [handler]\n * @returns {Function|HeatMap}\n */\n boxOnClick (handler) {\n if (!arguments.length) {\n return this._boxOnClick;\n }\n this._boxOnClick = handler;\n return this;\n }\n\n /**\n * Gets or sets the handler that fires when a column tick is clicked in the x axis.\n * By default, if any cells in the column are unselected, the whole column will be selected,\n * otherwise the whole column will be unselected.\n * @param {Function} [handler]\n * @returns {Function|HeatMap}\n */\n xAxisOnClick (handler) {\n if (!arguments.length) {\n return this._xAxisOnClick;\n }\n this._xAxisOnClick = handler;\n return this;\n }\n\n /**\n * Gets or sets the handler that fires when a row tick is clicked in the y axis.\n * By default, if any cells in the row are unselected, the whole row will be selected,\n * otherwise the whole row will be unselected.\n * @param {Function} [handler]\n * @returns {Function|HeatMap}\n */\n yAxisOnClick (handler) {\n if (!arguments.length) {\n return this._yAxisOnClick;\n }\n this._yAxisOnClick = handler;\n return this;\n }\n\n /**\n * Gets or sets the X border radius. Set to 0 to get full rectangles.\n * @param {Number} [xBorderRadius=6.75]\n * @returns {Number|HeatMap}\n */\n xBorderRadius (xBorderRadius) {\n if (!arguments.length) {\n return this._xBorderRadius;\n }\n this._xBorderRadius = xBorderRadius;\n return this;\n }\n\n /**\n * Gets or sets the Y border radius. Set to 0 to get full rectangles.\n * @param {Number} [yBorderRadius=6.75]\n * @returns {Number|HeatMap}\n */\n yBorderRadius (yBorderRadius) {\n if (!arguments.length) {\n return this._yBorderRadius;\n }\n this._yBorderRadius = yBorderRadius;\n return this;\n }\n\n isSelectedNode (d) {\n return this.hasFilter(d.key);\n }\n}\n\nexport const heatMap = (parent, chartGroup) => new HeatMap(parent, chartGroup);\n","import {select} from 'd3-selection';\n\nimport {pluck, utils} from '../core/utils';\nimport {d3compat} from '../core/config';\nimport {constants} from '../core/constants';\n\n/**\n * htmlLegend is a attachable widget that can be added to other dc charts to render horizontal/vertical legend\n * labels.\n * @example\n * chart.legend(HtmlLegend().container(legendContainerElement).horizontal(false))\n * @returns {HtmlLegend}\n */\nexport class HtmlLegend {\n constructor () {\n this._htmlLegendDivCssClass = 'dc-html-legend';\n this._legendItemCssClassHorizontal = 'dc-legend-item-horizontal';\n this._legendItemCssClassVertical = 'dc-legend-item-vertical';\n this._parent = undefined;\n this._container = undefined;\n this._legendText = pluck('name');\n this._maxItems = undefined;\n this._horizontal = false;\n this._legendItemClass = undefined;\n this._highlightSelected = false;\n this._keyboardAccessible = false;\n }\n\n parent (p) {\n if (!arguments.length) {\n return this._parent;\n }\n this._parent = p;\n return this;\n }\n\n render () {\n this._defaultLegendItemCssClass = this._horizontal ? this._legendItemCssClassHorizontal : this._legendItemCssClassVertical;\n this._container.select(`div.${this._htmlLegendDivCssClass}`).remove();\n\n const container = this._container.append('div').attr('class', this._htmlLegendDivCssClass);\n container.attr('style', `max-width:${this._container.nodes()[0].style.width}`);\n\n let legendables = this._parent.legendables();\n const filters = this._parent.filters();\n\n if (this._maxItems !== undefined) {\n legendables = legendables.slice(0, this._maxItems);\n }\n\n const legendItemClassName = this._legendItemClass ? this._legendItemClass : this._defaultLegendItemCssClass;\n\n const itemEnter = container.selectAll(`div.${legendItemClassName}`)\n .data(legendables).enter()\n .append('div')\n .classed(legendItemClassName, true)\n .on('mouseover', d3compat.eventHandler(d => this._parent.legendHighlight(d)))\n .on('mouseout', d3compat.eventHandler(d => this._parent.legendReset(d)))\n .on('click', d3compat.eventHandler(d => this._parent.legendToggle(d)));\n\n if (this._highlightSelected) {\n itemEnter.classed(constants.SELECTED_CLASS, d => filters.indexOf(d.name) !== -1);\n }\n\n itemEnter.append('span')\n .attr('class', 'dc-legend-item-color')\n .style('background-color', pluck('color'));\n\n itemEnter.append('span')\n .attr('class', 'dc-legend-item-label')\n .classed('dc-tabbable', this._keyboardAccessible)\n .attr('title', this._legendText)\n .text(this._legendText);\n\n if (this._keyboardAccessible) {\n this._makeLegendKeyboardAccessible();\n }\n }\n\n /**\n * Set the container selector for the legend widget. Required.\n * @param {String} [container]\n * @return {String|HtmlLegend}\n */\n container (container) {\n if (!arguments.length) {\n return this._container;\n }\n this._container = select(container);\n return this;\n }\n\n /**\n * This can be optionally used to override class for legenditem and just use this class style.\n * This is helpful for overriding the style of a particular chart rather than overriding\n * the style for all charts.\n *\n * Setting this will disable the highlighting of selected items also.\n * @param {String} [legendItemClass]\n * @return {String|HtmlLegend}\n */\n legendItemClass (legendItemClass) {\n if (!arguments.length) {\n return this._legendItemClass;\n }\n this._legendItemClass = legendItemClass;\n return this;\n }\n\n /**\n * This can be optionally used to enable highlighting legends for the selections/filters for the\n * chart.\n * @param {String} [highlightSelected]\n * @return {String|HtmlLegend}\n */\n highlightSelected (highlightSelected) {\n if (!arguments.length) {\n return this._highlightSelected;\n }\n this._highlightSelected = highlightSelected;\n return this;\n }\n\n /**\n * Display the legend horizontally instead of vertically\n * @param {String} [horizontal]\n * @return {String|HtmlLegend}\n */\n horizontal (horizontal) {\n if (!arguments.length) {\n return this._horizontal;\n }\n this._horizontal = horizontal;\n return this;\n }\n\n /**\n * Set or get the legend text function. The legend widget uses this function to render the legend\n * text for each item. If no function is specified the legend widget will display the names\n * associated with each group.\n * @param {Function} [legendText]\n * @returns {Function|HtmlLegend}\n * @example\n * // default legendText\n * legend.legendText(pluck('name'))\n *\n * // create numbered legend items\n * chart.legend(new HtmlLegend().legendText(function(d, i) { return i + '. ' + d.name; }))\n *\n * // create legend displaying group counts\n * chart.legend(new HtmlLegend().legendText(function(d) { return d.name + ': ' d.data; }))\n */\n legendText (legendText) {\n if (!arguments.length) {\n return this._legendText;\n }\n this._legendText = legendText;\n return this;\n }\n\n /**\n * Maximum number of legend items to display\n * @param {Number} [maxItems]\n * @return {HtmlLegend}\n */\n maxItems (maxItems) {\n if (!arguments.length) {\n return this._maxItems;\n }\n this._maxItems = utils.isNumber(maxItems) ? maxItems : undefined;\n return this;\n }\n\n /**\n * If set, individual legend items will be focusable from keyboard and on pressing Enter or Space\n * will behave as if clicked on.\n * \n * If `svgDescription` on the parent chart has not been explicitly set, will also set the default \n * SVG description text to the class constructor name, like BarChart or HeatMap, and make the entire\n * SVG focusable.\n * @param {Boolean} [keyboardAccessible=false]\n * @returns {Boolean|HtmlLegend}\n */\n keyboardAccessible (keyboardAccessible) {\n if (!arguments.length) {\n return this._keyboardAccessible;\n }\n this._keyboardAccessible = keyboardAccessible;\n return this;\n }\n\n _makeLegendKeyboardAccessible () {\n\n if (!this._parent._svgDescription) {\n\n this._parent.svg().append('desc')\n .attr('id', `desc-id-${this._parent.__dcFlag__}`)\n .html(`${this._parent.svgDescription()}`);\n\n this._parent.svg()\n .attr('tabindex', '0')\n .attr('role', 'img')\n .attr('aria-labelledby', `desc-id-${this._parent.__dcFlag__}`);\n }\n\n const tabElements = this.container()\n .selectAll('.dc-legend-item-label.dc-tabbable')\n .attr('tabindex', 0);\n\n tabElements\n .on('keydown', d3compat.eventHandler((d, event) => {\n // trigger only if d is an object\n if (event.keyCode === 13 && typeof d === 'object') {\n d.chart.legendToggle(d)\n } \n // special case for space key press - prevent scrolling\n if (event.keyCode === 32 && typeof d === 'object') {\n d.chart.legendToggle(d)\n event.preventDefault(); \n }\n }))\n .on('focus', d3compat.eventHandler(d => {\n this._parent.legendHighlight(d);\n }))\n .on('blur', d3compat.eventHandler(d => {\n this._parent.legendReset(d);\n }));\n }\n}\n\nexport const htmlLegend = () => new HtmlLegend();\n","import {pluck, utils} from '../core/utils';\nimport {d3compat} from '../core/config';\nimport {constants} from '../core/constants';\n\nconst LABEL_GAP = 2;\n\n/**\n * Legend is a attachable widget that can be added to other dc charts to render horizontal legend\n * labels.\n *\n * Examples:\n * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}\n * - {@link http://dc-js.github.com/dc.js/crime/index.html Canadian City Crime Stats}\n * @example\n * chart.legend(new Legend().x(400).y(10).itemHeight(13).gap(5))\n * @returns {Legend}\n */\nexport class Legend {\n constructor () {\n this._parent = undefined;\n this._x = 0;\n this._y = 0;\n this._itemHeight = 12;\n this._gap = 5;\n this._horizontal = false;\n this._legendWidth = 560;\n this._itemWidth = 70;\n this._autoItemWidth = false;\n this._legendText = pluck('name');\n this._maxItems = undefined;\n this._highlightSelected = false;\n this._keyboardAccessible = false;\n\n this._g = undefined;\n }\n\n parent (p) {\n if (!arguments.length) {\n return this._parent;\n }\n this._parent = p;\n return this;\n }\n\n /**\n * Set or get x coordinate for legend widget.\n * @param {Number} [x=0]\n * @returns {Number|Legend}\n */\n x (x) {\n if (!arguments.length) {\n return this._x;\n }\n this._x = x;\n return this;\n }\n\n /**\n * Set or get y coordinate for legend widget.\n * @param {Number} [y=0]\n * @returns {Number|Legend}\n */\n y (y) {\n if (!arguments.length) {\n return this._y;\n }\n this._y = y;\n return this;\n }\n\n /**\n * Set or get gap between legend items.\n * @param {Number} [gap=5]\n * @returns {Number|Legend}\n */\n gap (gap) {\n if (!arguments.length) {\n return this._gap;\n }\n this._gap = gap;\n return this;\n }\n\n /**\n * This can be optionally used to enable highlighting legends for the selections/filters for the\n * chart.\n * @param {String} [highlightSelected]\n * @return {String|dc.legend}\n **/\n highlightSelected (highlightSelected) {\n if (!arguments.length) {\n return this._highlightSelected;\n }\n this._highlightSelected = highlightSelected;\n return this;\n }\n\n /**\n * Set or get legend item height.\n * @param {Number} [itemHeight=12]\n * @returns {Number|Legend}\n */\n itemHeight (itemHeight) {\n if (!arguments.length) {\n return this._itemHeight;\n }\n this._itemHeight = itemHeight;\n return this;\n }\n\n /**\n * Position legend horizontally instead of vertically.\n * @param {Boolean} [horizontal=false]\n * @returns {Boolean|Legend}\n */\n horizontal (horizontal) {\n if (!arguments.length) {\n return this._horizontal;\n }\n this._horizontal = horizontal;\n return this;\n }\n\n /**\n * Maximum width for horizontal legend.\n * @param {Number} [legendWidth=500]\n * @returns {Number|Legend}\n */\n legendWidth (legendWidth) {\n if (!arguments.length) {\n return this._legendWidth;\n }\n this._legendWidth = legendWidth;\n return this;\n }\n\n /**\n * Legend item width for horizontal legend.\n * @param {Number} [itemWidth=70]\n * @returns {Number|Legend}\n */\n itemWidth (itemWidth) {\n if (!arguments.length) {\n return this._itemWidth;\n }\n this._itemWidth = itemWidth;\n return this;\n }\n\n /**\n * Turn automatic width for legend items on or off. If true, {@link Legend#itemWidth itemWidth} is ignored.\n * This setting takes into account the {@link Legend#gap gap}.\n * @param {Boolean} [autoItemWidth=false]\n * @returns {Boolean|Legend}\n */\n autoItemWidth (autoItemWidth) {\n if (!arguments.length) {\n return this._autoItemWidth;\n }\n this._autoItemWidth = autoItemWidth;\n return this;\n }\n\n /**\n * Set or get the legend text function. The legend widget uses this function to render the legend\n * text for each item. If no function is specified the legend widget will display the names\n * associated with each group.\n * @param {Function} [legendText]\n * @returns {Function|Legend}\n * @example\n * // default legendText\n * legend.legendText(pluck('name'))\n *\n * // create numbered legend items\n * chart.legend(new Legend().legendText(function(d, i) { return i + '. ' + d.name; }))\n *\n * // create legend displaying group counts\n * chart.legend(new Legend().legendText(function(d) { return d.name + ': ' d.data; }))\n */\n legendText (legendText) {\n if (!arguments.length) {\n return this._legendText;\n }\n this._legendText = legendText;\n return this;\n }\n\n /**\n * Maximum number of legend items to display\n * @param {Number} [maxItems]\n * @return {Legend}\n */\n maxItems (maxItems) {\n if (!arguments.length) {\n return this._maxItems;\n }\n this._maxItems = utils.isNumber(maxItems) ? maxItems : undefined;\n return this;\n }\n\n /**\n * If set, individual legend items will be focusable from keyboard and on pressing Enter or Space\n * will behave as if clicked on.\n * \n * If `svgDescription` on the parent chart has not been explicitly set, will also set the default \n * SVG description text to the class constructor name, like BarChart or HeatMap, and make the entire\n * SVG focusable.\n * @param {Boolean} [keyboardAccessible=false]\n * @returns {Boolean|Legend}\n */\n keyboardAccessible (keyboardAccessible) {\n if (!arguments.length) {\n return this._keyboardAccessible;\n }\n this._keyboardAccessible = keyboardAccessible;\n return this;\n }\n\n // Implementation methods\n\n _legendItemHeight () {\n return this._gap + this._itemHeight;\n }\n\n _makeLegendKeyboardAccessible () {\n\n if (!this._parent._svgDescription) {\n\n this._parent.svg().append('desc')\n .attr('id', `desc-id-${this._parent.__dcFlag__}`)\n .html(`${this._parent.svgDescription()}`);\n\n this._parent.svg()\n .attr('tabindex', '0')\n .attr('role', 'img')\n .attr('aria-labelledby', `desc-id-${this._parent.__dcFlag__}`);\n }\n\n const tabElements = this._parent.svg()\n .selectAll('.dc-legend .dc-tabbable')\n .attr('tabindex', 0);\n\n tabElements\n .on('keydown', d3compat.eventHandler((d, event) => {\n // trigger only if d is an object\n if (event.keyCode === 13 && typeof d === 'object') {\n d.chart.legendToggle(d)\n } \n // special case for space key press - prevent scrolling\n if (event.keyCode === 32 && typeof d === 'object') {\n d.chart.legendToggle(d)\n event.preventDefault(); \n }\n }))\n .on('focus', d3compat.eventHandler(d => {\n this._parent.legendHighlight(d);\n }))\n .on('blur', d3compat.eventHandler(d => {\n this._parent.legendReset(d);\n }));\n }\n\n render () {\n this._parent.svg().select('g.dc-legend').remove();\n this._g = this._parent.svg().append('g')\n .attr('class', 'dc-legend')\n .attr('transform', `translate(${this._x},${this._y})`);\n let legendables = this._parent.legendables();\n const filters = this._parent.filters();\n\n if (this._maxItems !== undefined) {\n legendables = legendables.slice(0, this._maxItems);\n }\n\n const itemEnter = this._g.selectAll('g.dc-legend-item')\n .data(legendables)\n .enter()\n .append('g')\n .attr('class', 'dc-legend-item')\n .on('mouseover', d3compat.eventHandler(d => {\n this._parent.legendHighlight(d);\n }))\n .on('mouseout', d3compat.eventHandler(d => {\n this._parent.legendReset(d);\n }))\n .on('click', d3compat.eventHandler(d => {\n d.chart.legendToggle(d);\n }));\n\n if (this._highlightSelected) {\n itemEnter.classed(constants.SELECTED_CLASS,\n d => filters.indexOf(d.name) !== -1);\n }\n\n\n this._g.selectAll('g.dc-legend-item')\n .classed('fadeout', d => d.chart.isLegendableHidden(d));\n\n if (legendables.some(pluck('dashstyle'))) {\n itemEnter\n .append('line')\n .attr('x1', 0)\n .attr('y1', this._itemHeight / 2)\n .attr('x2', this._itemHeight)\n .attr('y2', this._itemHeight / 2)\n .attr('stroke-width', 2)\n .attr('stroke-dasharray', pluck('dashstyle'))\n .attr('stroke', pluck('color'));\n } else {\n itemEnter\n .append('rect')\n .attr('width', this._itemHeight)\n .attr('height', this._itemHeight)\n .attr('fill', d => d ? d.color : 'blue');\n }\n\n {\n const self = this;\n\n itemEnter.append('text')\n .text(self._legendText)\n .classed('dc-tabbable', this._keyboardAccessible)\n .attr('x', self._itemHeight + LABEL_GAP)\n .attr('y', function () {\n return self._itemHeight / 2 + (this.clientHeight ? this.clientHeight : 13) / 2 - 2;\n });\n\n if (this._keyboardAccessible) {\n this._makeLegendKeyboardAccessible();\n }\n }\n\n let cumulativeLegendTextWidth = 0;\n let row = 0;\n\n {\n const self = this;\n\n itemEnter.attr('transform', function (d, i) {\n if (self._horizontal) {\n const itemWidth = self._autoItemWidth === true ? this.getBBox().width + self._gap : self._itemWidth;\n if ((cumulativeLegendTextWidth + itemWidth) > self._legendWidth && cumulativeLegendTextWidth > 0) {\n ++row;\n cumulativeLegendTextWidth = 0;\n }\n const translateBy = `translate(${cumulativeLegendTextWidth},${row * self._legendItemHeight()})`;\n cumulativeLegendTextWidth += itemWidth;\n return translateBy;\n } else {\n return `translate(0,${i * self._legendItemHeight()})`;\n }\n });\n }\n }\n\n}\n\nexport const legend = () => new Legend();\n","import {\n area,\n curveBasis,\n curveBasisClosed,\n curveBasisOpen,\n curveBundle,\n curveCardinal,\n curveCardinalClosed,\n curveCardinalOpen,\n curveLinear,\n curveLinearClosed,\n curveMonotoneX,\n curveStep,\n curveStepAfter,\n curveStepBefore,\n line\n} from 'd3-shape';\nimport {select} from 'd3-selection';\n\nimport {logger} from '../core/logger';\nimport {pluck, utils} from '../core/utils';\nimport {StackMixin} from '../base/stack-mixin';\nimport {transition} from '../core/core';\n\nconst DEFAULT_DOT_RADIUS = 5;\nconst TOOLTIP_G_CLASS = 'dc-tooltip';\nconst DOT_CIRCLE_CLASS = 'dot';\nconst Y_AXIS_REF_LINE_CLASS = 'yRef';\nconst X_AXIS_REF_LINE_CLASS = 'xRef';\nconst DEFAULT_DOT_OPACITY = 1e-6;\nconst LABEL_PADDING = 3;\n\n/**\n * Concrete line/area chart implementation.\n *\n * Examples:\n * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}\n * - {@link http://dc-js.github.com/dc.js/crime/index.html Canadian City Crime Stats}\n * @mixes StackMixin\n * @mixes CoordinateGridMixin\n */\nexport class LineChart extends StackMixin {\n /**\n * Create a Line Chart.\n * @example\n * // create a line chart under #chart-container1 element using the default global chart group\n * var chart1 = new LineChart('#chart-container1');\n * // create a line chart under #chart-container2 element using chart group A\n * var chart2 = new LineChart('#chart-container2', 'chartGroupA');\n * // create a sub-chart under a composite parent chart\n * var chart3 = new LineChart(compositeChart);\n * @param {String|node|d3.selection|CompositeChart} parent - Any valid\n * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector}\n * specifying a dom block element such as a div; or a dom element or d3 selection. If the line\n * chart is a sub-chart in a {@link CompositeChart Composite Chart} then pass in the parent\n * composite chart instance instead.\n * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.\n * Interaction with a chart will only trigger events and redraws within the chart's group.\n */\n constructor (parent, chartGroup) {\n super();\n\n this._renderArea = false;\n this._dotRadius = DEFAULT_DOT_RADIUS;\n this._dataPointRadius = null;\n this._dataPointFillOpacity = DEFAULT_DOT_OPACITY;\n this._dataPointStrokeOpacity = DEFAULT_DOT_OPACITY;\n this._curve = null;\n this._interpolate = null; // d3.curveLinear; // deprecated in 3.0\n this._tension = null; // deprecated in 3.0\n this._defined = undefined;\n this._dashStyle = undefined;\n this._xyTipsOn = true;\n\n this.transitionDuration(500);\n this.transitionDelay(0);\n this._rangeBandPadding(1);\n\n this.label(d => utils.printSingleValue(d.y0 + d.y), false);\n\n this.anchor(parent, chartGroup);\n }\n\n plotData () {\n const chartBody = this.chartBodyG();\n let layersList = chartBody.select('g.stack-list');\n\n if (layersList.empty()) {\n layersList = chartBody.append('g').attr('class', 'stack-list');\n }\n\n let layers = layersList.selectAll('g.stack').data(this.data());\n\n const layersEnter = layers\n .enter()\n .append('g')\n .attr('class', (d, i) => `stack _${i}`);\n\n layers = layersEnter.merge(layers);\n\n this._drawLine(layersEnter, layers);\n\n this._drawArea(layersEnter, layers);\n\n this._drawDots(chartBody, layers);\n\n if (this.renderLabel()) {\n this._drawLabels(layers);\n }\n }\n\n /**\n * Gets or sets the curve factory to use for lines and areas drawn, allowing e.g. step\n * functions, splines, and cubic interpolation. Typically you would use one of the interpolator functions\n * provided by {@link https://github.com/d3/d3-shape/blob/master/README.md#curves d3 curves}.\n *\n * Replaces the use of {@link LineChart#interpolate} and {@link LineChart#tension}\n * in dc.js < 3.0\n *\n * This is passed to\n * {@link https://github.com/d3/d3-shape/blob/master/README.md#line_curve line.curve} and\n * {@link https://github.com/d3/d3-shape/blob/master/README.md#area_curve area.curve}.\n * @example\n * // default\n * chart\n * .curve(d3.curveLinear);\n * // Add tension to curves that support it\n * chart\n * .curve(d3.curveCardinal.tension(0.5));\n * // You can use some specialized variation like\n * // https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline\n * chart\n * .curve(d3.curveCatmullRom.alpha(0.5));\n * @see {@link https://github.com/d3/d3-shape/blob/master/README.md#line_curve line.curve}\n * @see {@link https://github.com/d3/d3-shape/blob/master/README.md#area_curve area.curve}\n * @param {d3.curve} [curve=d3.curveLinear]\n * @returns {d3.curve|LineChart}\n */\n curve (curve) {\n if (!arguments.length) {\n return this._curve;\n }\n this._curve = curve;\n return this;\n }\n\n /**\n * Gets or sets the interpolator to use for lines drawn, by string name, allowing e.g. step\n * functions, splines, and cubic interpolation.\n *\n * Possible values are: 'linear', 'linear-closed', 'step', 'step-before', 'step-after', 'basis',\n * 'basis-open', 'basis-closed', 'bundle', 'cardinal', 'cardinal-open', 'cardinal-closed', and\n * 'monotone'.\n *\n * This function exists for backward compatibility. Use {@link LineChart#curve}\n * which is generic and provides more options.\n * Value set through `.curve` takes precedence over `.interpolate` and `.tension`.\n * @deprecated since version 3.0 use {@link LineChart#curve} instead\n * @see {@link LineChart#curve}\n * @param {d3.curve} [interpolate=d3.curveLinear]\n * @returns {d3.curve|LineChart}\n */\n interpolate (interpolate) {\n logger.warnOnce('dc.lineChart.interpolate has been deprecated since version 3.0 use dc.lineChart.curve instead');\n if (!arguments.length) {\n return this._interpolate;\n }\n this._interpolate = interpolate;\n return this;\n }\n\n /**\n * Gets or sets the tension to use for lines drawn, in the range 0 to 1.\n *\n * Passed to the {@link https://github.com/d3/d3-shape/blob/master/README.md#curves d3 curve function}\n * if it provides a `.tension` function. Example:\n * {@link https://github.com/d3/d3-shape/blob/master/README.md#curveCardinal_tension curveCardinal.tension}.\n *\n * This function exists for backward compatibility. Use {@link LineChart#curve}\n * which is generic and provides more options.\n * Value set through `.curve` takes precedence over `.interpolate` and `.tension`.\n * @deprecated since version 3.0 use {@link LineChart#curve} instead\n * @see {@link LineChart#curve}\n * @param {Number} [tension=0]\n * @returns {Number|LineChart}\n */\n tension (tension) {\n logger.warnOnce('dc.lineChart.tension has been deprecated since version 3.0 use dc.lineChart.curve instead');\n if (!arguments.length) {\n return this._tension;\n }\n this._tension = tension;\n return this;\n }\n\n /**\n * Gets or sets a function that will determine discontinuities in the line which should be\n * skipped: the path will be broken into separate subpaths if some points are undefined.\n * This function is passed to\n * {@link https://github.com/d3/d3-shape/blob/master/README.md#line_defined line.defined}\n *\n * Note: crossfilter will sometimes coerce nulls to 0, so you may need to carefully write\n * custom reduce functions to get this to work, depending on your data. See\n * {@link https://github.com/dc-js/dc.js/issues/615#issuecomment-49089248 this GitHub comment}\n * for more details and an example.\n * @see {@link https://github.com/d3/d3-shape/blob/master/README.md#line_defined line.defined}\n * @param {Function} [defined]\n * @returns {Function|LineChart}\n */\n defined (defined) {\n if (!arguments.length) {\n return this._defined;\n }\n this._defined = defined;\n return this;\n }\n\n /**\n * Set the line's d3 dashstyle. This value becomes the 'stroke-dasharray' of line. Defaults to empty\n * array (solid line).\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray stroke-dasharray}\n * @example\n * // create a Dash Dot Dot Dot\n * chart.dashStyle([3,1,1,1]);\n * @param {Array} [dashStyle=[]]\n * @returns {Array|LineChart}\n */\n dashStyle (dashStyle) {\n if (!arguments.length) {\n return this._dashStyle;\n }\n this._dashStyle = dashStyle;\n return this;\n }\n\n /**\n * Get or set render area flag. If the flag is set to true then the chart will render the area\n * beneath each line and the line chart effectively becomes an area chart.\n * @param {Boolean} [renderArea=false]\n * @returns {Boolean|LineChart}\n */\n renderArea (renderArea) {\n if (!arguments.length) {\n return this._renderArea;\n }\n this._renderArea = renderArea;\n return this;\n }\n\n _getColor (d, i) {\n return this.getColor.call(d, d.values, i);\n }\n\n // To keep it backward compatible, this covers multiple cases\n // See https://github.com/dc-js/dc.js/issues/1376\n // It will be removed when interpolate and tension are removed.\n _getCurveFactory () {\n let curve = null;\n\n // _curve takes precedence\n if (this._curve) {\n return this._curve;\n }\n\n // Approximate the D3v3 behavior\n if (typeof this._interpolate === 'function') {\n curve = this._interpolate;\n } else {\n // If _interpolate is string\n const mapping = {\n 'linear': curveLinear,\n 'linear-closed': curveLinearClosed,\n 'step': curveStep,\n 'step-before': curveStepBefore,\n 'step-after': curveStepAfter,\n 'basis': curveBasis,\n 'basis-open': curveBasisOpen,\n 'basis-closed': curveBasisClosed,\n 'bundle': curveBundle,\n 'cardinal': curveCardinal,\n 'cardinal-open': curveCardinalOpen,\n 'cardinal-closed': curveCardinalClosed,\n 'monotone': curveMonotoneX\n };\n curve = mapping[this._interpolate];\n }\n\n // Default value\n if (!curve) {\n curve = curveLinear;\n }\n\n if (this._tension !== null) {\n if (typeof curve.tension !== 'function') {\n logger.warn('tension was specified but the curve/interpolate does not support it.');\n } else {\n curve = curve.tension(this._tension);\n }\n }\n return curve;\n }\n\n _drawLine (layersEnter, layers) {\n const _line = line()\n .x(d => this.x()(d.x))\n .y(d => this.y()(d.y + d.y0))\n .curve(this._getCurveFactory());\n if (this._defined) {\n _line.defined(this._defined);\n }\n\n const path = layersEnter.append('path')\n .attr('class', 'line')\n .attr('stroke', (d, i) => this._getColor(d, i));\n if (this._dashStyle) {\n path.attr('stroke-dasharray', this._dashStyle);\n }\n\n transition(layers.select('path.line'), this.transitionDuration(), this.transitionDelay())\n //.ease('linear')\n .attr('stroke', (d, i) => this._getColor(d, i))\n .attr('d', d => this._safeD(_line(d.values)));\n }\n\n _drawArea (layersEnter, layers) {\n if (this._renderArea) {\n const _area = area()\n .x(d => this.x()(d.x))\n .y1(d => this.y()(d.y + d.y0))\n .y0(d => this.y()(d.y0))\n .curve(this._getCurveFactory());\n if (this._defined) {\n _area.defined(this._defined);\n }\n\n layersEnter.append('path')\n .attr('class', 'area')\n .attr('fill', (d, i) => this._getColor(d, i))\n .attr('d', d => this._safeD(_area(d.values)));\n\n transition(layers.select('path.area'), this.transitionDuration(), this.transitionDelay())\n //.ease('linear')\n .attr('fill', (d, i) => this._getColor(d, i))\n .attr('d', d => this._safeD(_area(d.values)));\n }\n }\n\n _safeD (d) {\n return (!d || d.indexOf('NaN') >= 0) ? 'M0,0' : d;\n }\n\n _drawDots (chartBody, layers) {\n if (this.xyTipsOn() === 'always' || (!(this.brushOn() || this.parentBrushOn()) && this.xyTipsOn())) {\n const tooltipListClass = `${TOOLTIP_G_CLASS}-list`;\n let tooltips = chartBody.select(`g.${tooltipListClass}`);\n\n if (tooltips.empty()) {\n tooltips = chartBody.append('g').attr('class', tooltipListClass);\n }\n\n layers.each((data, layerIndex) => {\n let points = data.values;\n if (this._defined) {\n points = points.filter(this._defined);\n }\n\n let g = tooltips.select(`g.${TOOLTIP_G_CLASS}._${layerIndex}`);\n if (g.empty()) {\n g = tooltips.append('g').attr('class', `${TOOLTIP_G_CLASS} _${layerIndex}`);\n }\n\n this._createRefLines(g);\n\n const dots = g.selectAll(`circle.${DOT_CIRCLE_CLASS}`)\n .data(points, pluck('x'));\n\n const chart = this;\n const dotsEnterModify = dots\n .enter()\n .append('circle')\n .attr('class', DOT_CIRCLE_CLASS)\n .classed('dc-tabbable', this._keyboardAccessible)\n .attr('cx', d => utils.safeNumber(this.x()(d.x)))\n .attr('cy', d => utils.safeNumber(this.y()(d.y + d.y0)))\n .attr('r', this._getDotRadius())\n .style('fill-opacity', this._dataPointFillOpacity)\n .style('stroke-opacity', this._dataPointStrokeOpacity)\n .attr('fill', this.getColor)\n .attr('stroke', this.getColor)\n .on('mousemove', function () {\n const dot = select(this);\n chart._showDot(dot);\n chart._showRefLines(dot, g);\n })\n .on('mouseout', function () {\n const dot = select(this);\n chart._hideDot(dot);\n chart._hideRefLines(g);\n })\n .merge(dots);\n\n // special case for on-focus for line chart and its dots\n if (this._keyboardAccessible) {\n\n this._svg.selectAll('.dc-tabbable')\n .attr('tabindex', 0)\n .on('focus', function () {\n const dot = select(this);\n chart._showDot(dot);\n chart._showRefLines(dot, g);\n })\n .on('blur', function () {\n const dot = select(this);\n chart._hideDot(dot);\n chart._hideRefLines(g);\n });\n }\n\n dotsEnterModify.call(dot => this._doRenderTitle(dot, data));\n\n transition(dotsEnterModify, this.transitionDuration())\n .attr('cx', d => utils.safeNumber(this.x()(d.x)))\n .attr('cy', d => utils.safeNumber(this.y()(d.y + d.y0)))\n .attr('fill', this.getColor);\n\n dots.exit().remove();\n });\n }\n }\n\n _drawLabels (layers) {\n const chart = this;\n layers.each(function (data, layerIndex) {\n const layer = select(this);\n const labels = layer.selectAll('text.lineLabel')\n .data(data.values, pluck('x'));\n\n const labelsEnterModify = labels\n .enter()\n .append('text')\n .attr('class', 'lineLabel')\n .attr('text-anchor', 'middle')\n .merge(labels);\n\n transition(labelsEnterModify, chart.transitionDuration())\n .attr('x', d => utils.safeNumber(chart.x()(d.x)))\n .attr('y', d => {\n const y = chart.y()(d.y + d.y0) - LABEL_PADDING;\n return utils.safeNumber(y);\n })\n .text(d => chart.label()(d));\n\n transition(labels.exit(), chart.transitionDuration())\n .attr('height', 0)\n .remove();\n });\n }\n\n _createRefLines (g) {\n const yRefLine = g.select(`path.${Y_AXIS_REF_LINE_CLASS}`).empty() ?\n g.append('path').attr('class', Y_AXIS_REF_LINE_CLASS) : g.select(`path.${Y_AXIS_REF_LINE_CLASS}`);\n yRefLine.style('display', 'none').attr('stroke-dasharray', '5,5');\n\n const xRefLine = g.select(`path.${X_AXIS_REF_LINE_CLASS}`).empty() ?\n g.append('path').attr('class', X_AXIS_REF_LINE_CLASS) : g.select(`path.${X_AXIS_REF_LINE_CLASS}`);\n xRefLine.style('display', 'none').attr('stroke-dasharray', '5,5');\n }\n\n _showDot (dot) {\n dot.style('fill-opacity', 0.8);\n dot.style('stroke-opacity', 0.8);\n dot.attr('r', this._dotRadius);\n return dot;\n }\n\n _showRefLines (dot, g) {\n const x = dot.attr('cx');\n const y = dot.attr('cy');\n const yAxisX = (this._yAxisX() - this.margins().left);\n const yAxisRefPathD = `M${yAxisX} ${y}L${x} ${y}`;\n const xAxisRefPathD = `M${x} ${this.yAxisHeight()}L${x} ${y}`;\n g.select(`path.${Y_AXIS_REF_LINE_CLASS}`).style('display', '').attr('d', yAxisRefPathD);\n g.select(`path.${X_AXIS_REF_LINE_CLASS}`).style('display', '').attr('d', xAxisRefPathD);\n }\n\n _getDotRadius () {\n return this._dataPointRadius || this._dotRadius;\n }\n\n _hideDot (dot) {\n dot.style('fill-opacity', this._dataPointFillOpacity)\n .style('stroke-opacity', this._dataPointStrokeOpacity)\n .attr('r', this._getDotRadius());\n }\n\n _hideRefLines (g) {\n g.select(`path.${Y_AXIS_REF_LINE_CLASS}`).style('display', 'none');\n g.select(`path.${X_AXIS_REF_LINE_CLASS}`).style('display', 'none');\n }\n\n _doRenderTitle (dot, d) {\n if (this.renderTitle()) {\n dot.select('title').remove();\n dot.append('title').text(pluck('data', this.title(d.name)));\n }\n }\n\n /**\n * Turn on/off the mouseover behavior of an individual data point which renders a circle and x/y axis\n * dashed lines back to each respective axis. This is ignored if the chart\n * {@link CoordinateGridMixin#brushOn brush} is on\n * @param {Boolean} [xyTipsOn=false]\n * @returns {Boolean|LineChart}\n */\n xyTipsOn (xyTipsOn) {\n if (!arguments.length) {\n return this._xyTipsOn;\n }\n this._xyTipsOn = xyTipsOn;\n return this;\n }\n\n /**\n * Get or set the radius (in px) for dots displayed on the data points.\n * @param {Number} [dotRadius=5]\n * @returns {Number|LineChart}\n */\n dotRadius (dotRadius) {\n if (!arguments.length) {\n return this._dotRadius;\n }\n this._dotRadius = dotRadius;\n return this;\n }\n\n /**\n * Always show individual dots for each datapoint.\n *\n * If `options` is falsy, it disables data point rendering. If no `options` are provided, the\n * current `options` values are instead returned.\n * @example\n * chart.renderDataPoints({radius: 2, fillOpacity: 0.8, strokeOpacity: 0.0})\n * @param {{fillOpacity: Number, strokeOpacity: Number, radius: Number}} [options={fillOpacity: 0.8, strokeOpacity: 0.0, radius: 2}]\n * @returns {{fillOpacity: Number, strokeOpacity: Number, radius: Number}|LineChart}\n */\n renderDataPoints (options) {\n if (!arguments.length) {\n return {\n fillOpacity: this._dataPointFillOpacity,\n strokeOpacity: this._dataPointStrokeOpacity,\n radius: this._dataPointRadius\n };\n } else if (!options) {\n this._dataPointFillOpacity = DEFAULT_DOT_OPACITY;\n this._dataPointStrokeOpacity = DEFAULT_DOT_OPACITY;\n this._dataPointRadius = null;\n } else {\n this._dataPointFillOpacity = options.fillOpacity || 0.8;\n this._dataPointStrokeOpacity = options.strokeOpacity || 0.0;\n this._dataPointRadius = options.radius || 2;\n }\n return this;\n }\n\n _colorFilter (color, dashstyle, inv) {\n return function () {\n const item = select(this);\n const match = (item.attr('stroke') === color &&\n item.attr('stroke-dasharray') === ((dashstyle instanceof Array) ?\n dashstyle.join(',') : null)) || item.attr('fill') === color;\n return inv ? !match : match;\n };\n }\n\n legendHighlight (d) {\n if (!this.isLegendableHidden(d)) {\n this.g().selectAll('path.line, path.area')\n .classed('highlight', this._colorFilter(d.color, d.dashstyle))\n .classed('fadeout', this._colorFilter(d.color, d.dashstyle, true));\n }\n }\n\n legendReset () {\n this.g().selectAll('path.line, path.area')\n .classed('highlight', false)\n .classed('fadeout', false);\n }\n\n legendables () {\n const legendables = super.legendables();\n if (!this._dashStyle) {\n return legendables;\n }\n return legendables.map(l => {\n l.dashstyle = this._dashStyle;\n return l;\n });\n }\n}\n\nexport const lineChart = (parent, chartGroup) => new LineChart(parent, chartGroup);\n","import {format} from 'd3-format';\nimport {easeQuad} from 'd3-ease';\nimport {interpolateNumber} from 'd3-interpolate';\n\nimport {BaseMixin} from '../base/base-mixin';\n\nconst SPAN_CLASS = 'number-display';\n\n/**\n * A display of a single numeric value.\n *\n * Unlike other charts, you do not need to set a dimension. Instead a group object must be provided and\n * a valueAccessor that returns a single value.\n *\n * If the group is a {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#crossfilter_groupAll groupAll}\n * then its `.value()` will be displayed. This is the recommended usage.\n *\n * However, if it is given an ordinary group, the `numberDisplay` will show the last bin's value, after\n * sorting with the {@link https://dc-js.github.io/dc.js/docs/html/dc.baseMixin.html#ordering__anchor ordering}\n * function. `numberDisplay` defaults the `ordering` function to sorting by value, so this will display\n * the largest value if the values are numeric.\n * @mixes BaseMixin\n */\nexport class NumberDisplay extends BaseMixin {\n /**\n * Create a Number Display widget.\n *\n * @example\n * // create a number display under #chart-container1 element using the default global chart group\n * var display1 = new NumberDisplay('#chart-container1');\n * @param {String|node|d3.selection} parent - Any valid\n * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying\n * a dom block element such as a div; or a dom element or d3 selection.\n * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.\n * Interaction with a chart will only trigger events and redraws within the chart's group.\n */\n constructor (parent, chartGroup) {\n super();\n\n this._formatNumber = format('.2s');\n this._html = {one: '', some: '', none: ''};\n this._lastValue = undefined;\n this._ariaLiveRegion = false;\n\n // dimension not required\n this._mandatoryAttributes(['group']);\n\n // default to ordering by value, to emulate old group.top(1) behavior when multiple groups\n this.ordering(kv => kv.value);\n\n this.data(group => {\n const valObj = group.value ? group.value() : this._maxBin(group.all());\n return this.valueAccessor()(valObj);\n });\n\n this.transitionDuration(250); // good default\n this.transitionDelay(0);\n\n this.anchor(parent, chartGroup);\n }\n\n /**\n * Gets or sets an optional object specifying HTML templates to use depending on the number\n * displayed. The text `%number` will be replaced with the current value.\n * - one: HTML template to use if the number is 1\n * - zero: HTML template to use if the number is 0\n * - some: HTML template to use otherwise\n * @example\n * numberWidget.html({\n * one:'%number record',\n * some:'%number records',\n * none:'no records'})\n * @param {{one:String, some:String, none:String}} [html={one: '', some: '', none: ''}]\n * @returns {{one:String, some:String, none:String}|NumberDisplay}\n */\n html (html) {\n if (!arguments.length) {\n return this._html;\n }\n if (html.none) {\n this._html.none = html.none;//if none available\n } else if (html.one) {\n this._html.none = html.one;//if none not available use one\n } else if (html.some) {\n this._html.none = html.some;//if none and one not available use some\n }\n if (html.one) {\n this._html.one = html.one;//if one available\n } else if (html.some) {\n this._html.one = html.some;//if one not available use some\n }\n if (html.some) {\n this._html.some = html.some;//if some available\n } else if (html.one) {\n this._html.some = html.one;//if some not available use one\n }\n return this;\n }\n\n /**\n * Calculate and return the underlying value of the display.\n * @returns {Number}\n */\n value () {\n return this.data();\n }\n\n _maxBin (all) {\n if (!all.length) {\n return null;\n }\n const sorted = this._computeOrderedGroups(all);\n return sorted[sorted.length - 1];\n }\n\n _doRender () {\n const newValue = this.value();\n let span = this.selectAll(`.${SPAN_CLASS}`);\n\n if (span.empty()) {\n span = span.data([0])\n .enter()\n .append('span')\n .attr('class', SPAN_CLASS)\n .classed('dc-tabbable', this._keyboardAccessible)\n .merge(span);\n\n if (this._keyboardAccessible) {\n span.attr('tabindex', '0');\n }\n\n if (this._ariaLiveRegion) {\n this.transitionDuration(0);\n span.attr('aria-live', 'polite');\n }\n }\n\n {\n const chart = this;\n span.transition()\n .duration(chart.transitionDuration())\n .delay(chart.transitionDelay())\n .ease(easeQuad)\n .tween('text', function () {\n // [XA] don't try and interpolate from Infinity, else this breaks.\n const interpStart = isFinite(chart._lastValue) ? chart._lastValue : 0;\n const interp = interpolateNumber(interpStart || 0, newValue);\n chart._lastValue = newValue;\n\n // need to save it in D3v4\n const node = this;\n return t => {\n let html = null;\n const num = chart.formatNumber()(interp(t));\n if (newValue === 0 && (chart._html.none !== '')) {\n html = chart._html.none;\n } else if (newValue === 1 && (chart._html.one !== '')) {\n html = chart._html.one;\n } else if (chart._html.some !== '') {\n html = chart._html.some;\n }\n node.innerHTML = html ? html.replace('%number', num) : num;\n };\n });\n }\n }\n\n _doRedraw () {\n return this._doRender();\n }\n\n /**\n * Get or set a function to format the value for the display.\n * @see {@link https://github.com/d3/d3-format/blob/master/README.md#format d3.format}\n * @param {Function} [formatter=d3.format('.2s')]\n * @returns {Function|NumberDisplay}\n */\n formatNumber (formatter) {\n if (!arguments.length) {\n return this._formatNumber;\n }\n this._formatNumber = formatter;\n return this;\n }\n\n /**\n * If set, the Number Display widget will have its aria-live attribute set to 'polite' which will\n * notify screen readers when the widget changes its value. Note that setting this method will also\n * disable the default transition between the old and the new values. This is to avoid change\n * notifications spoken out before the new value finishes re-drawing. It is also advisable to check\n * if the widget has appropriately set accessibility description or label. \n * @param {Boolean} [ariaLiveRegion=false]\n * @returns {Boolean|NumberDisplay}\n */\n ariaLiveRegion (ariaLiveRegion) {\n if (!arguments.length) {\n return this._ariaLiveRegion;\n }\n this._ariaLiveRegion = ariaLiveRegion;\n return this;\n }\n\n}\n\nexport const numberDisplay = (parent, chartGroup) => new NumberDisplay(parent, chartGroup);\n","import {min, sum} from 'd3-array';\nimport {arc, pie} from 'd3-shape';\nimport {select} from 'd3-selection';\nimport {interpolate} from 'd3-interpolate';\n\nimport {CapMixin} from '../base/cap-mixin';\nimport {ColorMixin} from '../base/color-mixin';\nimport {BaseMixin} from '../base/base-mixin';\nimport {transition} from '../core/core';\nimport {d3compat} from '../core/config';\n\nconst DEFAULT_MIN_ANGLE_FOR_LABEL = 0.5;\n\n/**\n * The pie chart implementation is usually used to visualize a small categorical distribution. The pie\n * chart uses keyAccessor to determine the slices, and valueAccessor to calculate the size of each\n * slice relative to the sum of all values. Slices are ordered by {@link BaseMixin#ordering ordering}\n * which defaults to sorting by key.\n *\n * Examples:\n * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}\n * @mixes CapMixin\n * @mixes ColorMixin\n * @mixes BaseMixin\n */\nexport class PieChart extends CapMixin(ColorMixin(BaseMixin)) {\n /**\n * Create a Pie Chart\n *\n * @example\n * // create a pie chart under #chart-container1 element using the default global chart group\n * var chart1 = new PieChart('#chart-container1');\n * // create a pie chart under #chart-container2 element using chart group A\n * var chart2 = new PieChart('#chart-container2', 'chartGroupA');\n * @param {String|node|d3.selection} parent - Any valid\n * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying\n * a dom block element such as a div; or a dom element or d3 selection.\n * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.\n * Interaction with a chart will only trigger events and redraws within the chart's group.\n */\n constructor (parent, chartGroup) {\n super();\n\n this._sliceCssClass = 'pie-slice';\n this._labelCssClass = 'pie-label';\n this._sliceGroupCssClass = 'pie-slice-group';\n this._labelGroupCssClass = 'pie-label-group';\n this._emptyCssClass = 'empty-chart';\n this._emptyTitle = 'empty';\n\n this._radius = undefined;\n this._givenRadius = undefined; // specified radius, if any\n this._innerRadius = 0;\n this._externalRadiusPadding = 0;\n\n\n this._g = undefined;\n this._cx = undefined;\n this._cy = undefined;\n this._minAngleForLabel = DEFAULT_MIN_ANGLE_FOR_LABEL;\n this._externalLabelRadius = undefined;\n this._drawPaths = false;\n\n this.colorAccessor(d => this.cappedKeyAccessor(d));\n\n this.title(d => `${this.cappedKeyAccessor(d)}: ${this.cappedValueAccessor(d)}`);\n\n this.label(d => this.cappedKeyAccessor(d));\n this.renderLabel(true);\n\n this.transitionDuration(350);\n this.transitionDelay(0);\n\n this.anchor(parent, chartGroup);\n }\n\n /**\n * Get or set the maximum number of slices the pie chart will generate. The top slices are determined by\n * value from high to low. Other slices exceeding the cap will be rolled up into one single *Others* slice.\n * @param {Number} [cap]\n * @returns {Number|PieChart}\n */\n slicesCap (cap) {\n return this.cap(cap)\n }\n\n _doRender () {\n this.resetSvg();\n\n this._g = this.svg()\n .append('g')\n .attr('transform', `translate(${this.cx()},${this.cy()})`);\n\n this._g.append('g').attr('class', this._sliceGroupCssClass);\n this._g.append('g').attr('class', this._labelGroupCssClass);\n\n this._drawChart();\n\n return this;\n }\n\n _drawChart () {\n // set radius from chart size if none given, or if given radius is too large\n const maxRadius = min([this.width(), this.height()]) / 2;\n this._radius = this._givenRadius && this._givenRadius < maxRadius ? this._givenRadius : maxRadius;\n\n const arcs = this._buildArcs();\n\n const pieLayout = this._pieLayout();\n let pieData;\n // if we have data...\n if (sum(this.data(), d => this.cappedValueAccessor(d))) {\n pieData = pieLayout(this.data());\n this._g.classed(this._emptyCssClass, false);\n } else {\n // otherwise we'd be getting NaNs, so override\n // note: abuse others for its ignoring the value accessor\n pieData = pieLayout([{key: this._emptyTitle, value: 1, others: [this._emptyTitle]}]);\n this._g.classed(this._emptyCssClass, true);\n }\n\n if (this._g) {\n const slices = this._g.select(`g.${this._sliceGroupCssClass}`)\n .selectAll(`g.${this._sliceCssClass}`)\n .data(pieData);\n\n const labels = this._g.select(`g.${this._labelGroupCssClass}`)\n .selectAll(`text.${this._labelCssClass}`)\n .data(pieData);\n\n this._removeElements(slices, labels);\n\n this._createElements(slices, labels, arcs, pieData);\n\n this._updateElements(pieData, arcs);\n\n this._highlightFilter();\n\n transition(this._g, this.transitionDuration(), this.transitionDelay())\n .attr('transform', `translate(${this.cx()},${this.cy()})`);\n }\n }\n\n _createElements (slices, labels, arcs, pieData) {\n const slicesEnter = this._createSliceNodes(slices);\n\n this._createSlicePath(slicesEnter, arcs);\n\n this._createTitles(slicesEnter);\n\n this._createLabels(labels, pieData, arcs);\n }\n\n _createSliceNodes (slices) {\n return slices\n .enter()\n .append('g')\n .attr('class', (d, i) => `${this._sliceCssClass} _${i}`)\n .classed('dc-tabbable', this._keyboardAccessible);\n }\n\n _createSlicePath (slicesEnter, arcs) {\n const slicePath = slicesEnter.append('path')\n .attr('fill', (d, i) => this._fill(d, i))\n .on('click', d3compat.eventHandler(d => this._onClick(d)))\n .attr('d', (d, i) => this._safeArc(d, i, arcs));\n\n if (this._keyboardAccessible) {\n this._makeKeyboardAccessible(this._onClick);\n }\n\n const tranNodes = transition(slicePath, this.transitionDuration(), this.transitionDelay());\n if (tranNodes.attrTween) {\n const chart = this;\n tranNodes.attrTween('d', function (d) {\n return chart._tweenPie(d, this);\n });\n }\n }\n\n _createTitles (slicesEnter) {\n if (this.renderTitle()) {\n slicesEnter.append('title').text(d => this.title()(d.data));\n }\n }\n\n _applyLabelText (labels) {\n labels\n .text(d => {\n const data = d.data;\n if ((this._sliceHasNoData(data) || this._sliceTooSmall(d)) && !this._isSelectedSlice(d)) {\n return '';\n }\n return this.label()(d.data);\n });\n }\n\n _positionLabels (labels, arcs) {\n this._applyLabelText(labels);\n transition(labels, this.transitionDuration(), this.transitionDelay())\n .attr('transform', d => this._labelPosition(d, arcs))\n .attr('text-anchor', 'middle');\n }\n\n _highlightSlice (i, whether) {\n this.select(`g.pie-slice._${i}`)\n .classed('highlight', whether);\n }\n\n _createLabels (labels, pieData, arcs) {\n if (this.renderLabel()) {\n const labelsEnter = labels\n .enter()\n .append('text')\n .attr('class', (d, i) => {\n let classes = `${this._sliceCssClass} ${this._labelCssClass} _${i}`;\n if (this._externalLabelRadius) {\n classes += ' external';\n }\n return classes;\n })\n .on('click', d3compat.eventHandler(d => this._onClick(d)))\n .on('mouseover', d3compat.eventHandler(d => {\n this._highlightSlice(d.index, true);\n }))\n .on('mouseout', d3compat.eventHandler(d => {\n this._highlightSlice(d.index, false);\n }));\n this._positionLabels(labelsEnter, arcs);\n if (this._externalLabelRadius && this._drawPaths) {\n this._updateLabelPaths(pieData, arcs);\n }\n }\n }\n\n _updateLabelPaths (pieData, arcs) {\n let polyline = this._g.selectAll(`polyline.${this._sliceCssClass}`)\n .data(pieData);\n\n polyline.exit().remove();\n\n polyline = polyline\n .enter()\n .append('polyline')\n .attr('class', (d, i) => `pie-path _${i} ${this._sliceCssClass}`)\n .on('click', d3compat.eventHandler(d => this._onClick(d)))\n .on('mouseover', d3compat.eventHandler(d => {\n this._highlightSlice(d.index, true);\n }))\n .on('mouseout', d3compat.eventHandler(d => {\n this._highlightSlice(d.index, false);\n }))\n .merge(polyline);\n\n const arc2 = arc()\n .outerRadius(this._radius - this._externalRadiusPadding + this._externalLabelRadius)\n .innerRadius(this._radius - this._externalRadiusPadding);\n const tranNodes = transition(polyline, this.transitionDuration(), this.transitionDelay());\n // this is one rare case where d3.selection differs from d3.transition\n if (tranNodes.attrTween) {\n tranNodes\n .attrTween('points', function (d) {\n let current = this._current || d;\n current = {startAngle: current.startAngle, endAngle: current.endAngle};\n const _interpolate = interpolate(current, d);\n this._current = _interpolate(0);\n return t => {\n const d2 = _interpolate(t);\n return [arcs.centroid(d2), arc2.centroid(d2)];\n };\n });\n } else {\n tranNodes.attr('points', d => [arcs.centroid(d), arc2.centroid(d)]);\n }\n tranNodes.style('visibility', d => d.endAngle - d.startAngle < 0.0001 ? 'hidden' : 'visible');\n\n }\n\n _updateElements (pieData, arcs) {\n this._updateSlicePaths(pieData, arcs);\n this._updateLabels(pieData, arcs);\n this._updateTitles(pieData);\n }\n\n _updateSlicePaths (pieData, arcs) {\n const slicePaths = this._g.selectAll(`g.${this._sliceCssClass}`)\n .data(pieData)\n .select('path')\n .attr('d', (d, i) => this._safeArc(d, i, arcs));\n const tranNodes = transition(slicePaths, this.transitionDuration(), this.transitionDelay());\n if (tranNodes.attrTween) {\n const chart = this;\n tranNodes.attrTween('d', function (d) {\n return chart._tweenPie(d, this);\n });\n }\n tranNodes.attr('fill', (d, i) => this._fill(d, i));\n }\n\n _updateLabels (pieData, arcs) {\n if (this.renderLabel()) {\n const labels = this._g.selectAll(`text.${this._labelCssClass}`)\n .data(pieData);\n this._positionLabels(labels, arcs);\n if (this._externalLabelRadius && this._drawPaths) {\n this._updateLabelPaths(pieData, arcs);\n }\n }\n }\n\n _updateTitles (pieData) {\n if (this.renderTitle()) {\n this._g.selectAll(`g.${this._sliceCssClass}`)\n .data(pieData)\n .select('title')\n .text(d => this.title()(d.data));\n }\n }\n\n _removeElements (slices, labels) {\n slices.exit().remove();\n labels.exit().remove();\n }\n\n _highlightFilter () {\n const chart = this;\n if (this.hasFilter()) {\n this.selectAll(`g.${this._sliceCssClass}`).each(function (d) {\n if (chart._isSelectedSlice(d)) {\n chart.highlightSelected(this);\n } else {\n chart.fadeDeselected(this);\n }\n });\n } else {\n this.selectAll(`g.${this._sliceCssClass}`).each(function () {\n chart.resetHighlight(this);\n });\n }\n }\n\n /**\n * Get or set the external radius padding of the pie chart. This will force the radius of the\n * pie chart to become smaller or larger depending on the value.\n * @param {Number} [externalRadiusPadding=0]\n * @returns {Number|PieChart}\n */\n externalRadiusPadding (externalRadiusPadding) {\n if (!arguments.length) {\n return this._externalRadiusPadding;\n }\n this._externalRadiusPadding = externalRadiusPadding;\n return this;\n }\n\n /**\n * Get or set the inner radius of the pie chart. If the inner radius is greater than 0px then the\n * pie chart will be rendered as a doughnut chart.\n * @param {Number} [innerRadius=0]\n * @returns {Number|PieChart}\n */\n innerRadius (innerRadius) {\n if (!arguments.length) {\n return this._innerRadius;\n }\n this._innerRadius = innerRadius;\n return this;\n }\n\n /**\n * Get or set the outer radius. If the radius is not set, it will be half of the minimum of the\n * chart width and height.\n * @param {Number} [radius]\n * @returns {Number|PieChart}\n */\n radius (radius) {\n if (!arguments.length) {\n return this._givenRadius;\n }\n this._givenRadius = radius;\n return this;\n }\n\n /**\n * Get or set center x coordinate position. Default is center of svg.\n * @param {Number} [cx]\n * @returns {Number|PieChart}\n */\n cx (cx) {\n if (!arguments.length) {\n return (this._cx || this.width() / 2);\n }\n this._cx = cx;\n return this;\n }\n\n /**\n * Get or set center y coordinate position. Default is center of svg.\n * @param {Number} [cy]\n * @returns {Number|PieChart}\n */\n cy (cy) {\n if (!arguments.length) {\n return (this._cy || this.height() / 2);\n }\n this._cy = cy;\n return this;\n }\n\n _buildArcs () {\n return arc()\n .outerRadius(this._radius - this._externalRadiusPadding)\n .innerRadius(this._innerRadius);\n }\n\n _isSelectedSlice (d) {\n return this.hasFilter(this.cappedKeyAccessor(d.data));\n }\n\n _doRedraw () {\n this._drawChart();\n return this;\n }\n\n /**\n * Get or set the minimal slice angle for label rendering. Any slice with a smaller angle will not\n * display a slice label.\n * @param {Number} [minAngleForLabel=0.5]\n * @returns {Number|PieChart}\n */\n minAngleForLabel (minAngleForLabel) {\n if (!arguments.length) {\n return this._minAngleForLabel;\n }\n this._minAngleForLabel = minAngleForLabel;\n return this;\n }\n\n _pieLayout () {\n return pie().sort(null).value(d => this.cappedValueAccessor(d));\n }\n\n _sliceTooSmall (d) {\n const angle = (d.endAngle - d.startAngle);\n return isNaN(angle) || angle < this._minAngleForLabel;\n }\n\n _sliceHasNoData (d) {\n return this.cappedValueAccessor(d) === 0;\n }\n\n _isOffCanvas (current) {\n return !current || isNaN(current.startAngle) || isNaN(current.endAngle);\n }\n\n _fill (d, i) {\n return this.getColor(d.data, i);\n }\n\n _onClick (d) {\n if (this._g.attr('class') !== this._emptyCssClass) {\n this.onClick(d.data);\n }\n }\n\n _safeArc (d, i, _arc) {\n let path = _arc(d, i);\n if (path.indexOf('NaN') >= 0) {\n path = 'M0,0';\n }\n return path;\n }\n\n /**\n * Title to use for the only slice when there is no data.\n * @param {String} [title]\n * @returns {String|PieChart}\n */\n emptyTitle (title) {\n if (arguments.length === 0) {\n return this._emptyTitle;\n }\n this._emptyTitle = title;\n return this;\n }\n\n /**\n * Position slice labels offset from the outer edge of the chart.\n *\n * The argument specifies the extra radius to be added for slice labels.\n * @param {Number} [externalLabelRadius]\n * @returns {Number|PieChart}\n */\n externalLabels (externalLabelRadius) {\n if (arguments.length === 0) {\n return this._externalLabelRadius;\n } else if (externalLabelRadius) {\n this._externalLabelRadius = externalLabelRadius;\n } else {\n this._externalLabelRadius = undefined;\n }\n\n return this;\n }\n\n /**\n * Get or set whether to draw lines from pie slices to their labels.\n *\n * @param {Boolean} [drawPaths]\n * @returns {Boolean|PieChart}\n */\n drawPaths (drawPaths) {\n if (arguments.length === 0) {\n return this._drawPaths;\n }\n this._drawPaths = drawPaths;\n return this;\n }\n\n _labelPosition (d, _arc) {\n let centroid;\n if (this._externalLabelRadius) {\n centroid = arc()\n .outerRadius(this._radius - this._externalRadiusPadding + this._externalLabelRadius)\n .innerRadius(this._radius - this._externalRadiusPadding + this._externalLabelRadius)\n .centroid(d);\n } else {\n centroid = _arc.centroid(d);\n }\n if (isNaN(centroid[0]) || isNaN(centroid[1])) {\n return 'translate(0,0)';\n } else {\n return `translate(${centroid})`;\n }\n }\n\n legendables () {\n return this.data().map((d, i) => {\n const legendable = {name: d.key, data: d.value, others: d.others, chart: this};\n legendable.color = this.getColor(d, i);\n return legendable;\n });\n }\n\n legendHighlight (d) {\n this._highlightSliceFromLegendable(d, true);\n }\n\n legendReset (d) {\n this._highlightSliceFromLegendable(d, false);\n }\n\n legendToggle (d) {\n this.onClick({key: d.name, others: d.others});\n }\n\n _highlightSliceFromLegendable (legendable, highlighted) {\n this.selectAll('g.pie-slice').each(function (d) {\n if (legendable.name === d.data.key) {\n select(this).classed('highlight', highlighted);\n }\n });\n }\n\n _tweenPie (b, element) {\n b.innerRadius = this._innerRadius;\n let current = element._current;\n if (this._isOffCanvas(current)) {\n current = {startAngle: 0, endAngle: 0};\n } else {\n // only interpolate startAngle & endAngle, not the whole data object\n current = {startAngle: current.startAngle, endAngle: current.endAngle};\n }\n const i = interpolate(current, b);\n element._current = i(0);\n return t => this._safeArc(i(t), 0, this._buildArcs());\n }\n\n\n}\n\nexport const pieChart = (parent, chartGroup) => new PieChart(parent, chartGroup);\n","import {extent} from 'd3-array';\nimport {axisBottom} from 'd3-axis';\nimport {scaleLinear} from 'd3-scale';\n\nimport {CapMixin} from '../base/cap-mixin';\nimport {MarginMixin} from '../base/margin-mixin';\nimport {ColorMixin} from '../base/color-mixin';\nimport {transition} from '../core/core';\nimport {d3compat} from '../core/config';\n\n/**\n * Concrete row chart implementation.\n *\n * Examples:\n * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}\n * @mixes CapMixin\n * @mixes MarginMixin\n * @mixes ColorMixin\n * @mixes BaseMixin\n */\nexport class RowChart extends CapMixin(ColorMixin(MarginMixin)) {\n /**\n * Create a Row Chart.\n * @example\n * // create a row chart under #chart-container1 element using the default global chart group\n * var chart1 = new RowChart('#chart-container1');\n * // create a row chart under #chart-container2 element using chart group A\n * var chart2 = new RowChart('#chart-container2', 'chartGroupA');\n * @param {String|node|d3.selection} parent - Any valid\n * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying\n * a dom block element such as a div; or a dom element or d3 selection.\n * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.\n * Interaction with a chart will only trigger events and redraws within the chart's group.\n */\n constructor (parent, chartGroup) {\n super();\n\n this._g = undefined;\n\n this._labelOffsetX = 10;\n this._labelOffsetY = 15;\n this._hasLabelOffsetY = false;\n this._dyOffset = '0.35em'; // this helps center labels https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#svg_text\n this._titleLabelOffsetX = 2;\n\n this._gap = 5;\n\n this._fixedBarHeight = false;\n this._rowCssClass = 'row';\n this._titleRowCssClass = 'titlerow';\n this._renderTitleLabel = false;\n\n this._x = undefined;\n\n this._elasticX = undefined;\n\n this._xAxis = axisBottom();\n\n this._rowData = undefined;\n\n this.rowsCap = this.cap;\n\n this.title(d => `${this.cappedKeyAccessor(d)}: ${this.cappedValueAccessor(d)}`);\n\n this.label(d => this.cappedKeyAccessor(d));\n\n this.anchor(parent, chartGroup);\n }\n\n _calculateAxisScale () {\n if (!this._x || this._elasticX) {\n const _extent = extent(this._rowData, d => this.cappedValueAccessor(d));\n if (_extent[0] > 0) {\n _extent[0] = 0;\n }\n if (_extent[1] < 0) {\n _extent[1] = 0;\n }\n this._x = scaleLinear().domain(_extent)\n .range([0, this.effectiveWidth()]);\n }\n this._xAxis.scale(this._x);\n }\n\n _drawAxis () {\n let axisG = this._g.select('g.axis');\n\n this._calculateAxisScale();\n\n if (axisG.empty()) {\n axisG = this._g.append('g').attr('class', 'axis');\n }\n axisG.attr('transform', `translate(0, ${this.effectiveHeight()})`);\n\n transition(axisG, this.transitionDuration(), this.transitionDelay())\n .call(this._xAxis);\n }\n\n _doRender () {\n this.resetSvg();\n\n this._g = this.svg()\n .append('g')\n .attr('transform', `translate(${this.margins().left},${this.margins().top})`);\n\n this._drawChart();\n\n return this;\n }\n\n /**\n * Gets or sets the x scale. The x scale can be any d3\n * {@link https://github.com/d3/d3-scale/blob/master/README.md d3.scale}.\n * @see {@link https://github.com/d3/d3-scale/blob/master/README.md d3.scale}\n * @param {d3.scale} [scale]\n * @returns {d3.scale|RowChart}\n */\n x (scale) {\n if (!arguments.length) {\n return this._x;\n }\n this._x = scale;\n return this;\n }\n\n _drawGridLines () {\n this._g.selectAll('g.tick')\n .select('line.grid-line')\n .remove();\n\n this._g.selectAll('g.tick')\n .append('line')\n .attr('class', 'grid-line')\n .attr('x1', 0)\n .attr('y1', 0)\n .attr('x2', 0)\n .attr('y2', () => -this.effectiveHeight());\n }\n\n _drawChart () {\n this._rowData = this.data();\n\n this._drawAxis();\n this._drawGridLines();\n\n let rows = this._g.selectAll(`g.${this._rowCssClass}`)\n .data(this._rowData);\n\n this._removeElements(rows);\n rows = this._createElements(rows)\n .merge(rows);\n this._updateElements(rows);\n }\n\n _createElements (rows) {\n const rowEnter = rows.enter()\n .append('g')\n .attr('class', (d, i) => `${this._rowCssClass} _${i}`);\n\n rowEnter.append('rect').attr('width', 0);\n\n this._createLabels(rowEnter);\n\n return rowEnter;\n }\n\n _removeElements (rows) {\n rows.exit().remove();\n }\n\n _rootValue () {\n const root = this._x(0);\n return (root === -Infinity || root !== root) ? this._x(1) : root;\n }\n\n _updateElements (rows) {\n const n = this._rowData.length;\n\n let height;\n if (!this._fixedBarHeight) {\n height = (this.effectiveHeight() - (n + 1) * this._gap) / n;\n } else {\n height = this._fixedBarHeight;\n }\n\n // vertically align label in center unless they override the value via property setter\n if (!this._hasLabelOffsetY) {\n this._labelOffsetY = height / 2;\n }\n\n const rect = rows.attr('transform', (d, i) => `translate(0,${(i + 1) * this._gap + i * height})`).select('rect')\n .attr('height', height)\n .attr('fill', this.getColor)\n .on('click', d3compat.eventHandler(d => this._onClick(d)))\n .classed('dc-tabbable', this._keyboardAccessible)\n .classed('deselected', d => (this.hasFilter()) ? !this._isSelectedRow(d) : false)\n .classed('selected', d => (this.hasFilter()) ? this._isSelectedRow(d) : false);\n\n if (this._keyboardAccessible) {\n this._makeKeyboardAccessible(d => this._onClick(d));\n }\n\n transition(rect, this.transitionDuration(), this.transitionDelay())\n .attr('width', d => Math.abs(this._rootValue() - this._x(this.cappedValueAccessor(d))))\n .attr('transform', d => this._translateX(d));\n\n this._createTitles(rows);\n this._updateLabels(rows);\n }\n\n _createTitles (rows) {\n if (this.renderTitle()) {\n rows.select('title').remove();\n rows.append('title').text(this.title());\n }\n }\n\n _createLabels (rowEnter) {\n if (this.renderLabel()) {\n rowEnter.append('text')\n .on('click', d3compat.eventHandler(d => this._onClick(d)));\n }\n if (this.renderTitleLabel()) {\n rowEnter.append('text')\n .attr('class', this._titleRowCssClass)\n .on('click', d3compat.eventHandler(d => this._onClick(d)));\n }\n }\n\n _updateLabels (rows) {\n if (this.renderLabel()) {\n const lab = rows.select('text')\n .attr('x', this._labelOffsetX)\n .attr('y', this._labelOffsetY)\n .attr('dy', this._dyOffset)\n .on('click', d3compat.eventHandler(d => this._onClick(d)))\n .attr('class', (d, i) => `${this._rowCssClass} _${i}`)\n .text(d => this.label()(d));\n transition(lab, this.transitionDuration(), this.transitionDelay())\n .attr('transform', d => this._translateX(d));\n }\n if (this.renderTitleLabel()) {\n const titlelab = rows.select(`.${this._titleRowCssClass}`)\n .attr('x', this.effectiveWidth() - this._titleLabelOffsetX)\n .attr('y', this._labelOffsetY)\n .attr('dy', this._dyOffset)\n .attr('text-anchor', 'end')\n .on('click', d3compat.eventHandler(d => this._onClick(d)))\n .attr('class', (d, i) => `${this._titleRowCssClass} _${i}`)\n .text(d => this.title()(d));\n transition(titlelab, this.transitionDuration(), this.transitionDelay())\n .attr('transform', d => this._translateX(d));\n }\n }\n\n /**\n * Turn on/off Title label rendering (values) using SVG style of text-anchor 'end'.\n * @param {Boolean} [renderTitleLabel=false]\n * @returns {Boolean|RowChart}\n */\n renderTitleLabel (renderTitleLabel) {\n if (!arguments.length) {\n return this._renderTitleLabel;\n }\n this._renderTitleLabel = renderTitleLabel;\n return this;\n }\n\n _onClick (d) {\n this.onClick(d);\n }\n\n _translateX (d) {\n const x = this._x(this.cappedValueAccessor(d)),\n x0 = this._rootValue(),\n s = x > x0 ? x0 : x;\n return `translate(${s},0)`;\n }\n\n _doRedraw () {\n this._drawChart();\n return this;\n }\n\n /**\n * Get or sets the x axis for the row chart instance.\n * See the {@link https://github.com/d3/d3-axis/blob/master/README.md d3.axis}\n * documention for more information.\n * @param {d3.axis} [xAxis]\n * @example\n * // customize x axis tick format\n * chart.xAxis().tickFormat(function (v) {return v + '%';});\n * // customize x axis tick values\n * chart.xAxis().tickValues([0, 100, 200, 300]);\n * // use a top-oriented axis. Note: position of the axis and grid lines will need to\n * // be set manually, see https://dc-js.github.io/dc.js/examples/row-top-axis.html\n * chart.xAxis(d3.axisTop())\n * @returns {d3.axis|RowChart}\n */\n xAxis (xAxis) {\n if (!arguments.length) {\n return this._xAxis;\n }\n this._xAxis = xAxis;\n return this;\n }\n\n /**\n * Get or set the fixed bar height. Default is [false] which will auto-scale bars.\n * For example, if you want to fix the height for a specific number of bars (useful in TopN charts)\n * you could fix height as follows (where count = total number of bars in your TopN and gap is\n * your vertical gap space).\n * @example\n * chart.fixedBarHeight( chartheight - (count + 1) * gap / count);\n * @param {Boolean|Number} [fixedBarHeight=false]\n * @returns {Boolean|Number|RowChart}\n */\n fixedBarHeight (fixedBarHeight) {\n if (!arguments.length) {\n return this._fixedBarHeight;\n }\n this._fixedBarHeight = fixedBarHeight;\n return this;\n }\n\n /**\n * Get or set the vertical gap space between rows on a particular row chart instance.\n * @param {Number} [gap=5]\n * @returns {Number|RowChart}\n */\n gap (gap) {\n if (!arguments.length) {\n return this._gap;\n }\n this._gap = gap;\n return this;\n }\n\n /**\n * Get or set the elasticity on x axis. If this attribute is set to true, then the x axis will rescale to auto-fit the\n * data range when filtered.\n * @param {Boolean} [elasticX]\n * @returns {Boolean|RowChart}\n */\n elasticX (elasticX) {\n if (!arguments.length) {\n return this._elasticX;\n }\n this._elasticX = elasticX;\n return this;\n }\n\n /**\n * Get or set the x offset (horizontal space to the top left corner of a row) for labels on a particular row chart.\n * @param {Number} [labelOffsetX=10]\n * @returns {Number|RowChart}\n */\n labelOffsetX (labelOffsetX) {\n if (!arguments.length) {\n return this._labelOffsetX;\n }\n this._labelOffsetX = labelOffsetX;\n return this;\n }\n\n /**\n * Get or set the y offset (vertical space to the top left corner of a row) for labels on a particular row chart.\n * @param {Number} [labelOffsety=15]\n * @returns {Number|RowChart}\n */\n labelOffsetY (labelOffsety) {\n if (!arguments.length) {\n return this._labelOffsetY;\n }\n this._labelOffsetY = labelOffsety;\n this._hasLabelOffsetY = true;\n return this;\n }\n\n /**\n * Get of set the x offset (horizontal space between right edge of row and right edge or text.\n * @param {Number} [titleLabelOffsetX=2]\n * @returns {Number|RowChart}\n */\n titleLabelOffsetX (titleLabelOffsetX) {\n if (!arguments.length) {\n return this._titleLabelOffsetX;\n }\n this._titleLabelOffsetX = titleLabelOffsetX;\n return this;\n }\n\n _isSelectedRow (d) {\n return this.hasFilter(this.cappedKeyAccessor(d));\n }\n}\n\nexport const rowChart = (parent, chartGroup) => new RowChart(parent, chartGroup);\n","import {symbol} from 'd3-shape';\nimport {select} from 'd3-selection';\nimport {brush} from 'd3-brush';\nimport {ascending} from 'd3-array'\n\nimport {CoordinateGridMixin} from '../base/coordinate-grid-mixin';\nimport {optionalTransition, transition} from '../core/core';\nimport {filters} from '../core/filters';\nimport {constants} from '../core/constants';\nimport {events} from '../core/events';\n\n/**\n * A scatter plot chart\n *\n * Examples:\n * - {@link http://dc-js.github.io/dc.js/examples/scatter.html Scatter Chart}\n * - {@link http://dc-js.github.io/dc.js/examples/multi-scatter.html Multi-Scatter Chart}\n * @mixes CoordinateGridMixin\n */\nexport class ScatterPlot extends CoordinateGridMixin {\n /**\n * Create a Scatter Plot.\n * @example\n * // create a scatter plot under #chart-container1 element using the default global chart group\n * var chart1 = new ScatterPlot('#chart-container1');\n * // create a scatter plot under #chart-container2 element using chart group A\n * var chart2 = new ScatterPlot('#chart-container2', 'chartGroupA');\n * // create a sub-chart under a composite parent chart\n * var chart3 = new ScatterPlot(compositeChart);\n * @param {String|node|d3.selection} parent - Any valid\n * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying\n * a dom block element such as a div; or a dom element or d3 selection.\n * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.\n * Interaction with a chart will only trigger events and redraws within the chart's group.\n */\n constructor (parent, chartGroup) {\n super();\n\n this._symbol = symbol();\n\n this._existenceAccessor = d => d.value;\n\n const originalKeyAccessor = this.keyAccessor();\n this.keyAccessor(d => originalKeyAccessor(d)[0]);\n this.valueAccessor(d => originalKeyAccessor(d)[1]);\n this.colorAccessor(() => this._groupName);\n\n // this basically just counteracts the setting of its own key/value accessors\n // see https://github.com/dc-js/dc.js/issues/702\n this.title(d => `${this.keyAccessor()(d)},${this.valueAccessor()(d)}: ${this.existenceAccessor()(d)}`);\n\n this._highlightedSize = 7;\n this._symbolSize = 5;\n this._excludedSize = 3;\n this._excludedColor = null;\n this._excludedOpacity = 1.0;\n this._emptySize = 0;\n this._emptyOpacity = 0;\n this._nonemptyOpacity = 1;\n this._emptyColor = null;\n this._filtered = [];\n this._canvas = null;\n this._context = null;\n this._useCanvas = false;\n\n\n // Use a 2 dimensional brush\n this.brush(brush());\n\n this._symbol.size((d, i) => this._elementSize(d, i));\n\n this.anchor(parent, chartGroup);\n }\n\n // Calculates element radius for canvas plot to be comparable to D3 area based symbol sizes\n _canvasElementSize (d, isFiltered) {\n if (!this._existenceAccessor(d)) {\n return this._emptySize / Math.sqrt(Math.PI);\n } else if (isFiltered) {\n return this._symbolSize / Math.sqrt(Math.PI);\n } else {\n return this._excludedSize / Math.sqrt(Math.PI);\n }\n }\n\n _elementSize (d, i) {\n if (!this._existenceAccessor(d)) {\n return Math.pow(this._emptySize, 2);\n } else if (this._filtered[i]) {\n return Math.pow(this._symbolSize, 2);\n } else {\n return Math.pow(this._excludedSize, 2);\n }\n }\n\n _locator (d) {\n return `translate(${this.x()(this.keyAccessor()(d))},${ \n this.y()(this.valueAccessor()(d))})`;\n }\n\n filter (filter) {\n if (!arguments.length) {\n return super.filter();\n }\n\n return super.filter(filters.RangedTwoDimensionalFilter(filter));\n }\n\n /**\n * Method that replaces original resetSvg and appropriately inserts canvas\n * element along with svg element and sets their CSS properties appropriately\n * so they are overlapped on top of each other.\n * Remove the chart's SVGElements from the dom and recreate the container SVGElement.\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGElement SVGElement}\n * @returns {SVGElement}\n */\n resetSvg () {\n if (!this._useCanvas) {\n return super.resetSvg();\n } else {\n super.resetSvg(); // Perform original svgReset inherited from baseMixin\n this.select('canvas').remove(); // remove old canvas\n\n const svgSel = this.svg();\n const rootSel = this.root();\n\n // Set root node to relative positioning and svg to absolute\n rootSel.style('position', 'relative');\n svgSel.style('position', 'relative');\n\n // Check if SVG element already has any extra top/left CSS offsets\n const svgLeft = isNaN(parseInt(svgSel.style('left'), 10)) ? 0 : parseInt(svgSel.style('left'), 10);\n const svgTop = isNaN(parseInt(svgSel.style('top'), 10)) ? 0 : parseInt(svgSel.style('top'), 10);\n const width = this.effectiveWidth();\n const height = this.effectiveHeight();\n const margins = this.margins(); // {top: 10, right: 130, bottom: 42, left: 42}\n\n // Add the canvas element such that it perfectly overlaps the plot area of the scatter plot SVG\n const devicePixelRatio = window.devicePixelRatio || 1;\n this._canvas = this.root().append('canvas')\n .attr('x', 0)\n .attr('y', 0)\n .attr('width', (width) * devicePixelRatio)\n .attr('height', (height) * devicePixelRatio)\n .style('width', `${width}px`)\n .style('height', `${height}px`)\n .style('position', 'absolute')\n .style('top', `${margins.top + svgTop}px`)\n .style('left', `${margins.left + svgLeft}px`)\n .style('z-index', -1) // Place behind SVG\n .style('pointer-events', 'none'); // Disable pointer events on canvas so SVG can capture brushing\n\n // Define canvas context and set clipping path\n this._context = this._canvas.node().getContext('2d');\n this._context.scale(devicePixelRatio, devicePixelRatio);\n this._context.rect(0, 0, width, height);\n this._context.clip(); // Setup clipping path\n this._context.imageSmoothingQuality = 'high';\n\n return this.svg(); // Respect original return param for this.resetSvg;\n }\n }\n\n _resizeCanvas () {\n const width = this.effectiveWidth();\n const height = this.effectiveHeight();\n\n const devicePixelRatio = window.devicePixelRatio || 1;\n this._canvas\n .attr('width', (width) * devicePixelRatio)\n .attr('height', (height) * devicePixelRatio)\n .style('width', `${width}px`)\n .style('height', `${height}px`);\n this._context.scale(devicePixelRatio, devicePixelRatio);\n }\n\n\n /**\n * Set or get whether to use canvas backend for plotting scatterPlot. Note that the\n * canvas backend does not currently support\n * {@link ScatterPlot#customSymbol customSymbol} or\n * {@link ScatterPlot#symbol symbol} methods and is limited to always plotting\n * with filled circles. Symbols are drawn with\n * {@link ScatterPlot#symbolSize symbolSize} radius. By default, the SVG backend\n * is used when `useCanvas` is set to `false`.\n * @param {Boolean} [useCanvas=false]\n * @return {Boolean|d3.selection}\n */\n useCanvas (useCanvas) {\n if (!arguments.length) {\n return this._useCanvas;\n }\n this._useCanvas = useCanvas;\n return this;\n }\n\n /**\n * Set or get canvas element. You should usually only ever use the get method as\n * dc.js will handle canvas element generation. Provides valid canvas only when\n * {@link ScatterPlot#useCanvas useCanvas} is set to `true`\n * @param {CanvasElement|d3.selection} [canvasElement]\n * @return {CanvasElement|d3.selection}\n */\n canvas (canvasElement) {\n if (!arguments.length) {\n return this._canvas;\n }\n this._canvas = canvasElement;\n return this;\n }\n\n /**\n * Get canvas 2D context. Provides valid context only when\n * {@link ScatterPlot#useCanvas useCanvas} is set to `true`\n * @return {CanvasContext}\n */\n context () {\n return this._context;\n }\n\n /*eslint complexity: [2,11] */\n // Plots data on canvas element. If argument provided, assumes legend is\n // currently being highlighted and modifies opacity/size of symbols accordingly\n // @param {Object} [legendHighlightDatum] - Datum provided to legendHighlight method\n _plotOnCanvas (legendHighlightDatum) {\n this._resizeCanvas();\n const context = this.context();\n context.clearRect(0, 0, (context.canvas.width + 2) * 1, (context.canvas.height + 2) * 1);\n const data = this.data();\n\n // Draw the data on canvas\n data.forEach((d, i) => {\n const isFiltered = !this.filter() || this.filter().isFiltered([d.key[0], d.key[1]]);\n // Calculate opacity for current data point\n let cOpacity = 1;\n if (!this._existenceAccessor(d)) {\n cOpacity = this._emptyOpacity;\n } else if (isFiltered) {\n cOpacity = this._nonemptyOpacity;\n } else {\n cOpacity = this.excludedOpacity();\n }\n // Calculate color for current data point\n let cColor = null;\n if (this._emptyColor && !this._existenceAccessor(d)) {\n cColor = this._emptyColor;\n } else if (this.excludedColor() && !isFiltered) {\n cColor = this.excludedColor();\n } else {\n cColor = this.getColor(d);\n }\n let cSize = this._canvasElementSize(d, isFiltered);\n\n // Adjust params for data points if legend is highlighted\n if (legendHighlightDatum) {\n const isHighlighted = (cColor === legendHighlightDatum.color);\n // Calculate opacity for current data point\n const fadeOutOpacity = 0.1; // TODO: Make this programmatically setable\n if (!isHighlighted) { // Fade out non-highlighted colors + highlighted colors outside filter\n cOpacity = fadeOutOpacity;\n }\n if (isHighlighted) { // Set size for highlighted color data points\n cSize = this._highlightedSize / Math.sqrt(Math.PI);\n }\n }\n\n // Draw point on canvas\n context.save();\n context.globalAlpha = cOpacity;\n context.beginPath();\n context.arc(this.x()(this.keyAccessor()(d)), this.y()(this.valueAccessor()(d)), cSize, 0, 2 * Math.PI, true);\n context.fillStyle = cColor;\n context.fill();\n // context.lineWidth = 0.5; // Commented out code to add stroke around scatter points if desired\n // context.strokeStyle = '#333';\n // context.stroke();\n context.restore();\n });\n }\n\n _plotOnSVG () {\n\n const data = this.data();\n\n if (this._keyboardAccessible) {\n // sort based on the x value (key)\n data.sort((a, b) => ascending(this.keyAccessor()(a), this.keyAccessor()(b)));\n }\n\n let symbols = this.chartBodyG().selectAll('path.symbol')\n .data(data);\n\n transition(symbols.exit(), this.transitionDuration(), this.transitionDelay())\n .attr('opacity', 0).remove();\n\n symbols = symbols\n .enter()\n .append('path')\n .attr('class', 'symbol')\n .classed('dc-tabbable', this._keyboardAccessible)\n .attr('opacity', 0)\n .attr('fill', this.getColor)\n .attr('transform', d => this._locator(d))\n .merge(symbols);\n\n // no click handler - just tabindex for reading out of tooltips\n if (this._keyboardAccessible) {\n this._makeKeyboardAccessible();\n symbols.order();\n }\n\n symbols.call(s => this._renderTitles(s, data));\n\n symbols.each((d, i) => {\n this._filtered[i] = !this.filter() || this.filter().isFiltered([this.keyAccessor()(d), this.valueAccessor()(d)]);\n });\n\n transition(symbols, this.transitionDuration(), this.transitionDelay())\n .attr('opacity', (d, i) => {\n if (!this._existenceAccessor(d)) {\n return this._emptyOpacity;\n } else if (this._filtered[i]) {\n return this._nonemptyOpacity;\n } else {\n return this.excludedOpacity();\n }\n })\n .attr('fill', (d, i) => {\n if (this._emptyColor && !this._existenceAccessor(d)) {\n return this._emptyColor;\n } else if (this.excludedColor() && !this._filtered[i]) {\n return this.excludedColor();\n } else {\n return this.getColor(d);\n }\n })\n .attr('transform', d => this._locator(d))\n .attr('d', this._symbol);\n }\n\n plotData () {\n if (this._useCanvas) {\n this._plotOnCanvas();\n } else {\n this._plotOnSVG();\n }\n }\n\n _renderTitles (_symbol, _d) {\n if (this.renderTitle()) {\n _symbol.selectAll('title').remove();\n _symbol.append('title').text(d => this.title()(d));\n }\n }\n\n /**\n * Get or set the existence accessor. If a point exists, it is drawn with\n * {@link ScatterPlot#symbolSize symbolSize} radius and\n * opacity 1; if it does not exist, it is drawn with\n * {@link ScatterPlot#emptySize emptySize} radius and opacity 0. By default,\n * the existence accessor checks if the reduced value is truthy.\n * @see {@link ScatterPlot#symbolSize symbolSize}\n * @see {@link ScatterPlot#emptySize emptySize}\n * @example\n * // default accessor\n * chart.existenceAccessor(function (d) { return d.value; });\n * @param {Function} [accessor]\n * @returns {Function|ScatterPlot}\n */\n existenceAccessor (accessor) {\n if (!arguments.length) {\n return this._existenceAccessor;\n }\n this._existenceAccessor = accessor;\n return this;\n }\n\n /**\n * Get or set the symbol type used for each point. By default the symbol is a circle (d3.symbolCircle).\n * Type can be a constant or an accessor.\n * @see {@link https://github.com/d3/d3-shape/blob/master/README.md#symbol_type symbol.type}\n * @example\n * // Circle type\n * chart.symbol(d3.symbolCircle);\n * // Square type\n * chart.symbol(d3.symbolSquare);\n * @param {Function} [type=d3.symbolCircle]\n * @returns {Function|ScatterPlot}\n */\n symbol (type) {\n if (!arguments.length) {\n return this._symbol.type();\n }\n this._symbol.type(type);\n return this;\n }\n\n /**\n * Get or set the symbol generator. By default `ScatterPlot` will use\n * {@link https://github.com/d3/d3-shape/blob/master/README.md#symbol d3.symbol()}\n * to generate symbols. `ScatterPlot` will set the\n * {@link https://github.com/d3/d3-shape/blob/master/README.md#symbol_size symbol size accessor}\n * on the symbol generator.\n * @see {@link https://github.com/d3/d3-shape/blob/master/README.md#symbol d3.symbol}\n * @see {@link https://stackoverflow.com/questions/25332120/create-additional-d3-js-symbols Create additional D3.js symbols}\n * @param {String|Function} [customSymbol=d3.symbol()]\n * @returns {String|Function|ScatterPlot}\n */\n customSymbol (customSymbol) {\n if (!arguments.length) {\n return this._symbol;\n }\n this._symbol = customSymbol;\n this._symbol.size((d, i) => this._elementSize(d, i));\n return this;\n }\n\n /**\n * Set or get radius for symbols.\n * @see {@link https://github.com/d3/d3-shape/blob/master/README.md#symbol_size d3.symbol.size}\n * @param {Number} [symbolSize=3]\n * @returns {Number|ScatterPlot}\n */\n symbolSize (symbolSize) {\n if (!arguments.length) {\n return this._symbolSize;\n }\n this._symbolSize = symbolSize;\n return this;\n }\n\n /**\n * Set or get radius for highlighted symbols.\n * @see {@link https://github.com/d3/d3-shape/blob/master/README.md#symbol_size d3.symbol.size}\n * @param {Number} [highlightedSize=5]\n * @returns {Number|ScatterPlot}\n */\n highlightedSize (highlightedSize) {\n if (!arguments.length) {\n return this._highlightedSize;\n }\n this._highlightedSize = highlightedSize;\n return this;\n }\n\n /**\n * Set or get size for symbols excluded from this chart's filter. If null, no\n * special size is applied for symbols based on their filter status.\n * @see {@link https://github.com/d3/d3-shape/blob/master/README.md#symbol_size d3.symbol.size}\n * @param {Number} [excludedSize=null]\n * @returns {Number|ScatterPlot}\n */\n excludedSize (excludedSize) {\n if (!arguments.length) {\n return this._excludedSize;\n }\n this._excludedSize = excludedSize;\n return this;\n }\n\n /**\n * Set or get color for symbols excluded from this chart's filter. If null, no\n * special color is applied for symbols based on their filter status.\n * @param {Number} [excludedColor=null]\n * @returns {Number|ScatterPlot}\n */\n excludedColor (excludedColor) {\n if (!arguments.length) {\n return this._excludedColor;\n }\n this._excludedColor = excludedColor;\n return this;\n }\n\n /**\n * Set or get opacity for symbols excluded from this chart's filter.\n * @param {Number} [excludedOpacity=1.0]\n * @returns {Number|ScatterPlot}\n */\n excludedOpacity (excludedOpacity) {\n if (!arguments.length) {\n return this._excludedOpacity;\n }\n this._excludedOpacity = excludedOpacity;\n return this;\n }\n\n /**\n * Set or get radius for symbols when the group is empty.\n * @see {@link https://github.com/d3/d3-shape/blob/master/README.md#symbol_size d3.symbol.size}\n * @param {Number} [emptySize=0]\n * @returns {Number|ScatterPlot}\n */\n emptySize (emptySize) {\n if (!arguments.length) {\n return this._emptySize;\n }\n this._emptySize = emptySize;\n return this;\n }\n\n hiddenSize (emptySize) {\n if (!arguments.length) {\n return this.emptySize();\n }\n return this.emptySize(emptySize);\n }\n\n /**\n * Set or get color for symbols when the group is empty. If null, just use the\n * {@link ColorMixin#colors colorMixin.colors} color scale zero value.\n * @param {String} [emptyColor=null]\n * @return {String}\n * @return {ScatterPlot}/\n */\n emptyColor (emptyColor) {\n if (!arguments.length) {\n return this._emptyColor;\n }\n this._emptyColor = emptyColor;\n return this;\n }\n\n /**\n * Set or get opacity for symbols when the group is empty.\n * @param {Number} [emptyOpacity=0]\n * @return {Number}\n * @return {ScatterPlot}\n */\n emptyOpacity (emptyOpacity) {\n if (!arguments.length) {\n return this._emptyOpacity;\n }\n this._emptyOpacity = emptyOpacity;\n return this;\n }\n\n /**\n * Set or get opacity for symbols when the group is not empty.\n * @param {Number} [nonemptyOpacity=1]\n * @return {Number}\n * @return {ScatterPlot}\n */\n nonemptyOpacity (nonemptyOpacity) {\n if (!arguments.length) {\n return this._emptyOpacity;\n }\n this._nonemptyOpacity = nonemptyOpacity;\n return this;\n }\n\n legendables () {\n return [{chart: this, name: this._groupName, color: this.getColor()}];\n }\n\n legendHighlight (d) {\n if (this._useCanvas) {\n this._plotOnCanvas(d); // Supply legend datum to plotOnCanvas\n } else {\n this._resizeSymbolsWhere(s => s.attr('fill') === d.color, this._highlightedSize);\n this.chartBodyG().selectAll('.chart-body path.symbol').filter(function () {\n return select(this).attr('fill') !== d.color;\n }).classed('fadeout', true);\n }\n }\n\n legendReset (d) {\n if (this._useCanvas) {\n this._plotOnCanvas(d); // Supply legend datum to plotOnCanvas\n } else {\n this._resizeSymbolsWhere(s => s.attr('fill') === d.color, this._symbolSize);\n this.chartBodyG().selectAll('.chart-body path.symbol').filter(function () {\n return select(this).attr('fill') !== d.color;\n }).classed('fadeout', false);\n }\n }\n\n _resizeSymbolsWhere (condition, size) {\n const symbols = this.chartBodyG().selectAll('.chart-body path.symbol').filter(function () {\n return condition(select(this));\n });\n const oldSize = this._symbol.size();\n this._symbol.size(Math.pow(size, 2));\n transition(symbols, this.transitionDuration(), this.transitionDelay()).attr('d', this._symbol);\n this._symbol.size(oldSize);\n }\n createBrushHandlePaths () {\n // no handle paths for poly-brushes\n }\n\n extendBrush (brushSelection) {\n if (this.round()) {\n brushSelection[0] = brushSelection[0].map(this.round());\n brushSelection[1] = brushSelection[1].map(this.round());\n }\n return brushSelection;\n }\n\n brushIsEmpty (brushSelection) {\n return !brushSelection || brushSelection[0][0] >= brushSelection[1][0] || brushSelection[0][1] >= brushSelection[1][1];\n }\n\n _brushing (evt) {\n if (this._ignoreBrushEvents) {\n return;\n }\n\n let brushSelection = evt.selection;\n\n // Testing with pixels is more reliable\n let brushIsEmpty = this.brushIsEmpty(brushSelection);\n\n if (brushSelection) {\n brushSelection = brushSelection.map(point => point.map((coord, i) => {\n const scale = i === 0 ? this.x() : this.y();\n return scale.invert(coord);\n }));\n\n brushSelection = this.extendBrush(brushSelection);\n\n // The rounding process might have made brushSelection empty, so we need to recheck\n brushIsEmpty = brushIsEmpty && this.brushIsEmpty(brushSelection);\n }\n\n this.redrawBrush(brushSelection, false);\n\n const ranged2DFilter = brushIsEmpty ? null : filters.RangedTwoDimensionalFilter(brushSelection);\n\n events.trigger(() => {\n this.replaceFilter(ranged2DFilter);\n this.redrawGroup();\n }, constants.EVENT_DELAY);\n }\n\n redrawBrush (brushSelection, doTransition) {\n // override default x axis brush from parent chart\n this._gBrush = this.gBrush();\n\n if (this.brushOn() && this._gBrush) {\n if (this.resizing()) {\n this.setBrushExtents(doTransition);\n }\n\n if (!brushSelection) {\n this._withoutBrushEvents(() => {\n this._gBrush\n .call(this.brush().move, brushSelection);\n });\n } else {\n brushSelection = brushSelection.map(point => point.map((coord, i) => {\n const scale = i === 0 ? this.x() : this.y();\n return scale(coord);\n }));\n\n const gBrush =\n optionalTransition(doTransition, this.transitionDuration(), this.transitionDelay())(this._gBrush);\n\n this._withoutBrushEvents(() => {\n gBrush\n .call(this.brush().move, brushSelection);\n });\n }\n }\n\n this.fadeDeselectedArea(brushSelection);\n }\n}\n\nexport const scatterPlot = (parent, chartGroup) => new ScatterPlot(parent, chartGroup);\n","import {events} from '../core/events';\nimport {BaseMixin} from '../base/base-mixin';\nimport {logger} from '../core/logger';\nimport {d3compat} from '../core/config';\n\nconst SELECT_CSS_CLASS = 'dc-select-menu';\nconst OPTION_CSS_CLASS = 'dc-select-option';\n\n/**\n * The select menu is a simple widget designed to filter a dimension by selecting an option from\n * an HTML `