2

i have a matrix of objects:

var data = [[
    {"value": "1"},
    {"value": "2"},
    {"value": "2"},
    {"value": "4"}
], [
    {"value": "1"},
    {"value": "2"},
    {"value": "2"},
    {"value": "4"}
], [
    {"value": "1"},
    {"value": "3"},
    {"value": "3"},
    {"value": "5"}
]];

separated cells

and I would like to render the matirx out as a html table with combined cells.

enter image description here

The sample of rendering matrix to htm table with separated cells:

var data = [[
    {"value": "1"}, {"value": "1"}, {"value": "1"}
], [
    {"value": "2"}, {"value": "2"}, {"value": "3"}
], [
    {"value": "2"}, {"value": "2"}, {"value": "3"}
], [
    {"value": "4"}, {"value": "4"}, {"value": "5"}
]];

function renderCombinedTable(data) {
    // TODO : realize
}

function renderSeparatedTable(data) {
    var xTable = document.createElement("table");
    for (var i = 0, ii = data.length; i < ii; i++) {
        xTable.appendChild(xTRs = document.createElement("tr"));
        for (var j = 0, jj = data[i].length; j < jj; j++) {
            var xTD = document.createElement("td");
            xTD.appendChild(document.createTextNode(data[i][j].value));
            xTRs.appendChild(xTD);
        }
    }
    return xTable;
}

document.body.appendChild(renderSeparatedTable(data));
td {
    border: solid 1px;
}

How do I get the table with combined cells?

I want the output to look something like this:

combined cells

madreason
  • 437
  • 4
  • 17

4 Answers4

3

Solved with code below

var data = [[
    {"value": "1"}, {"value": "1"}, {"value": "1"}
], [
    {"value": "2"}, {"value": "2"}, {"value": "3"}
], [
    {"value": "2"}, {"value": "2"}, {"value": "3"}
], [
    {"value": "4"}, {"value": "4"}, {"value": "5"}
]];

function renderCombinedTable(data) {
  var cell = [];
  var rows = data.length
      ,colums = data[0].length;

  var i, j;
  for (i = 0; i < rows; ++i) {
    cell[i] = [];
    for (j = 0; j < colums; ++j) {
      if (i > 0 && j > 0 && data[i][j].value === data[i-1][j-1].value) {
        cell[i][j] = cell[i-1][j-1];
      } else if (j > 0 && data[i][j].value === data[i][j - 1].value) {
        cell[i][j] = cell[i][j - 1];
        ++cell[i][j].w;
      } else if (i > 0 && data[i][j].value === data[i - 1][j].value) {
        cell[i][j] = cell[i - 1][j];
        ++cell[i][j].h;
      } else {
        cell[i][j] = {
          val: data[i][j].value,
          x: j,  y: i,
          w: 1,  h: 1
        };
      }
    }
  }
 
  
  var xTable = document.createElement("table");
  xTable.border = 1;
  for (var i = 0; i < rows; ++i) {
        xTable.appendChild(xTRs = document.createElement("tr"));
        for (j = 0; j < colums; ++j) {
          if (i > 0 && cell[i][j] === cell[i - 1][j]) {
            continue;
          } else if (j > 0 && cell[i][j] === cell[i][j - 1]) {
            continue;
          } else if (i > 0 && j > 0 && cell[i][j] === cell[i-1][j-1]) {
            continue;
          }
          var xTD = document.createElement("td");
          xTD.width = 30;
          if (cell[i][j].w > 1) {
            xTD.colSpan = cell[i][j].w;
          }
          if (cell[i][j].h > 1) {
            xTD.rowSpan = cell[i][j].h;
          }
          xTD.appendChild(document.createTextNode(cell[i][j].val));
          xTRs.appendChild(xTD);
        }
    }
  return xTable;
}

function renderSeparatedTable(data) {
    var xTable = document.createElement("table");
    for (var i = 0, ii = data.length; i < ii; i++) {
        xTable.appendChild(xTRs = document.createElement("tr"));
        for (var j = 0, jj = data[i].length; j < jj; j++) {
            var xTD = document.createElement("td");
            xTD.appendChild(document.createTextNode(data[i][j].value));
            xTRs.appendChild(xTD);
        }
    }
    return xTable;
}

document.body.appendChild(renderSeparatedTable(data));
document.body.appendChild(renderCombinedTable(data));

Note that, the 2nd row seems equal, but its infact 2col td and 1col td. Thought: 1. Create a cell info array

  1. Go through the array, if current is equal to left, or top, ref to its left or top, and increase the width/height.

  2. Like what you did, go through the cell store, but if cell is equal to its left, top or lefttop skip it.

  3. When draw a td, check if its width or height is larger than 1, if it is, set its rowSpan/colSpan.

Hope its what you want.

Note: This work only solves that same number is appears in rectangles, if the input is:

[
 [1, 1, 1],
 [1, 2, 2],
 [3, 3, 3]
]

This may not work, you need to check more in a row. And if input is:

[
 [1, 2, 1],
 [2, 2, 2],
 [3, 2, 3]
]

...Maybe you should consider just use separated ones :P

