| 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 |