This JavaScript program demonstrates how to draw a 2d graph with anti-aliased subsampling by using the zero level set of an implicit function embedded in 3d space. The magnitude of the gradient is used to measure the distance to the zero level, combined with the pen width and the pixel magnitude.
<!DOCTYPE html> <html> <head> <title>XoaX.net's Javascript</title> <script type="text/javascript" src="AntialiasingViaImplicitFunctions.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) { var qImageData = qContext.createImageData(this.miCanvasW, this.miCanvasH); const kdDxDp = this.PixelW(); const kdDyDp = this.PixelH(); 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 dMagPixel = Math.sqrt(kdDxDp*kdDxDp + kdDyDp*kdDyDp); // 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) < 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; } } // Use the image data to draw the pixels at (0, 0) //qContext.putImageData(qImageData, 0, 0); // 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); } } function F(x, y) { return x*x - y; } function GradF(x, y) { return [2*x, -1]; } 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); qGraph2D.DrawGraphOver(qContext2D, F, GradF, 4, 4, .5); }
© 20072025 XoaX.net LLC. All rights reserved.