An Illustrative Coding Example

Here we bring a code example showing inheritance/delegation in an object hierarchy as well as an illustration of how to put bitmap images onto the canvas.

Example 11.8. A Page With Three Canvases
<!doctype html>
<html>
    <head>
        <meta charset='utf-8'/>
        <title id='title'></title>
        <style>
            canvas {
                margin: 1em;
            }
        </style>
        <script type='module' src='./ShapeExec.js'></script>
    </head>
    <body>
        <h1 id='h1'></h1>
        <canvas id='foo'></canvas>
        <canvas id='bar'></canvas>
        <canvas id='baz'></canvas>
    </body>
</html>

Click here!


Example 11.9. The Shape Hierarchy in Shape.js
'use strict';
/**
 * Shape object, parent
 */
export class Shape {
    constructor(cv, x, y, color) {
        this.ctx = cv.getContext('2d');
        this.x = x;
        this.y = y;
        this.color = color;
    }

    draw() {
        this.ctx.beginPath();
        this.ctx.moveTo(this.x, this.y);
        this.ctx.rect(this.x, this.y, this.width, this.height);
        this.ctx.closePath();

        this.ctx.lineWidth = 2;
        this.ctx.fillStyle = this.color;
        this.ctx.fill();
        this.ctx.stroke();
    }
};

export class Rect extends Shape {
    constructor(cv, x, y, width, height, color) {
        super(cv, x, y, color);
        this.width = width;
        this.height = height;
    }
};

export class Circle extends Shape {
    constructor(cv, x, y, r, startangle, endangle, color, dir) {
        super(cv, x, y, color);
        this.r = r;
        this.startangle = startangle;
        this.endangle = endangle;
        this.direct = dir;
    }

    draw() {
        this.ctx.beginPath();
        this.ctx.moveTo(this.x, this.y);
        this.ctx.arc(this.x, this.y, this.r, this.startangle, this.endangle, this.direct);
        this.ctx.closePath();

        this.ctx.lineWidth = 2;
        this.ctx.fillStyle = this.color;
        this.ctx.fill();
        this.ctx.stroke();
    }
};

export class Ellipse extends Shape {
    constructor(cv, x, y, r, startangle, endangle, color, dir, scx, scy, rot) {
        super(cv, x, y, color);
        this.r = r;
        this.startangle = startangle;
        this.endangle = endangle;
        this.direct = dir;
        this.scalex = scx;
        this.scaley = scy;
        this.rotate = rot;
    }

    draw() {
        this.ctx.save();
        this.ctx.scale(this.scalex, this.scaley);
        this.ctx.beginPath();
        this.ctx.moveTo(this.x, this.y);
        this.ctx.arc(this.x, this.y, this.r, this.startangle, this.endangle, this.direct);
        this.ctx.closePath();
        this.ctx.restore();

        this.ctx.rotate(this.rotate);
        this.ctx.lineWidth = 2;
        this.ctx.fillStyle = this.color;
        this.ctx.fill();
        this.ctx.stroke();
    }
};

Here you see

  • A shape object with a couple of properties: x,y coordinates, a color, and a canvas reference to the intended placing in the DOM.
  • A draw method as a default. Its intention is to draw rectangles assuming most shapes will be rectangles.
  • Next the file contains a rectangle object with only one method, init to set values of the rectangle object. Notice that it supplies values for width, and height. Its coordinates and color is given to the inititalization method of shape. After the definition of the object you see a statement linking this object to the prototype of Shape. Because the Shape object can draw rectangles we do not need a method for drawing here.
  • Then you see a Circle object, also linked to the prototype of Shape. Its initializing method provides values for the circle specific parameters, while the common values are send to the Shape constructor. Here we have a method draw method overriding the namesake in Shape. This is useful and standard OOP style when we work with inheritance. The rectangle object inherited the drawing method from shape, but drawing a circle requires special code, so this object has to override the standard drawing method.
  • The Ellipse object also inherits from Shape. Like Circle it has separate parameters giving values to its specific properties, coordinates and color passed on to Shape. Drawing an ellipse requires its own drawing method so again we override the one from Shape. It is also different from drawing a circle because we employ scaling and rotation. Both are activities we for perhaps obvious reasons do not need on circles.

Example 11.10. The Page Executive Code ShapeExec.js
'use strict';
import {Canvas} from './nmlCanvas.js';
import {Rect, Circle, Ellipse} from './Shape.js';
import {$} from './nQuery.js';

const cv1 = function(cv, color) {
    let myCanvas = new Canvas(cv, color);

    let c = new Circle(myCanvas, 75, 75, 50, Math.PI*1/4, Math.PI*7/4, 'red', false);
    c.draw();

    let r = new Rect(myCanvas, 150, 15, 110, 45, 'yellow');
    r.draw();

    r = new Rect(myCanvas, 150, 85, 110, 45, 'green');
    r.draw();
}

const cv2 = function(cv, color) {
    let myCanvas = new Canvas(cv, color);

    let c = new Ellipse(myCanvas, 75, 75, 50, Math.PI*1/4, Math.PI*7/4, 'red', false, 1.5, 1, 0);
    c.draw();

    let r = new Rect(myCanvas, 210, 15, 45, 45, 'teal');
    r.draw();

    r = new Rect(myCanvas, 210, 85, 45, 45, 'blue');
    r.draw();
}

const cv3 = function(cv, color) {
    let myCanvas = new Canvas(cv, color);

    let img1 = new Image();
    img1.src = './outline-tran.2.gif';          // spawns load of image from net
    img1.addEventListener('load', function() {  // wait till its here
        myCanvas.getContext().drawImage(img1, 10,10, 100, 100);   // then draw
    });

    let img2 = document.createElement('img');
    img2.src = './sit3-shine.7.gif';
    img2.addEventListener('load', function() {
        myCanvas.getContext().drawImage(img2, 200, 50, 100, 100);
    });
}

const doThis = function() {
    const T = document.createTextNode("JS Testing Inheritance + imageDraw");
    $('title').appendChild(T);
    $('h1').appendChild(T);

    cv1('foo', 'silver');
    cv2('bar', 'gray');
    cv3('baz', '#eee');
}

window.addEventListener('load', doThis);

Image Files on the Canvas

In the JavaScript of Shape.html we have included a third canvas illustrating the code needed for putting regular bitmapped graphic files onto the canvas. It is done by the drawImage() method of the canvas context. The only complication we face is that we need to wait for an asynchroneous download of the image file when it is referenced. This is the same behaviour as the browser shows when rendering web pages in general. To wait for the image we need to code a load event handler on the image and let that handle the actual drawing.