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