More resources
- Mozilla's canvas tag tutorial.
- JavaScript: The Definitive Guide: the best tutorial/reference I know of.
- The official spec for the canvas element.
Why?
I've wanted to play with the canvas tag for a while now, and I finally have the perfect confluence of excuses to do so: I want to build an HTML-based rendering system for the output of my static analyzers; I'm introducing three engineering scholars to programming languages with graphics in JavaScript; and I'm building a super-optimizing compilation engine for dynamic languages like Scheme and JavaScript. Building a system for rendering univariate functions seems like a fun way to tip-toe into the canvas tag; below, you'll find an explanation of the system with a working demo.
Setting up the canvas
Setting up a canvas is straightforward; just drop in a canvas tag:
<canvas id="xy-graph" width="535" height="300"> CANVAS NOT SUPPORTED IN THIS BROWSER! </canvas>
The body of the canvas tag is what will be shown if the browser does not support the canvas tag.
Accessing the canvas
To draw on the canvas, grab ahold of the element itself:
var Canvas = document.getElementById('xy-graph');
And then grab its context:
if (Canvas.getContext) {
Ctx = Canvas.getContext('2d');
// Rendering code goes here.
} else {
// Canvas not supported code goes here.
}
To draw on the canvas, call methods on the Ctx object.
Preparing the canvas
To prepare the canvas for drawing, it's best to clear what's already on the canvas with the clearRect(startX,startY,finishX,finishY) method:
Ctx.clearRect(0,0,Canvas.width,Canvas.height) ;
Drawing lines
For graphing functions, we only need to draw lines, and we can do that with only four canvas context functions:
-
Ctx.beginPath()starts recording a new sequence of line segments. -
Ctx.moveTo(x,y)moves the pen to the coordinates(x,y). -
Ctx.lineTo(x,y)drags the pen to the coordinates(x,y). -
Ctx.stroke()connects all of the points in the path described.
Demo application: Graphing a function
Type JavaScript code in below that defines a function named F, and the click
Draw! button further down.
The full, well-commented source code to this app is about 200 lines, and it's available down below.
Source code
/* Initialization */
// To be called when the page finishes loading:
function init() {
Draw() ;
}
/* Canvas and context objects */
var Canvas = document.getElementById('xy-graph');
var Ctx = null ;
var Width = Canvas.width ;
var Height = Canvas.height ;
/*
The origin (0,0) of the canvas is the upper left:
(0,0)
--------- +X
|
|
|
|
+Y
Positive x coordinates go to the right, and positive y coordinates go down.
The origin in mathematics is the "center," and positive y goes *up*.
We'll refer to the mathematics coordinate system as the "logical"
coordinate system, and the coordinate system for the canvas as the
"physical" coordinate system.
The functions just below set up a mapping between the two coordinate
systems.
They're defined as functions, so that one wanted to, they could read
ther values from a from instead of having them hard-coded.
*/
// Returns the right boundary of the logical viewport:
function MaxX() {
return 10 ;
}
// Returns the left boundary of the logical viewport:
function MinX() {
return -10 ;
}
// Returns the top boundary of the logical viewport:
function MaxY() {
return MaxX() * Height / Width;
}
// Returns the bottom boundary of the logical viewport:
function MinY() {
return MinX() * Height / Width;
}
// Returns the physical x-coordinate of a logical x-coordinate:
function XC(x) {
return (x - MinX()) / (MaxX() - MinX()) * Width ;
}
// Returns the physical y-coordinate of a logical y-coordinate:
function YC(y) {
return Height - (y - MinY()) / (MaxY() - MinY()) * Height ;
}
/* Rendering functions */
// Clears the canvas, draws the axes and graphs the function F.
function Draw() {
// Evaluate the user-supplied code, which must bind a value to F.
eval(document.getElementById('function-code').value) ;
if (Canvas.getContext) {
// Set up the canvas:
Ctx = Canvas.getContext('2d');
Ctx.clearRect(0,0,Width,Height) ;
// Draw:
DrawAxes() ;
RenderFunction(F) ;
} else {
// Do nothing.
}
}
// Returns the distance between ticks on the X axis:
function XTickDelta() {
return 1 ;
}
// Returns the distance between ticks on the Y axis:
function YTickDelta() {
return 1 ;
}
// DrawAxes draws the X ad Y axes, with tick marks.
function DrawAxes() {
Ctx.save() ;
Ctx.lineWidth = 2 ;
// +Y axis
Ctx.beginPath() ;
Ctx.moveTo(XC(0),YC(0)) ;
Ctx.lineTo(XC(0),YC(MaxY())) ;
Ctx.stroke() ;
// -Y axis
Ctx.beginPath() ;
Ctx.moveTo(XC(0),YC(0)) ;
Ctx.lineTo(XC(0),YC(MinY())) ;
Ctx.stroke() ;
// Y axis tick marks
var delta = YTickDelta() ;
for (var i = 1; (i * delta) < MaxY() ; ++i) {
Ctx.beginPath() ;
Ctx.moveTo(XC(0) - 5,YC(i * delta)) ;
Ctx.lineTo(XC(0) + 5,YC(i * delta)) ;
Ctx.stroke() ;
}
var delta = YTickDelta() ;
for (var i = 1; (i * delta) > MinY() ; --i) {
Ctx.beginPath() ;
Ctx.moveTo(XC(0) - 5,YC(i * delta)) ;
Ctx.lineTo(XC(0) + 5,YC(i * delta)) ;
Ctx.stroke() ;
}
// +X axis
Ctx.beginPath() ;
Ctx.moveTo(XC(0),YC(0)) ;
Ctx.lineTo(XC(MaxX()),YC(0)) ;
Ctx.stroke() ;
// -X axis
Ctx.beginPath() ;
Ctx.moveTo(XC(0),YC(0)) ;
Ctx.lineTo(XC(MinX()),YC(0)) ;
Ctx.stroke() ;
// X tick marks
var delta = XTickDelta() ;
for (var i = 1; (i * delta) < MaxX() ; ++i) {
Ctx.beginPath() ;
Ctx.moveTo(XC(i * delta),YC(0)-5) ;
Ctx.lineTo(XC(i * delta),YC(0)+5) ;
Ctx.stroke() ;
}
var delta = XTickDelta() ;
for (var i = 1; (i * delta) > MinX() ; --i) {
Ctx.beginPath() ;
Ctx.moveTo(XC(i * delta),YC(0)-5) ;
Ctx.lineTo(XC(i * delta),YC(0)+5) ;
Ctx.stroke() ;
}
Ctx.restore() ;
}
// When rendering, XSTEP determines the horizontal distance between points:
var XSTEP = (MaxX()-MinX())/Width ;
// RenderFunction(f) renders the input funtion f on the canvas.
function RenderFunction(f) {
var first = true;
Ctx.beginPath() ;
for (var x = MinX(); x <= MaxX(); x += XSTEP) {
var y = f(x) ;
if (first) {
Ctx.moveTo(XC(x),YC(y)) ;
first = false ;
} else {
Ctx.lineTo(XC(x),YC(y)) ;
}
}
Ctx.stroke() ;
}
Twitter: @mattmight
Instagram: @mattmight
LinkedIn: matthewmight
Mastodon: @mattmight@mathstodon.xyz
Sub-reddit: /r/mattmight