heartbeat three.js websocket fun
This commit is contained in:
BIN
heartbeat.webm
Normal file
BIN
heartbeat.webm
Normal file
Binary file not shown.
@@ -3,9 +3,20 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta content="width=device-width, initial-scale=1" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1" name="viewport"/>
|
||||||
|
<meta name="theme-color" content="#1d1850"/>
|
||||||
|
|
||||||
<title>MEDTRACE: Case - Frontend Developer</title>
|
<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>
|
<script type="module" src="./App/App.js"></script>
|
||||||
<!-- "light" DOM styles -->
|
<!-- "light" DOM styles -->
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -12,8 +12,8 @@
|
|||||||
#cards {
|
#cards {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fill, 150px);
|
grid-template-columns: repeat(auto-fill, 135px);
|
||||||
grid-gap: 2rem 1rem;
|
grid-gap: 2rem 0.5rem;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +166,7 @@
|
|||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ecg-ticker name="patients" heart-rate="60"></ecg-ticker>
|
||||||
<h1 name="not-found" id="not-found">Not found</h1>
|
<h1 name="not-found" id="not-found">Not found</h1>
|
||||||
<h1 name="bad-route">Bad route</h1>
|
<h1 name="bad-route">Bad route</h1>
|
||||||
<h1 name="not-implemented">Not implemented yet</h1>
|
<h1 name="not-implemented">Not implemented yet</h1>
|
||||||
|
|||||||
9
src/ECGTicker/ECGTicker.html
Normal file
9
src/ECGTicker/ECGTicker.html
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<template id="ecg-ticker-template">
|
||||||
|
<style>
|
||||||
|
#canvas-container {
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="canvas-container"></div>
|
||||||
|
</template>
|
||||||
@@ -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;
|
HTMLTemplateElement;
|
||||||
|
|
||||||
enum Attributes {
|
enum Attributes {
|
||||||
MESSAGE = "message",
|
HEART_RATE = "heart-rate",
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class ECGTicker extends HTMLElement {
|
export default class ECGTicker extends HTMLElement {
|
||||||
static observedAttributes = [Attributes.MESSAGE];
|
static observedAttributes = [Attributes.HEART_RATE];
|
||||||
|
|
||||||
private _content: DocumentFragment;
|
private _content: DocumentFragment;
|
||||||
$message: HTMLElement;
|
$canvasContainer: HTMLElement;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.attachShadow({mode: "open"});
|
this.attachShadow({mode: "open"});
|
||||||
this._content = template.content.cloneNode(true) as DocumentFragment;
|
this._content = template.content.cloneNode(true) as DocumentFragment;
|
||||||
this.$message = this._content.getElementById("message")!;
|
this.$canvasContainer = this._content.getElementById("canvas-container")!;
|
||||||
}
|
}
|
||||||
|
|
||||||
connectedCallback() {
|
connectedCallback() {
|
||||||
@@ -25,18 +29,73 @@ export default class ECGTicker extends HTMLElement {
|
|||||||
} else {
|
} else {
|
||||||
console.warn("No shadowRoot detected.");
|
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) {
|
attributeChangedCallback(name: string, _prev: string, curr: string) {
|
||||||
if (Attributes.MESSAGE === name) {
|
if (Attributes.HEART_RATE === name) {
|
||||||
while (this.$message.firstChild) {
|
// send message via websocket to alter heart rate in real time
|
||||||
this.$message.removeChild(this.$message.lastChild!);
|
|
||||||
}
|
|
||||||
this.$message.appendChild(document.createTextNode(curr));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.customElements.define("x-ecg-ticker", ECGTicker);
|
window.customElements.define("ecg-ticker", ECGTicker);
|
||||||
|
|
||||||
export {};
|
export {};
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
header h1 {
|
header h1 {
|
||||||
font-size: 1.75rem;
|
font-size: 1.4rem;
|
||||||
}
|
}
|
||||||
#message {
|
#message {
|
||||||
margin: 15px;
|
margin: 15px;
|
||||||
|
|||||||
Reference in New Issue
Block a user