Skip to content

Instantly share code, notes, and snippets.

@adrianturcato
Last active November 13, 2017 19:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save adrianturcato/cf665b7cca9f6057691a to your computer and use it in GitHub Desktop.
Save adrianturcato/cf665b7cca9f6057691a to your computer and use it in GitHub Desktop.
3D Vector Field in D3.js
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body{
font-family: sans;
padding: 10px;
}
svg path{
stroke: #000;
stroke-width: 1px;
stroke: rgba(0,0,0,0.2);
}
svg{
width: 700px;
height: 400px;
}
</style>
</head>
<form>
Yaw: <input id="yawval" type="text" placeholder="Yaw" required>
Pitch: <input id="pitchval" type="text" placeholder="Pitch" required>
<input type="button" onclick="recalc()" value="Calc">
</form>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="vectorfield3d.js"></script>
<script>
var yaw=0.5,pitch=0.5, width=800, height=200, drag=false;
var gata = [de()];
function de(){
var output=[]
,xlength = 10
,ylength = 10
,zlength = 10;
for(var x = 0; x < xlength; x++){
for(var y = 0; y < ylength; y++){
for(var z = 0; z < zlength; z++){
output.push({
x: x
,xl: xlength
,y: y
,yl: ylength
,z: z * 20
,zl: zlength
,dx:0
,dy:0
,h: (y / 10 / (z + 1)) + 2
,theta: 1.5 * z / (y + 1)
,psi: 3.14 * y / (x + 1)
,depth:0
});
}
}
}
return output;
}
var svg = d3.select('body')
.append('svg')
.attr('height',height)
.attr('width',width)
.attr("transform", "translate(0," + 150 + ")");
var md = svg.selectAll("g")
.data(gata)
.enter().append("g")
.surface3D(width,height-300)
.surfaceHeight(function(d){
return d;
}).surfaceColor(function(d){
var c=d3.hsl((d+100), 0.6, 0.5).rgb();
return "rgb("+parseInt(c.r)+","+parseInt(c.g)+","+parseInt(c.b)+")";
});
svg.on("mousedown",function(){
drag = [d3.mouse(this), yaw, pitch];
})
.on("mouseup", function(){
drag = false;
}).on("mousemove", function(){
if(drag){
var mouse = d3.mouse(this);
yaw = drag[1] - (mouse[0] - drag[0][0]) / 50;
pitch = drag[2] + (mouse[1] - drag[0][1]) / 50;
pitch = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, pitch));
var pd = Math.round(pitch * 100) / 100
,yd = Math.round(yaw * 100) / 100;
document.getElementById("yawval").value = yd;
document.getElementById("pitchval").value = pd;
md.turntable(yaw, pitch);
}
});
function recalc(){
var y = document.getElementById("yawval").value
,p = document.getElementById("pitchval").value;
if(y && p) md.turntable(y, p);
}
</script>
</body>
</html>
(function(){
var Surface = function(node){
var heightFunction
,colorFunction
,timer
,timer
,transformPrecalc = [];
var displayWidth = 800
,displayHeight = 0
,zoom = 1;
var trans;
var ad = [{
x: 00
,xl: 10
,y: 0
,yl: 10
,z: 0
,zl: 10
,tx:0
,ty:0
,tz:0
,dx:0
,dy:0
,depth:0
},{
x: 0
,xl: 10
,y: 9
,yl: 10
,z: 0
,zl: 10
,dx:0
,dy:0
,depth:0
},{
x: 0
,xl: 10
,y: 0
,yl: 10
,z: 180
,zl: 10
,dx:0
,dy:0
,depth:0
},{
x: 9
,xl: 10
,y: 0
,yl: 10
,z: 0
,zl: 10
,dx:0
,dy:0
,depth:0
}];
this.setZoom = function(zoomLevel){
zoom = zoomLevel;
if(timer) clearTimeout(timer);
timer = setTimeout(renderSurface);
};
var mindepth = 1e6
,maxdepth = 0;
var getTransformedData = function(data){
var dl = data.length;
if(!heightFunction) return [[]];
for(var i = 0; i < dl; i++){
var a = (data[i].x - data[i].xl / 2) / (data[i].xl * 1.41) * displayWidth * zoom
,b = data[i].z * zoom
,c = (data[i].y - data[i].yl / 2) / (data[i].yl * 1.41) * displayWidth * zoom
,tx = transformPrecalc[0] * a + transformPrecalc[1] * b + transformPrecalc[2] * c
,ty = transformPrecalc[3] * a + transformPrecalc[4] * b + transformPrecalc[5] * c
,tz = transformPrecalc[6] * a + transformPrecalc[7] * b + transformPrecalc[8] * c;
data[i].dx = (tx + displayWidth / 2).toFixed(10);
data[i].dy = (ty + displayHeight / 2).toFixed(10);
var x2 = data[i].x + (data[i].h * Math.sin(data[i].psi) * Math.cos(data[i].theta))
,y2 = data[i].y + (data[i].h * Math.sin(data[i].psi) * Math.sin(data[i].theta))
,z2 = data[i].z + (data[i].h * Math.cos(data[i].psi))
,a2 = (x2 - data[i].xl / 2) / (data[i].xl * 1.41) * displayWidth * zoom
,b2 = z2 * zoom
,c2 = (y2 - data[i].yl / 2) / (data[i].yl * 1.41) * displayWidth * zoom
,tx2 = transformPrecalc[0] * a2 + transformPrecalc[1] * b2 + transformPrecalc[2] * c2
,ty2 = transformPrecalc[3] * a2 + transformPrecalc[4] * b2 + transformPrecalc[5] * c2
,tz2 = transformPrecalc[6] * a2 + transformPrecalc[7] * b2 + transformPrecalc[8] * c2;
data[i].dx2 = (tx2 + displayWidth / 2).toFixed(10);
data[i].dy2 = (ty2 + displayHeight / 2).toFixed(10);
var depth = tz + findtz(data[i].x + 1, data[i].y) + findtz(data[i].x + 1, data[i].y + 1) + findtz(data[i].x, data[i].y + 1);
data[i].depth = depth;
if(depth > maxdepth) maxdepth = depth;
if(depth < mindepth) mindepth = depth;
}
return data;
};
function findtz (x,y) {
var data = node.datum()
,dl = data.length
,tz = 0;
for(var i = 0; i < dl; i++){
if(data[i].x === x && data[i].y === y){
var a = (data[i].x - data[i].xl / 2) / (data[i].xl * 1.41) * displayWidth * zoom
,b = data[i].z * zoom
,c = (data[i].y - data[i].yl / 2) / (data[i].yl * 1.41) * displayWidth * zoom
tz = transformPrecalc[6] * a + transformPrecalc[7] * b + transformPrecalc[8] * c;
}
}
return tz;
}
var renderSurface = function(){
var od = node.datum()
,data = getTransformedData(od)
,axisdata = getTransformedData(ad);
data.sort(function(a, b){return b.depth - a.depth});
var opacityscale = d3.scale.linear()
.domain([mindepth, maxdepth])
.range([1,1e-6]);
//axis
var daxis = []
,al = axisdata.length;
for(i = 1; i < al; i++){
daxis.push({
path: 'M'+ axisdata[0].dx + ',' + axisdata[0].dy + 'L' + axisdata[i].dx + ',' + axisdata[i].dy
});
}
var axis = node.selectAll('path').data(daxis);
axis.enter().append("path");
if(trans){
axis = axis.transition().delay(trans.delay()).duration(trans.duration());
}
axis.attr("d",function(d){return d.path;});
trans = false;
//vector lines
var v = node.selectAll("line").data(data);
v.enter().append("line");
v.attr("x1", function(d){return d.dx;})
.attr("x2", function(d){return d.dx2;})
.attr("y1", function(d){return d.dy;})
.attr("y2", function(d){return d.dy2;})
.attr("stroke-width", 3);
if(colorFunction){
v.attr("stroke", function(d){return colorFunction(d.z)})
.attr("stroke-opacity", function(d){return opacityscale(d.depth);});
}
};
this.renderSurface = renderSurface;
this.setTurtable = function(yaw, pitch){
var cosA = Math.cos(pitch);
var sinA = Math.sin(pitch);
var cosB = Math.cos(yaw);
var sinB = Math.sin(yaw);
transformPrecalc[0] = cosB;
transformPrecalc[1] = 0;
transformPrecalc[2] = sinB;
transformPrecalc[3] = sinA * sinB;
transformPrecalc[4] = cosA;
transformPrecalc[5] = -sinA * cosB;
transformPrecalc[6] = -sinB * cosA;
transformPrecalc[7] = sinA;
transformPrecalc[8] = cosA * cosB;
if(timer) clearTimeout(timer);
timer = setTimeout(renderSurface);
return this;
};
this.setTurtable(2.0, 0.1);
this.surfaceColor = function(callback){
colorFunction = callback;
if(timer) clearTimeout(timer);
timer = setTimeout(renderSurface);
return this;
};
this.surfaceHeight = function(callback){
heightFunction = callback;
if(timer) clearTimeout(timer);
timer = setTimeout(renderSurface);
return this;
};
this.transition = function(){
var transition = d3.selection.prototype.transition.bind(node)();
colourFunction = null;
heightFunction = null;
transition.surfaceHeight = this.surfaceHeight;
transition.surfaceColor = this.surfaceColor;
trans = transition;
return transition;
};
this.setHeight = function(height){
if(height) displayHeight = height;
};
this.setWidth = function(width){
if(width) displayWidth = width;
};
};
d3.selection.prototype.surface3D = function(width, height){
if(!this.node().__surface__) this.node().__surface__=new Surface(this);
var surface = this.node().__surface__;
this.turntable = surface.setTurtable;
this.surfaceColor = surface.surfaceColor;
this.surfaceHeight = surface.surfaceHeight;
this.zoom = surface.setZoom;
surface.setHeight(height);
surface.setWidth(width);
this.transition = surface.transition.bind(surface);
return this;
};
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment