Core JavaScript

SVG - 3D Surface Plot with Lighting

This JavaScript Reference section displays the code for an example program that shows how to draw a 3d surface plot with lighting using scalable vector graphics (SVG).

Svg3dGraph.html

<!DOCTYPE html>
<html>
<head>
    <title>XoaX.net's Javascript</title>
</head>
<body>
    <script type="text/javascript" src="Svg3dGraph.js"></script>
</body>
</html>

Svg3dGraph.js

var qBody = document.getElementsByTagName("body")[0];

function CProjectedPoint(dX,dY,dZ) {
	var dScale = 250.0;
	var dDuDx = .612;
	var dDuDy = -dDuDx;
	var dDuDz = 0.0;
	var dDvDx = .25;
	var dDvDy = .25;
	var dDvDz = -.866;
	this.mdU = (dDuDx*dX + dDuDy*dY + dDuDz*dZ)*dScale;
	this.mdV = (dDvDx*dX + dDvDy*dY + dDvDz*dZ)*dScale;
}

var qMainDiv = document.createElementNS("http://www.w3.org/2000/svg", "svg")
qMainDiv.style.backgroundColor = "#eeeecc";
qMainDiv.style.width="800px";
qMainDiv.style.height="800px";
qMainDiv.style.position="absolute";

// Bottom x = [-1, 1], y = [-1, 1], z = -1
// scale
var qBottom = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
var cx = 400;
var cy = 400;
var qP1 = new CProjectedPoint(-1.0, -1.0, -1.0);
var qP2 = new CProjectedPoint(-1.0, 1.0, -1.0);
var qP3 = new CProjectedPoint(1.0, 1.0, -1.0);
var qP4 = new CProjectedPoint(1.0, -1.0, -1.0);
var dCoords = (qP1.mdU + cx) + " " + (qP1.mdV + cy) + " " +
				(qP2.mdU + cx) + " " + (qP2.mdV + cy) + " " +
				(qP3.mdU + cx) + " " + (qP3.mdV + cy) + " " +
				(qP4.mdU + cx) + " " + (qP4.mdV + cy);
qBottom.setAttribute('points', dCoords);
qBottom.setAttribute('fill', '#ffbbbb');
qBottom.setAttribute('fill-rule', 'nonzero');
qMainDiv.appendChild(qBottom);


// Left x = -1, y = [-1, 1], z = [-1, 1]
// scale
var qLeftSide = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
qP1 = new CProjectedPoint(-1.0, 1.0, 1.0);
qP2 = new CProjectedPoint(-1.0, 1.0, -1.0);
qP3 = new CProjectedPoint(-1.0, -1.0, -1.0);
qP4 = new CProjectedPoint(-1.0, -1.0, 1.0);
dCoords = (qP1.mdU + cx) + " " + (qP1.mdV + cy) + " " +
				(qP2.mdU + cx) + " " + (qP2.mdV + cy) + " " +
				(qP3.mdU + cx) + " " + (qP3.mdV + cy) + " " +
				(qP4.mdU + cx) + " " + (qP4.mdV + cy);
qLeftSide.setAttribute('points', dCoords);
qLeftSide.setAttribute('fill', '#ffeeee');
qLeftSide.setAttribute('fill-rule', 'nonzero');
qMainDiv.appendChild(qLeftSide);

// Right x = [-1, 1], y = -1, z = [-1, 1]
// scale
var qRightSide = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
qP1 = new CProjectedPoint(1.0, -1.0, 1.0);
qP2 = new CProjectedPoint(1.0, -1.0, -1.0);
qP3 = new CProjectedPoint(-1.0, -1.0, -1.0);
qP4 = new CProjectedPoint(-1.0, -1.0, 1.0);
dCoords = (qP1.mdU + cx) + " " + (qP1.mdV + cy) + " " +
				(qP2.mdU + cx) + " " + (qP2.mdV + cy) + " " +
				(qP3.mdU + cx) + " " + (qP3.mdV + cy) + " " +
				(qP4.mdU + cx) + " " + (qP4.mdV + cy);
