From 2459ee29fd5800e86aedbc20b5a354e8330d59b8 Mon Sep 17 00:00:00 2001
From: Gordon Woodhull <gordon@woodhull.com>
Date: Fri, 25 Nov 2016 15:24:04 -0500
Subject: [PATCH 01/17] update saucelabs edge

it was timing out with the old version - perhaps because edge has
two different version schemes and saucelabs now prefers the lower-numbered one?

so although the version number is 6 lower, it's about a year later :-/
---
 Gruntfile.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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'
                         }
                     ],

From a035b195daee994fafef90d4cff0041089e6fa24 Mon Sep 17 00:00:00 2001
From: Gordon Woodhull <gordon@woodhull.com>
Date: Fri, 25 Nov 2016 16:31:02 -0500
Subject: [PATCH 02/17] remove callback from dc.transition

in preparation for #1116

this was a confusing parameter, invoked when it was decided that there
would be a transition. since we used it in exactly the case where we
were calling attrTween, and we already had to check that in another
place, seems clearer just to check that every time.

(it was also going to make it impossible to add an int/function-typed
parameter)
---
 spec/core-spec.js | 16 ----------------
 src/core.js       |  6 +-----
 src/pie-chart.js  | 16 +++++++++-------
 3 files changed, 10 insertions(+), 28 deletions(-)

diff --git a/spec/core-spec.js b/spec/core-spec.js
index 85a6889b1..4e2c6dc5f 100644
--- a/spec/core-spec.js
+++ b/spec/core-spec.js
@@ -119,14 +119,6 @@ describe('dc.core', function () {
                 expect(selections.transition).toHaveBeenCalled();
                 expect(selections.duration).toHaveBeenCalled();
             });
-
-            it('transition callback should be triggered', function () {
-                var triggered = false;
-                dc.transition(selections, 100, function () {
-                    triggered = true;
-                });
-                expect(triggered).toBeTruthy();
-            });
         });
 
         describe('skip', function () {
@@ -135,14 +127,6 @@ describe('dc.core', function () {
                 expect(selections.transition).not.toHaveBeenCalled();
                 expect(selections.duration).not.toHaveBeenCalled();
             });
-
-            it('transition callback should not be triggered', function () {
-                var triggered = false;
-                dc.transition(selections, 0, function () {
-                    triggered = true;
-                });
-                expect(triggered).toBeFalsy();
-            });
         });
     });
 
diff --git a/src/core.js b/src/core.js
index d87969a69..947ecc7e6 100644
--- a/src/core.js
+++ b/src/core.js
@@ -265,7 +265,7 @@ dc.redrawAll = function (group) {
  */
 dc.disableTransitions = false;
 
-dc.transition = function (selections, duration, callback, name) {
+dc.transition = function (selections, duration, name) {
     if (duration <= 0 || duration === undefined || dc.disableTransitions) {
         return selections;
     }
@@ -274,10 +274,6 @@ dc.transition = function (selections, duration, callback, name) {
         .transition(name)
         .duration(duration);
 
-    if (typeof(callback) === 'function') {
-        callback(s);
-    }
-
     return s;
 };
 
diff --git a/src/pie-chart.js b/src/pie-chart.js
index b5f52aa08..6c12e7d53 100644
--- a/src/pie-chart.js
+++ b/src/pie-chart.js
@@ -153,9 +153,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());
+        if (transition.attrTween) {
+            transition.attrTween('d', tweenPie);
+        }
     }
 
     function createTitles (slicesEnter) {
@@ -276,10 +277,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());
+        if (transition.attrTween) {
+            transition.attrTween('d', tweenPie);
+        }
+        transition.attr('fill', fill);
     }
 
     function updateLabels (pieData, arc) {

From 457f522bd296c0edb1c2feab0793b26a96427a96 Mon Sep 17 00:00:00 2001
From: Gordon Woodhull <gordon@woodhull.com>
Date: Fri, 25 Nov 2016 16:35:16 -0500
Subject: [PATCH 03/17] test disableTransitions

---
 spec/core-spec.js | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/spec/core-spec.js b/spec/core-spec.js
index 4e2c6dc5f..bd2572bc6 100644
--- a/spec/core-spec.js
+++ b/spec/core-spec.js
@@ -127,6 +127,17 @@ describe('dc.core', function () {
                 expect(selections.transition).not.toHaveBeenCalled();
                 expect(selections.duration).not.toHaveBeenCalled();
             });
+
+            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;
+            });
         });
     });
 

