5
\$\begingroup\$

The challenge

Make a JavaScript program that can use a HTML table as a canvas, having each cell a pixel. You can either input the position and color values in a form or by using the console.

Score calculation

  • +10 points for not using JQuery
  • +15 points for the cells scaling equally to keep the same table aspect ratio when the browser is scaled
  • +15 points for accomplishing the last thing without using any kind of CSS
  • +[number of upvotes]
  • +20 points if you do not label the cells' tags with coordinates (it counts if you make an JavaScript array like {"cell1-1": ..., "cell1-2": ..., "cell1-3": ...} but it doesn't count when you label the <td> tag itself, like <td id="cell-7-9"> or <td class="cell-8-1">)

So if you have 67 upvotes, no JQuery (+10), the cells scaling (+15) but by using CSS (no +15) and you do not label the cells (+20), then your score will be 67 + 10 + 15 + 0 + 20 = 112.

The challenge ends on 20 Feb 2014, 4:00 PM GMT+0.

WINNERS:
Doorknob -> 61 + 3 = 64 points
Victor, with 60 + 1 = 61 points
they have very close scores so I couldn't exclude Victor.

\$\endgroup\$
  • \$\begingroup\$ 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' or class='cell4-8' is labeling, but just adding them to a javascript object like {"cell1-1": ..., "cell1-2": ..., "cell1-3": ...} is ok. Am I right? \$\endgroup\$ – Victor Stafusa Feb 16 '14 at 15:09
  • \$\begingroup\$ @Victor You are right. I'm changing the post now. Thanks! \$\endgroup\$ – ioanD Feb 16 '14 at 15:10
4
\$\begingroup\$

Score: 61

10 (no jQuery) + 15 (scaling cells) + 15 (the scaling part uses no CSS) + 20 (no labels) + 1 (upvotes)

When you click on a cell, it turns the current color.

JavaScript

var w = h = 20, color = 'red'
function createTable() {
    var tbl = document.createElement('table')
    tbl.id = 'tbl'
    for (var i = 0; i < h; i++) {
        var tr = document.createElement('tr')
        for (var i2 = 0; i2 < w; i2++) {
            var td = document.createElement('td')
            td.addEventListener('mousedown', draw)
            tr.appendChild(td)
        }
        tbl.appendChild(tr)
    }
    document.body.appendChild(tbl)
    return tbl
}
function killTable(tbl) {
    tbl.parentNode.removeChild(tbl)
}
var tbl = createTable()

function draw(e) {
   e.preventDefault()
   this.style.backgroundColor = color
}

document.getElementById('finalize').addEventListener('click', finalize)
function finalize() {
    var tds = document.getElementsByTagName('td')
    for (var i = 0; i < tds.length; i++) {
        tds[i].style.border = 'none'
        tds[i].removeEventListener('mousedown', draw)
    }
    document.getElementById('optionsWrapper').style.display = 'none'
}

document.getElementById('changeColor').addEventListener('click', changeColor)
function changeColor() {
    color = document.getElementById('color').value
}

document.getElementById('changeDims').addEventListener('click', changeDims)
function changeDims() {
    var dims = document.getElementById('dims').value.split('x')
    w = +dims[0], h = +dims[1]

    killTable(tbl)
    tbl = createTable()
}

HTML

<div id='optionsWrapper'>
    <button id='finalize'>Finalize</button><br/>
    <label for='color'>Color: </label><input id='color' type='text' value='red' /><button id='changeColor'>Go</button><br/>
    <label for='dims'>Change dimensions (WARNING: will erase drawing): </label><input id='dims' type='text' value='20x20' /><button id='changeDims'>Go</button>
</div>

CSS

td {
    width: 5px;
    height: 5px;
    padding: 0;
    border: 1px solid black;
}

table {
    border-collapse: collapse;
}

JSFiddle

http://jsfiddle.net/D9u2N/1/

Screenshots

Zoomed in view (while editing, click to enlarge to full size):

screenshot

Finalized view (zoomed out):

screenshot 2

\$\endgroup\$
2
\$\begingroup\$

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();
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.