-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathplot.html
122 lines (121 loc) · 3.74 KB
/
plot.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>🌱 plotting functions</title>
<style>
body {
background-color: #222;
max-width: 500px;
margin: auto;
font-family: serif;
font-size: 1.2em;
color: #edd;
text-align: left;
padding: 20px 8px;
}
@font-face {
font-family: "FontWithASyntaxHighlighter";
src: url("/fonts/FontWithASyntaxHighlighter-Regular.woff2")
format("woff2");
}
a {
color: cyan;
font-size: 1em;
}
canvas {
width: 500px;
max-width: 100%;
}
textarea,
pre {
font-family: "FontWithASyntaxHighlighter", monospace;
padding: 8px;
font-size: 12px;
border: 0;
overflow: auto;
outline: none;
background-color: #44444490;
color: white;
width: 100%;
box-sizing: border-box;
}
</style>
</head>
<body>
<h2>🌱 plotting functions</h2>
<p>how to plot functions with the bare minimum?</p>
<canvas id="plot1"></canvas>
<textarea id="input" type="text" rows="4"></textarea>
<details>
<summary>show page source</summary>
<pre id="pre"></pre>
</details>
<br />
<a href="/">back to garten.salat</a>
<script>
const canvas = document.querySelector("#plot1");
// make sure canvas is sharp
canvas.style.width = canvas.width;
canvas.style.height = canvas.height;
canvas.width *= window.devicePixelRatio;
canvas.height *= window.devicePixelRatio;
// define plot ranges
const xrange = [-Math.PI * 2, Math.PI * 2];
const yrange = [-1, 1];
// live coding
const input = document.querySelector("#input");
const onPlot = () => {
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
const lines = input.value.split("\n");
const colors = ["cyan", "magenta", "yellow", "white"];
for (let i in lines) {
const fn = new Function("x", `return ${lines[i]}`);
plot(fn, canvas, xrange, yrange, colors[i % colors.length]);
}
};
input.value = "Math.sin(x)\nMath.cos(x)"; // initial value
onPlot(); // initial render
input.addEventListener("input", onPlot); // rerender on change
// plot function
function plot(
fn,
canvas,
xrange = [-1, 1],
yrange = [-1, 1],
color = "black",
lineWidth = 4
) {
// these 3 functions are very good to know..
const lerp = (v, min, max) => v * (max - min) + min;
const invLerp = (v, min, max) => (v - min) / (max - min);
const remap = (v, vmin, vmax, omin, omax) =>
lerp(invLerp(v, vmin, vmax), omin, omax);
// prepare draw context
const ctx = canvas.getContext("2d");
ctx.lineWidth = lineWidth;
ctx.strokeStyle = color;
// function ranges
const [x0, x1] = xrange;
const [y0, y1] = yrange;
// draw ranges
const [px0, px1] = [0, canvas.width];
const [py0, py1] = [canvas.height - ctx.lineWidth, ctx.lineWidth];
// actual draw logic
ctx.beginPath();
for (let px = 0; px < canvas.width; px++) {
const x = remap(px, px0, px1, x0, x1);
const y = fn(x);
const py = remap(y, y0, y1, py0, py1);
px === 0 ? ctx.moveTo(px, py) : ctx.lineTo(px, py);
}
ctx.stroke();
}
// render code
const code = document.querySelector("html").outerHTML;
document.querySelector("#pre").textContent = code;
</script>
</body>
</html>