const res = 1000; class hyperbolicPlane extends HTMLElement { canvas = document.createElement("canvas"); ctx = this.canvas.getContext("2d"); button = document.createElement("button"); Poincare = true; connectedCallback() { this.width = this.clientWidth; this.height = this.width; this.canvas.width = this.width; this.canvas.height = this.width; this.ctx.scale(this.width / 2.2, this.height / 2.2); this.ctx.translate(1.1, 1.1); this.ctx.lineWidth = 0.01; this.appendChild(this.canvas); this.appendChild(this.button); this.button.style.position = "absolute"; this.button.style.bottom = "10px"; this.button.style.right = "10px"; this.style.position = "relative"; this.updateButton(); this.render(); } addGeodesic(A, U, t, id) { if (this.geodesics.some((geo) => geo.id === id)) { console.warn(`Geodesic with id ${id} already exists.`); return; } this.geodesics.push({ A, U, t, id }); this.render(); } setGeodesic(A, U, t, id) { const geodesic = this.geodesics.find((geo) => geo.id === id); if (!geodesic) { this.addGeodesic(A, U, t, id); return; } geodesic.A = A; geodesic.U = U; geodesic.t = t; this.render(); } addPoint(A, id) { if (this.points.some((geo) => geo.id === id)) { console.warn(`Point with id ${id} already exists.`); return; } this.points.push({ A, id }); this.render(); } setPoint(A, id) { const point = this.points.find((point) => point.id === id); if (!point) { this.addPoint(A, id); return; } point.A = A; this.render(); } render() { this.ctx.clearRect( -this.width / 2, -this.height / 2, this.width, this.height ); this.ctx.beginPath(); this.ctx.arc(0, 0, 1, 0, 2 * Math.PI); this.ctx.stroke(); this.geodesics.forEach(({ A, U, t }) => { this.drawGeodesic(A, U, t); }); this.points.forEach(({ A, id }) => { this.drawPoint(A, id); }); this.ctx.font = "0.05px Arial"; this.ctx.fillStyle = "black"; this.ctx.textAlign = "left"; this.ctx.fillText(this.text, -1, 1); } drawPoint(A) { const [x, y] = this.Projection(A); this.ctx.beginPath(); this.ctx.arc(x, y, 0.02, 0, 2 * Math.PI); this.ctx.fill(); } setText(value) { this.text = value; this.render(); } drawPoint(A, id) { const [x, y] = this.Projection(A); this.ctx.fillStyle = "red"; this.ctx.beginPath(); this.ctx.arc(x, y, 0.02, 0, 2 * Math.PI); this.ctx.fill(); this.ctx.font = "0.1px Arial"; this.ctx.textAlign = "center"; this.ctx.fillText(id, x, y - 0.05); } changeProjection() { this.Poincare = !this.Poincare; //console.log(this.Poincare); this.updateButton(); this.render(); } updateButton() { this.button.innerHTML = this.Poincare ? "Poincaré Projection" : "Klein Projection"; } Projection(point) { return this.Poincare ? PoincareProjection(point) : KleinProjection(point); } drawGeodesic(A, U, t) { const t0 = t[0]; const t1 = t[1]; const delta = (t1 - t0) / (res - 1); const tArray = Array.from({ length: res }, (_, i) => t0 + i * delta); console.log("test"); const points = tArray.map((t) => this.Projection(geodesic(A, U, t))); this.ctx.beginPath(); points.slice(1).forEach((point) => { this.ctx.lineTo(point[0], point[1]); }); this.ctx.stroke(); } constructor() { super(); this.button.onclick = () => this.changeProjection(); this.geodesics = []; this.points = []; this.text = ""; } } customElements.define("hyperbolic-plane", hyperbolicPlane);