From 675628b20da58ae704e1627e708f560587bb0393 Mon Sep 17 00:00:00 2001
From: Mauricio Bustos <m@bustos.org>
Date: Fri, 11 Mar 2016 09:17:40 +0100
Subject: [PATCH 04/17] transition delay update

---
 spec/box-plot-spec.js              |  1 +
 spec/coordinate-grid-chart-spec.js |  7 +++++++
 spec/core-spec.js                  | 10 ++++++++--
 src/bar-chart.js                   |  8 ++++----
 src/base-mixin.js                  | 21 ++++++++++++++++++++-
 src/box-plot.js                    |  3 ++-
 src/bubble-chart.js                |  6 ++++--
 src/bubble-mixin.js                |  4 ++--
 src/bubble-overlay.js              |  6 ++++--
 src/composite-chart.js             |  3 ++-
 src/coordinate-grid-mixin.js       | 18 +++++++++---------
 src/core.js                        | 15 ++++++++-------
 src/d3.box.js                      | 21 ++++++++++++++++++++-
 src/geo-choropleth-chart.js        |  2 +-
 src/heatmap.js                     |  6 +++---
 src/line-chart.js                  |  5 +++--
 src/number-display.js              |  2 ++
 src/pie-chart.js                   | 11 ++++++-----
 src/row-chart.js                   |  8 ++++----
 src/scatter-plot.js                |  6 +++---
 20 files changed, 113 insertions(+), 50 deletions(-)

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 bd2572bc6..ba48ed140 100644
--- a/spec/core-spec.js
+++ b/spec/core-spec.js
@@ -107,25 +107,31 @@ 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();
             });
         });
 
         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 should not be activated with dc.disableTransitions', function () {
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..fee20afa6 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.
  *
