• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1const objectAssign = require('object-assign');
2const Cell = require('./cell');
3const { ColSpanCell, RowSpanCell } = Cell;
4
5(function() {
6  function layoutTable(table) {
7    table.forEach(function(row, rowIndex) {
8      row.forEach(function(cell, columnIndex) {
9        cell.y = rowIndex;
10        cell.x = columnIndex;
11        for (let y = rowIndex; y >= 0; y--) {
12          let row2 = table[y];
13          let xMax = y === rowIndex ? columnIndex : row2.length;
14          for (let x = 0; x < xMax; x++) {
15            let cell2 = row2[x];
16            while (cellsConflict(cell, cell2)) {
17              cell.x++;
18            }
19          }
20        }
21      });
22    });
23  }
24
25  function maxWidth(table) {
26    let mw = 0;
27    table.forEach(function(row) {
28      row.forEach(function(cell) {
29        mw = Math.max(mw, cell.x + (cell.colSpan || 1));
30      });
31    });
32    return mw;
33  }
34
35  function maxHeight(table) {
36    return table.length;
37  }
38
39  function cellsConflict(cell1, cell2) {
40    let yMin1 = cell1.y;
41    let yMax1 = cell1.y - 1 + (cell1.rowSpan || 1);
42    let yMin2 = cell2.y;
43    let yMax2 = cell2.y - 1 + (cell2.rowSpan || 1);
44    let yConflict = !(yMin1 > yMax2 || yMin2 > yMax1);
45
46    let xMin1 = cell1.x;
47    let xMax1 = cell1.x - 1 + (cell1.colSpan || 1);
48    let xMin2 = cell2.x;
49    let xMax2 = cell2.x - 1 + (cell2.colSpan || 1);
50    let xConflict = !(xMin1 > xMax2 || xMin2 > xMax1);
51
52    return yConflict && xConflict;
53  }
54
55  function conflictExists(rows, x, y) {
56    let i_max = Math.min(rows.length - 1, y);
57    let cell = { x: x, y: y };
58    for (let i = 0; i <= i_max; i++) {
59      let row = rows[i];
60      for (let j = 0; j < row.length; j++) {
61        if (cellsConflict(cell, row[j])) {
62          return true;
63        }
64      }
65    }
66    return false;
67  }
68
69  function allBlank(rows, y, xMin, xMax) {
70    for (let x = xMin; x < xMax; x++) {
71      if (conflictExists(rows, x, y)) {
72        return false;
73      }
74    }
75    return true;
76  }
77
78  function addRowSpanCells(table) {
79    table.forEach(function(row, rowIndex) {
80      row.forEach(function(cell) {
81        for (let i = 1; i < cell.rowSpan; i++) {
82          let rowSpanCell = new RowSpanCell(cell);
83          rowSpanCell.x = cell.x;
84          rowSpanCell.y = cell.y + i;
85          rowSpanCell.colSpan = cell.colSpan;
86          insertCell(rowSpanCell, table[rowIndex + i]);
87        }
88      });
89    });
90  }
91
92  function addColSpanCells(cellRows) {
93    for (let rowIndex = cellRows.length - 1; rowIndex >= 0; rowIndex--) {
94      let cellColumns = cellRows[rowIndex];
95      for (let columnIndex = 0; columnIndex < cellColumns.length; columnIndex++) {
96        let cell = cellColumns[columnIndex];
97        for (let k = 1; k < cell.colSpan; k++) {
98          let colSpanCell = new ColSpanCell();
99          colSpanCell.x = cell.x + k;
100          colSpanCell.y = cell.y;
101          cellColumns.splice(columnIndex + 1, 0, colSpanCell);
102        }
103      }
104    }
105  }
106
107  function insertCell(cell, row) {
108    let x = 0;
109    while (x < row.length && row[x].x < cell.x) {
110      x++;
111    }
112    row.splice(x, 0, cell);
113  }
114
115  function fillInTable(table) {
116    let h_max = maxHeight(table);
117    let w_max = maxWidth(table);
118    for (let y = 0; y < h_max; y++) {
119      for (let x = 0; x < w_max; x++) {
120        if (!conflictExists(table, x, y)) {
121          let opts = { x: x, y: y, colSpan: 1, rowSpan: 1 };
122          x++;
123          while (x < w_max && !conflictExists(table, x, y)) {
124            opts.colSpan++;
125            x++;
126          }
127          let y2 = y + 1;
128          while (y2 < h_max && allBlank(table, y2, opts.x, opts.x + opts.colSpan)) {
129            opts.rowSpan++;
130            y2++;
131          }
132
133          let cell = new Cell(opts);
134          cell.x = opts.x;
135          cell.y = opts.y;
136          insertCell(cell, table[y]);
137        }
138      }
139    }
140  }
141
142  function generateCells(rows) {
143    return rows.map(function(row) {
144      if (!Array.isArray(row)) {
145        let key = Object.keys(row)[0];
146        row = row[key];
147        if (Array.isArray(row)) {
148          row = row.slice();
149          row.unshift(key);
150        } else {
151          row = [key, row];
152        }
153      }
154      return row.map(function(cell) {
155        return new Cell(cell);
156      });
157    });
158  }
159
160  function makeTableLayout(rows) {
161    let cellRows = generateCells(rows);
162    layoutTable(cellRows);
163    fillInTable(cellRows);
164    addRowSpanCells(cellRows);
165    addColSpanCells(cellRows);
166    return cellRows;
167  }
168
169  module.exports = {
170    makeTableLayout: makeTableLayout,
171    layoutTable: layoutTable,
172    addRowSpanCells: addRowSpanCells,
173    maxWidth: maxWidth,
174    fillInTable: fillInTable,
175    computeWidths: makeComputeWidths('colSpan', 'desiredWidth', 'x', 1),
176    computeHeights: makeComputeWidths('rowSpan', 'desiredHeight', 'y', 1),
177  };
178})();
179
180function makeComputeWidths(colSpan, desiredWidth, x, forcedMin) {
181  return function(vals, table) {
182    let result = [];
183    let spanners = [];
184    table.forEach(function(row) {
185      row.forEach(function(cell) {
186        if ((cell[colSpan] || 1) > 1) {
187          spanners.push(cell);
188        } else {
189          result[cell[x]] = Math.max(result[cell[x]] || 0, cell[desiredWidth] || 0, forcedMin);
190        }
191      });
192    });
193
194    vals.forEach(function(val, index) {
195      if (typeof val === 'number') {
196        result[index] = val;
197      }
198    });
199
200    //spanners.forEach(function(cell){
201    for (let k = spanners.length - 1; k >= 0; k--) {
202      let cell = spanners[k];
203      let span = cell[colSpan];
204      let col = cell[x];
205      let existingWidth = result[col];
206      let editableCols = typeof vals[col] === 'number' ? 0 : 1;
207      for (let i = 1; i < span; i++) {
208        existingWidth += 1 + result[col + i];
209        if (typeof vals[col + i] !== 'number') {
210          editableCols++;
211        }
212      }
213      if (cell[desiredWidth] > existingWidth) {
214        let i = 0;
215        while (editableCols > 0 && cell[desiredWidth] > existingWidth) {
216          if (typeof vals[col + i] !== 'number') {
217            let dif = Math.round((cell[desiredWidth] - existingWidth) / editableCols);
218            existingWidth += dif;
219            result[col + i] += dif;
220            editableCols--;
221          }
222          i++;
223        }
224      }
225    }
226
227    objectAssign(vals, result);
228    for (let j = 0; j < vals.length; j++) {
229      vals[j] = Math.max(forcedMin, vals[j] || 0);
230    }
231  };
232}
233