diff --git a/AUTHORS b/AUTHORS index 726d9a630..11137f2fa 100644 --- a/AUTHORS +++ b/AUTHORS @@ -82,3 +82,5 @@ Wei Ding Michael Dougherty Paul Mach Fil +Mauricio Bustos +Anders Dalvander diff --git a/Changelog.md b/Changelog.md index 7e2e87458..82174fa9e 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,11 @@ # 2.0 Series +## 2.0.0 beta 33 +* Use keyAccessor for box plots; fix ordinal boxplot brushing and whisker widths, by Matt Traynham ([#1022](https://github.com/dc-js/dc.js/pull/1022)) +* `transitionDelay` allows staggered transitions, by Mauricio Bustos ([#1116](https://github.com/dc-js/dc.js/pull/1116)) +* Removed the confusing callback from dc.transition and documented the function + ## 2.0.0 beta 32 -* elasticY and elasticX did not work if all values were negative (coordinate grid and row charts, respectively), by Sebastian Gröhn ([#879](https://github.com/dc-js/dc.js/issues/879) / [#1156](https://github.com/dc-js/dc.js/pull/1156)) +* `elasticY` and `elasticX` did not work if all values were negative (coordinate grid and row charts, respectively), by Sebastian Gröhn ([#879](https://github.com/dc-js/dc.js/issues/879) / [#1156](https://github.com/dc-js/dc.js/pull/1156)) * Improved implementation of alignYAxes, by Mohamed Gazal and Gordon Woodhull ([#1033](https://github.com/dc-js/dc.js/pull/1033)) * Examples of downloading the table data as it's formatted, and formatting legend items. * `legend.legendText` documentation was missing. diff --git a/Gruntfile.js b/Gruntfile.js index f2dc2c6cc..3fba605f8 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -209,7 +209,7 @@ module.exports = function (grunt) { }, { browserName: 'MicrosoftEdge', - version: '20.10240', + version: '14', platform: 'Windows 10' } ], diff --git a/package.json b/package.json index 4485966ef..263fcdb32 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "dc", - "version": "2.0.0-beta.32", + "version": "2.0.0-beta.33", "license": "Apache-2.0", "copyright": "2016", "description": "A multi-dimensional charting library built to work natively with crossfilter and rendered using d3.js ", diff --git a/spec/box-plot-spec.js b/spec/box-plot-spec.js index b55cbffc7..95b63f071 100644 --- a/spec/box-plot-spec.js +++ b/spec/box-plot-spec.js @@ -25,6 +25,7 @@ describe('dc.boxPlot', function () { .margins({top: 0, right: 0, bottom: 0, left: 0}) .boxPadding(0) .transitionDuration(0) + .transitionDelay(0) .y(d3.scale.linear().domain([0, 144])) .ordinalColors(['#01','#02']); }); diff --git a/spec/coordinate-grid-chart-spec.js b/spec/coordinate-grid-chart-spec.js index 7c133bb85..c056806c5 100644 --- a/spec/coordinate-grid-chart-spec.js +++ b/spec/coordinate-grid-chart-spec.js @@ -17,6 +17,7 @@ describe('dc.coordinateGridChart', function () { .dimension(dimension) .group(group) .transitionDuration(0) + .transitionDelay(0) .brushOn(false) .margins({top: 20, bottom: 0, right: 10, left: 0}) .x(d3.time.scale.utc().domain([makeDate(2012, 4, 20), makeDate(2012, 7, 15)])); @@ -63,6 +64,10 @@ describe('dc.coordinateGridChart', function () { expect(chart.transitionDuration()).toBe(0); }); + it('should have zero transition delay', function () { + expect(chart.transitionDelay()).toBe(0); + }); + it('should set the margins of the chart', function () { expect(chart.margins()).not.toBeNull(); }); @@ -197,6 +202,7 @@ describe('dc.coordinateGridChart', function () { .dimension(dimension) .group(group) .transitionDuration(0) + .transitionDelay(0) .brushOn(false) .margins({top: 20, bottom: 0, right: 10, left: 0}) .x(d3.time.scale.utc().domain([makeDate(2012, 4, 20), makeDate(2012, 7, 15)])); @@ -217,6 +223,7 @@ describe('dc.coordinateGridChart', function () { .dimension(dimension) .group(group) .transitionDuration(0) + .transitionDelay(0) .brushOn(false) .margins({top: 20, bottom: 0, right: 10, left: 0}) .x(d3.time.scale.utc().domain([makeDate(2012, 4, 20), makeDate(2012, 7, 15)])); diff --git a/spec/core-spec.js b/spec/core-spec.js index 85a6889b1..d10b13813 100644 --- a/spec/core-spec.js +++ b/spec/core-spec.js @@ -107,41 +107,61 @@ describe('dc.core', function () { }, duration: function () { return this; + }, + delay: function () { + return this; } }; spyOn(selections, 'transition').and.callThrough(); spyOn(selections, 'duration').and.callThrough(); + spyOn(selections, 'delay').and.callThrough(); }); describe('normal', function () { it('transition should be activated with duration', function () { - dc.transition(selections, 100); + dc.transition(selections, 100, 100); expect(selections.transition).toHaveBeenCalled(); expect(selections.duration).toHaveBeenCalled(); + expect(selections.delay).toHaveBeenCalled(); + expect(selections.duration).toHaveBeenCalledWith(100); + expect(selections.delay).toHaveBeenCalledWith(100); }); - - it('transition callback should be triggered', function () { - var triggered = false; - dc.transition(selections, 100, function () { - triggered = true; - }); - expect(triggered).toBeTruthy(); + it('with name', function () { + dc.transition(selections, 100, 100, 'transition-name'); + expect(selections.transition).toHaveBeenCalled(); + expect(selections.transition).toHaveBeenCalledWith('transition-name'); }); }); describe('skip', function () { it('transition should not be activated with 0 duration', function () { - dc.transition(selections, 0); + dc.transition(selections, 0, 0); expect(selections.transition).not.toHaveBeenCalled(); expect(selections.duration).not.toHaveBeenCalled(); + expect(selections.delay).not.toHaveBeenCalled(); }); - it('transition callback should not be triggered', function () { - var triggered = false; - dc.transition(selections, 0, function () { - triggered = true; - }); - expect(triggered).toBeFalsy(); + it('transition should not be activated with dc.disableTransitions', function () { + dc.disableTransitions = true; + dc.transition(selections, 100); + expect(selections.transition).not.toHaveBeenCalled(); + expect(selections.duration).not.toHaveBeenCalled(); + }); + + afterEach(function () { + dc.disableTransitions = false; + }); + }); + + describe('parameters', function () { + it('duration should not be called if skipped', function () { + dc.transition(selections); + expect(selections.duration).not.toHaveBeenCalled(); + }); + + it('delay should not be called if skipped', function () { + dc.transition(selections, 100); + expect(selections.delay).not.toHaveBeenCalled(); }); }); }); diff --git a/spec/line-chart-spec.js b/spec/line-chart-spec.js index c78b83696..426348e08 100644 --- a/spec/line-chart-spec.js +++ b/spec/line-chart-spec.js @@ -117,9 +117,36 @@ describe('dc.lineChart', function () { chart.render(); }); - it('should not render tooltips when boolean flag is false', function () { - expect(chart.select('.sub._0 .dc-tooltip._0 .dot').empty()).toBeTruthy(); - expect(chart.select('.sub._1 .dc-tooltip._0 .dot').empty()).toBeTruthy(); + it('should not render dots and tips when boolean flag is false', function () { + expect(chart.select('.dc-tooltip._0 .dot').empty()).toBeTruthy(); + expect(chart.select('.dc-tooltip._0 .dot title').empty()).toBeTruthy(); + }); + + }); + + describe('title rendering with brushOn', function () { + beforeEach(function () { + chart.brushOn(true) + .xyTipsOn(true); // default, for exposition + chart.render(); + }); + + it('should not render tips', function () { + expect(chart.select('.dc-tooltip._0 .dot').empty()).toBeTruthy(); + expect(chart.select('.dc-tooltip._0 .dot title').empty()).toBeTruthy(); + }); + + describe('with xyTipsOn always', function () { + beforeEach(function () { + chart.brushOn(true) + .xyTipsOn('always'); + chart.render(); + }); + + it('should render dots', function () { + expect(chart.select('.dc-tooltip._0 .dot').empty()).toBeFalsy(); + expect(chart.select('.dc-tooltip._0 .dot title').empty()).toBeFalsy(); + }); }); }); diff --git a/spec/utils-spec.js b/spec/utils-spec.js index 46442da0a..866878ff7 100644 --- a/spec/utils-spec.js +++ b/spec/utils-spec.js @@ -79,6 +79,18 @@ describe('dc utils', function () { var date = add(makeDate(2012, 0, 1), '10%'); expect(date.toString()).toEqual(makeDate(2012, 0, 11).toString()); }); + it('should be able to add hours to dates', function () { + var date = add(makeDate(2012, 0, 1), '24', 'hour'); + expect(date.toString()).toEqual(makeDate(2012, 0, 2).toString()); + }); + it('should be able to add weeks to dates', function () { + var date = add(makeDate(2012, 0, 1), '1', 'week'); + expect(date.toString()).toEqual(makeDate(2012, 0, 8).toString()); + }); + it('should be able to add month to dates', function () { + var date = add(makeDate(2012, 0, 1), '1', 'month'); + expect(date.toString()).toEqual(makeDate(2012, 1, 1).toString()); + }); }); describe('dc.utils.subtract', function () { var subtract; @@ -105,6 +117,17 @@ describe('dc utils', function () { var date = subtract(makeDate(2012, 0, 11), '10%'); expect(date.toString()).toEqual(makeDate(2012, 0, 1).toString()); }); + it('should be able to subtract hours from dates', function () { + var date = subtract(makeDate(2012, 0, 2), '24', 'hour'); + expect(date.toString()).toEqual(makeDate(2012, 0, 1).toString()); + }); + it('should be able to subtract week from dates', function () { + var date = subtract(makeDate(2012, 0, 8), '1', 'week'); + expect(date.toString()).toEqual(makeDate(2012, 0, 1).toString()); + }); + it('should be able to subtract month from dates', function () { + var date = subtract(makeDate(2012,1,1), '1', 'month'); + expect(date.toString()).toEqual(makeDate(2012, 0, 1).toString()); + }); }); }); - diff --git a/src/bar-chart.js b/src/bar-chart.js index 587ebf899..ab27398a7 100644 --- a/src/bar-chart.js +++ b/src/bar-chart.js @@ -99,7 +99,7 @@ dc.barChart = function (parent, chartGroup) { labels.attr('cursor', 'pointer'); } - dc.transition(labels, _chart.transitionDuration()) + dc.transition(labels, _chart.transitionDuration(), _chart.transitionDelay()) .attr('x', function (d) { var x = _chart.x()(d.x); if (!_centerBar) { @@ -120,7 +120,7 @@ dc.barChart = function (parent, chartGroup) { return _chart.label()(d); }); - dc.transition(labels.exit(), _chart.transitionDuration()) + dc.transition(labels.exit(), _chart.transitionDuration(), _chart.transitionDelay()) .attr('height', 0) .remove(); } @@ -144,7 +144,7 @@ dc.barChart = function (parent, chartGroup) { bars.on('click', _chart.onClick); } - dc.transition(bars, _chart.transitionDuration()) + dc.transition(bars, _chart.transitionDuration(), _chart.transitionDelay()) .attr('x', function (d) { var x = _chart.x()(d.x); if (_centerBar) { @@ -171,7 +171,7 @@ dc.barChart = function (parent, chartGroup) { .attr('fill', dc.pluck('data', _chart.getColor)) .select('title').text(dc.pluck('data', _chart.title(d.name))); - dc.transition(bars.exit(), _chart.transitionDuration()) + dc.transition(bars.exit(), _chart.transitionDuration(), _chart.transitionDelay()) .attr('x', function (d) { return _chart.x()(d.x); }) .attr('width', _barWidth * 0.9) .remove(); diff --git a/src/base-mixin.js b/src/base-mixin.js index 38ee9a533..22903bf1a 100644 --- a/src/base-mixin.js +++ b/src/base-mixin.js @@ -51,6 +51,8 @@ dc.baseMixin = function (_chart) { var _transitionDuration = 750; + var _transitionDelay = 0; + var _filterPrinter = dc.printers.filters; var _mandatoryAttributes = ['dimension', 'group']; @@ -609,6 +611,23 @@ dc.baseMixin = function (_chart) { return _chart; }; + /** + * Set or get the animation transition delay (in milliseconds) for this chart instance. + * @method transitionDelay + * @memberof dc.baseMixin + * @instance + * @param {Number} [delay=0] + * @return {Number} + * @return {dc.baseMixin} + */ + _chart.transitionDelay = function (delay) { + if (!arguments.length) { + return _transitionDelay; + } + _transitionDelay = delay; + return _chart; + }; + _chart._mandatoryAttributes = function (_) { if (!arguments.length) { return _mandatoryAttributes; @@ -656,7 +675,7 @@ dc.baseMixin = function (_chart) { _chart._activateRenderlets = function (event) { _listeners.pretransition(_chart); if (_chart.transitionDuration() > 0 && _svg) { - _svg.transition().duration(_chart.transitionDuration()) + _svg.transition().duration(_chart.transitionDuration()).delay(_chart.transitionDelay()) .each('end', function () { _listeners.renderlet(_chart); if (event) { diff --git a/src/box-plot.js b/src/box-plot.js index ddd5a52ad..a91b85d2f 100644 --- a/src/box-plot.js +++ b/src/box-plot.js @@ -1,3 +1,4 @@ + /** * A box plot is a chart that depicts numerical data via their quartile ranges. * @@ -146,7 +147,7 @@ dc.boxPlot = function (parent, chartGroup) { .duration(_chart.transitionDuration()) .tickFormat(_tickFormat); - var boxesG = _chart.chartBodyG().selectAll('g.box').data(_chart.data(), function (d) { return d.key; }); + var boxesG = _chart.chartBodyG().selectAll('g.box').data(_chart.data(), _chart.keyAccessor()); renderBoxes(boxesG); updateBoxes(boxesG); @@ -163,13 +164,13 @@ dc.boxPlot = function (parent, chartGroup) { .attr('transform', boxTransform) .call(_box) .on('click', function (d) { - _chart.filter(d.key); + _chart.filter(_chart.keyAccessor()(d)); _chart.redrawGroup(); }); } function updateBoxes (boxesG) { - dc.transition(boxesG, _chart.transitionDuration()) + dc.transition(boxesG, _chart.transitionDuration(), _chart.transitionDelay()) .attr('transform', boxTransform) .call(_box) .each(function () { @@ -183,13 +184,28 @@ dc.boxPlot = function (parent, chartGroup) { _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); - } - }); + if (_chart.isOrdinal()) { + _chart.g().selectAll('g.box').each(function (d) { + if (_chart.isSelectedNode(d)) { + _chart.highlightSelected(this); + } else { + _chart.fadeDeselected(this); + } + }); + } else { + var extent = _chart.brush().extent(); + var start = extent[0]; + var end = extent[1]; + var keyAccessor = _chart.keyAccessor(); + _chart.g().selectAll('g.box').each(function (d) { + var key = keyAccessor(d); + if (key < start || key >= end) { + _chart.fadeDeselected(this); + } else { + _chart.highlightSelected(this); + } + }); + } } else { _chart.g().selectAll('g.box').each(function () { _chart.resetHighlight(this); @@ -198,7 +214,7 @@ dc.boxPlot = function (parent, chartGroup) { }; _chart.isSelectedNode = function (d) { - return _chart.hasFilter(d.key); + return _chart.hasFilter(_chart.keyAccessor()(d)); }; _chart.yAxisMin = function () { diff --git a/src/bubble-chart.js b/src/bubble-chart.js index e4b82e20d..9f27a138d 100644 --- a/src/bubble-chart.js +++ b/src/bubble-chart.js @@ -33,6 +33,8 @@ dc.bubbleChart = function (parent, chartGroup) { _chart.transitionDuration(750); + _chart.transitionDelay(0); + var bubbleLocator = function (d) { return 'translate(' + (bubbleX(d)) + ',' + (bubbleY(d)) + ')'; }; @@ -114,7 +116,7 @@ dc.bubbleChart = function (parent, chartGroup) { .on('click', _chart.onClick) .attr('fill', _chart.getColor) .attr('r', 0); - dc.transition(bubbleG, _chart.transitionDuration()) + dc.transition(bubbleG, _chart.transitionDuration(), _chart.transitionDelay()) .selectAll('circle.' + _chart.BUBBLE_CLASS) .attr('r', function (d) { return _chart.bubbleR(d); @@ -129,7 +131,7 @@ dc.bubbleChart = function (parent, chartGroup) { } function updateNodes (bubbleG) { - dc.transition(bubbleG, _chart.transitionDuration()) + dc.transition(bubbleG, _chart.transitionDuration(), _chart.transitionDelay()) .attr('transform', bubbleLocator) .selectAll('circle.' + _chart.BUBBLE_CLASS) .attr('fill', _chart.getColor) diff --git a/src/bubble-mixin.js b/src/bubble-mixin.js index 1e30490fb..356f63f74 100644 --- a/src/bubble-mixin.js +++ b/src/bubble-mixin.js @@ -123,7 +123,7 @@ dc.bubbleMixin = function (_chart) { .attr('opacity', 0) .attr('pointer-events', labelPointerEvent) .text(labelFunction); - dc.transition(label, _chart.transitionDuration()) + dc.transition(label, _chart.transitionDuration(), _chart.transitionDelay()) .attr('opacity', labelOpacity); } }; @@ -133,7 +133,7 @@ dc.bubbleMixin = function (_chart) { var labels = bubbleGEnter.selectAll('text') .attr('pointer-events', labelPointerEvent) .text(labelFunction); - dc.transition(labels, _chart.transitionDuration()) + dc.transition(labels, _chart.transitionDuration(), _chart.transitionDelay()) .attr('opacity', labelOpacity); } }; diff --git a/src/bubble-overlay.js b/src/bubble-overlay.js index 901afb0f5..cf89a445f 100644 --- a/src/bubble-overlay.js +++ b/src/bubble-overlay.js @@ -48,6 +48,8 @@ dc.bubbleOverlay = function (parent, chartGroup) { _chart.transitionDuration(750); + _chart.transitionDelay(0); + _chart.radiusValueAccessor(function (d) { return d.value; }); @@ -108,7 +110,7 @@ dc.bubbleOverlay = function (parent, chartGroup) { .on('click', _chart.onClick); } - dc.transition(circle, _chart.transitionDuration()) + dc.transition(circle, _chart.transitionDuration(), _chart.transitionDelay()) .attr('r', function (d) { return _chart.bubbleR(d); }); @@ -159,7 +161,7 @@ dc.bubbleOverlay = function (parent, chartGroup) { var circle = nodeG.select('circle.' + BUBBLE_CLASS); - dc.transition(circle, _chart.transitionDuration()) + dc.transition(circle, _chart.transitionDuration(), _chart.transitionDelay()) .attr('r', function (d) { return _chart.bubbleR(d); }) diff --git a/src/composite-chart.js b/src/composite-chart.js index 261fb1996..77c7207a2 100644 --- a/src/composite-chart.js +++ b/src/composite-chart.js @@ -39,6 +39,7 @@ dc.compositeChart = function (parent, chartGroup) { _chart._mandatoryAttributes([]); _chart.transitionDuration(500); + _chart.transitionDelay(0); dc.override(_chart, '_generateG', function () { var g = this.__generateG(); @@ -58,7 +59,7 @@ dc.compositeChart = function (parent, chartGroup) { child.chartGroup(_chart.chartGroup()); child.svg(_chart.svg()); child.xUnits(_chart.xUnits()); - child.transitionDuration(_chart.transitionDuration()); + child.transitionDuration(_chart.transitionDuration(), _chart.transitionDelay()); child.brushOn(_chart.brushOn()); child.renderTitle(_chart.renderTitle()); child.elasticX(_chart.elasticX()); diff --git a/src/coordinate-grid-mixin.js b/src/coordinate-grid-mixin.js index c01d72265..b31f9f36f 100644 --- a/src/coordinate-grid-mixin.js +++ b/src/coordinate-grid-mixin.js @@ -64,6 +64,7 @@ dc.coordinateGridMixin = function (_chart) { var _xAxis = d3.svg.axis().orient('bottom'); var _xUnits = dc.units.integers; var _xAxisPadding = 0; + var _xAxisPaddingUnit = 'day'; var _xElasticity = false; var _xAxisLabel; var _xAxisLabelPadding = 0; @@ -381,9 +382,10 @@ dc.coordinateGridMixin = function (_chart) { * 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. + * 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 units being padded + * and a percentage string will be treated the same as an integer. The unit will be determined by the + * xAxisPaddingUnit variable. * @method xAxisPadding * @memberof dc.coordinateGridMixin * @instance @@ -399,6 +401,29 @@ dc.coordinateGridMixin = function (_chart) { return _chart; }; + /** + * Set or get x axis padding unit for the elastic x axis. The padding unit will determine which unit to + * use when applying xAxis padding if elasticX is turned on and if x-axis uses a time dimension; + * otherwise it is ignored. + * + * Padding unit is a string that will be used when the padding is calculated. Available parameters are + * the available d3 time intervals: + * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Intervals.md#interval d3.time.interval} + * @method xAxisPaddingUnit + * @memberof dc.coordinateGridMixin + * @instance + * @param {String} [unit='days'] + * @return {String} + * @return {dc.coordinateGridMixin} + */ + _chart.xAxisPaddingUnit = function (unit) { + if (!arguments.length) { + return _xAxisPaddingUnit; + } + _xAxisPaddingUnit = unit; + return _chart; + }; + /** * Returns the number of units displayed on the x axis using the unit measure configured by * .xUnits. @@ -519,10 +544,10 @@ dc.coordinateGridMixin = function (_chart) { axisXLab.text(_chart.xAxisLabel()); } - dc.transition(axisXG, _chart.transitionDuration()) + dc.transition(axisXG, _chart.transitionDuration(), _chart.transitionDelay()) .attr('transform', 'translate(' + _chart.margins().left + ',' + _chart._xAxisY() + ')') .call(_xAxis); - dc.transition(axisXLab, _chart.transitionDuration()) + dc.transition(axisXLab, _chart.transitionDuration(), _chart.transitionDelay()) .attr('transform', 'translate(' + (_chart.margins().left + _chart.xAxisLength() / 2) + ',' + (_chart.height() - _xAxisLabelPadding) + ')'); }; @@ -555,11 +580,11 @@ dc.coordinateGridMixin = function (_chart) { }) .attr('y2', 0) .attr('opacity', 0); - dc.transition(linesGEnter, _chart.transitionDuration()) + dc.transition(linesGEnter, _chart.transitionDuration(), _chart.transitionDelay()) .attr('opacity', 1); // update - dc.transition(lines, _chart.transitionDuration()) + dc.transition(lines, _chart.transitionDuration(), _chart.transitionDelay()) .attr('x1', function (d) { return _x(d); }) @@ -640,7 +665,7 @@ dc.coordinateGridMixin = function (_chart) { if (text && axisYLab.text() !== text) { axisYLab.text(text); } - dc.transition(axisYLab, _chart.transitionDuration()) + dc.transition(axisYLab, _chart.transitionDuration(), _chart.transitionDelay()) .attr('transform', 'translate(' + labelXPosition + ',' + labelYPosition + '),rotate(' + rotation + ')'); }; @@ -652,7 +677,7 @@ dc.coordinateGridMixin = function (_chart) { .attr('transform', 'translate(' + position + ',' + _chart.margins().top + ')'); } - dc.transition(axisYG, _chart.transitionDuration()) + dc.transition(axisYG, _chart.transitionDuration(), _chart.transitionDelay()) .attr('transform', 'translate(' + position + ',' + _chart.margins().top + ')') .call(axis); }; @@ -692,11 +717,11 @@ dc.coordinateGridMixin = function (_chart) { return scale(d); }) .attr('opacity', 0); - dc.transition(linesGEnter, _chart.transitionDuration()) + dc.transition(linesGEnter, _chart.transitionDuration(), _chart.transitionDelay()) .attr('opacity', 1); // update - dc.transition(lines, _chart.transitionDuration()) + dc.transition(lines, _chart.transitionDuration(), _chart.transitionDelay()) .attr('x1', 1) .attr('y1', function (d) { return scale(d); @@ -849,7 +874,7 @@ dc.coordinateGridMixin = function (_chart) { var min = d3.min(_chart.data(), function (e) { return _chart.keyAccessor()(e); }); - return dc.utils.subtract(min, _xAxisPadding); + return dc.utils.subtract(min, _xAxisPadding, _xAxisPaddingUnit); }; /** @@ -863,7 +888,7 @@ dc.coordinateGridMixin = function (_chart) { var max = d3.max(_chart.data(), function (e) { return _chart.keyAccessor()(e); }); - return dc.utils.add(max, _xAxisPadding); + return dc.utils.add(max, _xAxisPadding, _xAxisPaddingUnit); }; /** @@ -1057,7 +1082,7 @@ dc.coordinateGridMixin = function (_chart) { _chart.brush().extent(_chart.filter()); } - var gBrush = dc.optionalTransition(doTransition, _chart.transitionDuration())(g.select('g.brush')); + var gBrush = dc.optionalTransition(doTransition, _chart.transitionDuration(), _chart.transitionDelay())(g.select('g.brush')); _chart.setBrushY(gBrush); gBrush.call(_chart.brush() .x(_chart.x()) diff --git a/src/core.js b/src/core.js index d87969a69..980d55011 100644 --- a/src/core.js +++ b/src/core.js @@ -259,33 +259,50 @@ dc.redrawAll = function (group) { * If this boolean is set truthy, all transitions will be disabled, and changes to the charts will happen * immediately * @memberof dc - * @method disableTransitions + * @member disableTransitions * @type {Boolean} * @default false */ dc.disableTransitions = false; -dc.transition = function (selections, duration, callback, name) { - if (duration <= 0 || duration === undefined || dc.disableTransitions) { - return selections; +/** + * Start a transition on a selection if transitions are globally enabled + * ({@link dc.disableTransitions} is false) and the duration is greater than zero; otherwise return + * the selection. Since most operations are the same on a d3 selection and a d3 transition, this + * allows a common code path for both cases. + * @memberof dc + * @method transition + * @param {d3.selection} selection - the selection to be transitioned + * @param {Number|Function} [duration=250] - the duration of the transition in milliseconds, a + * function returning the duration, or 0 for no transition + * @param {Number|Function} [delay] - the delay of the transition in milliseconds, or a function + * returning the delay, or 0 for no delay + * @param {String} [name] - the name of the transition (if concurrent transitions on the same + * elements are needed) + * @returns {d3.transition|d3.selection} + */ +dc.transition = function (selection, duration, delay, name) { + if (dc.disableTransitions || duration <= 0) { + return selection; } - var s = selections - .transition(name) - .duration(duration); + var s = selection.transition(name); - if (typeof(callback) === 'function') { - callback(s); + if (duration >= 0 || duration !== undefined) { + s = s.duration(duration); + } + if (delay >= 0 || delay !== undefined) { + s = s.delay(delay); } return s; }; /* somewhat silly, but to avoid duplicating logic */ -dc.optionalTransition = function (enable, duration, callback, name) { +dc.optionalTransition = function (enable, duration, delay, name) { if (enable) { return function (selection) { - return dc.transition(selection, duration, callback, name); + return dc.transition(selection, duration, delay, name); }; } else { return function (selection) { diff --git a/src/d3.box.js b/src/d3.box.js index f8f198d19..5aaa9df7f 100644 --- a/src/d3.box.js +++ b/src/d3.box.js @@ -6,6 +6,7 @@ var width = 1, height = 1, duration = 0, + delay = 0, domain = null, value = Number, whiskers = boxWhiskers, @@ -62,20 +63,25 @@ .attr('x2', width / 2) .attr('y2', function (d) { return x0(d[1]); }) .style('opacity', 1e-6) - .transition() + .transition() .duration(duration) + .delay(delay) .style('opacity', 1) .attr('y1', function (d) { return x1(d[0]); }) .attr('y2', function (d) { return x1(d[1]); }); center.transition() .duration(duration) + .delay(delay) .style('opacity', 1) + .attr('x1', width / 2) + .attr('x2', width / 2) .attr('y1', function (d) { return x1(d[0]); }) .attr('y2', function (d) { return x1(d[1]); }); center.exit().transition() .duration(duration) + .delay(delay) .style('opacity', 1e-6) .attr('y1', function (d) { return x1(d[0]); }) .attr('y2', function (d) { return x1(d[1]); }) @@ -93,11 +99,14 @@ .attr('height', function (d) { return x0(d[0]) - x0(d[2]); }) .transition() .duration(duration) + .delay(delay) .attr('y', function (d) { return x1(d[2]); }) .attr('height', function (d) { return x1(d[0]) - x1(d[2]); }); box.transition() .duration(duration) + .delay(delay) + .attr('width', width) .attr('y', function (d) { return x1(d[2]); }) .attr('height', function (d) { return x1(d[0]) - x1(d[2]); }); @@ -113,11 +122,15 @@ .attr('y2', x0) .transition() .duration(duration) + .delay(delay) .attr('y1', x1) .attr('y2', x1); medianLine.transition() .duration(duration) + .delay(delay) + .attr('x1', 0) + .attr('x2', width) .attr('y1', x1) .attr('y2', x1); @@ -134,18 +147,23 @@ .style('opacity', 1e-6) .transition() .duration(duration) + .delay(delay) .attr('y1', x1) .attr('y2', x1) .style('opacity', 1); whisker.transition() .duration(duration) + .delay(delay) + .attr('x1', 0) + .attr('x2', width) .attr('y1', x1) .attr('y2', x1) .style('opacity', 1); whisker.exit().transition() .duration(duration) + .delay(delay) .attr('y1', x1) .attr('y2', x1) .style('opacity', 1e-6) @@ -163,16 +181,20 @@ .style('opacity', 1e-6) .transition() .duration(duration) + .delay(delay) .attr('cy', function (i) { return x1(d[i]); }) .style('opacity', 1); outlier.transition() .duration(duration) + .delay(delay) + .attr('cx', width / 2) .attr('cy', function (i) { return x1(d[i]); }) .style('opacity', 1); outlier.exit().transition() .duration(duration) + .delay(delay) .attr('cy', function (i) { return x1(d[i]); }) .style('opacity', 1e-6) .remove(); @@ -194,11 +216,14 @@ .text(format) .transition() .duration(duration) + .delay(delay) .attr('y', x1); boxTick.transition() .duration(duration) + .delay(delay) .text(format) + .attr('x', function (d, i) { return i & 1 ? width : 0; }) .attr('y', x1); // Update whisker ticks. These are handled separately from the box @@ -217,17 +242,21 @@ .style('opacity', 1e-6) .transition() .duration(duration) + .delay(delay) .attr('y', x1) .style('opacity', 1); whiskerTick.transition() .duration(duration) + .delay(delay) .text(format) + .attr('x', width) .attr('y', x1) .style('opacity', 1); whiskerTick.exit().transition() .duration(duration) + .delay(delay) .attr('y', x1) .style('opacity', 1e-6) .remove(); diff --git a/src/geo-choropleth-chart.js b/src/geo-choropleth-chart.js index 3afd0307a..c0c153fbe 100644 --- a/src/geo-choropleth-chart.js +++ b/src/geo-choropleth-chart.js @@ -140,7 +140,7 @@ dc.geoChoroplethChart = function (parent, chartGroup) { return _chart.onClick(d, layerIndex); }); - dc.transition(paths, _chart.transitionDuration()).attr('fill', function (d, i) { + dc.transition(paths, _chart.transitionDuration(), _chart.transitionDelay()).attr('fill', function (d, i) { return _chart.getColor(data[geoJson(layerIndex).keyAccessor(d)], i); }); } diff --git a/src/heatmap.js b/src/heatmap.js index cd89d7cb9..dc060b2ec 100644 --- a/src/heatmap.js +++ b/src/heatmap.js @@ -210,7 +210,7 @@ dc.heatMap = function (parent, chartGroup) { boxes.selectAll('title').text(_chart.title()); } - dc.transition(boxes.selectAll('rect'), _chart.transitionDuration()) + dc.transition(boxes.selectAll('rect'), _chart.transitionDuration(), _chart.transitionDelay()) .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) @@ -233,7 +233,7 @@ dc.heatMap = function (parent, chartGroup) { .attr('dy', 12) .on('click', _chart.xAxisOnClick()) .text(_chart.colsLabel()); - dc.transition(gColsText, _chart.transitionDuration()) + dc.transition(gColsText, _chart.transitionDuration(), _chart.transitionDelay()) .text(_chart.colsLabel()) .attr('x', function (d) { return cols(d) + boxWidth / 2; }) .attr('y', _chart.effectiveHeight()); @@ -250,7 +250,7 @@ dc.heatMap = function (parent, chartGroup) { .attr('dx', -2) .on('click', _chart.yAxisOnClick()) .text(_chart.rowsLabel()); - dc.transition(gRowsText, _chart.transitionDuration()) + dc.transition(gRowsText, _chart.transitionDuration(), _chart.transitionDelay()) .text(_chart.rowsLabel()) .attr('y', function (d) { return rows(d) + boxHeight / 2; }); gRowsText.exit().remove(); diff --git a/src/line-chart.js b/src/line-chart.js index 3cd8e280c..1e3988ad7 100644 --- a/src/line-chart.js +++ b/src/line-chart.js @@ -46,6 +46,7 @@ dc.lineChart = function (parent, chartGroup) { var _xyTipsOn = true; _chart.transitionDuration(500); + _chart.transitionDelay(0); _chart._rangeBandPadding(1); _chart.plotData = function () { @@ -211,7 +212,7 @@ dc.lineChart = function (parent, chartGroup) { path.attr('stroke-dasharray', _dashStyle); } - dc.transition(layers.select('path.line'), _chart.transitionDuration()) + dc.transition(layers.select('path.line'), _chart.transitionDuration(), _chart.transitionDelay()) //.ease('linear') .attr('stroke', colors) .attr('d', function (d) { @@ -244,7 +245,7 @@ dc.lineChart = function (parent, chartGroup) { return safeD(area(d.values)); }); - dc.transition(layers.select('path.area'), _chart.transitionDuration()) + dc.transition(layers.select('path.area'), _chart.transitionDuration(), _chart.transitionDelay()) //.ease('linear') .attr('fill', colors) .attr('d', function (d) { @@ -258,7 +259,7 @@ dc.lineChart = function (parent, chartGroup) { } function drawDots (chartBody, layers) { - if (!_chart.brushOn() && _chart.xyTipsOn()) { + if (_chart.xyTipsOn() === 'always' || (!_chart.brushOn() && _chart.xyTipsOn())) { var tooltipListClass = TOOLTIP_G_CLASS + '-list'; var tooltips = chartBody.select('g.' + tooltipListClass); diff --git a/src/number-display.js b/src/number-display.js index 514fdebf1..209b48b09 100644 --- a/src/number-display.js +++ b/src/number-display.js @@ -84,6 +84,7 @@ dc.numberDisplay = function (parent, chartGroup) { }); _chart.transitionDuration(250); // good default + _chart.transitionDelay(0); _chart._doRender = function () { var newValue = _chart.value(), @@ -98,6 +99,7 @@ dc.numberDisplay = function (parent, chartGroup) { span.transition() .duration(_chart.transitionDuration()) + .delay(_chart.transitionDelay()) .ease('quad-out-in') .tween('text', function () { // [XA] don't try and interpolate from Infinity, else this breaks. diff --git a/src/pie-chart.js b/src/pie-chart.js index b5f52aa08..d8d2eca0a 100644 --- a/src/pie-chart.js +++ b/src/pie-chart.js @@ -68,6 +68,7 @@ dc.pieChart = function (parent, chartGroup) { _chart.renderLabel(true); _chart.transitionDuration(350); + _chart.transitionDelay(0); _chart._doRender = function () { _chart.resetSvg(); @@ -120,7 +121,7 @@ dc.pieChart = function (parent, chartGroup) { highlightFilter(); - dc.transition(_g, _chart.transitionDuration()) + dc.transition(_g, _chart.transitionDuration(), _chart.transitionDelay()) .attr('transform', 'translate(' + _chart.cx() + ',' + _chart.cy() + ')'); } } @@ -153,9 +154,10 @@ dc.pieChart = function (parent, chartGroup) { return safeArc(d, i, arc); }); - dc.transition(slicePath, _chart.transitionDuration(), function (s) { - s.attrTween('d', tweenPie); - }); + var transition = dc.transition(slicePath, _chart.transitionDuration(), _chart.transitionDelay()); + if (transition.attrTween) { + transition.attrTween('d', tweenPie); + } } function createTitles (slicesEnter) { @@ -179,7 +181,7 @@ dc.pieChart = function (parent, chartGroup) { function positionLabels (labels, arc) { _chart._applyLabelText(labels); - dc.transition(labels, _chart.transitionDuration()) + dc.transition(labels, _chart.transitionDuration(), _chart.transitionDelay()) .attr('transform', function (d) { return labelPosition(d, arc); }) @@ -239,13 +241,14 @@ dc.pieChart = function (parent, chartGroup) { var arc2 = d3.svg.arc() .outerRadius(_radius - _externalRadiusPadding + _externalLabelRadius) .innerRadius(_radius - _externalRadiusPadding); - var transition = dc.transition(polyline, _chart.transitionDuration()); + var transition = dc.transition(polyline, _chart.transitionDuration(), _chart.transitionDelay()); // this is one rare case where d3.selection differs from d3.transition if (transition.attrTween) { transition .attrTween('points', function (d) { - this._current = this._current || d; - var interpolate = d3.interpolate(this._current, d); + var current = this._current || d; + current = {startAngle: current.startAngle, endAngle: current.endAngle}; + var interpolate = d3.interpolate(current, d); this._current = interpolate(0); return function (t) { var d2 = interpolate(t); @@ -276,10 +279,11 @@ dc.pieChart = function (parent, chartGroup) { .attr('d', function (d, i) { return safeArc(d, i, arc); }); - dc.transition(slicePaths, _chart.transitionDuration(), - function (s) { - s.attrTween('d', tweenPie); - }).attr('fill', fill); + var transition = dc.transition(slicePaths, _chart.transitionDuration(), _chart.transitionDelay()); + if (transition.attrTween) { + transition.attrTween('d', tweenPie); + } + transition.attr('fill', fill); } function updateLabels (pieData, arc) { diff --git a/src/row-chart.js b/src/row-chart.js index 1ac99e7a4..f592bb978 100644 --- a/src/row-chart.js +++ b/src/row-chart.js @@ -75,7 +75,7 @@ dc.rowChart = function (parent, chartGroup) { } axisG.attr('transform', 'translate(0, ' + _chart.effectiveHeight() + ')'); - dc.transition(axisG, _chart.transitionDuration()) + dc.transition(axisG, _chart.transitionDuration(), _chart.transitionDelay()) .call(_xAxis); } @@ -195,7 +195,7 @@ dc.rowChart = function (parent, chartGroup) { return (_chart.hasFilter()) ? isSelectedRow(d) : false; }); - dc.transition(rect, _chart.transitionDuration()) + dc.transition(rect, _chart.transitionDuration(), _chart.transitionDelay()) .attr('width', function (d) { return Math.abs(rootValue() - _x(_chart.valueAccessor()(d))); }) @@ -237,7 +237,7 @@ dc.rowChart = function (parent, chartGroup) { .text(function (d) { return _chart.label()(d); }); - dc.transition(lab, _chart.transitionDuration()) + dc.transition(lab, _chart.transitionDuration(), _chart.transitionDelay()) .attr('transform', translateX); } if (_chart.renderTitleLabel()) { @@ -253,7 +253,7 @@ dc.rowChart = function (parent, chartGroup) { .text(function (d) { return _chart.title()(d); }); - dc.transition(titlelab, _chart.transitionDuration()) + dc.transition(titlelab, _chart.transitionDuration(), _chart.transitionDelay()) .attr('transform', translateX); } } diff --git a/src/scatter-plot.js b/src/scatter-plot.js index a6fdca7c3..511f5ff16 100644 --- a/src/scatter-plot.js +++ b/src/scatter-plot.js @@ -79,7 +79,7 @@ dc.scatterPlot = function (parent, chartGroup) { _filtered[i] = !_chart.filter() || _chart.filter().isFiltered([d.key[0], d.key[1]]); }); - dc.transition(symbols, _chart.transitionDuration()) + dc.transition(symbols, _chart.transitionDuration(), _chart.transitionDelay()) .attr('opacity', function (d, i) { return !_existenceAccessor(d) ? 0 : _filtered[i] ? 1 : _chart.excludedOpacity(); @@ -92,7 +92,7 @@ dc.scatterPlot = function (parent, chartGroup) { .attr('transform', _locator) .attr('d', _symbol); - dc.transition(symbols.exit(), _chart.transitionDuration()) + dc.transition(symbols.exit(), _chart.transitionDuration(), _chart.transitionDelay()) .attr('opacity', 0).remove(); }; @@ -284,7 +284,7 @@ dc.scatterPlot = function (parent, chartGroup) { }); var oldSize = _symbol.size(); _symbol.size(Math.pow(size, 2)); - dc.transition(symbols, _chart.transitionDuration()).attr('d', _symbol); + dc.transition(symbols, _chart.transitionDuration(), _chart.transitionDelay()).attr('d', _symbol); _symbol.size(oldSize); } diff --git a/src/utils.js b/src/utils.js index 00fbc0f35..a04338e8d 100644 --- a/src/utils.js +++ b/src/utils.js @@ -130,7 +130,7 @@ dc.utils.printSingleValue.fformat = d3.format('.2f'); * @param {Number} r * @returns {String|Date|Number} */ -dc.utils.add = function (l, r) { +dc.utils.add = function (l, r, t) { if (typeof r === 'string') { r = r.replace('%', ''); } @@ -139,10 +139,8 @@ dc.utils.add = function (l, r) { if (typeof r === 'string') { r = +r; } - var d = new Date(); - d.setTime(l.getTime()); - d.setDate(l.getDate() + r); - return d; + t = t || 'day'; + return d3.time[t].offset(l, r); } else if (typeof r === 'string') { var percentage = (+r / 100); return l > 0 ? l * (1 + percentage) : l * (1 - percentage); @@ -162,7 +160,7 @@ dc.utils.add = function (l, r) { * @param {Number} r * @returns {String|Date|Number} */ -dc.utils.subtract = function (l, r) { +dc.utils.subtract = function (l, r, t) { if (typeof r === 'string') { r = r.replace('%', ''); } @@ -171,10 +169,8 @@ dc.utils.subtract = function (l, r) { if (typeof r === 'string') { r = +r; } - var d = new Date(); - d.setTime(l.getTime()); - d.setDate(l.getDate() - r); - return d; + t = t || 'day'; + return d3.time[t].offset(l, -r); } else if (typeof r === 'string') { var percentage = (+r / 100); return l < 0 ? l * (1 + percentage) : l * (1 - percentage);