summaryrefslogtreecommitdiffstats
path: root/ui/imports/lib/d3three.js
diff options
context:
space:
mode:
Diffstat (limited to 'ui/imports/lib/d3three.js')
-rw-r--r--ui/imports/lib/d3three.js789
1 files changed, 789 insertions, 0 deletions
diff --git a/ui/imports/lib/d3three.js b/ui/imports/lib/d3three.js
new file mode 100644
index 0000000..51493f2
--- /dev/null
+++ b/ui/imports/lib/d3three.js
@@ -0,0 +1,789 @@
+/////////////////////////////////////////////////////////////////////////////////////////
+// Copyright (c) 2017 Koren Lev (Cisco Systems), Yaron Yogev (Cisco Systems) and others /
+// /
+// All rights reserved. This program and the accompanying materials /
+// are made available under the terms of the Apache License, Version 2.0 /
+// which accompanies this distribution, and is available at /
+// http://www.apache.org/licenses/LICENSE-2.0 /
+/////////////////////////////////////////////////////////////////////////////////////////
+var chartOffset = 0;
+
+// D3.layout.force3d.js
+// (C) 2012 ziggy.jonsson.nyc@gmail.com
+// BSD license (http://opensource.org/licenses/BSD-3-Clause)
+
+d3.layout.force3d = function() {
+ var forceXY = d3.layout.force()
+ ,forceZ = d3.layout.force()
+ ,zNodes = {}
+ ,zLinks = {}
+ ,nodeID = 1
+ ,linkID = 1
+ ,tickFunction = Object
+
+ var force3d = {}
+
+ Object.keys(forceXY).forEach(function(d) {
+ force3d[d] = function() {
+ var result = forceXY[d].apply(this,arguments)
+ if (d !="nodes" && d!="links") forceZ[d].apply(this,arguments)
+ return (result == forceXY) ? force3d : result
+ }
+ })
+
+
+ force3d.on = function(name,fn) {
+ tickFunction = fn
+ return force3d
+ }
+
+
+ forceXY.on("tick",function() {
+
+ // Refresh zNodes add new, delete removed
+ var _zNodes = {}
+ forceXY.nodes().forEach(function(d,i) {
+ if (!d.id) d.id = nodeID++
+ _zNodes[d.id] = zNodes[d.id] || {x:d.z,px:d.z,py:d.z,y:d.z,id:d.id}
+ d.z = _zNodes[d.id].x
+ })
+ zNodes = _zNodes
+
+ // Refresh zLinks add new, delete removed
+ var _zLinks = {}
+ forceXY.links().forEach(function(d) {
+ var nytt = false
+ if (!d.linkID) { d.linkID = linkID++;nytt=true}
+ _zLinks[d.linkID] = zLinks[d.linkID] || {target:zNodes[d.target.id],source:zNodes[d.source.id]}
+
+ })
+ zLinks = _zLinks
+
+ // Update the nodes/links in forceZ
+ forceZ.nodes(d3.values(zNodes))
+ forceZ.links(d3.values(zLinks))
+ forceZ.start() // Need to kick forceZ so we don't lose the update mechanism
+
+ // And run the user defined function, if defined
+ tickFunction()
+ })
+
+ // Expose the sub-forces for debugging purposes
+ force3d.xy = forceXY
+ force3d.z = forceZ
+
+ return force3d
+}
+// end of d3.layout.force3d.js
+
+// Override default functions for d3
+THREE.Object3D.prototype.appendChild = function (c) {
+ this.add(c);
+ return c;
+};
+THREE.Object3D.prototype.querySelectorAll = function () { return []; };
+
+// this one is to use D3's .attr() on THREE's objects
+THREE.Object3D.prototype.setAttribute = function (name, value) {
+ var chain = name.split('.');
+ var object = this;
+ for (var i = 0; i < chain.length - 1; i++) {
+ object = object[chain[i]];
+ }
+ object[chain[chain.length - 1]] = value;
+}
+
+// d3three object
+D3THREE = function(singleton) {
+ this.labelGroup = new THREE.Object3D();
+ this.maxY = 0;
+ this.axisObjects = {};
+
+ this.running = true;
+
+ if (singleton) {
+ if (typeof(d3three) !== 'undefined') {
+ d3three.stop();
+ }
+ d3three = this;
+ }
+
+ //if (!singleton) {
+ // d3threes.push(this);
+ //}
+}
+
+D3THREE.prototype.init = function(divId) {
+ // standard THREE stuff, straight from examples
+ this.renderer = new THREE.WebGLRenderer({antialias: true, alpha : true, preserveDrawingBuffer: true});
+ this.renderer.shadowMap.enabled = true;
+ this.renderer.shadowMap.type = THREE.PCFSoftShadow;
+ this.renderer.shadowMapSoft = true;
+ this.renderer.shadowCameraNear = 1000;
+ this.renderer.shadowCameraFar = 10000;
+ this.renderer.shadowCameraFov = 50;
+ this.renderer.shadowMapBias = 0.0039;
+ this.renderer.shadowMapDarkness = 0.25;
+ this.renderer.shadowMapWidth = 10000;
+ this.renderer.shadowMapHeight = 10000;
+ this.renderer.physicallyBasedShading = true;
+
+ this.divId = divId;
+ this.width = document.getElementById(divId).offsetWidth;
+ this.height = document.getElementById(divId).offsetHeight;
+
+ this.renderer.setSize( this.width, this.height );
+
+ document.getElementById(divId).appendChild( this.renderer.domElement );
+
+ this.camera = new THREE.PerspectiveCamera( 30, this.width / this.height, 1, 100000 );
+ this.camera.position.z = -1000;
+ this.camera.position.x = -800;
+ this.camera.position.y = 600;
+
+ this.controls = new THREE.OrbitControls( this.camera, this.renderer.domElement );
+
+ this.scene = new THREE.Scene();
+
+ this.defaultLight = new THREE.AmbientLight( 0xbbbbb ); // soft white light
+ this.scene.add( this.defaultLight );
+
+ this.scene.add(this.labelGroup);
+
+ var self = this;
+ window.addEventListener( 'resize', self.onWindowResize.bind(self), false );
+}
+
+D3THREE.prototype.onWindowResize = function() {
+ var self = this;
+ self.camera.aspect = self.width / self.height;
+ self.camera.updateProjectionMatrix();
+
+ self.renderer.setSize( self.width, self.height );
+}
+
+D3THREE.prototype.animate = function() {
+ var self = this;
+ if (this.running) {
+ setTimeout( function() {
+ this.requestId = requestAnimationFrame( self.animate.bind(self) );
+ }, 1000 / 15 );
+
+ self.renderer.render( self.scene, self.camera );
+ self.controls.update();
+
+ self.labelGroup.children.forEach(function(l){
+ l.rotation.setFromRotationMatrix(self.camera.matrix, "YXZ");
+ l.rotation.x = 0;
+ l.rotation.z = 0;
+ });
+ } else {
+ window.removeEventListener( 'resize', self.onWindowResize.bind(self) );
+ while (self.scene.children.length > 0) {
+ var childObject = self.scene.children[0];
+ if (childObject.geometry) {
+ childObject.geometry.dispose();
+ }
+ if (childObject.material) {
+ childObject.material.dispose();
+ }
+ self.scene.remove(childObject);
+ delete(childObject);
+ }
+
+ self.renderer.context = null;
+ self.renderer.domElement = null;
+ self.renderer = null;
+
+ self.camera = null;
+ self.controls = null;
+ self.scene = null;
+ self.labelGroup = null;
+
+ cancelAnimationFrame(self.requestId);
+ }
+}
+
+D3THREE.prototype.stop = function() {
+ this.running = false;
+}
+
+D3THREE.prototype.render = function(element, data) {
+ element.render(data);
+}
+
+D3THREE.createAxis = function(dt) {
+ return new D3THREE.Axis(dt);
+}
+
+// d3three axis
+D3THREE.Axis = function(dt) {
+ this._scale = d3.scale.linear();
+ this._orient = "x";
+ this._tickFormat = function(d) { return d };
+ this._dt = dt;
+}
+
+D3THREE.Axis.prototype.orient = function(o) {
+ if (o) {
+ this._dt.axisObjects[o] = this;
+ this._orient = o;
+ }
+ return this;
+}
+
+D3THREE.Axis.prototype.scale = function(s) {
+ if (s) {
+ this._scale = s;
+ }
+ return this;
+}
+
+D3THREE.Axis.prototype.tickFormat = function(f) {
+ if (f) {
+ this._tickFormat = f;
+ }
+ return this;
+}
+
+D3THREE.Axis.prototype.interval = function() {
+ var interval;
+ if (typeof(this._scale.rangeBand) === 'function') {
+ // ordinal scale
+ interval = this._scale.range()[1];
+ } else {
+ interval = this._scale.range()[1] / (this._scale.ticks().length - 1);
+ }
+ return interval;
+}
+
+D3THREE.Axis.prototype.ticks = function() {
+ var ticks;
+ if (typeof(this._scale.rangeBand) === 'function') {
+ // ordinal scale
+ ticks = this._scale.domain();
+ } else {
+ ticks = this._scale.ticks();
+ }
+ return ticks;
+}
+
+D3THREE.Axis.prototype.getRotationShift = function() {
+ return this.interval() * (this.ticks().length - 1) / 2;
+}
+
+D3THREE.Axis.prototype.render = function() {
+ var material = new THREE.LineBasicMaterial({
+ color: 0xbbbbbb,
+ linewidth: 2
+ });
+
+ var tickMaterial = new THREE.LineBasicMaterial({
+ color: 0xbbbbbb,
+ linewidth: 1
+ });
+
+ var geometry = new THREE.Geometry();
+
+ interval = this.interval();
+
+ var interval = this.interval(), ticks = this.ticks();
+
+ // x,y axis shift, so rotation is from center of screen
+ var xAxisShift = this._dt.axisObjects.x.getRotationShift(),
+ yAxisShift = this._dt.axisObjects.y.getRotationShift();
+
+ for (var i = 0; i < ticks.length; i++) {
+ var tickMarGeometry = new THREE.Geometry();
+
+ var shape = new THREE.TextGeometry(this._tickFormat(ticks[i]),
+ {
+ size: 5,
+ height: 1,
+ curveSegments: 20
+ });
+ var wrapper = new THREE.MeshBasicMaterial({color: 0xbbbbbb});
+ var words = new THREE.Mesh(shape, wrapper);
+
+ if (this._orient === "y") {
+ // tick
+ geometry.vertices.push(new THREE.Vector3(i * interval - yAxisShift, chartOffset, 0 - xAxisShift));
+
+ tickMarGeometry.vertices.push(new THREE.Vector3(i * interval - yAxisShift, chartOffset, 0 - xAxisShift));
+ tickMarGeometry.vertices.push(new THREE.Vector3(i * interval - yAxisShift, -10 + chartOffset, 0 - xAxisShift));
+ var tickLine = new THREE.Line(tickMarGeometry, tickMaterial);
+ this._dt.scene.add(tickLine);
+
+ if (i * interval > this._dt.maxY) {
+ this._dt.maxY = i * interval;
+ }
+
+ words.position.set(i * interval - yAxisShift, -20 + chartOffset, 0 - xAxisShift);
+ } else if (this._orient === "z") {
+ // tick
+ geometry.vertices.push(new THREE.Vector3(0 + this._dt.maxY - yAxisShift, i * interval + chartOffset, 0 - xAxisShift));
+
+ tickMarGeometry.vertices.push(new THREE.Vector3(0 + this._dt.maxY - yAxisShift, i * interval + chartOffset, 0 - xAxisShift));
+ tickMarGeometry.vertices.push(new THREE.Vector3(10 + this._dt.maxY - yAxisShift, i * interval + chartOffset, 0 - xAxisShift));
+ var tickLine = new THREE.Line(tickMarGeometry, tickMaterial);
+ this._dt.scene.add(tickLine);
+
+ words.position.set(20 + this._dt.maxY - yAxisShift, i * interval + chartOffset, 0 - xAxisShift);
+ } else if (this._orient === "x") {
+ // tick
+ geometry.vertices.push(new THREE.Vector3(0 - yAxisShift, chartOffset, i * interval - xAxisShift));
+
+ tickMarGeometry.vertices.push(new THREE.Vector3(0 - yAxisShift, 0 + chartOffset, i * interval - xAxisShift));
+ tickMarGeometry.vertices.push(new THREE.Vector3(0 - yAxisShift, -10 + chartOffset, i * interval - xAxisShift));
+ var tickLine = new THREE.Line(tickMarGeometry, tickMaterial);
+ this._dt.scene.add(tickLine);
+
+ words.position.set(0 - yAxisShift, -20 + chartOffset, i * interval - xAxisShift);
+ }
+
+ this._dt.labelGroup.add(words);
+ }
+
+ var line = new THREE.Line(geometry, material);
+
+ this._dt.scene.add(line);
+}
+
+// Chart object
+D3THREE.Chart = function() {
+}
+
+D3THREE.Chart.prototype.config = function(c) {
+ this._config = $.extend(this._config, c);
+}
+
+D3THREE.Chart.prototype.init = function(dt) {
+ this._dt = dt;
+ // mouse move
+ var self = this;
+ this._dt.renderer.domElement.addEventListener( 'mousemove', function(e) {
+ self.onDocumentMouseMove(e);
+ }, false );
+}
+
+var cumulativeOffset = function(element) {
+ var top = 0, left = 0;
+ do {
+ top += element.offsetTop || 0;
+ left += element.offsetLeft || 0;
+ element = element.offsetParent;
+ } while(element);
+
+ return {
+ top: top,
+ left: left
+ };
+};
+
+D3THREE.Chart.prototype.detectNodeHover = function(e) {
+ var boundingRect = this._dt.renderer.domElement.getBoundingClientRect();
+
+ var vector = new THREE.Vector3();
+ vector.x = ( (e.clientX - boundingRect.left) / this._dt.renderer.domElement.width ) * 2 - 1;
+ vector.y = 1 - ( (e.clientY - boundingRect.top) / this._dt.renderer.domElement.height ) * 2;
+ vector.z = 1;
+
+ // create a check ray
+ vector.unproject( this._dt.camera );
+ var ray = new THREE.Raycaster( this._dt.camera.position,
+ vector.sub( this._dt.camera.position ).normalize() );
+
+ var intersects = ray.intersectObjects( this._nodeGroup.children );
+
+ for (var i = 0; i < this._nodeGroup.children.length; i++) {
+ this._nodeGroup.children[i].material.opacity = 1;
+ }
+
+ if (intersects.length > 0) {
+ var obj = intersects[0].object;
+ obj.material.opacity = 0.5;
+
+ var html = "";
+
+ html += "<div class=\"tooltip_kv\">";
+ html += "<span>";
+ html += "x: " + this._dt.axisObjects.x._tickFormat(obj.userData.x);
+ html += "</span><br>";
+ html += "<span>";
+ html += "y: " + this._dt.axisObjects.y._tickFormat(obj.userData.y);
+ html += "</span><br>";
+ html += "<span>";
+ html += "z: " + this._dt.axisObjects.z._tickFormat(obj.userData.z);
+ html += "</span><br>";
+ html += "</div>";
+
+ document.getElementById("tooltip-container").innerHTML = html;
+ document.getElementById("tooltip-container").style.display = "block";
+
+ document.getElementById("tooltip-container").style.top = (e.pageY + 10) + "px";
+ document.getElementById("tooltip-container").style.left = (e.pageX + 10) + "px";
+ } else {
+ document.getElementById("tooltip-container").style.display = "none";
+ }
+}
+
+// Scatter plot
+D3THREE.Scatter = function(dt) {
+ this.init(dt);
+
+ this._nodeGroup = new THREE.Object3D();
+
+ this._config = {color: 0x4682B4, pointRadius: 5};
+}
+
+D3THREE.Scatter.prototype = new D3THREE.Chart();
+
+D3THREE.Scatter.prototype.onDocumentMouseMove = function(e) {
+ // detect intersected spheres
+ this.detectNodeHover(e);
+}
+
+D3THREE.Scatter.prototype.render = function(data) {
+ var geometry = new THREE.SphereGeometry( this._config.pointRadius, 32, 32 );
+
+ this._dt.scene.add(this._nodeGroup);
+
+ // x,y axis shift, so rotation is from center of screen
+ var xAxisShift = this._dt.axisObjects.x.getRotationShift(),
+ yAxisShift = this._dt.axisObjects.y.getRotationShift();
+
+ var self = this;
+ d3.select(this._nodeGroup)
+ .selectAll()
+ .data(data)
+ .enter().append( function(d) {
+ var material = new THREE.MeshBasicMaterial( {
+ color: self._config.color } );
+ var mesh = new THREE.Mesh( geometry, material );
+ mesh.userData = {x: d.x, y: d.y, z: d.z};
+ return mesh;
+ } )
+ .attr("position.z", function(d) {
+ return self._dt.axisObjects.x._scale(d.x) - xAxisShift;
+ })
+ .attr("position.x", function(d) {
+ return self._dt.axisObjects.y._scale(d.y) - yAxisShift;
+ })
+ .attr("position.y", function(d) {
+ return self._dt.axisObjects.z._scale(d.z) + chartOffset;
+ });
+}
+
+// Surface plot
+D3THREE.Surface = function(dt) {
+ this.init(dt);
+
+ this._nodeGroup = new THREE.Object3D();
+
+ this._config = {color: 0x4682B4, pointColor: 0xff7f0e, pointRadius: 2};
+}
+
+D3THREE.Surface.prototype = new D3THREE.Chart();
+
+D3THREE.Surface.prototype.onDocumentMouseMove = function(e) {
+ // detect intersected spheres
+ var boundingRect = this._dt.renderer.domElement.getBoundingClientRect();
+
+ var vector = new THREE.Vector3();
+ vector.x = ( (e.clientX - boundingRect.left) / this._dt.renderer.domElement.width ) * 2 - 1;
+ vector.y = 1 - ( (e.clientY - boundingRect.top) / this._dt.renderer.domElement.height ) * 2;
+ vector.z = 1;
+
+ // create a check ray
+ vector.unproject( this._dt.camera );
+ var ray = new THREE.Raycaster( this._dt.camera.position,
+ vector.sub( this._dt.camera.position ).normalize() );
+
+ var meshIntersects = ray.intersectObjects( [this._meshSurface] );
+
+ if (meshIntersects.length > 0) {
+ for (var i = 0; i < this._nodeGroup.children.length; i++) {
+ this._nodeGroup.children[i].visible = true;
+ this._nodeGroup.children[i].material.opacity = 1;
+ }
+
+ this.detectNodeHover(e);
+ } else {
+ // hide nodes
+ for (var i = 0; i < this._nodeGroup.children.length; i++) {
+ this._nodeGroup.children[i].visible = false;
+ }
+ }
+}
+
+D3THREE.Surface.prototype.render = function(threeData) {
+ /* render data points */
+ var geometry = new THREE.SphereGeometry( this._config.pointRadius, 32, 32 );
+
+ this._dt.scene.add(this._nodeGroup);
+
+ // x,y axis shift, so rotation is from center of screen
+ var xAxisShift = this._dt.axisObjects.x.getRotationShift(),
+ yAxisShift = this._dt.axisObjects.y.getRotationShift();
+
+ var self = this;
+ d3.select(this._nodeGroup)
+ .selectAll()
+ .data(threeData)
+ .enter().append( function(d) {
+ var material = new THREE.MeshBasicMaterial( {
+ color: self._config.pointColor } );
+ var mesh = new THREE.Mesh( geometry, material );
+ mesh.userData = {x: d.x, y: d.y, z: d.z};
+ mesh.visible = false;
+ return mesh;
+ } )
+ .attr("position.z", function(d) {
+ return self._dt.axisObjects.x._scale(d.x) - xAxisShift;
+ })
+ .attr("position.x", function(d) {
+ return self._dt.axisObjects.y._scale(d.y) - yAxisShift;
+ })
+ .attr("position.y", function(d) {
+ return self._dt.axisObjects.z._scale(d.z) + chartOffset;
+ });
+
+ /* custom surface */
+ function distance (v1, v2)
+ {
+ var dx = v1.x - v2.x;
+ var dy = v1.y - v2.y;
+ var dz = v1.z - v2.z;
+
+ return Math.sqrt(dx*dx+dz*dz);
+ }
+
+ var vertices = [];
+ var holes = [];
+ var triangles, mesh;
+ var geometry = new THREE.Geometry();
+ var material = new THREE.MeshBasicMaterial({color: this._config.color});
+
+ for (var i = 0; i < threeData.length; i++) {
+ vertices.push(new THREE.Vector3(
+ self._dt.axisObjects.y._scale(threeData[i].y) - yAxisShift,
+ self._dt.axisObjects.z._scale(threeData[i].z) + chartOffset,
+ self._dt.axisObjects.x._scale(threeData[i].x) - xAxisShift));
+ }
+
+ geometry.vertices = vertices;
+
+ for (var i = 0; i < vertices.length; i++) {
+ // find three closest vertices to generate surface
+ var v1, v2, v3;
+ var distances = [];
+
+ // find vertices in same y or y + 1 row
+ var minY = Number.MAX_VALUE;
+ for (var j = i + 1; j < vertices.length; j++) {
+ if (i !== j && vertices[j].x > vertices[i].x) {
+ if (vertices[j].x < minY) {
+ minY = vertices[j].x;
+ }
+ }
+ }
+
+ var rowVertices = [], row2Vertices = [];
+ for (var j = i + 1; j < vertices.length; j++) {
+ if (i !== j && (vertices[j].x === vertices[i].x)) {
+ rowVertices.push({index: j, v: vertices[j]});
+ }
+ if (i !== j && (vertices[j].x === minY)) {
+ row2Vertices.push({index: j, v: vertices[j]});
+ }
+ }
+
+ if (rowVertices.length >= 1 && row2Vertices.length >= 2) {
+ // find smallest x
+ rowVertices.sort(function(a, b) {
+ if (a.v.z < b.v.z) {
+ return -1;
+ } else if (a.v.z === b.v.z) {
+ return 0;
+ } else {
+ return 1;
+ }
+ });
+
+ v1 = rowVertices[0].index;
+
+ row2Vertices.sort(function(a, b) {
+ if (a.v.z < b.v.z) {
+ return -1;
+ } else if (a.v.z === b.v.z) {
+ return 0;
+ } else {
+ return 1;
+ }
+ });
+
+ v2 = row2Vertices[0].index;
+ v3 = row2Vertices[1].index;
+
+ var fv = [i, v1, v2, v3];
+ fv = fv.sort(function(a, b) {
+ if (a < b) return -1;
+ else if (a === b) return 0;
+ else return 1;
+ });
+
+ geometry.faces.push( new THREE.Face3(fv[1], fv[0], fv[3]));
+ geometry.faces.push( new THREE.Face3(fv[0], fv[2], fv[3]));
+ }
+ }
+
+ this._meshSurface = new THREE.Mesh( geometry, material );
+ this._dt.scene.add(this._meshSurface);
+}
+
+// Bar plot
+D3THREE.Bar = function(dt) {
+ this.init(dt);
+
+ this._nodeGroup = new THREE.Object3D();
+
+ this._config = {color: 0x4682B4, barSize: 5};
+}
+
+D3THREE.Bar.prototype = new D3THREE.Chart();
+
+D3THREE.Bar.prototype.onDocumentMouseMove = function(e) {
+ this.detectNodeHover(e);
+}
+
+D3THREE.Bar.prototype.render = function(threeData) {
+ /* render data points */
+ this._dt.scene.add(this._nodeGroup);
+
+ // x,y axis shift, so rotation is from center of screen
+ var xAxisShift = this._dt.axisObjects.x.getRotationShift(),
+ yAxisShift = this._dt.axisObjects.y.getRotationShift();
+
+ var self = this;
+ d3.select(this._nodeGroup)
+ .selectAll()
+ .data(threeData)
+ .enter().append( function(d) {
+ var height = self._dt.axisObjects.z._scale(d.z) + chartOffset;
+ var geometry = new THREE.BoxGeometry( self._config.barSize, height, self._config.barSize );
+ var material = new THREE.MeshBasicMaterial( {
+ color: self._config.color } );
+ var mesh = new THREE.Mesh( geometry, material );
+ mesh.userData = {x: d.x, y: d.y, z: d.z};
+ return mesh;
+ } )
+ .attr("position.z", function(d) {
+ return self._dt.axisObjects.x._scale(d.x) - xAxisShift;
+ })
+ .attr("position.x", function(d) {
+ return self._dt.axisObjects.y._scale(d.y) - yAxisShift;
+ })
+ .attr("position.y", function(d) {
+ var height = self._dt.axisObjects.z._scale(d.z) + chartOffset;
+ return height / 2;
+ });
+}
+
+// Force layout plot
+D3THREE.Force = function(dt) {
+ this.init(dt);
+
+ this._nodeGroup = new THREE.Object3D();
+
+ this._config = {color: 0x4682B4, linkColor: 0xcccccc, linkWidth: 1};
+}
+
+D3THREE.Force.prototype = new D3THREE.Chart();
+
+D3THREE.Force.prototype.onDocumentMouseMove = function(e) {
+}
+
+D3THREE.Force.prototype.render = function(threeData) {
+ var spheres = [], three_links = [];
+ // Define the 3d force
+ var force = d3.layout.force3d()
+ .nodes(sort_data=[])
+ .links(links=[])
+ .size([50, 50])
+ .gravity(0.3)
+ .charge(-400)
+
+ var DISTANCE = 1;
+
+ for (var i = 0; i < threeData.nodes.length; i++) {
+ sort_data.push({x:threeData.nodes.x + DISTANCE,y:threeData.nodes.y + DISTANCE,z:0})
+
+ // set up the sphere vars
+ var radius = 5,
+ segments = 16,
+ rings = 16;
+
+ // create the sphere's material
+ var sphereMaterial = new THREE.MeshLambertMaterial({ color: this._config.color });
+
+ var sphere = new THREE.Mesh(
+ new THREE.SphereGeometry(
+ radius,
+ segments,
+ rings),
+ sphereMaterial);
+
+ spheres.push(sphere);
+
+ // add the sphere to the scene
+ this._dt.scene.add(sphere);
+ }
+
+ for (var i = 0; i < threeData.links.length; i++) {
+ links.push({target:sort_data[threeData.links[i].target],source:sort_data[threeData.links[i].source]});
+
+ var material = new THREE.LineBasicMaterial({ color: this._config.linkColor,
+ linewidth: this._config.linkWidth});
+ var geometry = new THREE.Geometry();
+
+ geometry.vertices.push( new THREE.Vector3( 0, 0, 0 ) );
+ geometry.vertices.push( new THREE.Vector3( 0, 0, 0 ) );
+ var line = new THREE.Line( geometry, material );
+ line.userData = { source: threeData.links[i].source,
+ target: threeData.links[i].target };
+ three_links.push(line);
+ this._dt.scene.add(line);
+
+ force.start();
+ }
+
+ // set up the axes
+ var x = d3.scale.linear().domain([0, 350]).range([0, 10]),
+ y = d3.scale.linear().domain([0, 350]).range([0, 10]),
+ z = d3.scale.linear().domain([0, 350]).range([0, 10]);
+
+ var self = this;
+ force.on("tick", function(e) {
+ for (var i = 0; i < sort_data.length; i++) {
+ spheres[i].position.set(x(sort_data[i].x) * 40 - 40, y(sort_data[i].y) * 40 - 40,z(sort_data[i].z) * 40 - 40);
+
+ for (var j = 0; j < three_links.length; j++) {
+ var line = three_links[j];
+ var vi = -1;
+ if (line.userData.source === i) {
+ vi = 0;
+ }
+ if (line.userData.target === i) {
+ vi = 1;
+ }
+
+ if (vi >= 0) {
+ line.geometry.vertices[vi].x = x(sort_data[i].x) * 40 - 40;
+ line.geometry.vertices[vi].y = y(sort_data[i].y) * 40 - 40;
+ line.geometry.vertices[vi].z = y(sort_data[i].z) * 40 - 40;
+ line.geometry.verticesNeedUpdate = true;
+ }
+ }
+ }
+ });
+}