diff --git a/package.json b/package.json index deb093d28..3a3b07260 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dc", - "version": "3.0.4", + "version": "3.0.4-groupstack", "license": "Apache-2.0", "copyright": "2017", "description": "A multi-dimensional charting library built to work natively with crossfilter and rendered using d3.js ", diff --git a/src/bar-chart.js b/src/bar-chart.js index 03d1581d6..c2a4ed6f0 100644 --- a/src/bar-chart.js +++ b/src/bar-chart.js @@ -27,16 +27,21 @@ dc.barChart = function (parent, chartGroup) { var MIN_BAR_WIDTH = 1; var DEFAULT_GAP_BETWEEN_BARS = 2; + var DEFAULT_GAP_BETWEEN_BAR_SERIES = 5; var LABEL_PADDING = 3; + var _type = 'dc.BAR_CHART'; var _chart = dc.stackMixin(dc.coordinateGridMixin({})); var _gap = DEFAULT_GAP_BETWEEN_BARS; + var _serieGap = DEFAULT_GAP_BETWEEN_BAR_SERIES; var _centerBar = false; var _alwaysUseRounding = false; var _barWidth; + _chart.type = _type; + dc.override(_chart, 'rescale', function () { _chart._rescale(); _barWidth = undefined; @@ -46,7 +51,7 @@ dc.barChart = function (parent, chartGroup) { 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.'); + 'See dc.js bar chart API documentation for details.'); } return _chart._render(); @@ -86,6 +91,18 @@ dc.barChart = function (parent, chartGroup) { return dc.utils.safeNumber(Math.abs(_chart.y()(d.y + d.y0) - _chart.y()(d.y0))); } + function getCharts () { + if (dc.instanceOfChart(parent) && typeof parent.children === 'function') { + var children = parent.children(); + if (children instanceof Array) { + return children.filter(function (chart) { + return chart.type === _type; + }); + } + } + return []; + } + function labelXPos (d) { var x = _chart.x()(d.x); if (!_centerBar) { @@ -139,12 +156,47 @@ dc.barChart = function (parent, chartGroup) { function barXPos (d) { var x = _chart.x()(d.x); - if (_centerBar) { - x -= _barWidth / 2; - } - if (_chart.isOrdinal() && _gap !== undefined) { - x += _gap / 2; + var charts = getCharts(); + var chartIndex = charts.indexOf(_chart); + + // A grouped chart + if (charts.length > 1) { + var numberOfCharts = charts.length; + var numberOfBars = _chart.xUnitCount(); + var barWidth; + var barPadding; + + if (_chart.isOrdinal()) { + barWidth = (_chart.x().bandwidth() - _chart.serieGap()) / (numberOfCharts); + } else { + barWidth = ((_chart.xAxisLength() / numberOfBars) - _chart.serieGap()) / numberOfCharts; + } + + if (_gap === undefined) { + barPadding = barWidth * (_chart.barPadding()); + } else { + barPadding = _gap; + } + + x += chartIndex * (barWidth); + if (_chart.isOrdinal()) { + x += _chart.serieGap() / 2; + x += barPadding / 2; + } else if (!_chart.isOrdinal() && _centerBar) { + x -= barWidth * numberOfCharts / 2; + x += barPadding / 2; + } + + // Not a grouped chart + } else { + if (_centerBar) { + x -= _barWidth / 2; + } + if (_chart.isOrdinal() && _gap !== undefined) { + x += _gap / 2; + } } + return dc.utils.safeNumber(x); } @@ -197,14 +249,37 @@ dc.barChart = function (parent, chartGroup) { function calculateBarWidth () { if (_barWidth === undefined) { var numberOfBars = _chart.xUnitCount(); + var charts = getCharts(); + + // A grouped chart + if (charts.length > 1) { + var numberOfCharts = charts.length, + barPadding, + barWidth; + + if (_chart.isOrdinal()) { + barWidth = (_chart.x().bandwidth() - _chart.serieGap()) / (numberOfCharts); + } else { + barWidth = ((_chart.xAxisLength() / numberOfBars) - _chart.serieGap()) / numberOfCharts; + } + + if (_gap === undefined) { + barPadding = barWidth * (_chart.barPadding()); + } else { + barPadding = _gap; + } + _barWidth = Math.floor(barWidth - barPadding); - // please can't we always use rangeBands for bar charts? - if (_chart.isOrdinal() && _gap === undefined) { - _barWidth = Math.floor(_chart.x().bandwidth()); - } else if (_gap) { - _barWidth = Math.floor((_chart.xAxisLength() - (numberOfBars - 1) * _gap) / numberOfBars); + // Not a grouped chart } else { - _barWidth = Math.floor(_chart.xAxisLength() / (1 + _chart.barPadding()) / numberOfBars); + // please can't we always use rangeBands for bar charts? + if (_chart.isOrdinal() && _gap === undefined) { + _barWidth = Math.floor(_chart.x().bandwidth()); + } 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) { @@ -214,30 +289,55 @@ dc.barChart = function (parent, chartGroup) { } _chart.fadeDeselectedArea = function (brushSelection) { - var bars = _chart.chartBodyG().selectAll('rect.bar'); - - 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.brushOn() || _chart.parentBrushOn()) { - if (!_chart.brushIsEmpty(brushSelection)) { - var start = brushSelection[0]; - var end = brushSelection[1]; + var charts = getCharts(); + var bars; + + // A grouped chart + if (charts.length > 1 && _chart.isOrdinal()) { + charts.forEach(function (chart, i) { + bars = chart.chartBodyG().selectAll('rect.bar'); + + 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); + } + } + }); - bars.classed(dc.constants.DESELECTED_CLASS, function (d) { - return d.x < start || d.x >= end; - }); - } else { - bars.classed(dc.constants.DESELECTED_CLASS, false); + // Not a grouped chart + } else { + bars = _chart.chartBodyG().selectAll('rect.bar'); + 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.brushOn() || _chart.parentBrushOn()) { + if (!_chart.brushIsEmpty(brushSelection)) { + var start = brushSelection[0]; + var end = brushSelection[1]; + + bars.classed(dc.constants.DESELECTED_CLASS, function (d) { + return d.x < start || d.x >= end; + }); + } else { + bars.classed(dc.constants.DESELECTED_CLASS, false); + } } } }; @@ -259,7 +359,18 @@ dc.barChart = function (parent, chartGroup) { }; dc.override(_chart, 'onClick', function (d) { - _chart._onClick(d.data); + var charts = getCharts(); + // A grouped chart + if (charts.length > 1) { + charts.forEach(function (chart, i) { + var filter = chart.keyAccessor()(d.data); + chart.filter(filter); + }); + _chart.redrawGroup(); + // Not a grouped chart + } else { + _chart._onClick(d.data); + } }); /** @@ -315,6 +426,24 @@ dc.barChart = function (parent, chartGroup) { return _chart; }; + /** + * Manually set fixed gap (in px) between bar groups instead of relying on the default auto-generated + * gap. Only applicable for grouped bar charts. + * @name serieGap + * @memberof dc.barChart + * @instance + * @param {Number} [serieGap=5] + * @return {Number} + * @return {dc.barChart} + */ + _chart.serieGap = function (serieGap) { + if (!arguments.length) { + return _serieGap; + } + _serieGap = serieGap; + return _chart; + }; + _chart.extendBrush = function (brushSelection) { if (brushSelection && _chart.round() && (!_centerBar || _alwaysUseRounding)) { brushSelection[0] = _chart.round()(brushSelection[0]); diff --git a/web/examples/bar-grouped-multi.html b/web/examples/bar-grouped-multi.html new file mode 100644 index 000000000..a073b96b9 --- /dev/null +++ b/web/examples/bar-grouped-multi.html @@ -0,0 +1,290 @@ + + + + dc.js - Two Grouped Bar Chart Example + + + + + + +
+ + +
+ Ordinal chart + +
+
+ Ordinal chart + +
+
+ Non ordinal chart with centerbar and brush on + +
+ + + + + + +
+ + diff --git a/web/examples/bar-grouped.html b/web/examples/bar-grouped.html new file mode 100644 index 000000000..f283c1743 --- /dev/null +++ b/web/examples/bar-grouped.html @@ -0,0 +1,108 @@ + + + + dc.js - Grouped Bar Chart Example + + + + + + +
+ +
+ + + + + + +
+ +