diff --git a/draw.js b/draw.js index 182d23e..eba5b99 100644 --- a/draw.js +++ b/draw.js @@ -1,19 +1,18 @@ class Drawing { - constructor() { - const div = document.createElement('div'); - const buttonsDiv = document.createElement('div'); - this.startButton = document.createElement('button'); - this.startButton.onclick = () => this.start(); - this.startButton.innerHTML = "Start"; - this.stopButton = document.createElement('button'); - this.stopButton.onclick = () => this.stop(); - this.stopButton.innerHTML = "Stop"; + constructor(divId) { + this.div = document.getElementById(divId); + this.div.classList.add('drawing'); + this.div.classList.add('container'); + this.buttonsDiv = document.createElement('div'); + this.titleDiv = document.createElement('div'); + this.titleDiv.classList.add('title'); + this.captionDiv = document.createElement('div'); + this.captionDiv.classList.add('caption'); this.canvas = document.createElement('canvas'); - buttonsDiv.appendChild(this.startButton); - buttonsDiv.appendChild(this.stopButton); - div.appendChild(buttonsDiv); - div.appendChild(this.canvas); - document.body.appendChild(div); + this.div.appendChild(this.titleDiv); + this.div.appendChild(this.buttonsDiv); + this.div.appendChild(this.canvas); + this.div.appendChild(this.captionDiv); this.ctx = this.canvas.getContext('2d'); this.sequence = []; this.t = 0; @@ -21,34 +20,128 @@ class Drawing { this.dt = 0; this.points = {}; this.stopped = true; - this.frame = [[-50, -50], [150, 150]]; + this.frame = [[-10, -10], [110, 110]]; + this.frameMargin = 10; this.scale = 1.0; - this.canvas.width = (this.frame[1][0] - this.frame[0][0]) * this.scale; - this.canvas.height = (this.frame[1][1] - this.frame[0][1]) * this.scale; this.speed = 1.0; + this.rendered = false; + this.updateCanvasSize(); + } + + addButtons() { + if (!this.buttonsAdded) { + this.startButton = document.createElement('button'); + this.startButton.onclick = () => this.start(); + this.startButton.innerHTML = "Start"; + this.stopButton = document.createElement('button'); + this.stopButton.onclick = () => this.stop(); + this.stopButton.innerHTML = "Stop"; + this.buttonsDiv.appendChild(this.startButton); + this.buttonsDiv.appendChild(this.stopButton); + this.buttonsAdded = true; + this.updateButtonStates(); + } + } + + updateButtonStates() { + if (this.stopped) { + this.stopButton.disabled = true; + this.startButton.disabled = false; + } else { + this.stopButton.disabled = false; + this.startButton.disabled = true; + } + } + + setTitle(title) { + this.titleDiv.innerHTML = title; + } + + setCaption(caption) { + this.captionDiv.innerHTML = caption; + } + + start() { + if (this.stopped) { + this.elideInterval = true; + this.stopped = false; + } + if (this.onStartFn) { + this.onStartFn(); + } + this.addButtons(); + this.updateButtonStates(); + this.animate(); + } + + stop() { + this.stopped = true; + this.updateButtonStates(); + } + + render() { + for (let action of this.sequence) { + action(); + } + this.rendered = true; + } + + animate() { + this.ctx.reset(); + this.render(); + if (!this.stopped) { + requestAnimationFrame((prevt) => { + const rt = document.timeline.currentTime; + const elapsed = rt - prevt; + if (this.elideInterval) { + this.dt = 0; + this.elideInterval = false; + } else { + this.dt = (rt - this.rt + elapsed) * this.speed; + } + this.t += this.dt; + this.rt = rt; + this.animate(); + }); + } } pixel([x, y]) { return [ - (x - this.frame[0][0]) * this.scale, - this.canvas.height - (y - this.frame[0][1]) * this.scale + (x - this.frame[0][0]) * this.scale + this.frameMargin, + this.canvas.height - (y - this.frame[0][1]) * this.scale - this.frameMargin ]; } + + onStart(fn) { + this.onStartFn = fn; + } setSpeed(speed) { this.speed = speed; } + updateCanvasSize() { + this.canvas.width = (this.frame[1][0] - this.frame[0][0]) * this.scale + 2 * this.frameMargin; + this.canvas.height = (this.frame[1][1] - this.frame[0][1]) * this.scale + 2 * this.frameMargin; + if (this.rendered) { + this.render(); + } + } + setFrame([x1, y1], [x2, y2]) { this.frame = [[x1, y1], [x2, y2]]; - this.canvas.width = (this.frame[1][0] - this.frame[0][0]) * this.scale; - this.canvas.height = (this.frame[1][1] - this.frame[0][1]) * this.scale; + this.updateCanvasSize(); + } + + setFrameMargin(frameMargin) { + this.frameMargin = frameMargin; + this.updateCanvasSize(); } setScale(zoom) { this.scale = zoom; - this.canvas.width = (this.frame[1][0] - this.frame[0][0]) * this.scale; - this.canvas.height = (this.frame[1][1] - this.frame[0][1]) * this.scale; + this.updateCanvasSize(); } setStroke(style, width) { @@ -64,6 +157,24 @@ class Drawing { }); } + definePoint(name, fn) { + this.points[name] = fn; + } + + getPoint(p) { + if (typeof p === 'string') { + const fn = this.points[p]; + if (!fn) { + const e = new Error; + e.message = `Point '${p}' is not defined`; + throw e; + } + return fn(); + } else { + return p; + } + } + line(p1, p2) { this.sequence.push(() => { this.ctx.beginPath(); @@ -72,6 +183,15 @@ class Drawing { this.ctx.stroke(); }); } + + arrow(p1, p2) { + this.sequence.push(() => { + this.ctx.beginPath(); + this.ctx.moveTo(...this.pixel(this.getPoint(p1))); + this.ctx.lineTo(...this.pixel(this.getPoint(p2))); + this.ctx.stroke(); + }); + } polyline(...points) { this.sequence.push(() => { @@ -115,7 +235,7 @@ class Drawing { if (oldest >= 0) { history = history.slice(oldest); } - this.ctx.strokeStyle = 'black'; + this.ctx.strokeStyle = 'gray'; this.ctx.lineWidth = 1; this.ctx.beginPath(); this.ctx.moveTo(...this.pixel(history[0].p)); @@ -128,78 +248,43 @@ class Drawing { } this.ctx.stroke(); fn(x, y); - }) + }); + return { + reset: () => { + history = []; + distance = 0; + } + }; } square(p, opts) { const size = opts?.size ?? 10; - this.object(p, opts, (x, y) => { + return this.object(p, opts, (x, y) => { this.ctx.fillRect(...this.pixel([x - size / 2, y + size / 2]), size * this.scale, size * this.scale); }); } circle(p, opts) { const radius = opts?.radius ?? 5; - this.object(p, opts, (x, y) => { + return this.object(p, opts, (x, y) => { this.ctx.beginPath(); this.ctx.ellipse(...this.pixel([x, y]), radius * this.scale, radius * this.scale, 0, 0, 2*Math.PI); this.ctx.fill(); }); } - definePoint(name, fn) { - this.points[name] = fn; - } - - getPoint(p) { - if (typeof p === 'string') { - const fn = this.points[p]; - if (!fn) { - const e = new Error; - e.message = `Point '${p}' is not defined`; - throw e; + func(opts, fn) { + const origin = opts?.origin ?? [0, 0]; + const domain = opts?.domain ?? [this.frame[0][0], this.frame[1][0]]; + const step = opts?.step ?? 1; + this.sequence.push(() => { + this.ctx.beginPath(); + this.ctx.moveTo(...this.pixel([domain[0], fn(domain[0])])); + for (let x = domain[0] + step; x <= domain[1]; x += step) { + this.ctx.lineTo(...this.pixel([x, fn(x)])); } - return fn(); - } else { - return p; - } - } - - render() { - for (let action of this.sequence) { - action(); - } - } - - animate() { - this.ctx.reset(); - this.render(); - if (!this.stopped) { - requestAnimationFrame((prevt) => { - const rt = document.timeline.currentTime; - const elapsed = rt - prevt; - if (this.elideInterval) { - this.dt = 0; - this.elideInterval = false; - } else { - this.dt = (rt - this.rt + elapsed) * this.speed; - } - this.t += this.dt; - this.rt = rt; - this.animate(); - }); - } - } - - stop() { - this.stopped = true; - } - - start() { - if (this.stopped) { - this.elideInterval = true; - this.stopped = false; - } - this.animate(); + this.ctx.lineTo(...this.pixel([domain[1], fn(domain[1])])); + this.ctx.stroke(); + }) } } \ No newline at end of file diff --git a/main.css b/main.css index 85c401d..fc32c2c 100644 --- a/main.css +++ b/main.css @@ -1,6 +1,27 @@ body { - margin: 50px; + margin: 1em; } + canvas { - border: 1px red dashed; + /* border: 1px red dashed; */ + margin: 0.5em; +} + +button { + margin: 0.25em; +} + +.drawing.container { + /* border: 1px black solid; */ + display: inline-block; + margin: 1em; +} + +.drawing > .title { + font-size: large; +} + +.drawing > .caption { + font-size: small; + text-align: center; } \ No newline at end of file diff --git a/test.html b/test.html index b6154a6..bee8324 100644 --- a/test.html +++ b/test.html @@ -8,23 +8,119 @@ +
+

+ Some body text +

+
+
+
+
+ \ No newline at end of file