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 |