@@ -169,7 +170,7 @@ dc.boxPlot = function (parent, chartGroup) {
     }
 
     function updateBoxes (boxesG) {
-        dc.transition(boxesG, _chart.transitionDuration())
+        dc.transition(boxesG, _chart.transitionDuration(), _chart.transitionDelay())
             .attr('transform', boxTransform)
             .call(_box)
             .each(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..117162d44 100644
--- a/src/coordinate-grid-mixin.js
+++ b/src/coordinate-grid-mixin.js
@@ -519,10 +519,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 +555,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 +640,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 +652,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 +692,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);
@@ -1057,7 +1057,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 947ecc7e6..a2ec2f626 100644
--- a/src/core.js
+++ b/src/core.js
@@ -265,23 +265,24 @@ dc.redrawAll = function (group) {
  */
 dc.disableTransitions = false;
 
-dc.transition = function (selections, duration, name) {
-    if (duration <= 0 || duration === undefined || dc.disableTransitions) {
+dc.transition = function (selections, duration, delay, name) {
+    if (dc.disableTransitions || duration <= 0 || duration === undefined) {
         return selections;
     }
 
-    var s = selections
-        .transition(name)
-        .duration(duration);
+    var s = selections.transition(name);
+
+    if (duration >= 0 || duration !== undefined) {s = s.duration(duration);}
+    if (delay >= 0 || delay !== undefined) {s = s.delay(delay);}
 
     return s;
 };
 
 /* somewhat silly, but to avoid duplicating logic */
-dc.optionalTransition = function (enable, duration, callback, name) {
+dc.optionalTransition = function (enable, duration, delay, callback, name) {
     if (enable) {
         return function (selection) {
-            return dc.transition(selection, duration, callback, name);
+            return dc.transition(selection, duration, delay, callback, name);
         };
     } else {
         return function (selection) {
diff --git a/src/d3.box.js b/src/d3.box.js
index f8f198d19..5e3e760de 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,23 @@
                     .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('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 +97,13 @@
                     .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('y', function (d) { return x1(d[2]); })
                     .attr('height', function (d) { return x1(d[0]) - x1(d[2]); });
 
@@ -113,11 +119,13 @@
                     .attr('y2', x0)
                     .transition()
                     .duration(duration)
+                    .delay(delay)
                     .attr('y1', x1)
                     .attr('y2', x1);
 
                 medianLine.transition()
                     .duration(duration)
+                    .delay(delay)
                     .attr('y1', x1)
                     .attr('y2', x1);
 
@@ -134,18 +142,21 @@
                     .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('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 +174,19 @@
                     .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('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,10 +208,12 @@
                     .text(format)
                     .transition()
                     .duration(duration)
+                    .delay(delay)
                     .attr('y', x1);
 
                 boxTick.transition()
                     .duration(duration)
+                    .delay(delay)
                     .text(format)
                     .attr('y', x1);
 
@@ -217,17 +233,20 @@
                     .style('opacity', 1e-6)
                     .transition()
                     .duration(duration)
+                    .delay(delay)
                     .attr('y', x1)
                     .style('opacity', 1);
 
                 whiskerTick.transition()
                     .duration(duration)
+                    .delay(delay)
                     .text(format)
                     .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..3ac9794bf 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) {
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 6c12e7d53..b586b7491 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,7 +154,7 @@ dc.pieChart = function (parent, chartGroup) {
                 return safeArc(d, i, arc);
             });
 
-        var transition = dc.transition(slicePath, _chart.transitionDuration());
+        var transition = dc.transition(slicePath, _chart.transitionDuration(), _chart.transitionDelay());
         if (transition.attrTween) {
             transition.attrTween('d', tweenPie);
         }
@@ -180,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);
             })
@@ -240,7 +241,7 @@ 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
@@ -277,7 +278,7 @@ dc.pieChart = function (parent, chartGroup) {
             .attr('d', function (d, i) {
                 return safeArc(d, i, arc);
             });
-        var transition = dc.transition(slicePaths, _chart.transitionDuration());
+        var transition = dc.transition(slicePaths, _chart.transitionDuration(), _chart.transitionDelay());
         if (transition.attrTween) {
             transition.attrTween('d', tweenPie);
         }
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);
     }
 

From 68f22e5d0b4dc87f9c16c997a9e082ea97c02771 Mon Sep 17 00:00:00 2001
From: Gordon Woodhull <gordon@woodhull.com>
Date: Fri, 25 Nov 2016 17:06:39 -0500
Subject: [PATCH 05/17] i'm surprised we don't lint this

---
 src/core.js | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/src/core.js b/src/core.js
index a2ec2f626..eedfe0b77 100644
--- a/src/core.js
+++ b/src/core.js
@@ -272,8 +272,12 @@ dc.transition = function (selections, duration, delay, name) {
 
     var s = selections.transition(name);
 
-    if (duration >= 0 || duration !== undefined) {s = s.duration(duration);}
-    if (delay >= 0 || delay !== undefined) {s = s.delay(delay);}
+    if (duration >= 0 || duration !== undefined) {
+        s = s.duration(duration);
+    }
+    if (delay >= 0 || delay !== undefined) {
+        s = s.delay(delay);
+    }
 
     return s;
 };

From 6cc2962ae6a5d87156fbe12ce7f66a47f4b1f55f Mon Sep 17 00:00:00 2001
From: Gordon Woodhull <gordon@woodhull.com>
Date: Fri, 25 Nov 2016 17:24:30 -0500
Subject: [PATCH 06/17] add tests for dc.transition parameters

---
 spec/core-spec.js | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/spec/core-spec.js b/spec/core-spec.js
index ba48ed140..d10b13813 100644
--- a/spec/core-spec.js
+++ b/spec/core-spec.js
@@ -123,6 +123,13 @@ describe('dc.core', function () {
                 expect(selections.transition).toHaveBeenCalled();
                 expect(selections.duration).toHaveBeenCalled();
                 expect(selections.delay).toHaveBeenCalled();
+                expect(selections.duration).toHaveBeenCalledWith(100);
+                expect(selections.delay).toHaveBeenCalledWith(100);
+            });
+            it('with name', function () {
+                dc.transition(selections, 100, 100, 'transition-name');
+                expect(selections.transition).toHaveBeenCalled();
+                expect(selections.transition).toHaveBeenCalledWith('transition-name');
             });
         });
 
@@ -145,6 +152,18 @@ describe('dc.core', 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();
+            });
+        });
     });
 
     describe('units', function () {

From c30d1ef95ad3978e28529df6626dedc5c3fb0d07 Mon Sep 17 00:00:00 2001
From: Gordon Woodhull <gordon@woodhull.com>
Date: Fri, 25 Nov 2016 17:49:09 -0500
Subject: [PATCH 07/17] document dc.transition

fix dc.disableTransitions doc
remove callback from dc.optionalTransition
---
 src/core.js | 30 +++++++++++++++++++++++-------
 1 file changed, 23 insertions(+), 7 deletions(-)

diff --git a/src/core.js b/src/core.js
index eedfe0b77..980d55011 100644
--- a/src/core.js
+++ b/src/core.js
@@ -259,18 +259,34 @@ 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, delay, name) {
-    if (dc.disableTransitions || duration <= 0 || duration === undefined) {
-        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);
+    var s = selection.transition(name);
 
     if (duration >= 0 || duration !== undefined) {
         s = s.duration(duration);
@@ -283,10 +299,10 @@ dc.transition = function (selections, duration, delay, name) {
 };
 
 /* somewhat silly, but to avoid duplicating logic */
-dc.optionalTransition = function (enable, duration, delay, callback, name) {
+dc.optionalTransition = function (enable, duration, delay, name) {
     if (enable) {
         return function (selection) {
-            return dc.transition(selection, duration, delay, callback, name);
+            return dc.transition(selection, duration, delay, name);
         };
     } else {
         return function (selection) {

From d2fb8200c3aafc99c7f55f99ba08f573f4bb15a0 Mon Sep 17 00:00:00 2001
From: Gordon Woodhull <gordon@woodhull.com>
Date: Fri, 25 Nov 2016 17:58:22 -0500
Subject: [PATCH 08/17] author; changelog

---
 AUTHORS      | 1 +
 Changelog.md | 4 ++++
 2 files changed, 5 insertions(+)

diff --git a/AUTHORS b/AUTHORS
index 726d9a630..329add2e5 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -82,3 +82,4 @@ Wei Ding <weiding8911@gmail.com>
 Michael Dougherty <maackle.d@gmail.com>
 Paul Mach <paulmach@gmail.com>
 Fil <fil@rezo.net>
+Mauricio Bustos <m@bustos.org>
diff --git a/Changelog.md b/Changelog.md
index 7e2e87458..cd8f65abb 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,4 +1,8 @@
 # 2.0 Series
+## 2.0.0 beta 33
+* 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))
 * Improved implementation of alignYAxes, by Mohamed Gazal and Gordon Woodhull ([#1033](https://github.com/dc-js/dc.js/pull/1033))

From f393074e6e606e976ddf21818323038b43b57a98 Mon Sep 17 00:00:00 2001
From: Matt Traynham <mrt6467@alum.uncw.edu>
Date: Mon, 12 Oct 2015 13:03:56 -0400
Subject: [PATCH 09/17] Fix key accessor when binding g.box.  Fix filter
 highlighting when brushing enabled.

---
 src/box-plot.js | 29 +++++++++++++++++++++--------
 1 file changed, 21 insertions(+), 8 deletions(-)

diff --git a/src/box-plot.js b/src/box-plot.js
index fee20afa6..2d5cfe80d 100644
--- a/src/box-plot.js
+++ b/src/box-plot.js
@@ -147,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);
@@ -184,13 +184,26 @@ 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];
+                _chart.g().selectAll('g.box').each(function (d) {
+                    if (d.key < start || d.key >= end) {
+                        _chart.fadeDeselected(this);
+                    } else {
+                        _chart.highlightSelected(this);
+                    }
+                });
+            }
         } else {
             _chart.g().selectAll('g.box').each(function () {
                 _chart.resetHighlight(this);

From 8210134d7be43f1c584dcb143db37b8b46ce12e9 Mon Sep 17 00:00:00 2001
From: Matt Traynham <mrt6467@alum.uncw.edu>
Date: Mon, 12 Oct 2015 13:42:54 -0400
Subject: [PATCH 10/17] Fixing my own commit with the keyAccessor problem

---
 src/box-plot.js | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/src/box-plot.js b/src/box-plot.js
index 2d5cfe80d..a91b85d2f 100644
--- a/src/box-plot.js
+++ b/src/box-plot.js
@@ -164,7 +164,7 @@ 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();
             });
     }
@@ -196,8 +196,10 @@ dc.boxPlot = function (parent, chartGroup) {
                 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) {
-                    if (d.key < start || d.key >= end) {
+                    var key = keyAccessor(d);
+                    if (key < start || key >= end) {
                         _chart.fadeDeselected(this);
                     } else {
                         _chart.highlightSelected(this);
@@ -212,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 () {

From 6a16e635170ece8dc7b9b77a35d8ce17abf31821 Mon Sep 17 00:00:00 2001
From: Matt Traynham <mrt6467@alum.uncw.edu>
Date: Thu, 15 Oct 2015 10:54:07 -0400
Subject: [PATCH 11/17] When the domain of the chart changes, we need to keep
 the width attributes of the box-whiskers consistent across all data points.

---
 src/d3.box.js | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/src/d3.box.js b/src/d3.box.js
index 5e3e760de..5aaa9df7f 100644
--- a/src/d3.box.js
+++ b/src/d3.box.js
@@ -74,6 +74,8 @@
                     .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]); });
 
@@ -104,6 +106,7 @@
                 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]); });
 
