diff options
Diffstat (limited to 'ui/imports/lib/d3three.js')
-rw-r--r-- | ui/imports/lib/d3three.js | 789 |
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; + } + } + } + }); +} |