This JavaScript program demonstrates how to draw a graph of 2d level sets with anti-aliased subsampling. The magnitude of the gradient is used to measure the distance to a level value, combined with the pen width and the pixel magnitude.
<!DOCTYPE html>
<html>
<head>
<title>XoaX.net's Javascript</title>
<script type="text/javascript" src="AntialiasedLevelSetGraphs.js"></script>
</head>
<body onload="Initialize()">
<canvas id="idCanvas" width="600" height ="600" style="background-color: #F0F0F0;"></canvas>
</body>
</html>class Graph2D {
constructor(dLowX, dHighX, dLowY, dHighY, iCanvasW, iCanvasH) {
this.mdLowX = dLowX;
this.mdHighX = dHighX;
this.mdLowY = dLowY;
this.mdHighY = dHighY;
this.miCanvasW = iCanvasW;
this.miCanvasH = iCanvasH;
}
PixelW() {
return (this.mdHighX - this.mdLowX)/this.miCanvasW;
}
PixelH() {
return (this.mdHighY - this.mdLowY)/this.miCanvasH;
}
// Map the function a a level curve of the function z(x, y) = -y(x) + ...
async DrawGraphOver(qContext, fnF, fnGradF, iSamplesPerPixelX, iSamplesPerPixelY, dPenWidth, daLevels) {
var qImageData = qContext.createImageData(this.miCanvasW, this.miCanvasH);
const kdDxDp = this.PixelW();
const kdDyDp = this.PixelH();
let dMagPixel = Math.sqrt(kdDxDp*kdDxDp + kdDyDp*kdDyDp);
const kdDxDs = kdDxDp/iSamplesPerPixelX;
const kdDyDs = kdDyDp/iSamplesPerPixelY;
//const kdGraphZ = dLineDepth/2;
const kdSamplesPerPixel = iSamplesPerPixelX*iSamplesPerPixelY;
let iPixel = 0;
// Run over the y pixel values
for (let j = 0; j < this.miCanvasH; ++j) {
for (let i = 0; i < this.miCanvasW; ++i) {
// Get the position of the center of the pixel and subtract half of the samples distance.
let dPixelX = this.mdLowX + (i + .5)*kdDxDp - kdDxDs*(iSamplesPerPixelX - 1)/2;
let dPixelY = this.mdHighY - (j + .5)*kdDyDp - kdDyDs*(iSamplesPerPixelY - 1)/2;
let iCount = 0;
for (let iSx = 0; iSx < iSamplesPerPixelX; ++iSx) {
let dX = dPixelX + iSx*kdDxDs;
for (let iSy = 0; iSy < iSamplesPerPixelY; ++iSy) {
let dY = dPixelY + iSy*kdDyDs;
let dF = fnF(dX, dY);
let daGradF = fnGradF(dX, dY);
let dMagGradF = Math.sqrt(daGradF[0]*daGradF[0] + daGradF[1]*daGradF[1]);
let dCloseLevel = FindClosestLevel(dF, daLevels);//5*Math.round(dF/5);
// Check whether the function value is less than the magnitude of the gradient.
// This tells us if we reach zero within on unit.
// This is multiplied by the magnitude of the pixel in spatial unit to make the unit pixels
// Finally, this is multiple by the pen width in pixels to make the drawing the correct width.
if (Math.abs(dF - dCloseLevel) < dPenWidth*dMagPixel*dMagGradF) {
++iCount;
}
}
}
var uiPixelColor = Math.round(255*(1.0 - iCount/kdSamplesPerPixel));
qImageData.data[iPixel] = uiPixelColor;
qImageData.data[iPixel + 1] = uiPixelColor;
qImageData.data[iPixel + 2] = uiPixelColor;
qImageData.data[iPixel + 3] = 255 - uiPixelColor;
iPixel += 4;
}
}
// This version allows the image data to be alpha composited to allow the axes to show through
const qBitmap = await createImageBitmap(qImageData);
qContext.drawImage(qBitmap, 0, 0);
}
}
// z = (y - x)^2 + (1 - x)^2
function F(x, y) {
return (y - x)*(y - x) + (1 - x)*(1 - x);
}
function GradF(x, y) {
return [-2*(y - x) - 2*(1 - x), 2*(y - x)];
}
function FindClosestLevel(dZ, daLevels) {
let dClosestDistance = Math.abs(dZ - daLevels[0]);
for (let i = 1; i < daLevels.length; ++i) {
let dNextDistance = Math.abs(dZ - daLevels[i]);
if (dNextDistance < dClosestDistance) {
dClosestDistance = dNextDistance;
if (i == daLevels.length - 1) {
return daLevels[daLevels.length-1];
}
} else {
return daLevels[i-1];
}
}
return daLevels[0];
}
function Initialize() {
var qCanvas = document.getElementById("idCanvas");
var qContext2D = qCanvas.getContext("2d");
// Draw the axes
qContext2D.strokeStyle = "lightgray";
qContext2D.lineWidth = "1";
qContext2D.beginPath();
qContext2D.moveTo(0, qCanvas.height/2);
qContext2D.lineTo(qCanvas.width, qCanvas.height/2);
qContext2D.moveTo(qCanvas.width/2, 0);
qContext2D.lineTo(qCanvas.width/2, qCanvas.height);
qContext2D.stroke();
var qGraph2D = new Graph2D(-4, 4, -4, 4, qCanvas.width, qCanvas.height);
var daLevels = [0, .25, .5, 1, 2, 4, 8, 16, 32, 64];
qGraph2D.DrawGraphOver(qContext2D, F, GradF, 4, 4, .5, daLevels);
}
© 20072025 XoaX.net LLC. All rights reserved.