@@ -126,6 +129,8 @@
                 medianLine.transition()
                     .duration(duration)
                     .delay(delay)
+                    .attr('x1', 0)
+                    .attr('x2', width)
                     .attr('y1', x1)
                     .attr('y2', x1);
 
@@ -150,6 +155,8 @@
                 whisker.transition()
                     .duration(duration)
                     .delay(delay)
+                    .attr('x1', 0)
+                    .attr('x2', width)
                     .attr('y1', x1)
                     .attr('y2', x1)
                     .style('opacity', 1);
@@ -181,6 +188,7 @@
                 outlier.transition()
                     .duration(duration)
                     .delay(delay)
+                    .attr('cx', width / 2)
                     .attr('cy', function (i) { return x1(d[i]); })
                     .style('opacity', 1);
 
@@ -215,6 +223,7 @@
                     .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
@@ -241,6 +250,7 @@
                     .duration(duration)
                     .delay(delay)
                     .text(format)
+                    .attr('x', width)
                     .attr('y', x1)
                     .style('opacity', 1);
 

From 0b61f7f828ec6e13fb34e73456a31b30674529ea Mon Sep 17 00:00:00 2001
From: Gordon Woodhull <gordon@woodhull.com>
Date: Fri, 25 Nov 2016 18:18:01 -0500
Subject: [PATCH 12/17] version & changelog

