Skip to content

Commit e858ff2

Browse files
a rewrite of bar transitions
composite 2-pass drawing pre- & post- scale change coord.plotData shall returns continuation if it wants to participate the immediate action draws anything before the transition the continuation will be run after the scale change note there is nothing asynchronous here. instead we are using continuations for the sake of capturing the joined selections that both passes need. 2-pass is implemented but not specialized for the bar chart only this commit also makes explicit that axis preparation and scale-setting is different for render and redraw. render sets the scales twice, probably to the same thing. this is because the scales must be valid for the first pass, and they must *not* be set before a redraw, because they must be consistent with the old view. we'll see if this causes problems when clients (& maybe focus chart) explicitly change the x domain. but if there are problems here, they are probably not new. any enter selections on a too-soon scale change were probably already screwed up. implement 2-phase drawing for bar chart but, as i guessed might happen, this does not work for cases where the scale was updated by the client copy/set/restore scales to make last available we must let the user change the scale, so we will need to copy after each draw. however, this push/pop stuff is probably wrong (and 2-phase is probably wrong too). will clean up later after i have a working poc. apply barWidth to entering this logic is just wrong what more is there to say the intent here was not to string-compare null but it ended up skipping zeroes this is still somewhat wrong because it *should* string-compare zeroes but let's just deal with the obvious and move on fetch data before restoring scales ... of course, the idea that the data should depend on the current scale is infuriating in itself, but again, this branch is a poc test left-to-right swipe currently the middle is lost make state explicit it's a lot of parameters but it seems much more clear to pass them in rather than assuming they will be set on the chart. get rid of the very clever continuation trick i just put in. :-| also stop caching barWidth and xUnitCount, which wouldn't work anymore (since we have to look at the past and present at once). this works but a lot of tests are failing it's now possible to observe scale changing type from linear to ordinal, for example fun. also track last right Y scale and don't pass the left :) this was not the way to process data better we do want to call .data() less, but this didn't actually help we do want to get data for the right range, this didn't do that either this was only here to get data at the right scale-moment but we're past that now fade out + refinements for ordinal bar transitions always fade out as we're fading in ordinal bars should not move or size as they enter/exit, because logically there is nowhere for them to come from / go to. pull domainFilter out of prepareValues too deep, too stateful start transitioning from .data() to computeStacks bar chart draw bounds enclosing old and new domain to avoid the gap technically we could remove what's outside the drawing area afterward rotate through a few more spans to display the off-edge bug #949
1 parent 3326423 commit e858ff2

7 files changed

+200
-126
lines changed

src/bar-chart.js

