6.102
6.102 — Software Construction
Spring 2025

Code Examples for Reading 17

Download

You can also download a ZIP file containing this code.

After downloading and unpacking the ZIP file:

  • npm install to install package dependencies;
  • npm run to see a list of the ways this code can be run.
    • For example, if npm run shows that there are scripts called foo and bar, you can run the code with either npm run foo or npm run bar.
    • Two conventional script names are test and start. If the code includes one of those script names, then you can omit run and just say npm test or npm start.
Seems to be necessary to have a triple-backtick block at the start of this page,
otherwise all the pre/code tags below aren't syntax highlighted.
So create a hidden one.

draw.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Draw!</title>

    <style>
        #canvas {
            border: 1px solid black;
        }
        div {
            margin: 5px;
        }
    </style>
</head>

<body>

    <div>
        Color: <input type="text" id="colorTextbox" value="green">
    </div>
    <div>
        Shape: <select id="shapeDropdown">
                    <option value="circle" selected>circle</option>
                    <option value="square">square</option>
                </select>
        <input type="checkbox" id="filledCheckbox" checked="true"> filled
    </div>
    <div>
        Size: <input type="number" id="sizeTextbox" value="10" min="1" max="50" maxlength="2"> 
              <input type="range" id="sizeSlider" value="10" min="1" max="50">
    </div>
    <div>
        <button id="drawRandomlyButton">Draw Randomly</button>
        <button id="clearButton">Clear</button>
    </div>

    <div>
        <canvas id="canvas" width="200" height="300">
        </canvas>
    </div>

    <script src="dist/draw-bundle.js"></script>
    <script>
        if ( ! document.body.classList.contains('codeLoaded')) {
            alert('You may need to "npm install" and "npm run compile" first. If you did that, look in the browser console for errors.')
        }
    </script>
</body>
</html>

draw.ts

/* Copyright (c) 2021-25 MIT 6.031/6.102 course staff, all rights reserved.
 * Redistribution of original or derived work requires permission of course staff.
 */

// this code is loaded into draw.html

/**
 * @param tag HTML element tag
 * @param id  id attribute
 * @returns   element whose tag is `tag` and id attribute is `id`
 * @throws    error if no such element
 */
function getElementById<Tag extends keyof HTMLElementTagNameMap>(tag: Tag, id: string): HTMLElementTagNameMap[Tag] {
    const element = document.querySelector<HTMLElementTagNameMap[Tag]>(`${tag}#${id}`);
    if (!element) { throw new Error(`no element ${tag}#${id} found`); }
    return element;
}

const drawRandomlyButton: HTMLButtonElement = getElementById('button', 'drawRandomlyButton');
const clearButton: HTMLButtonElement = getElementById('button', 'clearButton');

const canvasElement: HTMLCanvasElement = getElementById('canvas', 'canvas');

const colorTextbox: HTMLInputElement = getElementById('input', 'colorTextbox');

const shapeDropdown: HTMLSelectElement = getElementById('select', 'shapeDropdown');
const filledCheckbox: HTMLInputElement = getElementById('input', 'filledCheckbox');

const sizeTextbox: HTMLInputElement = getElementById('input', 'sizeTextbox');
const sizeSlider: HTMLInputElement = getElementById('input', 'sizeSlider');

canvasElement.addEventListener('click', (event:MouseEvent) => {
    drawShape(event.offsetX, event.offsetY);
});

drawRandomlyButton.addEventListener('click', () => {
    drawShape(canvasElement.width * Math.random(), canvasElement.height * Math.random());
    // for (let i = 0; i < 1_000_000; ++i) {
    //     drawShape(canvasElement.width * Math.random(), canvasElement.height * Math.random());
    // }
});

clearButton.addEventListener('click', clear);

// synchronize the size textbox and slider
sizeTextbox.addEventListener('input', () => {
    sizeSlider.value = sizeTextbox.value;
});
sizeSlider.addEventListener('input', () => {
    sizeTextbox.value = sizeSlider.value;
});


/**
 * @returns color shown in the color textbox
 */
function getColor():string {
    return colorTextbox.value; 
}

/**
 * @returns size shown in the size textbox
 */
 function getSize():number {
    return parseInt(sizeTextbox.value); 
}

/**
 * Draw a square filled with a random color.
 * @param x x position of center of box centers
 * @param y y position of center of box centers
 */
function drawShape(x: number, y: number):void {
    console.log(`canvas width: ${canvasElement.width}`);

    const canvas = canvasElement.getContext('2d');
    if ( ! canvas ) throw new Error('unable to get canvas drawing context');

    // save original context settings before we translate and change colors
    canvas.save();

    // move origin to (x,y)
    canvas.translate(x, y);

    // describe the shape, either a circle or a rectangle, centered on the origin
    const size = getSize();
    canvas.beginPath();
    if (shapeDropdown.value === 'circle') {
        canvas.arc(0, 0, size/2, 0, 2 * Math.PI);
    } else {
        canvas.rect(-size/2, -size/2, size, size)
    }

    // stroke the outline of the shape
    canvas.strokeStyle = getColor();
    canvas.lineWidth = 2;
    canvas.stroke();

    if (filledCheckbox.checked) {
        // fill with a color
        canvas.fillStyle = getColor();
        canvas.fill();
    }

    // reset the origin and styles back to defaults
    canvas.restore();
}

/**
 * Clear the drawing area, filling it with white again.
 */
function clear() {
    const context = canvasElement.getContext('2d');
    if ( ! context ) throw new Error('unable to get canvas drawing context');

    context.save();
    context.fillStyle = 'white';
    context.fillRect(0, 0, canvasElement.width, canvasElement.height);
    context.restore();
}

document.body.classList.add('codeLoaded'); // used to remind about compiling first