Index: charted/lib/charts/cartesian_renderers/stackedbar_chart_renderer.dart |
diff --git a/charted/lib/charts/cartesian_renderers/stackedbar_chart_renderer.dart b/charted/lib/charts/cartesian_renderers/stackedbar_chart_renderer.dart |
deleted file mode 100644 |
index 396b8042e7a93d0c91b713cfc0fd21549afb209f..0000000000000000000000000000000000000000 |
--- a/charted/lib/charts/cartesian_renderers/stackedbar_chart_renderer.dart |
+++ /dev/null |
@@ -1,326 +0,0 @@ |
-// |
-// Copyright 2014 Google Inc. All rights reserved. |
-// |
-// Use of this source code is governed by a BSD-style |
-// license that can be found in the LICENSE file or at |
-// https://developers.google.com/open-source/licenses/bsd |
-// |
- |
-part of charted.charts; |
- |
-class StackedBarChartRenderer extends CartesianRendererBase { |
- static const RADIUS = 2; |
- |
- final Iterable<int> dimensionsUsingBand = const[0]; |
- final bool alwaysAnimate; |
- |
- @override |
- final String name = "stack-rdr"; |
- |
- /// Used to capture the last measure with data in a data row. This is used |
- /// to decided whether to round the cornor of the bar or not. |
- List<int> _lastMeasureWithData = []; |
- |
- StackedBarChartRenderer({this.alwaysAnimate: false}); |
- |
- /// Returns false if the number of dimension axes on the area is 0. |
- /// Otherwise, the first dimension scale is used to render the chart. |
- @override |
- bool prepare(CartesianArea area, ChartSeries series) { |
- _ensureAreaAndSeries(area, series); |
- return true; |
- } |
- |
- @override |
- void draw(Element element, {Future schedulePostRender}) { |
- _ensureReadyToDraw(element); |
- var verticalBars = !area.config.isLeftAxisPrimary; |
- |
- var measuresCount = series.measures.length, |
- measureScale = area.measureScales(series).first, |
- dimensionScale = area.dimensionScales.first; |
- |
- var rows = new List() |
- ..addAll(area.data.rows.map((e) => |
- new List.generate(measuresCount, |
- (i) => e.elementAt(series.measures.elementAt(_reverseIdx(i)))))); |
- |
- var dimensionVals = area.data.rows.map( |
- (row) => row.elementAt(area.config.dimensions.first)).toList(); |
- |
- var groups = root.selectAll('.stack-rdr-rowgroup').data(rows); |
- var animateBarGroups = alwaysAnimate || !groups.isEmpty; |
- groups.enter.append('g') |
- ..classed('stack-rdr-rowgroup') |
- ..attrWithCallback('transform', (d, i, c) => verticalBars ? |
- 'translate(${dimensionScale.scale(dimensionVals[i])}, 0)' : |
- 'translate(0, ${dimensionScale.scale(dimensionVals[i])})'); |
- groups.attrWithCallback('data-row', (d, i, e) => i); |
- groups.exit.remove(); |
- |
- if (animateBarGroups) { |
- groups.transition() |
- ..attrWithCallback('transform', (d, i, c) => verticalBars ? |
- 'translate(${dimensionScale.scale(dimensionVals[i])}, 0)' : |
- 'translate(0, ${dimensionScale.scale(dimensionVals[i])})') |
- ..duration(theme.transitionDurationMilliseconds); |
- } |
- |
- var bar = |
- groups.selectAll('.stack-rdr-bar').dataWithCallback((d, i, c) => d); |
- |
- var prevOffsetVal = new List(); |
- |
- // Keep track of "y" values. |
- // These are used to insert values in the middle of stack when necessary |
- if (animateBarGroups) { |
- bar.each((d, i, e) { |
- var offset = e.dataset['offset'], |
- offsetVal = offset != null ? int.parse(offset) : 0; |
- if (i == 0) { |
- prevOffsetVal.add(offsetVal); |
- } else { |
- prevOffsetVal[prevOffsetVal.length - 1] = offsetVal; |
- } |
- }); |
- } |
- |
- |
- var barWidth = dimensionScale.rangeBand - theme.defaultStrokeWidth; |
- |
- // Calculate height of each segment in the bar. |
- // Uses prevAllZeroHeight and prevOffset to track previous segments |
- var prevAllZeroHeight = true, |
- prevOffset = 0; |
- var getBarLength = (d, i) { |
- if (!verticalBars) return measureScale.scale(d).round(); |
- var retval = rect.height - measureScale.scale(d).round(); |
- if (i != 0) { |
- // If previous bars has 0 height, don't offset for spacing |
- // If any of the previous bar has non 0 height, do the offset. |
- retval -= prevAllZeroHeight |
- ? 1 |
- : (theme.defaultSeparatorWidth + theme.defaultStrokeWidth); |
- retval += prevOffset; |
- } else { |
- // When rendering next group of bars, reset prevZeroHeight. |
- prevOffset = 0; |
- prevAllZeroHeight = true; |
- retval -= 1; // -1 so bar does not overlap x axis. |
- } |
- |
- if (retval <= 0) { |
- prevOffset = prevAllZeroHeight |
- ? 0 |
- : theme.defaultSeparatorWidth + theme.defaultStrokeWidth + retval; |
- retval = 0; |
- } |
- prevAllZeroHeight = (retval == 0) && prevAllZeroHeight; |
- return retval; |
- }; |
- |
- // Initial "y" position of a bar that is being created. |
- // Only used when animateBarGroups is set to true. |
- var ic = 10000000, |
- order = 0; |
- var getInitialBarPos = (i) { |
- var tempY; |
- if (i <= ic && i > 0) { |
- tempY = prevOffsetVal[order]; |
- order++; |
- } else { |
- tempY = verticalBars ? rect.height : 0; |
- } |
- ic = i; |
- return tempY; |
- }; |
- |
- // Position of a bar in the stack. yPos is used to keep track of the |
- // offset based on previous calls to getBarY |
- var yPos = 0; |
- var getBarPos = (d, i) { |
- if (verticalBars) { |
- if (i == 0) { |
- yPos = measureScale.scale(0).round(); |
- } |
- return yPos -= (rect.height - measureScale.scale(d).round()); |
- } else { |
- if (i == 0) { |
- // 1 to not overlap the axis line. |
- yPos = 1; |
- } |
- var pos = yPos; |
- yPos += measureScale.scale(d).round(); |
- // Check if after adding the height of the bar, if y has changed, if |
- // changed, we offset for space between the bars. |
- if (yPos != pos) { |
- yPos += (theme.defaultSeparatorWidth + theme.defaultStrokeWidth); |
- } |
- return pos; |
- } |
- }; |
- |
- var buildPath = (d, int i, Element e, bool animate, int roundIdx) { |
- var position = animate ? getInitialBarPos(i) : getBarPos(d, i), |
- length = animate ? 0 : getBarLength(d, i), |
- radius = series.measures.elementAt(_reverseIdx(i)) == roundIdx ? RADIUS : 0, |
- path = (length != 0) |
- ? verticalBars |
- ? topRoundedRect(0, position, barWidth, length, radius) |
- : rightRoundedRect(position, 0, length, barWidth, radius) |
- : ''; |
- e.attributes['data-offset'] = verticalBars ? |
- position.toString() : (position + length).toString(); |
- return path; |
- }; |
- |
- var enter = bar.enter.appendWithCallback((d, i, e) { |
- var rect = Namespace.createChildElement('path', e), |
- measure = series.measures.elementAt(_reverseIdx(i)), |
- row = int.parse(e.dataset['row']), |
- color = colorForValue(measure, row), |
- filter = filterForValue(measure, row), |
- style = stylesForValue(measure, row), |
- roundIndex = _lastMeasureWithData[row]; |
- |
- if (!isNullOrEmpty(style)) { |
- rect.classes.addAll(style); |
- } |
- rect.classes.add('stack-rdr-bar'); |
- |
- rect.attributes |
- ..['d'] = buildPath (d == null ? 0 : d, i, rect, animateBarGroups, |
- roundIndex) |
- ..['stroke-width'] = '${theme.defaultStrokeWidth}px' |
- ..['fill'] = color |
- ..['stroke'] = color; |
- |
- if (!isNullOrEmpty(filter)) { |
- rect.attributes['filter'] = filter; |
- } |
- if (!animateBarGroups) { |
- rect.attributes['data-column'] = '$measure'; |
- } |
- return rect; |
- }); |
- |
- enter |
- ..on('click', (d, i, e) => _event(mouseClickController, d, i, e)) |
- ..on('mouseover', (d, i, e) => _event(mouseOverController, d, i, e)) |
- ..on('mouseout', (d, i, e) => _event(mouseOutController, d, i, e)); |
- |
- if (animateBarGroups) { |
- bar.each((d, i, e) { |
- var measure = series.measures.elementAt(_reverseIdx(i)), |
- row = int.parse(e.parent.dataset['row']), |
- color = colorForValue(measure, row), |
- filter = filterForValue(measure, row), |
- styles = stylesForValue(measure, row); |
- e.attributes |
- ..['data-column'] = '$measure' |
- ..['fill'] = color |
- ..['stroke'] = color; |
- e.classes |
- ..removeAll(ChartState.VALUE_CLASS_NAMES) |
- ..addAll(styles); |
- if (isNullOrEmpty(filter)) { |
- e.attributes.remove('filter'); |
- } else { |
- e.attributes['filter'] = filter; |
- } |
- }); |
- |
- bar.transition() |
- ..attrWithCallback('d', (d, i, e) { |
- var row = int.parse(e.parent.dataset['row']), |
- roundIndex = _lastMeasureWithData[row]; |
- return buildPath(d == null ? 0 : d, i, e, false, roundIndex); |
- }); |
- } |
- |
- bar.exit.remove(); |
- } |
- |
- @override |
- void dispose() { |
- if (root == null) return; |
- root.selectAll('.stack-rdr-rowgroup').remove(); |
- } |
- |
- @override |
- double get bandInnerPadding => |
- area.theme.getDimensionAxisTheme().axisBandInnerPadding; |
- |
- @override |
- Extent get extent { |
- assert(area != null && series != null); |
- var rows = area.data.rows, |
- max = SMALL_INT_MIN, |
- min = SMALL_INT_MAX, |
- rowIndex = 0; |
- _lastMeasureWithData = new List.generate(rows.length, (i) => -1); |
- |
- rows.forEach((row) { |
- var bar = null; |
- series.measures.forEach((idx) { |
- var value = row.elementAt(idx); |
- if (value != null && value.isFinite) { |
- if (bar == null) bar = 0; |
- bar += value; |
- if (value.round() != 0 && _lastMeasureWithData[rowIndex] == -1) { |
- _lastMeasureWithData[rowIndex] = idx; |
- } |
- } |
- }); |
- if (bar > max) max = bar; |
- if (bar < min) min = bar; |
- rowIndex++; |
- }); |
- |
- return new Extent(min, max); |
- } |
- |
- @override |
- void handleStateChanges(List<ChangeRecord> changes) { |
- var groups = host.querySelectorAll('.stack-rdr-rowgroup'); |
- if (groups == null || groups.isEmpty) return; |
- |
- for(int i = 0, len = groups.length; i < len; ++i) { |
- var group = groups.elementAt(i), |
- bars = group.querySelectorAll('.stack-rdr-bar'), |
- row = int.parse(group.dataset['row']); |
- |
- for(int j = 0, barsCount = bars.length; j < barsCount; ++j) { |
- var bar = bars.elementAt(j), |
- column = int.parse(bar.dataset['column']), |
- color = colorForValue(column, row), |
- filter = filterForValue(column, row); |
- |
- bar.classes.removeAll(ChartState.VALUE_CLASS_NAMES); |
- bar.classes.addAll(stylesForValue(column, row)); |
- bar.attributes |
- ..['fill'] = color |
- ..['stroke'] = color; |
- if (isNullOrEmpty(filter)) { |
- bar.attributes.remove('filter'); |
- } else { |
- bar.attributes['filter'] = filter; |
- } |
- } |
- } |
- } |
- |
- void _event(StreamController controller, data, int index, Element e) { |
- if (controller == null) return; |
- var rowStr = e.parent.dataset['row']; |
- var row = rowStr != null ? int.parse(rowStr) : null; |
- controller.add(new DefaultChartEventImpl( |
- scope.event, area, series, row, |
- series.measures.elementAt(_reverseIdx(index)), data)); |
- } |
- |
- // Stacked bar chart renders items from bottom to top (first measure is at |
- // the bottom of the stack). We use [_reversedIdx] instead of index to |
- // match the color and order of what is displayed in the legend. |
- int _reverseIdx(int index) => series.measures.length - 1 - index; |
-} |