simulator/src/canvasApi.js
/* eslint-disable no-param-reassign */
import backgroundArea from './backgroundArea';
import simulationArea from './simulationArea';
import miniMapArea, { removeMiniMap, updatelastMinimapShown } from './minimap';
import { colors } from './themer/themer';
var unit = 10;
export function findDimensions(scope = globalScope) {
var totalObjects = 0;
simulationArea.minWidth = undefined;
simulationArea.maxWidth = undefined;
simulationArea.minHeight = undefined;
simulationArea.maxHeight = undefined;
for (var i = 0; i < updateOrder.length; i++) {
if (updateOrder[i] !== 'wires') {
for (var j = 0; j < scope[updateOrder[i]].length; j++) {
totalObjects += 1;
var obj = scope[updateOrder[i]][j];
if (totalObjects === 1) {
simulationArea.minWidth = obj.absX();
simulationArea.minHeight = obj.absY();
simulationArea.maxWidth = obj.absX();
simulationArea.maxHeight = obj.absY();
}
if (obj.objectType !== 'Node') {
if (obj.y - obj.upDimensionY < simulationArea.minHeight) { simulationArea.minHeight = obj.y - obj.upDimensionY; }
if (obj.y + obj.downDimensionY > simulationArea.maxHeight) { simulationArea.maxHeight = obj.y + obj.downDimensionY; }
if (obj.x - obj.leftDimensionX < simulationArea.minWidth) { simulationArea.minWidth = obj.x - obj.leftDimensionX; }
if (obj.x + obj.rightDimensionX > simulationArea.maxWidth) { simulationArea.maxWidth = obj.x + obj.rightDimensionX; }
} else {
if (obj.absY() < simulationArea.minHeight) { simulationArea.minHeight = obj.absY(); }
if (obj.absY() > simulationArea.maxHeight) { simulationArea.maxHeight = obj.absY(); }
if (obj.absX() < simulationArea.minWidth) { simulationArea.minWidth = obj.absX(); }
if (obj.absX() > simulationArea.maxWidth) { simulationArea.maxWidth = obj.absX(); }
}
}
}
}
simulationArea.objectList = updateOrder;
}
// Function used to change the zoom level wrt to a point
// fn to change scale (zoom) - It also shifts origin so that the position
// of the object in focus doesn't change
export function changeScale(delta, xx, yy, method = 1) {
// method = 3/2 - Zoom wrt center of screen
// method = 1 - Zoom wrt position of mouse
// Otherwise zoom wrt to selected object
if (method === 3) {
xx = (width / 2 - globalScope.ox) / globalScope.scale;
yy = (height / 2 - globalScope.oy) / globalScope.scale;
} else if (xx === undefined || yy === undefined || xx === 'zoomButton' || yy === 'zoomButton') {
if (simulationArea.lastSelected && simulationArea.lastSelected.objectType !== 'Wire') { // selected object
xx = simulationArea.lastSelected.x;
yy = simulationArea.lastSelected.y;
} else { // mouse location
// eslint-disable-next-line no-lonely-if
if (method === 1) {
xx = simulationArea.mouseX;
yy = simulationArea.mouseY;
} else if (method === 2) {
xx = (width / 2 - globalScope.ox) / globalScope.scale;
yy = (height / 2 - globalScope.oy) / globalScope.scale;
}
}
}
var oldScale = globalScope.scale;
globalScope.scale = Math.max(0.5, Math.min(4 * DPR, globalScope.scale + delta));
globalScope.scale = Math.round(globalScope.scale * 10) / 10;
globalScope.ox -= Math.round(xx * (globalScope.scale - oldScale)); // Shift accordingly, so that we zoom wrt to the selected point
globalScope.oy -= Math.round(yy * (globalScope.scale - oldScale));
// dots(true,false);
// MiniMap
if (!embed && !lightMode) {
findDimensions(globalScope);
miniMapArea.setup();
$('#miniMap').show();
updatelastMinimapShown();
$('#miniMap').show();
setTimeout(removeMiniMap, 2000);
}
}
// fn to draw Dots on screen
// the function is called only when the zoom level or size of screen changes.
// Otherwise for normal panning, the canvas itself is moved to give the illusion of movement
export function dots(dots = true, transparentBackground = false, force = false) {
var scale = unit * globalScope.scale;
var ox = globalScope.ox % scale; // offset
var oy = globalScope.oy % scale; // offset
document.getElementById('backgroundArea').style.left = (ox - scale) / DPR;
document.getElementById('backgroundArea').style.top = (oy - scale) / DPR;
if (globalScope.scale === simulationArea.prevScale && !force) return;
if (!backgroundArea.context) return;
simulationArea.prevScale = globalScope.scale;
var canvasWidth = backgroundArea.canvas.width; // max X distance
var canvasHeight = backgroundArea.canvas.height; // max Y distance
var ctx = backgroundArea.context;
ctx.beginPath();
backgroundArea.clear();
ctx.strokeStyle = colors["canvas_stroke"];
ctx.lineWidth = 1;
if (!transparentBackground) {
ctx.fillStyle = colors["canvas_fill"];
ctx.rect(0, 0, canvasWidth, canvasHeight);
ctx.fill();
}
if (!embed) {
var correction = 0.5 * (ctx.lineWidth % 2);
for (var i = 0; i < canvasWidth; i += scale) {
ctx.moveTo(Math.round(i + correction) - correction, 0);
ctx.lineTo(Math.round(i + correction) - correction, canvasHeight);
}
for (var j = 0; j < canvasHeight; j += scale) {
ctx.moveTo(0, Math.round(j + correction) - correction);
ctx.lineTo(canvasWidth, Math.round(j + correction) - correction);
}
ctx.stroke();
}
// Old Code
// function drawPixel(x, y, r, g, b, a) {
// var index = (x + y * canvasWidth) * 4;
// canvasData.data[index + 0] = r;
// canvasData.data[index + 1] = g;
// canvasData.data[index + 2] = b;
// canvasData.data[index + 3] = a;
// }
// if (dots) {
// var canvasData = ctx.getImageData(0, 0, canvasWidth, canvasHeight);
//
//
//
// for (var i = 0 + ox; i < canvasWidth; i += scale)
// for (var j = 0 + oy; j < canvasHeight; j += scale)
// drawPixel(i, j, 0, 0, 0, 255);
// ctx.putImageData(canvasData, 0, 0);
// }
}
// Helper canvas API starts here
// All canvas functions are wrt to a center point (xx,yy),
// direction is used to abstract rotation of everything by a certain angle
// Possible values for direction = "RIGHT" (default), "LEFT", "UP", "DOWN"
export function bezierCurveTo(x1, y1, x2, y2, x3, y3, xx, yy, dir) {
[x1, y1] = rotate(x1, y1, dir);
[x2, y2] = rotate(x2, y2, dir);
[x3, y3] = rotate(x3, y3, dir);
var { ox } = globalScope;
var { oy } = globalScope;
x1 *= globalScope.scale;
y1 *= globalScope.scale;
x2 *= globalScope.scale;
y2 *= globalScope.scale;
x3 *= globalScope.scale;
y3 *= globalScope.scale;
xx *= globalScope.scale;
yy *= globalScope.scale;
var ctx = simulationArea.context;
ctx.bezierCurveTo(Math.round(xx + ox + x1), Math.round(yy + oy + y1), Math.round(xx + ox + x2), Math.round(yy + oy + y2), Math.round(xx + ox + x3), Math.round(yy + oy + y3));
}
export function moveTo(ctx, x1, y1, xx, yy, dir, bypass = false) {
var correction = 0.5 * (ctx.lineWidth % 2);
let newX;
let newY;
[newX, newY] = rotate(x1, y1, dir);
newX *= globalScope.scale;
newY *= globalScope.scale;
xx *= globalScope.scale;
yy *= globalScope.scale;
if (bypass) { ctx.moveTo(xx + globalScope.ox + newX, yy + globalScope.oy + newY); } else { ctx.moveTo(Math.round(xx + globalScope.ox + newX - correction) + correction, Math.round(yy + globalScope.oy + newY - correction) + correction); }
}
export function lineTo(ctx, x1, y1, xx, yy, dir) {
let newX;
let newY;
var correction = 0.5 * (ctx.lineWidth % 2);
[newX, newY] = rotate(x1, y1, dir);
newX *= globalScope.scale;
newY *= globalScope.scale;
xx *= globalScope.scale;
yy *= globalScope.scale;
ctx.lineTo(Math.round(xx + globalScope.ox + newX - correction) + correction, Math.round(yy + globalScope.oy + newY - correction) + correction);
}
export function arc(ctx, sx, sy, radius, start, stop, xx, yy, dir) {
// ox-x of origin, xx- x of element , sx - shift in x from element
let Sx; let Sy; let newStart; let newStop; let counterClock;
var correction = 0.5 * (ctx.lineWidth % 2);
[Sx, Sy] = rotate(sx, sy, dir);
Sx *= globalScope.scale;
Sy *= globalScope.scale;
xx *= globalScope.scale;
yy *= globalScope.scale;
radius *= globalScope.scale;
[newStart, newStop, counterClock] = rotateAngle(start, stop, dir);
ctx.arc(Math.round(xx + globalScope.ox + Sx + correction) - correction, Math.round(yy + globalScope.oy + Sy + correction) - correction, Math.round(radius), newStart, newStop, counterClock);
}
export function arc2(ctx, sx, sy, radius, start, stop, xx, yy, dir) {
// ox-x of origin, xx- x of element , sx - shift in x from element
let Sx; let Sy; let newStart; let newStop; let counterClock;
var correction = 0.5 * (ctx.lineWidth % 2);
[Sx, Sy] = rotate(sx, sy, dir);
Sx *= globalScope.scale;
Sy *= globalScope.scale;
xx *= globalScope.scale;
yy *= globalScope.scale;
radius *= globalScope.scale;
[newStart, newStop, counterClock] = rotateAngle(start, stop, dir);
var pi = 0;
if (counterClock) { pi = Math.PI; }
ctx.arc(Math.round(xx + globalScope.ox + Sx + correction) - correction, Math.round(yy + globalScope.oy + Sy + correction) - correction, Math.round(radius), newStart + pi, newStop + pi);
}
export function drawCircle2(ctx, sx, sy, radius, xx, yy, dir) { // ox-x of origin, xx- x of element , sx - shift in x from element
let Sx;
let Sy;
[Sx, Sy] = rotate(sx, sy, dir);
Sx *= globalScope.scale;
Sy *= globalScope.scale;
xx *= globalScope.scale;
yy *= globalScope.scale;
radius *= globalScope.scale;
ctx.arc(Math.round(xx + globalScope.ox + Sx), Math.round(yy + globalScope.oy + Sy), Math.round(radius), 0, 2 * Math.PI);
}
export function rect(ctx, x1, y1, x2, y2) {
var correction = 0.5 * (ctx.lineWidth % 2);
x1 *= globalScope.scale;
y1 *= globalScope.scale;
x2 *= globalScope.scale;
y2 *= globalScope.scale;
ctx.rect(Math.round(globalScope.ox + x1 - correction) + correction, Math.round(globalScope.oy + y1 - correction) + correction, Math.round(x2), Math.round(y2));
}
export function drawImage(ctx,img, x1, y1, w_canvas, h_canvas) {
x1 *= globalScope.scale;
y1 *= globalScope.scale;
x1 += globalScope.ox;
y1 += globalScope.oy;
w_canvas *= globalScope.scale;
h_canvas *= globalScope.scale;
ctx.drawImage(img, x1, y1, w_canvas, h_canvas);
}
export function rect2(ctx, x1, y1, x2, y2, xx, yy, dir = 'RIGHT') {
var correction = 0.5 * (ctx.lineWidth % 2);
[x1, y1] = rotate(x1, y1, dir);
[x2, y2] = rotate(x2, y2, dir);
x1 *= globalScope.scale;
y1 *= globalScope.scale;
x2 *= globalScope.scale;
y2 *= globalScope.scale;
xx *= globalScope.scale;
yy *= globalScope.scale;
ctx.rect(Math.round(globalScope.ox + xx + x1 - correction) + correction, Math.round(globalScope.oy + yy + y1 - correction) + correction, Math.round(x2), Math.round(y2));
}
export function rotate(x1, y1, dir) {
if (dir === 'LEFT') { return [-x1, y1]; }
if (dir === 'DOWN') { return [y1, x1]; }
if (dir === 'UP') { return [y1, -x1]; }
return [x1, y1];
}
export function correctWidth(w) {
return Math.max(1, Math.round(w * globalScope.scale));
}
function rotateAngle(start, stop, dir) {
if (dir === 'LEFT') { return [start, stop, true]; }
if (dir === 'DOWN') { return [start - Math.PI / 2, stop - Math.PI / 2, true]; }
if (dir === 'UP') { return [start - Math.PI / 2, stop - Math.PI / 2, false]; }
return [start, stop, false];
}
export function drawLine(ctx, x1, y1, x2, y2, color, width) {
x1 *= globalScope.scale;
y1 *= globalScope.scale;
x2 *= globalScope.scale;
y2 *= globalScope.scale;
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineCap = 'round';
ctx.lineWidth = correctWidth(width);//* globalScope.scale;
var correction = 0.5 * (ctx.lineWidth % 2);
var hCorrection = 0;
var vCorrection = 0;
if (y1 === y2) vCorrection = correction;
if (x1 === x2) hCorrection = correction;
ctx.moveTo(Math.round(x1 + globalScope.ox + hCorrection) - hCorrection, Math.round(y1 + globalScope.oy + vCorrection) - vCorrection);
ctx.lineTo(Math.round(x2 + globalScope.ox + hCorrection) - hCorrection, Math.round(y2 + globalScope.oy + vCorrection) - vCorrection);
ctx.stroke();
}
// Checks if string color is a valid color using a hack
export function validColor(color) {
var $div = $('<div>');
$div.css('border', `1px solid ${color}`);
return ($div.css('border-color') !== '');
}
// Helper function to color "RED" to RGBA
export function colorToRGBA(color) {
var cvs;
var ctx;
cvs = document.createElement('canvas');
cvs.height = 1;
cvs.width = 1;
ctx = cvs.getContext('2d');
ctx.fillStyle = color;
ctx.fillRect(0, 0, 1, 1);
return ctx.getImageData(0, 0, 1, 1).data;
}
export function drawCircle(ctx, x1, y1, r, color) {
x1 *= globalScope.scale;
y1 *= globalScope.scale;
ctx.beginPath();
ctx.fillStyle = color;
ctx.arc(Math.round(x1 + globalScope.ox), Math.round(y1 + globalScope.oy), Math.round(r * globalScope.scale), 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
}
// To show message like values, node name etc
export function canvasMessage(ctx, str, x1, y1, fontSize = 10) {
if (!str || !str.length) return;
ctx.font = `${Math.round(fontSize * globalScope.scale)}px Raleway`;
ctx.textAlign = 'center';
var width = ctx.measureText(str).width / globalScope.scale + 8;
var height = 13;
ctx.strokeStyle = 'black';
ctx.lineWidth = correctWidth(1);
ctx.fillStyle = 'yellow';
ctx.save();
ctx.beginPath();
rect(ctx, x1 - width / 2, y1 - height / 2 - 3, width, height);
ctx.shadowColor = '#999';
ctx.shadowBlur = 10;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
ctx.stroke();
ctx.fill();
ctx.restore();
x1 *= globalScope.scale;
y1 *= globalScope.scale;
ctx.beginPath();
ctx.fillStyle = 'black';
ctx.fillText(str, Math.round(x1 + globalScope.ox), Math.round(y1 + globalScope.oy));
ctx.fill();
}
export function fillText(ctx, str, x1, y1, fontSize = 20) {
x1 *= globalScope.scale;
y1 *= globalScope.scale;
ctx.font = `${Math.round(fontSize * globalScope.scale)}px Raleway`;
ctx.fillText(str, Math.round(x1 + globalScope.ox), Math.round(y1 + globalScope.oy));
}
export function fillText2(ctx, str, x1, y1, xx, yy, dir) {
var angle = {
RIGHT: 0,
LEFT: 0,
DOWN: Math.PI / 2,
UP: -Math.PI / 2,
};
x1 *= globalScope.scale;
y1 *= globalScope.scale;
[x1, y1] = rotate(x1, y1, dir);
xx *= globalScope.scale;
yy *= globalScope.scale;
ctx.font = `${Math.round(14 * globalScope.scale)}px Raleway`;
ctx.save();
ctx.translate(Math.round(xx + x1 + globalScope.ox), Math.round(yy + y1 + globalScope.oy));
ctx.rotate(angle[dir]);
ctx.textAlign = 'center';
ctx.fillText(str, 0, Math.round(4 * globalScope.scale) * (1 - 0 * (+(dir === 'DOWN'))));
ctx.restore();
}
export function fillText4(ctx, str, x1, y1, xx, yy, dir, fontSize = 14, textAlign = 'center') {
var angle = {
RIGHT: 0,
LEFT: 0,
DOWN: Math.PI / 2,
UP: -Math.PI / 2,
};
x1 *= globalScope.scale;
y1 *= globalScope.scale;
[x1, y1] = rotate(x1, y1, dir);
xx *= globalScope.scale;
yy *= globalScope.scale;
ctx.font = `${Math.round(fontSize * globalScope.scale)}px Raleway`;
// ctx.font = 20+"px Raleway";
ctx.textAlign = textAlign;
ctx.fillText(str, xx + x1 + globalScope.ox, yy + y1 + globalScope.oy + Math.round(fontSize / 3 * globalScope.scale));
}
export function fillText3(ctx, str, x1, y1, xx = 0, yy = 0, fontSize = 14, font = 'Raleway', textAlign = 'center') {
x1 *= globalScope.scale;
y1 *= globalScope.scale;
xx *= globalScope.scale;
yy *= globalScope.scale;
ctx.font = `${Math.round(fontSize * globalScope.scale)}px ${font}`;
ctx.textAlign = textAlign;
ctx.fillText(str, Math.round(xx + x1 + globalScope.ox), Math.round(yy + y1 + globalScope.oy));
}
export const oppositeDirection = {
RIGHT: 'LEFT',
LEFT: 'RIGHT',
DOWN: 'UP',
UP: 'DOWN',
};
export const fixDirection = {
right: 'LEFT',
left: 'RIGHT',
down: 'UP',
up: 'DOWN',
LEFT: 'LEFT',
RIGHT: 'RIGHT',
UP: 'UP',
DOWN: 'DOWN',
};