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.