| OLD | NEW |
| (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 /// Creates an empty area and provides generic API for interaction with layout | |
| 12 /// based charts. | |
| 13 class DefaultLayoutAreaImpl implements LayoutArea { | |
| 14 /// Disposer for all change stream subscriptions related to data. | |
| 15 final _dataEventsDisposer = new SubscriptionsDisposer(); | |
| 16 | |
| 17 /// Disposer for all change stream subscriptions related to config. | |
| 18 final _configEventsDisposer = new SubscriptionsDisposer(); | |
| 19 | |
| 20 @override | |
| 21 final Element host; | |
| 22 | |
| 23 @override | |
| 24 final bool useRowColoring = true; | |
| 25 | |
| 26 @override | |
| 27 final ChartState state; | |
| 28 | |
| 29 @override | |
| 30 _ChartAreaLayout layout = new _ChartAreaLayout(); | |
| 31 | |
| 32 @override | |
| 33 Selection upperBehaviorPane; | |
| 34 | |
| 35 @override | |
| 36 Selection lowerBehaviorPane; | |
| 37 | |
| 38 @override | |
| 39 bool isReady = false; | |
| 40 | |
| 41 @override | |
| 42 ChartTheme theme; | |
| 43 | |
| 44 ChartData _data; | |
| 45 ChartConfig _config; | |
| 46 bool _autoUpdate = false; | |
| 47 | |
| 48 SelectionScope _scope; | |
| 49 Selection _svg; | |
| 50 Selection visualization; | |
| 51 | |
| 52 ChartSeries _series; | |
| 53 LayoutRenderer _renderer; | |
| 54 | |
| 55 bool _pendingLegendUpdate = false; | |
| 56 List<ChartBehavior> _behaviors = new List<ChartBehavior>(); | |
| 57 | |
| 58 SubscriptionsDisposer _rendererDisposer = new SubscriptionsDisposer(); | |
| 59 StreamController<ChartEvent> _valueMouseOverController; | |
| 60 StreamController<ChartEvent> _valueMouseOutController; | |
| 61 StreamController<ChartEvent> _valueMouseClickController; | |
| 62 | |
| 63 DefaultLayoutAreaImpl( | |
| 64 this.host, | |
| 65 ChartData data, | |
| 66 ChartConfig config, | |
| 67 this._autoUpdate, | |
| 68 this.state) { | |
| 69 assert(host != null); | |
| 70 assert(isNotInline(host)); | |
| 71 | |
| 72 this.data = data; | |
| 73 this.config = config; | |
| 74 theme = new QuantumChartTheme(); | |
| 75 | |
| 76 Transition.defaultEasingType = theme.transitionEasingType; | |
| 77 Transition.defaultEasingMode = theme.transitionEasingMode; | |
| 78 Transition.defaultDurationMilliseconds = | |
| 79 theme.transitionDurationMilliseconds; | |
| 80 } | |
| 81 | |
| 82 void dispose() { | |
| 83 _configEventsDisposer.dispose(); | |
| 84 _dataEventsDisposer.dispose(); | |
| 85 _config.legend.dispose(); | |
| 86 } | |
| 87 | |
| 88 static bool isNotInline(Element e) => | |
| 89 e != null && e.getComputedStyle().display != 'inline'; | |
| 90 | |
| 91 /// Set new data for this chart. If [value] is [Observable], subscribes to | |
| 92 /// changes and updates the chart when data changes. | |
| 93 @override | |
| 94 set data(ChartData value) { | |
| 95 _data = value; | |
| 96 _dataEventsDisposer.dispose(); | |
| 97 | |
| 98 if (autoUpdate && _data != null && _data is Observable) { | |
| 99 _dataEventsDisposer.add((_data as Observable).changes.listen((_) { | |
| 100 draw(); | |
| 101 })); | |
| 102 } | |
| 103 } | |
| 104 | |
| 105 @override | |
| 106 ChartData get data => _data; | |
| 107 | |
| 108 /// Set new config for this chart. If [value] is [Observable], subscribes to | |
| 109 /// changes and updates the chart when series or dimensions change. | |
| 110 @override | |
| 111 set config(ChartConfig value) { | |
| 112 _config = value; | |
| 113 _configEventsDisposer.dispose(); | |
| 114 _pendingLegendUpdate = true; | |
| 115 | |
| 116 if (_config != null && _config is Observable) { | |
| 117 _configEventsDisposer.add((_config as Observable).changes.listen((_) { | |
| 118 _pendingLegendUpdate = true; | |
| 119 draw(); | |
| 120 })); | |
| 121 } | |
| 122 } | |
| 123 | |
| 124 @override | |
| 125 ChartConfig get config => _config; | |
| 126 | |
| 127 @override | |
| 128 set autoUpdate(bool value) { | |
| 129 if (_autoUpdate != value) { | |
| 130 _autoUpdate = value; | |
| 131 this.data = _data; | |
| 132 this.config = _config; | |
| 133 } | |
| 134 } | |
| 135 | |
| 136 @override | |
| 137 bool get autoUpdate => _autoUpdate; | |
| 138 | |
| 139 /// Computes the size of chart and if changed from the previous time | |
| 140 /// size was computed, sets attributes on svg element | |
| 141 Rect _computeChartSize() { | |
| 142 int width = host.clientWidth, | |
| 143 height = host.clientHeight; | |
| 144 | |
| 145 if (config.minimumSize != null) { | |
| 146 width = max([width, config.minimumSize.width]); | |
| 147 height = max([height, config.minimumSize.height]); | |
| 148 } | |
| 149 | |
| 150 AbsoluteRect padding = theme.padding; | |
| 151 num paddingLeft = config.isRTL ? padding.end : padding.start; | |
| 152 Rect current = new Rect(paddingLeft, padding.top, | |
| 153 width - (padding.start + padding.end), | |
| 154 height - (padding.top + padding.bottom)); | |
| 155 if (layout.chartArea == null || layout.chartArea != current) { | |
| 156 var transform = 'translate(${paddingLeft},${padding.top})'; | |
| 157 | |
| 158 visualization.first.attributes['transform'] = transform; | |
| 159 lowerBehaviorPane.first.attributes['transform'] = transform; | |
| 160 upperBehaviorPane.first.attributes['transform'] = transform; | |
| 161 | |
| 162 _svg.attr('width', width.toString()); | |
| 163 _svg.attr('height', height.toString()); | |
| 164 layout.chartArea = current; | |
| 165 layout.renderArea = current; | |
| 166 layout._axes.clear(); | |
| 167 } | |
| 168 | |
| 169 return layout.chartArea; | |
| 170 } | |
| 171 | |
| 172 @override | |
| 173 draw({bool preRender:false, Future schedulePostRender}) { | |
| 174 assert(data != null && config != null); | |
| 175 assert(config.series != null && config.series.isNotEmpty); | |
| 176 | |
| 177 // One time initialization. | |
| 178 // Each [ChartArea] has it's own [SelectionScope] | |
| 179 if (_scope == null) { | |
| 180 _scope = new SelectionScope.element(host); | |
| 181 _svg = _scope.append('svg:svg')..classed('chart-canvas'); | |
| 182 | |
| 183 lowerBehaviorPane = _svg.append('g')..classed('lower-render-pane'); | |
| 184 visualization = _svg.append('g')..classed('chart-render-pane'); | |
| 185 upperBehaviorPane = _svg.append('g')..classed('upper-render-pane'); | |
| 186 | |
| 187 if (_behaviors.isNotEmpty) { | |
| 188 _behaviors.forEach( | |
| 189 (b) => b.init(this, upperBehaviorPane, lowerBehaviorPane)); | |
| 190 } | |
| 191 } | |
| 192 | |
| 193 // Compute chart sizes and filter out unsupported series | |
| 194 _computeChartSize(); | |
| 195 var series = config.series.firstWhere( | |
| 196 (s) => s.renderer.prepare(this, s), orElse: () => null), | |
| 197 group = visualization.first.querySelector('.series-group'); | |
| 198 | |
| 199 // We need atleast one matching series. | |
| 200 assert(series != null); | |
| 201 | |
| 202 // Create a group for rendering, if it was not already done. | |
| 203 if (group == null) { | |
| 204 group = Namespace.createChildElement('g', visualization.first) | |
| 205 ..classes.add('series-group'); | |
| 206 visualization.first.append(group); | |
| 207 } | |
| 208 | |
| 209 // If we previously displayed a series, verify that we are | |
| 210 // still using the same renderer. Otherwise, dispose the older one. | |
| 211 if (_renderer != null && series.renderer != _renderer) { | |
| 212 _rendererDisposer.dispose(); | |
| 213 } | |
| 214 | |
| 215 // Save and subscribe to events on the the current renderer. | |
| 216 _renderer = series.renderer; | |
| 217 if (_renderer is ChartRendererBehaviorSource) { | |
| 218 _rendererDisposer.addAll([ | |
| 219 _renderer.onValueClick.listen((ChartEvent e) { | |
| 220 if (state != null) { | |
| 221 if (state.isSelected(e.row)) { | |
| 222 state.unselect(e.row); | |
| 223 } else { | |
| 224 state.select(e.row); | |
| 225 } | |
| 226 } | |
| 227 if (_valueMouseClickController != null) { | |
| 228 _valueMouseClickController.add(e); | |
| 229 } | |
| 230 }), | |
| 231 _renderer.onValueMouseOver.listen((ChartEvent e) { | |
| 232 if (state != null) { | |
| 233 state.preview = e.row; | |
| 234 } | |
| 235 if (_valueMouseOverController != null) { | |
| 236 _valueMouseOverController.add(e); | |
| 237 } | |
| 238 }), | |
| 239 _renderer.onValueMouseOut.listen((ChartEvent e) { | |
| 240 if (state != null) { | |
| 241 if (e.row == state.preview) { | |
| 242 state.hovered = null; | |
| 243 } | |
| 244 } | |
| 245 if (_valueMouseOutController != null) { | |
| 246 _valueMouseOutController.add(e); | |
| 247 } | |
| 248 }) | |
| 249 ]); | |
| 250 } | |
| 251 | |
| 252 Iterable<ChartLegendItem> legend = | |
| 253 _renderer.layout(group, schedulePostRender:schedulePostRender); | |
| 254 | |
| 255 // Notify on the stream that the chart has been updated. | |
| 256 isReady = true; | |
| 257 | |
| 258 // Save the list of valid series and initialize axes. | |
| 259 _series = series; | |
| 260 | |
| 261 // Updates the legend if required. | |
| 262 _config.legend.update(legend, this); | |
| 263 } | |
| 264 | |
| 265 @override | |
| 266 Stream<ChartEvent> get onMouseUp => | |
| 267 host.onMouseUp | |
| 268 .map((MouseEvent e) => new DefaultChartEventImpl(e, this)); | |
| 269 | |
| 270 @override | |
| 271 Stream<ChartEvent> get onMouseDown => | |
| 272 host.onMouseDown | |
| 273 .map((MouseEvent e) => new DefaultChartEventImpl(e, this)); | |
| 274 | |
| 275 @override | |
| 276 Stream<ChartEvent> get onMouseOver => | |
| 277 host.onMouseOver | |
| 278 .map((MouseEvent e) => new DefaultChartEventImpl(e, this)); | |
| 279 | |
| 280 @override | |
| 281 Stream<ChartEvent> get onMouseOut => | |
| 282 host.onMouseOut | |
| 283 .map((MouseEvent e) => new DefaultChartEventImpl(e, this)); | |
| 284 | |
| 285 @override | |
| 286 Stream<ChartEvent> get onMouseMove => | |
| 287 host.onMouseMove | |
| 288 .map((MouseEvent e) => new DefaultChartEventImpl(e, this)); | |
| 289 | |
| 290 @override | |
| 291 Stream<ChartEvent> get onValueClick { | |
| 292 if (_valueMouseClickController == null) { | |
| 293 _valueMouseClickController = new StreamController.broadcast(sync: true); | |
| 294 } | |
| 295 return _valueMouseClickController.stream; | |
| 296 } | |
| 297 | |
| 298 @override | |
| 299 Stream<ChartEvent> get onValueMouseOver { | |
| 300 if (_valueMouseOverController == null) { | |
| 301 _valueMouseOverController = new StreamController.broadcast(sync: true); | |
| 302 } | |
| 303 return _valueMouseOverController.stream; | |
| 304 } | |
| 305 | |
| 306 @override | |
| 307 Stream<ChartEvent> get onValueMouseOut { | |
| 308 if (_valueMouseOutController == null) { | |
| 309 _valueMouseOutController = new StreamController.broadcast(sync: true); | |
| 310 } | |
| 311 return _valueMouseOutController.stream; | |
| 312 } | |
| 313 | |
| 314 @override | |
| 315 void addChartBehavior(ChartBehavior behavior) { | |
| 316 if (behavior == null || _behaviors.contains(behavior)) return; | |
| 317 _behaviors.add(behavior); | |
| 318 if (upperBehaviorPane != null && lowerBehaviorPane != null) { | |
| 319 behavior.init(this, upperBehaviorPane, lowerBehaviorPane); | |
| 320 } | |
| 321 } | |
| 322 | |
| 323 @override | |
| 324 void removeChartBehavior(ChartBehavior behavior) { | |
| 325 if (behavior == null || !_behaviors.contains(behavior)) return; | |
| 326 if (upperBehaviorPane != null && lowerBehaviorPane != null) { | |
| 327 behavior.dispose(); | |
| 328 } | |
| 329 _behaviors.remove(behavior); | |
| 330 } | |
| 331 } | |
| OLD | NEW |