+108-75
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,7 @@ dc.barChart = function (parent, chartGroup) {
3434
var _gap = DEFAULT_GAP_BETWEEN_BARS;
3535
var _centerBar = false;
3636
var _alwaysUseRounding = false;
37-
38-
var _barWidth;
39-
40-
dc.override(_chart, 'rescale', function () {
41-
_chart._rescale();
42-
_barWidth = undefined;
43-
return _chart;
44-
});
37+
var _growFromZero = false;
4538

4639
dc.override(_chart, 'render', function () {
4740
if (_chart.round() && _centerBar && !_alwaysUseRounding) {
@@ -56,11 +49,13 @@ dc.barChart = function (parent, chartGroup) {
5649
return dc.utils.printSingleValue(d.y0 + d.y);
5750
}, false);
5851

59-
_chart.plotData = function () {
52+
_chart.plotData = function (params) {
53+
var bounds = _chart.isOrdinal() ? null : params.fullBounds();
54+
var stackData = _chart.computeStacks(bounds);
6055
var layers = _chart.chartBodyG().selectAll('g.stack')
61-
.data(_chart.data());
62-
63-
calculateBarWidth();
56+
.data(stackData);
57+
params.preBarWidth = calculateBarWidth(params.preXScale);
58+
params.postBarWidth = calculateBarWidth(params.postXScale);
6459

6560
layers
6661
.enter()
@@ -73,19 +68,22 @@ dc.barChart = function (parent, chartGroup) {
7368
layers.each(function (d, i) {
7469
var layer = d3.select(this);
7570

76-
renderBars(layer, i, d);
71+
renderBars(layer, d, params);
7772

7873
if (_chart.renderLabel() && last === i) {
79-
renderLabels(layer, i, d);
74+
renderLabels(layer, d, params);
8075
}
8176
});
8277
};
8378

84-
function barHeight (d) {
85-
return dc.utils.safeNumber(Math.abs(_chart.y()(d.y + d.y0) - _chart.y()(d.y0)));
79+
function barHeight (yScale) {
80+
return function (d) {
81+
return dc.utils.safeNumber(Math.abs(yScale(d.y + d.y0) - yScale(d.y0)));
82+
};
8683
}
8784

88-
function renderLabels (layer, layerIndex, d) {
85+
function renderLabels (layer, d, params) {
86+
// stuff that should happen before scales updated
8987
var labels = layer.selectAll('text.barLabel')
9088
.data(d.values, dc.pluck('x'));
9189

@@ -100,21 +98,9 @@ dc.barChart = function (parent, chartGroup) {
10098
}
10199

102100
dc.transition(labels, _chart.transitionDuration(), _chart.transitionDelay())
103-
.attr('x', function (d) {
104-
var x = _chart.x()(d.x);
105-
if (!_centerBar) {
106-
x += _barWidth / 2;
107-
}
108-
return dc.utils.safeNumber(x);
109-
})
101+
.attr('x', barX(params.postXScale))
110102
.attr('y', function (d) {
111-
var y = _chart.y()(d.y + d.y0);
112-
113-
if (d.y < 0) {
114-
y -= barHeight(d);
115-
}
116-
117-
return dc.utils.safeNumber(y - LABEL_PADDING);
103+
return barY(params.postYScale)(d) - LABEL_PADDING;
118104
})
119105
.text(function (d) {
120106
return _chart.label()(d);
@@ -125,16 +111,59 @@ dc.barChart = function (parent, chartGroup) {
125111
.remove();
126112
}
127113

128-
function renderBars (layer, layerIndex, d) {
114+
function barX (xScale, barWidth) {
115+
return function (d) {
116+
var x = xScale(d.x);
117+
if (_centerBar) {
118+
x -= barWidth / 2;
119+
}
120+
if (_chart.isOrdinal() && _gap !== undefined) {
121+
x += _gap / 2;
122+
}
123+
return dc.utils.safeNumber(x);
124+
};
125+
}
126+
function barY (yScale) {
127+
return function (d) {
128+
var y = yScale(d.y + d.y0);
129+
130+
if (d.y < 0) {
131+
y -= barHeight(yScale)(d);
132+
}
133+
134+
return dc.utils.safeNumber(y);
135+
};
136+
}
137+
138+
function renderBars (layer, d, params) {
139+
// stuff that should happen before scales updated
129140
var bars = layer.selectAll('rect.bar')
130141
.data(d.values, dc.pluck('x'));
131142

132143
var enter = bars.enter()
133144
.append('rect')
134145
.attr('class', 'bar')
135-
.attr('fill', dc.pluck('data', _chart.getColor))
136-
.attr('y', _chart.yAxisHeight())
137-
.attr('height', 0);
146+
.attr('fill', dc.pluck('data', _chart.getColor));
147+
148+
if (!_chart.isOrdinal()) { // there's nowhere to arrive from if ordinal
149+
enter
150+
.attr('x', barX(params.preXScale, params.preBarWidth))
151+
.attr('width', params.preBarWidth);
152+
}
153+
154+
if (_growFromZero) {
155+
enter
156+
.attr('y', _chart.yAxisHeight())
157+
.attr('height', 0);
158+
} else {
159+
if (!_chart.isOrdinal()) {
160+
enter
161+
.attr('y', barY(params.preYScale))
162+
.attr('height', barHeight(params.preYScale));
163+
}
164+
enter
165+
.attr('opacity', 0);
166+
}
138167

139168
if (_chart.renderTitle()) {
140169
enter.append('title').text(dc.pluck('data', _chart.title(d.name)));
@@ -145,55 +174,41 @@ dc.barChart = function (parent, chartGroup) {
145174
}
146175

147176
dc.transition(bars, _chart.transitionDuration(), _chart.transitionDelay())
148-
.attr('x', function (d) {
149-
var x = _chart.x()(d.x);
150-
if (_centerBar) {
151-
x -= _barWidth / 2;
152-
}
153-
if (_chart.isOrdinal() && _gap !== undefined) {
154-
x += _gap / 2;
155-
}
156-
return dc.utils.safeNumber(x);
157-
})
158-
.attr('y', function (d) {
159-
var y = _chart.y()(d.y + d.y0);
160-
161-
if (d.y < 0) {
162-
y -= barHeight(d);
163-
}
164-
165-
return dc.utils.safeNumber(y);
166-
})
167-
.attr('width', _barWidth)
168-
.attr('height', function (d) {
169-
return barHeight(d);
170-
})
177+
.attr('x', barX(params.postXScale, params.postBarWidth))
178+
.attr('y', barY(params.postYScale))
179+
.attr('width', params.postBarWidth)
180+
.attr('height', barHeight(params.postYScale))
181+
.attr('opacity', 1)
171182
.attr('fill', dc.pluck('data', _chart.getColor))
172183
.select('title').text(dc.pluck('data', _chart.title(d.name)));
173184

174-
dc.transition(bars.exit(), _chart.transitionDuration(), _chart.transitionDelay())
175-
.attr('x', function (d) { return _chart.x()(d.x); })
176-
.attr('width', _barWidth * 0.9)
185+
var transOut = dc.transition(bars.exit(), _chart.transitionDuration(), _chart.transitionDelay())
186+
.attr('height', barHeight(params.postYScale))
187+
.attr('opacity', 0);
188+
if (!_chart.isOrdinal()) { // there's nowhere to "go" if ordinal
189+
transOut.attr('x', barX(params.postXScale, params.postBarWidth))
190+
.attr('y', barY(params.postYScale))
191+
.attr('width', params.postBarWidth);
192+
}
193+
transOut
177194
.remove();
178195
}
179196

180-
function calculateBarWidth () {
181-
if (_barWidth === undefined) {
182-
var numberOfBars = _chart.xUnitCount();
197+
function calculateBarWidth (xScale) {
198+
var barWidth, numberOfBars = _chart.xUnitCount(xScale);
183199

184-
// please can't we always use rangeBands for bar charts?
185-
if (_chart.isOrdinal() && _gap === undefined) {
186-
_barWidth = Math.floor(_chart.x().rangeBand());
187-
} else if (_gap) {
188-
_barWidth = Math.floor((_chart.xAxisLength() - (numberOfBars - 1) * _gap) / numberOfBars);
189-
} else {
190-
_barWidth = Math.floor(_chart.xAxisLength() / (1 + _chart.barPadding()) / numberOfBars);
191-
}
200+
if (_chart.isOrdinal() && _gap === undefined && xScale.rangeBand) { // may have been previously not ordinal
201+
barWidth = Math.floor(xScale.rangeBand());
202+
} else if (_gap) {
203+
barWidth = Math.floor((_chart.xAxisLength() - (numberOfBars - 1) * _gap) / numberOfBars);
204+
} else {
205+
barWidth = Math.floor(_chart.xAxisLength() / (1 + _chart.barPadding()) / numberOfBars);
206+
}
192207

193-
if (_barWidth === Infinity || isNaN(_barWidth) || _barWidth < MIN_BAR_WIDTH) {
194-
_barWidth = MIN_BAR_WIDTH;
195-
}
208+
if (barWidth === Infinity || isNaN(barWidth) || barWidth < MIN_BAR_WIDTH) {
209+
barWidth = MIN_BAR_WIDTH;
196210
}
211+
return barWidth;
197212
}
198213

199214
_chart.fadeDeselectedArea = function () {
@@ -270,6 +285,24 @@ dc.barChart = function (parent, chartGroup) {
270285
return _gap === undefined;
271286
};
272287

288+
/**
289+
* Whether bars should grow from the bottom of the chart when they are first drawn (true)
290+
* or fade in (false).
291+
* @method growFromZero
292+
* @memberof dc.barChart
293+
* @instance
294+
* @param {Boolean} [growFromZero=false]
295+
* @return {Boolean}
296+
* @return {dc.barChart}
297+
*/
298+
_chart.growFromZero = function (growFromZero) {
299+
if (!arguments.length) {
300+
return _growFromZero;
301+
}
302+
_growFromZero = growFromZero;
303+
return _chart;
304+
};
305+
273306
/**
274307
* Get or set the outer padding on an ordinal bar chart. This setting has no effect on non-ordinal charts.
275308
* Will pad the width by `padding * barWidth` on each side of the chart.

src/composite-chart.js

+11-4
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ dc.compositeChart = function (parent, chartGroup) {
3434
var _rightYAxis = d3.svg.axis(),
3535
_rightYAxisLabel = 0,
3636
_rightYAxisLabelPadding = DEFAULT_RIGHT_Y_AXIS_LABEL_PADDING,
37-
_rightY,
37+
_rightY, _lastRightYScale,
3838
_rightAxisGridLines = false;
3939

4040
_chart._mandatoryAttributes([]);
@@ -192,7 +192,13 @@ dc.compositeChart = function (parent, chartGroup) {
192192
child.g().attr('class', SUB_CHART_CLASS + ' _' + i);
193193
}
194194

195-
_chart.plotData = function () {
195+
_chart.plotData = function (params) {
196+
var rightParams = {
197+
preXScale: params.preXScale,
198+
preYScale: _lastRightYScale,
199+
postXScale: params.postYScale,
200+
postYScale: _chart.rightY()
201+
};
196202
for (var i = 0; i < _children.length; ++i) {
197203
var child = _children[i];
198204

@@ -211,15 +217,16 @@ dc.compositeChart = function (parent, chartGroup) {
211217
if (child.useRightYAxis()) {
212218
child.y(_chart.rightY());
213219
child.yAxis(_chart.rightYAxis());
220+
child.plotData(rightParams);
214221
} else {
215222
child.y(_chart.y());
216223
child.yAxis(_chart.yAxis());
224+
child.plotData(params);
217225
}
218226

219-
child.plotData();
220-
221227
child._activateRenderlets();
222228
}
229+
_lastRightYScale = _rightY ? _rightY.copy() : null;
223230
};
224231

225232
/**

0 commit comments

Comments
 (0)