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

Side by Side Diff: charted/lib/selection/src/selection_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 part of charted.selection;
9
10 /**
11 * Implementation of [Selection].
12 * Selections cannot be created directly - they are only created using
13 * the select or selectAll methods on [SelectionScope] and [Selection].
14 */
15 class _SelectionImpl implements Selection {
16
17 Iterable<SelectionGroup> groups;
18 SelectionScope scope;
19
20 /**
21 * Creates a new selection.
22 *
23 * When [source] is not specified, the new selection would have exactly
24 * one group with [SelectionScope.root] as it's parent. Otherwise, one group
25 * per for each non-null element is created with element as it's parent.
26 *
27 * When [selector] is specified, each group contains all elements matching
28 * [selector] and under the group's parent element. Otherwise, [fn] is
29 * called once per group with parent element's "data", "index" and the
30 * "element" itself passed as parameters. [fn] must return an iterable of
31 * elements to be used in each group.
32 */
33 _SelectionImpl.all({String selector, SelectionCallback<Iterable<Element>> fn,
34 SelectionScope this.scope, Selection source}) {
35 assert(selector != null || fn != null);
36 assert(source != null || scope != null);
37
38 if (selector != null) {
39 fn = (d, i, c) => c == null ?
40 scope.root.querySelectorAll(selector) :
41 c.querySelectorAll(selector);
42 }
43
44 var tmpGroups = new List<SelectionGroup>();
45 if (source != null) {
46 scope = source.scope;
47 for (int gi = 0; gi < source.groups.length; ++gi) {
48 final g = source.groups.elementAt(gi);
49 for (int ei = 0; ei < g.elements.length; ++ei) {
50 final e = g.elements.elementAt(ei);
51 if (e != null) {
52 tmpGroups.add(
53 new _SelectionGroupImpl(
54 fn(scope.datum(e), gi, e), parent: e));
55 }
56 }
57 }
58 } else {
59 tmpGroups.add(
60 new _SelectionGroupImpl(fn(null, 0, null), parent: scope.root));
61 }
62 groups = tmpGroups;
63 }
64
65 /**
66 * Same as [all] but only uses the first element matching [selector] when
67 * [selector] is specified. Otherwise, call [fn] which must return the
68 * element to be selected.
69 */
70 _SelectionImpl.single({String selector, SelectionCallback<Element> fn,
71 SelectionScope this.scope, Selection source}) {
72 assert(selector != null || fn != null);
73 assert(source != null || scope != null);
74
75 if (selector != null) {
76 fn = (d, i, c) => c == null ?
77 scope.root.querySelector(selector) :
78 c.querySelector(selector);
79 }
80
81 if (source != null) {
82 scope = source.scope;
83 groups = new List<SelectionGroup>.generate(source.groups.length, (gi) {
84 SelectionGroup g = source.groups.elementAt(gi);
85 return new _SelectionGroupImpl(
86 new List.generate(g.elements.length, (ei) {
87 var e = g.elements.elementAt(ei);
88 if (e != null) {
89 var datum = scope.datum(e);
90 var enterElement = fn(datum, ei, e);
91 if (datum != null) {
92 scope.associate(enterElement, datum);
93 }
94 return enterElement;
95 } else {
96 return null;
97 }
98 }), parent: g.parent);
99 });
100 } else {
101 groups = new List<SelectionGroup>.generate(1,
102 (_) => new _SelectionGroupImpl(new List.generate(1,
103 (_) => fn(null, 0, null), growable: false)), growable: false);
104 }
105 }
106
107 /** Creates a selection using the pre-computed list of [SelectionGroup] */
108 _SelectionImpl.selectionGroups(
109 Iterable<SelectionGroup> this.groups, SelectionScope this.scope);
110
111 /**
112 * Creates a selection using the list of elements. All elements will
113 * be part of the same group, with [SelectionScope.root] as the group's parent
114 */
115 _SelectionImpl.elements(Iterable elements, SelectionScope this.scope) {
116 groups = new List<SelectionGroup>()
117 ..add(new _SelectionGroupImpl(elements));
118 }
119
120 /**
121 * Utility to evaluate value of parameters (uses value when given
122 * or invokes a callback to get the value) and calls [action] for
123 * each non-null element in this selection
124 */
125 void _do(SelectionCallback f, Function action) {
126 each((d, i, e) => action(e, f == null ? null : f(scope.datum(e), i, e)));
127 }
128
129 /** Calls a function on each non-null element in the selection */
130 void each(SelectionCallback fn) {
131 if (fn == null) return;
132 for (int gi = 0, gLen = groups.length; gi < gLen; ++gi) {
133 final g = groups.elementAt(gi);
134 for (int ei = 0, eLen = g.elements.length; ei < eLen; ++ei) {
135 final e = g.elements.elementAt(ei);
136 if (e != null) fn(scope.datum(e), ei, e);
137 }
138 }
139 }
140
141 void on(String type, [SelectionCallback listener, bool capture]) {
142 Function getEventHandler(i, e) => (Event event) {
143 var previous = scope.event;
144 scope.event = event;
145 try {
146 listener(scope.datum(e), i, e);
147 } finally {
148 scope.event = previous;
149 }
150 };
151
152 if (!type.startsWith('.')) {
153 if (listener != null) {
154 // Add a listener to each element.
155 each((d, i, Element e){
156 var handlers = scope._listeners[e];
157 if (handlers == null) scope._listeners[e] = handlers = {};
158 handlers[type] = new Pair(getEventHandler(i, e), capture);
159 e.addEventListener(type, handlers[type].first, capture);
160 });
161 } else {
162 // Remove the listener from each element.
163 each((d, i, Element e) {
164 var handlers = scope._listeners[e];
165 if (handlers != null && handlers[type] != null) {
166 e.removeEventListener(
167 type, handlers[type].first, handlers[type].last);
168 }
169 });
170 }
171 } else {
172 // Remove all listeners on the event type (ignoring the namespace)
173 each((d, i, Element e) {
174 var handlers = scope._listeners[e],
175 t = type.substring(1);
176 handlers.forEach((String s, Pair<Function, bool> value) {
177 if (s.split('.')[0] == t) {
178 e.removeEventListener(s, value.first, value.last);
179 }
180 });
181 });
182 }
183 }
184
185 int get length {
186 int retval = 0;
187 each((d, i, e) => retval++);
188 return retval;
189 }
190
191 bool get isEmpty => length == 0;
192
193 /** First non-null element in this selection */
194 Element get first {
195 for (int gi = 0; gi < groups.length; gi++) {
196 SelectionGroup g = groups.elementAt(gi);
197 for (int ei = 0; ei < g.elements.length; ei++) {
198 if (g.elements.elementAt(ei) != null) {
199 return g.elements.elementAt(ei);
200 }
201 }
202 }
203 return null;
204 }
205
206 void attr(String name, val) {
207 assert(name != null && name.isNotEmpty);
208 attrWithCallback(name, toCallback(val));
209 }
210
211 void attrWithCallback(String name, SelectionCallback fn) {
212 assert(fn != null);
213 _do(fn, (e, v) => v == null ?
214 e.attributes.remove(name) : e.attributes[name] = "$v");
215 }
216
217 void classed(String name, [bool val = true]) {
218 assert(name != null && name.isNotEmpty);
219 classedWithCallback(name, toCallback(val));
220 }
221
222 void classedWithCallback(String name, SelectionCallback<bool> fn) {
223 assert(fn != null);
224 _do(fn, (e, v) =>
225 v == false ? e.classes.remove(name) : e.classes.add(name));
226 }
227
228 void style(String property, val, {String priority}) {
229 assert(property != null && property.isNotEmpty);
230 styleWithCallback(property,
231 toCallback(val as String), priority: priority);
232 }
233
234 void styleWithCallback(String property,
235 SelectionCallback<String> fn, {String priority}) {
236 assert(fn != null);
237 _do(fn, (Element e, String v) =>
238 v == null || v.isEmpty ?
239 e.style.removeProperty(property) :
240 e.style.setProperty(property, v, priority));
241 }
242
243 void text(String val) => textWithCallback(toCallback(val));
244
245 void textWithCallback(SelectionCallback<String> fn) {
246 assert(fn != null);
247 _do(fn, (e, v) => e.text = v == null ? '' : v);
248 }
249
250 void html(String val) => htmlWithCallback(toCallback(val));
251
252 void htmlWithCallback(SelectionCallback<String> fn) {
253 assert(fn != null);
254 _do(fn, (e, v) => e.innerHtml = v == null ? '' : v);
255 }
256
257 void remove() => _do(null, (e, _) => e.remove());
258
259 Selection select(String selector) {
260 assert(selector != null && selector.isNotEmpty);
261 return new _SelectionImpl.single(selector: selector, source: this);
262 }
263
264 Selection selectWithCallback(SelectionCallback<Element> fn) {
265 assert(fn != null);
266 return new _SelectionImpl.single(fn: fn, source:this);
267 }
268
269 Selection append(String tag) {
270 assert(tag != null && tag.isNotEmpty);
271 return appendWithCallback(
272 (d, ei, e) => Namespace.createChildElement(tag, e));
273 }
274
275 Selection appendWithCallback(SelectionCallback<Element> fn) {
276 assert(fn != null);
277 return new _SelectionImpl.single(fn: (datum, ei, e) {
278 Element child = fn(datum, ei, e);
279 return child == null ? null : e.append(child);
280 }, source: this);
281 }
282
283 Selection insert(String tag,
284 {String before, SelectionCallback<Element> beforeFn}) {
285 assert(tag != null && tag.isNotEmpty);
286 return insertWithCallback(
287 (d, ei, e) => Namespace.createChildElement(tag, e),
288 before: before, beforeFn: beforeFn);
289 }
290
291 Selection insertWithCallback(SelectionCallback<Element> fn,
292 {String before, SelectionCallback<Element> beforeFn}) {
293 assert(fn != null);
294 beforeFn =
295 before == null ? beforeFn : (d, ei, e) => e.querySelector(before);
296 return new _SelectionImpl.single(
297 fn: (datum, ei, e) {
298 Element child = fn(datum, ei, e);
299 Element before = beforeFn(datum, ei, e);
300 return child == null ? null : e.insertBefore(child, before);
301 },
302 source: this);
303 }
304
305 Selection selectAll(String selector) {
306 assert(selector != null && selector.isNotEmpty);
307 return new _SelectionImpl.all(selector: selector, source: this);
308 }
309
310 Selection selectAllWithCallback(SelectionCallback<Iterable<Element>> fn) {
311 assert(fn != null);
312 return new _SelectionImpl.all(fn: fn, source:this);
313 }
314
315 DataSelection data(Iterable vals, [SelectionKeyFunction keyFn]) {
316 assert(vals != null);
317 return dataWithCallback(toCallback(vals), keyFn);
318 }
319
320 DataSelection dataWithCallback(
321 SelectionCallback<Iterable> fn, [SelectionKeyFunction keyFn]) {
322 assert(fn != null);
323
324 var enterGroups = [],
325 updateGroups = [],
326 exitGroups = [];
327
328 // Create a dummy node to be used with enter() selection.
329 Object dummy(val) {
330 var element = new Object();
331 scope.associate(element, val);
332 return element;
333 };
334
335 // Joins data to all elements in the group.
336 void join(SelectionGroup g, Iterable vals) {
337 final int valuesLength = vals.length;
338 final int elementsLength = g.elements.length;
339
340 // Nodes exiting, entering and updating in this group.
341 // We maintain the nodes at the same index as they currently
342 // are (for exiting) or where they should be (for entering and updating)
343 var update = new List(valuesLength),
344 enter = new List(valuesLength),
345 exit = new List(elementsLength);
346
347 // Use key function to determine DOMElement to data associations.
348 if (keyFn != null) {
349 var keysOnDOM = [],
350 elementsByKey = {},
351 valuesByKey = {};
352
353 // Create a key to DOM element map.
354 // Used later to see if an element already exists for a key.
355 for (int ei = 0, len = elementsLength; ei < len; ++ei) {
356 final e = g.elements.elementAt(ei);
357 var keyValue = keyFn(scope.datum(e));
358 if (elementsByKey.containsKey(keyValue)) {
359 exit[ei] = e;
360 } else {
361 elementsByKey[keyValue] = e;
362 }
363 keysOnDOM.add(keyValue);
364 }
365
366 // Iterate through the values and find values that don't have
367 // corresponding elements in the DOM, collect the entering elements.
368 for (int vi = 0, len = valuesLength; vi < len; ++vi) {
369 final v = vals.elementAt(vi);
370 var keyValue = keyFn(v);
371 Element e = elementsByKey[keyValue];
372 if (e != null) {
373 update[vi] = e;
374 scope.associate(e, v);
375 } else if (!valuesByKey.containsKey(keyValue)) {
376 enter[vi] = dummy(v);
377 }
378 valuesByKey[keyValue] = v;
379 elementsByKey.remove(keyValue);
380 }
381
382 // Iterate through the previously saved keys to
383 // find a list of elements that don't have data anymore.
384 // We don't use elementsByKey.keys() because that does not
385 // guarantee the order of returned keys.
386 for (int i = 0, len = elementsLength; i < len; ++i) {
387 if (elementsByKey.containsKey(keysOnDOM[i])) {
388 exit[i] = g.elements.elementAt(i);
389 }
390 }
391 } else {
392 // When we don't have the key function, just use list index as the key
393 int updateElementsCount = math.min(elementsLength, valuesLength);
394 int i = 0;
395
396 // Collect a list of elements getting updated in this group
397 for (int len = updateElementsCount; i < len; ++i) {
398 var e = g.elements.elementAt(i);
399 if (e != null) {
400 scope.associate(e, vals.elementAt(i));
401 update[i] = e;
402 } else {
403 enter[i] = dummy(vals.elementAt(i));
404 }
405 }
406
407 // List of elements newly getting added
408 for (int len = valuesLength; i < len; ++i) {
409 enter[i] = dummy(vals.elementAt(i));
410 }
411
412 // List of elements exiting this group
413 for (int len = elementsLength; i < len; ++i) {
414 exit[i] = g.elements.elementAt(i);
415 }
416 }
417
418 // Create the element groups and set parents from the current group.
419 enterGroups.add(new _SelectionGroupImpl(enter, parent: g.parent));
420 updateGroups.add(new _SelectionGroupImpl(update, parent: g.parent));
421 exitGroups.add(new _SelectionGroupImpl(exit, parent: g.parent));
422 };
423
424 for (int gi = 0; gi < groups.length; ++gi) {
425 final g = groups.elementAt(gi);
426 join(g, fn(scope.datum(g.parent), gi, g.parent));
427 }
428
429 return new _DataSelectionImpl(
430 updateGroups, enterGroups, exitGroups, scope);
431 }
432
433 void datum(Iterable vals) {
434 throw new UnimplementedError();
435 }
436
437 void datumWithCallback(SelectionCallback<Iterable> fn) {
438 throw new UnimplementedError();
439 }
440
441 Transition transition() => new Transition(this);
442 }
443
444 /* Implementation of [DataSelection] */
445 class _DataSelectionImpl extends _SelectionImpl implements DataSelection {
446 EnterSelection enter;
447 ExitSelection exit;
448
449 _DataSelectionImpl(Iterable updated, Iterable entering, Iterable exiting,
450 SelectionScope scope) : super.selectionGroups(updated, scope) {
451 enter = new _EnterSelectionImpl(entering, this);
452 exit = new _ExitSelectionImpl(exiting, this);
453 }
454 }
455
456 /* Implementation of [EnterSelection] */
457 class _EnterSelectionImpl implements EnterSelection {
458 final DataSelection update;
459
460 SelectionScope scope;
461 Iterable<SelectionGroup> groups;
462
463 _EnterSelectionImpl(Iterable this.groups, DataSelection this.update) {
464 scope = update.scope;
465 }
466
467 bool get isEmpty => false;
468
469 Selection insert(String tag,
470 {String before, SelectionCallback<Element> beforeFn}) {
471 assert(tag != null && tag.isNotEmpty);
472 return insertWithCallback(
473 (d, ei, e) => Namespace.createChildElement(tag, e),
474 before: before, beforeFn: beforeFn);
475 }
476
477 Selection insertWithCallback(SelectionCallback<Element> fn,
478 {String before, SelectionCallback<Element> beforeFn}) {
479 assert(fn != null);
480 return selectWithCallback((d, ei, e) {
481 Element child = fn(d, ei, e);
482 e.insertBefore(child, e.querySelector(before));
483 return child;
484 });
485 }
486
487 Selection append(String tag) {
488 assert(tag != null && tag.isNotEmpty);
489 return appendWithCallback(
490 (d, ei, e) => Namespace.createChildElement(tag, e));
491 }
492
493 Selection appendWithCallback(SelectionCallback<Element> fn) {
494 assert(fn != null);
495 return selectWithCallback((datum, ei, e) {
496 Element child = fn(datum, ei, e);
497 e.append(child);
498 return child;
499 });
500 }
501
502 Selection select(String selector) {
503 assert(selector == null && selector.isNotEmpty);
504 return selectWithCallback((d, ei, e) => e.querySelector(selector));
505 }
506
507 Selection selectWithCallback(SelectionCallback<Element> fn) {
508 var subgroups = [];
509 for (int gi = 0, len = groups.length; gi < len; ++gi) {
510 final g = groups.elementAt(gi);
511 final u = update.groups.elementAt(gi);
512 final subgroup = [];
513 for (int ei = 0, eLen = g.elements.length; ei < eLen; ++ei) {
514 final e = g.elements.elementAt(ei);
515 if (e != null) {
516 var datum = scope.datum(e),
517 selected = fn(datum, ei, g.parent);
518 scope.associate(selected, datum);
519 u.elements[ei] = selected;
520 subgroup.add(selected);
521 } else {
522 subgroup.add(null);
523 }
524 }
525 subgroups.add(new _SelectionGroupImpl(subgroup, parent: g.parent));
526 }
527 return new _SelectionImpl.selectionGroups(subgroups, scope);
528 }
529 }
530
531 /* Implementation of [ExitSelection] */
532 class _ExitSelectionImpl extends _SelectionImpl implements ExitSelection {
533 final DataSelection update;
534 _ExitSelectionImpl(Iterable groups, DataSelection update)
535 : update = update, super.selectionGroups(groups, update.scope);
536 }
537
538 class _SelectionGroupImpl implements SelectionGroup {
539 Iterable<Element> elements;
540 Element parent;
541 _SelectionGroupImpl(this.elements, {this.parent});
542 }
OLDNEW
« no previous file with comments | « charted/lib/selection/selection_scope.dart ('k') | charted/lib/selection/src/transition_impl.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698