---
 Changelog.md | 7 ++++---
 package.json | 2 +-
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/Changelog.md b/Changelog.md
index cd8f65abb..82174fa9e 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,10 +1,11 @@
 # 2.0 Series
 ## 2.0.0 beta 33
-* 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
+* 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/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 ",

From 676b66d9b381ae31eedf58f983f33c2d56702714 Mon Sep 17 00:00:00 2001
From: Anders Dalvander <github@dalvander.com>
Date: Thu, 9 Jun 2016 10:42:30 +0200
Subject: [PATCH 13/17] Forcing dots to be shown depending on xyTipsOn for line
 chart.

---
 src/line-chart.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/line-chart.js b/src/line-chart.js
index 3ac9794bf..1e3988ad7 100644
--- a/src/line-chart.js
+++ b/src/line-chart.js
@@ -259,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);
 

From 081defb9907aa5459800fe45ef5c115970449d76 Mon Sep 17 00:00:00 2001
From: Gordon Woodhull <gordon@woodhull.com>
Date: Thu, 1 Dec 2016 14:31:33 -0500
Subject: [PATCH 14/17] this test was broken

selector copied from coordinate-grid-spec, would never match anything
---
 spec/line-chart-spec.js | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/spec/line-chart-spec.js b/spec/line-chart-spec.js
