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

Side by Side Diff: charted/lib/charts/src/cartesian_area_impl.dart

Issue 1400473008: Roll Observatory packages and add a roll script (Closed) Base URL: git@github.com:dart-lang/observatory_pub_packages.git@master
Patch Set: Created 5 years, 2 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
(Empty)
1 //
2 // Copyright 2014 Google Inc. All rights reserved.
3 //
4 // Use of this source code is governed by a BSD-style
5 // license that can be found in the LICENSE file or at
6 // https://developers.google.com/open-source/licenses/bsd
7 //
8
9 part of charted.charts;
10
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
13 /// which contain two dimension axes.
14 class DefaultCartesianAreaImpl implements CartesianArea {
15 /// Default identifiers used by the measure axes
16 static const MEASURE_AXIS_IDS = const['_default'];
17
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
20 /// dimension.
21 static const MEASURE_AXIS_ORIENTATIONS = const[
22 const[ORIENTATION_LEFT, ORIENTATION_RIGHT],
23 const[ORIENTATION_BOTTOM, ORIENTATION_TOP]
24 ];
25
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
28 /// dimension.
29 static const DIMENSION_AXIS_ORIENTATIONS = const[
30 const[ORIENTATION_BOTTOM, ORIENTATION_LEFT],
31 const[ORIENTATION_LEFT, ORIENTATION_BOTTOM]
32 ];
33
34 /// Mapping of measure axis Id to it's axis.
35 final _measureAxes = new LinkedHashMap<String, DefaultChartAxisImpl>();
36
37 /// Mapping of dimension column index to it's axis.
38 final _dimensionAxes = new LinkedHashMap<int, DefaultChartAxisImpl>();
39
40 /// Disposer for all change stream subscriptions related to data.
41 final _dataEventsDisposer = new SubscriptionsDisposer();
42
43 /// Disposer for all change stream subscriptions related to config.
44 final _configEventsDisposer = new SubscriptionsDisposer();
45
46 @override
47 final Element host;
48
49 @override
50 final bool useTwoDimensionAxes;
51
52 @override
53 final bool useRowColoring;
54
55 /// Indicates whether any renderers need bands on primary dimension
56 final List<int> dimensionsUsingBands = [];
57
58 @override
59 final ChartState state;
60
61 @override
62 _ChartAreaLayout layout = new _ChartAreaLayout();
63
64 @override
65 Selection upperBehaviorPane;
66
67 @override
68 Selection lowerBehaviorPane;
69
70 @override
71 bool isReady = false;
72
73 @override
74 ChartTheme theme;
75
76 ChartData _data;
77 ChartConfig _config;
78 bool _autoUpdate = false;
79
80 SelectionScope _scope;
81 Selection _svg;
82 Selection visualization;
83
84 Iterable<ChartSeries> _series;
85
86 bool _pendingLegendUpdate = false;
87 List<ChartBehavior> _behaviors = new List<ChartBehavior>();
88 Map<ChartSeries, _ChartSeriesInfo> _seriesInfoCache = new Map();
89
90 StreamController<ChartEvent> _valueMouseOverController;
91 StreamController<ChartEvent> _valueMouseOutController;
92 StreamController<ChartEvent> _valueMouseClickController;
93 StreamController<ChartArea> _chartAxesUpdatedController;
94
95 DefaultCartesianAreaImpl(
96 this.host,
97 ChartData data,
98 ChartConfig config,
99 bool autoUpdate,
100 this.useTwoDimensionAxes,
101 this.useRowColoring,
102 this.state) : _autoUpdate = autoUpdate {
103 assert(host != null);
104 assert(isNotInline(host));
105
106 this.data = data;
107 this.config = config;
108 theme = new QuantumChartTheme();
109
110 Transition.defaultEasingType = theme.transitionEasingType;
111 Transition.defaultEasingMode = theme.transitionEasingMode;
112 Transition.defaultDurationMilliseconds =
113 theme.transitionDurationMilliseconds;
114 }
115
116 void dispose() {
117 _configEventsDisposer.dispose();
118 _dataEventsDisposer.dispose();
119 _config.legend.dispose();
120
121 if (_valueMouseOverController != null) {
122 _valueMouseOverController.close();
123 _valueMouseOverController = null;
124 }
125 if (_valueMouseOutController != null) {
126 _valueMouseOutController.close();
127 _valueMouseOutController = null;
128 }
129 if (_valueMouseClickController != null) {
130 _valueMouseClickController.close();
131 _valueMouseClickController = null;
132 }
133 if (_chartAxesUpdatedController != null) {
134 _chartAxesUpdatedController.close();
135 _chartAxesUpdatedController = null;
136 }
137 }
138
139 static bool isNotInline(Element e) =>
140 e != null && e.getComputedStyle().display != 'inline';
141
142 /// Set new data for this chart. If [value] is [Observable], subscribes to
143 /// changes and updates the chart when data changes.
144 @override
145 set data(ChartData value) {
146 _data = value;
147 _dataEventsDisposer.dispose();
148 _pendingLegendUpdate = true;
149
150 if (autoUpdate && _data != null && _data is Observable) {
151 _dataEventsDisposer.add((_data as Observable).changes.listen((_) {
152 _pendingLegendUpdate = true;
153 draw();
154 }));
155 }
156 }
157
158 @override
159 ChartData get data => _data;
160
161 /// Set new config for this chart. If [value] is [Observable], subscribes to
162 /// changes and updates the chart when series or dimensions change.
163 @override
164 set config(ChartConfig value) {
165 _config = value;
166 _configEventsDisposer.dispose();
167 _pendingLegendUpdate = true;
168
169 if (_config != null && _config is Observable) {
170 _configEventsDisposer.add((_config as Observable).changes.listen((_) {
171 _pendingLegendUpdate = true;
172 draw();
173 }));
174 }
175 }
176
177 @override
178 ChartConfig get config => _config;
179
180 @override
181 set autoUpdate(bool value) {
182 if (_autoUpdate != value) {
183 _autoUpdate = value;
184 this.data = _data;
185 this.config = _config;
186 }
187 }
188
189 @override
190 bool get autoUpdate => _autoUpdate;
191
192 /// Gets measure axis from cache - creates a new instance of _ChartAxis
193 /// if one was not already created for the given [axisId].
194 DefaultChartAxisImpl _getMeasureAxis(String axisId) {
195 _measureAxes.putIfAbsent(axisId, () {
196 var axisConf = config.getMeasureAxis(axisId),
197 axis = axisConf != null ?
198 new DefaultChartAxisImpl.withAxisConfig(this, axisConf) :
199 new DefaultChartAxisImpl(this);
200 return axis;
201 });
202 return _measureAxes[axisId];
203 }
204
205 /// 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 DefaultChartAxisImpl _getDimensionAxis(int column) {
208 _dimensionAxes.putIfAbsent(column, () {
209 var axisConf = config.getDimensionAxis(column),
210 axis = axisConf != null ?
211 new DefaultChartAxisImpl.withAxisConfig(this, axisConf) :
212 new DefaultChartAxisImpl(this);
213 return axis;
214 });
215 return _dimensionAxes[column];
216 }
217
218 /// All columns rendered by a series must be of the same type.
219 bool _isSeriesValid(ChartSeries s) {
220 var first = data.columns.elementAt(s.measures.first).type;
221 return s.measures.every((i) =>
222 (i < data.columns.length) && data.columns.elementAt(i).type == first);
223 }
224
225 @override
226 Iterable<Scale> get dimensionScales =>
227 config.dimensions.map((int column) => _getDimensionAxis(column).scale);
228
229 @override
230 Iterable<Scale> measureScales(ChartSeries series) {
231 var axisIds = isNullOrEmpty(series.measureAxisIds)
232 ? MEASURE_AXIS_IDS
233 : series.measureAxisIds;
234 return axisIds.map((String id) => _getMeasureAxis(id).scale);
235 }
236
237 /// Computes the size of chart and if changed from the previous time
238 /// size was computed, sets attributes on svg element
239 Rect _computeChartSize() {
240 int width = host.clientWidth,
241 height = host.clientHeight;
242
243 if (config.minimumSize != null) {
244 width = max([width, config.minimumSize.width]);
245 height = max([height, config.minimumSize.height]);
246 }
247
248 AbsoluteRect padding = theme.padding;
249 num paddingLeft = config.isRTL ? padding.end : padding.start;
250 Rect current = new Rect(paddingLeft, padding.top,
251 width - (padding.start + padding.end),
252 height - (padding.top + padding.bottom));
253 if (layout.chartArea == null || layout.chartArea != current) {
254 _svg.attr('width', width.toString());
255 _svg.attr('height', height.toString());
256 layout.chartArea = current;
257
258 var transform = 'translate(${paddingLeft},${padding.top})';
259 visualization.first.attributes['transform'] = transform;
260 lowerBehaviorPane.first.attributes['transform'] = transform;
261 upperBehaviorPane.first.attributes['transform'] = transform;
262 }
263 return layout.chartArea;
264 }
265
266 @override
267 draw({bool preRender:false, Future schedulePostRender}) {
268 assert(data != null && config != null);
269 assert(config.series != null && config.series.isNotEmpty);
270
271 // One time initialization.
272 // Each [ChartArea] has it's own [SelectionScope]
273 if (_scope == null) {
274 _scope = new SelectionScope.element(host);
275 _svg = _scope.append('svg:svg')..classed('chart-canvas');
276 if (!isNullOrEmpty(theme.filters)) {
277 var element = _svg.first,
278 defs = Namespace.createChildElement('defs', element)
279 ..append(new SvgElement.svg(
280 theme.filters, treeSanitizer: new NullTreeSanitizer()));
281 _svg.first.append(defs);
282 }
283
284 lowerBehaviorPane = _svg.append('g')..classed('lower-render-pane');
285 visualization = _svg.append('g')..classed('chart-render-pane');
286 upperBehaviorPane = _svg.append('g')..classed('upper-render-pane');
287
288 if (_behaviors.isNotEmpty) {
289 _behaviors.forEach(
290 (b) => b.init(this, upperBehaviorPane, lowerBehaviorPane));
291 }
292 }
293
294 // Compute chart sizes and filter out unsupported series
295 _computeChartSize();
296 var series = config.series.where((s) =>
297 _isSeriesValid(s) && s.renderer.prepare(this, s)),
298 selection = visualization.selectAll('.series-group').
299 data(series, (x) => x.hashCode),
300 axesDomainCompleter = new Completer();
301
302 // 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.
304 axesDomainCompleter.future.then((_) {
305 selection.enter.append('svg:g')..classed('series-group');
306 String transform =
307 'translate(${layout.renderArea.x},${layout.renderArea.y})';
308
309 selection.each((ChartSeries s, _, Element group) {
310 _ChartSeriesInfo info = _seriesInfoCache[s];
311 if (info == null) {
312 info = _seriesInfoCache[s] = new _ChartSeriesInfo(this, s);
313 }
314 info.check();
315 group.attributes['transform'] = transform;
316 (s.renderer as CartesianRenderer)
317 .draw(group, schedulePostRender:schedulePostRender);
318 });
319
320 // A series that was rendered earlier isn't there anymore, remove it
321 selection.exit
322 ..each((ChartSeries s, _, __) {
323 var info = _seriesInfoCache.remove(s);
324 if (info != null) {
325 info.dispose();
326 }
327 })
328 ..remove();
329
330 // Notify on the stream that the chart has been updated.
331 isReady = true;
332 if (_chartAxesUpdatedController != null) {
333 _chartAxesUpdatedController.add(this);
334 }
335 });
336
337 // Save the list of valid series and initialize axes.
338 _series = series;
339 _initAxes(preRender: preRender);
340
341 // Render the chart, now that the axes layer is already in DOM.
342 axesDomainCompleter.complete();
343
344 // Updates the legend if required.
345 _updateLegend();
346 }
347
348 String _orientRTL(String orientation) => orientation;
349 Scale _scaleRTL(Scale scale) => scale;
350
351 /// Initialize the axes - required even if the axes are not being displayed.
352 _initAxes({bool preRender: false}) {
353 Map measureAxisUsers = <String,Iterable<ChartSeries>>{};
354
355 // Create necessary measures axes.
356 // If measure axes were not configured on the series, default is used.
357 _series.forEach((ChartSeries s) {
358 var measureAxisIds = isNullOrEmpty(s.measureAxisIds)
359 ? MEASURE_AXIS_IDS
360 : s.measureAxisIds;
361 measureAxisIds.forEach((axisId) {
362 _getMeasureAxis(axisId); // Creates axis if required
363 var users = measureAxisUsers[axisId];
364 if (users == null) {
365 measureAxisUsers[axisId] = [s];
366 } else {
367 users.add(s);
368 }
369 });
370 });
371
372 // Now that we know a list of series using each measure axis, configure
373 // the input domain of each axis.
374 measureAxisUsers.forEach((id, listOfSeries) {
375 var sampleCol = listOfSeries.first.measures.first,
376 sampleColSpec = data.columns.elementAt(sampleCol),
377 axis = _getMeasureAxis(id),
378 domain;
379
380 if (sampleColSpec.useOrdinalScale) {
381 throw new UnsupportedError(
382 'Ordinal measure axes are not currently supported.');
383 } else {
384 // Extent is available because [ChartRenderer.prepare] was already
385 // called (when checking for valid series in [draw].
386 Iterable extents = listOfSeries.map((s) => s.renderer.extent).toList();
387 var lowest = min(extents.map((e) => e.min)),
388 highest = max(extents.map((e) => e.max));
389
390 // Use default domain if lowest and highest are the same, right now
391 // lowest is always 0 unless it is less than 0 - change to lowest when
392 // we make use of it.
393 domain = highest == lowest
394 ? (highest == 0
395 ? [0, 1]
396 : (highest < 0 ? [highest, 0] : [0, highest]))
397 : (lowest <= 0 ? [lowest, highest] : [0, highest]);
398 }
399 axis.initAxisDomain(sampleCol, false, domain);
400 });
401
402 // Configure dimension axes.
403 int dimensionAxesCount = useTwoDimensionAxes ? 2 : 1;
404 config.dimensions.take(dimensionAxesCount).forEach((int column) {
405 var axis = _getDimensionAxis(column),
406 sampleColumnSpec = data.columns.elementAt(column),
407 values = data.rows.map((row) => row.elementAt(column)),
408 domain;
409
410 if (sampleColumnSpec.useOrdinalScale) {
411 domain = values.map((e) => e.toString()).toList();
412 } else {
413 var extent = new Extent.items(values);
414 domain = [extent.min, extent.max];
415 }
416 axis.initAxisDomain(column, true, domain);
417 });
418
419 // See if any dimensions need "band" on the axis.
420 dimensionsUsingBands.clear();
421 List<bool> usingBands = [false, false];
422 _series.forEach((ChartSeries s) =>
423 (s.renderer as CartesianRenderer).dimensionsUsingBand.forEach((x) {
424 if (x <= 1 && !(usingBands[x])) {
425 usingBands[x] = true;
426 dimensionsUsingBands.add(config.dimensions.elementAt(x));
427 }
428 }));
429
430 // List of measure and dimension axes that are displayed
431 assert(
432 isNullOrEmpty(config.displayedMeasureAxes) ||
433 config.displayedMeasureAxes.length < 2);
434 var measureAxesCount = dimensionAxesCount == 1 ? 2 : 0,
435 displayedMeasureAxes = (isNullOrEmpty(config.displayedMeasureAxes)
436 ? _measureAxes.keys.take(measureAxesCount)
437 : config.displayedMeasureAxes.take(measureAxesCount)).
438 toList(growable: false),
439 displayedDimensionAxes =
440 config.dimensions.take(dimensionAxesCount).toList(growable: false);
441
442 // Compute size of the dimension axes
443 if (config.renderDimensionAxes != false) {
444 var dimensionAxisOrientations = config.isLeftAxisPrimary
445 ? DIMENSION_AXIS_ORIENTATIONS.last
446 : DIMENSION_AXIS_ORIENTATIONS.first;
447 for (int i = 0, len = displayedDimensionAxes.length; i < len; ++i) {
448 var axis = _dimensionAxes[displayedDimensionAxes[i]],
449 orientation = _orientRTL(dimensionAxisOrientations[i]);
450 axis.prepareToDraw(orientation);
451 layout._axes[orientation] = axis.size;
452 }
453 }
454
455 // Compute size of the measure axes
456 if (displayedMeasureAxes.isNotEmpty) {
457 var measureAxisOrientations = config.isLeftAxisPrimary
458 ? MEASURE_AXIS_ORIENTATIONS.last
459 : MEASURE_AXIS_ORIENTATIONS.first;
460 displayedMeasureAxes.asMap().forEach((int index, String key) {
461 var axis = _measureAxes[key],
462 orientation = _orientRTL(measureAxisOrientations[index]);
463 axis.prepareToDraw(orientation);
464 layout._axes[orientation] = axis.size;
465 });
466 }
467
468 // Consolidate all the information that we collected into final layout
469 _computeLayout(
470 displayedMeasureAxes.isEmpty && config.renderDimensionAxes == false);
471
472 // Domains for all axes have been taken care of and _ChartAxis ensures
473 // that the scale is initialized on visible axes. Initialize the scale on
474 // all invisible measure scales.
475 if (_measureAxes.length != displayedMeasureAxes.length) {
476 _measureAxes.keys.forEach((String axisId) {
477 if (displayedMeasureAxes.contains(axisId)) return;
478 _getMeasureAxis(axisId).initAxisScale([layout.renderArea.height, 0]);
479 });
480 }
481
482 // Draw the visible measure axes, if any.
483 if (displayedMeasureAxes.isNotEmpty) {
484 var axisGroups = visualization.
485 selectAll('.measure-axis-group').data(displayedMeasureAxes);
486 // Update measure axis (add/remove/update)
487 axisGroups.enter.append('svg:g');
488 axisGroups.each((axisId, index, group) {
489 _getMeasureAxis(axisId).draw(group, _scope, preRender: preRender);
490 group.attributes['class'] = 'measure-axis-group measure-${index}';
491 });
492 axisGroups.exit.remove();
493 }
494
495 // Draw the dimension axes, unless asked not to.
496 if (config.renderDimensionAxes != false) {
497 var dimAxisGroups = visualization.
498 selectAll('.dimension-axis-group').data(displayedDimensionAxes);
499 // Update dimension axes (add/remove/update)
500 dimAxisGroups.enter.append('svg:g');
501 dimAxisGroups.each((column, index, group) {
502 _getDimensionAxis(column).draw(group, _scope, preRender: preRender);
503 group.attributes['class'] = 'dimension-axis-group dim-${index}';
504 });
505 dimAxisGroups.exit.remove();
506 } else {
507 // Initialize scale on invisible axis
508 var dimensionAxisOrientations = config.isLeftAxisPrimary ?
509 DIMENSION_AXIS_ORIENTATIONS.last : DIMENSION_AXIS_ORIENTATIONS.first;
510 for (int i = 0; i < dimensionAxesCount; ++i) {
511 var column = config.dimensions.elementAt(i),
512 axis = _dimensionAxes[column],
513 orientation = dimensionAxisOrientations[i];
514 axis.initAxisScale(orientation == ORIENTATION_LEFT ?
515 [layout.renderArea.height, 0] : [0, layout.renderArea.width]);
516 };
517 }
518 }
519
520 // Compute chart render area size and positions of all elements
521 _computeLayout(bool notRenderingAxes) {
522 if (notRenderingAxes) {
523 layout.renderArea =
524 new Rect(0, 0, layout.chartArea.height, layout.chartArea.width);
525 return;
526 }
527
528 var top = layout.axes[ORIENTATION_TOP],
529 left = layout.axes[ORIENTATION_LEFT],
530 bottom = layout.axes[ORIENTATION_BOTTOM],
531 right = layout.axes[ORIENTATION_RIGHT];
532
533 var renderAreaHeight = layout.chartArea.height -
534 (top.height + layout.axes[ORIENTATION_BOTTOM].height),
535 renderAreaWidth = layout.chartArea.width -
536 (left.width + layout.axes[ORIENTATION_RIGHT].width);
537
538 layout.renderArea = new Rect(
539 left.width, top.height, renderAreaWidth, renderAreaHeight);
540
541 layout._axes
542 ..[ORIENTATION_TOP] =
543 new Rect(left.width, 0, renderAreaWidth, top.height)
544 ..[ORIENTATION_RIGHT] =
545 new Rect(left.width + renderAreaWidth, top.y,
546 right.width, renderAreaHeight)
547 ..[ORIENTATION_BOTTOM] =
548 new Rect(left.width, top.height + renderAreaHeight,
549 renderAreaWidth, bottom.height)
550 ..[ORIENTATION_LEFT] =
551 new Rect(
552 left.width, top.height, left.width, renderAreaHeight);
553 }
554
555 // Updates the legend, if configuration changed since the last
556 // time the legend was updated.
557 _updateLegend() {
558 if (!_pendingLegendUpdate) return;
559 if (_config == null || _config.legend == null || _series.isEmpty) return;
560
561 var legend = <ChartLegendItem>[];
562 List seriesByColumn =
563 new List.generate(data.columns.length, (_) => new List());
564
565 _series.forEach((s) =>
566 s.measures.forEach((m) => seriesByColumn[m].add(s)));
567
568 seriesByColumn.asMap().forEach((int i, List s) {
569 if (s.length == 0) return;
570 legend.add(new ChartLegendItem(
571 index:i, label:data.columns.elementAt(i).label, series:s,
572 color:theme.getColorForKey(i)));
573 });
574
575 _config.legend.update(legend, this);
576 _pendingLegendUpdate = false;
577 }
578
579 @override
580 Stream<ChartEvent> get onMouseUp =>
581 host.onMouseUp
582 .map((MouseEvent e) => new DefaultChartEventImpl(e, this));
583
584 @override
585 Stream<ChartEvent> get onMouseDown =>
586 host.onMouseDown
587 .map((MouseEvent e) => new DefaultChartEventImpl(e, this));
588
589 @override
590 Stream<ChartEvent> get onMouseOver =>
591 host.onMouseOver
592 .map((MouseEvent e) => new DefaultChartEventImpl(e, this));
593
594 @override
595 Stream<ChartEvent> get onMouseOut =>
596 host.onMouseOut
597 .map((MouseEvent e) => new DefaultChartEventImpl(e, this));
598
599 @override
600 Stream<ChartEvent> get onMouseMove =>
601 host.onMouseMove
602 .map((MouseEvent e) => new DefaultChartEventImpl(e, this));
603
604 @override
605 Stream<ChartEvent> get onValueClick {
606 if (_valueMouseClickController == null) {
607 _valueMouseClickController = new StreamController.broadcast(sync: true);
608 }
609 return _valueMouseClickController.stream;
610 }
611
612 @override
613 Stream<ChartEvent> get onValueMouseOver {
614 if (_valueMouseOverController == null) {
615 _valueMouseOverController = new StreamController.broadcast(sync: true);
616 }
617 return _valueMouseOverController.stream;
618 }
619
620 @override
621 Stream<ChartEvent> get onValueMouseOut {
622 if (_valueMouseOutController == null) {
623 _valueMouseOutController = new StreamController.broadcast(sync: true);
624 }
625 return _valueMouseOutController.stream;
626 }
627
628 @override
629 Stream<ChartArea> get onChartAxesUpdated {
630 if (_chartAxesUpdatedController == null) {
631 _chartAxesUpdatedController = new StreamController.broadcast(sync: true);
632 }
633 return _chartAxesUpdatedController.stream;
634 }
635
636 @override
637 void addChartBehavior(ChartBehavior behavior) {
638 if (behavior == null || _behaviors.contains(behavior)) return;
639 _behaviors.add(behavior);
640 if (upperBehaviorPane != null && lowerBehaviorPane != null) {
641 behavior.init(this, upperBehaviorPane, lowerBehaviorPane);
642 }
643 }
644
645 @override
646 void removeChartBehavior(ChartBehavior behavior) {
647 if (behavior == null || !_behaviors.contains(behavior)) return;
648 if (upperBehaviorPane != null && lowerBehaviorPane != null) {
649 behavior.dispose();
650 }
651 _behaviors.remove(behavior);
652 }
653 }
654
655 class _ChartAreaLayout implements ChartAreaLayout {
656 final _axes = <String, Rect>{
657 ORIENTATION_LEFT: const Rect(),
658 ORIENTATION_RIGHT: const Rect(),
659 ORIENTATION_TOP: const Rect(),
660 ORIENTATION_BOTTOM: const Rect()
661 };
662
663 UnmodifiableMapView<String, Rect> _axesView;
664
665 @override
666 get axes => _axesView;
667
668 @override
669 Rect renderArea;
670
671 @override
672 Rect chartArea;
673
674 _ChartAreaLayout() {
675 _axesView = new UnmodifiableMapView(_axes);
676 }
677 }
678
679 class _ChartSeriesInfo {
680 CartesianRenderer _renderer;
681 SubscriptionsDisposer _disposer = new SubscriptionsDisposer();
682
683 DefaultChartSeriesImpl _series;
684 DefaultCartesianAreaImpl _area;
685 _ChartSeriesInfo(this._area, this._series);
686
687 _click(ChartEvent e) {
688 var state = _area.state;
689 if (state != null) {
690 if (state.isHighlighted(e.column, e.row)) {
691 state.unhighlight(e.column, e.row);
692 } else {
693 state.highlight(e.column, e.row);
694 }
695 }
696 if (_area._valueMouseClickController != null) {
697 _area._valueMouseClickController.add(e);
698 }
699 }
700
701 _mouseOver(ChartEvent e) {
702 var state = _area.state;
703 if (state != null) {
704 state.hovered = new Pair(e.column, e.row);
705 }
706 if (_area._valueMouseOverController != null) {
707 _area._valueMouseOverController.add(e);
708 }
709 }
710
711 _mouseOut(ChartEvent e) {
712 var state = _area.state;
713 if (state != null) {
714 var current = state.hovered;
715 if (current != null &&
716 current.first == e.column && current.last == e.row) {
717 state.hovered = null;
718 }
719 }
720 if (_area._valueMouseOutController != null) {
721 _area._valueMouseOutController.add(e);
722 }
723 }
724
725 check() {
726 if (_renderer != _series.renderer) {
727 dispose();
728 if (_series.renderer is ChartRendererBehaviorSource){
729 _disposer.addAll([
730 _series.renderer.onValueClick.listen(_click),
731 _series.renderer.onValueMouseOver.listen(_mouseOver),
732 _series.renderer.onValueMouseOut.listen(_mouseOut)
733 ]);
734 }
735 }
736 _renderer = _series.renderer;
737 }
738
739 dispose() => _disposer.dispose();
740 }
OLDNEW
« no previous file with comments | « charted/lib/charts/layout_renderers/pie_chart_renderer.dart ('k') | charted/lib/charts/src/chart_axis_impl.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698