Score: 60 + upvotes
- No jQuery used.
- No CSS.
- The rule says [cells scaling equally to keep the same table aspect ratio when the browser is scaled], it does that by not scaling at all when the window is resized and if you do use scaling/zoom in the browser, it will keep the aspect ratio.
- No cell or row labeling used.
The code basically creates a TableCanvas
object. This object creates a table-based canvas and using javascript's DOM API, inserts it in a DOM node. Colors are specified as strings like "rgb(0,255,0)"
for green.
It features:
- Resizing without deleting the canvas contents (except for the pixels clipped out of the canvas in the operation).
- Zooming (not to be confused with scaling).
- Clearing area to a color.
- Pixel manipulation.
- Line, rectangle and ellipse drawing.
- Extensible API to add your own methods.
- Allows the creation of multiple independent instances at the same time in different parts of your DOM.
Fiddle
The actual code
function TableCanvas(target, pp) {
var w = -1, h = -1, zoomH = 1, zoomV = 1, defaultColor = "rgb(255,255,255)";
var rows = {};
var tbl = document.createElement("table");
var tblBody = document.createElement("tbody");
this.remake = function(p) {
var newW = p.w ? p.w : w, newH = p.h ? p.h : h;
if (newW < 0 || newH < 0) newW = newH = 0;
if (p.zoomH) zoomH = p.zoomH >= 1 ? p.zoomH : 1;
if (p.zoomV) zoomV = p.zoomV >= 1 ? p.zoomV : 1;
if (p.defaultColor) defaultColor = p.defaultColor;
for (var j = 0; j < newH; j++) {
var isNewRow = j >= h;
if (isNewRow) {
var tr = document.createElement("tr");
rows[j] = {tr: tr, cells: {}};
tblBody.appendChild(tr);
}
for (var i = 0; i < newW; i++) {
var isNewCell = isNewRow || i >= w;
if (isNewCell) {
var td = document.createElement("td");
td.style.background = defaultColor ? defaultColor : "rgb(255,255,255)";
rows[j].cells[i] = {td: td};
rows[j].tr.appendChild(td);
}
rows[j].cells[i].td.style.paddingLeft = (zoomH - 1) + "px";
rows[j].cells[i].td.style.paddingTop = (zoomV - 1) + "px";
}
}
for (var j = h - 1; j >= 0; j--) {
for (var i = w - 1; i >= newW; i--) {
rows[j].tr.removeChild(rows[j].cells[i].td);
delete rows[j].cells[i];
}
if (j >= newH) {
tblBody.removeChild(rows[j].tr);
delete rows[j];
}
}
w = newW;
h = newH;
return this;
};
tbl.appendChild(tblBody);
target.appendChild(tbl);
tbl.setAttribute("border", "0");
tbl.style.borderSpacing = "0";
this.getTd = function(p) {
return (p.x >= 0 && p.y >= 0 && p.x < w && p.y < h) ? rows[p.y].cells[p.x].td : null;
};
this.getPixelColor = function(p) {
return (p.x >= 0 && p.y >= 0 && p.x < w && p.y < h) ? rows[p.y].cells[p.x].td.style.background : null;
};
this.setPixelColor = function(p) {
if (p.x >= 0 && p.y >= 0 && p.x < w && p.y < h) {
rows[p.y].cells[p.x].td.style.background = p.color ? p.color : defaultColor ? defaultColor : "rgb(255,255,255)";
}
return this;
};
this.drawRectangle = function(p) {
for (var a = Math.min(p.x1, p.x2); a <= Math.max(p.x1, p.x2); a++) {
for (var b = Math.min(p.y1, p.y2); b <= Math.max(p.y1, p.y2); b++) {
this.setPixelColor({x: a, y: b, color: p.color});
}
}
return this;
};
this.drawLine = function(p) {
var t, x1 = p.x1, x2 = p.x2, y1 = p.y1, y2 = p.y2;
if (Math.abs(x2 - x1) > Math.abs(y2 - y1)) {
if (x1 > x2) { t = x1; x1 = x2; x2 = t; t = y1; y1 = y2; y2 = t; }
for (var a = x1; a <= x2; a++) {
this.setPixelColor({y: y1 + (y1 === y2 ? 0 : Math.floor((a - x1) * (y2 - y1) / (x2 - x1))), x: a, color: p.color});
}
} else {
if (y1 > y2) { t = x1; x1 = x2; x2 = t; t = y1; y1 = y2; y2 = t; }
for (var a = y1; a <= y2; a++) {
this.setPixelColor({x: x1 + (x1 === x2 ? 0 : Math.floor((a - y1) * (x2 - x1) / (y2 - y1))), y: a, color: p.color});
}
}
return this;
};
this.drawEllipse = function(p) {
var t, x1 = p.x1, x2 = p.x2, y1 = p.y1, y2 = p.y2;
if (x1 > x2) { t = x1; x1 = x2; x2 = t; }
if (y1 > y2) { t = y1; y1 = y2; y2 = t; }
var rx = (x2 - x1 + 1) / 2, ry = (y2 - y1 + 1) / 2, cx = (x2 + x1) / 2, cy = (y2 + y1) / 2;
for (var a = x1; a <= x2; a++) {
for (var b = y1; b <= y2; b++) {
if ((a - cx) * (a - cx) / (rx * rx) + (b - cy) * (b - cy) / (ry * ry) <= 1) this.setPixelColor({x: a, y: b, color: p.color});
}
}
return this;
};
this.clear = function(c) {
this.drawRectangle({x1: 0, y1: 0, x2: w - 1, y2: h - 1, color: c});
return this;
};
this.getWidth = function() { return w; };
this.getHeight = function() { return h; };
this.remake(pp);
}
The test code
var body = document.getElementsByTagName("body")[0];
var divA = document.createElement("div"), divB = document.createElement("div");
body.appendChild(divA);
body.appendChild(divB);
divA.style.cssFloat = "left";
divA.style.marginRight = "10px";
divB.style.cssFloat = "left";
var canvas, canvas2, i = 0, f, commands = [
function() {
console.log("Create a magenta canvas");
canvas = new TableCanvas({target: divA, w: 50, h: 50, defaultColor: "rgb(255,0,255)"});
},
function() { console.log("Draw black pixel"); canvas.setPixelColor({x: 2, y: 2, color: "rgb(0,0,0)"}); },
function() { console.log("Zoom 10x horizontal, 5x vertical"); canvas.remake({zoomH: 10, zoomV: 5}); },
function() { console.log("Draw green pixel"); canvas.setPixelColor({x: 9, y: 16, color: "rgb(0,255,0)"}); },
function() { console.log("Zoom to 3x"); canvas.remake({zoomH: 3, zoomV: 3}); },
function() { console.log("Paint as yellow"); canvas.clear("rgb(255,255,0)"); },
function() { console.log("Draw second green pixel"); canvas.setPixelColor({x: 29, y: 36, color: "rgb(0,192,0)"}); },
function() { console.log("Resize 1"); canvas.remake({w: 50, h: 60, defaultColor: "rgb(0,0,255)"}); },
function() { console.log("Resize 2"); canvas.remake({w: 60, h: 45}); },
function() { console.log("Resize 3"); canvas.remake({w: 45, h: 50, defaultColor: "rgb(255,0,0)"}); },
function() { console.log("Resize 4"); canvas.remake({w: 60, h: 60}); },
function() {
console.log("Create a green second canvas");
canvas2 = new TableCanvas({target: divB, w: 50, h: 50, defaultColor: "rgb(0,255,0)"});
},
function() {
console.log("Draw smiley");
canvas2.drawEllipse({x1: 1, y1: 1, x2: 48, y2: 48, color: "rgb(255,255,0)"})
.drawEllipse({x1: 10, y1: 10, x2: 20, y2: 20, color: "rgb(0,0,0)"})
.drawEllipse({x1: 30, y1: 10, x2: 40, y2: 20, color: "rgb(0,0,0)"})
.drawLine({x1: 10, y1: 30, x2: 15, y2: 35, color: "rgb(0,0,0)"})
.drawLine({x1: 15, y1: 35, x2: 35, y2: 35, color: "rgb(0,0,0)"})
.drawLine({x1: 35, y1: 35, x2: 40, y2: 30, color: "rgb(0,0,0)"});
},
function() { console.log("Draw cyan rectangle"); canvas.drawRectangle({x1: 10, y1: 10, x2: 45, y2: 45, color: "rgb(0,255,255)"}); },
function() { console.log("Draw orange ellipse"); canvas.drawEllipse({x1: 28, y1: 14, x2: 38, y2: 49, color: "rgb(255,128,0)"}); },
function() { console.log("Draw blue line"); canvas.drawLine({x1: 12, x2: 20, y1: 40, y2: 5, color: "rgb(0,128,255)"}); },
function() { console.log("Zoom to 4x"); canvas2.remake({zoomH: 4, zoomV: 4, h: 80}); },
function() { console.log("Draw black line"); canvas.drawLine({x1: 4, y1: -9, x2: 32, y2: 60, color: "rgb(0,0,0)"}); },
function() { console.log("Zoom to 2x3"); canvas2.remake({zoomV: 3}); },
function() { console.log("Zoom to 1x"); canvas2.remake({zoomH: 1, zoomV: 1}); },
function() {}
];
f = function() {
(commands[i])();
i++;
if (i < commands.length) setTimeout(f, 400);
};
f();
Be more clear about the do not label the cells with coordinates requirement. What is exactly considered a labeled cell? I think that adding something like
id='cell4-8'
orclass='cell4-8'
is labeling, but just adding them to a javascript object like{"cell1-1": ..., "cell1-2": ..., "cell1-3": ...}
is ok. Am I right? – Victor Stafusa – 2014-02-16T15:09:58.280@Victor You are right. I'm changing the post now. Thanks! – ioanD – 2014-02-16T15:10:48.067