267 lines
4.9 KiB
JavaScript
267 lines
4.9 KiB
JavaScript
const canvas = document.getElementsByTagName("canvas")[0];
|
|
const ctx = canvas.getContext("2d");
|
|
|
|
let center;
|
|
let centerF = [0.5, 0.5];
|
|
let scale = 50;
|
|
let positions = [];
|
|
let func = null;
|
|
let drag = false;
|
|
let dragPos = null;
|
|
let dragIndex = -1;
|
|
let pointSize = 0.2;
|
|
|
|
let config = {
|
|
grid: true,
|
|
lines: false,
|
|
circles: false,
|
|
};
|
|
|
|
const coordinateToScreen = (x, y) => {
|
|
return [
|
|
center[0] + x * scale,
|
|
center[1] + y * scale,
|
|
];
|
|
};
|
|
|
|
const screenToCoordinate = (x, y) => {
|
|
return [
|
|
(x - center[0]) / scale,
|
|
(y - center[1]) / scale,
|
|
];
|
|
};
|
|
|
|
const evalPos = (a, b, c, x) => {
|
|
return a + (b + c * x) * x;
|
|
};
|
|
|
|
const lagrange = _ => {
|
|
func = x => {
|
|
let sum = 0;
|
|
|
|
for (let i = 0; i < positions.length; i++) {
|
|
let prod = positions[i][1];
|
|
|
|
for (let j = 0; j < positions.length; j++)
|
|
if (j != i)
|
|
prod *= (x - positions[j][0]) / (positions[i][0] - positions[j][0]);
|
|
|
|
sum += prod;
|
|
}
|
|
|
|
return sum;
|
|
};
|
|
};
|
|
|
|
const draw = _ => {
|
|
ctx.clearRect(0, 0, innerWidth, innerHeight);
|
|
|
|
for (let x = center[0] % scale; x < innerWidth; x += scale) {
|
|
if (Math.round(x) == Math.round(center[0]))
|
|
ctx.lineWidth = 2;
|
|
else if (config.grid)
|
|
ctx.lineWidth = 1;
|
|
else
|
|
continue;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, 0);
|
|
ctx.lineTo(x, innerHeight);
|
|
ctx.strokeStyle = "grey";
|
|
ctx.stroke();
|
|
}
|
|
|
|
for (let y = center[1] % scale; y < innerHeight; y += scale) {
|
|
if (Math.round(y) == Math.round(center[1]))
|
|
ctx.lineWidth = 2;
|
|
else if (config.grid)
|
|
ctx.lineWidth = 1;
|
|
else
|
|
continue;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, y);
|
|
ctx.lineTo(innerWidth, y);
|
|
ctx.strokeStyle = "grey";
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.lineWidth = 2;
|
|
|
|
ctx.beginPath();
|
|
for (let x = 0; x < innerWidth; x++) {
|
|
let y = Math.max(Math.min(center[1] + func((x - center[0]) / scale) * scale, +1e+37), -1e+37);
|
|
|
|
if (x == 0)
|
|
ctx.moveTo(x, y);
|
|
else
|
|
ctx.lineTo(x, y);
|
|
}
|
|
ctx.strokeStyle = "black";
|
|
ctx.stroke();
|
|
|
|
for (let i = 0; i < positions.length; i++) {
|
|
const pos = positions[i];
|
|
|
|
const [x, y] = coordinateToScreen(pos[0], pos[1]);
|
|
ctx.beginPath();
|
|
ctx.arc(x, y, scale * pointSize, 0, Math.PI * 2);
|
|
ctx.fillStyle = "blue";
|
|
ctx.fill();
|
|
|
|
if (i > 0) {
|
|
const last = positions[i - 1];
|
|
|
|
if (config.lines) {
|
|
const [lx, ly] = coordinateToScreen(last[0], last[1]);
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(lx, ly);
|
|
ctx.lineTo(x, y);
|
|
ctx.strokeStyle = "red";
|
|
ctx.stroke();
|
|
}
|
|
|
|
if (config.circles) {
|
|
const [cx, cy] = coordinateToScreen((pos[0] + last[0]) / 2, (pos[1] + last[1]) / 2);
|
|
|
|
ctx.beginPath();
|
|
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);
|
|
ctx.strokeStyle = "green";
|
|
ctx.stroke();
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const sortPositions = _ => {
|
|
positions.sort((a, b) => {
|
|
return a[0] < b[0] ? -1 : +1;
|
|
});
|
|
lagrange();
|
|
draw();
|
|
};
|
|
|
|
const addPosition = pos => {
|
|
positions.push(pos);
|
|
sortPositions();
|
|
};
|
|
|
|
const calculateCenter = _ => {
|
|
center = [
|
|
centerF[0] * innerWidth,
|
|
centerF[1] * innerHeight,
|
|
];
|
|
|
|
draw();
|
|
};
|
|
|
|
const resize = _ => {
|
|
canvas.width = innerWidth;
|
|
canvas.height = innerHeight;
|
|
|
|
calculateCenter();
|
|
};
|
|
|
|
const enableDrag = evt => {
|
|
dragPos = [evt.clientX / innerWidth, evt.clientY / innerHeight];
|
|
};
|
|
|
|
const disableDrag = _ => {
|
|
drag = false;
|
|
dragPos = null;
|
|
dragIndex = -1;
|
|
canvas.style.cursor = "auto";
|
|
};
|
|
|
|
const init = _ => {
|
|
lagrange();
|
|
resize();
|
|
};
|
|
|
|
canvas.addEventListener("mousedown", evt => {
|
|
if (evt.button != 0)
|
|
return;
|
|
|
|
enableDrag(evt);
|
|
|
|
for (let i = 0; i < positions.length; i++) {
|
|
const [x, y] = coordinateToScreen(positions[i][0], positions[i][1]);
|
|
|
|
if (Math.sqrt(Math.pow(evt.clientX - x, 2) + Math.pow(evt.clientY - y, 2)) < scale * pointSize) {
|
|
dragIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
|
|
canvas.addEventListener("mousemove", evt => {
|
|
if (evt.button != 0)
|
|
return;
|
|
|
|
const oldDragPos = dragPos;
|
|
|
|
if (oldDragPos) {
|
|
drag = true;
|
|
|
|
enableDrag(evt);
|
|
|
|
if (dragIndex == -1) {
|
|
canvas.style.cursor = "move";
|
|
|
|
centerF = [
|
|
centerF[0] + dragPos[0] - oldDragPos[0],
|
|
centerF[1] + dragPos[1] - oldDragPos[1],
|
|
];
|
|
|
|
calculateCenter();
|
|
} else {
|
|
canvas.style.cursor = "grabbing";
|
|
|
|
let pos = positions[dragIndex] = screenToCoordinate(dragPos[0] * innerWidth, dragPos[1] * innerHeight);
|
|
sortPositions();
|
|
dragIndex = positions.indexOf(pos);
|
|
}
|
|
}
|
|
});
|
|
|
|
canvas.addEventListener("mouseup", evt => {
|
|
if (evt.button != 0)
|
|
return;
|
|
|
|
if (! drag)
|
|
addPosition(screenToCoordinate(evt.clientX, evt.clientY));
|
|
|
|
disableDrag();
|
|
});
|
|
|
|
canvas.addEventListener("mouseleave", evt => {
|
|
if (evt.button != 0)
|
|
return;
|
|
|
|
disableDrag();
|
|
});
|
|
|
|
canvas.addEventListener("wheel", evt => {
|
|
scale -= evt.deltaY * 0.05;
|
|
scale = Math.max(1, scale);
|
|
|
|
draw();
|
|
});
|
|
|
|
addEventListener("resize", _ => {
|
|
resize();
|
|
});
|
|
|
|
for (let id of ["grid", "lines", "circles"]) {
|
|
let elem = document.getElementById(id);
|
|
elem.checked = config[id];
|
|
|
|
elem.addEventListener("input", evt => {
|
|
config[id] = elem.checked;
|
|
draw();
|
|
});
|
|
}
|
|
|
|
init();
|