110 lines
3.8 KiB
JavaScript
110 lines
3.8 KiB
JavaScript
|
|
/* workshop-seismogram.js — сырое движение земли бегущей линией.
|
|||
|
|
*
|
|||
|
|
* Где фингерпринт отвечает «из чего дрожь» (частоты), эта линия — «как двигалась
|
|||
|
|
* земля» (время). То же окно Земли, две системы координат: фингерпринт считается
|
|||
|
|
* *из* этого сигнала, так что волна первична, а сетка — её разложение.
|
|||
|
|
*
|
|||
|
|
* Линия — окно с вычтенным средним (датчик несёт DC ~1000 отсчётов, это его
|
|||
|
|
* положение, не движение) и авто-масштабом по пику окна. Высота не абсолютна:
|
|||
|
|
* важно, как линия меняется, а не насколько она высока.
|
|||
|
|
*/
|
|||
|
|
class WorkshopSeismogram extends HTMLElement {
|
|||
|
|
static get observedAttributes() { return ["height", "line-color"]; }
|
|||
|
|
|
|||
|
|
constructor() {
|
|||
|
|
super();
|
|||
|
|
this.attachShadow({ mode: "open" });
|
|||
|
|
this._wave = new Array(256).fill(0);
|
|||
|
|
this._unsub = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
connectedCallback() { this._render(); this._subscribe(); }
|
|||
|
|
|
|||
|
|
disconnectedCallback() {
|
|||
|
|
if (this._unsub) this._unsub();
|
|||
|
|
this._unsub = null;
|
|||
|
|
if (this._ro) this._ro.disconnect();
|
|||
|
|
this._ro = null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
attributeChangedCallback() { if (this.isConnected) this._render(); }
|
|||
|
|
|
|||
|
|
get height() {
|
|||
|
|
const v = parseInt(this.getAttribute("height") || "130", 10);
|
|||
|
|
return Number.isFinite(v) && v > 0 ? v : 130;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_subscribe() {
|
|||
|
|
if (!this.isConnected || this._unsub) return;
|
|||
|
|
this._unsub = window.WorkshopStream.on("noise", (ev) => this._update(ev));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_render() {
|
|||
|
|
const h = this.height;
|
|||
|
|
this.shadowRoot.innerHTML = `
|
|||
|
|
<style>
|
|||
|
|
:host { display: block; }
|
|||
|
|
canvas {
|
|||
|
|
width: 100%; height: ${h}px; display: block;
|
|||
|
|
background: var(--workshop-high, #10151a);
|
|||
|
|
border: 1px solid var(--workshop-line, #232a33);
|
|||
|
|
border-radius: 6px;
|
|||
|
|
}
|
|||
|
|
</style>
|
|||
|
|
<canvas></canvas>`;
|
|||
|
|
this._canvas = this.shadowRoot.querySelector("canvas");
|
|||
|
|
this._ctx = this._canvas.getContext("2d");
|
|||
|
|
// При connectedCallback элемент ещё не разложен, rect.width = 0 и canvas был
|
|||
|
|
// бы 1px. Размер — на следующем кадре, синхронизация через ResizeObserver.
|
|||
|
|
requestAnimationFrame(() => this._resize());
|
|||
|
|
if (!this._ro && typeof ResizeObserver !== "undefined") {
|
|||
|
|
this._ro = new ResizeObserver(() => this._resize());
|
|||
|
|
this._ro.observe(this);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_resize() {
|
|||
|
|
if (!this._canvas) return;
|
|||
|
|
const w = Math.max(1, Math.round(this.clientWidth || 640));
|
|||
|
|
const h = this.height;
|
|||
|
|
if (this._canvas.width !== w || this._canvas.height !== h) {
|
|||
|
|
this._canvas.width = w;
|
|||
|
|
this._canvas.height = h;
|
|||
|
|
}
|
|||
|
|
this._draw();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_update(ev) {
|
|||
|
|
if (!ev.wave || !ev.wave.length) return;
|
|||
|
|
this._wave = ev.wave;
|
|||
|
|
this._draw();
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
_draw() {
|
|||
|
|
if (!this._ctx) return;
|
|||
|
|
const ctx = this._ctx;
|
|||
|
|
const W = this._canvas.width, H = this._canvas.height;
|
|||
|
|
ctx.clearRect(0, 0, W, H);
|
|||
|
|
let peak = 0;
|
|||
|
|
for (const v of this._wave) peak = Math.max(peak, Math.abs(v));
|
|||
|
|
const scale = peak > 0 ? (H / 2 - 4) / peak : 1;
|
|||
|
|
ctx.strokeStyle = this.getAttribute("line-color") || "#4a9de0";
|
|||
|
|
ctx.lineWidth = 1;
|
|||
|
|
ctx.beginPath();
|
|||
|
|
const stepX = W / (this._wave.length - 1);
|
|||
|
|
for (let i = 0; i < this._wave.length; i++) {
|
|||
|
|
const x = i * stepX;
|
|||
|
|
const y = H / 2 - this._wave[i] * scale;
|
|||
|
|
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
|
|||
|
|
}
|
|||
|
|
ctx.stroke();
|
|||
|
|
ctx.strokeStyle = "#232a33";
|
|||
|
|
ctx.beginPath();
|
|||
|
|
ctx.moveTo(0, H / 2);
|
|||
|
|
ctx.lineTo(W, H / 2);
|
|||
|
|
ctx.stroke();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
customElements.define("workshop-seismogram", WorkshopSeismogram);
|