From b44a9248e73db30b8da29649d5f90ad1a2aeb98f Mon Sep 17 00:00:00 2001 From: Thomas Davis Date: Mon, 4 Aug 2014 15:57:17 +1000 Subject: [PATCH] dc --- ajax/libs/dc/2.0.0-alpha.2/dc.css | 317 ++ ajax/libs/dc/2.0.0-alpha.2/dc.js | 7873 ++++++++++++++++++++++++++ ajax/libs/dc/2.0.0-alpha.2/dc.min.js | 4 + ajax/libs/dc/2.0.0-alpha/dc.css | 317 ++ ajax/libs/dc/package.json | 2 +- auto-update.js | 10 +- 6 files changed, 8518 insertions(+), 5 deletions(-) create mode 100644 ajax/libs/dc/2.0.0-alpha.2/dc.css create mode 100644 ajax/libs/dc/2.0.0-alpha.2/dc.js create mode 100644 ajax/libs/dc/2.0.0-alpha.2/dc.min.js create mode 100644 ajax/libs/dc/2.0.0-alpha/dc.css diff --git a/ajax/libs/dc/2.0.0-alpha.2/dc.css b/ajax/libs/dc/2.0.0-alpha.2/dc.css new file mode 100644 index 000000000..ffd4b13ab --- /dev/null +++ b/ajax/libs/dc/2.0.0-alpha.2/dc.css @@ -0,0 +1,317 @@ +div.dc-chart { + float: left; +} + +.dc-chart rect.bar { + stroke: none; + cursor: pointer; +} + +.dc-chart rect.bar:hover { + fill-opacity: .5; +} + +.dc-chart rect.stack1 { + stroke: none; + fill: red; +} + +.dc-chart rect.stack2 { + stroke: none; + fill: green; +} + +.dc-chart rect.deselected { + stroke: none; + fill: #ccc; +} + +.dc-chart .empty-chart .pie-slice path { + fill: #FFEEEE; + cursor: default; +} + +.dc-chart .empty-chart .pie-slice { + cursor: default; +} + +.dc-chart .pie-slice { + fill: white; + font-size: 12px; + cursor: pointer; +} + +.dc-chart .pie-slice.external{ + fill: black; +} + +.dc-chart .pie-slice :hover { + fill-opacity: .8; +} + +.dc-chart .pie-slice.highlight { + fill-opacity: .8; +} + +.dc-chart .selected path { + stroke-width: 3; + stroke: #ccc; + fill-opacity: 1; +} + +.dc-chart .deselected path { + stroke: none; + fill-opacity: .5; + fill: #ccc; +} + +.dc-chart .axis path, .axis line { + fill: none; + stroke: #000; + shape-rendering: crispEdges; +} + +.dc-chart .axis text { + font: 10px sans-serif; +} + +.dc-chart .grid-line { + fill: none; + stroke: #ccc; + opacity: .5; + shape-rendering: crispEdges; +} + +.dc-chart .grid-line line { + fill: none; + stroke: #ccc; + opacity: .5; + shape-rendering: crispEdges; +} + +.dc-chart .brush rect.background { + z-index: -999; +} + +.dc-chart .brush rect.extent { + fill: steelblue; + fill-opacity: .125; +} + +.dc-chart .brush .resize path { + fill: #eee; + stroke: #666; +} + +.dc-chart path.line { + fill: none; + stroke-width: 1.5px; +} + +.dc-chart circle.dot { + stroke: none; +} + +.dc-chart g.dc-tooltip path { + fill: none; + stroke: grey; + stroke-opacity: .8; +} + +.dc-chart path.area { + fill-opacity: .3; + stroke: none; +} + +.dc-chart .node { + font-size: 0.7em; + cursor: pointer; +} + +.dc-chart .node :hover { + fill-opacity: .8; +} + +.dc-chart .selected circle { + stroke-width: 3; + stroke: #ccc; + fill-opacity: 1; +} + +.dc-chart .deselected circle { + stroke: none; + fill-opacity: .5; + fill: #ccc; +} + +.dc-chart .bubble { + stroke: none; + fill-opacity: 0.6; +} + +.dc-data-count { + float: right; + margin-top: 15px; + margin-right: 15px; +} + +.dc-data-count .filter-count { + color: #3182bd; + font-weight: bold; +} + +.dc-data-count .total-count { + color: #3182bd; + font-weight: bold; +} + +.dc-data-table { +} + +.dc-chart g.state { + cursor: pointer; +} + +.dc-chart g.state :hover { + fill-opacity: .8; +} + +.dc-chart g.state path { + stroke: white; +} + +.dc-chart g.selected path { +} + +.dc-chart g.deselected path { + fill: grey; +} + +.dc-chart g.selected text { +} + +.dc-chart g.deselected text { + display: none; +} + +.dc-chart g.county path { + stroke: white; + fill: none; +} + +.dc-chart g.debug rect { + fill: blue; + fill-opacity: .2; +} + +.dc-chart g.row rect { + fill-opacity: 0.8; + cursor: pointer; +} + +.dc-chart g.row rect:hover { + fill-opacity: 0.6; +} + +.dc-chart g.row text { + fill: white; + font-size: 12px; + cursor: pointer; +} + +.dc-legend { + font-size: 11px; +} + +.dc-legend-item { + cursor: pointer; +} + +.dc-chart g.axis text { + /* Makes it so the user can't accidentally click and select text that is meant as a label only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10 */ + -o-user-select: none; + user-select: none; + pointer-events: none; +} + +.dc-chart path.highlight { + stroke-width: 3; + fill-opacity: 1; + stroke-opacity: 1; +} + +.dc-chart .highlight { + fill-opacity: 1; + stroke-opacity: 1; +} + +.dc-chart .fadeout { + fill-opacity: 0.2; + stroke-opacity: 0.2; +} + +.dc-chart path.dc-symbol, g.dc-legend-item.fadeout { + fill-opacity: 0.5; + stroke-opacity: 0.5; +} + +.dc-hard .number-display { + float: none; +} + +.dc-chart .box text { + font: 10px sans-serif; + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10 */ + -o-user-select: none; + user-select: none; + pointer-events: none; +} + +.dc-chart .box line, +.dc-chart .box circle { + fill: #fff; + stroke: #000; + stroke-width: 1.5px; +} + +.dc-chart .box rect { + stroke: #000; + stroke-width: 1.5px; +} + +.dc-chart .box .center { + stroke-dasharray: 3,3; +} + +.dc-chart .box .outlier { + fill: none; + stroke: #ccc; +} + +.dc-chart .box.deselected .box { + fill: #ccc; +} + +.dc-chart .box.deselected { + opacity: .5; +} + +.dc-chart .symbol{ + stroke: none; +} + +.dc-chart .heatmap .box-group.deselected rect { + stroke: none; + fill-opacity: .5; + fill: #ccc; +} + +.dc-chart .heatmap g.axis text { + pointer-events: all; + cursor: pointer; +} diff --git a/ajax/libs/dc/2.0.0-alpha.2/dc.js b/ajax/libs/dc/2.0.0-alpha.2/dc.js new file mode 100644 index 000000000..d1983725b --- /dev/null +++ b/ajax/libs/dc/2.0.0-alpha.2/dc.js @@ -0,0 +1,7873 @@ +/*! + * dc 2.0.0-alpha.2 + * http://dc-js.github.io/dc.js/ + * Copyright 2012 Nick Zhu and other contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +(function() { function _dc(d3) { +'use strict'; + +/** +#### Version 2.0.0-alpha.2 + +The entire dc.js library is scoped under the **dc** name space. It does not introduce anything else +into the global name space. + +#### Function Chaining +Most dc functions are designed to allow function chaining, meaning they return the current chart +instance whenever it is appropriate. This way chart configuration can be written in the following +style: +```js +chart.width(300) + .height(300) + .filter("sunday") +``` +The getter forms of functions do not participate in function chaining because they necessarily +return values that are not the chart. (Although some, such as `.svg` and `.xAxis`, return values +that are chainable d3 objects.) + +**/ +var dc = { + version: "2.0.0-alpha.2", + constants: { + CHART_CLASS: "dc-chart", + DEBUG_GROUP_CLASS: "debug", + STACK_CLASS: "stack", + DESELECTED_CLASS: "deselected", + SELECTED_CLASS: "selected", + NODE_INDEX_NAME: "__index__", + GROUP_INDEX_NAME: "__group_index__", + DEFAULT_CHART_GROUP: "__default_chart_group__", + EVENT_DELAY: 40, + NEGLIGIBLE_NUMBER: 1e-10 + }, + _renderlet: null +}; + +dc.chartRegistry = function() { + // chartGroup:string => charts:array + var _chartMap = {}; + + function initializeChartGroup(group) { + if (!group) + group = dc.constants.DEFAULT_CHART_GROUP; + + if (!_chartMap[group]) + _chartMap[group] = []; + + return group; + } + + return { + has: function(chart) { + for (var e in _chartMap) { + if (_chartMap[e].indexOf(chart) >= 0) + return true; + } + return false; + }, + + register: function(chart, group) { + group = initializeChartGroup(group); + _chartMap[group].push(chart); + }, + + deregister: function (chart, group) { + group = initializeChartGroup(group); + for (var i = 0; i < _chartMap[group].length; i++) { + if (_chartMap[group][i].anchorName() === chart.anchorName()) { + _chartMap[group].splice(i, 1); + break; + } + } + }, + + clear: function(group) { + if (group) { + delete _chartMap[group]; + } else { + _chartMap = {}; + } + }, + + list: function(group) { + group = initializeChartGroup(group); + return _chartMap[group]; + } + }; +}(); + +dc.registerChart = function(chart, group) { + dc.chartRegistry.register(chart, group); +}; + +dc.deregisterChart = function (chart, group) { + dc.chartRegistry.deregister(chart, group); +}; + +dc.hasChart = function(chart) { + return dc.chartRegistry.has(chart); +}; + +dc.deregisterAllCharts = function(group) { + dc.chartRegistry.clear(group); +}; + +/** +## Utilities +**/ + +/** +#### dc.filterAll([chartGroup]) +Clear all filters on all charts within the given chart group. If the chart group is not given then +only charts that belong to the default chart group will be reset. +**/ +dc.filterAll = function(group) { + var charts = dc.chartRegistry.list(group); + for (var i = 0; i < charts.length; ++i) { + charts[i].filterAll(); + } +}; + +/** +#### dc.refocusAll([chartGroup]) +Reset zoom level / focus on all charts that belong to the given chart group. If the chart group is +not given then only charts that belong to the default chart group will be reset. +**/ +dc.refocusAll = function(group) { + var charts = dc.chartRegistry.list(group); + for (var i = 0; i < charts.length; ++i) { + if (charts[i].focus) charts[i].focus(); + } +}; + +/** +#### dc.renderAll([chartGroup]) +Re-render all charts belong to the given chart group. If the chart group is not given then only +charts that belong to the default chart group will be re-rendered. +**/ +dc.renderAll = function(group) { + var charts = dc.chartRegistry.list(group); + for (var i = 0; i < charts.length; ++i) { + charts[i].render(); + } + + if(dc._renderlet !== null) + dc._renderlet(group); +}; + +/** +#### dc.redrawAll([chartGroup]) +Redraw all charts belong to the given chart group. If the chart group is not given then only charts +that belong to the default chart group will be re-drawn. Redraw is different from re-render since +when redrawing dc tries to update the graphic incrementally, using transitions, instead of starting +from scratch. +**/ +dc.redrawAll = function(group) { + var charts = dc.chartRegistry.list(group); + for (var i = 0; i < charts.length; ++i) { + charts[i].redraw(); + } + + if(dc._renderlet !== null) + dc._renderlet(group); +}; + +/** +#### dc.disableTransitions +If this boolean is set truthy, all transitions will be disabled, and changes to the charts will happen +immediately. Default: false +**/ +dc.disableTransitions = false; + +dc.transition = function(selections, duration, callback) { + if (duration <= 0 || duration === undefined || dc.disableTransitions) + return selections; + + var s = selections + .transition() + .duration(duration); + + if (typeof(callback) === 'function') { + callback(s); + } + + return s; +}; + +dc.units = {}; + +/** +#### dc.units.integers +`dc.units.integers` is the default value for `xUnits` for the [Coordinate Grid +Chart](#coordinate-grid-chart) and should be used when the x values are a sequence of integers. + +It is a function that counts the number of integers in the range supplied in its start and end parameters. + +```js +chart.xUnits(dc.units.integers) // already the default +``` + +**/ +dc.units.integers = function(s, e) { + return Math.abs(e - s); +}; + +/** +#### dc.units.ordinal +This argument can be passed to the `xUnits` function of the to specify ordinal units for the x +axis. Usually this parameter is used in combination with passing `d3.scale.ordinal()` to `.x`. + +It just returns the domain passed to it, which for ordinal charts is an array of all values. + +```js +chart.xUnits(dc.units.ordinal) + .x(d3.scale.ordinal()) +``` + +**/ +dc.units.ordinal = function(s, e, domain){ + return domain; +}; + +/** +#### dc.units.fp.precision(precision) +This function generates an argument for the [Coordinate Grid Chart's](#coordinate-grid-chart) +`xUnits` function specifying that the x values are floating-point numbers with the given +precision. + +The returned function determines how many values at the given precision will fit into the range +supplied in its start and end parameters. + +```js +// specify values (and ticks) every 0.1 units +chart.xUnits(dc.units.fp.precision(0.1) +// there are 500 units between 0.5 and 1 if the precision is 0.001 +var thousandths = dc.units.fp.precision(0.001); +thousandths(0.5, 1.0) // returns 500 +``` +**/ +dc.units.fp = {}; +dc.units.fp.precision = function(precision){ + var _f = function(s, e){ + var d = Math.abs((e-s)/_f.resolution); + if(dc.utils.isNegligible(d - Math.floor(d))) + return Math.floor(d); + else + return Math.ceil(d); + }; + _f.resolution = precision; + return _f; +}; + +dc.round = {}; +dc.round.floor = function(n) { + return Math.floor(n); +}; +dc.round.ceil = function(n) { + return Math.ceil(n); +}; +dc.round.round = function(n) { + return Math.round(n); +}; + +dc.override = function(obj, functionName, newFunction) { + var existingFunction = obj[functionName]; + obj["_" + functionName] = existingFunction; + obj[functionName] = newFunction; +}; + +dc.renderlet = function(_){ + if(!arguments.length) return dc._renderlet; + dc._renderlet = _; + return dc; +}; + +dc.instanceOfChart = function (o) { + return o instanceof Object && o.__dc_flag__ && true; +}; + +dc.errors = {}; + +dc.errors.Exception = function(msg) { + var _msg = msg || "Unexpected internal error"; + + this.message = _msg; + + this.toString = function(){ + return _msg; + }; +}; + +dc.errors.InvalidStateException = function() { + dc.errors.Exception.apply(this, arguments); +}; + +dc.dateFormat = d3.time.format("%m/%d/%Y"); + +dc.printers = {}; + +dc.printers.filters = function (filters) { + var s = ""; + + for (var i = 0; i < filters.length; ++i) { + if (i > 0) s += ", "; + s += dc.printers.filter(filters[i]); + } + + return s; +}; + +dc.printers.filter = function (filter) { + var s = ""; + + if (filter) { + if (filter instanceof Array) { + if (filter.length >= 2) + s = "[" + dc.utils.printSingleValue(filter[0]) + " -> " + dc.utils.printSingleValue(filter[1]) + "]"; + else if (filter.length >= 1) + s = dc.utils.printSingleValue(filter[0]); + } else { + s = dc.utils.printSingleValue(filter); + } + } + + return s; +}; + +dc.pluck = function(n,f) { + if (!f) return function(d) { return d[n]; }; + return function(d,i) { return f.call(d,d[n],i); }; +}; + +dc.utils = {}; + +dc.utils.printSingleValue = function (filter) { + var s = "" + filter; + + if (filter instanceof Date) + s = dc.dateFormat(filter); + else if (typeof(filter) == "string") + s = filter; + else if (dc.utils.isFloat(filter)) + s = dc.utils.printSingleValue.fformat(filter); + else if (dc.utils.isInteger(filter)) + s = Math.round(filter); + + return s; +}; +dc.utils.printSingleValue.fformat = d3.format(".2f"); + +// FIXME: these assume than any string r is a percentage (whether or not it +// includes %). They also generate strange results if l is a string. +dc.utils.add = function (l, r) { + if (typeof r === "string") + r = r.replace("%", ""); + + if (l instanceof Date) { + if (typeof r === "string") r = +r; + var d = new Date(); + d.setTime(l.getTime()); + d.setDate(l.getDate() + r); + return d; + } else if (typeof r === "string") { + var percentage = (+r / 100); + return l > 0 ? l * (1 + percentage) : l * (1 - percentage); + } else { + return l + r; + } +}; + +dc.utils.subtract = function (l, r) { + if (typeof r === "string") + r = r.replace("%", ""); + + if (l instanceof Date) { + if (typeof r === "string") r = +r; + var d = new Date(); + d.setTime(l.getTime()); + d.setDate(l.getDate() - r); + return d; + } else if (typeof r === "string") { + var percentage = (+r / 100); + return l < 0 ? l * (1 + percentage) : l * (1 - percentage); + } else { + return l - r; + } +}; + +dc.utils.isNumber = function(n) { + return n===+n; +}; + +dc.utils.isFloat = function (n) { + return n===+n && n!==(n|0); +}; + +dc.utils.isInteger = function (n) { + return n===+n && n===(n|0); +}; + +dc.utils.isNegligible = function (n) { + return !dc.utils.isNumber(n) || (n < dc.constants.NEGLIGIBLE_NUMBER && n > -dc.constants.NEGLIGIBLE_NUMBER); +}; + +dc.utils.clamp = function (val, min, max) { + return val < min ? min : (val > max ? max : val); +}; + +var _idCounter = 0; +dc.utils.uniqueId = function () { + return ++_idCounter; +}; + +dc.utils.nameToId = function (name) { + return name.toLowerCase().replace(/[\s]/g, "_").replace(/[\.']/g, ""); +}; + +dc.utils.appendOrSelect = function (parent, name) { + var element = parent.select(name); + if (element.empty()) element = parent.append(name); + return element; +}; + +dc.utils.safeNumber = function(n){return dc.utils.isNumber(+n)?+n:0;}; + +dc.logger = {}; + +dc.logger.enableDebugLog = false; + +dc.logger.warn = function (msg) { + if (console) { + if (console.warn) { + console.warn(msg); + } else if (console.log) { + console.log(msg); + } + } + + return dc.logger; +}; + +dc.logger.debug = function (msg) { + if (dc.logger.enableDebugLog && console) { + if (console.debug) { + console.debug(msg); + } else if (console.log) { + console.log(msg); + } + } + + return dc.logger; +}; + +dc.events = { + current: null +}; + +/** +#### dc.events.trigger(function[, delay]) +This function triggers a throttled event function with a specified delay (in milli-seconds). Events +that are triggered repetitively due to user interaction such brush dragging might flood the library +and invoke more renders than can be executed in time. Using this function to wrap your event +function allows the library to smooth out the rendering by throttling events and only responding to +the most recent event. + +```js + chart.renderlet(function(chart){ + // smooth the rendering through event throttling + dc.events.trigger(function(){ + // focus some other chart to the range selected by user on this chart + someOtherChart.focus(chart.filter()); + }); + }) +``` +**/ +dc.events.trigger = function(closure, delay) { + if (!delay){ + closure(); + return; + } + + dc.events.current = closure; + + setTimeout(function() { + if (closure == dc.events.current) + closure(); + }, delay); +}; + +dc.filters = {}; + +/** +## Filters + +The dc.js filters are functions which are passed into crossfilter to chose which records will be +accumulated to produce values for the charts. In the crossfilter model, any filters applied on one +dimension will affect all the other dimensions but not that one. dc always applies a filter +function to the dimension; the function combines multiple filters and if any of them accept a +record, it is filtered in. + +These filter constructors are used as appropriate by the various charts to implement brushing. We +mention below which chart uses which filter. In some cases, many instances of a filter will be added. + +**/ + +/** +#### dc.filters.RangedFilter(low, high) + RangedFilter is a filter which accepts keys between `low` and `high`. It is used to implement X + axis brushing for the [coordinate grid charts](#coordinate-grid-mixin). +**/ +dc.filters.RangedFilter = function(low, high) { + var range = Array(low, high); + range.isFiltered = function(value) { + return value >= this[0] && value < this[1]; + }; + + return range; +}; + +/** +#### dc.filters.TwoDimensionalFilter(array) + TwoDimensionalFilter is a filter which accepts a single two-dimensional value. It is used by the + [heat map chart](#heat-map) to include particular cells as they are clicked. (Rows and columns are + filtered by filtering all the cells in the row or column.) +**/ +dc.filters.TwoDimensionalFilter = function(array) { + if (array === null) { return null; } + + var filter = array; + filter.isFiltered = function(value) { + return value.length && value.length == filter.length && + value[0] == filter[0] && value[1] == filter[1]; + }; + + return filter; +}; + +/** +#### dc.filters.RangedTwoDimensionalFilter(array) + The RangedTwoDimensionalFilter allows filtering all values which fit within a rectangular + region. It is used by the [scatter plot](#scatter-plot) to implement rectangular brushing. + + It takes two two-dimensional points in the form `[[x1,y1],[x2,y2]]`, and normalizes them so that + `x1 <= x2` and `y1 <- y2`. It then returns a filter which accepts any points which are in the + rectangular range including the lower values but excluding the higher values. + + If an array of two values are given to the RangedTwoDimensionalFilter, it interprets the values as + two x coordinates `x1` and `x2` and returns a filter which accepts any points for which `x1 <= x < + x2`. + **/ +dc.filters.RangedTwoDimensionalFilter = function(array){ + if (array === null) { return null; } + + var filter = array; + var fromBottomLeft; + + if (filter[0] instanceof Array) { + fromBottomLeft = [[Math.min(array[0][0], array[1][0]), + Math.min(array[0][1], array[1][1])], + [Math.max(array[0][0], array[1][0]), + Math.max(array[0][1], array[1][1])]]; + } else { + fromBottomLeft = [[array[0], -Infinity], + [array[1], Infinity]]; + } + + filter.isFiltered = function(value) { + var x, y; + + if (value instanceof Array) { + if (value.length != 2) return false; + x = value[0]; + y = value[1]; + } else { + x = value; + y = fromBottomLeft[0][1]; + } + + return x >= fromBottomLeft[0][0] && x < fromBottomLeft[1][0] && + y >= fromBottomLeft[0][1] && y < fromBottomLeft[1][1]; + }; + + return filter; +}; + +/** +## Base Mixin +Base Mixin is an abstract functional object representing a basic dc chart object +for all chart and widget implementations. Methods from the Base Mixin are inherited +and available on all chart implementation in the DC library. +**/ +dc.baseMixin = function (_chart) { + _chart.__dc_flag__ = dc.utils.uniqueId(); + + var _dimension; + var _group; + + var _anchor; + var _root; + var _svg; + + var _minWidth = 200; + var _default_width = function (element) { + var width = element && element.getBoundingClientRect && element.getBoundingClientRect().width; + return (width && width > _minWidth) ? width : _minWidth; + }; + var _width = _default_width; + + var _minHeight = 200; + var _default_height = function (element) { + var height = element && element.getBoundingClientRect && element.getBoundingClientRect().height; + return (height && height > _minHeight) ? height : _minHeight; + }; + var _height = _default_height; + + var _keyAccessor = dc.pluck('key'); + var _valueAccessor = dc.pluck('value'); + var _label = dc.pluck('key'); + + var _ordering = dc.pluck('key'); + var _orderSort; + + var _renderLabel = false; + + var _title = function (d) { + return _chart.keyAccessor()(d) + ": " + _chart.valueAccessor()(d); + }; + var _renderTitle = false; + + var _transitionDuration = 750; + + var _filterPrinter = dc.printers.filters; + + var _renderlets = []; + var _mandatoryAttributes = ['dimension', 'group']; + + var _chartGroup = dc.constants.DEFAULT_CHART_GROUP; + + var NULL_LISTENER = function () {}; + + var _listeners = d3.dispatch( + "preRender", + "postRender", + "preRedraw", + "postRedraw", + "filtered", + "zoomed"); + + var _legend; + + var _filters = []; + var _filterHandler = function (dimension, filters) { + dimension.filter(null); + + if (filters.length === 0) + dimension.filter(null); + else + dimension.filterFunction(function (d) { + for(var i = 0; i < filters.length; i++) { + var filter = filters[i]; + if (filter.isFiltered && filter.isFiltered(d)) { + return true; + } else if (filter <= d && filter >= d) { + return true; + } + } + return false; + }); + + return filters; + }; + + var _data = function (group) { + return group.all(); + }; + + /** + #### .width([value]) + Set or get the width attribute of a chart. See `.height` below for further description of the + behavior. + + **/ + _chart.width = function (w) { + if (!arguments.length) return _width(_root.node()); + _width = d3.functor(w || _default_width); + return _chart; + }; + + /** + #### .height([value]) + Set or get the height attribute of a chart. The height is applied to the SVG element generated by + the chart when rendered (or rerendered). If a value is given, then it will be used to calculate + the new height and the chart returned for method chaining. The value can either be a numeric, a + function, or falsy. If no value is specified then the value of the current height attribute will + be returned. + + By default, without an explicit height being given, the chart will select the width of its + anchor element. If that isn't possible it defaults to 200. Setting the value falsy will return + the chart to the default behavior + + Examples: + + ```js + chart.height(250); // Set the chart's height to 250px; + chart.height(function(anchor) { return doSomethingWith(anchor); }); // set the chart's height with a function + chart.height(null); // reset the height to the default auto calculation + ``` + + **/ + _chart.height = function (h) { + if (!arguments.length) return _height(_root.node()); + _height = d3.functor(h || _default_height); + return _chart; + }; + + /** + #### .minWidth([value]) + Set or get the minimum width attribute of a chart. This only applicable if the width is + calculated by dc. + + **/ + _chart.minWidth = function (w) { + if (!arguments.length) return _minWidth; + _minWidth = w; + return _chart; + }; + + /** + #### .minHeight([value]) + Set or get the minimum height attribute of a chart. This only applicable if the height is + calculated by dc. + + **/ + _chart.minHeight = function (w) { + if (!arguments.length) return _minHeight; + _minHeight = w; + return _chart; + }; + + /** + #### .dimension([value]) - **mandatory** + Set or get the dimension attribute of a chart. In dc a dimension can be any valid [crossfilter + dimension](https://github.com/square/crossfilter/wiki/API-Reference#wiki-dimension). + + If a value is given, then it will be used as the new dimension. If no value is specified then + the current dimension will be returned. + + **/ + _chart.dimension = function (d) { + if (!arguments.length) return _dimension; + _dimension = d; + _chart.expireCache(); + return _chart; + }; + + /** + #### .data([callback]) + Set the data callback or retrieve the chart's data set. The data callback is passed the chart's + group and by default will return `group.all()`. This behavior may be modified to, for instance, + return only the top 5 groups: + ``` + chart.data(function(group) { + return group.top(5); + }); + ``` + **/ + _chart.data = function(d) { + if (!arguments.length) return _data.call(_chart,_group); + _data = d3.functor(d); + _chart.expireCache(); + return _chart; + }; + + /** + #### .group([value, [name]]) - **mandatory** + Set or get the group attribute of a chart. In dc a group is a [crossfilter + group](https://github.com/square/crossfilter/wiki/API-Reference#wiki-group). Usually the group + should be created from the particular dimension associated with the same chart. If a value is + given, then it will be used as the new group. + + If no value specified then the current group will be returned. + If `name` is specified then it will be used to generate legend label. + + **/ + _chart.group = function (g, name) { + if (!arguments.length) return _group; + _group = g; + _chart._groupName = name; + _chart.expireCache(); + return _chart; + }; + + /** + #### .ordering([orderFunction]) + Get or set an accessor to order ordinal charts + **/ + _chart.ordering = function(o) { + if (!arguments.length) return _ordering; + _ordering = o; + _orderSort = crossfilter.quicksort.by(_ordering); + _chart.expireCache(); + return _chart; + }; + + _chart._computeOrderedGroups = function(data) { + var dataCopy = data.slice(0); + + if (dataCopy.length <= 1) + return dataCopy; + + if (!_orderSort) + _orderSort = crossfilter.quicksort.by(_ordering); + + return _orderSort(dataCopy, 0, dataCopy.length); + }; + + /** + #### .filterAll() + Clear all filters associated with this chart. + + **/ + _chart.filterAll = function () { + return _chart.filter(null); + }; + + /** + #### .select(selector) + Execute d3 single selection in the chart's scope using the given selector and return the d3 + selection. Roughly the same as: + ```js + d3.select("#chart-id").select(selector); + ``` + This function is **not chainable** since it does not return a chart instance; however the d3 + selection result can be chained to d3 function calls. + + **/ + _chart.select = function (s) { + return _root.select(s); + }; + + /** + #### .selectAll(selector) + Execute in scope d3 selectAll using the given selector and return d3 selection result. Roughly + the same as: + ```js + d3.select("#chart-id").selectAll(selector); + ``` + This function is **not chainable** since it does not return a chart instance; however the d3 + selection result can be chained to d3 function calls. + + **/ + _chart.selectAll = function (s) { + return _root ? _root.selectAll(s) : null; + }; + + /** + #### .anchor([anchorChart|anchorSelector|anchorNode], [chartGroup]) + Set the svg root to either be an existing chart's root; or any valid [d3 single + selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying a dom + block element such as a div; or a dom element or d3 selection. Optionally registers the chart + within the chartGroup. This class is called internally on chart initialization, but be called + again to relocate the chart. However, it will orphan any previously created SVG elements. + **/ + _chart.anchor = function (a, chartGroup) { + if (!arguments.length) return _anchor; + if (dc.instanceOfChart(a)) { + _anchor = a.anchor(); + _root = a.root(); + } else { + _anchor = a; + _root = d3.select(_anchor); + _root.classed(dc.constants.CHART_CLASS, true); + dc.registerChart(_chart, chartGroup); + } + _chartGroup = chartGroup; + return _chart; + }; + + /** + #### .anchorName() + Returns the dom id for the chart's anchored location. + + **/ + _chart.anchorName = function () { + var a = _chart.anchor(); + if (a && a.id) return a.id; + if (a && a.replace) return a.replace('#',''); + return "" + _chart.chartID(); + }; + + /** + #### .root([rootElement]) + Returns the root element where a chart resides. Usually it will be the parent div element where + the svg was created. You can also pass in a new root element however this is usually handled by + dc internally. Resetting the root element on a chart outside of dc internals may have + unexpected consequences. + + **/ + _chart.root = function (r) { + if (!arguments.length) return _root; + _root = r; + return _chart; + }; + + /** + #### .svg([svgElement]) + Returns the top svg element for this specific chart. You can also pass in a new svg element, + however this is usually handled by dc internally. Resetting the svg element on a chart outside + of dc internals may have unexpected consequences. + + **/ + _chart.svg = function (_) { + if (!arguments.length) return _svg; + _svg = _; + return _chart; + }; + + /** + #### .resetSvg() + Remove the chart's SVG elements from the dom and recreate the container SVG element. + **/ + _chart.resetSvg = function () { + _chart.select("svg").remove(); + return generateSvg(); + }; + + function generateSvg() { + _svg = _chart.root().append("svg") + .attr("width", _chart.width()) + .attr("height", _chart.height()); + return _svg; + } + + /** + #### .filterPrinter([filterPrinterFunction]) + Set or get the filter printer function. The filter printer function is used to generate human + friendly text for filter value(s) associated with the chart instance. By default dc charts use a + default filter printer `dc.printers.filter` that provides simple printing support for both + single value and ranged filters. + + **/ + _chart.filterPrinter = function (_) { + if (!arguments.length) return _filterPrinter; + _filterPrinter = _; + return _chart; + }; + + /** + #### .turnOnControls() & .turnOffControls() + Turn on/off optional control elements within the root element. dc currently supports the + following html control elements. + + * root.selectAll(".reset") - elements are turned on if the chart has an active filter. This type + of control element is usually used to store a reset link to allow user to reset filter on a + certain chart. This element will be turned off automatically if the filter is cleared. + * root.selectAll(".filter") elements are turned on if the chart has an active filter. The text + content of this element is then replaced with the current filter value using the filter printer + function. This type of element will be turned off automatically if the filter is cleared. + + **/ + _chart.turnOnControls = function () { + if (_root) { + _chart.selectAll(".reset").style("display", null); + _chart.selectAll(".filter").text(_filterPrinter(_chart.filters())).style("display", null); + } + return _chart; + }; + + _chart.turnOffControls = function () { + if (_root) { + _chart.selectAll(".reset").style("display", "none"); + _chart.selectAll(".filter").style("display", "none").text(_chart.filter()); + } + return _chart; + }; + + /** + #### .transitionDuration([duration]) + Set or get the animation transition duration(in milliseconds) for this chart instance. Default + duration is 750ms. + + **/ + _chart.transitionDuration = function (d) { + if (!arguments.length) return _transitionDuration; + _transitionDuration = d; + return _chart; + }; + + _chart._mandatoryAttributes = function (_) { + if (!arguments.length) return _mandatoryAttributes; + _mandatoryAttributes = _; + return _chart; + }; + + function checkForMandatoryAttributes(a) { + if (!_chart[a] || !_chart[a]()) + throw new dc.errors.InvalidStateException("Mandatory attribute chart." + a + + " is missing on chart[#" + _chart.anchorName() + "]"); + } + + /** + #### .render() + Invoking this method will force the chart to re-render everything from scratch. Generally it + should only be used to render the chart for the first time on the page or if you want to make + sure everything is redrawn from scratch instead of relying on the default incremental redrawing + behaviour. + + **/ + _chart.render = function () { + _listeners.preRender(_chart); + + if (_mandatoryAttributes) + _mandatoryAttributes.forEach(checkForMandatoryAttributes); + + var result = _chart._doRender(); + + if (_legend) _legend.render(); + + _chart._activateRenderlets("postRender"); + + return result; + }; + + _chart._activateRenderlets = function (event) { + if (_chart.transitionDuration() > 0 && _svg) { + _svg.transition().duration(_chart.transitionDuration()) + .each("end", function () { + runAllRenderlets(); + if (event) _listeners[event](_chart); + }); + } else { + runAllRenderlets(); + if (event) _listeners[event](_chart); + } + }; + + /** + #### .redraw() + Calling redraw will cause the chart to re-render data changes incrementally. If there is no + change in the underlying data dimension then calling this method will have no effect on the + chart. Most chart interaction in dc will automatically trigger this method through internal + events (in particular [dc.redrawAll](#dcredrawallchartgroup)); therefore, you only need to + manually invoke this function if data is manipulated outside of dc's control (for example if + data is loaded in the background using `crossfilter.add()`). + + **/ + _chart.redraw = function () { + _listeners.preRedraw(_chart); + + var result = _chart._doRedraw(); + + if (_legend) _legend.render(); + + _chart._activateRenderlets("postRedraw"); + + return result; + }; + + _chart.redrawGroup = function () { + dc.redrawAll(_chart.chartGroup()); + }; + + _chart.renderGroup = function () { + dc.renderAll(_chart.chartGroup()); + }; + + _chart._invokeFilteredListener = function (f) { + if (f !== undefined) _listeners.filtered(_chart, f); + }; + + _chart._invokeZoomedListener = function () { + _listeners.zoomed(_chart); + }; + + /** + #### .hasFilter([filter]) + Check whether is any active filter or a specific filter is associated with particular chart instance. + This function is **not chainable**. + + **/ + _chart.hasFilter = function (filter) { + if (!arguments.length) return _filters.length > 0; + return _filters.some(function(f) { + return filter <= f && filter >= f; + }); + }; + + function removeFilter(_) { + for(var i = 0; i < _filters.length; i++) { + if(_filters[i] <= _ && _filters[i] >= _) { + _filters.splice(i, 1); + break; + } + } + applyFilters(); + _chart._invokeFilteredListener(_); + } + + function addFilter(_) { + _filters.push(_); + applyFilters(); + _chart._invokeFilteredListener(_); + } + + function resetFilters() { + _filters = []; + applyFilters(); + _chart._invokeFilteredListener(null); + } + + function applyFilters() { + if (_chart.dimension() && _chart.dimension().filter) { + var fs = _filterHandler(_chart.dimension(), _filters); + _filters = fs ? fs : _filters; + } + } + + _chart.replaceFilter = function (_) { + _filters = []; + _chart.filter(_); + }; + + /** + #### .filter([filterValue]) + Filter the chart by the given value or return the current filter if the input parameter is missing. + ```js + // filter by a single string + chart.filter("Sunday"); + // filter by a single age + chart.filter(18); + ``` + + **/ + _chart.filter = function (_) { + if (!arguments.length) return _filters.length > 0 ? _filters[0] : null; + if (_ instanceof Array && _[0] instanceof Array && !_.isFiltered) { + _[0].forEach(function(d){ + if (_chart.hasFilter(d)) { + _filters.splice(_filters.indexOf(d), 1); + } else { + _filters.push(d); + } + }); + applyFilters(); + _chart._invokeFilteredListener(_); + } else if (_ === null) { + resetFilters(); + } else { + if (_chart.hasFilter(_)) + removeFilter(_); + else + addFilter(_); + } + + if (_root !== null && _chart.hasFilter()) { + _chart.turnOnControls(); + } else { + _chart.turnOffControls(); + } + + return _chart; + }; + + /** + #### .filters() + Returns all current filters. This method does not perform defensive cloning of the internal + filter array before returning, therefore any modification of the returned array will effect the + chart's internal filter storage. + + **/ + _chart.filters = function () { + return _filters; + }; + + _chart.highlightSelected = function (e) { + d3.select(e).classed(dc.constants.SELECTED_CLASS, true); + d3.select(e).classed(dc.constants.DESELECTED_CLASS, false); + }; + + _chart.fadeDeselected = function (e) { + d3.select(e).classed(dc.constants.SELECTED_CLASS, false); + d3.select(e).classed(dc.constants.DESELECTED_CLASS, true); + }; + + _chart.resetHighlight = function (e) { + d3.select(e).classed(dc.constants.SELECTED_CLASS, false); + d3.select(e).classed(dc.constants.DESELECTED_CLASS, false); + }; + + /** + #### .onClick(datum) + This function is passed to d3 as the onClick handler for each chart. The default behavior is to + filter on the clicked datum (passed to the callback) and redraw the chart group. + **/ + _chart.onClick = function (d) { + var filter = _chart.keyAccessor()(d); + dc.events.trigger(function () { + _chart.filter(filter); + _chart.redrawGroup(); + }); + }; + + /** + #### .filterHandler([function]) + Set or get the filter handler. The filter handler is a function that performs the filter action + on a specific dimension. Using a custom filter handler allows you to perform additional logic + before or after filtering. + + ```js + // default filter handler + function(dimension, filter){ + dimension.filter(filter); // perform filtering + return filter; // return the actual filter value + } + + // custom filter handler + chart.filterHandler(function(dimension, filter){ + var newFilter = filter + 10; + dimension.filter(newFilter); + return newFilter; // set the actual filter value to the new value + }); + ``` + + **/ + _chart.filterHandler = function (_) { + if (!arguments.length) return _filterHandler; + _filterHandler = _; + return _chart; + }; + + // abstract function stub + _chart._doRender = function () { + // do nothing in base, should be overridden by sub-function + return _chart; + }; + + _chart._doRedraw = function () { + // do nothing in base, should be overridden by sub-function + return _chart; + }; + + _chart.legendables = function () { + // do nothing in base, should be overridden by sub-function + return []; + }; + + _chart.legendHighlight = function (d) { + // do nothing in base, should be overridden by sub-function + }; + + _chart.legendReset = function (d) { + // do nothing in base, should be overridden by sub-function + }; + + _chart.legendToggle = function (d) { + // do nothing in base, should be overriden by sub-function + }; + + _chart.isLegendableHidden = function (d) { + // do nothing in base, should be overridden by sub-function + return false; + }; + + /** + #### .keyAccessor([keyAccessorFunction]) + Set or get the key accessor function. The key accessor function is used to retrieve the key + value from the crossfilter group. Key values are used differently in different charts, for + example keys correspond to slices in a pie chart and x axis positions in a grid coordinate chart. + ```js + // default key accessor + chart.keyAccessor(function(d) { return d.key; }); + // custom key accessor for a multi-value crossfilter reduction + chart.keyAccessor(function(p) { return p.value.absGain; }); + ``` + + **/ + _chart.keyAccessor = function (_) { + if (!arguments.length) return _keyAccessor; + _keyAccessor = _; + return _chart; + }; + + /** + #### .valueAccessor([valueAccessorFunction]) + Set or get the value accessor function. The value accessor function is used to retrieve the + value from the crossfilter group. Group values are used differently in different charts, for + example values correspond to slice sizes in a pie chart and y axis positions in a grid + coordinate chart. + ```js + // default value accessor + chart.valueAccessor(function(d) { return d.value; }); + // custom value accessor for a multi-value crossfilter reduction + chart.valueAccessor(function(p) { return p.value.percentageGain; }); + ``` + + **/ + _chart.valueAccessor = function (_) { + if (!arguments.length) return _valueAccessor; + _valueAccessor = _; + return _chart; + }; + + /** + #### .label([labelFunction]) + Set or get the label function. The chart class will use this function to render labels for each + child element in the chart, e.g. slices in a pie chart or bubbles in a bubble chart. Not every + chart supports the label function for example bar chart and line chart do not use this function + at all. + ```js + // default label function just return the key + chart.label(function(d) { return d.key; }); + // label function has access to the standard d3 data binding and can get quite complicated + chart.label(function(d) { return d.data.key + "(" + Math.floor(d.data.value / all.value() * 100) + "%)"; }); + ``` + + **/ + _chart.label = function (_) { + if (!arguments.length) return _label; + _label = _; + _renderLabel = true; + return _chart; + }; + + /** + #### .renderLabel(boolean) + Turn on/off label rendering + + **/ + _chart.renderLabel = function (_) { + if (!arguments.length) return _renderLabel; + _renderLabel = _; + return _chart; + }; + + /** + #### .title([titleFunction]) + Set or get the title function. The chart class will use this function to render the svg title + (usually interpreted by browser as tooltips) for each child element in the chart, e.g. a slice + in a pie chart or a bubble in a bubble chart. Almost every chart supports the title function; + however in grid coordinate charts you need to turn off the brush in order to see titles, because + otherwise the brush layer will block tooltip triggering. + ```js + // default title function just return the key + chart.title(function(d) { return d.key + ": " + d.value; }); + // title function has access to the standard d3 data binding and can get quite complicated + chart.title(function(p) { + return p.key.getFullYear() + + "\n" + + "Index Gain: " + numberFormat(p.value.absGain) + "\n" + + "Index Gain in Percentage: " + numberFormat(p.value.percentageGain) + "%\n" + + "Fluctuation / Index Ratio: " + numberFormat(p.value.fluctuationPercentage) + "%"; + }); + ``` + + **/ + _chart.title = function (_) { + if (!arguments.length) return _title; + _title = _; + _renderTitle = true; + return _chart; + }; + + /** + #### .renderTitle(boolean) + Turn on/off title rendering, or return the state of the render title flag if no arguments are + given. + + **/ + _chart.renderTitle = function (_) { + if (!arguments.length) return _renderTitle; + _renderTitle = _; + return _chart; + }; + + /** + #### .renderlet(renderletFunction) + A renderlet is similar to an event listener on rendering event. Multiple renderlets can be added + to an individual chart. Each time a chart is rerendered or redrawn the renderlets are invoked + right after the chart finishes its own drawing routine, giving you a way to modify the svg + elements. Renderlet functions take the chart instance as the only input parameter and you can + use the dc API or use raw d3 to achieve pretty much any effect. + ```js + // renderlet function + chart.renderlet(function(chart){ + // mix of dc API and d3 manipulation + chart.select("g.y").style("display", "none"); + // its a closure so you can also access other chart variable available in the closure scope + moveChart.filter(chart.filter()); + }); + ``` + + **/ + _chart.renderlet = function (_) { + _renderlets.push(_); + return _chart; + }; + + function runAllRenderlets() { + for (var i = 0; i < _renderlets.length; ++i) { + _renderlets[i](_chart); + } + } + + /** + #### .chartGroup([group]) + Get or set the chart group to which this chart belongs. Chart groups are rendered or redrawn + together since it is expected they share the same underlying crossfilter data set. + **/ + _chart.chartGroup = function (_) { + if (!arguments.length) return _chartGroup; + _chartGroup = _; + return _chart; + }; + + /** + #### .expireCache() + Expire the internal chart cache. dc charts cache some data internally on a per chart basis to + speed up rendering and avoid unnecessary calculation; however it might be useful to clear the + cache if you have changed state which will affect rendering. For example if you invoke the + `crossfilter.add` function or reset group or dimension after rendering it is a good idea to + clear the cache to make sure charts are rendered properly. + + **/ + _chart.expireCache = function () { + // do nothing in base, should be overridden by sub-function + return _chart; + }; + + /** + #### .legend([dc.legend]) + Attach a dc.legend widget to this chart. The legend widget will automatically draw legend labels + based on the color setting and names associated with each group. + + ```js + chart.legend(dc.legend().x(400).y(10).itemHeight(13).gap(5)) + ``` + + **/ + _chart.legend = function (l) { + if (!arguments.length) return _legend; + _legend = l; + _legend.parent(_chart); + return _chart; + }; + + /** + #### .chartID() + Returns the internal numeric ID of the chart. + **/ + _chart.chartID = function () { + return _chart.__dc_flag__; + }; + + /** + #### .options(optionsObject) + Set chart options using a configuration object. Each key in the object will cause the method of + the same name to be called with the value to set that attribute for the chart. + + Example: + ``` + chart.options({dimension: myDimension, group: myGroup}); + ``` + **/ + _chart.options = function(opts) { + for (var o in opts) { + if (typeof(_chart[o]) === 'function') { + _chart[o].call(_chart,opts[o]); + } else { + dc.logger.debug("Not a valid option setter name: " + o); + } + } + return _chart; + }; + + /** + ## Listeners + All dc chart instance supports the following listeners. + + #### .on("preRender", function(chart){...}) + This listener function will be invoked before chart rendering. + + #### .on("postRender", function(chart){...}) + This listener function will be invoked after chart finish rendering including all renderlets' logic. + + #### .on("preRedraw", function(chart){...}) + This listener function will be invoked before chart redrawing. + + #### .on("postRedraw", function(chart){...}) + This listener function will be invoked after chart finish redrawing including all renderlets' logic. + + #### .on("filtered", function(chart, filter){...}) + This listener function will be invoked after a filter is applied, added or removed. + + #### .on("zoomed", function(chart, filter){...}) + This listener function will be invoked after a zoom is triggered. + + **/ + _chart.on = function (event, listener) { + _listeners.on(event, listener); + return _chart; + }; + + return _chart; +}; + + +/** +## Margin Mixin + +Margin is a mixin that provides margin utility functions for both the Row Chart and Coordinate Grid +Charts. + +**/ +dc.marginMixin = function (_chart) { + var _margin = {top: 10, right: 50, bottom: 30, left: 30}; + + /** + #### .margins([margins]) + Get or set the margins for a particular coordinate grid chart instance. The margins is stored as + an associative Javascript array. Default margins: {top: 10, right: 50, bottom: 30, left: 30}. + + The margins can be accessed directly from the getter. + ```js + var leftMargin = chart.margins().left; // 30 by default + chart.margins().left = 50; + leftMargin = chart.margins().left; // now 50 + ``` + + **/ + _chart.margins = function (m) { + if (!arguments.length) return _margin; + _margin = m; + return _chart; + }; + + _chart.effectiveWidth = function () { + return _chart.width() - _chart.margins().left - _chart.margins().right; + }; + + _chart.effectiveHeight = function () { + return _chart.height() - _chart.margins().top - _chart.margins().bottom; + }; + + return _chart; +}; + +/** +## Color Mixin + +The Color Mixin is an abstract chart functional class providing universal coloring support +as a mix-in for any concrete chart implementation. + +**/ + +dc.colorMixin = function(_chart) { + var _colors = d3.scale.category20c(); + var _defaultAccessor = true; + + var _colorAccessor = function(d) { return _chart.keyAccessor()(d); }; + + /** + #### .colors([colorScale]) + Retrieve current color scale or set a new color scale. This methods accepts any function that + operates like a d3 scale. If not set the default is + `d3.scale.category20c()`. + ```js + // alternate categorical scale + chart.colors(d3.scale.category20b()); + + // ordinal scale + chart.colors(d3.scale.ordinal().range(['red','green','blue'])); + // convenience method, the same as above + chart.ordinalColors(['red','green','blue']); + + // set a linear scale + chart.linearColors(["#4575b4", "#ffffbf", "#a50026"]); + ``` + **/ + _chart.colors = function(_) { + if (!arguments.length) return _colors; + if (_ instanceof Array) _colors = d3.scale.quantize().range(_); // deprecated legacy support, note: this fails for ordinal domains + else _colors = d3.functor(_); + return _chart; + }; + + /** + #### .ordinalColors(r) + Convenience method to set the color scale to d3.scale.ordinal with range `r`. + + **/ + _chart.ordinalColors = function(r) { + return _chart.colors(d3.scale.ordinal().range(r)); + }; + + /** + #### .linearColors(r) + Convenience method to set the color scale to an Hcl interpolated linear scale with range `r`. + + **/ + _chart.linearColors = function(r) { + return _chart.colors(d3.scale.linear() + .range(r) + .interpolate(d3.interpolateHcl)); + }; + + /** + #### .colorAccessor([colorAccessorFunction]) + Set or the get color accessor function. This function will be used to map a data point in a + crossfilter group to a color value on the color scale. The default function uses the key + accessor. + ```js + // default index based color accessor + .colorAccessor(function(d, i){return i;}) + // color accessor for a multi-value crossfilter reduction + .colorAccessor(function(d){return d.value.absGain;}) + ``` + **/ + _chart.colorAccessor = function(_){ + if(!arguments.length) return _colorAccessor; + _colorAccessor = _; + _defaultAccessor = false; + return _chart; + }; + + // what is this? + _chart.defaultColorAccessor = function() { + return _defaultAccessor; + }; + + /** + #### .colorDomain([domain]) + Set or get the current domain for the color mapping function. The domain must be supplied as an + array. + + Note: previously this method accepted a callback function. Instead you may use a custom scale + set by `.colors`. + + **/ + _chart.colorDomain = function(_){ + if(!arguments.length) return _colors.domain(); + _colors.domain(_); + return _chart; + }; + + /** + #### .calculateColorDomain() + Set the domain by determining the min and max values as retrieved by `.colorAccessor` over the + chart's dataset. + + **/ + _chart.calculateColorDomain = function () { + var newDomain = [d3.min(_chart.data(), _chart.colorAccessor()), + d3.max(_chart.data(), _chart.colorAccessor())]; + _colors.domain(newDomain); + }; + + /** + #### .getColor(d [, i]) + Get the color for the datum d and counter i. This is used internally by charts to retrieve a color. + + **/ + _chart.getColor = function(d, i){ + return _colors(_colorAccessor.call(this,d, i)); + }; + + /** + #### .colorCalculator([value]) + Gets or sets chart.getColor. + **/ + _chart.colorCalculator = function(_){ + if(!arguments.length) return _chart.getColor; + _chart.getColor = _; + return _chart; + }; + + return _chart; +}; + +/** +## Coordinate Grid Mixin + +Includes: [Color Mixin](#color-mixin), [Margin Mixin](#margin-mixin), [Base Mixin](#base-mixin) + +Coordinate Grid is an abstract base chart designed to support a number of coordinate grid based +concrete chart types, e.g. bar chart, line chart, and bubble chart. + +**/ +dc.coordinateGridMixin = function (_chart) { + var GRID_LINE_CLASS = "grid-line"; + var HORIZONTAL_CLASS = "horizontal"; + var VERTICAL_CLASS = "vertical"; + var Y_AXIS_LABEL_CLASS = 'y-axis-label'; + var X_AXIS_LABEL_CLASS = 'x-axis-label'; + var DEFAULT_AXIS_LABEL_PADDING = 12; + + _chart = dc.colorMixin(dc.marginMixin(dc.baseMixin(_chart))); + + _chart.colors(d3.scale.category10()); + _chart._mandatoryAttributes().push('x'); + + var _parent; + var _g; + var _chartBodyG; + + var _x; + var _xOriginalDomain; + var _xAxis = d3.svg.axis().orient("bottom"); + var _xUnits = dc.units.integers; + var _xAxisPadding = 0; + var _xElasticity = false; + var _xAxisLabel; + var _xAxisLabelPadding = 0; + var _lastXDomain; + + var _y; + var _yAxis = d3.svg.axis().orient("left"); + var _yAxisPadding = 0; + var _yElasticity = false; + var _yAxisLabel; + var _yAxisLabelPadding = 0; + + var _brush = d3.svg.brush(); + var _brushOn = true; + var _round; + + var _renderHorizontalGridLine = false; + var _renderVerticalGridLine = false; + + var _refocused = false; + var _unitCount; + + var _zoomScale = [1, Infinity]; + var _zoomOutRestrict = true; + + var _zoom = d3.behavior.zoom().on("zoom", zoomHandler); + var _nullZoom = d3.behavior.zoom().on("zoom", null); + var _hasBeenMouseZoomable = false; + + var _rangeChart; + var _focusChart; + + var _mouseZoomable = false; + var _clipPadding = 0; + + var _outerRangeBandPadding = 0.5; + var _rangeBandPadding = 0; + + var _useRightYAxis = false; + + _chart.rescale = function () { + _unitCount = undefined; + }; + + /** + #### .rangeChart([chart]) + Get or set the range selection chart associated with this instance. Setting the range selection + chart using this function will automatically update its selection brush when the current chart + zooms in. In return the given range chart will also automatically attach this chart as its focus + chart hence zoom in when range brush updates. See the [Nasdaq 100 + Index](http://dc-js.github.com/dc.js/) example for this effect in action. + + **/ + _chart.rangeChart = function (_) { + if (!arguments.length) return _rangeChart; + _rangeChart = _; + _rangeChart.focusChart(_chart); + return _chart; + }; + + /** + #### .zoomScale([extent]) + Get or set the scale extent for mouse zooms. + + **/ + _chart.zoomScale = function (_) { + if (!arguments.length) return _zoomScale; + _zoomScale = _; + return _chart; + }; + + /** + #### .zoomOutRestrict([true/false]) + Get or set the zoom restriction for the chart. If true limits the zoom to origional domain of the chart. + **/ + _chart.zoomOutRestrict = function (r) { + if (!arguments.length) return _zoomOutRestrict; + _zoomScale[0] = r ? 1 : 0; + _zoomOutRestrict = r; + return _chart; + }; + + _chart._generateG = function (parent) { + if (parent === undefined) + _parent = _chart.svg(); + else + _parent = parent; + + _g = _parent.append("g"); + + _chartBodyG = _g.append("g").attr("class", "chart-body") + .attr("transform", "translate(" + _chart.margins().left + ", " + _chart.margins().top + ")") + .attr("clip-path", "url(#" + getClipPathId() + ")"); + + return _g; + }; + + /** + #### .g([gElement]) + Get or set the root g element. This method is usually used to retrieve the g element in order to + overlay custom svg drawing programatically. **Caution**: The root g element is usually generated + by dc.js internals, and resetting it might produce unpredictable result. + + **/ + _chart.g = function (_) { + if (!arguments.length) return _g; + _g = _; + return _chart; + }; + + /** + #### .mouseZoomable([boolean]) + Set or get mouse zoom capability flag (default: false). When turned on the chart will be + zoomable using the mouse wheel. If the range selector chart is attached zooming will also update + the range selection brush on the associated range selector chart. + + **/ + _chart.mouseZoomable = function (z) { + if (!arguments.length) return _mouseZoomable; + _mouseZoomable = z; + return _chart; + }; + + /** + #### .chartBodyG() + Retrieve the svg group for the chart body. + **/ + _chart.chartBodyG = function (_) { + if (!arguments.length) return _chartBodyG; + _chartBodyG = _; + return _chart; + }; + + /** + #### .x([xScale]) - **mandatory** + Get or set the x scale. The x scale can be any d3 + [quantitive scale](https://github.com/mbostock/d3/wiki/Quantitative-Scales) or + [ordinal scale](https://github.com/mbostock/d3/wiki/Ordinal-Scales). + ```js + // set x to a linear scale + chart.x(d3.scale.linear().domain([-2500, 2500])) + // set x to a time scale to generate histogram + chart.x(d3.time.scale().domain([new Date(1985, 0, 1), new Date(2012, 11, 31)])) + ``` + + **/ + _chart.x = function (_) { + if (!arguments.length) return _x; + _x = _; + _xOriginalDomain = _x.domain(); + return _chart; + }; + + _chart.xOriginalDomain = function () { + return _xOriginalDomain; + }; + + /** + #### .xUnits([xUnits function]) + Set or get the xUnits function. The coordinate grid chart uses the xUnits function to calculate + the number of data projections on x axis such as the number of bars for a bar chart or the + number of dots for a line chart. This function is expected to return a Javascript array of all + data points on x axis, or the number of points on the axis. [d3 time range functions + d3.time.days, d3.time.months, and + d3.time.years](https://github.com/mbostock/d3/wiki/Time-Intervals#aliases) are all valid xUnits + function. dc.js also provides a few units function, see the [Utilities](#utilities) section for + a list of built-in units functions. The default xUnits function is dc.units.integers. + ```js + // set x units to count days + chart.xUnits(d3.time.days); + // set x units to count months + chart.xUnits(d3.time.months); + ``` + A custom xUnits function can be used as long as it follows the following interface: + ```js + // units in integer + function(start, end, xDomain) { + // simply calculates how many integers in the domain + return Math.abs(end - start); + }; + + // fixed units + function(start, end, xDomain) { + // be aware using fixed units will disable the focus/zoom ability on the chart + return 1000; + }; + ``` + + **/ + _chart.xUnits = function (_) { + if (!arguments.length) return _xUnits; + _xUnits = _; + return _chart; + }; + + /** + #### .xAxis([xAxis]) + Set or get the x axis used by a particular coordinate grid chart instance. This function is most + useful when x axis customization is required. The x axis in dc.js is an instance of a [d3 + axis object](https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-axis); therefore it supports any + valid d3 axis manipulation. **Caution**: The x axis is usually generated internally by dc; + resetting it may cause unexpected results. + ```js + // customize x axis tick format + chart.xAxis().tickFormat(function(v) {return v + "%";}); + // customize x axis tick values + chart.xAxis().tickValues([0, 100, 200, 300]); + ``` + + **/ + _chart.xAxis = function (_) { + if (!arguments.length) return _xAxis; + _xAxis = _; + return _chart; + }; + + /** + #### .elasticX([boolean]) + Turn on/off elastic x axis behavior. If x axis elasticity is turned on, then the grid chart will + attempt to recalculate the x axis range whenever a redraw event is triggered. + + **/ + _chart.elasticX = function (_) { + if (!arguments.length) return _xElasticity; + _xElasticity = _; + return _chart; + }; + + /** + #### .xAxisPadding([padding]) + Set or get x axis padding for the elastic x axis. The padding will be added to both end of the x + axis if elasticX is turned on; otherwise it is ignored. + + * padding can be an integer or percentage in string (e.g. "10%"). Padding can be applied to + number or date x axes. When padding a date axis, an integer represents number of days being padded + and a percentage string will be treated the same as an integer. + + **/ + _chart.xAxisPadding = function (_) { + if (!arguments.length) return _xAxisPadding; + _xAxisPadding = _; + return _chart; + }; + + /** + #### .xUnitCount() + Returns the number of units displayed on the x axis using the unit measure configured by + .xUnits. + **/ + _chart.xUnitCount = function () { + if (_unitCount === undefined) { + var units = _chart.xUnits()(_chart.x().domain()[0], _chart.x().domain()[1], _chart.x().domain()); + + if (units instanceof Array) + _unitCount = units.length; + else + _unitCount = units; + } + + return _unitCount; + }; + /** + #### .useRightYAxis() + Gets or sets whether the chart should be drawn with a right axis instead of a left axis. When + used with a chart in a composite chart, allows both left and right Y axes to be shown on a + chart. + **/ + + _chart.useRightYAxis = function (_) { + if (!arguments.length) return _useRightYAxis; + _useRightYAxis = _; + return _chart; + }; + + /** + #### isOrdinal() + Returns true if the chart is using ordinal xUnits ([dc.units.ordinal](#dcunitsordinal)), or false + otherwise. Most charts behave differently with ordinal data and use the result of this method to + trigger the appropriate logic. + **/ + _chart.isOrdinal = function () { + return _chart.xUnits() === dc.units.ordinal; + }; + + _chart._useOuterPadding = function() { + return true; + }; + + _chart._ordinalXDomain = function() { + var groups = _chart._computeOrderedGroups(_chart.data()); + return groups.map(_chart.keyAccessor()); + }; + + function prepareXAxis(g) { + if(!_chart.isOrdinal()) { + if (_chart.elasticX()) + _x.domain([_chart.xAxisMin(), _chart.xAxisMax()]); + } + else { // _chart.isOrdinal() + if(_chart.elasticX() || _x.domain().length===0) + _x.domain(_chart._ordinalXDomain()); + } + + // has the domain changed? + var xdom = _x.domain(); + if(!_lastXDomain || xdom.some(function(elem, i) { return elem != _lastXDomain[i]; })) + _chart.rescale(); + _lastXDomain = xdom; + + // please can't we always use rangeBands for bar charts? + if (_chart.isOrdinal()) { + _x.rangeBands([0,_chart.xAxisLength()],_rangeBandPadding, + _chart._useOuterPadding()?_outerRangeBandPadding:0); + } else { + _x.range([0, _chart.xAxisLength()]); + } + + _xAxis = _xAxis.scale(_chart.x()); + + renderVerticalGridLines(g); + } + + _chart.renderXAxis = function (g) { + var axisXG = g.selectAll("g.x"); + + if (axisXG.empty()) + axisXG = g.append("g") + .attr("class", "axis x") + .attr("transform", "translate(" + _chart.margins().left + "," + _chart._xAxisY() + ")"); + + var axisXLab = g.selectAll("text."+X_AXIS_LABEL_CLASS); + if (axisXLab.empty() && _chart.xAxisLabel()) + axisXLab = g.append('text') + .attr("transform", "translate(" + (_chart.margins().left + _chart.xAxisLength() / 2) + "," + (_chart.height() - _xAxisLabelPadding) + ")") + .attr('class', X_AXIS_LABEL_CLASS) + .attr('text-anchor', 'middle') + .text(_chart.xAxisLabel()); + if (_chart.xAxisLabel() && axisXLab.text() != _chart.xAxisLabel()) + axisXLab.text(_chart.xAxisLabel()); + + dc.transition(axisXG, _chart.transitionDuration()) + .call(_xAxis); + }; + + function renderVerticalGridLines(g) { + var gridLineG = g.selectAll("g." + VERTICAL_CLASS); + + if (_renderVerticalGridLine) { + if (gridLineG.empty()) + gridLineG = g.insert("g", ":first-child") + .attr("class", GRID_LINE_CLASS + " " + VERTICAL_CLASS) + .attr("transform", "translate(" + _chart.margins().left + "," + _chart.margins().top + ")"); + + var ticks = _xAxis.tickValues() ? _xAxis.tickValues() : _x.ticks(_xAxis.ticks()[0]); + + var lines = gridLineG.selectAll("line") + .data(ticks); + + // enter + var linesGEnter = lines.enter() + .append("line") + .attr("x1", function (d) { + return _x(d); + }) + .attr("y1", _chart._xAxisY() - _chart.margins().top) + .attr("x2", function (d) { + return _x(d); + }) + .attr("y2", 0) + .attr("opacity", 0); + dc.transition(linesGEnter, _chart.transitionDuration()) + .attr("opacity", 1); + + // update + dc.transition(lines, _chart.transitionDuration()) + .attr("x1", function (d) { + return _x(d); + }) + .attr("y1", _chart._xAxisY() - _chart.margins().top) + .attr("x2", function (d) { + return _x(d); + }) + .attr("y2", 0); + + // exit + lines.exit().remove(); + } + else { + gridLineG.selectAll("line").remove(); + } + } + + _chart._xAxisY = function () { + return (_chart.height() - _chart.margins().bottom); + }; + + _chart.xAxisLength = function () { + return _chart.effectiveWidth(); + }; + + /** + #### .xAxisLabel([labelText, [, padding]]) + Set or get the x axis label. If setting the label, you may optionally include additional padding to + the margin to make room for the label. By default the padded is set to 12 to accomodate the text height. + **/ + _chart.xAxisLabel = function (_, padding) { + if (!arguments.length) return _xAxisLabel; + _xAxisLabel = _; + _chart.margins().bottom -= _xAxisLabelPadding; + _xAxisLabelPadding = (padding === undefined) ? DEFAULT_AXIS_LABEL_PADDING : padding; + _chart.margins().bottom += _xAxisLabelPadding; + return _chart; + }; + + _chart._prepareYAxis = function(g) { + if (_y === undefined || _chart.elasticY()) { + _y = d3.scale.linear(); + var min = _chart.yAxisMin() || 0, + max = _chart.yAxisMax() || 0; + _y.domain([min, max]).rangeRound([_chart.yAxisHeight(), 0]); + } + + _y.range([_chart.yAxisHeight(), 0]); + _yAxis = _yAxis.scale(_y); + + if (_useRightYAxis) + _yAxis.orient("right"); + + _chart._renderHorizontalGridLinesForAxis(g, _y, _yAxis); + }; + + _chart.renderYAxisLabel = function(axisClass, text, rotation, labelXPosition) { + labelXPosition = labelXPosition || _yAxisLabelPadding; + + var axisYLab = _chart.g().selectAll("text." + Y_AXIS_LABEL_CLASS + "." + axisClass + "-label"); + if (axisYLab.empty() && text) { + + var labelYPosition = (_chart.margins().top + _chart.yAxisHeight() / 2); + axisYLab = _chart.g().append('text') + .attr("transform", "translate(" + labelXPosition + "," + labelYPosition + "),rotate(" + rotation + ")") + .attr('class', Y_AXIS_LABEL_CLASS + " " + axisClass + "-label") + .attr('text-anchor', 'middle') + .text(text); + } + if (text && axisYLab.text() != text) { + axisYLab.text(text); + } + }; + + _chart.renderYAxisAt = function (axisClass, axis, position){ + var axisYG = _chart.g().selectAll("g." + axisClass); + if (axisYG.empty()) { + axisYG = _chart.g().append("g") + .attr("class", "axis " + axisClass) + .attr("transform", "translate(" + position + "," + _chart.margins().top + ")"); + } + + dc.transition(axisYG, _chart.transitionDuration()).call(axis); + }; + + _chart.renderYAxis = function () { + var axisPosition = _useRightYAxis ? (_chart.width() - _chart.margins().right) : _chart._yAxisX(); + _chart.renderYAxisAt("y", _yAxis, axisPosition); + var labelPosition = _useRightYAxis ? (_chart.width() - _yAxisLabelPadding) : _yAxisLabelPadding; + var rotation = _useRightYAxis ? 90 : -90; + _chart.renderYAxisLabel("y", _chart.yAxisLabel(), rotation, labelPosition); + }; + + _chart._renderHorizontalGridLinesForAxis = function (g, scale, axis) { + var gridLineG = g.selectAll("g." + HORIZONTAL_CLASS); + + if (_renderHorizontalGridLine) { + var ticks = axis.tickValues() ? axis.tickValues() : scale.ticks(axis.ticks()[0]); + + if (gridLineG.empty()) { + gridLineG = g.insert("g", ":first-child") + .attr("class", GRID_LINE_CLASS + " " + HORIZONTAL_CLASS) + .attr("transform", "translate(" + _chart.margins().left + "," + _chart.margins().top + ")"); + } + + var lines = gridLineG.selectAll("line") + .data(ticks); + + // enter + var linesGEnter = lines.enter() + .append("line") + .attr("x1", 1) + .attr("y1", function (d) { + return scale(d); + }) + .attr("x2", _chart.xAxisLength()) + .attr("y2", function (d) { + return scale(d); + }) + .attr("opacity", 0); + dc.transition(linesGEnter, _chart.transitionDuration()) + .attr("opacity", 1); + + // update + dc.transition(lines, _chart.transitionDuration()) + .attr("x1", 1) + .attr("y1", function (d) { + return scale(d); + }) + .attr("x2", _chart.xAxisLength()) + .attr("y2", function (d) { + return scale(d); + }); + + // exit + lines.exit().remove(); + } + else { + gridLineG.selectAll("line").remove(); + } + }; + + _chart._yAxisX = function () { + return _chart.useRightYAxis() ? _chart.width() - _chart.margins().right : _chart.margins().left; + }; + + + /** + #### .yAxisLabel([labelText, [, padding]]) + Set or get the y axis label. If setting the label, you may optionally include additional padding + to the margin to make room for the label. By default the padded is set to 12 to accomodate the + text height. + **/ + _chart.yAxisLabel = function (_, padding) { + if (!arguments.length) return _yAxisLabel; + _yAxisLabel = _; + _chart.margins().left -= _yAxisLabelPadding; + _yAxisLabelPadding = (padding === undefined) ? DEFAULT_AXIS_LABEL_PADDING : padding; + _chart.margins().left += _yAxisLabelPadding; + return _chart; + }; + + /** + #### .y([yScale]) + Get or set the y scale. The y scale is typically automatically determined by the chart implementation. + + **/ + _chart.y = function (_) { + if (!arguments.length) return _y; + _y = _; + return _chart; + }; + + /** + #### .yAxis([yAxis]) + Set or get the y axis used by the coordinate grid chart instance. This function is most useful + when y axis customization is required. The y axis in dc.js is simply an instance of a [d3 axis + object](https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-_axis); therefore it supports any + valid d3 axis manipulation. **Caution**: The y axis is usually generated internally by dc; + resetting it may cause unexpected results. + ```js + // customize y axis tick format + chart.yAxis().tickFormat(function(v) {return v + "%";}); + // customize y axis tick values + chart.yAxis().tickValues([0, 100, 200, 300]); + ``` + + **/ + _chart.yAxis = function (y) { + if (!arguments.length) return _yAxis; + _yAxis = y; + return _chart; + }; + + /** + #### .elasticY([boolean]) + Turn on/off elastic y axis behavior. If y axis elasticity is turned on, then the grid chart will + attempt to recalculate the y axis range whenever a redraw event is triggered. + + **/ + _chart.elasticY = function (_) { + if (!arguments.length) return _yElasticity; + _yElasticity = _; + return _chart; + }; + + /** + #### .renderHorizontalGridLines([boolean]) + Turn on/off horizontal grid lines. + + **/ + _chart.renderHorizontalGridLines = function (_) { + if (!arguments.length) return _renderHorizontalGridLine; + _renderHorizontalGridLine = _; + return _chart; + }; + + /** + #### .renderVerticalGridLines([boolean]) + Turn on/off vertical grid lines. + + **/ + _chart.renderVerticalGridLines = function (_) { + if (!arguments.length) return _renderVerticalGridLine; + _renderVerticalGridLine = _; + return _chart; + }; + + /** + #### .xAxisMin() + Calculates the minimum x value to display in the chart. Includes xAxisPadding if set. + **/ + _chart.xAxisMin = function () { + var min = d3.min(_chart.data(), function (e) { + return _chart.keyAccessor()(e); + }); + return dc.utils.subtract(min, _xAxisPadding); + }; + + /** + #### .xAxisMax() + Calculates the maximum x value to display in the chart. Includes xAxisPadding if set. + **/ + _chart.xAxisMax = function () { + var max = d3.max(_chart.data(), function (e) { + return _chart.keyAccessor()(e); + }); + return dc.utils.add(max, _xAxisPadding); + }; + + /** + #### .yAxisMin() + Calculates the minimum y value to display in the chart. Includes yAxisPadding if set. + **/ + _chart.yAxisMin = function () { + var min = d3.min(_chart.data(), function (e) { + return _chart.valueAccessor()(e); + }); + return dc.utils.subtract(min, _yAxisPadding); + }; + + /** + #### .yAxisMax() + Calculates the maximum y value to display in the chart. Includes yAxisPadding if set. + **/ + _chart.yAxisMax = function () { + var max = d3.max(_chart.data(), function (e) { + return _chart.valueAccessor()(e); + }); + return dc.utils.add(max, _yAxisPadding); + }; + + /** + #### .yAxisPadding([padding]) + Set or get y axis padding for the elastic y axis. The padding will be added to the top of the y + axis if elasticY is turned on; otherwise it is ignored. + + * padding can be an integer or percentage in string (e.g. "10%"). Padding can be applied to + number or date axes. When padding a date axis, an integer represents number of days being padded + and a percentage string will be treated the same as an integer. + + **/ + _chart.yAxisPadding = function (_) { + if (!arguments.length) return _yAxisPadding; + _yAxisPadding = _; + return _chart; + }; + + _chart.yAxisHeight = function () { + return _chart.effectiveHeight(); + }; + + /** + #### .round([rounding function]) + Set or get the rounding function used to quantize the selection when brushing is enabled. + ```js + // set x unit round to by month, this will make sure range selection brush will + // select whole months + chart.round(d3.time.month.round); + ``` + + **/ + _chart.round = function (_) { + if (!arguments.length) return _round; + _round = _; + return _chart; + }; + + _chart._rangeBandPadding = function (_) { + if (!arguments.length) return _rangeBandPadding; + _rangeBandPadding = _; + return _chart; + }; + + _chart._outerRangeBandPadding = function (_) { + if (!arguments.length) return _outerRangeBandPadding; + _outerRangeBandPadding = _; + return _chart; + }; + + dc.override(_chart, "filter", function (_) { + if (!arguments.length) return _chart._filter(); + + _chart._filter(_); + + if (_) { + _chart.brush().extent(_); + } else { + _chart.brush().clear(); + } + + return _chart; + }); + + _chart.brush = function (_) { + if (!arguments.length) return _brush; + _brush = _; + return _chart; + }; + + function brushHeight() { + return _chart._xAxisY() - _chart.margins().top; + } + + _chart.renderBrush = function (g) { + if (_brushOn) { + _brush.on("brush", _chart._brushing); + _brush.on("brushstart", _chart._disableMouseZoom); + _brush.on("brushend", configureMouseZoom); + + var gBrush = g.append("g") + .attr("class", "brush") + .attr("transform", "translate(" + _chart.margins().left + "," + _chart.margins().top + ")") + .call(_brush.x(_chart.x())); + _chart.setBrushY(gBrush); + _chart.setHandlePaths(gBrush); + + if (_chart.hasFilter()) { + _chart.redrawBrush(g); + } + } + }; + + _chart.setHandlePaths = function (gBrush) { + gBrush.selectAll(".resize").append("path").attr("d", _chart.resizeHandlePath); + }; + + _chart.setBrushY = function(gBrush){ + gBrush.selectAll("rect").attr("height", brushHeight()); + }; + + _chart.extendBrush = function () { + var extent = _brush.extent(); + if (_chart.round()) { + extent[0] = extent.map(_chart.round())[0]; + extent[1] = extent.map(_chart.round())[1]; + + _g.select(".brush") + .call(_brush.extent(extent)); + } + return extent; + }; + + _chart.brushIsEmpty = function (extent) { + return _brush.empty() || !extent || extent[1] <= extent[0]; + }; + + _chart._brushing = function() { + var extent = _chart.extendBrush(); + + _chart.redrawBrush(_g); + + if (_chart.brushIsEmpty(extent)) { + dc.events.trigger(function () { + _chart.filter(null); + _chart.redrawGroup(); + }, dc.constants.EVENT_DELAY); + } else { + var rangedFilter = dc.filters.RangedFilter(extent[0], extent[1]); + + dc.events.trigger(function () { + _chart.replaceFilter(rangedFilter); + _chart.redrawGroup(); + }, dc.constants.EVENT_DELAY); + } + }; + + _chart.redrawBrush = function (g) { + if (_brushOn) { + if (_chart.filter() && _chart.brush().empty()) + _chart.brush().extent(_chart.filter()); + + var gBrush = g.select("g.brush"); + gBrush.call(_chart.brush().x(_chart.x())); + _chart.setBrushY(gBrush); + } + + _chart.fadeDeselectedArea(); + }; + + _chart.fadeDeselectedArea = function () { + // do nothing, sub-chart should override this function + }; + + // borrowed from Crossfilter example + _chart.resizeHandlePath = function (d) { + var e = +(d == "e"), x = e ? 1 : -1, y = brushHeight() / 3; + /*jshint -W014 */ + return "M" + (0.5 * x) + "," + y + + "A6,6 0 0 " + e + " " + (6.5 * x) + "," + (y + 6) + + "V" + (2 * y - 6) + + "A6,6 0 0 " + e + " " + (0.5 * x) + "," + (2 * y) + + "Z" + + "M" + (2.5 * x) + "," + (y + 8) + + "V" + (2 * y - 8) + + "M" + (4.5 * x) + "," + (y + 8) + + "V" + (2 * y - 8); + /*jshint +W014 */ + }; + + function getClipPathId() { + return _chart.anchorName() + "-clip"; + } + + /** + #### .clipPadding([padding]) + Get or set the padding in pixels for the clip path. Once set padding will be applied evenly to + the top, left, right, and bottom when the clip path is generated. If set to zero, the clip area + will be exactly the chart body area minus the margins. Default: 5 + + **/ + _chart.clipPadding = function (p) { + if (!arguments.length) return _clipPadding; + _clipPadding = p; + return _chart; + }; + + function generateClipPath() { + var defs = dc.utils.appendOrSelect(_parent, "defs"); + + var chartBodyClip = dc.utils.appendOrSelect(defs, "clipPath").attr("id", getClipPathId()); + + var padding = _clipPadding * 2; + + dc.utils.appendOrSelect(chartBodyClip, "rect") + .attr("width", _chart.xAxisLength() + padding) + .attr("height", _chart.yAxisHeight() + padding); + } + + _chart._preprocessData = function() {}; + + _chart._doRender = function () { + _chart.resetSvg(); + + _chart._preprocessData(); + + _chart._generateG(); + generateClipPath(); + + drawChart(true); + + configureMouseZoom(); + + return _chart; + }; + + _chart._doRedraw = function () { + _chart._preprocessData(); + + drawChart(false); + + return _chart; + }; + + function drawChart (render) { + if (_chart.isOrdinal()) + _brushOn = false; + + prepareXAxis(_chart.g()); + _chart._prepareYAxis(_chart.g()); + + _chart.plotData(); + + if (_chart.elasticX() || _refocused || render) + _chart.renderXAxis(_chart.g()); + + if (_chart.elasticY() || render) + _chart.renderYAxis(_chart.g()); + + if (render) + _chart.renderBrush(_chart.g()); + else + _chart.redrawBrush(_chart.g()); + } + + function configureMouseZoom () { + if (_mouseZoomable) { + _chart._enableMouseZoom(); + } + else if (_hasBeenMouseZoomable) { + _chart._disableMouseZoom(); + } + } + + _chart._enableMouseZoom = function () { + _hasBeenMouseZoomable = true; + _zoom.x(_chart.x()) + .scaleExtent(_zoomScale) + .size([_chart.width(),_chart.height()]); + _chart.root().call(_zoom); + }; + + _chart._disableMouseZoom = function () { + _chart.root().call(_nullZoom); + }; + + function zoomHandler() { + _refocused = true; + if (_zoomOutRestrict) { + _chart.x().domain(constrainRange(_chart.x().domain(), _xOriginalDomain)); + if (_rangeChart) { + _chart.x().domain(constrainRange(_chart.x().domain(), _rangeChart.x().domain())); + } + } + + var domain = _chart.x().domain(); + var domFilter = dc.filters.RangedFilter(domain[0], domain[1]); + + _chart.replaceFilter(domFilter); + _chart.rescale(); + _chart.redraw(); + + if (_rangeChart && !rangesEqual(_chart.filter(), _rangeChart.filter())) { + dc.events.trigger( function () { + _rangeChart.replaceFilter(domFilter); + _rangeChart.redraw(); + }); + } + + _chart._invokeZoomedListener(); + + dc.events.trigger(function () { + _chart.redrawGroup(); + }, dc.constants.EVENT_DELAY); + + _refocused = !rangesEqual(domain, _xOriginalDomain); + } + + function constrainRange(range, constraint) { + var constrainedRange = []; + constrainedRange[0] = d3.max([range[0], constraint[0]]); + constrainedRange[1] = d3.min([range[1], constraint[1]]); + return constrainedRange; + } + + /** + #### .focus([range]) + Zoom this chart to focus on the given range. The given range should be an array containing only + 2 elements (`[start, end]`) defining a range in the x domain. If the range is not given or set + to null, then the zoom will be reset. _For focus to work elasticX has to be turned off; + otherwise focus will be ignored._ + ```js + chart.renderlet(function(chart){ + // smooth the rendering through event throttling + dc.events.trigger(function(){ + // focus some other chart to the range selected by user on this chart + someOtherChart.focus(chart.filter()); + }); + }) + ``` + + **/ + _chart.focus = function (range) { + if (hasRangeSelected(range)) + _chart.x().domain(range); + else + _chart.x().domain(_xOriginalDomain); + + _zoom.x(_chart.x()); + zoomHandler(); + }; + + _chart.refocused = function () { + return _refocused; + }; + + _chart.focusChart = function (c) { + if (!arguments.length) return _focusChart; + _focusChart = c; + _chart.on("filtered", function (chart) { + if (!chart.filter()) { + dc.events.trigger(function() { + _focusChart.x().domain(_focusChart.xOriginalDomain()); + }); + } else if (!rangesEqual(chart.filter(), _focusChart.filter())) { + dc.events.trigger(function () { + _focusChart.focus(chart.filter()); + }); + } + }); + return _chart; + }; + + function rangesEqual(range1, range2) { + if (!range1 && !range2) { + return true; + } + else if (!range1 || !range2) { + return false; + } + else if (range1.length === 0 && range2.length === 0) { + return true; + } + else if (range1[0].valueOf() === range2[0].valueOf() && + range1[1].valueOf() === range2[1].valueOf()) { + return true; + } + else return false; + } + + /** + #### .brushOn([boolean]) + Turn on/off the brush-based range filter. When brushing is on then user can drag the mouse + across a chart with a quantitative scale to perform range filtering based on the extent of the + brush, or click on the bars of an ordinal bar chart or slices of a pie chart to filter and + unfilter them. However turning on the brush filter will disable other interactive elements on + the chart such as highlighting, tool tips, and reference lines. Zooming will still be possible + if enabled, but only via scrolling (panning will be disabled.) Default: true + + **/ + _chart.brushOn = function (_) { + if (!arguments.length) return _brushOn; + _brushOn = _; + return _chart; + }; + + function hasRangeSelected(range) { + return range instanceof Array && range.length > 1; + } + + return _chart; +}; + +/** +## Stack Mixin + +Stack Mixin is an mixin that provides cross-chart support of stackability using d3.layout.stack. + +**/ +dc.stackMixin = function (_chart) { + + var _stackLayout = d3.layout.stack() + .values(prepareValues); + + var _stack = []; + var _titles = {}; + + var _hidableStacks = false; + + function prepareValues(layer, layerIdx) { + var valAccessor = layer.accessor || _chart.valueAccessor(); + layer.name = String(layer.name || layerIdx); + layer.values = layer.group.all().map(function(d,i) { + return {x: _chart.keyAccessor()(d,i), + y: layer.hidden ? null : valAccessor(d,i), + data: d, + layer: layer.name, + hidden: layer.hidden}; + }); + + layer.values = layer.values.filter(domainFilter()); + return layer.values; + } + + function domainFilter() { + if (!_chart.x()) return d3.functor(true); + var xDomain = _chart.x().domain(); + if (_chart.isOrdinal()) { + // TODO #416 + //var domainSet = d3.set(xDomain); + return function(p) { + return true; //domainSet.has(p.x); + }; + } + if (_chart.elasticX()) { + return function() { return true; }; + } + return function(p) { + //return true; + return p.x >= xDomain[0] && p.x <= xDomain[xDomain.length-1]; + }; + } + + /** + #### .stack(group[, name, accessor]) + Stack a new crossfilter group onto this chart with an optional custom value accessor. All stacks + in the same chart will share the same key accessor and therefore the same set of keys. + + For example, in a stacked bar chart, the bars of each stack will be positioned using the same set + of keys on the x axis, while stacked vertically. If name is specified then it will be used to + generate the legend label. + ```js + // stack group using default accessor + chart.stack(valueSumGroup) + // stack group using custom accessor + .stack(avgByDayGroup, function(d){return d.value.avgByDay;}); + ``` + + **/ + _chart.stack = function (group, name, accessor) { + if (!arguments.length) return _stack; + + if (arguments.length <= 2) + accessor = name; + + var layer = {group:group}; + if (typeof name === 'string') layer.name = name; + if (typeof accessor === 'function') layer.accessor = accessor; + _stack.push(layer); + + return _chart; + }; + + dc.override(_chart,'group', function (g,n,f) { + if (!arguments.length) return _chart._group(); + _stack = []; + _titles = {}; + _chart.stack(g,n); + if (f) _chart.valueAccessor(f); + return _chart._group(g,n); + }); + + /** + #### .hidableStacks([boolean]) + Allow named stacks to be hidden or shown by clicking on legend items. + This does not affect the behavior of hideStack or showStack. + + **/ + _chart.hidableStacks = function(_) { + if (!arguments.length) return _hidableStacks; + _hidableStacks = _; + return _chart; + }; + + function findLayerByName(n) { + var i = _stack.map(dc.pluck('name')).indexOf(n); + return _stack[i]; + } + + /** + #### .hideStack(name) + Hide all stacks on the chart with the given name. + The chart must be re-rendered for this change to appear. + + **/ + _chart.hideStack = function (stackName) { + var layer = findLayerByName(stackName); + if (layer) layer.hidden = true; + return _chart; + }; + + /** + #### .showStack(name) + Show all stacks on the chart with the given name. + The chart must be re-rendered for this change to appear. + + **/ + _chart.showStack = function (stackName) { + var layer = findLayerByName(stackName); + if (layer) layer.hidden = false; + return _chart; + }; + + _chart.getValueAccessorByIndex = function (index) { + return _stack[index].accessor || _chart.valueAccessor(); + }; + + _chart.yAxisMin = function () { + var min = d3.min(flattenStack(), function (p) { + return (p.y + p.y0 < p.y0) ? (p.y + p.y0) : p.y0; + }); + + return dc.utils.subtract(min, _chart.yAxisPadding()); + + }; + + _chart.yAxisMax = function () { + var max = d3.max(flattenStack(), function (p) { + return p.y + p.y0; + }); + + return dc.utils.add(max, _chart.yAxisPadding()); + }; + + function flattenStack() { + return _chart.data().reduce(function(all,layer) { + return all.concat(layer.values); + },[]); + } + + _chart.xAxisMin = function () { + var min = d3.min(flattenStack(), dc.pluck('x')); + return dc.utils.subtract(min, _chart.xAxisPadding()); + }; + + _chart.xAxisMax = function () { + var max = d3.max(flattenStack(), dc.pluck('x')); + return dc.utils.add(max, _chart.xAxisPadding()); + }; + + /** + #### .title([stackName], [titleFunction]) + Set or get the title function. Chart class will use this function to render svg title (usually interpreted by 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. Almost + every chart supports title function however in grid coordinate chart you need to turn off brush in order to use title + otherwise the brush layer will block tooltip trigger. + + If the first argument is a stack name, the title function will get or set the title for that stack. If stackName + is not provided, the first stack is implied. + ```js + // set a title function on "first stack" + chart.title("first stack", function(d) { return d.key + ": " + d.value; }); + // get a title function from "second stack" + var secondTitleFunction = chart.title("second stack"); + ); + ``` + **/ + dc.override(_chart, "title", function (stackName, titleAccessor) { + if (!stackName) return _chart._title(); + + if (typeof stackName === 'function') return _chart._title(stackName); + if (stackName == _chart._groupName && typeof titleAccessor === 'function') + return _chart._title(titleAccessor); + + if (typeof titleAccessor !== 'function') return _titles[stackName] || _chart._title(); + + _titles[stackName] = titleAccessor; + + return _chart; + }); + + /** + #### .stackLayout([layout]) + Gets or sets the stack layout algorithm, which computes a baseline for each stack and + propagates it to the next. The default is + [d3.layout.stack](https://github.com/mbostock/d3/wiki/Stack-Layout#stack). + **/ + _chart.stackLayout = function (stack) { + if (!arguments.length) return _stackLayout; + _stackLayout = stack; + return _chart; + }; + + function visability(l) { + return !l.hidden; + } + + _chart.data(function() { + var layers = _stack.filter(visability); + return layers.length ? _chart.stackLayout()(layers) : []; + }); + + _chart._ordinalXDomain = function () { + return flattenStack().map(dc.pluck('x')); + }; + + _chart.colorAccessor(function (d) { + var layer = this.layer || this.name || d.name || d.layer; + return layer; + }); + + _chart.legendables = function () { + return _stack.map(function (layer, i) { + return {chart:_chart, name:layer.name, hidden: layer.hidden || false, color:_chart.getColor.call(layer,layer.values,i)}; + }); + }; + + _chart.isLegendableHidden = function (d) { + var layer = findLayerByName(d.name); + return layer ? layer.hidden : false; + }; + + _chart.legendToggle = function (d) { + if(_hidableStacks) { + if (_chart.isLegendableHidden(d)) _chart.showStack(d.name); + else _chart.hideStack(d.name); + //_chart.redraw(); + _chart.renderGroup(); + } + }; + + return _chart; +}; + +/** +## Cap Mixin + +Cap is a mixin that groups small data elements below a _cap_ into an *others* grouping for both the +Row and Pie Charts. + +The top ordered elements in the group up to the cap amount will be kept in the chart, and the rest +will be replaced with an *others* element, with value equal to the sum of the replaced values. The +keys of the elements below the cap limit are recorded in order to filter by those keys when the +*others* element is clicked. + +**/ +dc.capMixin = function (_chart) { + + var _cap = Infinity; + + var _othersLabel = "Others"; + + var _othersGrouper = function (topRows) { + var topRowsSum = d3.sum(topRows, _chart.valueAccessor()), + allRows = _chart.group().all(), + allRowsSum = d3.sum(allRows, _chart.valueAccessor()), + topKeys = topRows.map(_chart.keyAccessor()), + allKeys = allRows.map(_chart.keyAccessor()), + topSet = d3.set(topKeys), + others = allKeys.filter(function(d){return !topSet.has(d);}); + if (allRowsSum > topRowsSum) + return topRows.concat([{"others": others, "key": _othersLabel, "value": allRowsSum - topRowsSum}]); + return topRows; + }; + + _chart.cappedKeyAccessor = function(d,i) { + if (d.others) + return d.key; + return _chart.keyAccessor()(d,i); + }; + + _chart.cappedValueAccessor = function(d,i) { + if (d.others) + return d.value; + return _chart.valueAccessor()(d,i); + }; + + _chart.data(function(group) { + if (_cap == Infinity) { + return _chart._computeOrderedGroups(group.all()); + } else { + var topRows = group.top(_cap); // ordered by crossfilter group order (default value) + topRows = _chart._computeOrderedGroups(topRows); // re-order using ordering (default key) + if (_othersGrouper) return _othersGrouper(topRows); + return topRows; + } + }); + + /** + #### .cap([count]) + Get or set the count of elements to that will be included in the cap. + **/ + _chart.cap = function (_) { + if (!arguments.length) return _cap; + _cap = _; + return _chart; + }; + + /** + #### .othersLabel([label]) + Get or set the label for *Others* slice when slices cap is specified. Default label is **Others**. + **/ + _chart.othersLabel = function (_) { + if (!arguments.length) return _othersLabel; + _othersLabel = _; + return _chart; + }; + + /** + #### .othersGrouper([grouperFunction]) + Get or set the grouper function that will perform the insertion of data for the *Others* slice + if the slices cap is specified. If set to a falsy value, no others will be added. By default the + grouper function computes the sum of all values below the cap. + ```js + chart.othersGrouper(function (data) { + // compute the value for others, presumably the sum of all values below the cap + var othersSum = yourComputeOthersValueLogic(data) + + // the keys are needed to properly filter when the others element is clicked + var othersKeys = yourComputeOthersKeysArrayLogic(data); + + // add the others row to the dataset + data.push({"key": "Others", "value": othersSum, "others": othersKeys }); + + return data; + }); + ``` + **/ + _chart.othersGrouper = function (_) { + if (!arguments.length) return _othersGrouper; + _othersGrouper = _; + return _chart; + }; + + dc.override(_chart, "onClick", function (d) { + if (d.others) + _chart.filter([d.others]); + _chart._onClick(d); + }); + + return _chart; +}; + +/** +## Bubble Mixin + +Includes: [Color Mixin](#color-mixin) + +This Mixin provides reusable functionalities for any chart that needs to visualize data using bubbles. + +**/ +dc.bubbleMixin = function (_chart) { + var _maxBubbleRelativeSize = 0.3; + var _minRadiusWithLabel = 10; + + _chart.BUBBLE_NODE_CLASS = "node"; + _chart.BUBBLE_CLASS = "bubble"; + _chart.MIN_RADIUS = 10; + + _chart = dc.colorMixin(_chart); + + _chart.renderLabel(true); + _chart.renderTitle(false); + + _chart.data(function(group) { + return group.top(Infinity); + }); + + var _r = d3.scale.linear().domain([0, 100]); + + var _rValueAccessor = function (d) { + return d.r; + }; + + /** + #### .r([bubbleRadiusScale]) + Get or set the bubble radius scale. By default the bubble chart uses + `d3.scale.linear().domain([0, 100])` as its r scale . + + **/ + _chart.r = function (_) { + if (!arguments.length) return _r; + _r = _; + return _chart; + }; + + /** + #### .radiusValueAccessor([radiusValueAccessor]) + Get or set the radius value accessor function. If set, the radius value accessor function will + be used to retrieve a data value for each bubble. The data retrieved then will be mapped using + the r scale to the actual bubble radius. This allows you to encode a data dimension using bubble + size. + + **/ + _chart.radiusValueAccessor = function (_) { + if (!arguments.length) return _rValueAccessor; + _rValueAccessor = _; + return _chart; + }; + + _chart.rMin = function () { + var min = d3.min(_chart.data(), function (e) { + return _chart.radiusValueAccessor()(e); + }); + return min; + }; + + _chart.rMax = function () { + var max = d3.max(_chart.data(), function (e) { + return _chart.radiusValueAccessor()(e); + }); + return max; + }; + + _chart.bubbleR = function (d) { + var value = _chart.radiusValueAccessor()(d); + var r = _chart.r()(value); + if (isNaN(r) || value <= 0) + r = 0; + return r; + }; + + var labelFunction = function (d) { + return _chart.label()(d); + }; + + var labelOpacity = function (d) { + return (_chart.bubbleR(d) > _minRadiusWithLabel) ? 1 : 0; + }; + + _chart._doRenderLabel = function (bubbleGEnter) { + if (_chart.renderLabel()) { + var label = bubbleGEnter.select("text"); + + if (label.empty()) { + label = bubbleGEnter.append("text") + .attr("text-anchor", "middle") + .attr("dy", ".3em") + .on("click", _chart.onClick); + } + + label + .attr("opacity", 0) + .text(labelFunction); + dc.transition(label, _chart.transitionDuration()) + .attr("opacity", labelOpacity); + } + }; + + _chart.doUpdateLabels = function (bubbleGEnter) { + if (_chart.renderLabel()) { + var labels = bubbleGEnter.selectAll("text") + .text(labelFunction); + dc.transition(labels, _chart.transitionDuration()) + .attr("opacity", labelOpacity); + } + }; + + var titleFunction = function (d) { + return _chart.title()(d); + }; + + _chart._doRenderTitles = function (g) { + if (_chart.renderTitle()) { + var title = g.select("title"); + + if (title.empty()) + g.append("title").text(titleFunction); + } + }; + + _chart.doUpdateTitles = function (g) { + if (_chart.renderTitle()) { + g.selectAll("title").text(titleFunction); + } + }; + + /** + #### .minRadiusWithLabel([radius]) + Get or set the minimum radius for label rendering. If a bubble's radius is less than this value + then no label will be rendered. Default: 10 + + **/ + _chart.minRadiusWithLabel = function (_) { + if (!arguments.length) return _minRadiusWithLabel; + _minRadiusWithLabel = _; + return _chart; + }; + + /** + #### .maxBubbleRelativeSize([relativeSize]) + Get or set the maximum relative size of a bubble to the length of x axis. This value is useful + when the difference in radius between bubbles is too great. Default: 0.3 + + **/ + _chart.maxBubbleRelativeSize = function (_) { + if (!arguments.length) return _maxBubbleRelativeSize; + _maxBubbleRelativeSize = _; + return _chart; + }; + + _chart.fadeDeselectedArea = function () { + if (_chart.hasFilter()) { + _chart.selectAll("g." + _chart.BUBBLE_NODE_CLASS).each(function (d) { + if (_chart.isSelectedNode(d)) { + _chart.highlightSelected(this); + } else { + _chart.fadeDeselected(this); + } + }); + } else { + _chart.selectAll("g." + _chart.BUBBLE_NODE_CLASS).each(function (d) { + _chart.resetHighlight(this); + }); + } + }; + + _chart.isSelectedNode = function (d) { + return _chart.hasFilter(d.key); + }; + + _chart.onClick = function (d) { + var filter = d.key; + dc.events.trigger(function () { + _chart.filter(filter); + _chart.redrawGroup(); + }); + }; + + return _chart; +}; + +/** +## Pie Chart + +Includes: [Cap Mixin](#cap-mixin), [Color Mixin](#color-mixin), [Base Mixin](#base-mixin) + +The pie chart implementation is usually used to visualize a small categorical distribution. The pie +chart uses keyAccessor to determine the slices, and valueAccessor to calculate the size of each +slice relative to the sum of all values. Slices are ordered by `.ordering` which defaults to sorting +by key. + +Examples: + +* [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) + +#### dc.pieChart(parent[, chartGroup]) +Create a pie chart instance and attaches it to the given parent element. + +Parameters: + +* parent : string | node | selection - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying + a dom block element such as a div; or a dom element or d3 selection. + +* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. + Interaction with a chart will only trigger events and redraws within the chart's group. + +Returns: +A newly created pie chart instance + +```js +// create a pie chart under #chart-container1 element using the default global chart group +var chart1 = dc.pieChart("#chart-container1"); +// create a pie chart under #chart-container2 element using chart group A +var chart2 = dc.pieChart("#chart-container2", "chartGroupA"); +``` + +**/ +dc.pieChart = function (parent, chartGroup) { + var DEFAULT_MIN_ANGLE_FOR_LABEL = 0.5; + + var _sliceCssClass = "pie-slice"; + var _emptyCssClass = "empty-chart"; + var _emptyTitle = "empty"; + + var _radius, + _innerRadius = 0; + + var _g; + + var _cx; + + var _cy; + + var _minAngleForLabel = DEFAULT_MIN_ANGLE_FOR_LABEL; + + var _externalLabelRadius; + + var _chart = dc.capMixin(dc.colorMixin(dc.baseMixin({}))); + + _chart.colorAccessor(_chart.cappedKeyAccessor); + + _chart.title(function (d) { + return _chart.cappedKeyAccessor(d) + ": " + _chart.cappedValueAccessor(d); + }); + + /** + #### .slicesCap([cap]) + Get or set the maximum number of slices the pie chart will generate. The top slices are determined by + value from high to low. Other slices exeeding the cap will be rolled up into one single *Others* slice. + The resulting data will still be sorted by .ordering (default by key). + + **/ + _chart.slicesCap = _chart.cap; + + _chart.label(_chart.cappedKeyAccessor); + _chart.renderLabel(true); + + _chart.transitionDuration(350); + + _chart._doRender = function () { + _chart.resetSvg(); + + _g = _chart.svg() + .append("g") + .attr("transform", "translate(" + _chart.cx() + "," + _chart.cy() + ")"); + + drawChart(); + + return _chart; + }; + + function drawChart() { + // set radius on basis of chart dimension if missing + _radius = _radius ? _radius : d3.min([_chart.width(), _chart.height()]) /2; + + var arc = buildArcs(); + + var pie = pieLayout(); + var pieData; + // if we have data... + if(d3.sum(_chart.data(), _chart.valueAccessor())) { + pieData = pie(_chart.data()); + _g.classed(_emptyCssClass, false); + } else { + // otherwise we'd be getting NaNs, so override + // note: abuse others for its ignoring the value accessor + pieData = pie([{key:_emptyTitle, value:1, others: [_emptyTitle]}]); + _g.classed(_emptyCssClass, true); + } + + if (_g) { + var slices = _g.selectAll("g." + _sliceCssClass) + .data(pieData); + + createElements(slices, arc, pieData); + + updateElements(pieData, arc); + + removeElements(slices); + + highlightFilter(); + } + } + + function createElements(slices, arc, pieData) { + var slicesEnter = createSliceNodes(slices); + + createSlicePath(slicesEnter, arc); + + createTitles(slicesEnter); + + createLabels(pieData, arc); + } + + function createSliceNodes(slices) { + var slicesEnter = slices + .enter() + .append("g") + .attr("class", function (d, i) { + return _sliceCssClass + " _" + i; + }); + return slicesEnter; + } + + function createSlicePath(slicesEnter, arc) { + var slicePath = slicesEnter.append("path") + .attr("fill", fill) + .on("click", onClick) + .attr("d", function (d, i) { + return safeArc(d, i, arc); + }); + + dc.transition(slicePath, _chart.transitionDuration(), function (s) { + s.attrTween("d", tweenPie); + }); + } + + function createTitles(slicesEnter) { + if (_chart.renderTitle()) { + slicesEnter.append("title").text(function (d) { + return _chart.title()(d); + }); + } + } + + function positionLabels(labelsEnter, arc) { + dc.transition(labelsEnter, _chart.transitionDuration()) + .attr("transform", function (d) { + return labelPosition(d, arc); + }) + .attr("text-anchor", "middle") + .text(function (d) { + var data = d.data; + if (sliceHasNoData(data) || sliceTooSmall(d)) + return ""; + return _chart.label()(d.data); + }); + } + + function createLabels(pieData, arc) { + if (_chart.renderLabel()) { + var labels = _g.selectAll("text." + _sliceCssClass) + .data(pieData); + + labels.exit().remove(); + + var labelsEnter = labels + .enter() + .append("text") + .attr("class", function (d, i) { + var classes = _sliceCssClass + " _" + i; + if(_externalLabelRadius) + classes += " external"; + return classes; + }) + .on("click", onClick); + positionLabels(labelsEnter, arc); + } + } + + function updateElements(pieData, arc) { + updateSlicePaths(pieData, arc); + updateLabels(pieData, arc); + updateTitles(pieData); + } + + function updateSlicePaths(pieData, arc) { + var slicePaths = _g.selectAll("g." + _sliceCssClass) + .data(pieData) + .select("path") + .attr("d", function (d, i) { + return safeArc(d, i, arc); + }); + dc.transition(slicePaths, _chart.transitionDuration(), + function (s) { + s.attrTween("d", tweenPie); + }).attr("fill", fill); + } + + function updateLabels(pieData, arc) { + if (_chart.renderLabel()) { + var labels = _g.selectAll("text." + _sliceCssClass) + .data(pieData); + positionLabels(labels, arc); + } + } + + function updateTitles(pieData) { + if (_chart.renderTitle()) { + _g.selectAll("g." + _sliceCssClass) + .data(pieData) + .select("title") + .text(function (d) { + return _chart.title()(d.data); + }); + } + } + + function removeElements(slices) { + slices.exit().remove(); + } + + function highlightFilter() { + if (_chart.hasFilter()) { + _chart.selectAll("g." + _sliceCssClass).each(function (d) { + if (isSelectedSlice(d)) { + _chart.highlightSelected(this); + } else { + _chart.fadeDeselected(this); + } + }); + } else { + _chart.selectAll("g." + _sliceCssClass).each(function (d) { + _chart.resetHighlight(this); + }); + } + } + + /** + #### .innerRadius([innerRadius]) + Get or set the inner radius of the pie chart. If the inner radius is greater than 0px then the + pie chart will be rendered as a doughnut chart. Default inner radius is 0px. + + **/ + _chart.innerRadius = function (r) { + if (!arguments.length) return _innerRadius; + _innerRadius = r; + return _chart; + }; + + /** + #### .radius([radius]) + Get or set the outer radius. Default radius is 90px. + + **/ + _chart.radius = function (r) { + if (!arguments.length) return _radius; + _radius = r; + return _chart; + }; + + /** + #### .cx([cx]) + Get or set center x coordinate position. Default is center of svg. + + **/ + _chart.cx = function (cx) { + if (!arguments.length) return (_cx || _chart.width() / 2); + _cx = cx; + return _chart; + }; + + /** + #### .cy([cy]) + Get or set center y coordinate position. Default is center of svg. + + **/ + _chart.cy = function (cy) { + if (!arguments.length) return (_cy || _chart.height() / 2); + _cy = cy; + return _chart; + }; + + function buildArcs() { + return d3.svg.arc().outerRadius(_radius).innerRadius(_innerRadius); + } + + function isSelectedSlice(d) { + return _chart.hasFilter(_chart.cappedKeyAccessor(d.data)); + } + + _chart._doRedraw = function () { + drawChart(); + return _chart; + }; + + /** + #### .minAngleForLabel([minAngle]) + Get or set the minimal slice angle for label rendering. Any slice with a smaller angle will not + display a slice label. Default min angle is 0.5. + **/ + _chart.minAngleForLabel = function (_) { + if (!arguments.length) return _minAngleForLabel; + _minAngleForLabel = _; + return _chart; + }; + + function pieLayout() { + return d3.layout.pie().sort(null).value(_chart.cappedValueAccessor); + } + + function sliceTooSmall(d) { + var angle = (d.endAngle - d.startAngle); + return isNaN(angle) || angle < _minAngleForLabel; + } + + function sliceHasNoData(d) { + return _chart.cappedValueAccessor(d) === 0; + } + + function tweenPie(b) { + b.innerRadius = _innerRadius; + var current = this._current; + if (isOffCanvas(current)) + current = {startAngle: 0, endAngle: 0}; + var i = d3.interpolate(current, b); + this._current = i(0); + return function (t) { + return safeArc(i(t), 0, buildArcs()); + }; + } + + function isOffCanvas(current) { + return !current || isNaN(current.startAngle) || isNaN(current.endAngle); + } + + function fill(d, i) { + return _chart.getColor(d.data, i); + } + + function onClick(d, i) { + if (_g.attr("class") != _emptyCssClass) + _chart.onClick(d.data, i); + } + + function safeArc(d, i, arc) { + var path = arc(d, i); + if (path.indexOf("NaN") >= 0) + path = "M0,0"; + return path; + } + + /** + #### .emptyTitle([title]) + Title to use for the only slice when there is no data + */ + _chart.emptyTitle = function(title) { + if (arguments.length === 0) + return _emptyTitle; + _emptyTitle = title; + return _chart; + }; + + /** + #### .externalLabels([radius]) + Position slice labels offset from the outer edge of the chart + + The given argument sets the radial offset. + */ + _chart.externalLabels = function(radius) { + if (arguments.length === 0) { + return _externalLabelRadius; + } else if(radius) { + _externalLabelRadius = radius; + } else { + _externalLabelRadius = undefined; + } + + return _chart; + }; + + function labelPosition(d, arc) { + var centroid; + if( _externalLabelRadius ) { + centroid = d3.svg.arc() + .outerRadius(_radius+_externalLabelRadius) + .innerRadius(_radius+_externalLabelRadius) + .centroid(d); + } else { + centroid = arc.centroid(d); + } + if (isNaN(centroid[0]) || isNaN(centroid[1])) { + return "translate(0,0)"; + } else { + return "translate(" + centroid + ")"; + } + } + + _chart.legendables = function() { + return _chart.data().map(function (d, i) { + var legendable = { name: d.key, data: d.value, others: d.others, chart:_chart }; + legendable.color = _chart.getColor(d,i); + return legendable; + }); + }; + + _chart.legendHighlight = function(d) { + highlightSliceFromLegendable(d, true); + }; + + _chart.legendReset = function(d) { + highlightSliceFromLegendable(d, false); + }; + + _chart.legendToggle = function(d) { + _chart.onClick({ key: d.name, others: d.others }); + }; + + function highlightSliceFromLegendable(legendable, highlighted) { + _chart.selectAll('g.pie-slice').each(function (d) { + if (legendable.name == d.data.key) { + d3.select(this).classed("highlight", highlighted); + } + }); + } + + return _chart.anchor(parent, chartGroup); +}; + +/** +## Bar Chart + +Includes: [Stack Mixin](#stack Mixin), [Coordinate Grid Mixin](#coordinate-grid-mixin) + +Concrete bar chart/histogram implementation. + +Examples: + +* [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) +* [Canadian City Crime Stats](http://dc-js.github.com/dc.js/crime/index.html) + +#### dc.barChart(parent[, chartGroup]) +Create a bar chart instance and attach it to the given parent element. + +Parameters: +* parent : string | node | selection | compositeChart - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying + a dom block element such as a div; or a dom element or d3 selection. + If the bar chart is a sub-chart in a [Composite Chart](#composite-chart) then pass in the parent composite + chart instance. +* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. + Interaction with a chart will only trigger events and redraws within the chart's group. + +Returns: +A newly created bar chart instance + +```js +// create a bar chart under #chart-container1 element using the default global chart group +var chart1 = dc.barChart("#chart-container1"); +// create a bar chart under #chart-container2 element using chart group A +var chart2 = dc.barChart("#chart-container2", "chartGroupA"); +// create a sub-chart under a composite parent chart +var chart3 = dc.barChart(compositeChart); +``` + +**/ +dc.barChart = function (parent, chartGroup) { + var MIN_BAR_WIDTH = 1; + var DEFAULT_GAP_BETWEEN_BARS = 2; + + var _chart = dc.stackMixin(dc.coordinateGridMixin({})); + + var _gap = DEFAULT_GAP_BETWEEN_BARS; + var _centerBar = false; + var _alwaysUseRounding = false; + + var _barWidth; + + dc.override(_chart, 'rescale', function () { + _chart._rescale(); + _barWidth = undefined; + }); + + dc.override(_chart, 'render', function () { + if (_chart.round() && _centerBar && !_alwaysUseRounding) { + dc.logger.warn("By default, brush rounding is disabled if bars are centered. " + + "See dc.js bar chart API documentation for details."); + } + + _chart._render(); + }); + + _chart.plotData = function () { + var layers = _chart.chartBodyG().selectAll("g.stack") + .data(_chart.data()); + + calculateBarWidth(); + + layers + .enter() + .append("g") + .attr("class", function (d, i) { + return "stack " + "_" + i; + }); + + layers.each(function (d, i) { + var layer = d3.select(this); + + renderBars(layer, i, d); + }); + }; + + function barHeight(d) { + return dc.utils.safeNumber(Math.abs(_chart.y()(d.y + d.y0) - _chart.y()(d.y0))); + } + + function renderBars(layer, layerIndex, d) { + var bars = layer.selectAll("rect.bar") + .data(d.values, dc.pluck('x')); + + var enter = bars.enter() + .append("rect") + .attr("class", "bar") + .attr("fill", dc.pluck('data',_chart.getColor)) + .attr("height", 0); + + if (_chart.renderTitle()) + enter.append("title").text(dc.pluck('data',_chart.title(d.name))); + + if (_chart.isOrdinal()) + bars.on("click", onClick); + + dc.transition(bars, _chart.transitionDuration()) + .attr("x", function (d) { + var x = _chart.x()(d.x); + if (_centerBar) x -= _barWidth / 2; + if (_chart.isOrdinal() && _gap!==undefined) x += _gap/2; + return dc.utils.safeNumber(x); + }) + .attr("y", function (d) { + var y = _chart.y()(d.y + d.y0); + + if (d.y < 0) + y -= barHeight(d); + + return dc.utils.safeNumber(y); + }) + .attr("width", _barWidth) + .attr("height", function (d) { + return barHeight(d); + }) + .attr("fill", dc.pluck('data',_chart.getColor)) + .select("title").text(dc.pluck('data',_chart.title(d.name))); + + dc.transition(bars.exit(), _chart.transitionDuration()) + .attr("height", 0) + .remove(); + } + + function calculateBarWidth() { + if (_barWidth === undefined) { + var numberOfBars = _chart.xUnitCount(); + + // please can't we always use rangeBands for bar charts? + if (_chart.isOrdinal() && _gap===undefined) + _barWidth = Math.floor(_chart.x().rangeBand()); + else if (_gap) + _barWidth = Math.floor((_chart.xAxisLength() - (numberOfBars - 1) * _gap) / numberOfBars); + else + _barWidth = Math.floor(_chart.xAxisLength() / (1 + _chart.barPadding()) / numberOfBars); + + if (_barWidth == Infinity || isNaN(_barWidth) || _barWidth < MIN_BAR_WIDTH) + _barWidth = MIN_BAR_WIDTH; + } + } + + _chart.fadeDeselectedArea = function () { + var bars = _chart.chartBodyG().selectAll("rect.bar"); + var extent = _chart.brush().extent(); + + if (_chart.isOrdinal()) { + if (_chart.hasFilter()) { + bars.classed(dc.constants.SELECTED_CLASS, function (d) { + return _chart.hasFilter(d.x); + }); + bars.classed(dc.constants.DESELECTED_CLASS, function (d) { + return !_chart.hasFilter(d.x); + }); + } else { + bars.classed(dc.constants.SELECTED_CLASS, false); + bars.classed(dc.constants.DESELECTED_CLASS, false); + } + } else { + if (!_chart.brushIsEmpty(extent)) { + var start = extent[0]; + var end = extent[1]; + + bars.classed(dc.constants.DESELECTED_CLASS, function (d) { + return d.x < start || d.x >= end; + }); + } else { + bars.classed(dc.constants.DESELECTED_CLASS, false); + } + } + }; + + /** + #### .centerBar(boolean) + Whether the bar chart will render each bar centered around the data position on x axis. Default: false + + **/ + _chart.centerBar = function (_) { + if (!arguments.length) return _centerBar; + _centerBar = _; + return _chart; + }; + + function onClick(d) { + _chart.onClick(d.data); + } + + /** + #### .barPadding([padding]) + Get or set the spacing between bars as a fraction of bar size. Valid values are between 0-1. + Setting this value will also remove any previously set `gap`. See the + [d3 docs](https://github.com/mbostock/d3/wiki/Ordinal-Scales#wiki-ordinal_rangeBands) + for a visual description of how the padding is applied. + **/ + _chart.barPadding = function (_) { + if (!arguments.length) return _chart._rangeBandPadding(); + _chart._rangeBandPadding(_); + _gap = undefined; + return _chart; + }; + + _chart._useOuterPadding = function() { + return _gap===undefined; + }; + + /** + #### .outerPadding([padding]) + Get or set the outer padding on an ordinal bar chart. This setting has no effect on non-ordinal charts. + Will pad the width by `padding * barWidth` on each side of the chart. + + Default: 0.5 + **/ + _chart.outerPadding = _chart._outerRangeBandPadding; + + /** + #### .gap(gapBetweenBars) + Manually set fixed gap (in px) between bars instead of relying on the default auto-generated + gap. By default the bar chart implementation will calculate and set the gap automatically + based on the number of data points and the length of the x axis. + + **/ + _chart.gap = function (_) { + if (!arguments.length) return _gap; + _gap = _; + return _chart; + }; + + _chart.extendBrush = function () { + var extent = _chart.brush().extent(); + if (_chart.round() && (!_centerBar || _alwaysUseRounding)) { + extent[0] = extent.map(_chart.round())[0]; + extent[1] = extent.map(_chart.round())[1]; + + _chart.chartBodyG().select(".brush") + .call(_chart.brush().extent(extent)); + } + + return extent; + }; + + /** + #### .alwaysUseRounding([boolean]) + Set or get whether rounding is enabled when bars are centered. Default: false. If false, using + rounding with centered bars will result in a warning and rounding will be ignored. This flag + has no effect if bars are not centered. + + When using standard d3.js rounding methods, the brush often doesn't align correctly with + centered bars since the bars are offset. The rounding function must add an offset to + compensate, such as in the following example. + ```js + chart.round(function(n) {return Math.floor(n)+0.5}); + ``` + **/ + _chart.alwaysUseRounding = function (_) { + if (!arguments.length) return _alwaysUseRounding; + _alwaysUseRounding = _; + return _chart; + }; + + function colorFilter(color,inv) { + return function() { + var item = d3.select(this); + var match = item.attr('fill') == color; + return inv ? !match : match; + }; + } + + _chart.legendHighlight = function (d) { + if(!_chart.isLegendableHidden(d)) { + _chart.g().selectAll('rect.bar') + .classed('highlight', colorFilter(d.color)) + .classed('fadeout', colorFilter(d.color,true)); + } + }; + + _chart.legendReset = function () { + _chart.g().selectAll('rect.bar') + .classed('highlight', false) + .classed('fadeout', false); + }; + + dc.override(_chart, "xAxisMax", function() { + var max = this._xAxisMax(); + if('resolution' in _chart.xUnits()) { + var res = _chart.xUnits().resolution; + max += res; + } + return max; + }); + + return _chart.anchor(parent, chartGroup); +}; + +/** +## Line Chart + +Includes [Stack Mixin](#stack-mixin), [Coordinate Grid Mixin](#coordinate-grid-mixin) + +Concrete line/area chart implementation. + +Examples: +* [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) +* [Canadian City Crime Stats](http://dc-js.github.com/dc.js/crime/index.html) + +#### dc.lineChart(parent[, chartGroup]) +Create a line chart instance and attach it to the given parent element. + +Parameters: + +* parent : string | node | selection | compositeChart - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying + a dom block element such as a div; or a dom element or d3 selection. + If the line chart is a sub-chart in a [Composite Chart](#composite-chart) then pass in the parent composite + chart instance. + +* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. + Interaction with a chart will only trigger events and redraws within the chart's group. + +Returns: +A newly created line chart instance + +```js +// create a line chart under #chart-container1 element using the default global chart group +var chart1 = dc.lineChart("#chart-container1"); +// create a line chart under #chart-container2 element using chart group A +var chart2 = dc.lineChart("#chart-container2", "chartGroupA"); +// create a sub-chart under a composite parent chart +var chart3 = dc.lineChart(compositeChart); +``` + +**/ +dc.lineChart = function (parent, chartGroup) { + var DEFAULT_DOT_RADIUS = 5; + var TOOLTIP_G_CLASS = "dc-tooltip"; + var DOT_CIRCLE_CLASS = "dot"; + var Y_AXIS_REF_LINE_CLASS = "yRef"; + var X_AXIS_REF_LINE_CLASS = "xRef"; + var DEFAULT_DOT_OPACITY = 1e-6; + + var _chart = dc.stackMixin(dc.coordinateGridMixin({})); + var _renderArea = false; + var _dotRadius = DEFAULT_DOT_RADIUS; + var _dataPointRadius = null; + var _dataPointFillOpacity = DEFAULT_DOT_OPACITY; + var _dataPointStrokeOpacity = DEFAULT_DOT_OPACITY; + var _interpolate = 'linear'; + var _tension = 0.7; + var _defined; + var _dashStyle; + + _chart.transitionDuration(500); + _chart._rangeBandPadding(1); + + _chart.plotData = function () { + var chartBody = _chart.chartBodyG(); + var layersList = chartBody.selectAll("g.stack-list"); + + if (layersList.empty()) layersList = chartBody.append("g").attr("class", "stack-list"); + + var layers = layersList.selectAll("g.stack").data(_chart.data()); + + var layersEnter = layers + .enter() + .append("g") + .attr("class", function (d, i) { + return "stack " + "_" + i; + }); + + drawLine(layersEnter, layers); + + drawArea(layersEnter, layers); + + drawDots(chartBody, layers); + }; + + /** + #### .interpolate([value]) + Gets or sets the interpolator to use for lines drawn, by string name, allowing e.g. step + functions, splines, and cubic interpolation. This is passed to + [d3.svg.line.interpolate](https://github.com/mbostock/d3/wiki/SVG-Shapes#line_interpolate) and + [d3.svg.area.interpolate](https://github.com/mbostock/d3/wiki/SVG-Shapes#area_interpolate), + where you can find a complete list of valid arguments + **/ + _chart.interpolate = function(_){ + if (!arguments.length) return _interpolate; + _interpolate = _; + return _chart; + }; + + /** + #### .tension([value]) Gets or sets the tension to use for lines drawn, in the range 0 to 1. + This parameter further customizes the interpolation behavior. It is passed to + [d3.svg.line.tension](https://github.com/mbostock/d3/wiki/SVG-Shapes#line_tension) and + [d3.svg.area.tension](https://github.com/mbostock/d3/wiki/SVG-Shapes#area_tension). Default: + 0.7 + **/ + _chart.tension = function(_){ + if (!arguments.length) return _tension; + _tension = _; + return _chart; + }; + + /** + #### .defined([value]) + Gets or sets a function that will determine discontinuities in the line which should be + skipped: the path will be broken into separate subpaths if some points are undefined. + This function is passed to + [d3.svg.line.defined](https://github.com/mbostock/d3/wiki/SVG-Shapes#line_defined) + + Note: crossfilter will sometimes coerce nulls to 0, so you may need to carefully write + custom reduce functions to get this to work, depending on your data. See + https://github.com/dc-js/dc.js/issues/615#issuecomment-49089248 + **/ + _chart.defined = function(_){ + if (!arguments.length) return _defined; + _defined = _; + return _chart; + }; + + /** + #### .dashStyle([array]) + Set the line's d3 dashstyle. This value becomes the "stroke-dasharray" of line. Defaults to empty + array (solid line). + ```js + // create a Dash Dot Dot Dot + chart.dashStyle([3,1,1,1]); + ``` + **/ + _chart.dashStyle = function (_) { + if (!arguments.length) return _dashStyle; + _dashStyle = _; + return _chart; + }; + + /** + #### .renderArea([boolean]) + Get or set render area flag. If the flag is set to true then the chart will render the area + beneath each line and the line chart effectively becomes an area chart. + + **/ + _chart.renderArea = function (_) { + if (!arguments.length) return _renderArea; + _renderArea = _; + return _chart; + }; + + function colors(d, i) { + return _chart.getColor.call(d,d.values,i); + } + + function drawLine(layersEnter, layers) { + var line = d3.svg.line() + .x(function (d) { + return _chart.x()(d.x); + }) + .y(function (d) { + return _chart.y()(d.y + d.y0); + }) + .interpolate(_interpolate) + .tension(_tension); + if (_defined) + line.defined(_defined); + + var path = layersEnter.append("path") + .attr("class", "line") + .attr("stroke", colors); + if (_dashStyle) + path.attr("stroke-dasharray", _dashStyle); + + dc.transition(layers.select("path.line"), _chart.transitionDuration()) + //.ease("linear") + .attr("stroke", colors) + .attr("d", function (d) { + return safeD(line(d.values)); + }); + } + + function drawArea(layersEnter, layers) { + if (_renderArea) { + var area = d3.svg.area() + .x(function (d) { + return _chart.x()(d.x); + }) + .y(function (d) { + return _chart.y()(d.y + d.y0); + }) + .y0(function (d) { + return _chart.y()(d.y0); + }) + .interpolate(_interpolate) + .tension(_tension); + if (_defined) + area.defined(_defined); + + layersEnter.append("path") + .attr("class", "area") + .attr("fill", colors) + .attr("d", function (d) { + return safeD(area(d.values)); + }); + + dc.transition(layers.select("path.area"), _chart.transitionDuration()) + //.ease("linear") + .attr("fill", colors) + .attr("d", function (d) { + return safeD(area(d.values)); + }); + } + } + + function safeD(d){ + return (!d || d.indexOf("NaN") >= 0) ? "M0,0" : d; + } + + function drawDots(chartBody, layers) { + if (!_chart.brushOn()) { + var tooltipListClass = TOOLTIP_G_CLASS + "-list"; + var tooltips = chartBody.select("g." + tooltipListClass); + + if (tooltips.empty()) tooltips = chartBody.append("g").attr("class", tooltipListClass); + + layers.each(function (d, layerIndex) { + var points = d.values; + if (_defined) points = points.filter(_defined); + + var g = tooltips.select("g." + TOOLTIP_G_CLASS + "._" + layerIndex); + if (g.empty()) g = tooltips.append("g").attr("class", TOOLTIP_G_CLASS + " _" + layerIndex); + + createRefLines(g); + + var dots = g.selectAll("circle." + DOT_CIRCLE_CLASS) + .data(points,dc.pluck('x')); + + dots.enter() + .append("circle") + .attr("class", DOT_CIRCLE_CLASS) + .attr("r", getDotRadius()) + .attr("fill", _chart.getColor) + .style("fill-opacity", _dataPointFillOpacity) + .style("stroke-opacity", _dataPointStrokeOpacity) + .on("mousemove", function (d) { + var dot = d3.select(this); + showDot(dot); + showRefLines(dot, g); + }) + .on("mouseout", function (d) { + var dot = d3.select(this); + hideDot(dot); + hideRefLines(g); + }) + .append("title").text(dc.pluck('data', _chart.title(d.name))); + + dots.attr("cx", function (d) { + return dc.utils.safeNumber(_chart.x()(d.x)); + }) + .attr("cy", function (d) { + return dc.utils.safeNumber(_chart.y()(d.y + d.y0)); + }) + .attr("fill", _chart.getColor) + .select("title").text(dc.pluck('data', _chart.title(d.name))); + + dots.exit().remove(); + }); + } + } + + function createRefLines(g) { + var yRefLine = g.select("path." + Y_AXIS_REF_LINE_CLASS).empty() ? g.append("path").attr("class", Y_AXIS_REF_LINE_CLASS) : g.select("path." + Y_AXIS_REF_LINE_CLASS); + yRefLine.style("display", "none").attr("stroke-dasharray", "5,5"); + + var xRefLine = g.select("path." + X_AXIS_REF_LINE_CLASS).empty() ? g.append("path").attr("class", X_AXIS_REF_LINE_CLASS) : g.select("path." + X_AXIS_REF_LINE_CLASS); + xRefLine.style("display", "none").attr("stroke-dasharray", "5,5"); + } + + function showDot(dot) { + dot.style("fill-opacity", 0.8); + dot.style("stroke-opacity", 0.8); + dot.attr("r", _dotRadius); + return dot; + } + + function showRefLines(dot, g) { + var x = dot.attr("cx"); + var y = dot.attr("cy"); + var yAxisX = (_chart._yAxisX() - _chart.margins().left); + var yAxisRefPathD = "M" + yAxisX + " " + y + "L" + (x) + " " + (y); + var xAxisRefPathD = "M" + x + " " + _chart.yAxisHeight() + "L" + x + " " + y; + g.select("path." + Y_AXIS_REF_LINE_CLASS).style("display", "").attr("d", yAxisRefPathD); + g.select("path." + X_AXIS_REF_LINE_CLASS).style("display", "").attr("d", xAxisRefPathD); + } + + function getDotRadius() { + return _dataPointRadius || _dotRadius; + } + + function hideDot(dot) { + dot.style("fill-opacity", _dataPointFillOpacity) + .style("stroke-opacity", _dataPointStrokeOpacity) + .attr("r", getDotRadius()); + } + + function hideRefLines(g) { + g.select("path." + Y_AXIS_REF_LINE_CLASS).style("display", "none"); + g.select("path." + X_AXIS_REF_LINE_CLASS).style("display", "none"); + } + + /** + #### .dotRadius([dotRadius]) + Get or set the radius (in px) for dots displayed on the data points. Default dot radius is 5. + **/ + _chart.dotRadius = function (_) { + if (!arguments.length) return _dotRadius; + _dotRadius = _; + return _chart; + }; + + /** + #### .renderDataPoints([options]) + Always show individual dots for each datapoint. + + Options, if given, is an object that can contain the following: + + * fillOpacity (default 0.8) + * strokeOpacity (default 0.8) + * radius (default 2) + + If `options` is falsy, it disables data point rendering. + + If no `options` are provided, the current `options` values are instead returned. + + Example: + ``` + chart.renderDataPoints({radius: 2, fillOpacity: 0.8, strokeOpacity: 0.8}) + ``` + **/ + _chart.renderDataPoints = function (options) { + if (!arguments.length) { + return { + fillOpacity: _dataPointFillOpacity, + strokeOpacity: _dataPointStrokeOpacity, + radius: _dataPointRadius + }; + } else if (!options) { + _dataPointFillOpacity = DEFAULT_DOT_OPACITY; + _dataPointStrokeOpacity = DEFAULT_DOT_OPACITY; + _dataPointRadius = null; + } else { + _dataPointFillOpacity = options.fillOpacity || 0.8; + _dataPointStrokeOpacity = options.strokeOpacity || 0.8; + _dataPointRadius = options.radius || 2; + } + return _chart; + }; + + function colorFilter(color, dashstyle, inv) { + return function() { + var item = d3.select(this); + var match = (item.attr('stroke') == color && item.attr("stroke-dasharray") == ((dashstyle instanceof Array) ? dashstyle.join(",") : null) )|| item.attr('fill') == color; + return inv ? !match : match; + }; + } + + _chart.legendHighlight = function (d) { + if(!_chart.isLegendableHidden(d)) { + _chart.g().selectAll('path.line, path.area') + .classed('highlight', colorFilter(d.color, d.dashstyle)) + .classed('fadeout', colorFilter(d.color, d.dashstyle, true)); + } + }; + + _chart.legendReset = function () { + _chart.g().selectAll('path.line, path.area') + .classed('highlight', false) + .classed('fadeout', false); + }; + + dc.override(_chart,'legendables', function() { + var legendables = _chart._legendables(); + if (!_dashStyle) return legendables; + return legendables.map(function(l) { + l.dashstyle = _dashStyle; + return l; + }); + }); + + return _chart.anchor(parent, chartGroup); +}; + +/** +## Data Count Widget + +Includes: [Base Mixin](#base-mixin) + +The data count widget is a simple widget designed to display the number of records selected by the +current filters out of the total number of records in the data set. Once created the data count widget +will automatically update the text content of the following elements under the parent element. + +* ".total-count" - total number of records +* ".filter-count" - number of records matched by the current filters + +Examples: + +* [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) + +#### dc.dataCount(parent[, chartGroup]) +Create a data count widget and attach it to the given parent element. + +Parameters: + +* parent : string | node | selection - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying + a dom block element such as a div; or a dom element or d3 selection. +* chartGroup : string (optional) - name of the chart group this widget should be placed in. + The data count widget will only react to filter changes in the chart group. + +Returns: +A newly created data count widget instance + +#### .dimension(allData) - **mandatory** +For the data count widget the only valid dimension is the entire data set. + +#### .group(groupAll) - **mandatory** +For the data count widget the only valid group is the group returned by `dimension.groupAll()`. + +```js +var ndx = crossfilter(data); +var all = ndx.groupAll(); + +dc.dataCount(".dc-data-count") + .dimension(ndx) + .group(all); +``` + +**/ +dc.dataCount = function(parent, chartGroup) { + var SPAN_CLASS = 'data-count-display'; + var _formatNumber = d3.format(",d"); + var _chart = dc.baseMixin({}); + var _html = {some:"",all:""}; + + /** + #### html([object]) + Gets or sets an optional object specifying HTML templates to use depending how many items are + selected. The text `%total-count` will replaced with the total number of records, and the text + `%filter-count` will be replaced with the number of selected records. + - all: HTML template to use if all items are selected + - some: HTML template to use if not all items are selected + + ```js + counter.html({ + some: "%filter-count out of %total-count records selected", + all: "All records selected. Click on charts to apply filters" + }) + ``` + **/ + _chart.html = function(s) { + if (!arguments.length) return _html; + if(s.all) + _html.all = s.all; + if(s.some) + _html.some = s.some; + return _chart; + }; + + _chart._doRender = function() { + var tot = _chart.dimension().size(), + val = _chart.group().value(); + var all = _formatNumber(tot); + var selected = _formatNumber(val); + + if((tot===val)&&(_html.all!=="")) { + _chart.root().html(_html.all.replace('%total-count',all).replace('%filter-count',selected)); + } + else if(_html.some!=="") { + _chart.root().html(_html.some.replace('%total-count',all).replace('%filter-count',selected)); + } else { + _chart.selectAll(".total-count").text(all); + _chart.selectAll(".filter-count").text(selected); + } + return _chart; + }; + + _chart._doRedraw = function(){ + return _chart._doRender(); + }; + + return _chart.anchor(parent, chartGroup); +}; + +/** +## Data Table Widget + +Includes: [Base Mixin](#base-mixin) + +The data table is a simple widget designed to list crossfilter focused data set (rows being +filtered) in a good old tabular fashion. + +Examples: +* [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) + +#### dc.dataTable(parent[, chartGroup]) +Create a data table widget instance and attach it to the given parent element. + +Parameters: +* parent : string | node | selection - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying + a dom block element such as a div; or a dom element or d3 selection. + +* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. + Interaction with a chart will only trigger events and redraws within the chart's group. + +Returns: +A newly created data table widget instance + +**/ +dc.dataTable = function(parent, chartGroup) { + var LABEL_CSS_CLASS = "dc-table-label"; + var ROW_CSS_CLASS = "dc-table-row"; + var COLUMN_CSS_CLASS = "dc-table-column"; + var GROUP_CSS_CLASS = "dc-table-group"; + + var _chart = dc.baseMixin({}); + + var _size = 25; + var _columns = []; + var _sortBy = function(d) { + return d; + }; + var _order = d3.ascending; + + _chart._doRender = function() { + _chart.selectAll("tbody").remove(); + + renderRows(renderGroups()); + + return _chart; + }; + + function renderGroups() { + var groups = _chart.root().selectAll("tbody") + .data(nestEntries(), function(d) { + return _chart.keyAccessor()(d); + }); + + var rowGroup = groups + .enter() + .append("tbody"); + + rowGroup + .append("tr") + .attr("class", GROUP_CSS_CLASS) + .append("td") + .attr("class", LABEL_CSS_CLASS) + .attr("colspan", _columns.length) + .html(function(d) { + return _chart.keyAccessor()(d); + }); + + groups.exit().remove(); + + return rowGroup; + } + + function nestEntries() { + var entries = _chart.dimension().top(_size); + + return d3.nest() + .key(_chart.group()) + .sortKeys(_order) + .entries(entries.sort(function(a, b){ + return _order(_sortBy(a), _sortBy(b)); + })); + } + + function renderRows(groups) { + var rows = groups.order() + .selectAll("tr." + ROW_CSS_CLASS) + .data(function(d) { + return d.values; + }); + + var rowEnter = rows.enter() + .append("tr") + .attr("class", ROW_CSS_CLASS); + + _columns.forEach(function(f,i) { + rowEnter.append("td") + .attr("class", COLUMN_CSS_CLASS + " _" + i) + .html(f); + }); + + rows.exit().remove(); + + return rows; + } + + _chart._doRedraw = function() { + return _chart._doRender(); + }; + + /** + #### .size([size]) + Get or set the table size which determines the number of rows displayed by the widget. + + **/ + _chart.size = function(s) { + if (!arguments.length) return _size; + _size = s; + return _chart; + }; + + /** + #### .columns([columnFunctionArray]) + Get or set column functions. The data table widget uses an array of functions to generate dynamic + columns. Column functions are simple javascript functions with only one input argument d which + represents a row in the data set, and the return value of these functions will be used directly + to generate table content for the cells. + + ```js + chart.columns([ + function(d) { + return d.date; + }, + function(d) { + return d.open; + }, + function(d) { + return d.close; + }, + function(d) { + return numberFormat(d.close - d.open); + }, + function(d) { + return d.volume; + } + ]); + ``` + + **/ + _chart.columns = function(_) { + if (!arguments.length) return _columns; + _columns = _; + return _chart; + }; + + /** + #### .sortBy([sortByFunction]) + Get or set sort-by function. This function works as a value accessor at row level and returns a + particular field to be sorted by. Default value: identity function + + ```js + chart.sortBy(function(d) { + return d.date; + }); + ``` + + **/ + _chart.sortBy = function(_) { + if (!arguments.length) return _sortBy; + _sortBy = _; + return _chart; + }; + + /** + #### .order([order]) + Get or set sort order. Default value: ``` d3.ascending ``` + + ```js + chart.order(d3.descending); + ``` + + **/ + _chart.order = function(_) { + if (!arguments.length) return _order; + _order = _; + return _chart; + }; + + return _chart.anchor(parent, chartGroup); +}; + +/** + ## Data Grid Widget + + Includes: [Base Mixin](#base-mixin) + + Data grid is a simple widget designed to list the filtered records, providing + a simple way to define how the items are displayed. + + Examples: + * [List of members of the european parliament](http://europarl.me/dc.js/web/ep/index.html) + + #### dc.dataGrid(parent[, chartGroup]) + Create a data grid widget instance and attach it to the given parent element. + +Parameters: +* parent : string | node | selection - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying + a dom block element such as a div; or a dom element or d3 selection. + +* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. + Interaction with a chart will only trigger events and redraws within the chart's group. + +Returns: +A newly created data grid widget instance + + **/ +dc.dataGrid = function(parent, chartGroup) { + var LABEL_CSS_CLASS = "dc-grid-label"; + var ITEM_CSS_CLASS = "dc-grid-item"; + var GROUP_CSS_CLASS = "dc-grid-group"; + var GRID_CSS_CLASS = "dc-grid-top"; + + var _chart = dc.baseMixin({}); + + var _size = 999; // shouldn't be needed, but you might + var _html = function (d) { return "you need to provide an html() handling param: " + JSON.stringify(d); }; + var _sortBy = function(d) { + return d; + }; + var _order = d3.ascending; + + var _htmlGroup = function (d) { + return "

"+ + _chart.keyAccessor()(d)+"

"; + }; + + _chart._doRender = function() { + _chart.selectAll("div."+ GRID_CSS_CLASS).remove(); + + renderItems(renderGroups()); + + return _chart; + }; + + function renderGroups() { + var groups = _chart.root().selectAll("div."+ GRID_CSS_CLASS) + .data(nestEntries(), function(d) { + return _chart.keyAccessor()(d); + }); + + var itemGroup = groups + .enter() + .append("div") + .attr("class", GRID_CSS_CLASS); + + if (_htmlGroup) { + itemGroup + .html(function(d) { + return _htmlGroup(d); + }); + } + + groups.exit().remove(); + return itemGroup; + } + + function nestEntries() { + var entries = _chart.dimension().top(_size); + + return d3.nest() + .key(_chart.group()) + .sortKeys(_order) + .entries(entries.sort(function(a, b){ + return _order(_sortBy(a), _sortBy(b)); + })); + } + + function renderItems(groups) { + var items = groups.order() + .selectAll("div." + ITEM_CSS_CLASS) + .data(function(d) { + return d.values; + }); + + items.enter() + .append("div") + .attr("class", ITEM_CSS_CLASS) + .html(function(d) { + return _html(d); + }); + + items.exit().remove(); + + return items; + } + + _chart._doRedraw = function() { + return _chart._doRender(); + }; + + /** + #### .size([size]) + Get or set the grid size which determines the number of items displayed by the widget. + + **/ + _chart.size = function(s) { + if (!arguments.length) return _size; + _size = s; + return _chart; + }; + + /** + #### .html( function (data) { return ""; }) + Get or set the function that formats an item. The data grid widget uses a + function to generate dynamic html. Use your favourite templating engine or + generate the string directly. + ```js + chart.html(function (d) { return "
"+data.exampleString+"
";}); + ``` + + **/ + _chart.html = function(_) { + if (!arguments.length) return _html; + _html = _; + return _chart; + }; + + + /** + #### .htmlGroup( function (data) { return ""; }) + Get or set the function that formats a group label. + ```js + chart.htmlGroup (function (d) { return "

".d.key . "with " . d.values.length ." items

"}); + ``` + + **/ + _chart.htmlGroup = function(_) { + if (!arguments.length) return _htmlGroup; + _htmlGroup = _; + return _chart; + }; + + /** + #### .sortBy([sortByFunction]) + Get or set sort-by function. This function works as a value accessor at the item + level and returns a particular field to be sorted. + by. Default: identity function + + ```js + chart.sortBy(function(d) { + return d.date; + }); + ``` + + **/ + _chart.sortBy = function(_) { + if (!arguments.length) return _sortBy; + _sortBy = _; + return _chart; + }; + + /** + #### .order([order]) + Get or set sort order function. Default value: ``` d3.ascending ``` + + ```js + chart.order(d3.descending); + ``` + + **/ + _chart.order = function(_) { + if (!arguments.length) return _order; + _order = _; + return _chart; + }; + + return _chart.anchor(parent, chartGroup); +}; + +/** +## Bubble Chart + +Includes: [Bubble Mixin](#bubble-mixin), [Coordinate Grid Mixin](#coordinate-grid-mixin) + +A concrete implementation of a general purpose bubble chart that allows data visualization using the +following dimensions: + +* x axis position +* y axis position +* bubble radius +* color + +Examples: +* [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) +* [US Venture Capital Landscape 2011](http://dc-js.github.com/dc.js/vc/index.html) + +#### dc.bubbleChart(parent[, chartGroup]) +Create a bubble chart instance and attach it to the given parent element. + +Parameters: +* parent : string | node | selection | compositeChart - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying + a dom block element such as a div; or a dom element or d3 selection. +* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. + Interaction with a chart will only trigger events and redraws within the chart's group. + +Returns: +A newly created bubble chart instance + +```js +// create a bubble chart under #chart-container1 element using the default global chart group +var bubbleChart1 = dc.bubbleChart("#chart-container1"); +// create a bubble chart under #chart-container2 element using chart group A +var bubbleChart2 = dc.bubbleChart("#chart-container2", "chartGroupA"); +``` + +**/ +dc.bubbleChart = function(parent, chartGroup) { + var _chart = dc.bubbleMixin(dc.coordinateGridMixin({})); + + var _elasticRadius = false; + + _chart.transitionDuration(750); + + var bubbleLocator = function(d) { + return "translate(" + (bubbleX(d)) + "," + (bubbleY(d)) + ")"; + }; + + /** + #### .elasticRadius([boolean]) + Turn on or off the elastic bubble radius feature, or return the value of the flag. If this + feature is turned on, then bubble radii will be automatically rescaled to fit the chart better. + + **/ + _chart.elasticRadius = function(_) { + if (!arguments.length) return _elasticRadius; + _elasticRadius = _; + return _chart; + }; + + _chart.plotData = function() { + if (_elasticRadius) + _chart.r().domain([_chart.rMin(), _chart.rMax()]); + + _chart.r().range([_chart.MIN_RADIUS, _chart.xAxisLength() * _chart.maxBubbleRelativeSize()]); + + var bubbleG = _chart.chartBodyG().selectAll("g." + _chart.BUBBLE_NODE_CLASS) + .data(_chart.data(), function (d) { return d.key; }); + + renderNodes(bubbleG); + + updateNodes(bubbleG); + + removeNodes(bubbleG); + + _chart.fadeDeselectedArea(); + }; + + function renderNodes(bubbleG) { + var bubbleGEnter = bubbleG.enter().append("g"); + + bubbleGEnter + .attr("class", _chart.BUBBLE_NODE_CLASS) + .attr("transform", bubbleLocator) + .append("circle").attr("class", function(d, i) { + return _chart.BUBBLE_CLASS + " _" + i; + }) + .on("click", _chart.onClick) + .attr("fill", _chart.getColor) + .attr("r", 0); + dc.transition(bubbleG, _chart.transitionDuration()) + .selectAll("circle." + _chart.BUBBLE_CLASS) + .attr("r", function(d) { + return _chart.bubbleR(d); + }) + .attr("opacity", function(d) { + return (_chart.bubbleR(d) > 0) ? 1 : 0; + }); + + _chart._doRenderLabel(bubbleGEnter); + + _chart._doRenderTitles(bubbleGEnter); + } + + function updateNodes(bubbleG) { + dc.transition(bubbleG, _chart.transitionDuration()) + .attr("transform", bubbleLocator) + .selectAll("circle." + _chart.BUBBLE_CLASS) + .attr("fill", _chart.getColor) + .attr("r", function(d) { + return _chart.bubbleR(d); + }) + .attr("opacity", function(d) { + return (_chart.bubbleR(d) > 0) ? 1 : 0; + }); + + _chart.doUpdateLabels(bubbleG); + _chart.doUpdateTitles(bubbleG); + } + + function removeNodes(bubbleG) { + bubbleG.exit().remove(); + } + + function bubbleX(d) { + var x = _chart.x()(_chart.keyAccessor()(d)); + if (isNaN(x)) + x = 0; + return x; + } + + function bubbleY(d) { + var y = _chart.y()(_chart.valueAccessor()(d)); + if (isNaN(y)) + y = 0; + return y; + } + + _chart.renderBrush = function(g) { + // override default x axis brush from parent chart + }; + + _chart.redrawBrush = function(g) { + // override default x axis brush from parent chart + _chart.fadeDeselectedArea(); + }; + + return _chart.anchor(parent, chartGroup); +}; + +/** +## Composite Chart + +Includes: [Coordinate Grid Mixin](#coordinate-grid-mixin) + +Composite charts are a special kind of chart that render multiple charts on the same Coordinate +Grid. You can overlay (compose) different bar/line/area charts in a single composite chart to +achieve some quite flexible charting effects. + +#### dc.compositeChart(parent[, chartGroup]) +Create a composite chart instance and attach it to the given parent element. + +Parameters: +* parent : string | node | selection - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying + a dom block element such as a div; or a dom element or d3 selection. +* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. + Interaction with a chart will only trigger events and redraws within the chart's group. + +Returns: +A newly created composite chart instance + +```js +// create a composite chart under #chart-container1 element using the default global chart group +var compositeChart1 = dc.compositeChart("#chart-container1"); +// create a composite chart under #chart-container2 element using chart group A +var compositeChart2 = dc.compositeChart("#chart-container2", "chartGroupA"); +``` + +**/ +dc.compositeChart = function (parent, chartGroup) { + + var SUB_CHART_CLASS = "sub"; + var DEFAULT_RIGHT_Y_AXIS_LABEL_PADDING = 12; + + var _chart = dc.coordinateGridMixin({}); + var _children = []; + + var _childOptions = {}; + + var _shareColors = false, + _shareTitle = true; + + var _rightYAxis = d3.svg.axis(), + _rightYAxisLabel = 0, + _rightYAxisLabelPadding = DEFAULT_RIGHT_Y_AXIS_LABEL_PADDING, + _rightY, + _rightAxisGridLines = false; + + _chart._mandatoryAttributes([]); + _chart.transitionDuration(500); + + dc.override(_chart, "_generateG", function () { + var g = this.__generateG(); + + for (var i = 0; i < _children.length; ++i) { + var child = _children[i]; + + generateChildG(child, i); + + if (!child.dimension()) child.dimension(_chart.dimension()); + if (!child.group()) child.group(_chart.group()); + + child.chartGroup(_chart.chartGroup()); + child.svg(_chart.svg()); + child.xUnits(_chart.xUnits()); + child.transitionDuration(_chart.transitionDuration()); + child.brushOn(_chart.brushOn()); + } + + return g; + }); + + _chart._brushing = function () { + var extent = _chart.extendBrush(); + var brushIsEmpty = _chart.brushIsEmpty(extent); + + for (var i = 0; i < _children.length; ++i) { + _children[i].filter(null); + if (!brushIsEmpty) _children[i].filter(extent); + } + }; + + _chart._prepareYAxis = function () { + if (leftYAxisChildren().length !== 0) { prepareLeftYAxis(); } + if (rightYAxisChildren().length !== 0) { prepareRightYAxis(); } + + if (leftYAxisChildren().length > 0 && !_rightAxisGridLines) { + _chart._renderHorizontalGridLinesForAxis(_chart.g(), _chart.y(), _chart.yAxis()); + } + else if (rightYAxisChildren().length > 0) { + _chart._renderHorizontalGridLinesForAxis(_chart.g(), _rightY, _rightYAxis); + } + }; + + _chart.renderYAxis = function () { + if (leftYAxisChildren().length !== 0) { + _chart.renderYAxisAt("y", _chart.yAxis(), _chart.margins().left); + _chart.renderYAxisLabel("y", _chart.yAxisLabel(), -90); + } + + if (rightYAxisChildren().length !== 0) { + _chart.renderYAxisAt("yr", _chart.rightYAxis(), _chart.width() - _chart.margins().right); + _chart.renderYAxisLabel("yr", _chart.rightYAxisLabel(), 90, _chart.width() - _rightYAxisLabelPadding); + } + }; + + function prepareRightYAxis() { + if (_chart.rightY() === undefined || _chart.elasticY()) { + _chart.rightY(d3.scale.linear()); + _chart.rightY().domain([rightYAxisMin(), rightYAxisMax()]).rangeRound([_chart.yAxisHeight(), 0]); + } + + _chart.rightY().range([_chart.yAxisHeight(), 0]); + _chart.rightYAxis(_chart.rightYAxis().scale(_chart.rightY())); + + _chart.rightYAxis().orient("right"); + } + + function prepareLeftYAxis() { + if (_chart.y() === undefined || _chart.elasticY()) { + _chart.y(d3.scale.linear()); + _chart.y().domain([yAxisMin(), yAxisMax()]).rangeRound([_chart.yAxisHeight(), 0]); + } + + _chart.y().range([_chart.yAxisHeight(), 0]); + _chart.yAxis(_chart.yAxis().scale(_chart.y())); + + _chart.yAxis().orient("left"); + } + + function generateChildG(child, i) { + child._generateG(_chart.g()); + child.g().attr("class", SUB_CHART_CLASS + " _" + i); + } + + _chart.plotData = function () { + for (var i = 0; i < _children.length; ++i) { + var child = _children[i]; + + if (!child.g()) { + generateChildG(child, i); + } + + if (_shareColors) { + child.colors(_chart.colors()); + } + + child.x(_chart.x()); + + child.xAxis(_chart.xAxis()); + + if (child.useRightYAxis()) { + child.y(_chart.rightY()); + child.yAxis(_chart.rightYAxis()); + } + else { + child.y(_chart.y()); + child.yAxis(_chart.yAxis()); + } + + child.plotData(); + + child._activateRenderlets(); + } + }; + + /** + #### .useRightAxisGridLines(bool) + Get or set whether to draw gridlines from the right y axis. Drawing from the left y axis is the + default behavior. This option is only respected when subcharts with both left and right y-axes + are present. + **/ + _chart.useRightAxisGridLines = function(_) { + if (!arguments) return _rightAxisGridLines; + + _rightAxisGridLines = _; + return _chart; + }; + + /** + #### .childOptions({object}) + Get or set chart-specific options for all child charts. This is equivalent to calling `.options` + on each child chart. + **/ + _chart.childOptions = function (_) { + if(!arguments.length) return _childOptions; + _childOptions = _; + _children.forEach(function(child){ + child.options(_childOptions); + }); + return _chart; + }; + + _chart.fadeDeselectedArea = function () { + for (var i = 0; i < _children.length; ++i) { + var child = _children[i]; + child.brush(_chart.brush()); + child.fadeDeselectedArea(); + } + }; + + /** + #### .rightYAxisLabel([labelText]) + Set or get the right y axis label. + **/ + _chart.rightYAxisLabel = function (_, padding) { + if (!arguments.length) return _rightYAxisLabel; + _rightYAxisLabel = _; + _chart.margins().right -= _rightYAxisLabelPadding; + _rightYAxisLabelPadding = (padding === undefined) ? DEFAULT_RIGHT_Y_AXIS_LABEL_PADDING : padding; + _chart.margins().right += _rightYAxisLabelPadding; + return _chart; + }; + + /** + #### .compose(subChartArray) + Combine the given charts into one single composite coordinate grid chart. + + ```js + // compose the given charts in the array into one single composite chart + moveChart.compose([ + // when creating sub-chart you need to pass in the parent chart + dc.lineChart(moveChart) + .group(indexAvgByMonthGroup) // if group is missing then parent's group will be used + .valueAccessor(function(d){return d.value.avg;}) + // most of the normal functions will continue to work in a composed chart + .renderArea(true) + .stack(monthlyMoveGroup, function(d){return d.value;}) + .title(function(d){ + var value = d.value.avg?d.value.avg:d.value; + if(isNaN(value)) value = 0; + return dateFormat(d.key) + "\n" + numberFormat(value); + }), + dc.barChart(moveChart) + .group(volumeByMonthGroup) + .centerBar(true) + ]); + ``` + + **/ + _chart.compose = function (charts) { + _children = charts; + _children.forEach(function(child) { + child.height(_chart.height()); + child.width(_chart.width()); + child.margins(_chart.margins()); + + if (_shareTitle) child.title(_chart.title()); + + child.options(_childOptions); + }); + return _chart; + }; + + /** + #### .children() + Returns the child charts which are composed into the composite chart. + **/ + + _chart.children = function () { + return _children; + }; + + /** + #### .shareColors([boolean]) + Get or set color sharing for the chart. If set, the `.colors()` value from this chart + will be shared with composed children. Additionally if the child chart implements + Stackable and has not set a custom .colorAccessor, then it will generate a color + specific to its order in the composition. + **/ + _chart.shareColors = function (_) { + if (!arguments.length) return _shareColors; + _shareColors = _; + return _chart; + }; + + /** + #### .shareTitle([[boolean]) + Get or set title sharing for the chart. If set, the `.title()` value from this chart will be + shared with composed children. Default value is true. + **/ + _chart.shareTitle = function (_) { + if (!arguments.length) return _shareTitle; + _shareTitle = _; + return _chart; + }; + + /** + #### .rightY([yScale]) + Get or set the y scale for the right axis. The right y scale is typically automatically + generated by the chart implementation. + + **/ + _chart.rightY = function (_) { + if (!arguments.length) return _rightY; + _rightY = _; + return _chart; + }; + + function leftYAxisChildren() { + return _children.filter(function (child) { + return !child.useRightYAxis(); + }); + } + + function rightYAxisChildren() { + return _children.filter(function (child) { + return child.useRightYAxis(); + }); + } + + function getYAxisMin(charts) { + return charts.map(function(c) { + return c.yAxisMin(); + }); + } + + delete _chart.yAxisMin; + function yAxisMin() { + return d3.min(getYAxisMin(leftYAxisChildren())); + } + + function rightYAxisMin() { + return d3.min(getYAxisMin(rightYAxisChildren())); + } + + function getYAxisMax(charts) { + return charts.map(function(c) { + return c.yAxisMax(); + }); + } + + delete _chart.yAxisMax; + function yAxisMax() { + return dc.utils.add(d3.max(getYAxisMax(leftYAxisChildren())), _chart.yAxisPadding()); + } + + function rightYAxisMax() { + return dc.utils.add(d3.max(getYAxisMax(rightYAxisChildren())), _chart.yAxisPadding()); + } + + function getAllXAxisMinFromChildCharts() { + return _children.map(function(c) { + return c.xAxisMin(); + }); + } + + dc.override(_chart, 'xAxisMin',function () { + return dc.utils.subtract(d3.min(getAllXAxisMinFromChildCharts()), _chart.xAxisPadding()); + }); + + function getAllXAxisMaxFromChildCharts() { + return _children.map(function(c) { + return c.xAxisMax(); + }); + } + + dc.override(_chart, 'xAxisMax',function () { + return dc.utils.add(d3.max(getAllXAxisMaxFromChildCharts()), _chart.xAxisPadding()); + }); + + _chart.legendables = function () { + return _children.reduce(function(items,child) { + if (_shareColors) child.colors(_chart.colors()); + items.push.apply(items, child.legendables()); + return items; + },[]); + }; + + _chart.legendHighlight = function (d) { + for (var j = 0; j < _children.length; ++j) { + var child = _children[j]; + child.legendHighlight(d); + } + }; + + _chart.legendReset = function (d) { + for (var j = 0; j < _children.length; ++j) { + var child = _children[j]; + child.legendReset(d); + } + }; + + _chart.legendToggle = function (d) { + console.log("composite should not be getting legendToggle itself"); + }; + + /** + #### .rightYAxis([yAxis]) + Set or get the right y axis used by the composite chart. This function is most useful when y + axis customization is required. The y axis in dc.js is an instance of a [d3 axis + object](https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-_axis) therefore it supports any valid + d3 axis manipulation. **Caution**: The y axis is usually generated internally by dc; + resetting it may cause unexpected results. + ```js + // customize y axis tick format + chart.rightYAxis().tickFormat(function(v) {return v + "%";}); + // customize y axis tick values + chart.rightYAxis().tickValues([0, 100, 200, 300]); + ``` + + **/ + _chart.rightYAxis = function (rightYAxis) { + if (!arguments.length) return _rightYAxis; + _rightYAxis = rightYAxis; + return _chart; + }; + + return _chart.anchor(parent, chartGroup); +}; + +/** + ## Series Chart + + Includes: [Composite Chart](#composite chart) + + A series chart is a chart that shows multiple series of data overlaid on one chart, where the + series is specified in the data. It is a specialization of Composite Chart and inherits all + composite features other than recomposing the chart. + + #### dc.seriesChart(parent[, chartGroup]) + Create a series chart instance and attach it to the given parent element. + + Parameters: +* parent : string | node | selection - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying + a dom block element such as a div; or a dom element or d3 selection. + +* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. + Interaction with a chart will only trigger events and redraws within the chart's group. + + Returns: + A newly created series chart instance + + ```js + // create a series chart under #chart-container1 element using the default global chart group + var seriesChart1 = dc.seriesChart("#chart-container1"); + // create a series chart under #chart-container2 element using chart group A + var seriesChart2 = dc.seriesChart("#chart-container2", "chartGroupA"); + ``` + + **/ +dc.seriesChart = function (parent, chartGroup) { + var _chart = dc.compositeChart(parent, chartGroup); + + var _charts = {}; + var _chartFunction = dc.lineChart; + var _seriesAccessor; + var _seriesSort = d3.ascending; + var _valueSort = keySort; + + _chart._mandatoryAttributes().push('seriesAccessor','chart'); + _chart.shareColors(true); + + function keySort(a,b) { + return d3.ascending(_chart.keyAccessor()(a), _chart.keyAccessor()(b)); + } + + _chart._preprocessData = function () { + var keep = []; + var children_changed; + var nester = d3.nest().key(_seriesAccessor); + if(_seriesSort) + nester.sortKeys(_seriesSort); + if(_valueSort) + nester.sortValues(_valueSort); + var nesting = nester.entries(_chart.data()); + var children = + nesting.map(function(sub,i) { + var subChart = _charts[sub.key] || _chartFunction.call(_chart,_chart,chartGroup,sub.key,i); + if(!_charts[sub.key]) + children_changed = true; + _charts[sub.key] = subChart; + keep.push(sub.key); + return subChart + .dimension(_chart.dimension()) + .group({all:d3.functor(sub.values)}, sub.key) + .keyAccessor(_chart.keyAccessor()) + .valueAccessor(_chart.valueAccessor()) + .brushOn(_chart.brushOn()); + }); + // this works around the fact compositeChart doesn't really + // have a removal interface + Object.keys(_charts) + .filter(function(c) {return keep.indexOf(c) === -1;}) + .forEach(function(c) { + clearChart(c); + children_changed = true; + }); + _chart._compose(children); + if(children_changed && _chart.legend()) + _chart.legend().render(); + }; + + function clearChart(c) { + if(_charts[c].g()) + _charts[c].g().remove(); + delete _charts[c]; + } + + function resetChildren() { + Object.keys(_charts).map(clearChart); + _charts = {}; + } + + /** + #### .chart([function]) + Get or set the chart function, which generates the child charts. Default: dc.lineChart + + ``` + // put interpolation on the line charts used for the series + chart.chart(function(c) { return dc.lineChart(c).interpolate('basis'); }) + // do a scatter series chart + chart.chart(dc.scatterPlot) + ``` + + **/ + _chart.chart = function(_) { + if (!arguments.length) return _chartFunction; + _chartFunction = _; + resetChildren(); + return _chart; + }; + + /** + #### .seriesAccessor([accessor]) + Get or set accessor function for the displayed series. Given a datum, this function + should return the series that datum belongs to. + **/ + _chart.seriesAccessor = function(_) { + if (!arguments.length) return _seriesAccessor; + _seriesAccessor = _; + resetChildren(); + return _chart; + }; + + /** + #### .seriesSort([sortFunction]) + Get or set a function to sort the list of series by, given series values. + + Example: + ``` + chart.seriesSort(d3.descending); + ``` + **/ + _chart.seriesSort = function(_) { + if (!arguments.length) return _seriesSort; + _seriesSort = _; + resetChildren(); + return _chart; + }; + + /** + #### .valueSort([sortFunction]) + Get or set a function to sort each series values by. By default this is the key accessor which, + for example, will ensure a lineChart series connects its points in increasing key/x order, + rather than haphazardly. + **/ + _chart.valueSort = function(_) { + if (!arguments.length) return _valueSort; + _valueSort = _; + resetChildren(); + return _chart; + }; + + // make compose private + _chart._compose = _chart.compose; + delete _chart.compose; + + return _chart; +}; + +/** +## Geo Choropleth Chart + +Includes: [Color Mixin](#color-mixin), [Base Mixin](#base-mixin) + +The geo choropleth chart is designed as an easy way to create a crossfilter driven choropleth map +from GeoJson data. This chart implementation was inspired by [the great d3 choropleth +example](http://bl.ocks.org/4060606). + +Examples: +* [US Venture Capital Landscape 2011](http://dc-js.github.com/dc.js/vc/index.html) + +#### dc.geoChoroplethChart(parent[, chartGroup]) +Create a choropleth chart instance and attach it to the given parent element. + +Parameters: +* parent : string | node | selection - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying + a dom block element such as a div; or a dom element or d3 selection. + +* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. + Interaction with a chart will only trigger events and redraws within the chart's group. + +Returns: +A newly created choropleth chart instance + +```js +// create a choropleth chart under "#us-chart" element using the default global chart group +var chart1 = dc.geoChoroplethChart("#us-chart"); +// create a choropleth chart under "#us-chart2" element using chart group A +var chart2 = dc.compositeChart("#us-chart2", "chartGroupA"); +``` + +**/ +dc.geoChoroplethChart = function (parent, chartGroup) { + var _chart = dc.colorMixin(dc.baseMixin({})); + + _chart.colorAccessor(function (d) { + return d || 0; + }); + + var _geoPath = d3.geo.path(); + var _projectionFlag; + + var _geoJsons = []; + + _chart._doRender = function () { + _chart.resetSvg(); + for (var layerIndex = 0; layerIndex < _geoJsons.length; ++layerIndex) { + var states = _chart.svg().append("g") + .attr("class", "layer" + layerIndex); + + var regionG = states.selectAll("g." + geoJson(layerIndex).name) + .data(geoJson(layerIndex).data) + .enter() + .append("g") + .attr("class", geoJson(layerIndex).name); + + regionG + .append("path") + .attr("fill", "white") + .attr("d", _geoPath); + + regionG.append("title"); + + plotData(layerIndex); + } + _projectionFlag = false; + }; + + function plotData(layerIndex) { + var data = generateLayeredData(); + + if (isDataLayer(layerIndex)) { + var regionG = renderRegionG(layerIndex); + + renderPaths(regionG, layerIndex, data); + + renderTitle(regionG, layerIndex, data); + } + } + + function generateLayeredData() { + var data = {}; + var groupAll = _chart.data(); + for (var i = 0; i < groupAll.length; ++i) { + data[_chart.keyAccessor()(groupAll[i])] = _chart.valueAccessor()(groupAll[i]); + } + return data; + } + + function isDataLayer(layerIndex) { + return geoJson(layerIndex).keyAccessor; + } + + function renderRegionG(layerIndex) { + var regionG = _chart.svg() + .selectAll(layerSelector(layerIndex)) + .classed("selected", function (d) { + return isSelected(layerIndex, d); + }) + .classed("deselected", function (d) { + return isDeselected(layerIndex, d); + }) + .attr("class", function (d) { + var layerNameClass = geoJson(layerIndex).name; + var regionClass = dc.utils.nameToId(geoJson(layerIndex).keyAccessor(d)); + var baseClasses = layerNameClass + " " + regionClass; + if (isSelected(layerIndex, d)) baseClasses += " selected"; + if (isDeselected(layerIndex, d)) baseClasses += " deselected"; + return baseClasses; + }); + return regionG; + } + + function layerSelector(layerIndex) { + return "g.layer" + layerIndex + " g." + geoJson(layerIndex).name; + } + + function isSelected(layerIndex, d) { + return _chart.hasFilter() && _chart.hasFilter(getKey(layerIndex, d)); + } + + function isDeselected(layerIndex, d) { + return _chart.hasFilter() && !_chart.hasFilter(getKey(layerIndex, d)); + } + + function getKey(layerIndex, d) { + return geoJson(layerIndex).keyAccessor(d); + } + + function geoJson(index) { + return _geoJsons[index]; + } + + function renderPaths(regionG, layerIndex, data) { + var paths = regionG + .select("path") + .attr("fill", function () { + var currentFill = d3.select(this).attr("fill"); + if (currentFill) + return currentFill; + return "none"; + }) + .on("click", function (d) { + return _chart.onClick(d, layerIndex); + }); + + dc.transition(paths, _chart.transitionDuration()).attr("fill", function (d, i) { + return _chart.getColor(data[geoJson(layerIndex).keyAccessor(d)], i); + }); + } + + _chart.onClick = function (d, layerIndex) { + var selectedRegion = geoJson(layerIndex).keyAccessor(d); + dc.events.trigger(function () { + _chart.filter(selectedRegion); + _chart.redrawGroup(); + }); + }; + + function renderTitle(regionG, layerIndex, data) { + if (_chart.renderTitle()) { + regionG.selectAll("title").text(function (d) { + var key = getKey(layerIndex, d); + var value = data[key]; + return _chart.title()({key: key, value: value}); + }); + } + } + + _chart._doRedraw = function () { + for (var layerIndex = 0; layerIndex < _geoJsons.length; ++layerIndex) { + plotData(layerIndex); + if(_projectionFlag) { + _chart.svg().selectAll("g." + geoJson(layerIndex).name + " path").attr("d", _geoPath); + } + } + _projectionFlag = false; + }; + + /** + #### .overlayGeoJson(json, name, keyAccessor) - **mandatory** + Use this function to insert a new GeoJson map layer. This function can be invoked multiple times + if you have multiple GeoJson data layers to render on top of each other. If you overlay multiple + layers with the same name the new overlay will override the existing one. + + Parameters: + * json - GeoJson feed + * name - name of the layer + * keyAccessor - accessor function used to extract "key" from the GeoJson data. The key extracted by + this function should match the keys returned by the crossfilter groups. + + ```js + // insert a layer for rendering US states + chart.overlayGeoJson(statesJson.features, "state", function(d) { + return d.properties.name; + }); + ``` + + **/ + _chart.overlayGeoJson = function (json, name, keyAccessor) { + for (var i = 0; i < _geoJsons.length; ++i) { + if (_geoJsons[i].name == name) { + _geoJsons[i].data = json; + _geoJsons[i].keyAccessor = keyAccessor; + return _chart; + } + } + _geoJsons.push({name: name, data: json, keyAccessor: keyAccessor}); + return _chart; + }; + + /** + #### .projection(projection) + Set custom geo projection function. See the available [d3 geo projection + functions](https://github.com/mbostock/d3/wiki/Geo-Projections). Default value: albersUsa. + + **/ + _chart.projection = function (projection) { + _geoPath.projection(projection); + _projectionFlag = true; + return _chart; + }; + + /** + #### .geoJsons() + Returns all GeoJson layers currently registered with this chart. The returned array is a + reference to this chart's internal data structure, so any modification to this array will also + modify this chart's internal registration. + + Returns an array of objects containing fields {name, data, accessor} + + **/ + _chart.geoJsons = function () { + return _geoJsons; + }; + + /** + #### .geoPath() + Returns the [d3.geo.path](https://github.com/mbostock/d3/wiki/Geo-Paths#path) object used to + render the projection and features. Can be useful for figuring out the bounding box of the + feature set and thus a way to calculate scale and translation for the projection. + + **/ + _chart.geoPath = function () { + return _geoPath; + }; + + /** + #### .removeGeoJson(name) + Remove a GeoJson layer from this chart by name + + **/ + _chart.removeGeoJson = function (name) { + var geoJsons = []; + + for (var i = 0; i < _geoJsons.length; ++i) { + var layer = _geoJsons[i]; + if (layer.name != name) { + geoJsons.push(layer); + } + } + + _geoJsons = geoJsons; + + return _chart; + }; + + return _chart.anchor(parent, chartGroup); +}; + +/** +## Bubble Overlay Chart + +Includes: [Bubble Mixin](#bubble-mixin), [Base Mixin](#base-mixin) + +The bubble overlay chart is quite different from the typical bubble chart. With the bubble overlay +chart you can arbitrarily place bubbles on an existing svg or bitmap image, thus changing the +typical x and y positioning while retaining the capability to visualize data using bubble radius +and coloring. + +Examples: +* [Canadian City Crime Stats](http://dc-js.github.com/dc.js/crime/index.html) + +#### dc.bubbleOverlay(parent[, chartGroup]) +Create a bubble overlay chart instance and attach it to the given parent element. + +Parameters: +* parent : string | node | selection - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying + a dom block element such as a div; or a dom element or d3 selection. + off-screen. Typically this element should also be the parent of the underlying image. +* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. + Interaction with a chart will only trigger events and redraws within the chart's group. + +Returns: +A newly created bubble overlay chart instance + +```js +// create a bubble overlay chart on top of the "#chart-container1 svg" element using the default global chart group +var bubbleChart1 = dc.bubbleOverlayChart("#chart-container1").svg(d3.select("#chart-container1 svg")); +// create a bubble overlay chart on top of the "#chart-container2 svg" element using chart group A +var bubbleChart2 = dc.compositeChart("#chart-container2", "chartGroupA").svg(d3.select("#chart-container2 svg")); +``` + +#### .svg(imageElement) - **mandatory** +Set the underlying svg image element. Unlike other dc charts this chart will not generate a svg +element; therefore the bubble overlay chart will not work if this function is not invoked. If the +underlying image is a bitmap, then an empty svg will need to be created on top of the image. + +```js +// set up underlying svg element +chart.svg(d3.select("#chart svg")); +``` + +**/ +dc.bubbleOverlay = function(root, chartGroup) { + var BUBBLE_OVERLAY_CLASS = "bubble-overlay"; + var BUBBLE_NODE_CLASS = "node"; + var BUBBLE_CLASS = "bubble"; + + var _chart = dc.bubbleMixin(dc.baseMixin({})); + var _g; + var _points = []; + + _chart.transitionDuration(750); + + _chart.radiusValueAccessor(function(d) { + return d.value; + }); + + /** + #### .point(name, x, y) - **mandatory** + Set up a data point on the overlay. The name of a data point should match a specific "key" among + data groups generated using keyAccessor. If a match is found (point name <-> data group key) + then a bubble will be generated at the position specified by the function. x and y + value specified here are relative to the underlying svg. + + **/ + _chart.point = function(name, x, y) { + _points.push({name: name, x: x, y: y}); + return _chart; + }; + + _chart._doRender = function() { + _g = initOverlayG(); + + _chart.r().range([_chart.MIN_RADIUS, _chart.width() * _chart.maxBubbleRelativeSize()]); + + initializeBubbles(); + + _chart.fadeDeselectedArea(); + + return _chart; + }; + + function initOverlayG() { + _g = _chart.select("g." + BUBBLE_OVERLAY_CLASS); + if (_g.empty()) + _g = _chart.svg().append("g").attr("class", BUBBLE_OVERLAY_CLASS); + return _g; + } + + function initializeBubbles() { + var data = mapData(); + + _points.forEach(function(point) { + var nodeG = getNodeG(point, data); + + var circle = nodeG.select("circle." + BUBBLE_CLASS); + + if (circle.empty()) + circle = nodeG.append("circle") + .attr("class", BUBBLE_CLASS) + .attr("r", 0) + .attr("fill", _chart.getColor) + .on("click", _chart.onClick); + + dc.transition(circle, _chart.transitionDuration()) + .attr("r", function(d) { + return _chart.bubbleR(d); + }); + + _chart._doRenderLabel(nodeG); + + _chart._doRenderTitles(nodeG); + }); + } + + function mapData() { + var data = {}; + _chart.data().forEach(function(datum) { + data[_chart.keyAccessor()(datum)] = datum; + }); + return data; + } + + function getNodeG(point, data) { + var bubbleNodeClass = BUBBLE_NODE_CLASS + " " + dc.utils.nameToId(point.name); + + var nodeG = _g.select("g." + dc.utils.nameToId(point.name)); + + if (nodeG.empty()) { + nodeG = _g.append("g") + .attr("class", bubbleNodeClass) + .attr("transform", "translate(" + point.x + "," + point.y + ")"); + } + + nodeG.datum(data[point.name]); + + return nodeG; + } + + _chart._doRedraw = function() { + updateBubbles(); + + _chart.fadeDeselectedArea(); + + return _chart; + }; + + function updateBubbles() { + var data = mapData(); + + _points.forEach(function(point) { + var nodeG = getNodeG(point, data); + + var circle = nodeG.select("circle." + BUBBLE_CLASS); + + dc.transition(circle, _chart.transitionDuration()) + .attr("r", function(d) { + return _chart.bubbleR(d); + }) + .attr("fill", _chart.getColor); + + _chart.doUpdateLabels(nodeG); + + _chart.doUpdateTitles(nodeG); + }); + } + + _chart.debug = function(flag) { + if(flag){ + var debugG = _chart.select("g." + dc.constants.DEBUG_GROUP_CLASS); + + if(debugG.empty()) + debugG = _chart.svg() + .append("g") + .attr("class", dc.constants.DEBUG_GROUP_CLASS); + + var debugText = debugG.append("text") + .attr("x", 10) + .attr("y", 20); + + debugG + .append("rect") + .attr("width", _chart.width()) + .attr("height", _chart.height()) + .on("mousemove", function() { + var position = d3.mouse(debugG.node()); + var msg = position[0] + ", " + position[1]; + debugText.text(msg); + }); + }else{ + _chart.selectAll(".debug").remove(); + } + + return _chart; + }; + + _chart.anchor(root, chartGroup); + + return _chart; +}; + +/** +## Row Chart + +Includes: [Cap Mixin](#cap-mixin), [Margin Mixin](#margin-mixin), [Color Mixin](#color-mixin), [Base Mixin](#base-mixin) + +Concrete row chart implementation. + +#### dc.rowChart(parent[, chartGroup]) +Create a row chart instance and attach it to the given parent element. + +Parameters: + +* parent : string | node | selection - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying + a dom block element such as a div; or a dom element or d3 selection. + +* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. + Interaction with a chart will only trigger events and redraws within the chart's group. + +Returns: +A newly created row chart instance + +```js +// create a row chart under #chart-container1 element using the default global chart group +var chart1 = dc.rowChart("#chart-container1"); +// create a row chart under #chart-container2 element using chart group A +var chart2 = dc.rowChart("#chart-container2", "chartGroupA"); +``` + +**/ +dc.rowChart = function (parent, chartGroup) { + + var _g; + + var _labelOffsetX = 10; + var _labelOffsetY = 15; + var _titleLabelOffsetX = 2; + + var _gap = 5; + + var _fixedBarHeight = false; + var _rowCssClass = "row"; + var _titleRowCssClass = "titlerow"; + var _renderTitleLabel = false; + + var _chart = dc.capMixin(dc.marginMixin(dc.colorMixin(dc.baseMixin({})))); + + var _x; + + var _elasticX; + + var _xAxis = d3.svg.axis().orient("bottom"); + + var _rowData; + + _chart.rowsCap = _chart.cap; + + function calculateAxisScale() { + if (!_x || _elasticX) { + var extent = d3.extent(_rowData, _chart.cappedValueAccessor); + if (extent[0] > 0) extent[0] = 0; + _x = d3.scale.linear().domain(extent) + .range([0, _chart.effectiveWidth()]); + } + _xAxis.scale(_x); + } + + function drawAxis() { + var axisG = _g.select("g.axis"); + + calculateAxisScale(); + + if (axisG.empty()) + axisG = _g.append("g").attr("class", "axis") + .attr("transform", "translate(0, " + _chart.effectiveHeight() + ")"); + + dc.transition(axisG, _chart.transitionDuration()) + .call(_xAxis); + } + + _chart._doRender = function () { + _chart.resetSvg(); + + _g = _chart.svg() + .append("g") + .attr("transform", "translate(" + _chart.margins().left + "," + _chart.margins().top + ")"); + + drawChart(); + + return _chart; + }; + + _chart.title(function (d) { + return _chart.cappedKeyAccessor(d) + ": " + _chart.cappedValueAccessor(d); + }); + + _chart.label(_chart.cappedKeyAccessor); + + /** + #### .x([scale]) + Gets or sets the x scale. The x scale can be any d3 + [quantitive scale](https://github.com/mbostock/d3/wiki/Quantitative-Scales) + **/ + _chart.x = function(x){ + if(!arguments.length) return _x; + _x = x; + return _chart; + }; + + function drawGridLines() { + _g.selectAll("g.tick") + .select("line.grid-line") + .remove(); + + _g.selectAll("g.tick") + .append("line") + .attr("class", "grid-line") + .attr("x1", 0) + .attr("y1", 0) + .attr("x2", 0) + .attr("y2", function () { + return -_chart.effectiveHeight(); + }); + } + + function drawChart() { + _rowData = _chart.data(); + + drawAxis(); + drawGridLines(); + + var rows = _g.selectAll("g." + _rowCssClass) + .data(_rowData); + + createElements(rows); + removeElements(rows); + updateElements(rows); + } + + function createElements(rows) { + var rowEnter = rows.enter() + .append("g") + .attr("class", function (d, i) { + return _rowCssClass + " _" + i; + }); + + rowEnter.append("rect").attr("width", 0); + + createLabels(rowEnter); + updateLabels(rows); + } + + function removeElements(rows) { + rows.exit().remove(); + } + + function rootValue() { + var root = _x(0); + return root === -Infinity ? _x(1) : root; + } + + function updateElements(rows) { + var n = _rowData.length; + + var height; + if (!_fixedBarHeight) height = (_chart.effectiveHeight() - (n + 1) * _gap) / n; + else height = _fixedBarHeight; + + var rect = rows.attr("transform",function (d, i) { + return "translate(0," + ((i + 1) * _gap + i * height) + ")"; + }).select("rect") + .attr("height", height) + .attr("fill", _chart.getColor) + .on("click", onClick) + .classed("deselected", function (d) { + return (_chart.hasFilter()) ? !isSelectedRow(d) : false; + }) + .classed("selected", function (d) { + return (_chart.hasFilter()) ? isSelectedRow(d) : false; + }); + + dc.transition(rect, _chart.transitionDuration()) + .attr("width", function (d) { + return Math.abs(rootValue() - _x(_chart.valueAccessor()(d))); + }) + .attr("transform", translateX); + + createTitles(rows); + updateLabels(rows); + } + + function createTitles(rows) { + if (_chart.renderTitle()) { + rows.selectAll("title").remove(); + rows.append("title").text(_chart.title()); + } + } + + function createLabels(rowEnter) { + if (_chart.renderLabel()) { + rowEnter.append("text") + .on("click", onClick); + } + if (_chart.renderTitleLabel()) { + rowEnter.append("text") + .attr("class", _titleRowCssClass) + .on("click", onClick); + } + } + + function updateLabels(rows) { + if (_chart.renderLabel()) { + var lab = rows.select("text") + .attr("x", _labelOffsetX) + .attr("y", _labelOffsetY) + .on("click", onClick) + .attr("class", function (d, i) { + return _rowCssClass + " _" + i; + }) + .text(function (d) { + return _chart.label()(d); + }); + dc.transition(lab, _chart.transitionDuration()) + .attr("transform", translateX); + } + if (_chart.renderTitleLabel()) { + var titlelab = rows.select("." + _titleRowCssClass) + .attr("x", _chart.effectiveWidth() - _titleLabelOffsetX) + .attr("y", _labelOffsetY) + .attr("text-anchor", "end") + .on("click", onClick) + .attr("class", function (d, i) { + return _titleRowCssClass + " _" + i ; + }) + .text(function (d) { + return _chart.title()(d); + }); + dc.transition(titlelab, _chart.transitionDuration()) + .attr("transform", translateX); + } + } + + /** + #### .renderTitleLabel(boolean) + Turn on/off Title label rendering (values) using SVG style of text-anchor 'end' + + **/ + _chart.renderTitleLabel = function (_) { + if (!arguments.length) return _renderTitleLabel; + _renderTitleLabel = _; + return _chart; + }; + + function onClick(d) { + _chart.onClick(d); + } + + function translateX(d) { + var x = _x(_chart.cappedValueAccessor(d)), + x0 = rootValue(), + s = x > x0 ? x0 : x; + return "translate("+s+",0)"; + } + + _chart._doRedraw = function () { + drawChart(); + return _chart; + }; + + /** + #### .xAxis() + Get the x axis for the row chart instance. Note: not settable for row charts. + See the [d3 axis object](https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-axis) documention for more information. + ```js + // customize x axis tick format + chart.xAxis().tickFormat(function(v) {return v + "%";}); + // customize x axis tick values + chart.xAxis().tickValues([0, 100, 200, 300]); + ``` + + **/ + _chart.xAxis = function () { + return _xAxis; + }; + + /** + #### .fixedBarHeight([height]) + Get or set the fixed bar height. Default is [false] which will auto-scale bars. + For example, if you want to fix the height for a specific number of bars (useful in TopN charts) + you could fix height as follows (where count = total number of bars in your TopN and gap is your vertical gap space). + ```js + chart.fixedBarHeight( chartheight - (count + 1) * gap / count); + ``` + **/ + _chart.fixedBarHeight = function (g) { + if (!arguments.length) return _fixedBarHeight; + _fixedBarHeight = g; + return _chart; + }; + + /** + #### .gap([gap]) + Get or set the vertical gap space between rows on a particular row chart instance. Default gap is 5px; + + **/ + _chart.gap = function (g) { + if (!arguments.length) return _gap; + _gap = g; + return _chart; + }; + + /** + #### .elasticX([boolean]) + Get or set the elasticity on x axis. If this attribute is set to true, then the x axis will rescle to auto-fit the data + range when filtered. + + **/ + _chart.elasticX = function (_) { + if (!arguments.length) return _elasticX; + _elasticX = _; + return _chart; + }; + + /** + #### .labelOffsetX([x]) + Get or set the x offset (horizontal space to the top left corner of a row) for labels on a particular row chart. Default x offset is 10px; + + **/ + _chart.labelOffsetX = function (o) { + if (!arguments.length) return _labelOffsetX; + _labelOffsetX = o; + return _chart; + }; + + /** + #### .labelOffsetY([y]) + Get or set the y offset (vertical space to the top left corner of a row) for labels on a particular row chart. Default y offset is 15px; + + **/ + _chart.labelOffsetY = function (o) { + if (!arguments.length) return _labelOffsetY; + _labelOffsetY = o; + return _chart; + }; + + /** + #### .titleLabelOffsetx([x]) + Get of set the x offset (horizontal space between right edge of row and right edge or text. Default x offset is 2px; + + **/ + _chart.titleLabelOffsetX = function (o) { + if (!arguments.length) return _titleLabelOffsetX; + _titleLabelOffsetX = o; + return _chart; + }; + + function isSelectedRow (d) { + return _chart.hasFilter(_chart.cappedKeyAccessor(d)); + } + + return _chart.anchor(parent, chartGroup); +}; + +/** +## Legend +Legend is a attachable widget that can be added to other dc charts to render horizontal legend +labels. + +```js +chart.legend(dc.legend().x(400).y(10).itemHeight(13).gap(5)) +``` + +Examples: +* [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) +* [Canadian City Crime Stats](http://dc-js.github.com/dc.js/crime/index.html) + +**/ +dc.legend = function () { + var LABEL_GAP = 2; + + var _legend = {}, + _parent, + _x = 0, + _y = 0, + _itemHeight = 12, + _gap = 5, + _horizontal = false, + _legendWidth = 560, + _itemWidth = 70; + + var _g; + + _legend.parent = function (p) { + if (!arguments.length) return _parent; + _parent = p; + return _legend; + }; + + _legend.render = function () { + _parent.svg().select("g.dc-legend").remove(); + _g = _parent.svg().append("g") + .attr("class", "dc-legend") + .attr("transform", "translate(" + _x + "," + _y + ")"); + var legendables = _parent.legendables(); + + var itemEnter = _g.selectAll('g.dc-legend-item') + .data(legendables) + .enter() + .append("g") + .attr("class", "dc-legend-item") + .on("mouseover", function(d) { + _parent.legendHighlight(d); + }) + .on("mouseout", function (d) { + _parent.legendReset(d); + }) + .on("click", function (d) { + d.chart.legendToggle(d); + }); + + _g.selectAll('g.dc-legend-item') + .classed("fadeout", function(d) { + return d.chart.isLegendableHidden(d); + }); + + if (legendables.some(dc.pluck('dashstyle'))) { + itemEnter + .append("line") + .attr("x1", 0) + .attr("y1", _itemHeight / 2) + .attr("x2", _itemHeight) + .attr("y2", _itemHeight / 2) + .attr("stroke-width", 2) + .attr("stroke-dasharray", dc.pluck('dashstyle')) + .attr("stroke", dc.pluck('color')); + } else { + itemEnter + .append("rect") + .attr("width", _itemHeight) + .attr("height", _itemHeight) + .attr("fill", function(d){return d?d.color:"blue";}); + } + + itemEnter.append("text") + .text(dc.pluck('name')) + .attr("x", _itemHeight + LABEL_GAP) + .attr("y", function(){return _itemHeight / 2 + (this.clientHeight?this.clientHeight:13) / 2 - 2;}); + + var _cumulativeLegendTextWidth = 0; + var row = 0; + itemEnter.attr("transform", function(d, i) { + if(_horizontal) { + var translateBy = "translate(" + _cumulativeLegendTextWidth + "," + row * legendItemHeight() + ")"; + if ((_cumulativeLegendTextWidth + _itemWidth) >= _legendWidth) { + ++row ; + _cumulativeLegendTextWidth = 0 ; + } else { + _cumulativeLegendTextWidth += _itemWidth; + } + return translateBy; + } + else { + return "translate(0," + i * legendItemHeight() + ")"; + } + }); + }; + + function legendItemHeight() { + return _gap + _itemHeight; + } + + /** + #### .x([value]) + Set or get x coordinate for legend widget. Default: 0. + **/ + _legend.x = function (x) { + if (!arguments.length) return _x; + _x = x; + return _legend; + }; + + /** + #### .y([value]) + Set or get y coordinate for legend widget. Default: 0. + **/ + _legend.y = function (y) { + if (!arguments.length) return _y; + _y = y; + return _legend; + }; + + /** + #### .gap([value]) + Set or get gap between legend items. Default: 5. + **/ + _legend.gap = function (gap) { + if (!arguments.length) return _gap; + _gap = gap; + return _legend; + }; + + /** + #### .itemHeight([value]) + Set or get legend item height. Default: 12. + **/ + _legend.itemHeight = function (h) { + if (!arguments.length) return _itemHeight; + _itemHeight = h; + return _legend; + }; + + /** + #### .horizontal([boolean]) + Position legend horizontally instead of vertically + **/ + _legend.horizontal = function(_) { + if (!arguments.length) return _horizontal; + _horizontal = _; + return _legend; + }; + + /** + #### .legendWidth([value]) + Maximum width for horizontal legend. Default: 560. + **/ + _legend.legendWidth = function(_) { + if (!arguments.length) return _legendWidth; + _legendWidth = _; + return _legend; + }; + + /** + #### .itemWidth([value]) + legendItem width for horizontal legend. Default: 70. + **/ + _legend.itemWidth = function(_) { + if (!arguments.length) return _itemWidth; + _itemWidth = _; + return _legend; + }; + + return _legend; +}; + +/** +## Scatter Plot + +Includes: [Coordinate Grid Mixin](#coordinate-grid-mixin) + +A scatter plot chart + +#### dc.scatterPlot(parent[, chartGroup]) +Create a scatter plot instance and attach it to the given parent element. + +Parameters: + +* parent : string | node | selection | compositeChart - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying + a dom block element such as a div; or a dom element or d3 selection. + If the scatter plot is a sub-chart in a [Composite Chart](#composite-chart) then pass in the parent composite + chart instance. + +* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. + Interaction with a chart will only trigger events and redraws within the chart's group. + +Returns: +A newly created scatter plot instance + +```js +// create a scatter plot under #chart-container1 element using the default global chart group +var chart1 = dc.scatterPlot("#chart-container1"); +// create a scatter plot under #chart-container2 element using chart group A +var chart2 = dc.scatterPlot("#chart-container2", "chartGroupA"); +// create a sub-chart under a composite parent chart +var chart3 = dc.scatterPlot(compositeChart); +``` + + **/ +dc.scatterPlot = function (parent, chartGroup) { + var _chart = dc.coordinateGridMixin({}); + var _symbol = d3.svg.symbol(); + + var originalKeyAccessor = _chart.keyAccessor(); + _chart.keyAccessor(function (d) { return originalKeyAccessor(d)[0]; }); + _chart.valueAccessor(function (d) { return originalKeyAccessor(d)[1]; }); + _chart.colorAccessor(function (d) { return _chart._groupName; }); + + var _locator = function (d) { + return "translate(" + _chart.x()(_chart.keyAccessor()(d)) + "," + + _chart.y()(_chart.valueAccessor()(d)) + ")"; + }; + + var _symbolSize = 3; + var _highlightedSize = 5; + var _hiddenSize = 0; + + _symbol.size(function(d) { + if(d.value === 0) + return _hiddenSize; + else if(this.filtered) + return Math.pow(_highlightedSize, 2); + else + return Math.pow(_symbolSize, 2); + }); + + dc.override(_chart, "_filter", function(filter) { + if (!arguments.length) return _chart.__filter(); + + return _chart.__filter(dc.filters.RangedTwoDimensionalFilter(filter)); + }); + + _chart.plotData = function () { + var symbols = _chart.chartBodyG().selectAll("path.symbol") + .data(_chart.data()); + + symbols + .enter() + .append("path") + .attr("class", "symbol") + .attr("opacity", 0) + .attr("fill", _chart.getColor) + .attr("transform", _locator); + + dc.transition(symbols, _chart.transitionDuration()) + .attr("opacity", function(d) { return d.value ? 1 : 0; }) + .attr("fill", _chart.getColor) + .attr("transform", _locator) + .attr("d", _symbol); + + dc.transition(symbols.exit(), _chart.transitionDuration()) + .attr("opacity", 0).remove(); + }; + + /** + #### .symbol([type]) + Get or set the symbol type used for each point. By default the symbol is a circle. See the D3 + [docs](https://github.com/mbostock/d3/wiki/SVG-Shapes#wiki-symbol_type) for acceptable types. + Type can be a constant or an accessor. + + **/ + _chart.symbol = function(type) { + if(!arguments.length) return _symbol.type(); + _symbol.type(type); + return _chart; + }; + + /** + #### .symbolSize([radius]) + Set or get radius for symbols. Default: 3. + + **/ + _chart.symbolSize = function(s){ + if(!arguments.length) return _symbolSize; + _symbolSize = s; + return _chart; + }; + + /** + #### .highlightedSize([radius]) + Set or get radius for highlighted symbols. Default: 4. + + **/ + _chart.highlightedSize = function(s){ + if(!arguments.length) return _highlightedSize; + _highlightedSize = s; + return _chart; + }; + + /** + #### .hiddenSize([radius]) + Set or get radius for symbols when the group is empty. Default: 0. + + **/ + _chart.hiddenSize = function(s){ + if(!arguments.length) return _hiddenSize; + _hiddenSize = s; + return _chart; + }; + + _chart.legendables = function () { + return [{chart: _chart, name: _chart._groupName, color: _chart.getColor()}]; + }; + + _chart.legendHighlight = function (d) { + resizeSymbolsWhere(function (symbol) { + return symbol.attr('fill') == d.color; + }, _highlightedSize); + _chart.selectAll('.chart-body path.symbol').filter(function () { + return d3.select(this).attr('fill') != d.color; + }).classed('fadeout', true); + }; + + _chart.legendReset = function (d) { + resizeSymbolsWhere(function (symbol) { + return symbol.attr('fill') == d.color; + }, _symbolSize); + _chart.selectAll('.chart-body path.symbol').filter(function () { + return d3.select(this).attr('fill') != d.color; + }).classed('fadeout', false); + }; + + function resizeSymbolsWhere(condition, size) { + var symbols = _chart.selectAll('.chart-body path.symbol').filter(function (d) { + return condition(d3.select(this)); + }); + var oldSize = _symbol.size(); + _symbol.size(Math.pow(size, 2)); + dc.transition(symbols, _chart.transitionDuration()).attr("d", _symbol); + _symbol.size(oldSize); + } + + _chart.setHandlePaths = function () { + // no handle paths for poly-brushes + }; + + _chart.extendBrush = function () { + var extent = _chart.brush().extent(); + if (_chart.round()) { + extent[0] = extent[0].map(_chart.round()); + extent[1] = extent[1].map(_chart.round()); + + _chart.g().select(".brush") + .call(_chart.brush().extent(extent)); + } + return extent; + }; + + _chart.brushIsEmpty = function (extent) { + return _chart.brush().empty() || !extent || extent[0][0] >= extent[1][0] || extent[0][1] >= extent[1][1]; + }; + + function resizeFiltered(filter) { + var symbols = _chart.selectAll('.chart-body path.symbol').each(function (d) { + this.filtered = filter && filter.isFiltered(d.key); + }); + + dc.transition(symbols, _chart.transitionDuration()).attr("d", _symbol); + } + + _chart._brushing = function () { + var extent = _chart.extendBrush(); + + _chart.redrawBrush(_chart.g()); + + if (_chart.brushIsEmpty(extent)) { + dc.events.trigger(function () { + _chart.filter(null); + _chart.redrawGroup(); + }); + + resizeFiltered(false); + + } else { + var ranged2DFilter = dc.filters.RangedTwoDimensionalFilter(extent); + dc.events.trigger(function () { + _chart.filter(null); + _chart.filter(ranged2DFilter); + _chart.redrawGroup(); + }, dc.constants.EVENT_DELAY); + + resizeFiltered(ranged2DFilter); + } + }; + + _chart.setBrushY = function (gBrush) { + gBrush.call(_chart.brush().y(_chart.y())); + }; + + return _chart.anchor(parent, chartGroup); +}; + +/** +## Number Display Widget + +Includes: [Base Mixin](#base-mixin) + +A display of a single numeric value. + +Examples: + +* [Test Example](http://dc-js.github.io/dc.js/examples/number.html) + +#### dc.numberDisplay(parent[, chartGroup]) +Create a Number Display instance and attach it to the given parent element. + +Unlike other charts, you do not need to set a dimension. Instead a group object must be provided and +a valueAccessor that returns a single value. + +Parameters: + +* parent : string | node | selection - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying + a dom block element such as a div; or a dom element or d3 selection. +* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. + The number display widget will only react to filter changes in the chart group. + +Returns: +A newly created number display instance + +```js +// create a number display under #chart-container1 element using the default global chart group +var display1 = dc.numberDisplay("#chart-container1"); +``` + +**/ +dc.numberDisplay = function (parent, chartGroup) { + var SPAN_CLASS = 'number-display'; + var _formatNumber = d3.format(".2s"); + var _chart = dc.baseMixin({}); + var _html = {one:"",some:"",none:""}; + + // dimension not required + _chart._mandatoryAttributes(['group']); + + /** + #### .html([object]) + Gets or sets an optional object specifying HTML templates to use depending on the number + displayed. The text `%number` will be replaced with the current value. + - one: HTML template to use if the number is 1 + - zero: HTML template to use if the number is 0 + - some: HTML template to use otherwise + + ```js + numberWidget.html({ + one:"%number record", + some:"%number records", + none:"no records"}) + ``` + **/ + + _chart.html = function(s) { + if (!arguments.length) return _html; + if(s.none) + _html.none = s.none;//if none available + else if(s.one) + _html.none = s.one;//if none not available use one + else if(s.some) + _html.none = s.some;//if none and one not available use some + if(s.one) + _html.one = s.one;//if one available + else if(s.some) + _html.one = s.some;//if one not available use some + if(s.some) + _html.some = s.some;//if some available + else if(s.one) + _html.some = s.one;//if some not available use one + return _chart; + }; + + /** + #### .value() + Calculate and return the underlying value of the display + **/ + + _chart.value = function () { + return _chart.data(); + }; + + _chart.data(function (group) { + var valObj = group.value ? group.value() : group.top(1)[0]; + return _chart.valueAccessor()(valObj); + }); + + _chart.transitionDuration(250); // good default + + _chart._doRender = function () { + var newValue = _chart.value(), + span = _chart.selectAll("."+SPAN_CLASS); + + if(span.empty()) + span = span.data([0]) + .enter() + .append("span") + .attr("class", SPAN_CLASS); + + span.transition() + .duration(_chart.transitionDuration()) + .ease('quad-out-in') + .tween("text", function () { + var interp = d3.interpolateNumber(this.lastValue || 0, newValue); + this.lastValue = newValue; + return function (t) { + var html = null, num = _chart.formatNumber()(interp(t)); + if(newValue===0 && (_html.none!=="")) + html = _html.none; + else if(newValue===1 &&(_html.one!=="")) + html = _html.one; + else if(_html.some!=="") + html = _html.some; + this.innerHTML = html ? html.replace("%number", num) : num; + }; + }); + }; + + _chart._doRedraw = function(){ + return _chart._doRender(); + }; + + /** + #### .formatNumber([formatter]) + Get or set a function to format the value for the display. By default `d3.format(".2s");` is used. + + **/ + _chart.formatNumber = function (_) { + if (!arguments.length) return _formatNumber; + _formatNumber = _; + return _chart; + }; + + return _chart.anchor(parent, chartGroup); +}; + +/** + ## Heat Map + + Includes: [Color Mixin](#color-mixin), [Margin Mixin](#margin-mixin), [Base Mixin](#base-mixin) + + A heat map is matrix that represents the values of two dimensions of data using colors. + + #### dc.heatMap(parent[, chartGroup]) + Create a heat map instance and attach it to the given parent element. + + Parameters: +* parent : string | node | selection - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying + a dom block element such as a div; or a dom element or d3 selection. + +* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. + Interaction with a chart will only trigger events and redraws within the chart's group. + + Returns: + A newly created heat map instance + + ```js + // create a heat map under #chart-container1 element using the default global chart group + var heatMap1 = dc.heatMap("#chart-container1"); + // create a heat map under #chart-container2 element using chart group A + var heatMap2 = dc.heatMap("#chart-container2", "chartGroupA"); + ``` + + **/ +dc.heatMap = function (parent, chartGroup) { + + var DEFAULT_BORDER_RADIUS = 6.75; + + var _chartBody; + + var _cols; + var _rows; + var _xBorderRadius = DEFAULT_BORDER_RADIUS; + var _yBorderRadius = DEFAULT_BORDER_RADIUS; + + var _chart = dc.colorMixin(dc.marginMixin(dc.baseMixin({}))); + _chart._mandatoryAttributes(['group']); + _chart.title(_chart.colorAccessor()); + + var _xAxisOnClick = function (d) { filterAxis(0, d); }; + var _yAxisOnClick = function (d) { filterAxis(1, d); }; + var _boxOnClick = function (d) { + var filter = d.key; + dc.events.trigger(function() { + _chart.filter(filter); + _chart.redrawGroup(); + }); + }; + + function filterAxis(axis, value) { + var cellsOnAxis = _chart.selectAll(".box-group").filter( function (d) { + return d.key[axis] == value; + }); + var unfilteredCellsOnAxis = cellsOnAxis.filter( function (d) { + return !_chart.hasFilter(d.key); + }); + dc.events.trigger(function() { + if(unfilteredCellsOnAxis.empty()) { + cellsOnAxis.each( function (d) { + _chart.filter(d.key); + }); + } else { + unfilteredCellsOnAxis.each( function (d) { + _chart.filter(d.key); + }); + } + _chart.redrawGroup(); + }); + } + + dc.override(_chart, "filter", function(filter) { + if (!arguments.length) return _chart._filter(); + + return _chart._filter(dc.filters.TwoDimensionalFilter(filter)); + }); + + function uniq(d,i,a) { + return !i || a[i-1] != d; + } + + /** + #### .rows([values]) + Gets or sets the values used to create the rows of the heatmap, as an array. By default, all + the values will be fetched from the data using the value accessor, and they will be sorted in + ascending order. + **/ + + _chart.rows = function (_) { + if (arguments.length) { + _rows = _; + return _chart; + } + if (_rows) return _rows; + var rowValues = _chart.data().map(_chart.valueAccessor()); + rowValues.sort(d3.ascending); + return d3.scale.ordinal().domain(rowValues.filter(uniq)); + }; + + /** + #### .cols([keys]) + Gets or sets the keys used to create the columns of the heatmap, as an array. By default, all + the values will be fetched from the data using the key accessor, and they will be sorted in + ascending order. + **/ + _chart.cols = function (_) { + if (arguments.length) { + _cols = _; + return _chart; + } + if (_cols) return _cols; + var colValues = _chart.data().map(_chart.keyAccessor()); + colValues.sort(d3.ascending); + return d3.scale.ordinal().domain(colValues.filter(uniq)); + }; + + _chart._doRender = function () { + _chart.resetSvg(); + + _chartBody = _chart.svg() + .append("g") + .attr("class", "heatmap") + .attr("transform", "translate(" + _chart.margins().left + "," + _chart.margins().top + ")"); + + return _chart._doRedraw(); + }; + + _chart._doRedraw = function () { + var rows = _chart.rows(), + cols = _chart.cols(), + rowCount = rows.domain().length, + colCount = cols.domain().length, + boxWidth = Math.floor(_chart.effectiveWidth() / colCount), + boxHeight = Math.floor(_chart.effectiveHeight() / rowCount); + + cols.rangeRoundBands([0, _chart.effectiveWidth()]); + rows.rangeRoundBands([_chart.effectiveHeight(), 0]); + + var boxes = _chartBody.selectAll("g.box-group").data(_chart.data(), function(d,i) { + return _chart.keyAccessor()(d,i) + '\0' + _chart.valueAccessor()(d,i); + }); + var gEnter = boxes.enter().append("g") + .attr("class", "box-group"); + + gEnter.append("rect") + .attr("class","heat-box") + .attr("fill", "white") + .on("click", _chart.boxOnClick()); + + gEnter.append("title") + .text(_chart.title()); + + dc.transition(boxes.selectAll("rect"), _chart.transitionDuration()) + .attr("x", function(d,i) { return cols(_chart.keyAccessor()(d,i)); }) + .attr("y", function(d,i) { return rows(_chart.valueAccessor()(d,i)); }) + .attr("rx", _xBorderRadius) + .attr("ry", _yBorderRadius) + .attr("fill", _chart.getColor) + .attr("width", boxWidth) + .attr("height", boxHeight); + + boxes.exit().remove(); + + var gCols = _chartBody.selectAll("g.cols"); + if (gCols.empty()) + gCols = _chartBody.append("g").attr("class", "cols axis"); + var gColsText = gCols.selectAll('text').data(cols.domain()); + gColsText.enter().append("text") + .attr("x", function(d) { return cols(d) + boxWidth/2; }) + .style("text-anchor", "middle") + .attr("y", _chart.effectiveHeight()) + .attr("dy", 12) + .on("click", _chart.xAxisOnClick()) + .text(function(d) { return d; }); + dc.transition(gColsText, _chart.transitionDuration()) + .text(function(d) { return d; }) + .attr("x", function(d) { return cols(d) + boxWidth/2; }); + gColsText.exit().remove(); + var gRows = _chartBody.selectAll("g.rows"); + if (gRows.empty()) + gRows = _chartBody.append("g").attr("class", "rows axis"); + var gRowsText = gRows.selectAll('text').data(rows.domain()); + gRowsText.enter().append("text") + .attr("dy", 6) + .style("text-anchor", "end") + .attr("x", 0) + .attr("dx", -2) + .on("click", _chart.yAxisOnClick()) + .text(function(d) { return d; }); + dc.transition(gRowsText, _chart.transitionDuration()) + .text(function(d) { return d; }) + .attr("y", function(d) { return rows(d) + boxHeight/2; }); + gRowsText.exit().remove(); + + if (_chart.hasFilter()) { + _chart.selectAll("g.box-group").each(function (d) { + if (_chart.isSelectedNode(d)) { + _chart.highlightSelected(this); + } else { + _chart.fadeDeselected(this); + } + }); + } else { + _chart.selectAll("g.box-group").each(function () { + _chart.resetHighlight(this); + }); + } + return _chart; + }; + /** + #### .boxOnClick([handler]) + Gets or sets the handler that fires when an individual cell is clicked in the heatmap. + By default, filtering of the cell will be toggled. + **/ + _chart.boxOnClick = function (f) { + if (!arguments.length) return _boxOnClick; + _boxOnClick = f; + return _chart; + }; + + /** + #### .xAxisOnClick([handler]) + Gets or sets the handler that fires when a column tick is clicked in the x axis. + By default, if any cells in the column are unselected, the whole column will be selected, + otherwise the whole column will be unselected. + **/ + _chart.xAxisOnClick = function (f) { + if (!arguments.length) return _xAxisOnClick; + _xAxisOnClick = f; + return _chart; + }; + + /** + #### .yAxisOnClick([handler]) + Gets or sets the handler that fires when a row tick is clicked in the y axis. + By default, if any cells in the row are unselected, the whole row will be selected, + otherwise the whole row will be unselected. + **/ + _chart.yAxisOnClick = function (f) { + if (!arguments.length) return _yAxisOnClick; + _yAxisOnClick = f; + return _chart; + }; + + /** + #### .xBorderRadius([value]) + Gets or sets the X border radius. Set to 0 to get full rectangles. Default: 6.75 + */ + _chart.xBorderRadius = function (d) { + if (!arguments.length) return _xBorderRadius; + _xBorderRadius = d; + return _chart; + }; + + /** + #### .xBorderRadius([value]) + Gets or sets the Y border radius. Set to 0 to get full rectangles. Default: 6.75 + */ + _chart.yBorderRadius = function (d) { + if (!arguments.length) return _yBorderRadius; + _yBorderRadius = d; + return _chart; + }; + + _chart.isSelectedNode = function (d) { + return _chart.hasFilter(d.key); + }; + + return _chart.anchor(parent, chartGroup); +}; + +// https://github.com/d3/d3-plugins/blob/master/box/box.js +(function() { + +// Inspired by http://informationandvisualization.de/blog/box-plot +d3.box = function() { + var width = 1, + height = 1, + duration = 0, + domain = null, + value = Number, + whiskers = boxWhiskers, + quartiles = boxQuartiles, + tickFormat = null; + + // For each small multiple… + function box(g) { + g.each(function(d, i) { + d = d.map(value).sort(d3.ascending); + var g = d3.select(this), + n = d.length, + min = d[0], + max = d[n - 1]; + + // Compute quartiles. Must return exactly 3 elements. + var quartileData = d.quartiles = quartiles(d); + + // Compute whiskers. Must return exactly 2 elements, or null. + var whiskerIndices = whiskers && whiskers.call(this, d, i), + whiskerData = whiskerIndices && whiskerIndices.map(function(i) { return d[i]; }); + + // Compute outliers. If no whiskers are specified, all data are "outliers". + // We compute the outliers as indices, so that we can join across transitions! + var outlierIndices = whiskerIndices + ? d3.range(0, whiskerIndices[0]).concat(d3.range(whiskerIndices[1] + 1, n)) + : d3.range(n); + + // Compute the new x-scale. + var x1 = d3.scale.linear() + .domain(domain && domain.call(this, d, i) || [min, max]) + .range([height, 0]); + + // Retrieve the old x-scale, if this is an update. + var x0 = this.__chart__ || d3.scale.linear() + .domain([0, Infinity]) + .range(x1.range()); + + // Stash the new scale. + this.__chart__ = x1; + + // Note: the box, median, and box tick elements are fixed in number, + // so we only have to handle enter and update. In contrast, the outliers + // and other elements are variable, so we need to exit them! Variable + // elements also fade in and out. + + // Update center line: the vertical line spanning the whiskers. + var center = g.selectAll("line.center") + .data(whiskerData ? [whiskerData] : []); + + center.enter().insert("line", "rect") + .attr("class", "center") + .attr("x1", width / 2) + .attr("y1", function(d) { return x0(d[0]); }) + .attr("x2", width / 2) + .attr("y2", function(d) { return x0(d[1]); }) + .style("opacity", 1e-6) + .transition() + .duration(duration) + .style("opacity", 1) + .attr("y1", function(d) { return x1(d[0]); }) + .attr("y2", function(d) { return x1(d[1]); }); + + center.transition() + .duration(duration) + .style("opacity", 1) + .attr("y1", function(d) { return x1(d[0]); }) + .attr("y2", function(d) { return x1(d[1]); }); + + center.exit().transition() + .duration(duration) + .style("opacity", 1e-6) + .attr("y1", function(d) { return x1(d[0]); }) + .attr("y2", function(d) { return x1(d[1]); }) + .remove(); + + // Update innerquartile box. + var box = g.selectAll("rect.box") + .data([quartileData]); + + box.enter().append("rect") + .attr("class", "box") + .attr("x", 0) + .attr("y", function(d) { return x0(d[2]); }) + .attr("width", width) + .attr("height", function(d) { return x0(d[0]) - x0(d[2]); }) + .transition() + .duration(duration) + .attr("y", function(d) { return x1(d[2]); }) + .attr("height", function(d) { return x1(d[0]) - x1(d[2]); }); + + box.transition() + .duration(duration) + .attr("y", function(d) { return x1(d[2]); }) + .attr("height", function(d) { return x1(d[0]) - x1(d[2]); }); + + // Update median line. + var medianLine = g.selectAll("line.median") + .data([quartileData[1]]); + + medianLine.enter().append("line") + .attr("class", "median") + .attr("x1", 0) + .attr("y1", x0) + .attr("x2", width) + .attr("y2", x0) + .transition() + .duration(duration) + .attr("y1", x1) + .attr("y2", x1); + + medianLine.transition() + .duration(duration) + .attr("y1", x1) + .attr("y2", x1); + + // Update whiskers. + var whisker = g.selectAll("line.whisker") + .data(whiskerData || []); + + whisker.enter().insert("line", "circle, text") + .attr("class", "whisker") + .attr("x1", 0) + .attr("y1", x0) + .attr("x2", width) + .attr("y2", x0) + .style("opacity", 1e-6) + .transition() + .duration(duration) + .attr("y1", x1) + .attr("y2", x1) + .style("opacity", 1); + + whisker.transition() + .duration(duration) + .attr("y1", x1) + .attr("y2", x1) + .style("opacity", 1); + + whisker.exit().transition() + .duration(duration) + .attr("y1", x1) + .attr("y2", x1) + .style("opacity", 1e-6) + .remove(); + + // Update outliers. + var outlier = g.selectAll("circle.outlier") + .data(outlierIndices, Number); + + outlier.enter().insert("circle", "text") + .attr("class", "outlier") + .attr("r", 5) + .attr("cx", width / 2) + .attr("cy", function(i) { return x0(d[i]); }) + .style("opacity", 1e-6) + .transition() + .duration(duration) + .attr("cy", function(i) { return x1(d[i]); }) + .style("opacity", 1); + + outlier.transition() + .duration(duration) + .attr("cy", function(i) { return x1(d[i]); }) + .style("opacity", 1); + + outlier.exit().transition() + .duration(duration) + .attr("cy", function(i) { return x1(d[i]); }) + .style("opacity", 1e-6) + .remove(); + + // Compute the tick format. + var format = tickFormat || x1.tickFormat(8); + + // Update box ticks. + var boxTick = g.selectAll("text.box") + .data(quartileData); + + boxTick.enter().append("text") + .attr("class", "box") + .attr("dy", ".3em") + .attr("dx", function(d, i) { return i & 1 ? 6 : -6; }) + .attr("x", function(d, i) { return i & 1 ? width : 0; }) + .attr("y", x0) + .attr("text-anchor", function(d, i) { return i & 1 ? "start" : "end"; }) + .text(format) + .transition() + .duration(duration) + .attr("y", x1); + + boxTick.transition() + .duration(duration) + .text(format) + .attr("y", x1); + + // Update whisker ticks. These are handled separately from the box + // ticks because they may or may not exist, and we want don't want + // to join box ticks pre-transition with whisker ticks post-. + var whiskerTick = g.selectAll("text.whisker") + .data(whiskerData || []); + + whiskerTick.enter().append("text") + .attr("class", "whisker") + .attr("dy", ".3em") + .attr("dx", 6) + .attr("x", width) + .attr("y", x0) + .text(format) + .style("opacity", 1e-6) + .transition() + .duration(duration) + .attr("y", x1) + .style("opacity", 1); + + whiskerTick.transition() + .duration(duration) + .text(format) + .attr("y", x1) + .style("opacity", 1); + + whiskerTick.exit().transition() + .duration(duration) + .attr("y", x1) + .style("opacity", 1e-6) + .remove(); + }); + d3.timer.flush(); + } + + box.width = function(x) { + if (!arguments.length) return width; + width = x; + return box; + }; + + box.height = function(x) { + if (!arguments.length) return height; + height = x; + return box; + }; + + box.tickFormat = function(x) { + if (!arguments.length) return tickFormat; + tickFormat = x; + return box; + }; + + box.duration = function(x) { + if (!arguments.length) return duration; + duration = x; + return box; + }; + + box.domain = function(x) { + if (!arguments.length) return domain; + domain = x == null ? x : d3.functor(x); + return box; + }; + + box.value = function(x) { + if (!arguments.length) return value; + value = x; + return box; + }; + + box.whiskers = function(x) { + if (!arguments.length) return whiskers; + whiskers = x; + return box; + }; + + box.quartiles = function(x) { + if (!arguments.length) return quartiles; + quartiles = x; + return box; + }; + + return box; +}; + +function boxWhiskers(d) { + return [0, d.length - 1]; +} + +function boxQuartiles(d) { + return [ + d3.quantile(d, .25), + d3.quantile(d, .5), + d3.quantile(d, .75) + ]; +} + +})(); + +/** + ## Box Plot + + Includes: [Coordinate Grid Mixin](#coordinate-grid-mixin) + + A box plot is a chart that depicts numerical data via their quartile ranges. + + #### dc.boxPlot(parent[, chartGroup]) + Create a box plot instance and attach it to the given parent element. + + Parameters: + * parent : string | node | selection - any valid + [d3 single selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) representing + a dom block element such as a div; or a dom element or d3 selection. +* chartGroup : string (optional) - name of the chart group this chart instance should be placed in. + Interaction with a chart will only trigger events and redraws within the chart's group. + + Returns: + A newly created box plot instance + + ```js + // create a box plot under #chart-container1 element using the default global chart group + var boxPlot1 = dc.boxPlot("#chart-container1"); + // create a box plot under #chart-container2 element using chart group A + var boxPlot2 = dc.boxPlot("#chart-container2", "chartGroupA"); + ``` + + **/ +dc.boxPlot = function (parent, chartGroup) { + var _chart = dc.coordinateGridMixin({}); + + var _whisker_iqr_factor = 1.5; + var _whiskers_iqr = default_whiskers_iqr; + var _whiskers = _whiskers_iqr(_whisker_iqr_factor); + + var _box = d3.box(); + var _tickFormat = null; + + var _boxWidth = function (innerChartWidth, xUnits) { + if (_chart.isOrdinal()) + return _chart.x().rangeBand(); + else + return innerChartWidth / (1 + _chart.boxPadding()) / xUnits; + }; + + // default padding to handle min/max whisker text + _chart.yAxisPadding(12); + + // default to ordinal + _chart.x(d3.scale.ordinal()); + _chart.xUnits(dc.units.ordinal); + + // valueAccessor should return an array of values that can be coerced into numbers + // or if data is overloaded for a static array of arrays, it should be `Number`. + // Empty arrays are not included. + _chart.data(function(group) { + return group.all().map(function (d) { + d.map = function(accessor) { return accessor.call(d,d); }; + return d; + }).filter(function (d) { + var values = _chart.valueAccessor()(d); + return values.length !== 0; + }); + }); + + /** + #### .boxPadding([padding]) + Get or set the spacing between boxes as a fraction of box size. Valid values are within 0-1. + See the [d3 docs](https://github.com/mbostock/d3/wiki/Ordinal-Scales#wiki-ordinal_rangeBands) + for a visual description of how the padding is applied. + + Default: 0.8 + **/ + _chart.boxPadding = _chart._rangeBandPadding; + _chart.boxPadding(0.8); + + /** + #### .outerPadding([padding]) + Get or set the outer padding on an ordinal box chart. This setting has no effect on non-ordinal charts + or on charts with a custom `.boxWidth`. Will pad the width by `padding * barWidth` on each side of the chart. + + Default: 0.5 + **/ + _chart.outerPadding = _chart._outerRangeBandPadding; + _chart.outerPadding(0.5); + + /** + #### .boxWidth(width || function(innerChartWidth, xUnits) { ... }) + Get or set the numerical width of the boxplot box. The width may also be a function taking as + parameters the chart width excluding the right and left margins, as well as the number of x + units. + **/ + _chart.boxWidth = function(_) { + if (!arguments.length) return _boxWidth; + _boxWidth = d3.functor(_); + return _chart; + }; + + var boxTransform = function (d, i) { + var xOffset = _chart.x()(_chart.keyAccessor()(d,i)); + return "translate(" + xOffset + ",0)"; + }; + + _chart._preprocessData = function () { + if (_chart.elasticX()) { + _chart.x().domain([]); + } + }; + + _chart.plotData = function () { + var _calculatedBoxWidth = _boxWidth(_chart.effectiveWidth(), _chart.xUnitCount()); + + _box.whiskers(_whiskers) + .width(_calculatedBoxWidth) + .height(_chart.effectiveHeight()) + .value(_chart.valueAccessor()) + .domain(_chart.y().domain()) + .duration(_chart.transitionDuration()) + .tickFormat(_tickFormat); + + var boxesG = _chart.chartBodyG().selectAll('g.box').data(_chart.data(), function (d) { return d.key; }); + + renderBoxes(boxesG); + updateBoxes(boxesG); + removeBoxes(boxesG); + + _chart.fadeDeselectedArea(); + }; + + function renderBoxes(boxesG) { + var boxesGEnter = boxesG.enter().append("g"); + + boxesGEnter + .attr("class", "box") + .attr("transform", boxTransform) + .call(_box) + .on("click", function(d) { + _chart.filter(d.key); + _chart.redrawGroup(); + }); + } + + function updateBoxes(boxesG) { + dc.transition(boxesG, _chart.transitionDuration()) + .attr("transform", boxTransform) + .call(_box) + .each(function() { + d3.select(this).select('rect.box').attr("fill", _chart.getColor); + }); + } + + function removeBoxes(boxesG) { + boxesG.exit().remove().call(_box); + } + + _chart.fadeDeselectedArea = function () { + if (_chart.hasFilter()) { + _chart.g().selectAll("g.box").each(function (d) { + if (_chart.isSelectedNode(d)) { + _chart.highlightSelected(this); + } else { + _chart.fadeDeselected(this); + } + }); + } else { + _chart.g().selectAll("g.box").each(function () { + _chart.resetHighlight(this); + }); + } + }; + + _chart.isSelectedNode = function (d) { + return _chart.hasFilter(d.key); + }; + + _chart.yAxisMin = function () { + var min = d3.min(_chart.data(), function (e) { + return d3.min(_chart.valueAccessor()(e)); + }); + return dc.utils.subtract(min, _chart.yAxisPadding()); + }; + + _chart.yAxisMax = function () { + var max = d3.max(_chart.data(), function (e) { + return d3.max(_chart.valueAccessor()(e)); + }); + return dc.utils.add(max, _chart.yAxisPadding()); + }; + + /** + #### .tickFormat() + Set the numerical format of the boxplot median, whiskers and quartile labels. Defaults to + integer formatting. + ```js + // format ticks to 2 decimal places + chart.tickFormat(d3.format(".2f")); + ``` + **/ + _chart.tickFormat = function(x) { + if (!arguments.length) return _tickFormat; + _tickFormat = x; + return _chart; + }; + + // Returns a function to compute the interquartile range. + function default_whiskers_iqr(k) { + return function (d) { + var q1 = d.quartiles[0], + q3 = d.quartiles[2], + iqr = (q3 - q1) * k, + i = -1, + j = d.length; + while (d[++i] < q1 - iqr); + while (d[--j] > q3 + iqr); + return [i, j]; + }; + } + + return _chart.anchor(parent, chartGroup); +}; + +// Renamed functions + +dc.abstractBubbleChart = dc.bubbleMixin; +dc.baseChart = dc.baseMixin; +dc.capped = dc.capMixin; +dc.colorChart = dc.colorMixin; +dc.coordinateGridChart = dc.coordinateGridMixin; +dc.marginable = dc.marginMixin; +dc.stackableChart = dc.stackMixin; + +return dc;} +if(typeof define === "function" && define.amd) { + define(["d3"], _dc); +} else if(typeof module === "object" && module.exports) { + module.exports = _dc(d3); +} else { + this.dc = _dc(d3); +} +} +)(); diff --git a/ajax/libs/dc/2.0.0-alpha.2/dc.min.js b/ajax/libs/dc/2.0.0-alpha.2/dc.min.js new file mode 100644 index 000000000..23e89516b --- /dev/null +++ b/ajax/libs/dc/2.0.0-alpha.2/dc.min.js @@ -0,0 +1,4 @@ +!function(){function a(a){"use strict";var b={version:"2.0.0-alpha.2",constants:{CHART_CLASS:"dc-chart",DEBUG_GROUP_CLASS:"debug",STACK_CLASS:"stack",DESELECTED_CLASS:"deselected",SELECTED_CLASS:"selected",NODE_INDEX_NAME:"__index__",GROUP_INDEX_NAME:"__group_index__",DEFAULT_CHART_GROUP:"__default_chart_group__",EVENT_DELAY:40,NEGLIGIBLE_NUMBER:1e-10},_renderlet:null};b.chartRegistry=function(){function a(a){return a||(a=b.constants.DEFAULT_CHART_GROUP),c[a]||(c[a]=[]),a}var c={};return{has:function(a){for(var b in c)if(c[b].indexOf(a)>=0)return!0;return!1},register:function(b,d){d=a(d),c[d].push(b)},deregister:function(b,d){d=a(d);for(var e=0;e=c||void 0===c||b.disableTransitions)return a;var e=a.transition().duration(c);return"function"==typeof d&&d(e),e},b.units={},b.units.integers=function(a,b){return Math.abs(b-a)},b.units.ordinal=function(a,b,c){return c},b.units.fp={},b.units.fp.precision=function(a){var c=function(a,d){var e=Math.abs((d-a)/c.resolution);return b.utils.isNegligible(e-Math.floor(e))?Math.floor(e):Math.ceil(e)};return c.resolution=a,c},b.round={},b.round.floor=function(a){return Math.floor(a)},b.round.ceil=function(a){return Math.ceil(a)},b.round.round=function(a){return Math.round(a)},b.override=function(a,b,c){var d=a[b];a["_"+b]=d,a[b]=c},b.renderlet=function(a){return arguments.length?(b._renderlet=a,b):b._renderlet},b.instanceOfChart=function(a){return a instanceof Object&&a.__dc_flag__&&!0},b.errors={},b.errors.Exception=function(a){var b=a||"Unexpected internal error";this.message=b,this.toString=function(){return b}},b.errors.InvalidStateException=function(){b.errors.Exception.apply(this,arguments)},b.dateFormat=a.time.format("%m/%d/%Y"),b.printers={},b.printers.filters=function(a){for(var c="",d=0;d0&&(c+=", "),c+=b.printers.filter(a[d]);return c},b.printers.filter=function(a){var c="";return a&&(a instanceof Array?a.length>=2?c="["+b.utils.printSingleValue(a[0])+" -> "+b.utils.printSingleValue(a[1])+"]":a.length>=1&&(c=b.utils.printSingleValue(a[0])):c=b.utils.printSingleValue(a)),c},b.pluck=function(a,b){return b?function(c,d){return b.call(c,c[a],d)}:function(b){return b[a]}},b.utils={},b.utils.printSingleValue=function(a){var c=""+a;return a instanceof Date?c=b.dateFormat(a):"string"==typeof a?c=a:b.utils.isFloat(a)?c=b.utils.printSingleValue.fformat(a):b.utils.isInteger(a)&&(c=Math.round(a)),c},b.utils.printSingleValue.fformat=a.format(".2f"),b.utils.add=function(a,b){if("string"==typeof b&&(b=b.replace("%","")),a instanceof Date){"string"==typeof b&&(b=+b);var c=new Date;return c.setTime(a.getTime()),c.setDate(a.getDate()+b),c}if("string"==typeof b){var d=+b/100;return a>0?a*(1+d):a*(1-d)}return a+b},b.utils.subtract=function(a,b){if("string"==typeof b&&(b=b.replace("%","")),a instanceof Date){"string"==typeof b&&(b=+b);var c=new Date;return c.setTime(a.getTime()),c.setDate(a.getDate()-b),c}if("string"==typeof b){var d=+b/100;return 0>a?a*(1+d):a*(1-d)}return a-b},b.utils.isNumber=function(a){return a===+a},b.utils.isFloat=function(a){return a===+a&&a!==(0|a)},b.utils.isInteger=function(a){return a===+a&&a===(0|a)},b.utils.isNegligible=function(a){return!b.utils.isNumber(a)||a-b.constants.NEGLIGIBLE_NUMBER},b.utils.clamp=function(a,b,c){return b>a?b:a>c?c:a};var c=0;return b.utils.uniqueId=function(){return++c},b.utils.nameToId=function(a){return a.toLowerCase().replace(/[\s]/g,"_").replace(/[\.']/g,"")},b.utils.appendOrSelect=function(a,b){var c=a.select(b);return c.empty()&&(c=a.append(b)),c},b.utils.safeNumber=function(a){return b.utils.isNumber(+a)?+a:0},b.logger={},b.logger.enableDebugLog=!1,b.logger.warn=function(a){return console&&(console.warn?console.warn(a):console.log&&console.log(a)),b.logger},b.logger.debug=function(a){return b.logger.enableDebugLog&&console&&(console.debug?console.debug(a):console.log&&console.log(a)),b.logger},b.events={current:null},b.events.trigger=function(a,c){return c?(b.events.current=a,void setTimeout(function(){a==b.events.current&&a()},c)):void a()},b.filters={},b.filters.RangedFilter=function(a,b){var c=Array(a,b);return c.isFiltered=function(a){return a>=this[0]&&a=b[0][0]&&c=b[0][1]&&d=a){K.splice(b,1);break}i(),c._invokeFilteredListener(a)}function g(a){K.push(a),i(),c._invokeFilteredListener(a)}function h(){K=[],i(),c._invokeFilteredListener(null)}function i(){if(c.dimension()&&c.dimension().filter){var a=L(c.dimension(),K);K=a?a:K}}function j(){for(var a=0;ar?b:r},t=s,u=200,v=function(a){var b=a&&a.getBoundingClientRect&&a.getBoundingClientRect().height;return b&&b>u?b:u},w=v,x=b.pluck("key"),y=b.pluck("value"),z=b.pluck("key"),A=b.pluck("key"),B=!1,C=function(a){return c.keyAccessor()(a)+": "+c.valueAccessor()(a)},D=!1,E=750,F=b.printers.filters,G=[],H=["dimension","group"],I=b.constants.DEFAULT_CHART_GROUP,J=a.dispatch("preRender","postRender","preRedraw","postRedraw","filtered","zoomed"),K=[],L=function(a,b){return a.filter(null),0===b.length?a.filter(null):a.filterFunction(function(a){for(var c=0;c=d&&d>=a)return!0}return!1}),b},M=function(a){return a.all()};return c.width=function(b){return arguments.length?(t=a.functor(b||s),c):t(n.node())},c.height=function(b){return arguments.length?(w=a.functor(b||v),c):w(n.node())},c.minWidth=function(a){return arguments.length?(r=a,c):r},c.minHeight=function(a){return arguments.length?(u=a,c):u},c.dimension=function(a){return arguments.length?(k=a,c.expireCache(),c):k},c.data=function(b){return arguments.length?(M=a.functor(b),c.expireCache(),c):M.call(c,l)},c.group=function(a,b){return arguments.length?(l=a,c._groupName=b,c.expireCache(),c):l},c.ordering=function(a){return arguments.length?(A=a,p=crossfilter.quicksort.by(A),c.expireCache(),c):A},c._computeOrderedGroups=function(a){var b=a.slice(0);return b.length<=1?b:(p||(p=crossfilter.quicksort.by(A)),p(b,0,b.length))},c.filterAll=function(){return c.filter(null)},c.select=function(a){return n.select(a)},c.selectAll=function(a){return n?n.selectAll(a):null},c.anchor=function(d,e){return arguments.length?(b.instanceOfChart(d)?(m=d.anchor(),n=d.root()):(m=d,n=a.select(m),n.classed(b.constants.CHART_CLASS,!0),b.registerChart(c,e)),I=e,c):m},c.anchorName=function(){var a=c.anchor();return a&&a.id?a.id:a&&a.replace?a.replace("#",""):""+c.chartID()},c.root=function(a){return arguments.length?(n=a,c):n},c.svg=function(a){return arguments.length?(o=a,c):o},c.resetSvg=function(){return c.select("svg").remove(),d()},c.filterPrinter=function(a){return arguments.length?(F=a,c):F},c.turnOnControls=function(){return n&&(c.selectAll(".reset").style("display",null),c.selectAll(".filter").text(F(c.filters())).style("display",null)),c},c.turnOffControls=function(){return n&&(c.selectAll(".reset").style("display","none"),c.selectAll(".filter").style("display","none").text(c.filter())),c},c.transitionDuration=function(a){return arguments.length?(E=a,c):E},c._mandatoryAttributes=function(a){return arguments.length?(H=a,c):H},c.render=function(){J.preRender(c),H&&H.forEach(e);var a=c._doRender();return q&&q.render(),c._activateRenderlets("postRender"),a},c._activateRenderlets=function(a){c.transitionDuration()>0&&o?o.transition().duration(c.transitionDuration()).each("end",function(){j(),a&&J[a](c)}):(j(),a&&J[a](c))},c.redraw=function(){J.preRedraw(c);var a=c._doRedraw();return q&&q.render(),c._activateRenderlets("postRedraw"),a},c.redrawGroup=function(){b.redrawAll(c.chartGroup())},c.renderGroup=function(){b.renderAll(c.chartGroup())},c._invokeFilteredListener=function(a){void 0!==a&&J.filtered(c,a)},c._invokeZoomedListener=function(){J.zoomed(c)},c.hasFilter=function(a){return arguments.length?K.some(function(b){return b>=a&&a>=b}):K.length>0},c.replaceFilter=function(a){K=[],c.filter(a)},c.filter=function(a){return arguments.length?(a instanceof Array&&a[0]instanceof Array&&!a.isFiltered?(a[0].forEach(function(a){c.hasFilter(a)?K.splice(K.indexOf(a),1):K.push(a)}),i(),c._invokeFilteredListener(a)):null===a?h():c.hasFilter(a)?f(a):g(a),null!==n&&c.hasFilter()?c.turnOnControls():c.turnOffControls(),c):K.length>0?K[0]:null},c.filters=function(){return K},c.highlightSelected=function(c){a.select(c).classed(b.constants.SELECTED_CLASS,!0),a.select(c).classed(b.constants.DESELECTED_CLASS,!1)},c.fadeDeselected=function(c){a.select(c).classed(b.constants.SELECTED_CLASS,!1),a.select(c).classed(b.constants.DESELECTED_CLASS,!0)},c.resetHighlight=function(c){a.select(c).classed(b.constants.SELECTED_CLASS,!1),a.select(c).classed(b.constants.DESELECTED_CLASS,!1)},c.onClick=function(a){var d=c.keyAccessor()(a);b.events.trigger(function(){c.filter(d),c.redrawGroup()})},c.filterHandler=function(a){return arguments.length?(L=a,c):L},c._doRender=function(){return c},c._doRedraw=function(){return c},c.legendables=function(){return[]},c.legendHighlight=function(){},c.legendReset=function(){},c.legendToggle=function(){},c.isLegendableHidden=function(){return!1},c.keyAccessor=function(a){return arguments.length?(x=a,c):x},c.valueAccessor=function(a){return arguments.length?(y=a,c):y},c.label=function(a){return arguments.length?(z=a,B=!0,c):z},c.renderLabel=function(a){return arguments.length?(B=a,c):B},c.title=function(a){return arguments.length?(C=a,D=!0,c):C},c.renderTitle=function(a){return arguments.length?(D=a,c):D},c.renderlet=function(a){return G.push(a),c},c.chartGroup=function(a){return arguments.length?(I=a,c):I},c.expireCache=function(){return c},c.legend=function(a){return arguments.length?(q=a,q.parent(c),c):q},c.chartID=function(){return c.__dc_flag__},c.options=function(a){for(var d in a)"function"==typeof c[d]?c[d].call(c,a[d]):b.logger.debug("Not a valid option setter name: "+d);return c},c.on=function(a,b){return J.on(a,b),c},c},b.marginMixin=function(a){var b={top:10,right:50,bottom:30,left:30};return a.margins=function(c){return arguments.length?(b=c,a):b},a.effectiveWidth=function(){return a.width()-a.margins().left-a.margins().right},a.effectiveHeight=function(){return a.height()-a.margins().top-a.margins().bottom},a},b.colorMixin=function(b){var c=a.scale.category20c(),d=!0,e=function(a){return b.keyAccessor()(a)};return b.colors=function(d){return arguments.length?(c=d instanceof Array?a.scale.quantize().range(d):a.functor(d),b):c},b.ordinalColors=function(c){return b.colors(a.scale.ordinal().range(c))},b.linearColors=function(c){return b.colors(a.scale.linear().range(c).interpolate(a.interpolateHcl))},b.colorAccessor=function(a){return arguments.length?(e=a,d=!1,b):e},b.defaultColorAccessor=function(){return d},b.colorDomain=function(a){return arguments.length?(c.domain(a),b):c.domain()},b.calculateColorDomain=function(){var d=[a.min(b.data(),b.colorAccessor()),a.max(b.data(),b.colorAccessor())];c.domain(d)},b.getColor=function(a,b){return c(e.call(this,a,b))},b.colorCalculator=function(a){return arguments.length?(b.getColor=a,b):b.getColor},b},b.coordinateGridMixin=function(c){function d(a){c.isOrdinal()?(c.elasticX()||0===x.domain().length)&&x.domain(c._ordinalXDomain()):c.elasticX()&&x.domain([c.xAxisMin(),c.xAxisMax()]);var b=x.domain();(!A||b.some(function(a,b){return a!=A[b]}))&&c.rescale(),A=b,c.isOrdinal()?x.rangeBands([0,c.xAxisLength()],bb,c._useOuterPadding()?ab:0):x.range([0,c.xAxisLength()]),H=H.scale(c.x()),e(a)}function e(a){var d=a.selectAll("g."+q);if(T){d.empty()&&(d=a.insert("g",":first-child").attr("class",o+" "+q).attr("transform","translate("+c.margins().left+","+c.margins().top+")"));var e=H.tickValues()?H.tickValues():x.ticks(H.ticks()[0]),f=d.selectAll("line").data(e),g=f.enter().append("line").attr("x1",function(a){return x(a)}).attr("y1",c._xAxisY()-c.margins().top).attr("x2",function(a){return x(a)}).attr("y2",0).attr("opacity",0);b.transition(g,c.transitionDuration()).attr("opacity",1),b.transition(f,c.transitionDuration()).attr("x1",function(a){return x(a)}).attr("y1",c._xAxisY()-c.margins().top).attr("x2",function(a){return x(a)}).attr("y2",0),f.exit().remove()}else d.selectAll("line").remove()}function f(){return c._xAxisY()-c.margins().top}function g(){return c.anchorName()+"-clip"}function h(){var a=b.utils.appendOrSelect(u,"defs"),d=b.utils.appendOrSelect(a,"clipPath").attr("id",g()),e=2*_;b.utils.appendOrSelect(d,"rect").attr("width",c.xAxisLength()+e).attr("height",c.yAxisHeight()+e)}function i(a){c.isOrdinal()&&(R=!1),d(c.g()),c._prepareYAxis(c.g()),c.plotData(),(c.elasticX()||U||a)&&c.renderXAxis(c.g()),(c.elasticY()||a)&&c.renderYAxis(c.g()),a?c.renderBrush(c.g()):c.redrawBrush(c.g())}function j(){$?c._enableMouseZoom():Z&&c._disableMouseZoom()}function k(){U=!0,W&&(c.x().domain(l(c.x().domain(),y)),F&&c.x().domain(l(c.x().domain(),F.x().domain())));var a=c.x().domain(),d=b.filters.RangedFilter(a[0],a[1]);c.replaceFilter(d),c.rescale(),c.redraw(),F&&!m(c.filter(),F.filter())&&b.events.trigger(function(){F.replaceFilter(d),F.redraw()}),c._invokeZoomedListener(),b.events.trigger(function(){c.redrawGroup()},b.constants.EVENT_DELAY),U=!m(a,y)}function l(b,c){var d=[];return d[0]=a.max([b[0],c[0]]),d[1]=a.min([b[1],c[1]]),d}function m(a,b){return a||b?a&&b?0===a.length&&0===b.length?!0:a[0].valueOf()===b[0].valueOf()&&a[1].valueOf()===b[1].valueOf()?!0:!1:!1:!0}function n(a){return a instanceof Array&&a.length>1}var o="grid-line",p="horizontal",q="vertical",r="y-axis-label",s="x-axis-label",t=12;c=b.colorMixin(b.marginMixin(b.baseMixin(c))),c.colors(a.scale.category10()),c._mandatoryAttributes().push("x");var u,v,w,x,y,z,A,B,C,D,E,F,G,H=a.svg.axis().orient("bottom"),I=b.units.integers,J=0,K=!1,L=0,M=a.svg.axis().orient("left"),N=0,O=!1,P=0,Q=a.svg.brush(),R=!0,S=!1,T=!1,U=!1,V=[1,1/0],W=!0,X=a.behavior.zoom().on("zoom",k),Y=a.behavior.zoom().on("zoom",null),Z=!1,$=!1,_=0,ab=.5,bb=0,cb=!1;return c.rescale=function(){E=void 0},c.rangeChart=function(a){return arguments.length?(F=a,F.focusChart(c),c):F},c.zoomScale=function(a){return arguments.length?(V=a,c):V},c.zoomOutRestrict=function(a){return arguments.length?(V[0]=a?1:0,W=a,c):W},c._generateG=function(a){return u=void 0===a?c.svg():a,v=u.append("g"),w=v.append("g").attr("class","chart-body").attr("transform","translate("+c.margins().left+", "+c.margins().top+")").attr("clip-path","url(#"+g()+")"),v},c.g=function(a){return arguments.length?(v=a,c):v},c.mouseZoomable=function(a){return arguments.length?($=a,c):$},c.chartBodyG=function(a){return arguments.length?(w=a,c):w},c.x=function(a){return arguments.length?(x=a,y=x.domain(),c):x},c.xOriginalDomain=function(){return y},c.xUnits=function(a){return arguments.length?(I=a,c):I},c.xAxis=function(a){return arguments.length?(H=a,c):H},c.elasticX=function(a){return arguments.length?(K=a,c):K},c.xAxisPadding=function(a){return arguments.length?(J=a,c):J},c.xUnitCount=function(){if(void 0===E){var a=c.xUnits()(c.x().domain()[0],c.x().domain()[1],c.x().domain());E=a instanceof Array?a.length:a}return E},c.useRightYAxis=function(a){return arguments.length?(cb=a,c):cb},c.isOrdinal=function(){return c.xUnits()===b.units.ordinal},c._useOuterPadding=function(){return!0},c._ordinalXDomain=function(){var a=c._computeOrderedGroups(c.data());return a.map(c.keyAccessor())},c.renderXAxis=function(a){var d=a.selectAll("g.x");d.empty()&&(d=a.append("g").attr("class","axis x").attr("transform","translate("+c.margins().left+","+c._xAxisY()+")"));var e=a.selectAll("text."+s);e.empty()&&c.xAxisLabel()&&(e=a.append("text").attr("transform","translate("+(c.margins().left+c.xAxisLength()/2)+","+(c.height()-L)+")").attr("class",s).attr("text-anchor","middle").text(c.xAxisLabel())),c.xAxisLabel()&&e.text()!=c.xAxisLabel()&&e.text(c.xAxisLabel()),b.transition(d,c.transitionDuration()).call(H)},c._xAxisY=function(){return c.height()-c.margins().bottom},c.xAxisLength=function(){return c.effectiveWidth()},c.xAxisLabel=function(a,b){return arguments.length?(z=a,c.margins().bottom-=L,L=void 0===b?t:b,c.margins().bottom+=L,c):z},c._prepareYAxis=function(b){if(void 0===B||c.elasticY()){B=a.scale.linear();var d=c.yAxisMin()||0,e=c.yAxisMax()||0;B.domain([d,e]).rangeRound([c.yAxisHeight(),0])}B.range([c.yAxisHeight(),0]),M=M.scale(B),cb&&M.orient("right"),c._renderHorizontalGridLinesForAxis(b,B,M)},c.renderYAxisLabel=function(a,b,d,e){e=e||P;var f=c.g().selectAll("text."+r+"."+a+"-label");if(f.empty()&&b){var g=c.margins().top+c.yAxisHeight()/2;f=c.g().append("text").attr("transform","translate("+e+","+g+"),rotate("+d+")").attr("class",r+" "+a+"-label").attr("text-anchor","middle").text(b)}b&&f.text()!=b&&f.text(b)},c.renderYAxisAt=function(a,d,e){var f=c.g().selectAll("g."+a);f.empty()&&(f=c.g().append("g").attr("class","axis "+a).attr("transform","translate("+e+","+c.margins().top+")")),b.transition(f,c.transitionDuration()).call(d)},c.renderYAxis=function(){var a=cb?c.width()-c.margins().right:c._yAxisX();c.renderYAxisAt("y",M,a);var b=cb?c.width()-P:P,d=cb?90:-90;c.renderYAxisLabel("y",c.yAxisLabel(),d,b)},c._renderHorizontalGridLinesForAxis=function(a,d,e){var f=a.selectAll("g."+p);if(S){var g=e.tickValues()?e.tickValues():d.ticks(e.ticks()[0]);f.empty()&&(f=a.insert("g",":first-child").attr("class",o+" "+p).attr("transform","translate("+c.margins().left+","+c.margins().top+")"));var h=f.selectAll("line").data(g),i=h.enter().append("line").attr("x1",1).attr("y1",function(a){return d(a)}).attr("x2",c.xAxisLength()).attr("y2",function(a){return d(a)}).attr("opacity",0);b.transition(i,c.transitionDuration()).attr("opacity",1),b.transition(h,c.transitionDuration()).attr("x1",1).attr("y1",function(a){return d(a)}).attr("x2",c.xAxisLength()).attr("y2",function(a){return d(a)}),h.exit().remove()}else f.selectAll("line").remove()},c._yAxisX=function(){return c.useRightYAxis()?c.width()-c.margins().right:c.margins().left},c.yAxisLabel=function(a,b){return arguments.length?(C=a,c.margins().left-=P,P=void 0===b?t:b,c.margins().left+=P,c):C},c.y=function(a){return arguments.length?(B=a,c):B},c.yAxis=function(a){return arguments.length?(M=a,c):M},c.elasticY=function(a){return arguments.length?(O=a,c):O},c.renderHorizontalGridLines=function(a){return arguments.length?(S=a,c):S},c.renderVerticalGridLines=function(a){return arguments.length?(T=a,c):T},c.xAxisMin=function(){var d=a.min(c.data(),function(a){return c.keyAccessor()(a)});return b.utils.subtract(d,J)},c.xAxisMax=function(){var d=a.max(c.data(),function(a){return c.keyAccessor()(a)});return b.utils.add(d,J)},c.yAxisMin=function(){var d=a.min(c.data(),function(a){return c.valueAccessor()(a)});return b.utils.subtract(d,N)},c.yAxisMax=function(){var d=a.max(c.data(),function(a){return c.valueAccessor()(a)});return b.utils.add(d,N)},c.yAxisPadding=function(a){return arguments.length?(N=a,c):N},c.yAxisHeight=function(){return c.effectiveHeight()},c.round=function(a){return arguments.length?(D=a,c):D},c._rangeBandPadding=function(a){return arguments.length?(bb=a,c):bb},c._outerRangeBandPadding=function(a){return arguments.length?(ab=a,c):ab},b.override(c,"filter",function(a){return arguments.length?(c._filter(a),a?c.brush().extent(a):c.brush().clear(),c):c._filter()}),c.brush=function(a){return arguments.length?(Q=a,c):Q},c.renderBrush=function(a){if(R){Q.on("brush",c._brushing),Q.on("brushstart",c._disableMouseZoom),Q.on("brushend",j);var b=a.append("g").attr("class","brush").attr("transform","translate("+c.margins().left+","+c.margins().top+")").call(Q.x(c.x()));c.setBrushY(b),c.setHandlePaths(b),c.hasFilter()&&c.redrawBrush(a)}},c.setHandlePaths=function(a){a.selectAll(".resize").append("path").attr("d",c.resizeHandlePath)},c.setBrushY=function(a){a.selectAll("rect").attr("height",f())},c.extendBrush=function(){var a=Q.extent();return c.round()&&(a[0]=a.map(c.round())[0],a[1]=a.map(c.round())[1],v.select(".brush").call(Q.extent(a))),a},c.brushIsEmpty=function(a){return Q.empty()||!a||a[1]<=a[0]},c._brushing=function(){var a=c.extendBrush();if(c.redrawBrush(v),c.brushIsEmpty(a))b.events.trigger(function(){c.filter(null),c.redrawGroup()},b.constants.EVENT_DELAY);else{var d=b.filters.RangedFilter(a[0],a[1]);b.events.trigger(function(){c.replaceFilter(d),c.redrawGroup()},b.constants.EVENT_DELAY)}},c.redrawBrush=function(a){if(R){c.filter()&&c.brush().empty()&&c.brush().extent(c.filter());var b=a.select("g.brush");b.call(c.brush().x(c.x())),c.setBrushY(b)}c.fadeDeselectedArea()},c.fadeDeselectedArea=function(){},c.resizeHandlePath=function(a){var b=+("e"==a),c=b?1:-1,d=f()/3;return"M"+.5*c+","+d+"A6,6 0 0 "+b+" "+6.5*c+","+(d+6)+"V"+(2*d-6)+"A6,6 0 0 "+b+" "+.5*c+","+2*d+"ZM"+2.5*c+","+(d+8)+"V"+(2*d-8)+"M"+4.5*c+","+(d+8)+"V"+(2*d-8)},c.clipPadding=function(a){return arguments.length?(_=a,c):_},c._preprocessData=function(){},c._doRender=function(){return c.resetSvg(),c._preprocessData(),c._generateG(),h(),i(!0),j(),c},c._doRedraw=function(){return c._preprocessData(),i(!1),c},c._enableMouseZoom=function(){Z=!0,X.x(c.x()).scaleExtent(V).size([c.width(),c.height()]),c.root().call(X)},c._disableMouseZoom=function(){c.root().call(Y)},c.focus=function(a){c.x().domain(n(a)?a:y),X.x(c.x()),k()},c.refocused=function(){return U},c.focusChart=function(a){return arguments.length?(G=a,c.on("filtered",function(a){a.filter()?m(a.filter(),G.filter())||b.events.trigger(function(){G.focus(a.filter())}):b.events.trigger(function(){G.x().domain(G.xOriginalDomain())})}),c):G},c.brushOn=function(a){return arguments.length?(R=a,c):R},c},b.stackMixin=function(c){function d(a,b){var d=a.accessor||c.valueAccessor();return a.name=String(a.name||b),a.values=a.group.all().map(function(b,e){return{x:c.keyAccessor()(b,e),y:a.hidden?null:d(b,e),data:b,layer:a.name,hidden:a.hidden}}),a.values=a.values.filter(e()),a.values}function e(){if(!c.x())return a.functor(!0);var b=c.x().domain();return c.isOrdinal()?function(){return!0}:c.elasticX()?function(){return!0}:function(a){return a.x>=b[0]&&a.x<=b[b.length-1]}}function f(a){var c=j.map(b.pluck("name")).indexOf(a);return j[c]}function g(){return c.data().reduce(function(a,b){return a.concat(b.values)},[])}function h(a){return!a.hidden}var i=a.layout.stack().values(d),j=[],k={},l=!1;return c.stack=function(a,b,d){if(!arguments.length)return j;arguments.length<=2&&(d=b);var e={group:a};return"string"==typeof b&&(e.name=b),"function"==typeof d&&(e.accessor=d),j.push(e),c},b.override(c,"group",function(a,b,d){return arguments.length?(j=[],k={},c.stack(a,b),d&&c.valueAccessor(d),c._group(a,b)):c._group()}),c.hidableStacks=function(a){return arguments.length?(l=a,c):l},c.hideStack=function(a){var b=f(a);return b&&(b.hidden=!0),c},c.showStack=function(a){var b=f(a);return b&&(b.hidden=!1),c},c.getValueAccessorByIndex=function(a){return j[a].accessor||c.valueAccessor()},c.yAxisMin=function(){var d=a.min(g(),function(a){return a.y+a.y0d?b.concat([{others:k,key:e,value:g-d}]):b};return c.cappedKeyAccessor=function(a,b){return a.others?a.key:c.keyAccessor()(a,b)},c.cappedValueAccessor=function(a,b){return a.others?a.value:c.valueAccessor()(a,b)},c.data(function(a){if(1/0==d)return c._computeOrderedGroups(a.all());var b=a.top(d);return b=c._computeOrderedGroups(b),f?f(b):b}),c.cap=function(a){return arguments.length?(d=a,c):d},c.othersLabel=function(a){return arguments.length?(e=a,c):e},c.othersGrouper=function(a){return arguments.length?(f=a,c):f},b.override(c,"onClick",function(a){a.others&&c.filter([a.others]),c._onClick(a)}),c},b.bubbleMixin=function(c){var d=.3,e=10;c.BUBBLE_NODE_CLASS="node",c.BUBBLE_CLASS="bubble",c.MIN_RADIUS=10,c=b.colorMixin(c),c.renderLabel(!0),c.renderTitle(!1),c.data(function(a){return a.top(1/0)});var f=a.scale.linear().domain([0,100]),g=function(a){return a.r};c.r=function(a){return arguments.length?(f=a,c):f},c.radiusValueAccessor=function(a){return arguments.length?(g=a,c):g},c.rMin=function(){var b=a.min(c.data(),function(a){return c.radiusValueAccessor()(a)});return b},c.rMax=function(){var b=a.max(c.data(),function(a){return c.radiusValueAccessor()(a)});return b},c.bubbleR=function(a){var b=c.radiusValueAccessor()(a),d=c.r()(b);return(isNaN(d)||0>=b)&&(d=0),d};var h=function(a){return c.label()(a)},i=function(a){return c.bubbleR(a)>e?1:0};c._doRenderLabel=function(a){if(c.renderLabel()){var d=a.select("text");d.empty()&&(d=a.append("text").attr("text-anchor","middle").attr("dy",".3em").on("click",c.onClick)),d.attr("opacity",0).text(h),b.transition(d,c.transitionDuration()).attr("opacity",i)}},c.doUpdateLabels=function(a){if(c.renderLabel()){var d=a.selectAll("text").text(h);b.transition(d,c.transitionDuration()).attr("opacity",i)}};var j=function(a){return c.title()(a)};return c._doRenderTitles=function(a){if(c.renderTitle()){var b=a.select("title");b.empty()&&a.append("title").text(j)}},c.doUpdateTitles=function(a){c.renderTitle()&&a.selectAll("title").text(j)},c.minRadiusWithLabel=function(a){return arguments.length?(e=a,c):e},c.maxBubbleRelativeSize=function(a){return arguments.length?(d=a,c):d},c.fadeDeselectedArea=function(){c.selectAll("g."+c.BUBBLE_NODE_CLASS).each(c.hasFilter()?function(a){c.isSelectedNode(a)?c.highlightSelected(this):c.fadeDeselected(this)}:function(){c.resetHighlight(this)})},c.isSelectedNode=function(a){return c.hasFilter(a.key)},c.onClick=function(a){var d=a.key;b.events.trigger(function(){c.filter(d),c.redrawGroup()})},c},b.pieChart=function(c,d){function e(){D=D?D:a.min([O.width(),O.height()])/2;var b,c=r(),d=t();if(a.sum(O.data(),O.valueAccessor())?(b=d(O.data()),E.classed(K,!1)):(b=d([{key:L,value:1,others:[L]}]),E.classed(K,!0)),E){var e=E.selectAll("g."+J).data(b);f(e,c,b),l(b,c),p(e),q()}}function f(a,b,c){var d=g(a);h(d,b),i(d),k(c,b)}function g(a){var b=a.enter().append("g").attr("class",function(a,b){return J+" _"+b});return b}function h(a,c){var d=a.append("path").attr("fill",y).on("click",z).attr("d",function(a,b){return A(a,b,c)});b.transition(d,O.transitionDuration(),function(a){a.attrTween("d",w)})}function i(a){O.renderTitle()&&a.append("title").text(function(a){return O.title()(a)})}function j(a,c){b.transition(a,O.transitionDuration()).attr("transform",function(a){return B(a,c)}).attr("text-anchor","middle").text(function(a){var b=a.data;return v(b)||u(a)?"":O.label()(a.data)})}function k(a,b){if(O.renderLabel()){var c=E.selectAll("text."+J).data(a);c.exit().remove();var d=c.enter().append("text").attr("class",function(a,b){var c=J+" _"+b;return H&&(c+=" external"),c}).on("click",z);j(d,b)}}function l(a,b){m(a,b),n(a,b),o(a)}function m(a,c){var d=E.selectAll("g."+J).data(a).select("path").attr("d",function(a,b){return A(a,b,c)});b.transition(d,O.transitionDuration(),function(a){a.attrTween("d",w)}).attr("fill",y)}function n(a,b){if(O.renderLabel()){var c=E.selectAll("text."+J).data(a);j(c,b)}}function o(a){O.renderTitle()&&E.selectAll("g."+J).data(a).select("title").text(function(a){return O.title()(a.data)})}function p(a){a.exit().remove()}function q(){O.selectAll("g."+J).each(O.hasFilter()?function(a){s(a)?O.highlightSelected(this):O.fadeDeselected(this)}:function(){O.resetHighlight(this)})}function r(){return a.svg.arc().outerRadius(D).innerRadius(M)}function s(a){return O.hasFilter(O.cappedKeyAccessor(a.data))}function t(){return a.layout.pie().sort(null).value(O.cappedValueAccessor)}function u(a){var b=a.endAngle-a.startAngle;return isNaN(b)||N>b}function v(a){return 0===O.cappedValueAccessor(a)}function w(b){b.innerRadius=M;var c=this._current;x(c)&&(c={startAngle:0,endAngle:0});var d=a.interpolate(c,b);return this._current=d(0),function(a){return A(d(a),0,r())}}function x(a){return!a||isNaN(a.startAngle)||isNaN(a.endAngle)}function y(a,b){return O.getColor(a.data,b)}function z(a,b){E.attr("class")!=K&&O.onClick(a.data,b)}function A(a,b,c){var d=c(a,b);return d.indexOf("NaN")>=0&&(d="M0,0"),d}function B(b,c){var d;return d=H?a.svg.arc().outerRadius(D+H).innerRadius(D+H).centroid(b):c.centroid(b),isNaN(d[0])||isNaN(d[1])?"translate(0,0)":"translate("+d+")"}function C(b,c){O.selectAll("g.pie-slice").each(function(d){b.name==d.data.key&&a.select(this).classed("highlight",c)})}var D,E,F,G,H,I=.5,J="pie-slice",K="empty-chart",L="empty",M=0,N=I,O=b.capMixin(b.colorMixin(b.baseMixin({})));return O.colorAccessor(O.cappedKeyAccessor),O.title(function(a){return O.cappedKeyAccessor(a)+": "+O.cappedValueAccessor(a)}),O.slicesCap=O.cap,O.label(O.cappedKeyAccessor),O.renderLabel(!0),O.transitionDuration(350),O._doRender=function(){return O.resetSvg(),E=O.svg().append("g").attr("transform","translate("+O.cx()+","+O.cy()+")"),e(),O},O.innerRadius=function(a){return arguments.length?(M=a,O):M},O.radius=function(a){return arguments.length?(D=a,O):D},O.cx=function(a){return arguments.length?(F=a,O):F||O.width()/2},O.cy=function(a){return arguments.length?(G=a,O):G||O.height()/2 +},O._doRedraw=function(){return e(),O},O.minAngleForLabel=function(a){return arguments.length?(N=a,O):N},O.emptyTitle=function(a){return 0===arguments.length?L:(L=a,O)},O.externalLabels=function(a){return 0===arguments.length?H:(H=a?a:void 0,O)},O.legendables=function(){return O.data().map(function(a,b){var c={name:a.key,data:a.value,others:a.others,chart:O};return c.color=O.getColor(a,b),c})},O.legendHighlight=function(a){C(a,!0)},O.legendReset=function(a){C(a,!1)},O.legendToggle=function(a){O.onClick({key:a.name,others:a.others})},O.anchor(c,d)},b.barChart=function(c,d){function e(a){return b.utils.safeNumber(Math.abs(m.y()(a.y+a.y0)-m.y()(a.y0)))}function f(a,c,d){var f=a.selectAll("rect.bar").data(d.values,b.pluck("x")),g=f.enter().append("rect").attr("class","bar").attr("fill",b.pluck("data",m.getColor)).attr("height",0);m.renderTitle()&&g.append("title").text(b.pluck("data",m.title(d.name))),m.isOrdinal()&&f.on("click",h),b.transition(f,m.transitionDuration()).attr("x",function(a){var c=m.x()(a.x);return o&&(c-=j/2),m.isOrdinal()&&void 0!==n&&(c+=n/2),b.utils.safeNumber(c)}).attr("y",function(a){var c=m.y()(a.y+a.y0);return a.y<0&&(c-=e(a)),b.utils.safeNumber(c)}).attr("width",j).attr("height",function(a){return e(a)}).attr("fill",b.pluck("data",m.getColor)).select("title").text(b.pluck("data",m.title(d.name))),b.transition(f.exit(),m.transitionDuration()).attr("height",0).remove()}function g(){if(void 0===j){var a=m.xUnitCount();j=Math.floor(m.isOrdinal()&&void 0===n?m.x().rangeBand():n?(m.xAxisLength()-(a-1)*n)/a:m.xAxisLength()/(1+m.barPadding())/a),(1/0==j||isNaN(j)||k>j)&&(j=k)}}function h(a){m.onClick(a.data)}function i(b,c){return function(){var d=a.select(this),e=d.attr("fill")==b;return c?!e:e}}var j,k=1,l=2,m=b.stackMixin(b.coordinateGridMixin({})),n=l,o=!1,p=!1;return b.override(m,"rescale",function(){m._rescale(),j=void 0}),b.override(m,"render",function(){m.round()&&o&&!p&&b.logger.warn("By default, brush rounding is disabled if bars are centered. See dc.js bar chart API documentation for details."),m._render()}),m.plotData=function(){var b=m.chartBodyG().selectAll("g.stack").data(m.data());g(),b.enter().append("g").attr("class",function(a,b){return"stack _"+b}),b.each(function(b,c){var d=a.select(this);f(d,c,b)})},m.fadeDeselectedArea=function(){var a=m.chartBodyG().selectAll("rect.bar"),c=m.brush().extent();if(m.isOrdinal())m.hasFilter()?(a.classed(b.constants.SELECTED_CLASS,function(a){return m.hasFilter(a.x)}),a.classed(b.constants.DESELECTED_CLASS,function(a){return!m.hasFilter(a.x)})):(a.classed(b.constants.SELECTED_CLASS,!1),a.classed(b.constants.DESELECTED_CLASS,!1));else if(m.brushIsEmpty(c))a.classed(b.constants.DESELECTED_CLASS,!1);else{var d=c[0],e=c[1];a.classed(b.constants.DESELECTED_CLASS,function(a){return a.x=e})}},m.centerBar=function(a){return arguments.length?(o=a,m):o},m.barPadding=function(a){return arguments.length?(m._rangeBandPadding(a),n=void 0,m):m._rangeBandPadding()},m._useOuterPadding=function(){return void 0===n},m.outerPadding=m._outerRangeBandPadding,m.gap=function(a){return arguments.length?(n=a,m):n},m.extendBrush=function(){var a=m.brush().extent();return!m.round()||o&&!p||(a[0]=a.map(m.round())[0],a[1]=a.map(m.round())[1],m.chartBodyG().select(".brush").call(m.brush().extent(a))),a},m.alwaysUseRounding=function(a){return arguments.length?(p=a,m):p},m.legendHighlight=function(a){m.isLegendableHidden(a)||m.g().selectAll("rect.bar").classed("highlight",i(a.color)).classed("fadeout",i(a.color,!0))},m.legendReset=function(){m.g().selectAll("rect.bar").classed("highlight",!1).classed("fadeout",!1)},b.override(m,"xAxisMax",function(){var a=this._xAxisMax();if("resolution"in m.xUnits()){var b=m.xUnits().resolution;a+=b}return a}),m.anchor(c,d)},b.lineChart=function(c,d){function e(a,b){return y.getColor.call(a,a.values,b)}function f(c,d){var f=a.svg.line().x(function(a){return y.x()(a.x)}).y(function(a){return y.y()(a.y+a.y0)}).interpolate(E).tension(F);q&&f.defined(q);var g=c.append("path").attr("class","line").attr("stroke",e);r&&g.attr("stroke-dasharray",r),b.transition(d.select("path.line"),y.transitionDuration()).attr("stroke",e).attr("d",function(a){return h(f(a.values))})}function g(c,d){if(z){var f=a.svg.area().x(function(a){return y.x()(a.x)}).y(function(a){return y.y()(a.y+a.y0)}).y0(function(a){return y.y()(a.y0)}).interpolate(E).tension(F);q&&f.defined(q),c.append("path").attr("class","area").attr("fill",e).attr("d",function(a){return h(f(a.values))}),b.transition(d.select("path.area"),y.transitionDuration()).attr("fill",e).attr("d",function(a){return h(f(a.values))})}}function h(a){return!a||a.indexOf("NaN")>=0?"M0,0":a}function i(c,d){if(!y.brushOn()){var e=t+"-list",f=c.select("g."+e);f.empty()&&(f=c.append("g").attr("class",e)),d.each(function(c,d){var e=c.values;q&&(e=e.filter(q));var g=f.select("g."+t+"._"+d);g.empty()&&(g=f.append("g").attr("class",t+" _"+d)),j(g);var h=g.selectAll("circle."+u).data(e,b.pluck("x"));h.enter().append("circle").attr("class",u).attr("r",m()).attr("fill",y.getColor).style("fill-opacity",C).style("stroke-opacity",D).on("mousemove",function(){var b=a.select(this);k(b),l(b,g)}).on("mouseout",function(){var b=a.select(this);n(b),o(g)}).append("title").text(b.pluck("data",y.title(c.name))),h.attr("cx",function(a){return b.utils.safeNumber(y.x()(a.x))}).attr("cy",function(a){return b.utils.safeNumber(y.y()(a.y+a.y0))}).attr("fill",y.getColor).select("title").text(b.pluck("data",y.title(c.name))),h.exit().remove()})}}function j(a){var b=a.select("path."+v).empty()?a.append("path").attr("class",v):a.select("path."+v);b.style("display","none").attr("stroke-dasharray","5,5");var c=a.select("path."+w).empty()?a.append("path").attr("class",w):a.select("path."+w);c.style("display","none").attr("stroke-dasharray","5,5")}function k(a){return a.style("fill-opacity",.8),a.style("stroke-opacity",.8),a.attr("r",A),a}function l(a,b){var c=a.attr("cx"),d=a.attr("cy"),e=y._yAxisX()-y.margins().left,f="M"+e+" "+d+"L"+c+" "+d,g="M"+c+" "+y.yAxisHeight()+"L"+c+" "+d;b.select("path."+v).style("display","").attr("d",f),b.select("path."+w).style("display","").attr("d",g)}function m(){return B||A}function n(a){a.style("fill-opacity",C).style("stroke-opacity",D).attr("r",m())}function o(a){a.select("path."+v).style("display","none"),a.select("path."+w).style("display","none")}function p(b,c,d){return function(){var e=a.select(this),f=e.attr("stroke")==b&&e.attr("stroke-dasharray")==(c instanceof Array?c.join(","):null)||e.attr("fill")==b;return d?!f:f}}var q,r,s=5,t="dc-tooltip",u="dot",v="yRef",w="xRef",x=1e-6,y=b.stackMixin(b.coordinateGridMixin({})),z=!1,A=s,B=null,C=x,D=x,E="linear",F=.7;return y.transitionDuration(500),y._rangeBandPadding(1),y.plotData=function(){var a=y.chartBodyG(),b=a.selectAll("g.stack-list");b.empty()&&(b=a.append("g").attr("class","stack-list"));var c=b.selectAll("g.stack").data(y.data()),d=c.enter().append("g").attr("class",function(a,b){return"stack _"+b});f(d,c),g(d,c),i(a,c)},y.interpolate=function(a){return arguments.length?(E=a,y):E},y.tension=function(a){return arguments.length?(F=a,y):F},y.defined=function(a){return arguments.length?(q=a,y):q},y.dashStyle=function(a){return arguments.length?(r=a,y):r},y.renderArea=function(a){return arguments.length?(z=a,y):z},y.dotRadius=function(a){return arguments.length?(A=a,y):A},y.renderDataPoints=function(a){return arguments.length?(a?(C=a.fillOpacity||.8,D=a.strokeOpacity||.8,B=a.radius||2):(C=x,D=x,B=null),y):{fillOpacity:C,strokeOpacity:D,radius:B}},y.legendHighlight=function(a){y.isLegendableHidden(a)||y.g().selectAll("path.line, path.area").classed("highlight",p(a.color,a.dashstyle)).classed("fadeout",p(a.color,a.dashstyle,!0))},y.legendReset=function(){y.g().selectAll("path.line, path.area").classed("highlight",!1).classed("fadeout",!1)},b.override(y,"legendables",function(){var a=y._legendables();return r?a.map(function(a){return a.dashstyle=r,a}):a}),y.anchor(c,d)},b.dataCount=function(c,d){var e=a.format(",d"),f=b.baseMixin({}),g={some:"",all:""};return f.html=function(a){return arguments.length?(a.all&&(g.all=a.all),a.some&&(g.some=a.some),f):g},f._doRender=function(){var a=f.dimension().size(),b=f.group().value(),c=e(a),d=e(b);return a===b&&""!==g.all?f.root().html(g.all.replace("%total-count",c).replace("%filter-count",d)):""!==g.some?f.root().html(g.some.replace("%total-count",c).replace("%filter-count",d)):(f.selectAll(".total-count").text(c),f.selectAll(".filter-count").text(d)),f},f._doRedraw=function(){return f._doRender()},f.anchor(c,d)},b.dataTable=function(c,d){function e(){var a=l.root().selectAll("tbody").data(f(),function(a){return l.keyAccessor()(a)}),b=a.enter().append("tbody");return b.append("tr").attr("class",k).append("td").attr("class",h).attr("colspan",n.length).html(function(a){return l.keyAccessor()(a)}),a.exit().remove(),b}function f(){var b=l.dimension().top(m);return a.nest().key(l.group()).sortKeys(p).entries(b.sort(function(a,b){return p(o(a),o(b))}))}function g(a){var b=a.order().selectAll("tr."+i).data(function(a){return a.values}),c=b.enter().append("tr").attr("class",i);return n.forEach(function(a,b){c.append("td").attr("class",j+" _"+b).html(a)}),b.exit().remove(),b}var h="dc-table-label",i="dc-table-row",j="dc-table-column",k="dc-table-group",l=b.baseMixin({}),m=25,n=[],o=function(a){return a},p=a.ascending;return l._doRender=function(){return l.selectAll("tbody").remove(),g(e()),l},l._doRedraw=function(){return l._doRender()},l.size=function(a){return arguments.length?(m=a,l):m},l.columns=function(a){return arguments.length?(n=a,l):n},l.sortBy=function(a){return arguments.length?(o=a,l):o},l.order=function(a){return arguments.length?(p=a,l):p},l.anchor(c,d)},b.dataGrid=function(c,d){function e(){var a=l.root().selectAll("div."+k).data(f(),function(a){return l.keyAccessor()(a)}),b=a.enter().append("div").attr("class",k);return q&&b.html(function(a){return q(a)}),a.exit().remove(),b}function f(){var b=l.dimension().top(m);return a.nest().key(l.group()).sortKeys(p).entries(b.sort(function(a,b){return p(o(a),o(b))}))}function g(a){var b=a.order().selectAll("div."+i).data(function(a){return a.values});return b.enter().append("div").attr("class",i).html(function(a){return n(a)}),b.exit().remove(),b}var h="dc-grid-label",i="dc-grid-item",j="dc-grid-group",k="dc-grid-top",l=b.baseMixin({}),m=999,n=function(a){return"you need to provide an html() handling param: "+JSON.stringify(a)},o=function(a){return a},p=a.ascending,q=function(a){return"

"+l.keyAccessor()(a)+"

"};return l._doRender=function(){return l.selectAll("div."+k).remove(),g(e()),l},l._doRedraw=function(){return l._doRender()},l.size=function(a){return arguments.length?(m=a,l):m},l.html=function(a){return arguments.length?(n=a,l):n},l.htmlGroup=function(a){return arguments.length?(q=a,l):q},l.sortBy=function(a){return arguments.length?(o=a,l):o},l.order=function(a){return arguments.length?(p=a,l):p},l.anchor(c,d)},b.bubbleChart=function(a,c){function d(a){var c=a.enter().append("g");c.attr("class",i.BUBBLE_NODE_CLASS).attr("transform",k).append("circle").attr("class",function(a,b){return i.BUBBLE_CLASS+" _"+b}).on("click",i.onClick).attr("fill",i.getColor).attr("r",0),b.transition(a,i.transitionDuration()).selectAll("circle."+i.BUBBLE_CLASS).attr("r",function(a){return i.bubbleR(a)}).attr("opacity",function(a){return i.bubbleR(a)>0?1:0}),i._doRenderLabel(c),i._doRenderTitles(c)}function e(a){b.transition(a,i.transitionDuration()).attr("transform",k).selectAll("circle."+i.BUBBLE_CLASS).attr("fill",i.getColor).attr("r",function(a){return i.bubbleR(a)}).attr("opacity",function(a){return i.bubbleR(a)>0?1:0}),i.doUpdateLabels(a),i.doUpdateTitles(a)}function f(a){a.exit().remove()}function g(a){var b=i.x()(i.keyAccessor()(a));return isNaN(b)&&(b=0),b}function h(a){var b=i.y()(i.valueAccessor()(a));return isNaN(b)&&(b=0),b}var i=b.bubbleMixin(b.coordinateGridMixin({})),j=!1;i.transitionDuration(750);var k=function(a){return"translate("+g(a)+","+h(a)+")"};return i.elasticRadius=function(a){return arguments.length?(j=a,i):j},i.plotData=function(){j&&i.r().domain([i.rMin(),i.rMax()]),i.r().range([i.MIN_RADIUS,i.xAxisLength()*i.maxBubbleRelativeSize()]);var a=i.chartBodyG().selectAll("g."+i.BUBBLE_NODE_CLASS).data(i.data(),function(a){return a.key});d(a),e(a),f(a),i.fadeDeselectedArea()},i.renderBrush=function(){},i.redrawBrush=function(){i.fadeDeselectedArea()},i.anchor(a,c)},b.compositeChart=function(c,d){function e(){(void 0===u.rightY()||u.elasticY())&&(u.rightY(a.scale.linear()),u.rightY().domain([l(),o()]).rangeRound([u.yAxisHeight(),0])),u.rightY().range([u.yAxisHeight(),0]),u.rightYAxis(u.rightYAxis().scale(u.rightY())),u.rightYAxis().orient("right")}function f(){(void 0===u.y()||u.elasticY())&&(u.y(a.scale.linear()),u.y().domain([k(),n()]).rangeRound([u.yAxisHeight(),0])),u.y().range([u.yAxisHeight(),0]),u.yAxis(u.yAxis().scale(u.y())),u.yAxis().orient("left")}function g(a,b){a._generateG(u.g()),a.g().attr("class",s+" _"+b)}function h(){return v.filter(function(a){return!a.useRightYAxis()})}function i(){return v.filter(function(a){return a.useRightYAxis()})}function j(a){return a.map(function(a){return a.yAxisMin()})}function k(){return a.min(j(h()))}function l(){return a.min(j(i()))}function m(a){return a.map(function(a){return a.yAxisMax()})}function n(){return b.utils.add(a.max(m(h())),u.yAxisPadding())}function o(){return b.utils.add(a.max(m(i())),u.yAxisPadding())}function p(){return v.map(function(a){return a.xAxisMin()})}function q(){return v.map(function(a){return a.xAxisMax()})}var r,s="sub",t=12,u=b.coordinateGridMixin({}),v=[],w={},x=!1,y=!0,z=a.svg.axis(),A=0,B=t,C=!1;return u._mandatoryAttributes([]),u.transitionDuration(500),b.override(u,"_generateG",function(){for(var a=this.__generateG(),b=0;b0&&!C?u._renderHorizontalGridLinesForAxis(u.g(),u.y(),u.yAxis()):i().length>0&&u._renderHorizontalGridLinesForAxis(u.g(),r,z)},u.renderYAxis=function(){0!==h().length&&(u.renderYAxisAt("y",u.yAxis(),u.margins().left),u.renderYAxisLabel("y",u.yAxisLabel(),-90)),0!==i().length&&(u.renderYAxisAt("yr",u.rightYAxis(),u.width()-u.margins().right),u.renderYAxisLabel("yr",u.rightYAxisLabel(),90,u.width()-B))},u.plotData=function(){for(var a=0;a0&&(b[0]=0),t=a.scale.linear().domain(b).range([0,E.effectiveWidth()])}F.scale(t)}function f(){var a=s.select("g.axis");e(),a.empty()&&(a=s.append("g").attr("class","axis").attr("transform","translate(0, "+E.effectiveHeight()+")")),b.transition(a,E.transitionDuration()).call(F)}function g(){s.selectAll("g.tick").select("line.grid-line").remove(),s.selectAll("g.tick").append("line").attr("class","grid-line").attr("x1",0).attr("y1",0).attr("x2",0).attr("y2",function(){return-E.effectiveHeight()})}function h(){v=E.data(),f(),g();var a=s.selectAll("g."+B).data(v);i(a),j(a),l(a)}function i(a){var b=a.enter().append("g").attr("class",function(a,b){return B+" _"+b});b.append("rect").attr("width",0),n(b),o(a)}function j(a){a.exit().remove()}function k(){var a=t(0);return a===-1/0?t(1):a}function l(a){var c,d=v.length;c=A?A:(E.effectiveHeight()-(d+1)*z)/d;var e=a.attr("transform",function(a,b){return"translate(0,"+((b+1)*z+b*c)+")"}).select("rect").attr("height",c).attr("fill",E.getColor).on("click",p).classed("deselected",function(a){return E.hasFilter()?!r(a):!1}).classed("selected",function(a){return E.hasFilter()?r(a):!1});b.transition(e,E.transitionDuration()).attr("width",function(a){return Math.abs(k()-t(E.valueAccessor()(a)))}).attr("transform",q),m(a),o(a)}function m(a){E.renderTitle()&&(a.selectAll("title").remove(),a.append("title").text(E.title()))}function n(a){E.renderLabel()&&a.append("text").on("click",p),E.renderTitleLabel()&&a.append("text").attr("class",C).on("click",p)}function o(a){if(E.renderLabel()){var c=a.select("text").attr("x",w).attr("y",x).on("click",p).attr("class",function(a,b){return B+" _"+b}).text(function(a){return E.label()(a)});b.transition(c,E.transitionDuration()).attr("transform",q)}if(E.renderTitleLabel()){var d=a.select("."+C).attr("x",E.effectiveWidth()-y).attr("y",x).attr("text-anchor","end").on("click",p).attr("class",function(a,b){return C+" _"+b}).text(function(a){return E.title()(a)});b.transition(d,E.transitionDuration()).attr("transform",q)}}function p(a){E.onClick(a)}function q(a){var b=t(E.cappedValueAccessor(a)),c=k(),d=b>c?c:b;return"translate("+d+",0)"}function r(a){return E.hasFilter(E.cappedKeyAccessor(a))}var s,t,u,v,w=10,x=15,y=2,z=5,A=!1,B="row",C="titlerow",D=!1,E=b.capMixin(b.marginMixin(b.colorMixin(b.baseMixin({})))),F=a.svg.axis().orient("bottom");return E.rowsCap=E.cap,E._doRender=function(){return E.resetSvg(),s=E.svg().append("g").attr("transform","translate("+E.margins().left+","+E.margins().top+")"),h(),E},E.title(function(a){return E.cappedKeyAccessor(a)+": "+E.cappedValueAccessor(a)}),E.label(E.cappedKeyAccessor),E.x=function(a){return arguments.length?(t=a,E):t},E.renderTitleLabel=function(a){return arguments.length?(D=a,E):D},E._doRedraw=function(){return h(),E},E.xAxis=function(){return F},E.fixedBarHeight=function(a){return arguments.length?(A=a,E):A},E.gap=function(a){return arguments.length?(z=a,E):z},E.elasticX=function(a){return arguments.length?(u=a,E):u},E.labelOffsetX=function(a){return arguments.length?(w=a,E):w},E.labelOffsetY=function(a){return arguments.length?(x=a,E):x},E.titleLabelOffsetX=function(a){return arguments.length?(y=a,E):y},E.anchor(c,d)},b.legend=function(){function a(){return j+i}var c,d,e=2,f={},g=0,h=0,i=12,j=5,k=!1,l=560,m=70;return f.parent=function(a){return arguments.length?(c=a,f):c},f.render=function(){c.svg().select("g.dc-legend").remove(),d=c.svg().append("g").attr("class","dc-legend").attr("transform","translate("+g+","+h+")");var f=c.legendables(),j=d.selectAll("g.dc-legend-item").data(f).enter().append("g").attr("class","dc-legend-item").on("mouseover",function(a){c.legendHighlight(a)}).on("mouseout",function(a){c.legendReset(a)}).on("click",function(a){a.chart.legendToggle(a)});d.selectAll("g.dc-legend-item").classed("fadeout",function(a){return a.chart.isLegendableHidden(a)}),f.some(b.pluck("dashstyle"))?j.append("line").attr("x1",0).attr("y1",i/2).attr("x2",i).attr("y2",i/2).attr("stroke-width",2).attr("stroke-dasharray",b.pluck("dashstyle")).attr("stroke",b.pluck("color")):j.append("rect").attr("width",i).attr("height",i).attr("fill",function(a){return a?a.color:"blue"}),j.append("text").text(b.pluck("name")).attr("x",i+e).attr("y",function(){return i/2+(this.clientHeight?this.clientHeight:13)/2-2});var n=0,o=0;j.attr("transform",function(b,c){if(k){var d="translate("+n+","+o*a()+")";return n+m>=l?(++o,n=0):n+=m,d}return"translate(0,"+c*a()+")"})},f.x=function(a){return arguments.length?(g=a,f):g},f.y=function(a){return arguments.length?(h=a,f):h},f.gap=function(a){return arguments.length?(j=a,f):j},f.itemHeight=function(a){return arguments.length?(i=a,f):i},f.horizontal=function(a){return arguments.length?(k=a,f):k},f.legendWidth=function(a){return arguments.length?(l=a,f):l},f.itemWidth=function(a){return arguments.length?(m=a,f):m},f},b.scatterPlot=function(c,d){function e(c,d){var e=g.selectAll(".chart-body path.symbol").filter(function(){return c(a.select(this))}),f=h.size();h.size(Math.pow(d,2)),b.transition(e,g.transitionDuration()).attr("d",h),h.size(f)}function f(a){var c=g.selectAll(".chart-body path.symbol").each(function(b){this.filtered=a&&a.isFiltered(b.key)});b.transition(c,g.transitionDuration()).attr("d",h)}var g=b.coordinateGridMixin({}),h=a.svg.symbol(),i=g.keyAccessor();g.keyAccessor(function(a){return i(a)[0]}),g.valueAccessor(function(a){return i(a)[1]}),g.colorAccessor(function(){return g._groupName});var j=function(a){return"translate("+g.x()(g.keyAccessor()(a))+","+g.y()(g.valueAccessor()(a))+")"},k=3,l=5,m=0;return h.size(function(a){return 0===a.value?m:this.filtered?Math.pow(l,2):Math.pow(k,2)}),b.override(g,"_filter",function(a){return arguments.length?g.__filter(b.filters.RangedTwoDimensionalFilter(a)):g.__filter()}),g.plotData=function(){var a=g.chartBodyG().selectAll("path.symbol").data(g.data());a.enter().append("path").attr("class","symbol").attr("opacity",0).attr("fill",g.getColor).attr("transform",j),b.transition(a,g.transitionDuration()).attr("opacity",function(a){return a.value?1:0}).attr("fill",g.getColor).attr("transform",j).attr("d",h),b.transition(a.exit(),g.transitionDuration()).attr("opacity",0).remove()},g.symbol=function(a){return arguments.length?(h.type(a),g):h.type()},g.symbolSize=function(a){return arguments.length?(k=a,g):k},g.highlightedSize=function(a){return arguments.length?(l=a,g):l},g.hiddenSize=function(a){return arguments.length?(m=a,g):m},g.legendables=function(){return[{chart:g,name:g._groupName,color:g.getColor()}]},g.legendHighlight=function(b){e(function(a){return a.attr("fill")==b.color},l),g.selectAll(".chart-body path.symbol").filter(function(){return a.select(this).attr("fill")!=b.color}).classed("fadeout",!0)},g.legendReset=function(b){e(function(a){return a.attr("fill")==b.color},k),g.selectAll(".chart-body path.symbol").filter(function(){return a.select(this).attr("fill")!=b.color}).classed("fadeout",!1)},g.setHandlePaths=function(){},g.extendBrush=function(){var a=g.brush().extent();return g.round()&&(a[0]=a[0].map(g.round()),a[1]=a[1].map(g.round()),g.g().select(".brush").call(g.brush().extent(a))),a},g.brushIsEmpty=function(a){return g.brush().empty()||!a||a[0][0]>=a[1][0]||a[0][1]>=a[1][1]},g._brushing=function(){var a=g.extendBrush();if(g.redrawBrush(g.g()),g.brushIsEmpty(a))b.events.trigger(function(){g.filter(null),g.redrawGroup()}),f(!1);else{var c=b.filters.RangedTwoDimensionalFilter(a);b.events.trigger(function(){g.filter(null),g.filter(c),g.redrawGroup()},b.constants.EVENT_DELAY),f(c)}},g.setBrushY=function(a){a.call(g.brush().y(g.y()))},g.anchor(c,d)},b.numberDisplay=function(c,d){var e="number-display",f=a.format(".2s"),g=b.baseMixin({}),h={one:"",some:"",none:""};return g._mandatoryAttributes(["group"]),g.html=function(a){return arguments.length?(a.none?h.none=a.none:a.one?h.none=a.one:a.some&&(h.none=a.some),a.one?h.one=a.one:a.some&&(h.one=a.some),a.some?h.some=a.some:a.one&&(h.some=a.one),g):h},g.value=function(){return g.data()},g.data(function(a){var b=a.value?a.value():a.top(1)[0];return g.valueAccessor()(b)}),g.transitionDuration(250),g._doRender=function(){var b=g.value(),c=g.selectAll("."+e);c.empty()&&(c=c.data([0]).enter().append("span").attr("class",e)),c.transition().duration(g.transitionDuration()).ease("quad-out-in").tween("text",function(){var c=a.interpolateNumber(this.lastValue||0,b);return this.lastValue=b,function(a){var d=null,e=g.formatNumber()(c(a));0===b&&""!==h.none?d=h.none:1===b&&""!==h.one?d=h.one:""!==h.some&&(d=h.some),this.innerHTML=d?d.replace("%number",e):e}})},g._doRedraw=function(){return g._doRender()},g.formatNumber=function(a){return arguments.length?(f=a,g):f},g.anchor(c,d)},b.heatMap=function(c,d){function e(a,c){var d=m.selectAll(".box-group").filter(function(b){return b.key[a]==c}),e=d.filter(function(a){return!m.hasFilter(a.key)});b.events.trigger(function(){e.empty()?d.each(function(a){m.filter(a.key)}):e.each(function(a){m.filter(a.key)}),m.redrawGroup()})}function f(a,b,c){return!b||c[b-1]!=a}var g,h,i,j=6.75,k=j,l=j,m=b.colorMixin(b.marginMixin(b.baseMixin({})));m._mandatoryAttributes(["group"]),m.title(m.colorAccessor());var n=function(a){e(0,a)},o=function(a){e(1,a)},p=function(a){var c=a.key;b.events.trigger(function(){m.filter(c),m.redrawGroup()})};return b.override(m,"filter",function(a){return arguments.length?m._filter(b.filters.TwoDimensionalFilter(a)):m._filter()}),m.rows=function(b){if(arguments.length)return i=b,m;if(i)return i;var c=m.data().map(m.valueAccessor());return c.sort(a.ascending),a.scale.ordinal().domain(c.filter(f))},m.cols=function(b){if(arguments.length)return h=b,m;if(h)return h;var c=m.data().map(m.keyAccessor());return c.sort(a.ascending),a.scale.ordinal().domain(c.filter(f))},m._doRender=function(){return m.resetSvg(),g=m.svg().append("g").attr("class","heatmap").attr("transform","translate("+m.margins().left+","+m.margins().top+")"),m._doRedraw()},m._doRedraw=function(){var a=m.rows(),c=m.cols(),d=a.domain().length,e=c.domain().length,f=Math.floor(m.effectiveWidth()/e),h=Math.floor(m.effectiveHeight()/d);c.rangeRoundBands([0,m.effectiveWidth()]),a.rangeRoundBands([m.effectiveHeight(),0]);var i=g.selectAll("g.box-group").data(m.data(),function(a,b){return m.keyAccessor()(a,b)+"\x00"+m.valueAccessor()(a,b)}),j=i.enter().append("g").attr("class","box-group"); +j.append("rect").attr("class","heat-box").attr("fill","white").on("click",m.boxOnClick()),j.append("title").text(m.title()),b.transition(i.selectAll("rect"),m.transitionDuration()).attr("x",function(a,b){return c(m.keyAccessor()(a,b))}).attr("y",function(b,c){return a(m.valueAccessor()(b,c))}).attr("rx",k).attr("ry",l).attr("fill",m.getColor).attr("width",f).attr("height",h),i.exit().remove();var n=g.selectAll("g.cols");n.empty()&&(n=g.append("g").attr("class","cols axis"));var o=n.selectAll("text").data(c.domain());o.enter().append("text").attr("x",function(a){return c(a)+f/2}).style("text-anchor","middle").attr("y",m.effectiveHeight()).attr("dy",12).on("click",m.xAxisOnClick()).text(function(a){return a}),b.transition(o,m.transitionDuration()).text(function(a){return a}).attr("x",function(a){return c(a)+f/2}),o.exit().remove();var p=g.selectAll("g.rows");p.empty()&&(p=g.append("g").attr("class","rows axis"));var q=p.selectAll("text").data(a.domain());return q.enter().append("text").attr("dy",6).style("text-anchor","end").attr("x",0).attr("dx",-2).on("click",m.yAxisOnClick()).text(function(a){return a}),b.transition(q,m.transitionDuration()).text(function(a){return a}).attr("y",function(b){return a(b)+h/2}),q.exit().remove(),m.selectAll("g.box-group").each(m.hasFilter()?function(a){m.isSelectedNode(a)?m.highlightSelected(this):m.fadeDeselected(this)}:function(){m.resetHighlight(this)}),m},m.boxOnClick=function(a){return arguments.length?(p=a,m):p},m.xAxisOnClick=function(a){return arguments.length?(n=a,m):n},m.yAxisOnClick=function(a){return arguments.length?(o=a,m):o},m.xBorderRadius=function(a){return arguments.length?(k=a,m):k},m.yBorderRadius=function(a){return arguments.length?(l=a,m):l},m.isSelectedNode=function(a){return m.hasFilter(a.key)},m.anchor(c,d)},function(){function b(a){return[0,a.length-1]}function c(b){return[a.quantile(b,.25),a.quantile(b,.5),a.quantile(b,.75)]}a.box=function(){function d(b){b.each(function(b,c){b=b.map(i).sort(a.ascending);var d=a.select(this),m=b.length,n=b[0],o=b[m-1],p=b.quartiles=k(b),q=j&&j.call(this,b,c),r=q&&q.map(function(a){return b[a]}),s=q?a.range(0,q[0]).concat(a.range(q[1]+1,m)):a.range(m),t=a.scale.linear().domain(h&&h.call(this,b,c)||[n,o]).range([f,0]),u=this.__chart__||a.scale.linear().domain([0,1/0]).range(t.range());this.__chart__=t;var v=d.selectAll("line.center").data(r?[r]:[]);v.enter().insert("line","rect").attr("class","center").attr("x1",e/2).attr("y1",function(a){return u(a[0])}).attr("x2",e/2).attr("y2",function(a){return u(a[1])}).style("opacity",1e-6).transition().duration(g).style("opacity",1).attr("y1",function(a){return t(a[0])}).attr("y2",function(a){return t(a[1])}),v.transition().duration(g).style("opacity",1).attr("y1",function(a){return t(a[0])}).attr("y2",function(a){return t(a[1])}),v.exit().transition().duration(g).style("opacity",1e-6).attr("y1",function(a){return t(a[0])}).attr("y2",function(a){return t(a[1])}).remove();var w=d.selectAll("rect.box").data([p]);w.enter().append("rect").attr("class","box").attr("x",0).attr("y",function(a){return u(a[2])}).attr("width",e).attr("height",function(a){return u(a[0])-u(a[2])}).transition().duration(g).attr("y",function(a){return t(a[2])}).attr("height",function(a){return t(a[0])-t(a[2])}),w.transition().duration(g).attr("y",function(a){return t(a[2])}).attr("height",function(a){return t(a[0])-t(a[2])});var x=d.selectAll("line.median").data([p[1]]);x.enter().append("line").attr("class","median").attr("x1",0).attr("y1",u).attr("x2",e).attr("y2",u).transition().duration(g).attr("y1",t).attr("y2",t),x.transition().duration(g).attr("y1",t).attr("y2",t);var y=d.selectAll("line.whisker").data(r||[]);y.enter().insert("line","circle, text").attr("class","whisker").attr("x1",0).attr("y1",u).attr("x2",e).attr("y2",u).style("opacity",1e-6).transition().duration(g).attr("y1",t).attr("y2",t).style("opacity",1),y.transition().duration(g).attr("y1",t).attr("y2",t).style("opacity",1),y.exit().transition().duration(g).attr("y1",t).attr("y2",t).style("opacity",1e-6).remove();var z=d.selectAll("circle.outlier").data(s,Number);z.enter().insert("circle","text").attr("class","outlier").attr("r",5).attr("cx",e/2).attr("cy",function(a){return u(b[a])}).style("opacity",1e-6).transition().duration(g).attr("cy",function(a){return t(b[a])}).style("opacity",1),z.transition().duration(g).attr("cy",function(a){return t(b[a])}).style("opacity",1),z.exit().transition().duration(g).attr("cy",function(a){return t(b[a])}).style("opacity",1e-6).remove();var A=l||t.tickFormat(8),B=d.selectAll("text.box").data(p);B.enter().append("text").attr("class","box").attr("dy",".3em").attr("dx",function(a,b){return 1&b?6:-6}).attr("x",function(a,b){return 1&b?e:0}).attr("y",u).attr("text-anchor",function(a,b){return 1&b?"start":"end"}).text(A).transition().duration(g).attr("y",t),B.transition().duration(g).text(A).attr("y",t);var C=d.selectAll("text.whisker").data(r||[]);C.enter().append("text").attr("class","whisker").attr("dy",".3em").attr("dx",6).attr("x",e).attr("y",u).text(A).style("opacity",1e-6).transition().duration(g).attr("y",t).style("opacity",1),C.transition().duration(g).text(A).attr("y",t).style("opacity",1),C.exit().transition().duration(g).attr("y",t).style("opacity",1e-6).remove()}),a.timer.flush()}var e=1,f=1,g=0,h=null,i=Number,j=b,k=c,l=null;return d.width=function(a){return arguments.length?(e=a,d):e},d.height=function(a){return arguments.length?(f=a,d):f},d.tickFormat=function(a){return arguments.length?(l=a,d):l},d.duration=function(a){return arguments.length?(g=a,d):g},d.domain=function(b){return arguments.length?(h=null==b?b:a.functor(b),d):h},d.value=function(a){return arguments.length?(i=a,d):i},d.whiskers=function(a){return arguments.length?(j=a,d):j},d.quartiles=function(a){return arguments.length?(k=a,d):k},d}}(),b.boxPlot=function(c,d){function e(a){var b=a.enter().append("g");b.attr("class","box").attr("transform",p).call(m).on("click",function(a){i.filter(a.key),i.redrawGroup()})}function f(c){b.transition(c,i.transitionDuration()).attr("transform",p).call(m).each(function(){a.select(this).select("rect.box").attr("fill",i.getColor)})}function g(a){a.exit().remove().call(m)}function h(a){return function(b){for(var c=b.quartiles[0],d=b.quartiles[2],e=(d-c)*a,f=-1,g=b.length;b[++f]d+e;);return[f,g]}}var i=b.coordinateGridMixin({}),j=1.5,k=h,l=k(j),m=a.box(),n=null,o=function(a,b){return i.isOrdinal()?i.x().rangeBand():a/(1+i.boxPadding())/b};i.yAxisPadding(12),i.x(a.scale.ordinal()),i.xUnits(b.units.ordinal),i.data(function(a){return a.all().map(function(a){return a.map=function(b){return b.call(a,a)},a}).filter(function(a){var b=i.valueAccessor()(a);return 0!==b.length})}),i.boxPadding=i._rangeBandPadding,i.boxPadding(.8),i.outerPadding=i._outerRangeBandPadding,i.outerPadding(.5),i.boxWidth=function(b){return arguments.length?(o=a.functor(b),i):o};var p=function(a,b){var c=i.x()(i.keyAccessor()(a,b));return"translate("+c+",0)"};return i._preprocessData=function(){i.elasticX()&&i.x().domain([])},i.plotData=function(){var a=o(i.effectiveWidth(),i.xUnitCount());m.whiskers(l).width(a).height(i.effectiveHeight()).value(i.valueAccessor()).domain(i.y().domain()).duration(i.transitionDuration()).tickFormat(n);var b=i.chartBodyG().selectAll("g.box").data(i.data(),function(a){return a.key});e(b),f(b),g(b),i.fadeDeselectedArea()},i.fadeDeselectedArea=function(){i.g().selectAll("g.box").each(i.hasFilter()?function(a){i.isSelectedNode(a)?i.highlightSelected(this):i.fadeDeselected(this)}:function(){i.resetHighlight(this)})},i.isSelectedNode=function(a){return i.hasFilter(a.key)},i.yAxisMin=function(){var c=a.min(i.data(),function(b){return a.min(i.valueAccessor()(b))});return b.utils.subtract(c,i.yAxisPadding())},i.yAxisMax=function(){var c=a.max(i.data(),function(b){return a.max(i.valueAccessor()(b))});return b.utils.add(c,i.yAxisPadding())},i.tickFormat=function(a){return arguments.length?(n=a,i):n},i.anchor(c,d)},b.abstractBubbleChart=b.bubbleMixin,b.baseChart=b.baseMixin,b.capped=b.capMixin,b.colorChart=b.colorMixin,b.coordinateGridChart=b.coordinateGridMixin,b.marginable=b.marginMixin,b.stackableChart=b.stackMixin,b}"function"==typeof define&&define.amd?define(["d3"],a):"object"==typeof module&&module.exports?module.exports=a(d3):this.dc=a(d3)}(); +//# sourceMappingURL=dc.min.js.map \ No newline at end of file diff --git a/ajax/libs/dc/2.0.0-alpha/dc.css b/ajax/libs/dc/2.0.0-alpha/dc.css new file mode 100644 index 000000000..ffd4b13ab --- /dev/null +++ b/ajax/libs/dc/2.0.0-alpha/dc.css @@ -0,0 +1,317 @@ +div.dc-chart { + float: left; +} + +.dc-chart rect.bar { + stroke: none; + cursor: pointer; +} + +.dc-chart rect.bar:hover { + fill-opacity: .5; +} + +.dc-chart rect.stack1 { + stroke: none; + fill: red; +} + +.dc-chart rect.stack2 { + stroke: none; + fill: green; +} + +.dc-chart rect.deselected { + stroke: none; + fill: #ccc; +} + +.dc-chart .empty-chart .pie-slice path { + fill: #FFEEEE; + cursor: default; +} + +.dc-chart .empty-chart .pie-slice { + cursor: default; +} + +.dc-chart .pie-slice { + fill: white; + font-size: 12px; + cursor: pointer; +} + +.dc-chart .pie-slice.external{ + fill: black; +} + +.dc-chart .pie-slice :hover { + fill-opacity: .8; +} + +.dc-chart .pie-slice.highlight { + fill-opacity: .8; +} + +.dc-chart .selected path { + stroke-width: 3; + stroke: #ccc; + fill-opacity: 1; +} + +.dc-chart .deselected path { + stroke: none; + fill-opacity: .5; + fill: #ccc; +} + +.dc-chart .axis path, .axis line { + fill: none; + stroke: #000; + shape-rendering: crispEdges; +} + +.dc-chart .axis text { + font: 10px sans-serif; +} + +.dc-chart .grid-line { + fill: none; + stroke: #ccc; + opacity: .5; + shape-rendering: crispEdges; +} + +.dc-chart .grid-line line { + fill: none; + stroke: #ccc; + opacity: .5; + shape-rendering: crispEdges; +} + +.dc-chart .brush rect.background { + z-index: -999; +} + +.dc-chart .brush rect.extent { + fill: steelblue; + fill-opacity: .125; +} + +.dc-chart .brush .resize path { + fill: #eee; + stroke: #666; +} + +.dc-chart path.line { + fill: none; + stroke-width: 1.5px; +} + +.dc-chart circle.dot { + stroke: none; +} + +.dc-chart g.dc-tooltip path { + fill: none; + stroke: grey; + stroke-opacity: .8; +} + +.dc-chart path.area { + fill-opacity: .3; + stroke: none; +} + +.dc-chart .node { + font-size: 0.7em; + cursor: pointer; +} + +.dc-chart .node :hover { + fill-opacity: .8; +} + +.dc-chart .selected circle { + stroke-width: 3; + stroke: #ccc; + fill-opacity: 1; +} + +.dc-chart .deselected circle { + stroke: none; + fill-opacity: .5; + fill: #ccc; +} + +.dc-chart .bubble { + stroke: none; + fill-opacity: 0.6; +} + +.dc-data-count { + float: right; + margin-top: 15px; + margin-right: 15px; +} + +.dc-data-count .filter-count { + color: #3182bd; + font-weight: bold; +} + +.dc-data-count .total-count { + color: #3182bd; + font-weight: bold; +} + +.dc-data-table { +} + +.dc-chart g.state { + cursor: pointer; +} + +.dc-chart g.state :hover { + fill-opacity: .8; +} + +.dc-chart g.state path { + stroke: white; +} + +.dc-chart g.selected path { +} + +.dc-chart g.deselected path { + fill: grey; +} + +.dc-chart g.selected text { +} + +.dc-chart g.deselected text { + display: none; +} + +.dc-chart g.county path { + stroke: white; + fill: none; +} + +.dc-chart g.debug rect { + fill: blue; + fill-opacity: .2; +} + +.dc-chart g.row rect { + fill-opacity: 0.8; + cursor: pointer; +} + +.dc-chart g.row rect:hover { + fill-opacity: 0.6; +} + +.dc-chart g.row text { + fill: white; + font-size: 12px; + cursor: pointer; +} + +.dc-legend { + font-size: 11px; +} + +.dc-legend-item { + cursor: pointer; +} + +.dc-chart g.axis text { + /* Makes it so the user can't accidentally click and select text that is meant as a label only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10 */ + -o-user-select: none; + user-select: none; + pointer-events: none; +} + +.dc-chart path.highlight { + stroke-width: 3; + fill-opacity: 1; + stroke-opacity: 1; +} + +.dc-chart .highlight { + fill-opacity: 1; + stroke-opacity: 1; +} + +.dc-chart .fadeout { + fill-opacity: 0.2; + stroke-opacity: 0.2; +} + +.dc-chart path.dc-symbol, g.dc-legend-item.fadeout { + fill-opacity: 0.5; + stroke-opacity: 0.5; +} + +.dc-hard .number-display { + float: none; +} + +.dc-chart .box text { + font: 10px sans-serif; + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10 */ + -o-user-select: none; + user-select: none; + pointer-events: none; +} + +.dc-chart .box line, +.dc-chart .box circle { + fill: #fff; + stroke: #000; + stroke-width: 1.5px; +} + +.dc-chart .box rect { + stroke: #000; + stroke-width: 1.5px; +} + +.dc-chart .box .center { + stroke-dasharray: 3,3; +} + +.dc-chart .box .outlier { + fill: none; + stroke: #ccc; +} + +.dc-chart .box.deselected .box { + fill: #ccc; +} + +.dc-chart .box.deselected { + opacity: .5; +} + +.dc-chart .symbol{ + stroke: none; +} + +.dc-chart .heatmap .box-group.deselected rect { + stroke: none; + fill-opacity: .5; + fill: #ccc; +} + +.dc-chart .heatmap g.axis text { + pointer-events: all; + cursor: pointer; +} diff --git a/ajax/libs/dc/package.json b/ajax/libs/dc/package.json index 361cec4f9..659ae921b 100644 --- a/ajax/libs/dc/package.json +++ b/ajax/libs/dc/package.json @@ -1,7 +1,7 @@ { "name": "dc", "filename": "dc.min.js", - "version": "1.7.1", + "version": "2.0.0-alpha.2", "license": "Apache-2.0", "copyright": "2014", "description": "A multi-dimensional charting library built to work natively with crossfilter and rendered using d3.js ", diff --git a/auto-update.js b/auto-update.js index 6d59d8214..cd2ab4a20 100644 --- a/auto-update.js +++ b/auto-update.js @@ -177,12 +177,17 @@ var processNewVersion = function(pkg, version){ var copyPath = path.join(libPath, copyPart) fs.mkdirsSync(path.dirname(copyPath)) fs.renameSync(extractFilePath, copyPath); - updated = true + updated = true; }); }); }); if(updated){ newVersionCount++; + var libPatha =path.normalize(path.join(__dirname, 'ajax', 'libs', pkg.name, 'package.json')); + console.log('------------'.red, libPatha.green); + pkg.version = version; + + fs.writeFileSync(libPatha, JSON.stringify(pkg, null, 2) + '\n', 'utf8'); } return errors; } @@ -253,9 +258,6 @@ var updateLibrary = function (pkg, cb) { }, function(err){ var msg = 'Library finished' + (err ? ' ' + err.error : ''); console.log(msg); - var npmVersion = result.body['dist-tags'] && result.body['dist-tags'].latest || 0; - pkg.version = npmVersion; - fs.writeFileSync('ajax/libs/' + pkg.name + '/package.json', JSON.stringify(pkg, null, 2) + '\n', 'utf8'); cb(null); }); });