fuyushimoya
  • 9,715
  • 3
  • 27
  • 34
  • I like the algorithm here. – Mike Brant Jun 17 '15 at 18:03
  • It's great, thx. I've upgraded the code on [jsfiddle](http://jsfiddle.net/madreason/bfp8erdt/3/). Yes, data is always rectrangled and top boxes include lower boxes without crossings. Sorry, I'm replace ++x to x++ in your code) Why do you use it? more faster? – madreason Jun 18 '15 at 11:29
  • 1
    It's just my coding habit, where I was told by my mentor, it's no difference in javascript I believe, but it seems there's some minor diff on other language between ++x and x++. See this [post](http://stackoverflow.com/questions/12396740/is-x-more-efficient-than-x-in-java) – fuyushimoya Jun 18 '15 at 11:47
  • 1
    There is a difference between `++x` and `x++` in JavaScript, but not when it's used as the final expression in a `for` loop. See http://jsfiddle.net/y5r8bhbv/ – Rick Hitchcock Jun 18 '15 at 13:03
1

I would think that in order to implement this, you would need to do 3 things:

First, you would need to sort the elements in each row based on their value (that is if you want values like 2,3,2 to become 2,2,3 such that the 2's can be combined). This also assumes you are not getting the values in a sorted manner currently. If they are already sorted, then you are one step ahead. I am not going to show this step, as this should be something you can easily do (or find out how to do).

Next, you would need to reduce the objects in each row down to a data structure that can explicitly indicate the number of repeated values for eventual use in colspan. That might look something like this:

var reducedData = [];
for (i = 0; i < data.length; i++) {
    var row = data[i];
    var tempRow = [];
    var tempRowIndex = -1;
    var currentValue = '';
    for (j = 0; j < row.length; j++) {
        var element = row[j];
        if(element.value !== currentValue) {
            // we have encountered new value in this row
            currentValue = element.value;
            // we need to create a new container for this value
            tempRow.push({value: currentValue, colspan: 1});
            tempRowIndex++;     
        } else {
            // we have encountered another element with same value
            // increment colspan
            tempRow[tempRowIndex].colspan++;
        }
    }
    reducedData.push(tempRow);
}

Finally, utilize this new array for rendering your table. You would use colspan value for each element in new array to set colspan attribute on each <td>. Again I am not going to show this code is this should be pretty similar to what you already have, with the difference being you just assign colspan attribute.

Note: I just noted that you are also wanting to combine rows, which I didn't comment on. Here you would add a step after the fist column-based reduction to reduce rows. Here I am assuming that you can only only reduced rows if they are an exact match. You would take a similar approach to the logic I have shown above and deep compare the reduced rows with subsequent reduced rows. Don't have time to show code example here, but hopefully this gets you off to good start.

Mike Brant
  • 70,514
  • 10
  • 99
  • 103
1

Use your renderSeparatedTable function to create the table.

Then iterate backwards through the rows and cells, adding colSpan and rowSpan to the first instance of matching cells. Keep a list of duplicate cells in the process.

After iterating through the rows and cells, delete all the duplicate cells.

Snippet:

var data = [[{"value": "1"}, {"value": "1"}, {"value": "1"}], 
            [{"value": "2"}, {"value": "2"}, {"value": "3"}], 
            [{"value": "2"}, {"value": "2"}, {"value": "3"}], 
            [{"value": "4"}, {"value": "4"}, {"value": "5"}],
            [{"value": "5"}, {"value": "5"}, {"value": "5"}]
           ];

function renderCombinedTable(data) {
  var tb= renderSeparatedTable(data),
      rows= tb.rows,
      obs= [];  //obsolete matching cells
  
  for(var i = rows.length-1 ; i>=0 ; i--) {
    cells= rows[i].cells;
    for(var j = cells.length-1 ; j>=0 ; j--) {
      if(j && (cells[j].textContent === cells[j-1].textContent)) {
        cells[j-1].colSpan= (cells[j].colSpan || 0) + 1;
        obs.push(cells[j]);
      }
      else if(i && (cells[j].textContent === rows[i-1].cells[j].textContent)) {
        rows[i-1].cells[j].rowSpan= (cells[j].rowSpan || 0) + 1;
        obs.push(cells[j]);
      }
    }
  }
  while(obs.length) {
    obs[0].parentNode.removeChild(obs.shift());
  }
  return tb;
}

function renderSeparatedTable(data) {
    var xTable = document.createElement("table");
    for (var i = 0, ii = data.length; i < ii; i++) {
        xTable.appendChild(xTRs = document.createElement("tr"));
        for (var j = 0, jj = data[i].length; j < jj; j++) {
            var xTD = document.createElement("td");
            xTD.appendChild(document.createTextNode(data[i][j].value));
            xTRs.appendChild(xTD);
        }
    }
    return xTable;
}

document.body.appendChild(renderSeparatedTable(data));

document.body.appendChild(renderCombinedTable(data));
td {
  border: solid 1px;
  width: 20px;
  text-align: center;
}
Rick Hitchcock
  • 35,202
  • 5
  • 48
  • 79
  • It's an interesting solution. But this solution will be slower than solution with additional index-matrix. Thx – madreason Jun 18 '15 at 11:12
0

One way that you could accomplish the combined cells effect you're looking for is by using the colspan and rowspan html attributes for your and tags.

For example if you have <td colspan="2">1</td> then that table cell will extend for 2 columns.

I've mocked up a JSFiddle that roughly represents your desired output. The exception being the 2 cell doesn't extend both in columns and rows:

JSFiddle

Pavan Ravipati
  • 1,890
  • 14
  • 21
  • Yep, but I'm interested in an algorithm, which determines the number of rows and cells and their colspan and rowspan numbers – madreason Jun 17 '15 at 16:49