| 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 |