var drawing = createImage(100,100);
var onResize = function(){
drawing.width = canvas.width;
drawing.height = canvas.height;
redrawBuffers = true;
ctx.font = "18px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
view.pos.x = cw;
view.pos.y = ch;
view.update();
}
const paths = [];
const path = {
addPoint(x,y){
this.points.push({x,y});
},
draw(ctx){
var i = 0;
ctx.beginPath();
ctx.moveTo(this.points[i].x,this.points[i++].y);
while(i < this.points.length){
ctx.lineTo(this.points[i].x,this.points[i++].y);
}
ctx.stroke();
}
}
function addPath(){
var newPath;
newPath = Object.assign({points : []},path);
paths.push(newPath)
return newPath;
}
function drawAll(ctx){
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,w,h);
var m = view.matrix;
ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
var i = 0;
for(i = 0; i < paths.length; i ++){
paths[i].draw(ctx);
}
}
const view = {
matrix : [1,0,0,1,0,0],
invMatrix : [1,0,0,1,0,0],
rotate : 0,
scale : 1,
pos : {
x : 0,
y : 0,
},
update(){
var xdx = Math.cos(this.rotate) * this.scale;
var xdy = Math.sin(this.rotate) * this.scale;
var m = this.matrix;
var im = this.invMatrix;
m[0] = xdx;
m[1] = xdy;
m[2] = -xdy;
m[3] = xdx;
m[4] = this.pos.x;
m[5] = this.pos.y;
cross = m[0] * m[3] - m[1] * m[2];
im[0] = m[3] / cross;
im[1] = -m[1] / cross;
im[2] = -m[2] / cross;
im[3] = m[0] / cross;
},
mouseToWorld(){
var xx, yy, m;
m = this.invMatrix;
xx = mouse.x - this.matrix[4];
yy = mouse.y - this.matrix[5];
mouse.xr = xx * m[0] + yy * m[2];
mouse.yr = xx * m[1] + yy * m[3];
},
toWorld(x,y,point = {}){
var xx, yy, m;
m = this.invMatrix;
xx = x - this.matrix[4];
yy = y - this.matrix[5];
point.x = xx * m[0] + yy * m[2];
point.y = xx * m[1] + yy * m[3];
return point;
},
toScreen(x,y,point = {}){
var m;
m = this.matrix;
point.x = x * m[0] + y * m[2] + m[4];
point.y = x * m[1] + y * m[3] + m[5];
return point;
},
clickOrigin : {
x : 0,
y : 0,
scale : 1,
},
dragging : false,
startDrag(){
if(!view.dragging){
view.dragging = true;
view.clickOrigin.x = mouse.xr;
view.clickOrigin.y = mouse.yr;
view.clickOrigin.screenX = mouse.x;
view.clickOrigin.screenY = mouse.y;
view.clickOrigin.scale = view.scale;
}
}
}
function panView(){
view.startDrag();
view.pos.x -= (view.clickOrigin.screenX - mouse.x);
view.pos.y -= (view.clickOrigin.screenY - mouse.y);
view.update();
view.mouseToWorld();
view.clickOrigin.screenX = mouse.x;
view.clickOrigin.screenY = mouse.y;
}
function scaleView(){
view.startDrag();
var y = view.clickOrigin.screenY - mouse.y;
if(y !== 0){
view.scale = view.clickOrigin.scale + (y/ch);
view.update();
}
}
function rotateView(){
view.startDrag();
workingCoord = view.toScreen(0,0,workingCoord);
var x = workingCoord.x - mouse.x;
var y = workingCoord.y - mouse.y;
var dist = Math.sqrt(x * x + y * y);
if(dist > 2 / view.scale){
view.rotate = Math.atan2(-y,-x);
view.update();
}
}
var currentPath;
var redrawBuffers = false;
var workingCoord;
function display() {
var showTransform = false;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.globalAlpha = 1;
ctx.clearRect(0, 0, w, h);
view.mouseToWorld();
var m = view.matrix;
if(mouse.shift || mouse.alt || mouse.ctrl){
if(mouse.shift){
ctx.fillText("Click drag to pan",cw, 20);
}else if(mouse.ctrl){
ctx.fillText("Click drag to rotate",cw, 20);
}else{
ctx.fillText("Click drag to scale : " + view.scale.toFixed(4),cw, 20);
}
}else{
ctx.fillText("Click drag to draw.",cw, 20);
ctx.fillText("Hold [shift], [ctrl], or [alt] and use mouse to pan, rotate, scale",cw, 40);
}
if(mouse.buttonRaw === 1){
if(mouse.shift || mouse.alt || mouse.ctrl){
if(mouse.shift){
panView();
}else if(mouse.ctrl){
rotateView();
}else{
scaleView();
}
m = view.matrix;
showTransform = true;
redrawBuffers = true;
}else{
if(currentPath === undefined){
currentPath = addPath();
}
currentPath.addPoint(mouse.xr,mouse.yr)
}
}else{
if(currentPath !== undefined){
currentPath.draw(drawing.ctx);
currentPath = undefined;
}
view.dragging = false;
}
if(showTransform){
redrawBuffers = false;
drawAll(drawing.ctx);
ctx.drawImage(drawing,0,0);
}else{
if(redrawBuffers){
redrawBuffers = false;
drawAll(drawing.ctx);
}
ctx.drawImage(drawing,0,0);
ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
drawing.ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
if(mouse.buttonRaw === 0){
var invScale = 1 / view.scale;
ctx.beginPath();
ctx.moveTo(mouse.xr - 10 * invScale,mouse.yr);
ctx.lineTo(mouse.xr + 10 * invScale,mouse.yr);
ctx.moveTo(mouse.xr ,mouse.yr - 10 * invScale);
ctx.lineTo(mouse.xr ,mouse.yr + 10 * invScale);
ctx.lineWidth = invScale;
ctx.stroke();
ctx.lineWidth = 1;
}
}
if(currentPath){
currentPath.draw(ctx);
}
if(mouse.ctrl){
ctx.setTransform(m[0],m[1],m[2],m[3],m[4],m[5]);
view.mouseToWorld();
ctx.strokeStyle = "black";
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(0,0,3,0,Math.PI * 2);
ctx.moveTo(0,0);
ctx.lineTo(mouse.xr,mouse.yr);
ctx.stroke();
ctx.lineWidth = 1.5;
ctx.strokeStyle = "red";
ctx.beginPath();
ctx.arc(0,0,3,0,Math.PI * 2);
ctx.moveTo(0,0);
ctx.lineTo(mouse.xr,mouse.yr);
ctx.stroke();
ctx.strokeStyle = "black";
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(200000 / view.scale,0);
ctx.stroke();
ctx.scale(1/ view.scale,1 / view.scale);
ctx.fillText("X axis",100 ,-10 );
}
}
var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true;
;(function(){
const RESIZE_DEBOUNCE_TIME = 100;
var createCanvas, resizeCanvas, setGlobals, resizeCount = 0;
createCanvas = function () {
var c,
cs;
cs = (c = document.createElement("canvas")).style;
cs.position = "absolute";
cs.top = cs.left = "0px";
cs.zIndex = 1000;
document.body.appendChild(c);
return c;
}
resizeCanvas = function () {
if (canvas === undefined) {
canvas = createCanvas();
}
canvas.width = innerWidth;
canvas.height = innerHeight;
ctx = canvas.getContext("2d");
if (typeof setGlobals === "function") {
setGlobals();
}
if (typeof onResize === "function") {
if(firstRun){
onResize();
firstRun = false;
}else{
resizeCount += 1;
setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
}
}
}
function debounceResize() {
resizeCount -= 1;
if (resizeCount <= 0) {
onResize();
}
}
setGlobals = function () {
cw = (w = canvas.width) / 2;
ch = (h = canvas.height) / 2;
}
mouse = (function () {
function preventDefault(e) {
e.preventDefault();
}
var mouse = {
x : 0,
y : 0,
w : 0,
alt : false,
shift : false,
ctrl : false,
buttonRaw : 0,
over : false,
bm : [1, 2, 4, 6, 5, 3],
active : false,
bounds : null,
crashRecover : null,
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.pageX - m.bounds.left;
m.y = e.pageY - m.bounds.top;
m.alt = e.altKey;
m.shift = e.shiftKey;
m.ctrl = e.ctrlKey;
if (t === "mousedown") {
m.buttonRaw |= m.bm[e.which - 1];
} else if (t === "mouseup") {
m.buttonRaw &= m.bm[e.which + 2];
} else if (t === "mouseout") {
m.buttonRaw = 0;
m.over = false;
} else if (t === "mouseover") {
m.over = true;
} else if (t === "mousewheel") {
m.w = e.wheelDelta;
} else if (t === "DOMMouseScroll") {
m.w = -e.detail;
}
if (m.callbacks) {
m.callbacks.forEach(c => c(e));
}
e.preventDefault();
}
m.addCallback = function (callback) {
if (typeof callback === "function") {
if (m.callbacks === undefined) {
m.callbacks = [callback];
} else {
m.callbacks.push(callback);
}
}
}
m.start = function (element) {
if (m.element !== undefined) {
m.removeMouse();
}
m.element = element === undefined ? document : element;
m.mouseEvents.forEach(n => {
m.element.addEventListener(n, mouseMove);
});
m.element.addEventListener("contextmenu", preventDefault, false);
m.active = true;
}
m.remove = function () {
if (m.element !== undefined) {
m.mouseEvents.forEach(n => {
m.element.removeEventListener(n, mouseMove);
});
m.element.removeEventListener("contextmenu", preventDefault);
m.element = m.callbacks = undefined;
m.active = false;
}
}
return mouse;
})();
function update(timer) {
globalTime = timer;
display();
requestAnimationFrame(update);
}
setTimeout(function(){
resizeCanvas();
mouse.start(canvas, true);
window.addEventListener("resize", resizeCanvas);
requestAnimationFrame(update);
},0);
})();
function createImage(w,h){var i=document.createElement("canvas");i.width=w;i.height=h;i.ctx=i.getContext("2d");return i;}