]> git.lizzy.rs Git - lagrange-playground.git/blob - init.js
Add view source link
[lagrange-playground.git] / init.js
1 const canvas = document.getElementsByTagName("canvas")[0];
2 const ctx = canvas.getContext("2d");
3
4 let center;
5 let centerF = [0.5, 0.5];
6 let scale = 50;
7 let positions = [];
8 let func = null;
9 let drag = false;
10 let dragPos = null;
11 let dragIndex = -1;
12 let pointSize = 0.2;
13
14 let config = {
15         grid: true,
16         lines: false,
17         circles: false,
18 };
19
20 const coordinateToScreen = (x, y) => {
21         return [
22                 center[0] + x * scale,
23                 center[1] + y * scale,
24         ];
25 };
26
27 const screenToCoordinate = (x, y) => {
28         return [
29                 (x - center[0]) / scale,
30                 (y - center[1]) / scale,
31         ];
32 };
33
34 const evalPos = (a, b, c, x) => {
35     return a + (b + c * x) * x;
36 };
37
38 const lagrange = _ => {
39         func = x => {
40                 let sum = 0;
41
42                 for (let i = 0; i < positions.length; i++) {
43                         let prod = positions[i][1];
44
45                         for (let j = 0; j < positions.length; j++)
46                                 if (j != i)
47                                         prod *= (x - positions[j][0]) / (positions[i][0] - positions[j][0]);
48
49                         sum += prod;
50                 }
51
52                 return sum;
53         };
54 };
55
56 const draw = _ => {
57         ctx.clearRect(0, 0, innerWidth, innerHeight);
58
59         for (let x = center[0] % scale; x < innerWidth; x += scale) {
60                 if (Math.round(x) == Math.round(center[0]))
61                         ctx.lineWidth = 2;
62                 else if (config.grid)
63                         ctx.lineWidth = 1;
64                 else
65                         continue;
66
67                 ctx.beginPath();
68                 ctx.moveTo(x, 0);
69                 ctx.lineTo(x, innerHeight);
70                 ctx.strokeStyle = "grey";
71                 ctx.stroke();
72         }
73
74         for (let y = center[1] % scale; y < innerHeight; y += scale) {
75                 if (Math.round(y) == Math.round(center[1]))
76                         ctx.lineWidth = 2;
77                 else if (config.grid)
78                         ctx.lineWidth = 1;
79                 else
80                         continue;
81
82                 ctx.beginPath();
83                 ctx.moveTo(0, y);
84                 ctx.lineTo(innerWidth, y);
85                 ctx.strokeStyle = "grey";
86                 ctx.stroke();
87         }
88
89         ctx.lineWidth = 2;
90
91         ctx.beginPath();
92         for (let x = 0; x < innerWidth; x++) {
93                 let y = Math.max(Math.min(center[1] + func((x - center[0]) / scale) * scale, +1e+37), -1e+37);
94
95                 if (x == 0)
96                         ctx.moveTo(x, y);
97                 else
98                         ctx.lineTo(x, y);
99         }
100         ctx.strokeStyle = "black";
101         ctx.stroke();
102
103         for (let i = 0; i < positions.length; i++) {
104                 const pos = positions[i];
105
106                 const [x, y] = coordinateToScreen(pos[0], pos[1]);
107                 ctx.beginPath();
108                 ctx.arc(x, y, scale * pointSize, 0, Math.PI * 2);
109                 ctx.fillStyle = "blue";
110                 ctx.fill();
111
112                 if (i > 0) {
113                         const last = positions[i - 1];
114
115                         if (config.lines) {
116                                 const [lx, ly] = coordinateToScreen(last[0], last[1]);
117
118                                 ctx.beginPath();
119                                 ctx.moveTo(lx, ly);
120                                 ctx.lineTo(x, y);
121                                 ctx.strokeStyle = "red";
122                                 ctx.stroke();
123                         }
124
125                         if (config.circles) {
126                                 const [cx, cy] = coordinateToScreen((pos[0] + last[0]) / 2, (pos[1] + last[1]) / 2);
127
128                                 ctx.beginPath();
129                                 ctx.arc(cx, cy, scale * Math.sqrt(Math.pow(pos[0] - last[0], 2) + Math.pow(pos[1] - last[1], 2)) / 2, 0, Math.PI * 2);
130                                 ctx.strokeStyle = "green";
131                                 ctx.stroke();
132                         }
133                 }
134         }
135 };
136
137 const sortPositions = _ => {
138         positions.sort((a, b) => {
139                 return a[0] < b[0] ? -1 : +1;
140         });
141         lagrange();
142         draw();
143 };
144
145 const addPosition = pos => {
146         positions.push(pos);
147         sortPositions();
148 };
149
150 const calculateCenter = _ => {
151         center = [
152                 centerF[0] * innerWidth,
153                 centerF[1] * innerHeight,
154         ];
155
156         draw();
157 };
158
159 const resize = _ => {
160         canvas.width = innerWidth;
161         canvas.height = innerHeight;
162
163         calculateCenter();
164 };
165
166 const enableDrag = evt => {
167         dragPos = [evt.clientX / innerWidth, evt.clientY / innerHeight];
168 };
169
170 const disableDrag = _ => {
171         drag = false;
172         dragPos = null;
173         dragIndex = -1;
174         canvas.style.cursor = "auto";
175 };
176
177 const init = _ => {
178         lagrange();
179         resize();
180 };
181
182 canvas.addEventListener("mousedown", evt => {
183         if (evt.button != 0)
184                 return;
185
186         enableDrag(evt);
187
188         for (let i = 0; i < positions.length; i++) {
189                 const [x, y] = coordinateToScreen(positions[i][0], positions[i][1]);
190
191                 if (Math.sqrt(Math.pow(evt.clientX - x, 2) + Math.pow(evt.clientY - y, 2)) < scale * pointSize) {
192                         dragIndex = i;
193                         break;
194                 }
195         }
196 });
197
198 canvas.addEventListener("mousemove", evt => {
199         if (evt.button != 0)
200                 return;
201
202         const oldDragPos = dragPos;
203
204         if (oldDragPos) {
205                 drag = true;
206
207                 enableDrag(evt);
208
209                 if (dragIndex == -1) {
210                         canvas.style.cursor = "move";
211
212                         centerF = [
213                                 centerF[0] + dragPos[0] - oldDragPos[0],
214                                 centerF[1] + dragPos[1] - oldDragPos[1],
215                         ];
216
217                         calculateCenter();
218                 } else {
219                         canvas.style.cursor = "grabbing";
220
221                         let pos = positions[dragIndex] = screenToCoordinate(dragPos[0] * innerWidth, dragPos[1] * innerHeight);
222                         sortPositions();
223                         dragIndex = positions.indexOf(pos);
224                 }
225         }
226 });
227
228 canvas.addEventListener("mouseup", evt => {
229         if (evt.button != 0)
230                 return;
231
232         if (! drag)
233                 addPosition(screenToCoordinate(evt.clientX, evt.clientY));
234
235         disableDrag();
236 });
237
238 canvas.addEventListener("mouseleave", evt => {
239         if (evt.button != 0)
240                 return;
241
242         disableDrag();
243 });
244
245 canvas.addEventListener("wheel", evt => {
246         scale -= evt.deltaY * 0.05;
247         scale = Math.max(1, scale);
248
249         draw();
250 });
251
252 addEventListener("resize", _ => {
253         resize();
254 });
255
256 for (let id of ["grid", "lines", "circles"]) {
257         let elem = document.getElementById(id);
258         elem.checked = config[id];
259
260         elem.addEventListener("input", evt => {
261                 config[id] = elem.checked;
262                 draw();
263         });
264 }
265
266 init();