├── deps ├── Stats.js ├── Three.js └── d3.v2.js ├── examples └── dot.html └── lib └── d3-threeD.js /deps/Stats.js: -------------------------------------------------------------------------------- 1 | // stats.js r8 - http://github.com/mrdoob/stats.js 2 | var Stats=function(){var h,a,n=0,o=0,i=Date.now(),u=i,p=i,l=0,q=1E3,r=0,e,j,f,b=[[16,16,48],[0,255,255]],m=0,s=1E3,t=0,d,k,g,c=[[16,48,16],[0,255,0]];h=document.createElement("div");h.style.cursor="pointer";h.style.width="80px";h.style.opacity="0.9";h.style.zIndex="10001";h.addEventListener("mousedown",function(a){a.preventDefault();n=(n+1)%2;n==0?(e.style.display="block",d.style.display="none"):(e.style.display="none",d.style.display="block")},!1);e=document.createElement("div");e.style.textAlign= 3 | "left";e.style.lineHeight="1.2em";e.style.backgroundColor="rgb("+Math.floor(b[0][0]/2)+","+Math.floor(b[0][1]/2)+","+Math.floor(b[0][2]/2)+")";e.style.padding="0 0 3px 3px";h.appendChild(e);j=document.createElement("div");j.style.fontFamily="Helvetica, Arial, sans-serif";j.style.fontSize="9px";j.style.color="rgb("+b[1][0]+","+b[1][1]+","+b[1][2]+")";j.style.fontWeight="bold";j.innerHTML="FPS";e.appendChild(j);f=document.createElement("div");f.style.position="relative";f.style.width="74px";f.style.height= 4 | "30px";f.style.backgroundColor="rgb("+b[1][0]+","+b[1][1]+","+b[1][2]+")";for(e.appendChild(f);f.children.length<74;)a=document.createElement("span"),a.style.width="1px",a.style.height="30px",a.style.cssFloat="left",a.style.backgroundColor="rgb("+b[0][0]+","+b[0][1]+","+b[0][2]+")",f.appendChild(a);d=document.createElement("div");d.style.textAlign="left";d.style.lineHeight="1.2em";d.style.backgroundColor="rgb("+Math.floor(c[0][0]/2)+","+Math.floor(c[0][1]/2)+","+Math.floor(c[0][2]/2)+")";d.style.padding= 5 | "0 0 3px 3px";d.style.display="none";h.appendChild(d);k=document.createElement("div");k.style.fontFamily="Helvetica, Arial, sans-serif";k.style.fontSize="9px";k.style.color="rgb("+c[1][0]+","+c[1][1]+","+c[1][2]+")";k.style.fontWeight="bold";k.innerHTML="MS";d.appendChild(k);g=document.createElement("div");g.style.position="relative";g.style.width="74px";g.style.height="30px";g.style.backgroundColor="rgb("+c[1][0]+","+c[1][1]+","+c[1][2]+")";for(d.appendChild(g);g.children.length<74;)a=document.createElement("span"), 6 | a.style.width="1px",a.style.height=Math.random()*30+"px",a.style.cssFloat="left",a.style.backgroundColor="rgb("+c[0][0]+","+c[0][1]+","+c[0][2]+")",g.appendChild(a);return{domElement:h,update:function(){i=Date.now();m=i-u;s=Math.min(s,m);t=Math.max(t,m);k.textContent=m+" MS ("+s+"-"+t+")";var a=Math.min(30,30-m/200*30);g.appendChild(g.firstChild).style.height=a+"px";u=i;o++;if(i>p+1E3)l=Math.round(o*1E3/(i-p)),q=Math.min(q,l),r=Math.max(r,l),j.textContent=l+" FPS ("+q+"-"+r+")",a=Math.min(30,30-l/ 7 | 100*30),f.appendChild(f.firstChild).style.height=a+"px",p=i,o=0}}}; 8 | 9 | -------------------------------------------------------------------------------- /examples/dot.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dot Plot 5 | 6 | 7 | 8 | 9 | 32 | 33 | 34 | 105 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /lib/d3-threeD.js: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | 6 | function d3threeD(exports) { 7 | 8 | const DEGS_TO_RADS = Math.PI / 180, 9 | UNIT_SIZE = 100; 10 | 11 | const DIGIT_0 = 48, DIGIT_9 = 57, COMMA = 44, SPACE = 32, PERIOD = 46, 12 | MINUS = 45; 13 | function transformSVGPath(pathStr) { 14 | var path = new THREE.Shape(); 15 | 16 | var idx = 1, len = pathStr.length, activeCmd, 17 | x = 0, y = 0, nx = 0, ny = 0, firstX = null, firstY = null, 18 | x1 = 0, x2 = 0, y1 = 0, y2 = 0, 19 | rx = 0, ry = 0, xar = 0, laf = 0, sf = 0, cx, cy; 20 | 21 | function eatNum() { 22 | var sidx, c, isFloat = false, s; 23 | // eat delims 24 | while (idx < len) { 25 | c = pathStr.charCodeAt(idx); 26 | if (c !== COMMA && c !== SPACE) 27 | break; 28 | idx++; 29 | } 30 | if (c === MINUS) 31 | sidx = idx++; 32 | else 33 | sidx = idx; 34 | // eat number 35 | while (idx < len) { 36 | c = pathStr.charCodeAt(idx); 37 | if (DIGIT_0 <= c && c <= DIGIT_9) { 38 | idx++; 39 | continue; 40 | } 41 | else if (c === PERIOD) { 42 | idx++; 43 | isFloat = true; 44 | continue; 45 | } 46 | 47 | s = pathStr.substring(sidx, idx); 48 | return isFloat ? parseFloat(s) : parseInt(s); 49 | } 50 | 51 | s = pathStr.substring(sidx); 52 | return isFloat ? parseFloat(s) : parseInt(s); 53 | } 54 | 55 | function nextIsNum() { 56 | var c; 57 | // do permanently eat any delims... 58 | while (idx < len) { 59 | c = pathStr.charCodeAt(idx); 60 | if (c !== COMMA && c !== SPACE) 61 | break; 62 | idx++; 63 | } 64 | c = pathStr.charCodeAt(idx); 65 | return (c === MINUS || (DIGIT_0 <= c && c <= DIGIT_9)); 66 | } 67 | 68 | var canRepeat; 69 | activeCmd = pathStr[0]; 70 | while (idx <= len) { 71 | canRepeat = true; 72 | switch (activeCmd) { 73 | // moveto commands, become lineto's if repeated 74 | case 'M': 75 | x = eatNum(); 76 | y = eatNum(); 77 | path.moveTo(x, y); 78 | activeCmd = 'L'; 79 | break; 80 | case 'm': 81 | x += eatNum(); 82 | y += eatNum(); 83 | path.moveTo(x, y); 84 | activeCmd = 'l'; 85 | break; 86 | case 'Z': 87 | case 'z': 88 | canRepeat = false; 89 | if (x !== firstX || y !== firstY) 90 | path.lineTo(firstX, firstY); 91 | break; 92 | // - lines! 93 | case 'L': 94 | case 'H': 95 | case 'V': 96 | nx = (activeCmd === 'V') ? x : eatNum(); 97 | ny = (activeCmd === 'H') ? y : eatNum(); 98 | path.lineTo(nx, ny); 99 | x = nx; 100 | y = ny; 101 | break; 102 | case 'l': 103 | case 'h': 104 | case 'v': 105 | nx = (activeCmd === 'v') ? x : (x + eatNum()); 106 | ny = (activeCmd === 'h') ? y : (y + eatNum()); 107 | path.lineTo(nx, ny); 108 | x = nx; 109 | y = ny; 110 | break; 111 | // - cubic bezier 112 | case 'C': 113 | x1 = eatNum(); y1 = eatNum(); 114 | case 'S': 115 | if (activeCmd === 'S') { 116 | x1 = 2 * x - x2; y1 = 2 * y - y2; 117 | } 118 | x2 = eatNum(); 119 | y2 = eatNum(); 120 | nx = eatNum(); 121 | ny = eatNum(); 122 | path.bezierCurveTo(x1, y1, x2, y2, nx, ny); 123 | x = nx; y = ny; 124 | break; 125 | case 'c': 126 | x1 = x + eatNum(); 127 | y1 = y + eatNum(); 128 | case 's': 129 | if (activeCmd === 's') { 130 | x1 = 2 * x - x2; 131 | y1 = 2 * y - y2; 132 | } 133 | x2 = x + eatNum(); 134 | y2 = y + eatNum(); 135 | nx = x + eatNum(); 136 | ny = y + eatNum(); 137 | path.bezierCurveTo(x1, y1, x2, y2, nx, ny); 138 | x = nx; y = ny; 139 | break; 140 | // - quadratic bezier 141 | case 'Q': 142 | x1 = eatNum(); y1 = eatNum(); 143 | case 'T': 144 | if (activeCmd === 'T') { 145 | x1 = 2 * x - x1; 146 | y1 = 2 * y - y1; 147 | } 148 | nx = eatNum(); 149 | ny = eatNum(); 150 | path.quadraticCurveTo(x1, y1, nx, ny); 151 | x = nx; 152 | y = ny; 153 | break; 154 | case 'q': 155 | x1 = x + eatNum(); 156 | y1 = y + eatNum(); 157 | case 't': 158 | if (activeCmd === 't') { 159 | x1 = 2 * x - x1; 160 | y1 = 2 * y - y1; 161 | } 162 | nx = x + eatNum(); 163 | ny = y + eatNum(); 164 | path.quadraticCurveTo(x1, y1, nx, ny); 165 | x = nx; y = ny; 166 | break; 167 | // - elliptical arc 168 | case 'A': 169 | rx = eatNum(); 170 | ry = eatNum(); 171 | xar = eatNum() * DEGS_TO_RADS; 172 | laf = eatNum(); 173 | sf = eatNum(); 174 | nx = eatNum(); 175 | ny = eatNum(); 176 | if (rx !== ry) { 177 | console.warn("Forcing elliptical arc to be a circular one :(", 178 | rx, ry); 179 | } 180 | // SVG implementation notes does all the math for us! woo! 181 | // http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes 182 | // step1, using x1 as x1' 183 | x1 = Math.cos(xar) * (x - nx) / 2 + Math.sin(xar) * (y - ny) / 2; 184 | y1 = -Math.sin(xar) * (x - nx) / 2 + Math.cos(xar) * (y - ny) / 2; 185 | // step 2, using x2 as cx' 186 | var norm = Math.sqrt( 187 | (rx*rx * ry*ry - rx*rx * y1*y1 - ry*ry * x1*x1) / 188 | (rx*rx * y1*y1 + ry*ry * x1*x1)); 189 | if (laf === sf) 190 | norm = -norm; 191 | x2 = norm * rx * y1 / ry; 192 | y2 = norm * -ry * x1 / rx; 193 | // step 3 194 | cx = Math.cos(xar) * x2 - Math.sin(xar) * y2 + (x + nx) / 2; 195 | cy = Math.sin(xar) * x2 + Math.cos(xar) * y2 + (y + ny) / 2; 196 | 197 | var u = new THREE.Vector2(1, 0), 198 | v = new THREE.Vector2((x1 - x2) / rx, 199 | (y1 - y2) / ry); 200 | var startAng = Math.acos(u.dot(v) / u.length() / v.length()); 201 | if (u.x * v.y - u.y * v.x < 0) 202 | startAng = -startAng; 203 | 204 | // we can reuse 'v' from start angle as our 'u' for delta angle 205 | u.x = (-x1 - x2) / rx; 206 | u.y = (-y1 - y2) / ry; 207 | 208 | var deltaAng = Math.acos(v.dot(u) / v.length() / u.length()); 209 | // This normalization ends up making our curves fail to triangulate... 210 | if (v.x * u.y - v.y * u.x < 0) 211 | deltaAng = -deltaAng; 212 | if (!sf && deltaAng > 0) 213 | deltaAng -= Math.PI * 2; 214 | if (sf && deltaAng < 0) 215 | deltaAng += Math.PI * 2; 216 | 217 | path.absarc(cx, cy, rx, startAng, startAng + deltaAng, sf); 218 | x = nx; 219 | y = ny; 220 | break; 221 | default: 222 | throw new Error("weird path command: " + activeCmd); 223 | } 224 | if (firstX === null) { 225 | firstX = x; 226 | firstY = y; 227 | } 228 | // just reissue the command 229 | if (canRepeat && nextIsNum()) 230 | continue; 231 | activeCmd = pathStr[idx++]; 232 | } 233 | 234 | return path; 235 | } 236 | 237 | function applySVGTransform(obj, tstr) { 238 | var idx = tstr.indexOf('('), len = tstr.length, 239 | cmd = tstr.substring(0, idx++); 240 | function eatNum() { 241 | var sidx, c, isFloat = false, s; 242 | // eat delims 243 | while (idx < len) { 244 | c = tstr.charCodeAt(idx); 245 | if (c !== COMMA && c !== SPACE) 246 | break; 247 | idx++; 248 | } 249 | if (c === MINUS) 250 | sidx = idx++; 251 | else 252 | sidx = idx; 253 | // eat number 254 | while (idx < len) { 255 | c = tstr.charCodeAt(idx); 256 | if (DIGIT_0 <= c && c <= DIGIT_9) { 257 | idx++; 258 | continue; 259 | } 260 | else if (c === PERIOD) { 261 | idx++; 262 | isFloat = true; 263 | continue; 264 | } 265 | 266 | s = tstr.substring(sidx, idx); 267 | return isFloat ? parseFloat(s) : parseInt(s); 268 | } 269 | 270 | s = tstr.substring(sidx); 271 | return isFloat ? parseFloat(s) : parseInt(s); 272 | } 273 | switch (cmd) { 274 | case 'translate': 275 | obj.position.x = Math.floor(eatNum() * UNIT_SIZE); 276 | obj.position.y = Math.floor(eatNum() * UNIT_SIZE); 277 | //console.log("translated:", obj.position.x, obj.position.y); 278 | break; 279 | case 'scale': 280 | obj.scale.x = Math.floor(eatNum() * UNIT_SIZE); 281 | obj.scale.y = Math.floor(eatNum() * UNIT_SIZE); 282 | break; 283 | default: 284 | console.warn("don't understand transform", tstr); 285 | break; 286 | } 287 | } 288 | 289 | function wrap_setAttribute(name, value) { 290 | } 291 | function wrap_setAttributeNS(namespace, name, value) { 292 | } 293 | 294 | var extrudeDefaults = { 295 | amount: 1, 296 | bevelEnabled: false, 297 | material: 0, 298 | extrudeMaterial: 0, 299 | }; 300 | 301 | function commonSetAttribute(name, value) { 302 | switch (name) { 303 | case 'x': 304 | this.position.x = Math.floor(value * UNIT_SIZE); 305 | break; 306 | 307 | case 'y': 308 | this.position.y = Math.floor(value * UNIT_SIZE); 309 | break; 310 | 311 | case 'class': 312 | this.clazz = value; 313 | break; 314 | 315 | case 'stroke': 316 | case 'fill': 317 | if (typeof(value) !== 'string') 318 | value = value.toString(); 319 | this.material.color.setHex(parseInt(value.substring(1), 16)); 320 | break; 321 | 322 | case 'transform': 323 | applySVGTransform(this, value); 324 | break; 325 | 326 | case 'd': 327 | var shape = transformSVGPath(value), 328 | geom = shape.extrude(extrudeDefaults); 329 | this.geometry = geom; 330 | this.geometry.boundingSphere = {radius: 3 * UNIT_SIZE}; 331 | this.scale.set(UNIT_SIZE, UNIT_SIZE, UNIT_SIZE); 332 | 333 | break; 334 | 335 | default: 336 | throw new Error("no setter for: " + name); 337 | } 338 | } 339 | function commonSetAttributeNS(namespace, name, value) { 340 | this.setAttribute(name, value); 341 | } 342 | 343 | function Group(parentThing) { 344 | THREE.Object3D.call(this); 345 | 346 | this.d3class = ''; 347 | 348 | parentThing.add(this); 349 | }; 350 | Group.prototype = new THREE.Object3D(); 351 | Group.prototype.constructor = Group; 352 | Group.prototype.d3tag = 'g'; 353 | Group.prototype.setAttribute = commonSetAttribute; 354 | Group.prototype.setAttributeNS = commonSetAttributeNS; 355 | 356 | function fabGroup() { 357 | return new Group(this); 358 | } 359 | 360 | function Mesh(parentThing, tag, geometry, material) { 361 | THREE.Mesh.call(this, geometry, material); 362 | 363 | this.d3tag = tag; 364 | this.d3class = ''; 365 | 366 | parentThing.add(this); 367 | } 368 | Mesh.prototype = new THREE.Mesh(); 369 | Mesh.prototype.constructor = Mesh; 370 | Mesh.prototype.setAttribute = commonSetAttribute; 371 | Mesh.prototype.setAttributeNS = commonSetAttributeNS; 372 | 373 | 374 | const SPHERE_SEGS = 16, SPHERE_RINGS = 16, 375 | DEFAULT_COLOR = 0xcc0000; 376 | 377 | var sharedSphereGeom = null, 378 | sharedCubeGeom = null; 379 | 380 | function fabSphere() { 381 | if (!sharedSphereGeom) 382 | sharedSphereGeom = new THREE.SphereGeometry( 383 | UNIT_SIZE / 2, SPHERE_SEGS, SPHERE_RINGS); 384 | var material = new THREE.MeshLambertMaterial({ 385 | color: DEFAULT_COLOR, 386 | }); 387 | return new Mesh(this, 'sphere', sharedSphereGeom, material); 388 | } 389 | 390 | function fabCube() { 391 | if (!sharedCubeGeom) 392 | sharedCubeGeom = new THREE.CubeGeometry(UNIT_SIZE, UNIT_SIZE, UNIT_SIZE); 393 | var material = new THREE.MeshLambertMaterial({ 394 | color: DEFAULT_COLOR, 395 | }); 396 | return new Mesh(this, 'cube', sharedCubeGeom, material); 397 | } 398 | 399 | function fabPath() { 400 | // start with a cube that we will replace with the path once it gets created 401 | if (!sharedCubeGeom) 402 | sharedCubeGeom = new THREE.CubeGeometry(UNIT_SIZE, UNIT_SIZE, UNIT_SIZE); 403 | var material = new THREE.MeshLambertMaterial({ 404 | color: DEFAULT_COLOR, 405 | }); 406 | return new Mesh(this, 'path', sharedCubeGeom, material); 407 | } 408 | 409 | function Scene() { 410 | THREE.Scene.call(this); 411 | this.renderer = null; 412 | this.camera = null; 413 | this.controls = null; 414 | this._d3_width = null; 415 | this._d3_height = null; 416 | } 417 | Scene.prototype = new THREE.Scene(); 418 | Scene.prototype.constructor = Scene; 419 | Scene.prototype._setBounds = function() { 420 | this.renderer.setSize(this._d3_width, this._d3_height); 421 | var aspect = this.camera.aspect; 422 | this.camera.position.set( 423 | this._d3_width * UNIT_SIZE / 2, 424 | this._d3_height * UNIT_SIZE / 2, 425 | Math.max(this._d3_width * UNIT_SIZE / Math.sqrt(2), 426 | this._d3_height * UNIT_SIZE / Math.sqrt(2))); 427 | this.controls.target.set(this.camera.position.x, this.camera.position.y, 0); 428 | console.log("camera:", this.camera.position.x, this.camera.position.y, 429 | this.camera.position.z); 430 | 431 | 432 | 433 | //this.camera.position.z = 1000; 434 | }; 435 | Scene.prototype.setAttribute = function(name, value) { 436 | switch (name) { 437 | case 'width': 438 | this._d3_width = value; 439 | if (this._d3_height) 440 | this._setBounds(); 441 | break; 442 | case 'height': 443 | this._d3_height = value; 444 | if (this._d3_width) 445 | this._setBounds(); 446 | break; 447 | } 448 | }; 449 | 450 | function fabVis() { 451 | var camera, scene, controls, renderer; 452 | 453 | // - scene 454 | scene = new Scene(); 455 | 456 | // - camera 457 | camera = scene.camera = new THREE.PerspectiveCamera( 458 | 75, 459 | window.innerWidth / window.innerHeight, 460 | 1, 100000); 461 | /* 462 | camera = scene.camera = new THREE.OrthographicCamera( 463 | window.innerWidth / -2, window.innerWidth / 2, 464 | window.innerHeight / 2, window.innerHeight / -2, 465 | 1, 50000); 466 | */ 467 | scene.add(camera); 468 | 469 | // - controls 470 | // from misc_camera_trackball.html example 471 | controls = scene.controls = new THREE.TrackballControls(camera); 472 | controls.rotateSpeed = 1.0; 473 | controls.zoomSpeed = 1.2; 474 | controls.panSpeed = 0.8; 475 | 476 | controls.noZoom = false; 477 | controls.noPan = false; 478 | 479 | controls.staticMoving = true; 480 | controls.dynamicDampingFactor = 0.3; 481 | 482 | controls.keys = [65, 83, 68]; 483 | 484 | controls.addEventListener('change', render); 485 | 486 | // - light 487 | /* 488 | var pointLight = new THREE.PointLight(0xFFFFFF); 489 | pointLight.position.set(10, 50, 130); 490 | scene.add(pointLight); 491 | */ 492 | 493 | var spotlight = new THREE.SpotLight(0xffffff); 494 | spotlight.position.set(-50000, 50000, 100000); 495 | scene.add(spotlight); 496 | 497 | var backlight = new THREE.SpotLight(0x888888); 498 | backlight.position.set(50000, -50000, -100000); 499 | scene.add(backlight); 500 | 501 | /* 502 | var ambientLight = new THREE.AmbientLight(0x888888); 503 | scene.add(ambientLight); 504 | */ 505 | 506 | function helperPlanes(maxBound) { 507 | var geom = new THREE.PlaneGeometry(maxBound, maxBound, 4, 4); 508 | for (var i = 0; i < 4; i++) { 509 | var color, cx, cy; 510 | switch (i) { 511 | case 0: 512 | color = 0xff0000; 513 | cx = maxBound / 2; 514 | cy = maxBound / 2; 515 | break; 516 | case 1: 517 | color = 0x00ff00; 518 | cx = maxBound / 2; 519 | cy = -maxBound / 2; 520 | break; 521 | case 2: 522 | color = 0x0000ff; 523 | cx = -maxBound / 2; 524 | cy = -maxBound / 2; 525 | break; 526 | case 3: 527 | color = 0xffff00; 528 | cx = -maxBound / 2; 529 | cy = maxBound / 2; 530 | break; 531 | } 532 | var material = new THREE.MeshLambertMaterial({ color: color }); 533 | var mesh = new THREE.Mesh(geom, material); 534 | mesh.position.set(cx, cy, -1); 535 | scene.add(mesh); 536 | } 537 | } 538 | //helperPlanes(UNIT_SIZE * 225); 539 | 540 | // - renderer 541 | renderer = scene.renderer = new THREE.WebGLRenderer({ 542 | // too slow... 543 | //antialias: true, 544 | }); 545 | this.appendChild( renderer.domElement ); 546 | 547 | // - stats 548 | var stats = new Stats(); 549 | stats.domElement.style.position = 'absolute'; 550 | stats.domElement.style.top = '0px'; 551 | stats.domElement.style.zIndex = 100; 552 | this.appendChild( stats.domElement ); 553 | 554 | function animate() { 555 | requestAnimationFrame(animate, renderer.domElement); 556 | controls.update(); 557 | } 558 | 559 | function render() { 560 | renderer.render(scene, camera); 561 | stats.update(); 562 | } 563 | 564 | animate(); 565 | 566 | return scene; 567 | }; 568 | 569 | 570 | d3.selection.prototype.append3d = function(name) { 571 | var append; 572 | switch (name) { 573 | case 'svg': 574 | append = fabVis; 575 | break; 576 | 577 | case 'g': 578 | append = fabGroup; 579 | break; 580 | 581 | case 'path': 582 | append = fabPath; 583 | break; 584 | 585 | case 'text': 586 | case 'line': 587 | case 'rect': 588 | throw new Error("Did not implement: " + name); 589 | break; 590 | 591 | case 'sphere': 592 | append = fabSphere; 593 | break; 594 | } 595 | 596 | return this.select(append); 597 | }; 598 | d3.selection.enter.prototype.append3d = d3.selection.prototype.append3d; 599 | 600 | function select3d_selector(constraint) { 601 | var tagCheck = null, classCheck = null, 602 | idxPeriod = constraint.indexOf('.'); 603 | if (idxPeriod === -1) { 604 | tagCheck = constraint; 605 | } 606 | else if (idxPeriod === 0) { 607 | classCheck = constraint.substring(1); 608 | } 609 | else { 610 | tagCheck = constraint.substring(0, idxPeriod); 611 | classCheck = constraint.substring(idxPeriod + 1); 612 | } 613 | return function() { 614 | var results = []; 615 | for (var i = 0; i < this.children.length; i++) { 616 | var kid = this.children[i]; 617 | if ((!tagCheck || kid.d3tag === tagCheck) && 618 | (!classCheck || kid.d3class === classCheck)) 619 | results.push(kid); 620 | } 621 | return results; 622 | }; 623 | } 624 | 625 | d3.selection.prototype.select3d = function(constraint) { 626 | return this.select(select3d_selector(constraint)); 627 | }; 628 | d3.selection.prototype.selectAll3d = function(constraint) { 629 | return this.selectAll(select3d_selector(constraint)); 630 | }; 631 | 632 | 633 | } 634 | 635 | var $d3g = {}; 636 | d3threeD($d3g); 637 | --------------------------------------------------------------------------------