algomin
Version:
Algomin - Viszualizing Distributed Algorithms
226 lines (190 loc) • 7.78 kB
JavaScript
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;
}
}