index c78b83696..50f0c7068 100644
--- a/spec/line-chart-spec.js
+++ b/spec/line-chart-spec.js
@@ -117,10 +117,11 @@ 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('label rendering off', function () {

From 3a90e89fdc23037f3e672345d75e9d6e9ca78b3d Mon Sep 17 00:00:00 2001
From: Gordon Woodhull <gordon@woodhull.com>
Date: Thu, 1 Dec 2016 14:40:19 -0500
Subject: [PATCH 15/17] negative and positive tests for .xyTipsOn

fixes #1152
---
 AUTHORS                 |  1 +
 spec/line-chart-spec.js | 26 ++++++++++++++++++++++++++
 2 files changed, 27 insertions(+)

diff --git a/AUTHORS b/AUTHORS
index 329add2e5..11137f2fa 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -83,3 +83,4 @@ Michael Dougherty <maackle.d@gmail.com>
 Paul Mach <paulmach@gmail.com>
 Fil <fil@rezo.net>
 Mauricio Bustos <m@bustos.org>
+Anders Dalvander <github@dalvander.com>
diff --git a/spec/line-chart-spec.js b/spec/line-chart-spec.js
index 50f0c7068..426348e08 100644
--- a/spec/line-chart-spec.js
+++ b/spec/line-chart-spec.js
@@ -124,6 +124,32 @@ describe('dc.lineChart', function () {
 
         });
 
+        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();
+                });
+            });
+        });
+
         describe('label rendering off', function () {
             beforeEach(function () {
                 chart.renderLabel(false);

From ea511e06b2acd3fa62723de3ec7337b2b449a0bc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexander=20Stillesj=C3=B6?= <a.stillesjo@gmail.com>
Date: Mon, 28 Nov 2016 08:54:11 +0100
Subject: [PATCH 16/17] Add support for different kinds of date offsets.

Add method for xAxisPaddingUnit

Fix lint warnings

Add more test cases

Refactor subtract/add util functions

closes #892
---
 spec/utils-spec.js           | 25 ++++++++++++++++++++++++-
 src/coordinate-grid-mixin.js | 35 ++++++++++++++++++++++++++++++-----
 src/utils.js                 | 16 ++++++----------
 3 files changed, 60 insertions(+), 16 deletions(-)

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/coordinate-grid-mixin.js b/src/coordinate-grid-mixin.js
index 117162d44..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.
@@ -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);
     };
 
     /**
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);

From 936f72e2bba8b783ce6bf7252cfee5a99d193296 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alexander=20Stillesj=C3=B6?= <a.stillesjo@gmail.com>
Date: Mon, 20 Jun 2016 07:38:14 +0200
Subject: [PATCH 17/17] Don't interpolate angles when updating label paths

not data
---
 src/pie-chart.js | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/pie-chart.js b/src/pie-chart.js
index b586b7491..d8d2eca0a 100644
--- a/src/pie-chart.js
+++ b/src/pie-chart.js
@@ -246,8 +246,9 @@ dc.pieChart = function (parent, chartGroup) {
         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);