UNPKG

algomin

Version:

Algomin - Viszualizing Distributed Algorithms

226 lines (190 loc) 7.78 kB
import {Model} from "../model/Model"; import "./View.css"; /** * View class responsible for rendering the animation */ export class View { /** * When initialized, it sets all attributes and triggers the updateCanvas() render loop * @param target_div ID of the root div or HTMLElement * @param controller Controller */ constructor(target_div, controller) { this._canvas = typeof target_div === "string" ? document.getElementById(target_div) : target_div this._slider = null; this._play_button = null; this._time_text = null; this._objects = []; this._controller = controller; this.updateCanvas(); } /** * Clears all DOM-Elements inside the canvas */ clearObjects() { if(this._canvas){ this._canvas.innerHTML = ""; } this._objects = []; this._slider = []; } /** * Render loop traversing all objects and setting its style attributes * when the object is visible. Also updating the slider. It Loops * every 6 ms to ensure a smooth animation for monitors up to 165 hz. */ updateCanvas() { setTimeout(() => { this._objects.forEach(x => { if (x.object.isVisible()) { x.dom_element.style.width = x.object.w + "px"; x.dom_element.style.height = x.object.h + "px"; x.dom_element.style.top = x.object.y + "px"; x.dom_element.style.left = x.object.x + "px"; x.dom_element.style.opacity = "1"; if (x.object.type === "message"){ x.dom_element.firstChild.style.transform = "translate(-50%, -50%) rotate(" + x.object.labelAngle + "deg)"; x.dom_element.style.transform = "rotate(" + x.object.angle + "deg)"; } if(x.object.type === "shape"){ this.setShapeDimensions(x.object); } } else { x.dom_element.style.opacity = "0"; } }) if (this._slider) { this._slider.value = Model.time; } if (this._play_button) { this._play_button.className = this._controller.model.running ? "fas fa-pause" : "fas fa-play"; } if (this._time_text) { this._time_text.innerText = `${(Model.time / 1000).toFixed(0)}s / ${(Model.algorithm_duration / 1000).toFixed(0)}s`; } this.updateCanvas(); }, 6); } /** * Sets the dimensions of a shape in relation to the canvas size */ setShapeDimensions(shape){ this._controller.setShapeX(shape, Math.round((this._canvas.offsetWidth / 100) * shape.percentageX)); this._controller.setShapeY(shape, Math.round((this._canvas.offsetHeight / 100) * shape.percentageY)); this._controller.setShapeW(shape, Math.round((this._canvas.offsetWidth / 100) * shape.percentageW)); this._controller.setShapeH(shape, Math.round((this._canvas.offsetHeight / 100) * shape.percentageH)); } /** * Sets the initial position of a message after the shapes got the dimensions */ setInitialMessagePosition(message){ this._controller.setMessageX(message, message.source.middleX); this._controller.setMessageY(message, message.source.middleY); } /** * Creates the slider control for the animation */ createSlider() { const slider_div = document.createElement('div'); const play_button_div = document.createElement("div"); const time_div = document.createElement("div"); const slider = document.createElement("input"); const play_button_icon = document.createElement("i"); const time_text = document.createElement("p"); play_button_div.id = "algomin_slider_play_button_div"; time_div.id = "algomin_slider_time_div"; slider_div.className = "algomin_slider_div"; slider.type = "range"; slider.min = 0; slider.max = Model.algorithm_duration; slider.value = 0; slider.className = "algomin_slider"; slider.style.width = this._canvas.clientWidth + "px"; play_button_icon.className = "fas fa-play"; time_text.id = "alomin_slider_time_text"; play_button_div.addEventListener("click", () => { this._controller.handlePlayButtonClick(); }) slider.addEventListener("input", (e) => { this._controller.handleSliderInput(parseInt(e.target.value)); }); play_button_div.appendChild(play_button_icon); slider_div.appendChild(play_button_div); slider_div.appendChild(time_div); slider_div.appendChild(slider); this._time_text = time_div; this._play_button = play_button_icon; this._slider = slider; this._canvas.appendChild(slider_div); } /** * Creates all existing objects in the Model to DOM-Elements */ createObjects() { this._objects = []; Model.shape.shapes.forEach(shape => { this.setShapeDimensions(shape); this.createShape(shape); }) Model.message.messages.forEach(message => { this.setInitialMessagePosition(message); this.createMessage(message); }) } /** * Creates a Shape (Node) as a DOM-Element * @param shape Shape object */ createShape(shape) { const new_div = document.createElement('div'); const new_div_header = document.createElement('div'); new_div.className = "algomin_shape"; new_div.id = shape.id; new_div.style.position = "absolute"; new_div.style.width = shape.w + "px"; new_div.style.height = shape.h + "px"; new_div.style.top = shape.y + "px"; new_div.style.left = shape.x + "px"; new_div_header.className = "algomin_shape_header"; new_div_header.innerText = shape.label; new_div.appendChild(new_div_header); this._canvas.appendChild(new_div); this._objects.push({object: shape, dom_element: new_div}); } /** * Creates a Message as a DOM-Element * @param message */ createMessage(message) { const message_div = document.createElement('div'); const message_p = document.createElement('p'); message_div.id = message.id; message_div.className = "algomin_message"; message_div.style.width = message.w + "px"; message_div.style.height = message.h + "px"; message_div.style.top = message.y + "px"; message_div.style.left = message.x + "px"; message_div.style.transform = "rotate(" + message.angle + "deg)"; message_p.className = "algomin_message_label"; message_p.innerHTML = message.label; message_p.style.transform = "translate(-50%, -50%) rotate(" + message.labelAngle + "deg)"; message_div.appendChild(message_p); this._canvas.appendChild(message_div); this._objects.push({object: message, dom_element: message_div}); } get canvas(){ return this._canvas; } get slider(){ return this._slider; } get play_button(){ return this._play_button; } get time_text(){ return this._time_text; } get objects(){ return this._objects; } }