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 part of charted.selection; | 8 part of charted.selection; |
9 | 9 |
10 /** | 10 /** |
11 * Implementation of [Selection]. | 11 * Implementation of [Selection]. |
12 * Selections cannot be created directly - they are only created using | 12 * Selections cannot be created directly - they are only created using |
13 * the select or selectAll methods on [SelectionScope] and [Selection]. | 13 * the select or selectAll methods on [SelectionScope] and [Selection]. |
14 */ | 14 */ |
15 class _SelectionImpl implements Selection { | 15 class _SelectionImpl implements Selection { |
16 Iterable<SelectionGroup> groups; | 16 List<SelectionGroup> groups; |
17 SelectionScope scope; | 17 SelectionScope scope; |
18 | 18 |
19 /** | 19 /** |
20 * Creates a new selection. | 20 * Creates a new selection. |
21 * | 21 * |
22 * When [source] is not specified, the new selection would have exactly | 22 * When [source] is not specified, the new selection would have exactly |
23 * one group with [SelectionScope.root] as it's parent. Otherwise, one group | 23 * one group with [SelectionScope.root] as it's parent. Otherwise, one group |
24 * per for each non-null element is created with element as it's parent. | 24 * per for each non-null element is created with element as it's parent. |
25 * | 25 * |
26 * When [selector] is specified, each group contains all elements matching | 26 * When [selector] is specified, each group contains all elements matching |
(...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
105 } else { | 105 } else { |
106 groups = new List<SelectionGroup>.generate( | 106 groups = new List<SelectionGroup>.generate( |
107 1, | 107 1, |
108 (_) => new _SelectionGroupImpl( | 108 (_) => new _SelectionGroupImpl( |
109 new List.generate(1, (_) => fn(null, 0, null), growable: false)), | 109 new List.generate(1, (_) => fn(null, 0, null), growable: false)), |
110 growable: false); | 110 growable: false); |
111 } | 111 } |
112 } | 112 } |
113 | 113 |
114 /** Creates a selection using the pre-computed list of [SelectionGroup] */ | 114 /** Creates a selection using the pre-computed list of [SelectionGroup] */ |
115 _SelectionImpl.selectionGroups( | 115 _SelectionImpl.selectionGroups(this.groups, this.scope); |
116 Iterable<SelectionGroup> this.groups, SelectionScope this.scope); | |
117 | 116 |
118 /** | 117 /** |
119 * Creates a selection using the list of elements. All elements will | 118 * Creates a selection using the list of elements. All elements will |
120 * be part of the same group, with [SelectionScope.root] as the group's parent | 119 * be part of the same group, with [SelectionScope.root] as the group's parent |
121 */ | 120 */ |
122 _SelectionImpl.elements(Iterable elements, SelectionScope this.scope) { | 121 _SelectionImpl.elements( |
123 groups = new List<SelectionGroup>()..add(new _SelectionGroupImpl(elements)); | 122 Iterable<Element> elements, SelectionScope this.scope) { |
| 123 groups = new List<SelectionGroup>() |
| 124 ..add(new _SelectionGroupImpl(elements.toList())); |
124 } | 125 } |
125 | 126 |
126 /** | 127 /** |
127 * Utility to evaluate value of parameters (uses value when given | 128 * Utility to evaluate value of parameters (uses value when given |
128 * or invokes a callback to get the value) and calls [action] for | 129 * or invokes a callback to get the value) and calls [action] for |
129 * each non-null element in this selection | 130 * each non-null element in this selection |
130 */ | 131 */ |
131 void _do(SelectionCallback f, Function action) { | 132 void _do(SelectionCallback f, Function action) { |
132 each((d, i, e) => action(e, f == null ? null : f(scope.datum(e), i, e))); | 133 each((d, i, e) => action(e, f == null ? null : f(scope.datum(e), i, e))); |
133 } | 134 } |
134 | 135 |
135 /** Calls a function on each non-null element in the selection */ | 136 /** Calls a function on each non-null element in the selection */ |
136 void each(SelectionCallback fn) { | 137 void each(SelectionCallback fn) { |
137 if (fn == null) return; | 138 if (fn == null) return; |
138 for (int gi = 0, gLen = groups.length; gi < gLen; ++gi) { | 139 for (int gi = 0, gLen = groups.length; gi < gLen; ++gi) { |
139 final g = groups.elementAt(gi); | 140 final g = groups.elementAt(gi); |
140 for (int ei = 0, eLen = g.elements.length; ei < eLen; ++ei) { | 141 for (int ei = 0, eLen = g.elements.length; ei < eLen; ++ei) { |
141 final e = g.elements.elementAt(ei); | 142 final e = g.elements.elementAt(ei); |
142 if (e != null) fn(scope.datum(e), ei, e); | 143 if (e != null) fn(scope.datum(e), ei, e); |
143 } | 144 } |
144 } | 145 } |
145 } | 146 } |
146 | 147 |
147 void on(String type, [SelectionCallback listener, bool capture]) { | 148 void on(String type, [SelectionCallback listener, bool capture]) { |
148 Function getEventHandler(i, e) => (Event event) { | 149 EventListener getEventHandler(i, e) => (Event event) { |
149 var previous = scope.event; | 150 var previous = scope.event; |
150 scope.event = event; | 151 scope.event = event; |
151 try { | 152 try { |
152 listener(scope.datum(e), i, e); | 153 listener(scope.datum(e), i, e); |
153 } finally { | 154 } finally { |
154 scope.event = previous; | 155 scope.event = previous; |
155 } | 156 } |
156 }; | 157 }; |
157 | 158 |
158 if (!type.startsWith('.')) { | 159 if (!type.startsWith('.')) { |
(...skipping 12 matching lines...) Expand all Loading... |
171 if (handlers != null && handlers[type] != null) { | 172 if (handlers != null && handlers[type] != null) { |
172 e.removeEventListener( | 173 e.removeEventListener( |
173 type, handlers[type].first, handlers[type].last); | 174 type, handlers[type].first, handlers[type].last); |
174 } | 175 } |
175 }); | 176 }); |
176 } | 177 } |
177 } else { | 178 } else { |
178 // Remove all listeners on the event type (ignoring the namespace) | 179 // Remove all listeners on the event type (ignoring the namespace) |
179 each((d, i, Element e) { | 180 each((d, i, Element e) { |
180 var handlers = scope._listeners[e], t = type.substring(1); | 181 var handlers = scope._listeners[e], t = type.substring(1); |
181 handlers.forEach((String s, Pair<Function, bool> value) { | 182 handlers.forEach((String s, Pair<EventListener, bool> value) { |
182 if (s.split('.')[0] == t) { | 183 if (s.split('.')[0] == t) { |
183 e.removeEventListener(s, value.first, value.last); | 184 e.removeEventListener(s, value.first, value.last); |
184 } | 185 } |
185 }); | 186 }); |
186 }); | 187 }); |
187 } | 188 } |
188 } | 189 } |
189 | 190 |
190 int get length { | 191 int get length { |
191 int retval = 0; | 192 int retval = 0; |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
274 } | 275 } |
275 | 276 |
276 Selection append(String tag) { | 277 Selection append(String tag) { |
277 assert(tag != null && tag.isNotEmpty); | 278 assert(tag != null && tag.isNotEmpty); |
278 return appendWithCallback( | 279 return appendWithCallback( |
279 (d, ei, e) => Namespace.createChildElement(tag, e)); | 280 (d, ei, e) => Namespace.createChildElement(tag, e)); |
280 } | 281 } |
281 | 282 |
282 Selection appendWithCallback(SelectionCallback<Element> fn) { | 283 Selection appendWithCallback(SelectionCallback<Element> fn) { |
283 assert(fn != null); | 284 assert(fn != null); |
284 return new _SelectionImpl.single(fn: (datum, ei, e) { | 285 return new _SelectionImpl.single( |
285 Element child = fn(datum, ei, e); | 286 fn: (datum, ei, e) { |
286 return child == null ? null : e.append(child); | 287 Element child = fn(datum, ei, e); |
287 }, source: this); | 288 return child == null ? null : e.append(child); |
| 289 }, |
| 290 source: this); |
288 } | 291 } |
289 | 292 |
290 Selection insert(String tag, | 293 Selection insert(String tag, |
291 {String before, SelectionCallback<Element> beforeFn}) { | 294 {String before, SelectionCallback<Element> beforeFn}) { |
292 assert(tag != null && tag.isNotEmpty); | 295 assert(tag != null && tag.isNotEmpty); |
293 return insertWithCallback( | 296 return insertWithCallback( |
294 (d, ei, e) => Namespace.createChildElement(tag, e), | 297 (d, ei, e) => Namespace.createChildElement(tag, e), |
295 before: before, | 298 before: before, |
296 beforeFn: beforeFn); | 299 beforeFn: beforeFn); |
297 } | 300 } |
298 | 301 |
299 Selection insertWithCallback(SelectionCallback<Element> fn, | 302 Selection insertWithCallback(SelectionCallback<Element> fn, |
300 {String before, SelectionCallback<Element> beforeFn}) { | 303 {String before, SelectionCallback<Element> beforeFn}) { |
301 assert(fn != null); | 304 assert(fn != null); |
302 beforeFn = | 305 beforeFn = |
303 before == null ? beforeFn : (d, ei, e) => e.querySelector(before); | 306 before == null ? beforeFn : (d, ei, e) => e.querySelector(before); |
304 return new _SelectionImpl.single(fn: (datum, ei, e) { | 307 return new _SelectionImpl.single( |
305 Element child = fn(datum, ei, e); | 308 fn: (datum, ei, e) { |
306 Element before = beforeFn(datum, ei, e); | 309 Element child = fn(datum, ei, e); |
307 return child == null ? null : e.insertBefore(child, before); | 310 Element before = beforeFn(datum, ei, e); |
308 }, source: this); | 311 return child == null ? null : e.insertBefore(child, before); |
| 312 }, |
| 313 source: this); |
309 } | 314 } |
310 | 315 |
311 Selection selectAll(String selector) { | 316 Selection selectAll(String selector) { |
312 assert(selector != null && selector.isNotEmpty); | 317 assert(selector != null && selector.isNotEmpty); |
313 return new _SelectionImpl.all(selector: selector, source: this); | 318 return new _SelectionImpl.all(selector: selector, source: this); |
314 } | 319 } |
315 | 320 |
316 Selection selectAllWithCallback(SelectionCallback<Iterable<Element>> fn) { | 321 Selection selectAllWithCallback(SelectionCallback<Iterable<Element>> fn) { |
317 assert(fn != null); | 322 assert(fn != null); |
318 return new _SelectionImpl.all(fn: fn, source: this); | 323 return new _SelectionImpl.all(fn: fn, source: this); |
319 } | 324 } |
320 | 325 |
321 DataSelection data(Iterable vals, [SelectionKeyFunction keyFn]) { | 326 DataSelection data(Iterable vals, [SelectionKeyFunction keyFn]) { |
322 assert(vals != null); | 327 assert(vals != null); |
323 return dataWithCallback(toCallback(vals), keyFn); | 328 return dataWithCallback(toCallback(vals), keyFn); |
324 } | 329 } |
325 | 330 |
326 DataSelection dataWithCallback(SelectionCallback<Iterable> fn, | 331 DataSelection dataWithCallback(SelectionCallback<Iterable> fn, |
327 [SelectionKeyFunction keyFn]) { | 332 [SelectionKeyFunction keyFn]) { |
328 assert(fn != null); | 333 assert(fn != null); |
329 | 334 |
330 var enterGroups = [], updateGroups = [], exitGroups = []; | 335 var enterGroups = <SelectionGroup>[], |
| 336 updateGroups = <SelectionGroup>[], |
| 337 exitGroups = <SelectionGroup>[]; |
331 | 338 |
332 // Create a dummy node to be used with enter() selection. | 339 // Create a dummy node to be used with enter() selection. |
333 Object dummy(val) { | 340 Element dummy(val) { |
334 var element = new Object(); | 341 var element = new Element.div(); |
335 scope.associate(element, val); | 342 scope.associate(element, val); |
336 return element; | 343 return element; |
337 } | 344 } |
338 ; | 345 ; |
339 | 346 |
340 // Joins data to all elements in the group. | 347 // Joins data to all elements in the group. |
341 void join(SelectionGroup g, Iterable vals) { | 348 void join(SelectionGroup g, Iterable vals) { |
342 final int valuesLength = vals.length; | 349 final int valuesLength = vals.length; |
343 final int elementsLength = g.elements.length; | 350 final int elementsLength = g.elements.length; |
344 | 351 |
345 // Nodes exiting, entering and updating in this group. | 352 // Nodes exiting, entering and updating in this group. |
346 // We maintain the nodes at the same index as they currently | 353 // We maintain the nodes at the same index as they currently |
347 // are (for exiting) or where they should be (for entering and updating) | 354 // are (for exiting) or where they should be (for entering and updating) |
348 var update = new List(valuesLength), | 355 var update = new List<Element>(valuesLength); |
349 enter = new List(valuesLength), | 356 var enter = new List<Element>(valuesLength); |
350 exit = new List(elementsLength); | 357 var exit = new List<Element>(elementsLength); |
351 | 358 |
352 // Use key function to determine DOMElement to data associations. | 359 // Use key function to determine DOMElement to data associations. |
353 if (keyFn != null) { | 360 if (keyFn != null) { |
354 var keysOnDOM = [], elementsByKey = {}, valuesByKey = {}; | 361 var keysOnDOM = [], elementsByKey = {}, valuesByKey = {}; |
355 | 362 |
356 // Create a key to DOM element map. | 363 // Create a key to DOM element map. |
357 // Used later to see if an element already exists for a key. | 364 // Used later to see if an element already exists for a key. |
358 for (int ei = 0, len = elementsLength; ei < len; ++ei) { | 365 for (int ei = 0, len = elementsLength; ei < len; ++ei) { |
359 final e = g.elements.elementAt(ei); | 366 final e = g.elements.elementAt(ei); |
360 var keyValue = keyFn(scope.datum(e)); | 367 var keyValue = keyFn(scope.datum(e)); |
(...skipping 81 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
442 } | 449 } |
443 | 450 |
444 Transition transition() => new Transition(this); | 451 Transition transition() => new Transition(this); |
445 } | 452 } |
446 | 453 |
447 /* Implementation of [DataSelection] */ | 454 /* Implementation of [DataSelection] */ |
448 class _DataSelectionImpl extends _SelectionImpl implements DataSelection { | 455 class _DataSelectionImpl extends _SelectionImpl implements DataSelection { |
449 EnterSelection enter; | 456 EnterSelection enter; |
450 ExitSelection exit; | 457 ExitSelection exit; |
451 | 458 |
452 _DataSelectionImpl(Iterable updated, Iterable entering, Iterable exiting, | 459 _DataSelectionImpl( |
| 460 List<SelectionGroup> updated, |
| 461 Iterable<SelectionGroup> entering, |
| 462 Iterable<SelectionGroup> exiting, |
453 SelectionScope scope) | 463 SelectionScope scope) |
454 : super.selectionGroups(updated, scope) { | 464 : super.selectionGroups(updated, scope) { |
455 enter = new _EnterSelectionImpl(entering, this); | 465 enter = new _EnterSelectionImpl(entering, this); |
456 exit = new _ExitSelectionImpl(exiting, this); | 466 exit = new _ExitSelectionImpl(exiting, this); |
457 } | 467 } |
458 } | 468 } |
459 | 469 |
460 /* Implementation of [EnterSelection] */ | 470 /* Implementation of [EnterSelection] */ |
461 class _EnterSelectionImpl implements EnterSelection { | 471 class _EnterSelectionImpl implements EnterSelection { |
462 final DataSelection update; | 472 final DataSelection update; |
463 | 473 |
464 SelectionScope scope; | 474 SelectionScope scope; |
465 Iterable<SelectionGroup> groups; | 475 Iterable<SelectionGroup> groups; |
466 | 476 |
467 _EnterSelectionImpl(Iterable this.groups, DataSelection this.update) { | 477 _EnterSelectionImpl(this.groups, this.update) { |
468 scope = update.scope; | 478 scope = update.scope; |
469 } | 479 } |
470 | 480 |
471 bool get isEmpty => false; | 481 bool get isEmpty => false; |
472 | 482 |
473 Selection insert(String tag, | 483 Selection insert(String tag, |
474 {String before, SelectionCallback<Element> beforeFn}) { | 484 {String before, SelectionCallback<Element> beforeFn}) { |
475 assert(tag != null && tag.isNotEmpty); | 485 assert(tag != null && tag.isNotEmpty); |
476 return insertWithCallback( | 486 return insertWithCallback( |
477 (d, ei, e) => Namespace.createChildElement(tag, e), | 487 (d, ei, e) => Namespace.createChildElement(tag, e), |
(...skipping 25 matching lines...) Expand all Loading... |
503 return child; | 513 return child; |
504 }); | 514 }); |
505 } | 515 } |
506 | 516 |
507 Selection select(String selector) { | 517 Selection select(String selector) { |
508 assert(selector == null && selector.isNotEmpty); | 518 assert(selector == null && selector.isNotEmpty); |
509 return selectWithCallback((d, ei, e) => e.querySelector(selector)); | 519 return selectWithCallback((d, ei, e) => e.querySelector(selector)); |
510 } | 520 } |
511 | 521 |
512 Selection selectWithCallback(SelectionCallback<Element> fn) { | 522 Selection selectWithCallback(SelectionCallback<Element> fn) { |
513 var subgroups = []; | 523 var subgroups = <SelectionGroup>[]; |
514 for (int gi = 0, len = groups.length; gi < len; ++gi) { | 524 for (int gi = 0, len = groups.length; gi < len; ++gi) { |
515 final g = groups.elementAt(gi); | 525 final g = groups.elementAt(gi); |
516 final u = update.groups.elementAt(gi); | 526 final u = update.groups.elementAt(gi); |
517 final subgroup = []; | 527 final subgroup = <Element>[]; |
518 for (int ei = 0, eLen = g.elements.length; ei < eLen; ++ei) { | 528 for (int ei = 0, eLen = g.elements.length; ei < eLen; ++ei) { |
519 final e = g.elements.elementAt(ei); | 529 final e = g.elements.elementAt(ei); |
520 if (e != null) { | 530 if (e != null) { |
521 var datum = scope.datum(e), selected = fn(datum, ei, g.parent); | 531 var datum = scope.datum(e), selected = fn(datum, ei, g.parent); |
522 scope.associate(selected, datum); | 532 scope.associate(selected, datum); |
523 u.elements[ei] = selected; | 533 u.elements[ei] = selected; |
524 subgroup.add(selected); | 534 subgroup.add(selected); |
525 } else { | 535 } else { |
526 subgroup.add(null); | 536 subgroup.add(null); |
527 } | 537 } |
528 } | 538 } |
529 subgroups.add(new _SelectionGroupImpl(subgroup, parent: g.parent)); | 539 subgroups.add(new _SelectionGroupImpl(subgroup, parent: g.parent)); |
530 } | 540 } |
531 return new _SelectionImpl.selectionGroups(subgroups, scope); | 541 return new _SelectionImpl.selectionGroups(subgroups, scope); |
532 } | 542 } |
533 } | 543 } |
534 | 544 |
535 /* Implementation of [ExitSelection] */ | 545 /* Implementation of [ExitSelection] */ |
536 class _ExitSelectionImpl extends _SelectionImpl implements ExitSelection { | 546 class _ExitSelectionImpl extends _SelectionImpl implements ExitSelection { |
537 final DataSelection update; | 547 final DataSelection update; |
538 _ExitSelectionImpl(Iterable groups, DataSelection update) | 548 _ExitSelectionImpl(List<SelectionGroup> groups, DataSelection update) |
539 : update = update, | 549 : update = update, |
540 super.selectionGroups(groups, update.scope); | 550 super.selectionGroups(groups, update.scope); |
541 } | 551 } |
542 | 552 |
543 class _SelectionGroupImpl implements SelectionGroup { | 553 class _SelectionGroupImpl implements SelectionGroup { |
544 Iterable<Element> elements; | 554 List<Element> elements; |
545 Element parent; | 555 Element parent; |
546 _SelectionGroupImpl(this.elements, {this.parent}); | 556 _SelectionGroupImpl(this.elements, {this.parent}); |
547 } | 557 } |
OLD | NEW |