1 var d3_raphael_selection = function(groups, d3_raphael_root) { 2 d3_arraySubclass(groups, d3_raphael_selectionPrototype); 3 groups.root = d3_raphael_root; 4 5 return groups; 6 }; 7 8 var d3_raphael_selectionPrototype = []; 9 10 // todo: see if it is possible to generalize this method from the almost identical one in d3 11 12 /** 13 * Binds the provided data to the selected Raphael element(s). <br /> 14 * <br /> 15 * IMPLEMENTATION NOTE: Usage identical to the native d3 version, except internally, instead of binding the data to the DOM objects (like d3 does), 16 * the data is bound to the Raphael wrapper element(s) of the DOM. 17 * 18 * @see <a href="https://github.com/mbostock/d3/wiki/Selections#wiki-data">d3.selection.data()</a> 19 * @param value 20 * @param {function} key_function 21 * @return {D3RaphaelUpdateSelection} 22 * 23 * @function 24 * @name D3RaphaelSelection#data 25 */ 26 d3_raphael_selectionPrototype.data = function(value, key_function) { 27 var i = -1, 28 n = this.length, 29 group, 30 node; 31 32 // If no value is specified, return the first value. 33 if (!arguments.length) { 34 value = new Array(n = (group = this[0]).length); 35 while (++i < n) { 36 if (node = group[i]) { 37 value[i] = node.__data__; 38 } 39 } 40 return value; 41 } 42 43 function bind(group, groupData) { 44 var i, 45 n = group.length, 46 m = groupData.length, 47 n0 = Math.min(n, m), 48 n1 = Math.max(n, m), 49 updateNodes = [], 50 enterNodes = [], 51 exitNodes = [], 52 node, 53 nodeData; 54 55 if (key_function) { 56 var nodeByKeyValue = new d3_Map, 57 keyValues = [], 58 keyValue, 59 j = groupData.length; 60 61 for (i = -1; ++i < n;) { 62 keyValue = key_function.call(node = group[i], node.__data__, i); 63 if (nodeByKeyValue.has(keyValue)) { 64 exitNodes[j++] = node; // duplicate key 65 } else { 66 nodeByKeyValue.set(keyValue, node); 67 } 68 keyValues.push(keyValue); 69 } 70 71 for (i = -1; ++i < m;) { 72 keyValue = key_function.call(groupData, nodeData = groupData[i], i) 73 if (nodeByKeyValue.has(keyValue)) { 74 updateNodes[i] = node = nodeByKeyValue.get(keyValue); 75 node.__data__ = nodeData; 76 enterNodes[i] = exitNodes[i] = null; 77 } else { 78 enterNodes[i] = d3_selection_dataNode(nodeData); 79 updateNodes[i] = exitNodes[i] = null; 80 } 81 nodeByKeyValue.remove(keyValue); 82 } 83 84 for (i = -1; ++i < n;) { 85 if (nodeByKeyValue.has(keyValues[i])) { 86 exitNodes[i] = group[i]; 87 } 88 } 89 } else { 90 for (i = -1; ++i < n0;) { 91 node = group[i]; 92 nodeData = groupData[i]; 93 if (node) { 94 node.__data__ = nodeData; 95 updateNodes[i] = node; 96 enterNodes[i] = exitNodes[i] = null; 97 } else { 98 enterNodes[i] = d3_selection_dataNode(nodeData); 99 updateNodes[i] = exitNodes[i] = null; 100 } 101 } 102 for (; i < m; ++i) { 103 enterNodes[i] = d3_selection_dataNode(groupData[i]); 104 updateNodes[i] = exitNodes[i] = null; 105 } 106 for (; i < n1; ++i) { 107 exitNodes[i] = group[i]; 108 enterNodes[i] = updateNodes[i] = null; 109 } 110 } 111 112 enterNodes.update 113 = updateNodes; 114 115 enterNodes.parentNode 116 = updateNodes.parentNode 117 = exitNodes.parentNode 118 = group.parentNode; 119 120 enter.push(enterNodes); 121 update.push(updateNodes); 122 exit.push(exitNodes); 123 } 124 125 var enter = d3_raphael_enterSelection([], this.root), 126 update = d3_raphael_selection([], this.root), 127 exit = d3_raphael_selection([], this.root); 128 129 if (typeof value === "function") { 130 while (++i < n) { 131 bind(group = this[i], value.call(group, group.parentNode.__data__, i)); 132 } 133 } else { 134 while (++i < n) { 135 bind(group = this[i], value); 136 } 137 } 138 139 /** 140 * Returns the entering selection: placeholder nodes for each data element for which no corresponding existing DOM element was found in the current selection. 141 * 142 * @return {D3RaphaelEnterSelection} 143 * 144 * @see <code><a href="https://github.com/mbostock/d3/wiki/Selections#wiki-enter">d3.selection.enter()</a></code> 145 * 146 * @function 147 * @name D3RaphaelUpdateSelection#enter 148 */ 149 update.enter = function() { return enter; }; 150 151 /** 152 * Returns the exiting selection: existing DOM elements in the current selection for which no new data element was found. 153 * 154 * @return {D3RaphaelSelection} 155 * 156 * @see <code><a href="https://github.com/mbostock/d3/wiki/Selections#wiki-exit">d3.selection.exit()</a></code> 157 * 158 * @function 159 * @name D3RaphaelUpdateSelection#exit 160 */ 161 update.exit = function() { return exit; }; 162 return update; 163 }; 164 165 /** 166 * Appends an element of the specified primitive type for each of the 167 * Raphael elements in the selection. <br /> 168 * <br /> 169 * NOTE: This method behaves similarly to the d3 version, except <strong>appended elements aren't children</strong> 170 * of the selection's existing elements. In Raphael, all elements are in a flat list, peer to eachother, a child of 171 * the root containing element. 172 * 173 * @param {String} type 174 * @return {D3RaphaelSelection} with each existing element replaced with a appended element of the specified type. 175 * 176 * @see <a href="https://github.com/mbostock/d3/wiki/Selections#wiki-append">d3.selection.append()</a> 177 * @see d3_raphael_paperShapes for a list of supported primitive types 178 * 179 * @name D3RaphaelSelection#append 180 * @function 181 */ 182 d3_raphael_selectionPrototype.append = function(type) { 183 var groups = [], 184 group, 185 nodeData; 186 187 for(var j = 0; j < this.length; j++) { 188 groups.push((group = [])); 189 190 for(var i = 0; i < this[j].length; i++) { 191 if((nodeData = this[j][i])) { 192 var newNode = this.root.create(type); 193 194 if("__data__" in nodeData) 195 newNode.__data__ = nodeData.__data__; 196 197 group.push(newNode); 198 } else { 199 group.push(null); 200 } 201 } 202 } 203 204 return d3_raphael_selection(groups, this.root); 205 } 206 207 /** 208 * Manipulates the Raphael elements in this selection by changing the specified attribute to 209 * the specified value. <br/> 210 * <br/> 211 * Generally, it behaves similarly to the d3 version. Like d3, the <code>value</code> 212 * parameter can be a function to provide an attribute value specific to each elements of the selection.<br /> 213 * <br /> 214 * In addition to the attributes supported natively by Raphael, there are few additions: 215 * <dl> 216 * <dt>d</dt><dd>is an alias for Raphael attribute <code>path</code>. (Intended for compatibility with existing d3 code)</dd> 217 * <dt>class</dt><dd>Sets the element's class name (like d3 does for the same attribute name)</dd> 218 * </dl> 219 * 220 * @param {String} name Raphael attribute name 221 * @param {value of function} value the value (or a function that returns the value) to change the attribute to 222 * @return {D3RaphaelSelection} this 223 * 224 * @see <a href="http://raphaeljs.com/reference.html#Element.attr">Raphael.element.attr()</a> 225 * @see <a href="https://github.com/mbostock/d3/wiki/Selections#wiki-attr">d3.selection.attr()</a> 226 * 227 * @function 228 * @name D3RaphaelSelection#attr 229 */ 230 d3_raphael_selectionPrototype.attr = function(name, value) { 231 var valueF = (typeof value === "function") ? value : function() { return value; }; 232 this.each(function() { 233 var value = valueF.apply(this, arguments); 234 235 switch(name) { 236 case "class": 237 this.addClass(value); 238 break; 239 default: 240 this.attr(name, value); 241 } 242 243 }); 244 245 return this; 246 }; 247 248 /** 249 * Adds or removes the specified class name from the selections elements depending on the "truthness" of 250 * value. <br /> 251 * <br /> 252 * NOTE: Only adding class names is supported now, you cannot remove a class name currently with this method. 253 * 254 * @param {String} name class name 255 * @param {truthy or function} add 256 * @return {D3RaphaelSelection} this 257 * 258 * @see <a href="https://github.com/mbostock/d3/wiki/Selections#wiki-classed">d3.selection.classed()</a> 259 * 260 * @function 261 * @name D3RaphaelSelection#classed 262 */ 263 d3_raphael_selectionPrototype.classed = function(name, add) { 264 var addF = d3_raphael_functify(add); 265 266 this.each(function() { 267 if(addF.apply(this, arguments)) 268 this.addClass(name); 269 else 270 throw_raphael_not_supported(); 271 }) 272 273 return this; 274 } 275 276 277 /** 278 * Changes the text of the selection's <code>text</code> elements. <br /> 279 * <br /> 280 * NOTE: <strong>This version behaves differently than the native d3 version,</strong> which changes the text content of the 281 * selection's DOM elements. 282 * 283 * @see <a href="https://github.com/mbostock/d3/wiki/Selections#wiki-_text">d3.selection.text()</a> 284 * 285 * @param value 286 * @return {D3RaphaelSelection} this 287 * 288 * @function 289 * @name D3RaphaelSelection#text 290 */ 291 d3_raphael_selectionPrototype.text = function(value) { 292 var valueF = d3_raphael_functify(value); 293 294 this.each(function() { 295 this.attr("text", valueF.apply(this, arguments)); 296 }); 297 298 return this; 299 } 300 301 /** 302 * Performs a selection testing _all_ the elements in the Raphael paper that match the specified type, returning a new selection 303 * with only the first element found (if any). <br /> 304 * <br /> 305 * NOTE: <strong>This method behaves differently than the native d3 version.</strong> Since the Raphael paper 306 * is inherently a flat list of elements, there is no concept of a selection that is scoped by it's parent element 307 * (like in d3). Thus, every call to <code>select</code> searches on all elements in the paper, regardless of the 308 * existing content of the selection. <br /> 309 * <br /> 310 * NOTE: Currently, the selector string is limited in features. Right now, you can only specify the Raphael primitive 311 * type name you want to select, no other selector strings are supported (like css class name). 312 * 313 * @see <a href="https://github.com/mbostock/d3/wiki/Selections#wiki-d3_select">d3.select()</a> 314 * @see d3_raphael_paperShapes for a list of supported primitive types 315 * 316 * @param {String} type Raphael primitive type name 317 * @return {D3RaphaelSelection} the new selection. 318 * 319 * @function 320 * @name D3RaphaelSelection#select 321 */ 322 d3_raphael_selectionPrototype.select = function(type) { 323 return this.root.select(type); 324 }; 325 326 /** 327 * Performs a selection testing _all_ the elements in the Raphael paper that match the specified type, returning a new selection 328 * with the found elements (if any).<br /> 329 * <br /> 330 * NOTE: <strong>This method behaves differently than the native d3 version.</strong> Since the Raphael paper 331 * is inherently a flat list of elements, there is no concept of a selection that is scoped by it's parent element 332 * (like in d3). Thus, every call to <code>select</code> searches on all elements in the paper, regardless of the 333 * existing content of the selection. <br /> 334 * <br /> 335 * NOTE: Currently, the selector string is limited in features. Right now, you can only specify the Raphael primitive 336 * type name you want to select, no other selector strings are supported (like css class name). 337 * 338 * @see <a href="https://github.com/mbostock/d3/wiki/Selections#wiki-d3_selectAll">d3.selectAll()</a> 339 * @see d3_raphael_paperShapes for a list of supported primitive types 340 * 341 * @param {String} type Raphael primitive type name 342 * @return {D3RaphaelSelection} the new selection. 343 * 344 * @function 345 * @name D3RaphaelSelection#selectAll 346 */ 347 d3_raphael_selectionPrototype.selectAll = function(type) { 348 return this.root.selectAll(type); 349 }; 350 351 352 /** 353 * Iterate over the elements of the selection, executing the specified function. <br /> 354 * <br /> 355 * NOTE: This method iterates over the Raphael created wrapper element (which internally contains the native DOM element, 356 * either SVG or VML via <code>element.node</code>. 357 * 358 * @see <a href="https://github.com/mbostock/d3/wiki/Selections#wiki-each">d3.selection.each()</a> 359 * @see <a href="http://raphaeljs.com/reference.html#Element">Raphael.Element</a> 360 * 361 * @param {function} callback <code>function(datum, index) { // this is the Raphael element }</code> 362 * 363 * @function 364 * @name D3RaphaelSelection#each 365 */ 366 d3_raphael_selectionPrototype.each = d3_selectionPrototype.each; 367 368 /** 369 * Returns true if the current selection is empty. 370 * 371 * @see <a href="https://github.com/mbostock/d3/wiki/Selections#wiki-empty">d3.selection.empty()</a> 372 * 373 * @function 374 * @name D3RaphaelSelection#empty 375 */ 376 d3_raphael_selectionPrototype.empty = d3_selectionPrototype.empty; 377 378 /** 379 * Returns the first non-null element in the current selection. If the selection is empty, returns null. 380 * 381 * @see <a href="https://github.com/mbostock/d3/wiki/Selections#wiki-node">d3.selection.node()</a> 382 * 383 * @function 384 * @name D3RaphaelSelection#node 385 */ 386 d3_raphael_selectionPrototype.node = d3_selectionPrototype.node; 387 388 /** 389 * Sets arbitrary properties on the selections Raphael elements. 390 * 391 * @see <a href="https://github.com/mbostock/d3/wiki/Selections#wiki-property">d3.selection.property()</a> 392 * @see <a href="http://raphaeljs.com/reference.html#Element">Raphael.Element</a> 393 * 394 * @param {String} name property name 395 * @param value property value 396 * @return {D3RaphaelSelection} this 397 * 398 * @function 399 * @name D3RaphaelSelection#property 400 */ 401 d3_raphael_selectionPrototype.property = d3_selectionPrototype.property; 402 403 /** 404 * Invokes the specified function once, passing in the current selection along with any optional arguments. The call operator always returns the current selection, regardless of the return value of the specified function. 405 * 406 * @see <a href="https://github.com/mbostock/d3/wiki/Selections#wiki-call">d3.selection.call()</a> 407 * 408 * @param {function} func 409 * @param {*} arguments optional arguments to pass to the function 410 * 411 * @function 412 * @name D3RaphaelSelection#call 413 */ 414 d3_raphael_selectionPrototype.call = d3_selectionPrototype.call; 415 416 417 /** 418 * Gets or sets the bound data for each selection element. 419 * 420 * @see <a href="https://github.com/mbostock/d3/wiki/Selections#wiki-datum">d3.selection.datum()</a> 421 * 422 * @param {Array} value 423 * @return {D3RaphaelSelection} this 424 * 425 * @function 426 * @name D3RaphaelSelection#datum 427 */ 428 d3_raphael_selectionPrototype.datum = d3_selectionPrototype.datum; 429 430 d3_raphael_selectionPrototype.style = throw_raphael_not_supported; 431 d3_raphael_selectionPrototype.html = throw_raphael_not_supported; 432 d3_raphael_selectionPrototype.insert = throw_raphael_not_supported; 433 d3_raphael_selectionPrototype.filter = throw_raphael_not_supported; 434 d3_raphael_selectionPrototype.sort = throw_raphael_not_supported; 435 d3_raphael_selectionPrototype.order = throw_raphael_not_supported; 436 d3_raphael_selectionPrototype.on = throw_raphael_not_supported; 437 d3_raphael_selectionPrototype.transition = throw_raphael_not_supported; 438