qRightSide.setAttribute('points', dCoords);
qRightSide.setAttribute('fill', '#ffd8d8');
qRightSide.setAttribute('fill-rule', 'nonzero');
qMainDiv.appendChild(qRightSide);

// Border - hexagon around the graph
// scale
var qBorder = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
qP1 = new CProjectedPoint(-1.0, -1.0, 1.0);
qP2 = new CProjectedPoint(1.0, -1.0, 1.0);
qP3 = new CProjectedPoint(1.0, -1.0, -1.0);
qP4 = new CProjectedPoint(1.0, 1.0, -1.0);
var qP5 = new CProjectedPoint(-1.0, 1.0, -1.0);
var qP6 = new CProjectedPoint(-1.0, 1.0, 1.0);
dCoords = (qP1.mdU + cx) + " " + (qP1.mdV + cy) + " " +
				(qP2.mdU + cx) + " " + (qP2.mdV + cy) + " " +
				(qP3.mdU + cx) + " " + (qP3.mdV + cy) + " " +
				(qP4.mdU + cx) + " " + (qP4.mdV + cy) + " " +
				(qP5.mdU + cx) + " " + (qP5.mdV + cy) + " " +
				(qP6.mdU + cx) + " " + (qP6.mdV + cy);
qBorder.setAttribute('points', dCoords);
qBorder.setAttribute('fill', '#ffeedd');
qBorder.setAttribute('fill-rule', 'nonzero');
qBorder.setAttribute('stroke-width', '4');
qBorder.setAttribute('stroke', 'rgb(255, 128, 128)');
qMainDiv.appendChild(qBorder);

// Inner (background) Lines
var qLineZ = document.createElementNS("http://www.w3.org/2000/svg", "line");
qP1 = new CProjectedPoint(-1.0, -1.0, 1.0);
qP2 = new CProjectedPoint(-1.0, -1.0, -1.0);
qLineZ.setAttribute('x1', qP1.mdU + cx);
qLineZ.setAttribute('y1', qP1.mdV + cy);
qLineZ.setAttribute('x2', qP2.mdU + cx);
qLineZ.setAttribute('y2', qP2.mdV + cy);
qLineZ.setAttribute('stroke-width', '2');
qLineZ.setAttribute('stroke', 'rgb(255, 128, 128)');
qMainDiv.appendChild(qLineZ);

var qLineY = document.createElementNS("http://www.w3.org/2000/svg", "line");
qP1 = new CProjectedPoint(-1.0, 1.0, -1.0);
qP2 = new CProjectedPoint(-1.0, -1.0, -1.0);
qLineY.setAttribute('x1', qP1.mdU + cx);
qLineY.setAttribute('y1', qP1.mdV + cy);
qLineY.setAttribute('x2', qP2.mdU + cx);
qLineY.setAttribute('y2', qP2.mdV + cy);
qLineY.setAttribute('stroke-width', '2');
qLineY.setAttribute('stroke', 'rgb(255, 128, 128)');
qMainDiv.appendChild(qLineY);

var qLineX = document.createElementNS("http://www.w3.org/2000/svg", "line");
qP1 = new CProjectedPoint(1.0, -1.0, -1.0);
qP2 = new CProjectedPoint(-1.0, -1.0, -1.0);
qLineX.setAttribute('x1', qP1.mdU + cx);
qLineX.setAttribute('y1', qP1.mdV + cy);
qLineX.setAttribute('x2', qP2.mdU + cx);
qLineX.setAttribute('y2', qP2.mdV + cy);
qLineX.setAttribute('stroke-width', '2');
qLineX.setAttribute('stroke', 'rgb(255, 128, 128)');
qMainDiv.appendChild(qLineX);


