Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(20)

Side by Side Diff: packages/charted/lib/charts/cartesian_renderers/stackedbar_chart_renderer.dart

Issue 1521693002: Roll Observatory deps (charted -> ^0.3.0) (Closed) Base URL: https://chromium.googlesource.com/external/github.com/dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // 1 //
2 // Copyright 2014 Google Inc. All rights reserved. 2 // Copyright 2014 Google Inc. All rights reserved.
3 // 3 //
4 // Use of this source code is governed by a BSD-style 4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at 5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd 6 // https://developers.google.com/open-source/licenses/bsd
7 // 7 //
8 8
9 part of charted.charts; 9 part of charted.charts;
10 10
11 class StackedBarChartRenderer extends CartesianRendererBase { 11 class StackedBarChartRenderer extends CartesianRendererBase {
12 static const RADIUS = 2; 12 static const RADIUS = 2;
13 13
14 final Iterable<int> dimensionsUsingBand = const[0]; 14 final Iterable<int> dimensionsUsingBand = const [0];
15 final bool alwaysAnimate; 15 final bool alwaysAnimate;
16 16
17 @override 17 @override
18 final String name = "stack-rdr"; 18 final String name = "stack-rdr";
19 19
20 /// Used to capture the last measure with data in a data row. This is used 20 /// Used to capture the last measure with data in a data row. This is used
21 /// to decided whether to round the cornor of the bar or not. 21 /// to decided whether to round the cornor of the bar or not.
22 List<int> _lastMeasureWithData = []; 22 List<int> _lastMeasureWithData = [];
23 23
24 StackedBarChartRenderer({this.alwaysAnimate: false}); 24 StackedBarChartRenderer({this.alwaysAnimate: false});
25 25
26 /// Returns false if the number of dimension axes on the area is 0. 26 /// Returns false if the number of dimension axes on the area is 0.
27 /// Otherwise, the first dimension scale is used to render the chart. 27 /// Otherwise, the first dimension scale is used to render the chart.
28 @override 28 @override
29 bool prepare(CartesianArea area, ChartSeries series) { 29 bool prepare(CartesianArea area, ChartSeries series) {
30 _ensureAreaAndSeries(area, series); 30 _ensureAreaAndSeries(area, series);
31 return true; 31 return true;
32 } 32 }
33 33
34 @override 34 @override
35 void draw(Element element, {Future schedulePostRender}) { 35 void draw(Element element, {Future schedulePostRender}) {
36 _ensureReadyToDraw(element); 36 _ensureReadyToDraw(element);
37 var verticalBars = !area.config.isLeftAxisPrimary; 37 var verticalBars = !area.config.isLeftAxisPrimary;
38 38
39 var measuresCount = series.measures.length, 39 var measuresCount = series.measures.length,
40 measureScale = area.measureScales(series).first, 40 measureScale = area.measureScales(series).first,
41 dimensionScale = area.dimensionScales.first; 41 dimensionScale = area.dimensionScales.first;
42 42
43 var rows = new List() 43 var rows = new List()
44 ..addAll(area.data.rows.map((e) => 44 ..addAll(area.data.rows.map((e) => new List.generate(measuresCount,
45 new List.generate(measuresCount, 45 (i) => e.elementAt(series.measures.elementAt(_reverseIdx(i))))));
46 (i) => e.elementAt(series.measures.elementAt(_reverseIdx(i))))));
47 46
48 var dimensionVals = area.data.rows.map( 47 var dimensionVals = area.data.rows
49 (row) => row.elementAt(area.config.dimensions.first)).toList(); 48 .map((row) => row.elementAt(area.config.dimensions.first))
49 .toList();
50 50
51 var groups = root.selectAll('.stack-rdr-rowgroup').data(rows); 51 var groups = root.selectAll('.stack-rdr-rowgroup').data(rows);
52 var animateBarGroups = alwaysAnimate || !groups.isEmpty; 52 var animateBarGroups = alwaysAnimate || !groups.isEmpty;
53 groups.enter.append('g') 53 groups.enter.append('g')
54 ..classed('stack-rdr-rowgroup') 54 ..classed('stack-rdr-rowgroup')
55 ..attrWithCallback('transform', (d, i, c) => verticalBars ? 55 ..attrWithCallback(
56 'translate(${dimensionScale.scale(dimensionVals[i])}, 0)' : 56 'transform',
57 'translate(0, ${dimensionScale.scale(dimensionVals[i])})'); 57 (d, i, c) => verticalBars
58 ? 'translate(${dimensionScale.scale(dimensionVals[i])}, 0)'
59 : 'translate(0, ${dimensionScale.scale(dimensionVals[i])})');
58 groups.attrWithCallback('data-row', (d, i, e) => i); 60 groups.attrWithCallback('data-row', (d, i, e) => i);
59 groups.exit.remove(); 61 groups.exit.remove();
60 62
61 if (animateBarGroups) { 63 if (animateBarGroups) {
62 groups.transition() 64 groups.transition()
63 ..attrWithCallback('transform', (d, i, c) => verticalBars ? 65 ..attrWithCallback(
64 'translate(${dimensionScale.scale(dimensionVals[i])}, 0)' : 66 'transform',
65 'translate(0, ${dimensionScale.scale(dimensionVals[i])})') 67 (d, i, c) => verticalBars
68 ? 'translate(${dimensionScale.scale(dimensionVals[i])}, 0)'
69 : 'translate(0, ${dimensionScale.scale(dimensionVals[i])})')
66 ..duration(theme.transitionDurationMilliseconds); 70 ..duration(theme.transitionDurationMilliseconds);
67 } 71 }
68 72
69 var bar = 73 var bar =
70 groups.selectAll('.stack-rdr-bar').dataWithCallback((d, i, c) => d); 74 groups.selectAll('.stack-rdr-bar').dataWithCallback((d, i, c) => d);
71 75
72 var prevOffsetVal = new List(); 76 var prevOffsetVal = new List();
73 77
74 // Keep track of "y" values. 78 // Keep track of "y" values.
75 // These are used to insert values in the middle of stack when necessary 79 // These are used to insert values in the middle of stack when necessary
76 if (animateBarGroups) { 80 if (animateBarGroups) {
77 bar.each((d, i, e) { 81 bar.each((d, i, e) {
78 var offset = e.dataset['offset'], 82 var offset = e.dataset['offset'],
79 offsetVal = offset != null ? int.parse(offset) : 0; 83 offsetVal = offset != null ? int.parse(offset) : 0;
80 if (i == 0) { 84 if (i == 0) {
81 prevOffsetVal.add(offsetVal); 85 prevOffsetVal.add(offsetVal);
82 } else { 86 } else {
83 prevOffsetVal[prevOffsetVal.length - 1] = offsetVal; 87 prevOffsetVal[prevOffsetVal.length - 1] = offsetVal;
84 } 88 }
85 }); 89 });
86 } 90 }
87
88 91
89 var barWidth = dimensionScale.rangeBand - theme.defaultStrokeWidth; 92 var barWidth = dimensionScale.rangeBand - theme.defaultStrokeWidth;
90 93
91 // Calculate height of each segment in the bar. 94 // Calculate height of each segment in the bar.
92 // Uses prevAllZeroHeight and prevOffset to track previous segments 95 // Uses prevAllZeroHeight and prevOffset to track previous segments
93 var prevAllZeroHeight = true, 96 var prevAllZeroHeight = true, prevOffset = 0;
94 prevOffset = 0;
95 var getBarLength = (d, i) { 97 var getBarLength = (d, i) {
96 if (!verticalBars) return measureScale.scale(d).round(); 98 if (!verticalBars) return measureScale.scale(d).round();
97 var retval = rect.height - measureScale.scale(d).round(); 99 var retval = rect.height - measureScale.scale(d).round();
98 if (i != 0) { 100 if (i != 0) {
99 // If previous bars has 0 height, don't offset for spacing 101 // If previous bars has 0 height, don't offset for spacing
100 // If any of the previous bar has non 0 height, do the offset. 102 // If any of the previous bar has non 0 height, do the offset.
101 retval -= prevAllZeroHeight 103 retval -= prevAllZeroHeight
102 ? 1 104 ? 1
103 : (theme.defaultSeparatorWidth + theme.defaultStrokeWidth); 105 : (theme.defaultSeparatorWidth + theme.defaultStrokeWidth);
104 retval += prevOffset; 106 retval += prevOffset;
105 } else { 107 } else {
106 // When rendering next group of bars, reset prevZeroHeight. 108 // When rendering next group of bars, reset prevZeroHeight.
107 prevOffset = 0; 109 prevOffset = 0;
108 prevAllZeroHeight = true; 110 prevAllZeroHeight = true;
109 retval -= 1; // -1 so bar does not overlap x axis. 111 retval -= 1; // -1 so bar does not overlap x axis.
110 } 112 }
111 113
112 if (retval <= 0) { 114 if (retval <= 0) {
113 prevOffset = prevAllZeroHeight 115 prevOffset = prevAllZeroHeight
114 ? 0 116 ? 0
115 : theme.defaultSeparatorWidth + theme.defaultStrokeWidth + retval; 117 : theme.defaultSeparatorWidth + theme.defaultStrokeWidth + retval;
116 retval = 0; 118 retval = 0;
117 } 119 }
118 prevAllZeroHeight = (retval == 0) && prevAllZeroHeight; 120 prevAllZeroHeight = (retval == 0) && prevAllZeroHeight;
119 return retval; 121 return retval;
120 }; 122 };
121 123
122 // Initial "y" position of a bar that is being created. 124 // Initial "y" position of a bar that is being created.
123 // Only used when animateBarGroups is set to true. 125 // Only used when animateBarGroups is set to true.
124 var ic = 10000000, 126 var ic = 10000000, order = 0;
125 order = 0;
126 var getInitialBarPos = (i) { 127 var getInitialBarPos = (i) {
127 var tempY; 128 var tempY;
128 if (i <= ic && i > 0) { 129 if (i <= ic && i > 0) {
129 tempY = prevOffsetVal[order]; 130 tempY = prevOffsetVal[order];
130 order++; 131 order++;
131 } else { 132 } else {
132 tempY = verticalBars ? rect.height : 0; 133 tempY = verticalBars ? rect.height : 0;
133 } 134 }
134 ic = i; 135 ic = i;
135 return tempY; 136 return tempY;
(...skipping 20 matching lines...) Expand all
156 if (yPos != pos) { 157 if (yPos != pos) {
157 yPos += (theme.defaultSeparatorWidth + theme.defaultStrokeWidth); 158 yPos += (theme.defaultSeparatorWidth + theme.defaultStrokeWidth);
158 } 159 }
159 return pos; 160 return pos;
160 } 161 }
161 }; 162 };
162 163
163 var buildPath = (d, int i, Element e, bool animate, int roundIdx) { 164 var buildPath = (d, int i, Element e, bool animate, int roundIdx) {
164 var position = animate ? getInitialBarPos(i) : getBarPos(d, i), 165 var position = animate ? getInitialBarPos(i) : getBarPos(d, i),
165 length = animate ? 0 : getBarLength(d, i), 166 length = animate ? 0 : getBarLength(d, i),
166 radius = series.measures.elementAt(_reverseIdx(i)) == roundIdx ? RADIU S : 0, 167 radius =
168 series.measures.elementAt(_reverseIdx(i)) == roundIdx ? RADIUS : 0,
167 path = (length != 0) 169 path = (length != 0)
168 ? verticalBars 170 ? verticalBars
169 ? topRoundedRect(0, position, barWidth, length, radius) 171 ? topRoundedRect(0, position, barWidth, length, radius)
170 : rightRoundedRect(position, 0, length, barWidth, radius) 172 : rightRoundedRect(position, 0, length, barWidth, radius)
171 : ''; 173 : '';
172 e.attributes['data-offset'] = verticalBars ? 174 e.attributes['data-offset'] =
173 position.toString() : (position + length).toString(); 175 verticalBars ? position.toString() : (position + length).toString();
174 return path; 176 return path;
175 }; 177 };
176 178
177 var enter = bar.enter.appendWithCallback((d, i, e) { 179 var enter = bar.enter.appendWithCallback((d, i, e) {
178 var rect = Namespace.createChildElement('path', e), 180 var rect = Namespace.createChildElement('path', e),
179 measure = series.measures.elementAt(_reverseIdx(i)), 181 measure = series.measures.elementAt(_reverseIdx(i)),
180 row = int.parse(e.dataset['row']), 182 row = int.parse(e.dataset['row']),
181 color = colorForValue(measure, row), 183 color = colorForValue(measure, row),
182 filter = filterForValue(measure, row), 184 filter = filterForValue(measure, row),
183 style = stylesForValue(measure, row), 185 style = stylesForValue(measure, row),
184 roundIndex = _lastMeasureWithData[row]; 186 roundIndex = _lastMeasureWithData[row];
185 187
186 if (!isNullOrEmpty(style)) { 188 if (!isNullOrEmpty(style)) {
187 rect.classes.addAll(style); 189 rect.classes.addAll(style);
188 } 190 }
189 rect.classes.add('stack-rdr-bar'); 191 rect.classes.add('stack-rdr-bar');
190 192
191 rect.attributes 193 rect.attributes
192 ..['d'] = buildPath (d == null ? 0 : d, i, rect, animateBarGroups, 194 ..['d'] =
193 roundIndex) 195 buildPath(d == null ? 0 : d, i, rect, animateBarGroups, roundIndex)
194 ..['stroke-width'] = '${theme.defaultStrokeWidth}px' 196 ..['stroke-width'] = '${theme.defaultStrokeWidth}px'
195 ..['fill'] = color 197 ..['fill'] = color
196 ..['stroke'] = color; 198 ..['stroke'] = color;
197 199
198 if (!isNullOrEmpty(filter)) { 200 if (!isNullOrEmpty(filter)) {
199 rect.attributes['filter'] = filter; 201 rect.attributes['filter'] = filter;
200 } 202 }
201 if (!animateBarGroups) { 203 if (!animateBarGroups) {
202 rect.attributes['data-column'] = '$measure'; 204 rect.attributes['data-column'] = '$measure';
203 } 205 }
(...skipping 24 matching lines...) Expand all
228 } else { 230 } else {
229 e.attributes['filter'] = filter; 231 e.attributes['filter'] = filter;
230 } 232 }
231 }); 233 });
232 234
233 bar.transition() 235 bar.transition()
234 ..attrWithCallback('d', (d, i, e) { 236 ..attrWithCallback('d', (d, i, e) {
235 var row = int.parse(e.parent.dataset['row']), 237 var row = int.parse(e.parent.dataset['row']),
236 roundIndex = _lastMeasureWithData[row]; 238 roundIndex = _lastMeasureWithData[row];
237 return buildPath(d == null ? 0 : d, i, e, false, roundIndex); 239 return buildPath(d == null ? 0 : d, i, e, false, roundIndex);
238 }); 240 });
239 } 241 }
240 242
241 bar.exit.remove(); 243 bar.exit.remove();
242 } 244 }
243 245
244 @override 246 @override
245 void dispose() { 247 void dispose() {
246 if (root == null) return; 248 if (root == null) return;
247 root.selectAll('.stack-rdr-rowgroup').remove(); 249 root.selectAll('.stack-rdr-rowgroup').remove();
248 } 250 }
(...skipping 29 matching lines...) Expand all
278 }); 280 });
279 281
280 return new Extent(min, max); 282 return new Extent(min, max);
281 } 283 }
282 284
283 @override 285 @override
284 void handleStateChanges(List<ChangeRecord> changes) { 286 void handleStateChanges(List<ChangeRecord> changes) {
285 var groups = host.querySelectorAll('.stack-rdr-rowgroup'); 287 var groups = host.querySelectorAll('.stack-rdr-rowgroup');
286 if (groups == null || groups.isEmpty) return; 288 if (groups == null || groups.isEmpty) return;
287 289
288 for(int i = 0, len = groups.length; i < len; ++i) { 290 for (int i = 0, len = groups.length; i < len; ++i) {
289 var group = groups.elementAt(i), 291 var group = groups.elementAt(i),
290 bars = group.querySelectorAll('.stack-rdr-bar'), 292 bars = group.querySelectorAll('.stack-rdr-bar'),
291 row = int.parse(group.dataset['row']); 293 row = int.parse(group.dataset['row']);
292 294
293 for(int j = 0, barsCount = bars.length; j < barsCount; ++j) { 295 for (int j = 0, barsCount = bars.length; j < barsCount; ++j) {
294 var bar = bars.elementAt(j), 296 var bar = bars.elementAt(j),
295 column = int.parse(bar.dataset['column']), 297 column = int.parse(bar.dataset['column']),
296 color = colorForValue(column, row), 298 color = colorForValue(column, row),
297 filter = filterForValue(column, row); 299 filter = filterForValue(column, row);
298 300
299 bar.classes.removeAll(ChartState.VALUE_CLASS_NAMES); 301 bar.classes.removeAll(ChartState.VALUE_CLASS_NAMES);
300 bar.classes.addAll(stylesForValue(column, row)); 302 bar.classes.addAll(stylesForValue(column, row));
301 bar.attributes 303 bar.attributes
302 ..['fill'] = color 304 ..['fill'] = color
303 ..['stroke'] = color; 305 ..['stroke'] = color;
304 if (isNullOrEmpty(filter)) { 306 if (isNullOrEmpty(filter)) {
305 bar.attributes.remove('filter'); 307 bar.attributes.remove('filter');
306 } else { 308 } else {
307 bar.attributes['filter'] = filter; 309 bar.attributes['filter'] = filter;
308 } 310 }
309 } 311 }
310 } 312 }
311 } 313 }
312 314
313 void _event(StreamController controller, data, int index, Element e) { 315 void _event(StreamController controller, data, int index, Element e) {
314 if (controller == null) return; 316 if (controller == null) return;
315 var rowStr = e.parent.dataset['row']; 317 var rowStr = e.parent.dataset['row'];
316 var row = rowStr != null ? int.parse(rowStr) : null; 318 var row = rowStr != null ? int.parse(rowStr) : null;
317 controller.add(new DefaultChartEventImpl( 319 controller.add(new DefaultChartEventImpl(scope.event, area, series, row,
318 scope.event, area, series, row,
319 series.measures.elementAt(_reverseIdx(index)), data)); 320 series.measures.elementAt(_reverseIdx(index)), data));
320 } 321 }
321 322
322 // Stacked bar chart renders items from bottom to top (first measure is at 323 // Stacked bar chart renders items from bottom to top (first measure is at
323 // the bottom of the stack). We use [_reversedIdx] instead of index to 324 // the bottom of the stack). We use [_reversedIdx] instead of index to
324 // match the color and order of what is displayed in the legend. 325 // match the color and order of what is displayed in the legend.
325 int _reverseIdx(int index) => series.measures.length - 1 - index; 326 int _reverseIdx(int index) => series.measures.length - 1 - index;
326 } 327 }
OLDNEW
« no previous file with comments | « packages/charted/lib/charts/cartesian_renderers/line_chart_renderer.dart ('k') | packages/charted/lib/charts/chart_area.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698