heartbeat three.js websocket fun

This commit is contained in:
2024-04-23 07:52:20 -04:00
parent 9891ba5b49
commit 8bc482a236
6 changed files with 95 additions and 15 deletions

BIN
heartbeat.webm Normal file

Binary file not shown.

View File

@@ -3,9 +3,20 @@
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1" name="viewport"/>
<meta name="theme-color" content="#1d1850"/>
<title>MEDTRACE: Case - Frontend Developer</title>
<!-- <script type="module" src="./MyCard/MyCard.js"></script> -->
<!-- ability to import three.js from cdn -->
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.163.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.163.0/examples/jsm/"
}
}
</script>
<script type="module" src="./App/App.js"></script>
<!-- "light" DOM styles -->
<style>

View File

@@ -12,8 +12,8 @@
#cards {
padding: 20px;
display: grid;
grid-template-columns: repeat(auto-fill, 150px);
grid-gap: 2rem 1rem;
grid-template-columns: repeat(auto-fill, 135px);
grid-gap: 2rem 0.5rem;
justify-content: space-between;
}
@@ -166,6 +166,7 @@
</section>
</div>
<ecg-ticker name="patients" heart-rate="60"></ecg-ticker>
<h1 name="not-found" id="not-found">Not found</h1>
<h1 name="bad-route">Bad route</h1>
<h1 name="not-implemented">Not implemented yet</h1>

View File

@@ -0,0 +1,9 @@
<template id="ecg-ticker-template">
<style>
#canvas-container {
height: 100vh;
width: 100vw;
}
</style>
<div id="canvas-container"></div>
</template>

View File

@@ -1,22 +1,26 @@
const template = document.getElementById("my-card-template") as
import * as THREE from "three";
import {OrbitControls} from "three/addons/controls/OrbitControls.js";
const template = document.getElementById("ecg-ticker-template") as
HTMLTemplateElement;
enum Attributes {
MESSAGE = "message",
HEART_RATE = "heart-rate",
}
export default class ECGTicker extends HTMLElement {
static observedAttributes = [Attributes.MESSAGE];
static observedAttributes = [Attributes.HEART_RATE];
private _content: DocumentFragment;
$message: HTMLElement;
$canvasContainer: HTMLElement;
constructor() {
super();
this.attachShadow({mode: "open"});
this._content = template.content.cloneNode(true) as DocumentFragment;
this.$message = this._content.getElementById("message")!;
this.$canvasContainer = this._content.getElementById("canvas-container")!;
}
connectedCallback() {
@@ -25,18 +29,73 @@ export default class ECGTicker extends HTMLElement {
} else {
console.warn("No shadowRoot detected.");
}
this.initECGAnimation();
}
initECGAnimation() {
const renderer = new THREE.WebGLRenderer();
renderer.setSize(200, 300);
this.$canvasContainer.appendChild(renderer.domElement);
const scene = new THREE.Scene();
const axesHelper = new THREE.AxesHelper(5);
scene.add(axesHelper);
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
const orbit = new OrbitControls(camera, renderer.domElement);
camera.position.set(1, 2, 5);
orbit.update();
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
const boxMaterial = new THREE.MeshBasicMaterial({color: 0xffff00});
const box = new THREE.Mesh(boxGeometry, boxMaterial);
scene.add(box);
let heart_amp = 0.0;
function animate(x) {
let sr = 120;
let freq = 20;
box.position.x = 0.01 * Math.sin(2 * Math.PI * freq * (x / sr));
box.position.y = 0.02 * Math.sin(2 * Math.PI * (freq/2) * (x / sr));
// box.position.z = 0.05 * Math.sin(2 * Math.PI * (freq/3) * (x / sr));
box.position.z = heart_amp * 1;
box.rotation.set(x/2000, x/2000, 0);
// camera.position.x = 1 * Math.sin(2 * Math.PI * (freq/6) * (x / sr));
// camera.position.y = 2 * Math.sin(2 * Math.PI * (freq/11) * (x / sr));
// camera.position.z = 1 + Math.abs(15 * Math.sin(2 * Math.PI * (freq/75) * (x / sr)));
renderer.render(scene, camera);
}
// const socket = new WebSocket(`${location.protocol === "https" ? "wss" : "ws"}://${location.hostname}:7890/ecg`);
const socket = new WebSocket(`ws://localhost:7890/ecg`);
socket.onmessage = (({data}) => {
heart_amp = data;
console.log(heart_amp);
});
renderer.setAnimationLoop(animate);
}
attributeChangedCallback(name: string, _prev: string, curr: string) {
if (Attributes.MESSAGE === name) {
while (this.$message.firstChild) {
this.$message.removeChild(this.$message.lastChild!);
}
this.$message.appendChild(document.createTextNode(curr));
if (Attributes.HEART_RATE === name) {
// send message via websocket to alter heart rate in real time
}
}
};
window.customElements.define("x-ecg-ticker", ECGTicker);
window.customElements.define("ecg-ticker", ECGTicker);
export {};

View File

@@ -22,7 +22,7 @@
}
header h1 {
font-size: 1.75rem;
font-size: 1.4rem;
}
#message {
margin: 15px;