var dDeltaX = 2.0/100.0;
var dDeltaY = 2.0/100.0;
var daaZ = new Array(101);
// Calculate the z values
for (var x = 0; x < 101; ++x) {
	daaZ[x] = new Array(101);
	for (var y = 0; y < 101; ++y) {
		var dX = -1 + x*dDeltaX;
		var dY = -1 + y*dDeltaY;
		var dZ = (dX*dX + dY*dY)*10;
		if (dZ < 1.0e-5)  {
			daaZ[x][y] = 1.0;
		} else {
			daaZ[x][y] = (Math.sin(3*dZ)/(3*dZ));
		}
	}
}

function Normalize(daV) {
	var dLength = Math.sqrt(daV[0]*daV[0] + daV[1]*daV[1] + daV[2]*daV[2]);
	daV[0] /= dLength;
	daV[1] /= dLength;
	daV[2] /= dLength;
}

function Cross(daV1, daV2) {
	var daN = new Array(3);
	daN[0] = daV1[1]*daV2[2] - daV1[2]*daV2[1];
	daN[1] = daV1[2]*daV2[0] - daV1[0]*daV2[2];
	daN[2] = daV1[0]*daV2[1] - daV1[1]*daV2[0];
	return daN;
}

// Compute the light reflectance on the triangle from above - take the z value
function Light(daaT) {
	var daaV = new Array(2);
	daaV[0] = new Array(3);
	daaV[1] = new Array(3);
	for (var k = 0; k < 3; ++k) {
		daaV[0][k] = daaT[1][k] - daaT[0][k];
		daaV[1][k] = daaT[2][k] - daaT[0][k];
	}
	// Normalize those vectors, which are the sides of the triangle
	Normalize(daaV[0]);
	Normalize(daaV[1]);
	// Take a Cross Product to get the normal vector
	var daN = Cross(daaV[0], daaV[1]);
	Normalize(daN);
	return Math.abs(daN[2]);
}

var qGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
var daaTriangle = new Array(3);
daaTriangle[0] = new Array(3);
daaTriangle[1] = new Array(3);
daaTriangle[2] = new Array(3);
var daaProjTri = new Array(3);
for (var i = 0; i < 100; ++i) {
	for (var j = 0; j < 100; ++j) {

		// Triangle 1
		daaTriangle[0][0] = -1 + i*dDeltaX;
		daaTriangle[0][1] = -1 + j*dDeltaY;
		daaTriangle[0][2] = daaZ[i][j];
		daaTriangle[1][0] = -1 + (i+1)*dDeltaX;
		daaTriangle[1][1] = -1 + j*dDeltaY;
		daaTriangle[1][2] = daaZ[i+1][j];
		daaTriangle[2][0] = -1 + i*dDeltaX;
		daaTriangle[2][1] = -1 + (j+1)*dDeltaY;
		daaTriangle[2][2] = daaZ[i][j+1];
		var qTriangle = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
		daaProjTri[0] = new CProjectedPoint(daaTriangle[0][0], daaTriangle[0][1], daaZ[i][j]);
		daaProjTri[1] = new CProjectedPoint(daaTriangle[1][0], daaTriangle[1][1], daaZ[i+1][j]);
		daaProjTri[2] = new CProjectedPoint(daaTriangle[2][0], daaTriangle[2][1], daaZ[i][j+1]);
		dCoords = (daaProjTri[0].mdU + cx) + " " + (daaProjTri[0].mdV + cy) + " " +
						(daaProjTri[1].mdU + cx) + " " + (daaProjTri[1].mdV + cy) + " " +
						(daaProjTri[2].mdU + cx) + " " + (daaProjTri[2].mdV + cy);
		qTriangle.setAttribute('points', dCoords);
		var dLight = Light(daaTriangle);
		var qColor = 'rgb(' + Math.floor(dLight*128 + 100) + ', ' +
			Math.floor(dLight*128 + 127.9) + ', ' +
			Math.floor(dLight*128 + 100) + ')';
		qTriangle.setAttribute('fill', qColor);
		qTriangle.setAttribute('fill-rule', 'nonzero');
		qTriangle.setAttribute('stroke-width', '1');
		qTriangle.setAttribute('stroke', qColor);
		qGroup.appendChild(qTriangle);

		// Triangle 2
		daaTriangle[0][0] = -1 + (i+1)*dDeltaX;
		daaTriangle[0][1] = -1 + (j+1)*dDeltaY;
		daaTriangle[0][2] = daaZ[i+1][j+1];
		qTriangle = document.createElementNS("http://www.w3.org/2000/svg", "polygon");
		daaProjTri[0] = new CProjectedPoint(daaTriangle[0][0], daaTriangle[0][1], daaZ[i+1][j+1]);
		dCoords = (daaProjTri[0].mdU + cx) + " " + (daaProjTri[0].mdV + cy) + " " +
					(daaProjTri[1].mdU + cx) + " " + (daaProjTri[1].mdV + cy) + " " +
					(daaProjTri[2].mdU + cx) + " " + (daaProjTri[2].mdV + cy);
		qTriangle.setAttribute('points', dCoords);
		dLight = Light(daaTriangle);
		qColor = 'rgb(' + Math.floor(dLight*128 + 100) + ', ' +
			Math.floor(dLight*128 + 127.9) + ', ' +
			Math.floor(dLight*128 + 100) + ')';
		qTriangle.setAttribute('fill', qColor);
		qTriangle.setAttribute('fill-rule', 'nonzero');
		qTriangle.setAttribute('stroke-width', '1');
		qTriangle.setAttribute('stroke', qColor);
		qGroup.appendChild(qTriangle);
	}
}

