OLD | NEW |
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 /// Displays either one or two dimension axes and zero or more measure axis. | 11 /// Displays either one or two dimension axes and zero or more measure axis. |
12 /// The number of measure axes displayed is zero in charts like bubble chart | 12 /// The number of measure axes displayed is zero in charts like bubble chart |
13 /// which contain two dimension axes. | 13 /// which contain two dimension axes. |
14 class DefaultCartesianAreaImpl implements CartesianArea { | 14 class DefaultCartesianAreaImpl implements CartesianArea { |
15 /// Default identifiers used by the measure axes | 15 /// Default identifiers used by the measure axes |
16 static const MEASURE_AXIS_IDS = const['_default']; | 16 static const MEASURE_AXIS_IDS = const ['_default']; |
17 | 17 |
18 /// Orientations used by measure axes. First, when "x" axis is the primary | 18 /// Orientations used by measure axes. First, when "x" axis is the primary |
19 /// and the only dimension. Second, when "y" axis is the primary and the only | 19 /// and the only dimension. Second, when "y" axis is the primary and the only |
20 /// dimension. | 20 /// dimension. |
21 static const MEASURE_AXIS_ORIENTATIONS = const[ | 21 static const MEASURE_AXIS_ORIENTATIONS = const [ |
22 const[ORIENTATION_LEFT, ORIENTATION_RIGHT], | 22 const [ORIENTATION_LEFT, ORIENTATION_RIGHT], |
23 const[ORIENTATION_BOTTOM, ORIENTATION_TOP] | 23 const [ORIENTATION_BOTTOM, ORIENTATION_TOP] |
24 ]; | 24 ]; |
25 | 25 |
26 /// Orientations used by the dimension axes. First, when "x" is the | 26 /// Orientations used by the dimension axes. First, when "x" is the |
27 /// primary dimension and the last one for cases where "y" axis is primary | 27 /// primary dimension and the last one for cases where "y" axis is primary |
28 /// dimension. | 28 /// dimension. |
29 static const DIMENSION_AXIS_ORIENTATIONS = const[ | 29 static const DIMENSION_AXIS_ORIENTATIONS = const [ |
30 const[ORIENTATION_BOTTOM, ORIENTATION_LEFT], | 30 const [ORIENTATION_BOTTOM, ORIENTATION_LEFT], |
31 const[ORIENTATION_LEFT, ORIENTATION_BOTTOM] | 31 const [ORIENTATION_LEFT, ORIENTATION_BOTTOM] |
32 ]; | 32 ]; |
33 | 33 |
34 /// Mapping of measure axis Id to it's axis. | 34 /// Mapping of measure axis Id to it's axis. |
35 final _measureAxes = new LinkedHashMap<String, DefaultChartAxisImpl>(); | 35 final _measureAxes = new LinkedHashMap<String, DefaultChartAxisImpl>(); |
36 | 36 |
37 /// Mapping of dimension column index to it's axis. | 37 /// Mapping of dimension column index to it's axis. |
38 final _dimensionAxes = new LinkedHashMap<int, DefaultChartAxisImpl>(); | 38 final _dimensionAxes = new LinkedHashMap<int, DefaultChartAxisImpl>(); |
39 | 39 |
40 /// Disposer for all change stream subscriptions related to data. | 40 /// Disposer for all change stream subscriptions related to data. |
41 final _dataEventsDisposer = new SubscriptionsDisposer(); | 41 final _dataEventsDisposer = new SubscriptionsDisposer(); |
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
92 StreamController<ChartEvent> _valueMouseClickController; | 92 StreamController<ChartEvent> _valueMouseClickController; |
93 StreamController<ChartArea> _chartAxesUpdatedController; | 93 StreamController<ChartArea> _chartAxesUpdatedController; |
94 | 94 |
95 DefaultCartesianAreaImpl( | 95 DefaultCartesianAreaImpl( |
96 this.host, | 96 this.host, |
97 ChartData data, | 97 ChartData data, |
98 ChartConfig config, | 98 ChartConfig config, |
99 bool autoUpdate, | 99 bool autoUpdate, |
100 this.useTwoDimensionAxes, | 100 this.useTwoDimensionAxes, |
101 this.useRowColoring, | 101 this.useRowColoring, |
102 this.state) : _autoUpdate = autoUpdate { | 102 this.state) |
| 103 : _autoUpdate = autoUpdate { |
103 assert(host != null); | 104 assert(host != null); |
104 assert(isNotInline(host)); | 105 assert(isNotInline(host)); |
105 | 106 |
106 this.data = data; | 107 this.data = data; |
107 this.config = config; | 108 this.config = config; |
108 theme = new QuantumChartTheme(); | 109 theme = new QuantumChartTheme(); |
109 | 110 |
110 Transition.defaultEasingType = theme.transitionEasingType; | 111 Transition.defaultEasingType = theme.transitionEasingType; |
111 Transition.defaultEasingMode = theme.transitionEasingMode; | 112 Transition.defaultEasingMode = theme.transitionEasingMode; |
112 Transition.defaultDurationMilliseconds = | 113 Transition.defaultDurationMilliseconds = |
(...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
187 } | 188 } |
188 | 189 |
189 @override | 190 @override |
190 bool get autoUpdate => _autoUpdate; | 191 bool get autoUpdate => _autoUpdate; |
191 | 192 |
192 /// Gets measure axis from cache - creates a new instance of _ChartAxis | 193 /// Gets measure axis from cache - creates a new instance of _ChartAxis |
193 /// if one was not already created for the given [axisId]. | 194 /// if one was not already created for the given [axisId]. |
194 DefaultChartAxisImpl _getMeasureAxis(String axisId) { | 195 DefaultChartAxisImpl _getMeasureAxis(String axisId) { |
195 _measureAxes.putIfAbsent(axisId, () { | 196 _measureAxes.putIfAbsent(axisId, () { |
196 var axisConf = config.getMeasureAxis(axisId), | 197 var axisConf = config.getMeasureAxis(axisId), |
197 axis = axisConf != null ? | 198 axis = axisConf != null |
198 new DefaultChartAxisImpl.withAxisConfig(this, axisConf) : | 199 ? new DefaultChartAxisImpl.withAxisConfig(this, axisConf) |
199 new DefaultChartAxisImpl(this); | 200 : new DefaultChartAxisImpl(this); |
200 return axis; | 201 return axis; |
201 }); | 202 }); |
202 return _measureAxes[axisId]; | 203 return _measureAxes[axisId]; |
203 } | 204 } |
204 | 205 |
205 /// Gets a dimension axis from cache - creates a new instance of _ChartAxis | 206 /// Gets a dimension axis from cache - creates a new instance of _ChartAxis |
206 /// if one was not already created for the given dimension [column]. | 207 /// if one was not already created for the given dimension [column]. |
207 DefaultChartAxisImpl _getDimensionAxis(int column) { | 208 DefaultChartAxisImpl _getDimensionAxis(int column) { |
208 _dimensionAxes.putIfAbsent(column, () { | 209 _dimensionAxes.putIfAbsent(column, () { |
209 var axisConf = config.getDimensionAxis(column), | 210 var axisConf = config.getDimensionAxis(column), |
210 axis = axisConf != null ? | 211 axis = axisConf != null |
211 new DefaultChartAxisImpl.withAxisConfig(this, axisConf) : | 212 ? new DefaultChartAxisImpl.withAxisConfig(this, axisConf) |
212 new DefaultChartAxisImpl(this); | 213 : new DefaultChartAxisImpl(this); |
213 return axis; | 214 return axis; |
214 }); | 215 }); |
215 return _dimensionAxes[column]; | 216 return _dimensionAxes[column]; |
216 } | 217 } |
217 | 218 |
218 /// All columns rendered by a series must be of the same type. | 219 /// All columns rendered by a series must be of the same type. |
219 bool _isSeriesValid(ChartSeries s) { | 220 bool _isSeriesValid(ChartSeries s) { |
220 var first = data.columns.elementAt(s.measures.first).type; | 221 var first = data.columns.elementAt(s.measures.first).type; |
221 return s.measures.every((i) => | 222 return s.measures.every((i) => |
222 (i < data.columns.length) && data.columns.elementAt(i).type == first); | 223 (i < data.columns.length) && data.columns.elementAt(i).type == first); |
223 } | 224 } |
224 | 225 |
225 @override | 226 @override |
226 Iterable<Scale> get dimensionScales => | 227 Iterable<Scale> get dimensionScales => |
227 config.dimensions.map((int column) => _getDimensionAxis(column).scale); | 228 config.dimensions.map((int column) => _getDimensionAxis(column).scale); |
228 | 229 |
229 @override | 230 @override |
230 Iterable<Scale> measureScales(ChartSeries series) { | 231 Iterable<Scale> measureScales(ChartSeries series) { |
231 var axisIds = isNullOrEmpty(series.measureAxisIds) | 232 var axisIds = isNullOrEmpty(series.measureAxisIds) |
232 ? MEASURE_AXIS_IDS | 233 ? MEASURE_AXIS_IDS |
233 : series.measureAxisIds; | 234 : series.measureAxisIds; |
234 return axisIds.map((String id) => _getMeasureAxis(id).scale); | 235 return axisIds.map((String id) => _getMeasureAxis(id).scale); |
235 } | 236 } |
236 | 237 |
237 /// Computes the size of chart and if changed from the previous time | 238 /// Computes the size of chart and if changed from the previous time |
238 /// size was computed, sets attributes on svg element | 239 /// size was computed, sets attributes on svg element |
239 Rect _computeChartSize() { | 240 Rect _computeChartSize() { |
240 int width = host.clientWidth, | 241 int width = host.clientWidth, height = host.clientHeight; |
241 height = host.clientHeight; | |
242 | 242 |
243 if (config.minimumSize != null) { | 243 if (config.minimumSize != null) { |
244 width = max([width, config.minimumSize.width]); | 244 width = max([width, config.minimumSize.width]); |
245 height = max([height, config.minimumSize.height]); | 245 height = max([height, config.minimumSize.height]); |
246 } | 246 } |
247 | 247 |
248 AbsoluteRect padding = theme.padding; | 248 AbsoluteRect padding = theme.padding; |
249 num paddingLeft = config.isRTL ? padding.end : padding.start; | 249 num paddingLeft = config.isRTL ? padding.end : padding.start; |
250 Rect current = new Rect(paddingLeft, padding.top, | 250 Rect current = new Rect( |
| 251 paddingLeft, |
| 252 padding.top, |
251 width - (padding.start + padding.end), | 253 width - (padding.start + padding.end), |
252 height - (padding.top + padding.bottom)); | 254 height - (padding.top + padding.bottom)); |
253 if (layout.chartArea == null || layout.chartArea != current) { | 255 if (layout.chartArea == null || layout.chartArea != current) { |
254 _svg.attr('width', width.toString()); | 256 _svg.attr('width', width.toString()); |
255 _svg.attr('height', height.toString()); | 257 _svg.attr('height', height.toString()); |
256 layout.chartArea = current; | 258 layout.chartArea = current; |
257 | 259 |
258 var transform = 'translate(${paddingLeft},${padding.top})'; | 260 var transform = 'translate(${paddingLeft},${padding.top})'; |
259 visualization.first.attributes['transform'] = transform; | 261 visualization.first.attributes['transform'] = transform; |
260 lowerBehaviorPane.first.attributes['transform'] = transform; | 262 lowerBehaviorPane.first.attributes['transform'] = transform; |
261 upperBehaviorPane.first.attributes['transform'] = transform; | 263 upperBehaviorPane.first.attributes['transform'] = transform; |
262 } | 264 } |
263 return layout.chartArea; | 265 return layout.chartArea; |
264 } | 266 } |
265 | 267 |
266 @override | 268 @override |
267 draw({bool preRender:false, Future schedulePostRender}) { | 269 draw({bool preRender: false, Future schedulePostRender}) { |
268 assert(data != null && config != null); | 270 assert(data != null && config != null); |
269 assert(config.series != null && config.series.isNotEmpty); | 271 assert(config.series != null && config.series.isNotEmpty); |
270 | 272 |
271 // One time initialization. | 273 // One time initialization. |
272 // Each [ChartArea] has it's own [SelectionScope] | 274 // Each [ChartArea] has it's own [SelectionScope] |
273 if (_scope == null) { | 275 if (_scope == null) { |
274 _scope = new SelectionScope.element(host); | 276 _scope = new SelectionScope.element(host); |
275 _svg = _scope.append('svg:svg')..classed('chart-canvas'); | 277 _svg = _scope.append('svg:svg')..classed('chart-canvas'); |
276 if (!isNullOrEmpty(theme.filters)) { | 278 if (!isNullOrEmpty(theme.filters)) { |
277 var element = _svg.first, | 279 var element = _svg.first, |
278 defs = Namespace.createChildElement('defs', element) | 280 defs = Namespace.createChildElement('defs', element) |
279 ..append(new SvgElement.svg( | 281 ..append(new SvgElement.svg(theme.filters, |
280 theme.filters, treeSanitizer: new NullTreeSanitizer())); | 282 treeSanitizer: new NullTreeSanitizer())); |
281 _svg.first.append(defs); | 283 _svg.first.append(defs); |
282 } | 284 } |
283 | 285 |
284 lowerBehaviorPane = _svg.append('g')..classed('lower-render-pane'); | 286 lowerBehaviorPane = _svg.append('g')..classed('lower-render-pane'); |
285 visualization = _svg.append('g')..classed('chart-render-pane'); | 287 visualization = _svg.append('g')..classed('chart-render-pane'); |
286 upperBehaviorPane = _svg.append('g')..classed('upper-render-pane'); | 288 upperBehaviorPane = _svg.append('g')..classed('upper-render-pane'); |
287 | 289 |
288 if (_behaviors.isNotEmpty) { | 290 if (_behaviors.isNotEmpty) { |
289 _behaviors.forEach( | 291 _behaviors |
290 (b) => b.init(this, upperBehaviorPane, lowerBehaviorPane)); | 292 .forEach((b) => b.init(this, upperBehaviorPane, lowerBehaviorPane)); |
291 } | 293 } |
292 } | 294 } |
293 | 295 |
294 // Compute chart sizes and filter out unsupported series | 296 // Compute chart sizes and filter out unsupported series |
295 _computeChartSize(); | 297 _computeChartSize(); |
296 var series = config.series.where((s) => | 298 var series = config.series |
297 _isSeriesValid(s) && s.renderer.prepare(this, s)), | 299 .where((s) => _isSeriesValid(s) && s.renderer.prepare(this, s)), |
298 selection = visualization.selectAll('.series-group'). | 300 selection = visualization |
299 data(series, (x) => x.hashCode), | 301 .selectAll('.series-group') |
| 302 .data(series, (x) => x.hashCode), |
300 axesDomainCompleter = new Completer(); | 303 axesDomainCompleter = new Completer(); |
301 | 304 |
302 // Wait till the axes are rendered before rendering series. | 305 // Wait till the axes are rendered before rendering series. |
303 // In an SVG, z-index is based on the order of nodes in the DOM. | 306 // In an SVG, z-index is based on the order of nodes in the DOM. |
304 axesDomainCompleter.future.then((_) { | 307 axesDomainCompleter.future.then((_) { |
305 selection.enter.append('svg:g')..classed('series-group'); | 308 selection.enter.append('svg:g')..classed('series-group'); |
306 String transform = | 309 String transform = |
307 'translate(${layout.renderArea.x},${layout.renderArea.y})'; | 310 'translate(${layout.renderArea.x},${layout.renderArea.y})'; |
308 | 311 |
309 selection.each((ChartSeries s, _, Element group) { | 312 selection.each((ChartSeries s, _, Element group) { |
310 _ChartSeriesInfo info = _seriesInfoCache[s]; | 313 _ChartSeriesInfo info = _seriesInfoCache[s]; |
311 if (info == null) { | 314 if (info == null) { |
312 info = _seriesInfoCache[s] = new _ChartSeriesInfo(this, s); | 315 info = _seriesInfoCache[s] = new _ChartSeriesInfo(this, s); |
313 } | 316 } |
314 info.check(); | 317 info.check(); |
315 group.attributes['transform'] = transform; | 318 group.attributes['transform'] = transform; |
316 (s.renderer as CartesianRenderer) | 319 (s.renderer as CartesianRenderer) |
317 .draw(group, schedulePostRender:schedulePostRender); | 320 .draw(group, schedulePostRender: schedulePostRender); |
318 }); | 321 }); |
319 | 322 |
320 // A series that was rendered earlier isn't there anymore, remove it | 323 // A series that was rendered earlier isn't there anymore, remove it |
321 selection.exit | 324 selection.exit |
322 ..each((ChartSeries s, _, __) { | 325 ..each((ChartSeries s, _, __) { |
323 var info = _seriesInfoCache.remove(s); | 326 var info = _seriesInfoCache.remove(s); |
324 if (info != null) { | 327 if (info != null) { |
325 info.dispose(); | 328 info.dispose(); |
326 } | 329 } |
327 }) | 330 }) |
(...skipping 15 matching lines...) Expand all Loading... |
343 | 346 |
344 // Updates the legend if required. | 347 // Updates the legend if required. |
345 _updateLegend(); | 348 _updateLegend(); |
346 } | 349 } |
347 | 350 |
348 String _orientRTL(String orientation) => orientation; | 351 String _orientRTL(String orientation) => orientation; |
349 Scale _scaleRTL(Scale scale) => scale; | 352 Scale _scaleRTL(Scale scale) => scale; |
350 | 353 |
351 /// Initialize the axes - required even if the axes are not being displayed. | 354 /// Initialize the axes - required even if the axes are not being displayed. |
352 _initAxes({bool preRender: false}) { | 355 _initAxes({bool preRender: false}) { |
353 Map measureAxisUsers = <String,Iterable<ChartSeries>>{}; | 356 Map measureAxisUsers = <String, Iterable<ChartSeries>>{}; |
354 | 357 |
355 // Create necessary measures axes. | 358 // Create necessary measures axes. |
356 // If measure axes were not configured on the series, default is used. | 359 // If measure axes were not configured on the series, default is used. |
357 _series.forEach((ChartSeries s) { | 360 _series.forEach((ChartSeries s) { |
358 var measureAxisIds = isNullOrEmpty(s.measureAxisIds) | 361 var measureAxisIds = |
359 ? MEASURE_AXIS_IDS | 362 isNullOrEmpty(s.measureAxisIds) ? MEASURE_AXIS_IDS : s.measureAxisIds; |
360 : s.measureAxisIds; | |
361 measureAxisIds.forEach((axisId) { | 363 measureAxisIds.forEach((axisId) { |
362 _getMeasureAxis(axisId); // Creates axis if required | 364 _getMeasureAxis(axisId); // Creates axis if required |
363 var users = measureAxisUsers[axisId]; | 365 var users = measureAxisUsers[axisId]; |
364 if (users == null) { | 366 if (users == null) { |
365 measureAxisUsers[axisId] = [s]; | 367 measureAxisUsers[axisId] = [s]; |
366 } else { | 368 } else { |
367 users.add(s); | 369 users.add(s); |
368 } | 370 } |
369 }); | 371 }); |
370 }); | 372 }); |
371 | 373 |
372 // Now that we know a list of series using each measure axis, configure | 374 // Now that we know a list of series using each measure axis, configure |
(...skipping 22 matching lines...) Expand all Loading... |
395 ? [0, 1] | 397 ? [0, 1] |
396 : (highest < 0 ? [highest, 0] : [0, highest])) | 398 : (highest < 0 ? [highest, 0] : [0, highest])) |
397 : (lowest <= 0 ? [lowest, highest] : [0, highest]); | 399 : (lowest <= 0 ? [lowest, highest] : [0, highest]); |
398 } | 400 } |
399 axis.initAxisDomain(sampleCol, false, domain); | 401 axis.initAxisDomain(sampleCol, false, domain); |
400 }); | 402 }); |
401 | 403 |
402 // Configure dimension axes. | 404 // Configure dimension axes. |
403 int dimensionAxesCount = useTwoDimensionAxes ? 2 : 1; | 405 int dimensionAxesCount = useTwoDimensionAxes ? 2 : 1; |
404 config.dimensions.take(dimensionAxesCount).forEach((int column) { | 406 config.dimensions.take(dimensionAxesCount).forEach((int column) { |
405 var axis = _getDimensionAxis(column), | 407 var axis = _getDimensionAxis(column), |
406 sampleColumnSpec = data.columns.elementAt(column), | 408 sampleColumnSpec = data.columns.elementAt(column), |
407 values = data.rows.map((row) => row.elementAt(column)), | 409 values = data.rows.map((row) => row.elementAt(column)), |
408 domain; | 410 domain; |
409 | 411 |
410 if (sampleColumnSpec.useOrdinalScale) { | 412 if (sampleColumnSpec.useOrdinalScale) { |
411 domain = values.map((e) => e.toString()).toList(); | 413 domain = values.map((e) => e.toString()).toList(); |
412 } else { | 414 } else { |
413 var extent = new Extent.items(values); | 415 var extent = new Extent.items(values); |
414 domain = [extent.min, extent.max]; | 416 domain = [extent.min, extent.max]; |
415 } | 417 } |
416 axis.initAxisDomain(column, true, domain); | 418 axis.initAxisDomain(column, true, domain); |
417 }); | 419 }); |
418 | 420 |
419 // See if any dimensions need "band" on the axis. | 421 // See if any dimensions need "band" on the axis. |
420 dimensionsUsingBands.clear(); | 422 dimensionsUsingBands.clear(); |
421 List<bool> usingBands = [false, false]; | 423 List<bool> usingBands = [false, false]; |
422 _series.forEach((ChartSeries s) => | 424 _series.forEach((ChartSeries s) => |
423 (s.renderer as CartesianRenderer).dimensionsUsingBand.forEach((x) { | 425 (s.renderer as CartesianRenderer).dimensionsUsingBand.forEach((x) { |
424 if (x <= 1 && !(usingBands[x])) { | 426 if (x <= 1 && !(usingBands[x])) { |
425 usingBands[x] = true; | 427 usingBands[x] = true; |
426 dimensionsUsingBands.add(config.dimensions.elementAt(x)); | 428 dimensionsUsingBands.add(config.dimensions.elementAt(x)); |
427 } | 429 } |
428 })); | 430 })); |
429 | 431 |
430 // List of measure and dimension axes that are displayed | 432 // List of measure and dimension axes that are displayed |
431 assert( | 433 assert(isNullOrEmpty(config.displayedMeasureAxes) || |
432 isNullOrEmpty(config.displayedMeasureAxes) || | |
433 config.displayedMeasureAxes.length < 2); | 434 config.displayedMeasureAxes.length < 2); |
434 var measureAxesCount = dimensionAxesCount == 1 ? 2 : 0, | 435 var measureAxesCount = dimensionAxesCount == 1 ? 2 : 0, |
435 displayedMeasureAxes = (isNullOrEmpty(config.displayedMeasureAxes) | 436 displayedMeasureAxes = (isNullOrEmpty(config.displayedMeasureAxes) |
436 ? _measureAxes.keys.take(measureAxesCount) | 437 ? _measureAxes.keys.take(measureAxesCount) |
437 : config.displayedMeasureAxes.take(measureAxesCount)). | 438 : config.displayedMeasureAxes.take(measureAxesCount)) |
438 toList(growable: false), | 439 .toList(growable: false), |
439 displayedDimensionAxes = | 440 displayedDimensionAxes = |
440 config.dimensions.take(dimensionAxesCount).toList(growable: false); | 441 config.dimensions.take(dimensionAxesCount).toList(growable: false); |
441 | 442 |
442 // Compute size of the dimension axes | 443 // Compute size of the dimension axes |
443 if (config.renderDimensionAxes != false) { | 444 if (config.renderDimensionAxes != false) { |
444 var dimensionAxisOrientations = config.isLeftAxisPrimary | 445 var dimensionAxisOrientations = config.isLeftAxisPrimary |
445 ? DIMENSION_AXIS_ORIENTATIONS.last | 446 ? DIMENSION_AXIS_ORIENTATIONS.last |
446 : DIMENSION_AXIS_ORIENTATIONS.first; | 447 : DIMENSION_AXIS_ORIENTATIONS.first; |
447 for (int i = 0, len = displayedDimensionAxes.length; i < len; ++i) { | 448 for (int i = 0, len = displayedDimensionAxes.length; i < len; ++i) { |
448 var axis = _dimensionAxes[displayedDimensionAxes[i]], | 449 var axis = _dimensionAxes[displayedDimensionAxes[i]], |
449 orientation = _orientRTL(dimensionAxisOrientations[i]); | 450 orientation = _orientRTL(dimensionAxisOrientations[i]); |
450 axis.prepareToDraw(orientation); | 451 axis.prepareToDraw(orientation); |
(...skipping 23 matching lines...) Expand all Loading... |
474 // all invisible measure scales. | 475 // all invisible measure scales. |
475 if (_measureAxes.length != displayedMeasureAxes.length) { | 476 if (_measureAxes.length != displayedMeasureAxes.length) { |
476 _measureAxes.keys.forEach((String axisId) { | 477 _measureAxes.keys.forEach((String axisId) { |
477 if (displayedMeasureAxes.contains(axisId)) return; | 478 if (displayedMeasureAxes.contains(axisId)) return; |
478 _getMeasureAxis(axisId).initAxisScale([layout.renderArea.height, 0]); | 479 _getMeasureAxis(axisId).initAxisScale([layout.renderArea.height, 0]); |
479 }); | 480 }); |
480 } | 481 } |
481 | 482 |
482 // Draw the visible measure axes, if any. | 483 // Draw the visible measure axes, if any. |
483 if (displayedMeasureAxes.isNotEmpty) { | 484 if (displayedMeasureAxes.isNotEmpty) { |
484 var axisGroups = visualization. | 485 var axisGroups = visualization |
485 selectAll('.measure-axis-group').data(displayedMeasureAxes); | 486 .selectAll('.measure-axis-group') |
| 487 .data(displayedMeasureAxes); |
486 // Update measure axis (add/remove/update) | 488 // Update measure axis (add/remove/update) |
487 axisGroups.enter.append('svg:g'); | 489 axisGroups.enter.append('svg:g'); |
488 axisGroups.each((axisId, index, group) { | 490 axisGroups.each((axisId, index, group) { |
489 _getMeasureAxis(axisId).draw(group, _scope, preRender: preRender); | 491 _getMeasureAxis(axisId).draw(group, _scope, preRender: preRender); |
490 group.attributes['class'] = 'measure-axis-group measure-${index}'; | 492 group.attributes['class'] = 'measure-axis-group measure-${index}'; |
491 }); | 493 }); |
492 axisGroups.exit.remove(); | 494 axisGroups.exit.remove(); |
493 } | 495 } |
494 | 496 |
495 // Draw the dimension axes, unless asked not to. | 497 // Draw the dimension axes, unless asked not to. |
496 if (config.renderDimensionAxes != false) { | 498 if (config.renderDimensionAxes != false) { |
497 var dimAxisGroups = visualization. | 499 var dimAxisGroups = visualization |
498 selectAll('.dimension-axis-group').data(displayedDimensionAxes); | 500 .selectAll('.dimension-axis-group') |
| 501 .data(displayedDimensionAxes); |
499 // Update dimension axes (add/remove/update) | 502 // Update dimension axes (add/remove/update) |
500 dimAxisGroups.enter.append('svg:g'); | 503 dimAxisGroups.enter.append('svg:g'); |
501 dimAxisGroups.each((column, index, group) { | 504 dimAxisGroups.each((column, index, group) { |
502 _getDimensionAxis(column).draw(group, _scope, preRender: preRender); | 505 _getDimensionAxis(column).draw(group, _scope, preRender: preRender); |
503 group.attributes['class'] = 'dimension-axis-group dim-${index}'; | 506 group.attributes['class'] = 'dimension-axis-group dim-${index}'; |
504 }); | 507 }); |
505 dimAxisGroups.exit.remove(); | 508 dimAxisGroups.exit.remove(); |
506 } else { | 509 } else { |
507 // Initialize scale on invisible axis | 510 // Initialize scale on invisible axis |
508 var dimensionAxisOrientations = config.isLeftAxisPrimary ? | 511 var dimensionAxisOrientations = config.isLeftAxisPrimary |
509 DIMENSION_AXIS_ORIENTATIONS.last : DIMENSION_AXIS_ORIENTATIONS.first; | 512 ? DIMENSION_AXIS_ORIENTATIONS.last |
| 513 : DIMENSION_AXIS_ORIENTATIONS.first; |
510 for (int i = 0; i < dimensionAxesCount; ++i) { | 514 for (int i = 0; i < dimensionAxesCount; ++i) { |
511 var column = config.dimensions.elementAt(i), | 515 var column = config.dimensions.elementAt(i), |
512 axis = _dimensionAxes[column], | 516 axis = _dimensionAxes[column], |
513 orientation = dimensionAxisOrientations[i]; | 517 orientation = dimensionAxisOrientations[i]; |
514 axis.initAxisScale(orientation == ORIENTATION_LEFT ? | 518 axis.initAxisScale(orientation == ORIENTATION_LEFT |
515 [layout.renderArea.height, 0] : [0, layout.renderArea.width]); | 519 ? [layout.renderArea.height, 0] |
516 }; | 520 : [0, layout.renderArea.width]); |
| 521 } |
| 522 ; |
517 } | 523 } |
518 } | 524 } |
519 | 525 |
520 // Compute chart render area size and positions of all elements | 526 // Compute chart render area size and positions of all elements |
521 _computeLayout(bool notRenderingAxes) { | 527 _computeLayout(bool notRenderingAxes) { |
522 if (notRenderingAxes) { | 528 if (notRenderingAxes) { |
523 layout.renderArea = | 529 layout.renderArea = |
524 new Rect(0, 0, layout.chartArea.height, layout.chartArea.width); | 530 new Rect(0, 0, layout.chartArea.height, layout.chartArea.width); |
525 return; | 531 return; |
526 } | 532 } |
527 | 533 |
528 var top = layout.axes[ORIENTATION_TOP], | 534 var top = layout.axes[ORIENTATION_TOP], |
529 left = layout.axes[ORIENTATION_LEFT], | 535 left = layout.axes[ORIENTATION_LEFT], |
530 bottom = layout.axes[ORIENTATION_BOTTOM], | 536 bottom = layout.axes[ORIENTATION_BOTTOM], |
531 right = layout.axes[ORIENTATION_RIGHT]; | 537 right = layout.axes[ORIENTATION_RIGHT]; |
532 | 538 |
533 var renderAreaHeight = layout.chartArea.height - | 539 var renderAreaHeight = layout.chartArea.height - |
534 (top.height + layout.axes[ORIENTATION_BOTTOM].height), | 540 (top.height + layout.axes[ORIENTATION_BOTTOM].height), |
535 renderAreaWidth = layout.chartArea.width - | 541 renderAreaWidth = layout.chartArea.width - |
536 (left.width + layout.axes[ORIENTATION_RIGHT].width); | 542 (left.width + layout.axes[ORIENTATION_RIGHT].width); |
537 | 543 |
538 layout.renderArea = new Rect( | 544 layout.renderArea = |
539 left.width, top.height, renderAreaWidth, renderAreaHeight); | 545 new Rect(left.width, top.height, renderAreaWidth, renderAreaHeight); |
540 | 546 |
541 layout._axes | 547 layout._axes |
542 ..[ORIENTATION_TOP] = | 548 ..[ORIENTATION_TOP] = new Rect(left.width, 0, renderAreaWidth, top.height) |
543 new Rect(left.width, 0, renderAreaWidth, top.height) | 549 ..[ORIENTATION_RIGHT] = new Rect( |
544 ..[ORIENTATION_RIGHT] = | 550 left.width + renderAreaWidth, top.y, right.width, renderAreaHeight) |
545 new Rect(left.width + renderAreaWidth, top.y, | 551 ..[ORIENTATION_BOTTOM] = new Rect(left.width, |
546 right.width, renderAreaHeight) | 552 top.height + renderAreaHeight, renderAreaWidth, bottom.height) |
547 ..[ORIENTATION_BOTTOM] = | |
548 new Rect(left.width, top.height + renderAreaHeight, | |
549 renderAreaWidth, bottom.height) | |
550 ..[ORIENTATION_LEFT] = | 553 ..[ORIENTATION_LEFT] = |
551 new Rect( | 554 new Rect(left.width, top.height, left.width, renderAreaHeight); |
552 left.width, top.height, left.width, renderAreaHeight); | |
553 } | 555 } |
554 | 556 |
555 // Updates the legend, if configuration changed since the last | 557 // Updates the legend, if configuration changed since the last |
556 // time the legend was updated. | 558 // time the legend was updated. |
557 _updateLegend() { | 559 _updateLegend() { |
558 if (!_pendingLegendUpdate) return; | 560 if (!_pendingLegendUpdate) return; |
559 if (_config == null || _config.legend == null || _series.isEmpty) return; | 561 if (_config == null || _config.legend == null || _series.isEmpty) return; |
560 | 562 |
561 var legend = <ChartLegendItem>[]; | 563 var legend = <ChartLegendItem>[]; |
562 List seriesByColumn = | 564 List seriesByColumn = |
563 new List.generate(data.columns.length, (_) => new List()); | 565 new List.generate(data.columns.length, (_) => new List()); |
564 | 566 |
565 _series.forEach((s) => | 567 _series.forEach((s) => s.measures.forEach((m) => seriesByColumn[m].add(s))); |
566 s.measures.forEach((m) => seriesByColumn[m].add(s))); | |
567 | 568 |
568 seriesByColumn.asMap().forEach((int i, List s) { | 569 seriesByColumn.asMap().forEach((int i, List s) { |
569 if (s.length == 0) return; | 570 if (s.length == 0) return; |
570 legend.add(new ChartLegendItem( | 571 legend.add(new ChartLegendItem( |
571 index:i, label:data.columns.elementAt(i).label, series:s, | 572 index: i, |
572 color:theme.getColorForKey(i))); | 573 label: data.columns.elementAt(i).label, |
| 574 series: s, |
| 575 color: theme.getColorForKey(i))); |
573 }); | 576 }); |
574 | 577 |
575 _config.legend.update(legend, this); | 578 _config.legend.update(legend, this); |
576 _pendingLegendUpdate = false; | 579 _pendingLegendUpdate = false; |
577 } | 580 } |
578 | 581 |
579 @override | 582 @override |
580 Stream<ChartEvent> get onMouseUp => | 583 Stream<ChartEvent> get onMouseUp => |
581 host.onMouseUp | 584 host.onMouseUp.map((MouseEvent e) => new DefaultChartEventImpl(e, this)); |
582 .map((MouseEvent e) => new DefaultChartEventImpl(e, this)); | |
583 | 585 |
584 @override | 586 @override |
585 Stream<ChartEvent> get onMouseDown => | 587 Stream<ChartEvent> get onMouseDown => host.onMouseDown |
586 host.onMouseDown | 588 .map((MouseEvent e) => new DefaultChartEventImpl(e, this)); |
587 .map((MouseEvent e) => new DefaultChartEventImpl(e, this)); | |
588 | 589 |
589 @override | 590 @override |
590 Stream<ChartEvent> get onMouseOver => | 591 Stream<ChartEvent> get onMouseOver => host.onMouseOver |
591 host.onMouseOver | 592 .map((MouseEvent e) => new DefaultChartEventImpl(e, this)); |
592 .map((MouseEvent e) => new DefaultChartEventImpl(e, this)); | |
593 | 593 |
594 @override | 594 @override |
595 Stream<ChartEvent> get onMouseOut => | 595 Stream<ChartEvent> get onMouseOut => |
596 host.onMouseOut | 596 host.onMouseOut.map((MouseEvent e) => new DefaultChartEventImpl(e, this)); |
597 .map((MouseEvent e) => new DefaultChartEventImpl(e, this)); | |
598 | 597 |
599 @override | 598 @override |
600 Stream<ChartEvent> get onMouseMove => | 599 Stream<ChartEvent> get onMouseMove => host.onMouseMove |
601 host.onMouseMove | 600 .map((MouseEvent e) => new DefaultChartEventImpl(e, this)); |
602 .map((MouseEvent e) => new DefaultChartEventImpl(e, this)); | |
603 | 601 |
604 @override | 602 @override |
605 Stream<ChartEvent> get onValueClick { | 603 Stream<ChartEvent> get onValueClick { |
606 if (_valueMouseClickController == null) { | 604 if (_valueMouseClickController == null) { |
607 _valueMouseClickController = new StreamController.broadcast(sync: true); | 605 _valueMouseClickController = new StreamController.broadcast(sync: true); |
608 } | 606 } |
609 return _valueMouseClickController.stream; | 607 return _valueMouseClickController.stream; |
610 } | 608 } |
611 | 609 |
612 @override | 610 @override |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
647 if (behavior == null || !_behaviors.contains(behavior)) return; | 645 if (behavior == null || !_behaviors.contains(behavior)) return; |
648 if (upperBehaviorPane != null && lowerBehaviorPane != null) { | 646 if (upperBehaviorPane != null && lowerBehaviorPane != null) { |
649 behavior.dispose(); | 647 behavior.dispose(); |
650 } | 648 } |
651 _behaviors.remove(behavior); | 649 _behaviors.remove(behavior); |
652 } | 650 } |
653 } | 651 } |
654 | 652 |
655 class _ChartAreaLayout implements ChartAreaLayout { | 653 class _ChartAreaLayout implements ChartAreaLayout { |
656 final _axes = <String, Rect>{ | 654 final _axes = <String, Rect>{ |
657 ORIENTATION_LEFT: const Rect(), | 655 ORIENTATION_LEFT: const Rect(), |
658 ORIENTATION_RIGHT: const Rect(), | 656 ORIENTATION_RIGHT: const Rect(), |
659 ORIENTATION_TOP: const Rect(), | 657 ORIENTATION_TOP: const Rect(), |
660 ORIENTATION_BOTTOM: const Rect() | 658 ORIENTATION_BOTTOM: const Rect() |
661 }; | 659 }; |
662 | 660 |
663 UnmodifiableMapView<String, Rect> _axesView; | 661 UnmodifiableMapView<String, Rect> _axesView; |
664 | 662 |
665 @override | 663 @override |
666 get axes => _axesView; | 664 get axes => _axesView; |
667 | 665 |
668 @override | 666 @override |
669 Rect renderArea; | 667 Rect renderArea; |
670 | 668 |
671 @override | 669 @override |
(...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
706 if (_area._valueMouseOverController != null) { | 704 if (_area._valueMouseOverController != null) { |
707 _area._valueMouseOverController.add(e); | 705 _area._valueMouseOverController.add(e); |
708 } | 706 } |
709 } | 707 } |
710 | 708 |
711 _mouseOut(ChartEvent e) { | 709 _mouseOut(ChartEvent e) { |
712 var state = _area.state; | 710 var state = _area.state; |
713 if (state != null) { | 711 if (state != null) { |
714 var current = state.hovered; | 712 var current = state.hovered; |
715 if (current != null && | 713 if (current != null && |
716 current.first == e.column && current.last == e.row) { | 714 current.first == e.column && |
| 715 current.last == e.row) { |
717 state.hovered = null; | 716 state.hovered = null; |
718 } | 717 } |
719 } | 718 } |
720 if (_area._valueMouseOutController != null) { | 719 if (_area._valueMouseOutController != null) { |
721 _area._valueMouseOutController.add(e); | 720 _area._valueMouseOutController.add(e); |
722 } | 721 } |
723 } | 722 } |
724 | 723 |
725 check() { | 724 check() { |
726 if (_renderer != _series.renderer) { | 725 if (_renderer != _series.renderer) { |
727 dispose(); | 726 dispose(); |
728 if (_series.renderer is ChartRendererBehaviorSource){ | 727 if (_series.renderer is ChartRendererBehaviorSource) { |
729 _disposer.addAll([ | 728 _disposer.addAll([ |
730 _series.renderer.onValueClick.listen(_click), | 729 _series.renderer.onValueClick.listen(_click), |
731 _series.renderer.onValueMouseOver.listen(_mouseOver), | 730 _series.renderer.onValueMouseOver.listen(_mouseOver), |
732 _series.renderer.onValueMouseOut.listen(_mouseOut) | 731 _series.renderer.onValueMouseOut.listen(_mouseOut) |
733 ]); | 732 ]); |
734 } | 733 } |
735 } | 734 } |
736 _renderer = _series.renderer; | 735 _renderer = _series.renderer; |
737 } | 736 } |
738 | 737 |
739 dispose() => _disposer.dispose(); | 738 dispose() { |
| 739 _renderer?.dispose(); |
| 740 _disposer.dispose(); |
| 741 } |
740 } | 742 } |
OLD | NEW |