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

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

Issue 2989763002: Update charted to 0.4.8 and roll (Closed)
Patch Set: Removed Cutch from list of reviewers Created 3 years, 4 months 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 // Copyright 2017 Google Inc. All rights reserved.
2 // Copyright 2014 Google Inc. All rights reserved.
3 // 2 //
4 // Use of this source code is governed by a BSD-style 3 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at 4 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd 5 // https://developers.google.com/open-source/licenses/bsd
7 // 6 //
8 7
9 part of charted.charts; 8 part of charted.charts;
10 9
11 class LineChartRenderer extends CartesianRendererBase { 10 class StackedLineChartRenderer extends CartesianRendererBase {
12 final Iterable<int> dimensionsUsingBand = const []; 11 final Iterable<int> dimensionsUsingBand = const [];
13 12
14 final bool alwaysAnimate; 13 final bool alwaysAnimate;
14 final bool showHoverCardOnTrackedDataPoints;
15 final bool trackDataPoints; 15 final bool trackDataPoints;
16 final bool trackOnDimensionAxis; 16 final bool trackOnDimensionAxis;
17 final int quantitativeScaleProximity; 17 final int quantitativeScaleProximity;
18 18
19 bool _trackingPointsCreated = false; 19 bool _trackingPointsCreated = false;
20 List _xPositions = []; 20 List _xPositions = [];
21 21
22 // Currently hovered row/column 22 // Currently hovered row/column
23 int _savedOverRow = 0; 23 int _savedOverRow = 0;
24 int _savedOverColumn = 0; 24 int _savedOverColumn = 0;
25 25
26 int currentDataIndex = -1; 26 int currentDataIndex = -1;
27 27
28 @override 28 @override
29 final String name = "line-rdr"; 29 final String name = "stacked-line-rdr";
30 30
31 LineChartRenderer( 31 StackedLineChartRenderer(
32 {this.alwaysAnimate: false, 32 {this.alwaysAnimate: false,
33 this.showHoverCardOnTrackedDataPoints: false,
33 this.trackDataPoints: true, 34 this.trackDataPoints: true,
34 this.trackOnDimensionAxis: false, 35 this.trackOnDimensionAxis: false,
35 this.quantitativeScaleProximity: 5}); 36 this.quantitativeScaleProximity: 5});
36 37
37 // Returns false if the number of dimension axes on the area is 0. 38 // Returns false if the number of dimension axes on the area is 0.
38 // Otherwise, the first dimension scale is used to render the chart. 39 // Otherwise, the first dimension scale is used to render the chart.
39 @override 40 @override
40 bool prepare(ChartArea area, ChartSeries series) { 41 bool prepare(ChartArea area, ChartSeries series) {
41 _ensureAreaAndSeries(area, series); 42 _ensureAreaAndSeries(area, series);
42 if (trackDataPoints != false) { 43 if (trackDataPoints) {
43 _trackPointerInArea(); 44 _trackPointerInArea();
44 } 45 }
45 return area is CartesianArea; 46 return area is CartesianArea;
46 } 47 }
47 48
48 @override 49 @override
49 void draw(Element element, {Future schedulePostRender}) { 50 void draw(Element element, {Future schedulePostRender}) {
50 _ensureReadyToDraw(element); 51 _ensureReadyToDraw(element);
51 52
52 var measureScale = area.measureScales(series).first, 53 var measureScale = area.measureScales(series).first,
53 dimensionScale = area.dimensionScales.first; 54 dimensionScale = area.dimensionScales.first;
54 55
55 // Create lists of values in measure columns.
56 var lines = series.measures.map((column) {
57 return area.data.rows.map((values) => values[column]).toList();
58 }).toList();
59
60 // We only support one dimension axes, so we always use the 56 // We only support one dimension axes, so we always use the
61 // first dimension. 57 // first dimension.
62 var x = area.data.rows 58 var x = area.data.rows
63 .map((row) => row.elementAt(area.config.dimensions.first)) 59 .map((row) => row.elementAt(area.config.dimensions.first))
64 .toList(); 60 .toList();
65 61
62 var accumulated = new List.filled(x.length, 0.0);
63
64 var reversedMeasures = series.measures.toList().reversed.toList();
65 // Create lists of values used for drawing.
66 // First Half: previous values reversed (need for drawing)
67 // Second Half: current accumulated values (need for drawing)
68 var lines = reversedMeasures.map((column) {
69 var row = area.data.rows.map((values) => values[column]).toList();
70 return accumulated.reversed.toList()..addAll(
71 new List.generate(x.length, (i) => accumulated[i] += row[i]));
72 }).toList();
73
66 var rangeBandOffset = 74 var rangeBandOffset =
67 dimensionScale is OrdinalScale ? dimensionScale.rangeBand / 2 : 0; 75 dimensionScale is OrdinalScale ? dimensionScale.rangeBand / 2 : 0;
68 76
69 // If tracking data points is enabled, cache location of points that 77 // If tracking data points is enabled, cache location of points that
70 // represent data. 78 // represent data.
71 if (trackDataPoints) { 79 if (trackDataPoints) {
72 _xPositions = 80 _xPositions =
73 x.map((val) => dimensionScale.scale(val) + rangeBandOffset).toList(); 81 x.map((val) => dimensionScale.scale(val) + rangeBandOffset).toList();
74 } 82 }
75 83
76 var line = new SvgLine( 84 var fillLine = new SvgLine(
85 xValueAccessor: (d, i) {
86 // The first x.length values are the bottom part of the path that
87 // should be drawn backword. The second part is the accumulated values
88 // that should be drawn forward.
89 var xval = i < x.length ? x[x.length - i - 1] : x[i - x.length];
90 return dimensionScale.scale(xval) + rangeBandOffset;
91 },
92 yValueAccessor: (d, i) => measureScale.scale(d));
93 var strokeLine = new SvgLine(
77 xValueAccessor: (d, i) => dimensionScale.scale(x[i]) + rangeBandOffset, 94 xValueAccessor: (d, i) => dimensionScale.scale(x[i]) + rangeBandOffset,
78 yValueAccessor: (d, i) => measureScale.scale(d)); 95 yValueAccessor: (d, i) => measureScale.scale(d));
79 96
80 // Add lines and hook up hover and selection events. 97 // Add lines and hook up hover and selection events.
81 var svgLines = root.selectAll('.line-rdr-line').data(lines); 98 var svgLines = root.selectAll('.stacked-line-rdr-line').data(lines.reversed) ;
82 svgLines.enter.append('path').each((d, i, e) { 99 svgLines.enter.append('g');
83 e.attributes['fill'] = 'none';
84 });
85 100
86 svgLines.each((d, i, e) { 101 svgLines.each((d, i, e) {
87 var column = series.measures.elementAt(i), 102 var column = series.measures.elementAt(i),
88 color = colorForColumn(column), 103 color = colorForColumn(column),
89 filter = filterForColumn(column), 104 filter = filterForColumn(column),
90 styles = stylesForColumn(column); 105 styles = stylesForColumn(column),
106 fill = new SvgElement.tag('path'),
107 stroke = new SvgElement.tag('path'),
108 fillData = d,
109 // Second half contains the accumulated data for this measure
110 strokeData = d.sublist(x.length, d.length);
91 e.attributes 111 e.attributes
92 ..['d'] = line.path(d, i, e)
93 ..['stroke'] = color 112 ..['stroke'] = color
113 ..['fill'] = color
94 ..['class'] = styles.isEmpty 114 ..['class'] = styles.isEmpty
95 ? 'line-rdr-line' 115 ? 'stacked-line-rdr-line'
96 : 'line-rdr-line ${styles.join(' ')}' 116 : 'stacked-line-rdr-line ${styles.join(' ')}'
97 ..['data-column'] = '$column'; 117 ..['data-column'] = '$column';
118 fill.attributes
119 ..['d'] = fillLine.path(fillData, i, e)
120 ..['stroke'] = 'none';
121 stroke.attributes
122 ..['d'] = strokeLine.path(strokeData, i, e)
123 ..['fill'] = 'none';
124 e.children = [fill, stroke];
98 if (isNullOrEmpty(filter)) { 125 if (isNullOrEmpty(filter)) {
99 e.attributes.remove('filter'); 126 e.attributes.remove('filter');
100 } else { 127 } else {
101 e.attributes['filter'] = filter; 128 e.attributes['filter'] = filter;
102 } 129 }
103 }); 130 });
104 131
105 if (area.state != null) { 132 if (area.state != null) {
106 svgLines 133 svgLines
107 ..on('click', (d, i, e) => _mouseClickHandler(d, i, e)) 134 ..on('click', (d, i, e) => _mouseClickHandler(d, i, e))
108 ..on('mouseover', (d, i, e) => _mouseOverHandler(d, i, e)) 135 ..on('mouseover', (d, i, e) => _mouseOverHandler(d, i, e))
109 ..on('mouseout', (d, i, e) => _mouseOutHandler(d, i, e)); 136 ..on('mouseout', (d, i, e) => _mouseOutHandler(d, i, e));
110 } 137 }
111 138
112 svgLines.exit.remove(); 139 svgLines.exit.remove();
113 } 140 }
114 141
115 @override 142 @override
116 void dispose() { 143 void dispose() {
144 _disposer.dispose();
117 if (root == null) return; 145 if (root == null) return;
118 root.selectAll('.line-rdr-line').remove(); 146 root.selectAll('.stacked-line-rdr-line').remove();
119 root.selectAll('.line-rdr-point').remove(); 147 root.selectAll('.stacked-line-rdr-point').remove();
120 _disposer.dispose(); 148 }
149
150 @override
151 Extent get extent {
152 assert(area != null && series != null);
153 var rows = area.data.rows,
154 max = SMALL_INT_MIN,
155 min = SMALL_INT_MAX,
156 rowIndex = 0;
157
158 rows.forEach((row) {
159 var line = null;
160 series.measures.forEach((idx) {
161 var value = row.elementAt(idx);
162 if (value != null && value.isFinite) {
163 if (line == null) line = 0.0;
164 line += value;
165 }
166 });
167 if (line > max) max = line;
168 if (line < min) min = line;
169 rowIndex++;
170 });
171
172 return new Extent(min, max);
121 } 173 }
122 174
123 @override 175 @override
124 void handleStateChanges(List<ChangeRecord> changes) { 176 void handleStateChanges(List<ChangeRecord> changes) {
125 var lines = host.querySelectorAll('.line-rdr-line'); 177 var lines = host.querySelectorAll('.stacked-line-rdr-line');
126 if (lines == null || lines.isEmpty) return; 178 if (lines == null || lines.isEmpty) return;
127 179
128 for (int i = 0, len = lines.length; i < len; ++i) { 180 for (int i = 0, len = lines.length; i < len; ++i) {
129 var line = lines.elementAt(i), 181 var line = lines.elementAt(i),
130 column = int.parse(line.dataset['column']), 182 column = int.parse(line.dataset['column']),
131 filter = filterForColumn(column); 183 filter = filterForColumn(column);
132 line.classes.removeAll(ChartState.COLUMN_CLASS_NAMES); 184 line.classes.removeAll(ChartState.COLUMN_CLASS_NAMES);
133 line.classes.addAll(stylesForColumn(column)); 185 line.classes.addAll(stylesForColumn(column));
134 line.attributes['stroke'] = colorForColumn(column); 186 line.attributes['stroke'] = colorForColumn(column);
187 line.attributes['fill'] = colorForColumn(column);
135 188
136 if (isNullOrEmpty(filter)) { 189 if (isNullOrEmpty(filter)) {
137 line.attributes.remove('filter'); 190 line.attributes.remove('filter');
138 } else { 191 } else {
139 line.attributes['filter'] = filter; 192 line.attributes['filter'] = filter;
140 } 193 }
141 } 194 }
142 } 195 }
143 196
144 void _createTrackingCircles() { 197 void _createTrackingCircles() {
145 var linePoints = root.selectAll('.line-rdr-point').data(series.measures); 198 var linePoints = root.selectAll('.stacked-line-rdr-point')
199 .data(series.measures.toList().reversed);
146 linePoints.enter.append('circle').each((d, i, e) { 200 linePoints.enter.append('circle').each((d, i, e) {
147 e.classes.add('line-rdr-point'); 201 e.classes.add('stacked-line-rdr-point');
148 e.attributes['r'] = '4'; 202 e.attributes['r'] = '4';
149 }); 203 });
150 204
151 linePoints 205 linePoints
152 ..each((d, i, e) { 206 ..each((d, i, e) {
153 var color = colorForColumn(d); 207 var color = colorForColumn(d);
154 e.attributes 208 e.attributes
155 ..['r'] = '4' 209 ..['r'] = '4'
156 ..['stroke'] = color 210 ..['stroke'] = color
157 ..['fill'] = color 211 ..['fill'] = color
158 ..['data-column'] = '$d'; 212 ..['data-column'] = '$d';
159 }) 213 })
160 ..on('click', _mouseClickHandler) 214 ..on('click', _mouseClickHandler)
161 ..on('mousemove', _mouseOverHandler) // Ensure that we update values 215 ..on('mousemove', _mouseOverHandler) // Ensure that we update values
162 ..on('mouseover', _mouseOverHandler) 216 ..on('mouseover', _mouseOverHandler)
163 ..on('mouseout', _mouseOutHandler); 217 ..on('mouseout', _mouseOutHandler);
164 218
165 linePoints.exit.remove(); 219 linePoints.exit.remove();
166 _trackingPointsCreated = true; 220 _trackingPointsCreated = true;
167 } 221 }
168 222
169 void _showTrackingCircles(int row) { 223 void _showTrackingCircles(ChartEvent event, int row) {
170 if (_trackingPointsCreated == false) { 224 if (_trackingPointsCreated == false) {
171 _createTrackingCircles(); 225 _createTrackingCircles();
172 } 226 }
173 227
228 double cumulated = 0.0;
174 var yScale = area.measureScales(series).first; 229 var yScale = area.measureScales(series).first;
175 root.selectAll('.line-rdr-point').each((d, i, e) { 230 root.selectAll('.stacked-line-rdr-point').each((d, i, e) {
176 var x = _xPositions[row], 231 var x = _xPositions[row],
177 measureVal = area.data.rows.elementAt(row).elementAt(d); 232 measureVal = cumulated += area.data.rows.elementAt(row).elementAt(d);
178 if (measureVal != null && measureVal.isFinite) { 233 if (measureVal != null && measureVal.isFinite) {
179 var color = colorForColumn(d), filter = filterForColumn(d); 234 var color = colorForColumn(d), filter = filterForColumn(d);
180 e.attributes 235 e.attributes
181 ..['cx'] = '$x' 236 ..['cx'] = '$x'
182 ..['cy'] = '${yScale.scale(measureVal)}' 237 ..['cy'] = '${yScale.scale(measureVal)}'
183 ..['fill'] = color 238 ..['fill'] = color
184 ..['stroke'] = color 239 ..['stroke'] = color
185 ..['data-row'] = '$row'; 240 ..['data-row'] = '$row';
186 e.style 241 e.style
187 ..setProperty('opacity', '1') 242 ..setProperty('opacity', '1')
188 ..setProperty('visibility', 'visible'); 243 ..setProperty('visibility', 'visible');
189 if (isNullOrEmpty(filter)) { 244 if (isNullOrEmpty(filter)) {
190 e.attributes.remove('filter'); 245 e.attributes.remove('filter');
191 } else { 246 } else {
192 e.attributes['filter'] = filter; 247 e.attributes['filter'] = filter;
193 } 248 }
194 } else { 249 } else {
195 e.style 250 e.style
196 ..setProperty('opacity', '$EPSILON') 251 ..setProperty('opacity', '$EPSILON')
197 ..setProperty('visibility', 'hidden'); 252 ..setProperty('visibility', 'hidden');
198 } 253 }
199 }); 254 });
255
256 if (showHoverCardOnTrackedDataPoints) {
257 var firstMeasureColumn = series.measures.first;
258 mouseOverController.add(new DefaultChartEventImpl(
259 event.source, area, series, row, firstMeasureColumn, 0));
260 _savedOverRow = row;
261 _savedOverColumn = firstMeasureColumn;
262 }
200 } 263 }
201 264
202 void _hideTrackingCircles() { 265 void _hideTrackingCircles(ChartEvent event) {
203 root.selectAll('.line-rdr-point') 266 root.selectAll('.stacked-line-rdr-point')
204 ..style('opacity', '0.0') 267 ..style('opacity', '0.0')
205 ..style('visibility', 'hidden'); 268 ..style('visibility', 'hidden');
269 if (showHoverCardOnTrackedDataPoints) {
270 mouseOutController.add(new DefaultChartEventImpl(
271 event.source, area, series, _savedOverRow, _savedOverColumn, 0));
272 }
206 } 273 }
207 274
208 int _getNearestRowIndex(num x) { 275 int _getNearestRowIndex(num x) {
209 var lastSmallerValue = 0; 276 double lastSmallerValue = 0.0;
210 var chartX = x - area.layout.renderArea.x; 277 var chartX = x - area.layout.renderArea.x;
211 for (var i = 0; i < _xPositions.length; i++) { 278 for (var i = 0; i < _xPositions.length; i++) {
212 var pos = _xPositions[i]; 279 var pos = _xPositions[i];
213 if (pos < chartX) { 280 if (pos < chartX) {
214 lastSmallerValue = pos; 281 lastSmallerValue = pos;
215 } else { 282 } else {
216 return i == 0 283 return i == 0
217 ? 0 284 ? 0
218 : (chartX - lastSmallerValue <= pos - chartX) ? i - 1 : i; 285 : (chartX - lastSmallerValue <= pos - chartX) ? i - 1 : i;
219 } 286 }
220 } 287 }
221 return _xPositions.length - 1; 288 return _xPositions.length - 1;
222 } 289 }
223 290
224 void _trackPointerInArea() { 291 void _trackPointerInArea() {
225 _trackingPointsCreated = false; 292 _trackingPointsCreated = false;
226 _disposer.add(area.onMouseMove.listen((ChartEvent event) { 293 _disposer.add(area.onMouseMove.listen((ChartEvent event) {
227 if (area.layout.renderArea.contains(event.chartX, event.chartY)) { 294 if (area.layout.renderArea.contains(event.chartX, event.chartY)) {
228 var row = _getNearestRowIndex(event.chartX); 295 var row = _getNearestRowIndex(event.chartX);
229 window.animationFrame.then((_) => _showTrackingCircles(row)); 296 window.animationFrame.then((_) {
297 _showTrackingCircles(event, row);
298 });
230 } else { 299 } else {
231 _hideTrackingCircles(); 300 _hideTrackingCircles(event);
232 } 301 }
233 })); 302 }));
234 _disposer.add(area.onMouseOut.listen((ChartEvent event) { 303 _disposer.add(area.onMouseOut.listen((ChartEvent event) {
235 _hideTrackingCircles(); 304 _hideTrackingCircles(event);
236 })); 305 }));
237 } 306 }
238 307
239 void _mouseClickHandler(d, int i, Element e) { 308 void _mouseClickHandler(d, int i, Element e) {
240 if (area.state != null) { 309 if (area.state != null) {
241 area.state.select(int.parse(e.dataset['column'])); 310 var selectedColumn = int.parse(e.dataset['column']);
311 area.state.isSelected(selectedColumn)
312 ? area.state.unselect(selectedColumn)
313 : area.state.select(selectedColumn);
242 } 314 }
243 if (mouseClickController != null && e.tagName == 'circle') { 315 if (mouseClickController != null && e.tagName == 'circle') {
244 var row = int.parse(e.dataset['row']), 316 var row = int.parse(e.dataset['row']),
245 column = int.parse(e.dataset['column']); 317 column = int.parse(e.dataset['column']);
246 mouseClickController.add( 318 mouseClickController.add(
247 new DefaultChartEventImpl(scope.event, area, series, row, column, d)); 319 new DefaultChartEventImpl(scope.event, area, series, row, column, d));
248 } 320 }
249 } 321 }
250 322
251 void _mouseOverHandler(d, int i, Element e) { 323 void _mouseOverHandler(d, int i, Element e) {
(...skipping 12 matching lines...) Expand all
264 if (area.state != null && 336 if (area.state != null &&
265 area.state.preview == int.parse(e.dataset['column'])) { 337 area.state.preview == int.parse(e.dataset['column'])) {
266 area.state.preview = null; 338 area.state.preview = null;
267 } 339 }
268 if (mouseOutController != null && e.tagName == 'circle') { 340 if (mouseOutController != null && e.tagName == 'circle') {
269 mouseOutController.add(new DefaultChartEventImpl( 341 mouseOutController.add(new DefaultChartEventImpl(
270 scope.event, area, series, _savedOverRow, _savedOverColumn, d)); 342 scope.event, area, series, _savedOverRow, _savedOverColumn, d));
271 } 343 }
272 } 344 }
273 } 345 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698