qMainDiv.appendChild(qGroup);

// Outer Lines
qLineZ = document.createElementNS("http://www.w3.org/2000/svg", "line");
qP1 = new CProjectedPoint(1.0, 1.0, 1.0);
qP2 = new CProjectedPoint(1.0, 1.0, -1.0);
qLineZ.setAttribute('x1', qP1.mdU + cx);
qLineZ.setAttribute('y1', qP1.mdV + cy);
qLineZ.setAttribute('x2', qP2.mdU + cx);
qLineZ.setAttribute('y2', qP2.mdV + cy);
qLineZ.setAttribute('stroke-width', '1');
qLineZ.setAttribute('stroke', 'rgb(255, 128, 128)');
qMainDiv.appendChild(qLineZ);

qLineY = document.createElementNS("http://www.w3.org/2000/svg", "line");
qP1 = new CProjectedPoint(1.0, 1.0, 1.0);
qP2 = new CProjectedPoint(1.0, -1.0, 1.0);
qLineY.setAttribute('x1', qP1.mdU + cx);
qLineY.setAttribute('y1', qP1.mdV + cy);
qLineY.setAttribute('x2', qP2.mdU + cx);
qLineY.setAttribute('y2', qP2.mdV + cy);
qLineY.setAttribute('stroke-width', '1');
qLineY.setAttribute('stroke', 'rgb(255, 128, 128)');
qMainDiv.appendChild(qLineY);

qLineX = document.createElementNS("http://www.w3.org/2000/svg", "line");
qP1 = new CProjectedPoint(1.0, 1.0, 1.0);
qP2 = new CProjectedPoint(-1.0, 1.0, 1.0);
qLineX.setAttribute('x1', qP1.mdU + cx);
qLineX.setAttribute('y1', qP1.mdV + cy);
qLineX.setAttribute('x2', qP2.mdU + cx);
qLineX.setAttribute('y2', qP2.mdV + cy);
qLineX.setAttribute('stroke-width', '1');
qLineX.setAttribute('stroke', 'rgb(255, 128, 128)');
qMainDiv.appendChild(qLineX);

qBody.appendChild(qMainDiv);
 

Output

 
 

© 2007–2024 XoaX.net LLC. All rights reserved.