canvas-datagrid 0.3.24 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/draw.js CHANGED
@@ -108,6 +108,37 @@ export default function (self) {
108
108
  '</svg>\n',
109
109
  );
110
110
  }
111
+ /**
112
+ * @param {number[]} coords [x0,y0, x1,y1, x2,y2, ...]
113
+ */
114
+ function strokeLines(coords) {
115
+ if (coords.length < 4) return;
116
+ self.ctx.beginPath();
117
+ self.ctx.moveTo(
118
+ coords[0] + self.canvasOffsetLeft,
119
+ coords[1] + self.canvasOffsetTop,
120
+ );
121
+ for (let i = 2; i < coords.length; i += 2) {
122
+ const x = coords[i] + self.canvasOffsetLeft;
123
+ const y = coords[i + 1] + self.canvasOffsetTop;
124
+ self.ctx.lineTo(x, y);
125
+ }
126
+ self.ctx.stroke();
127
+ }
128
+ /**
129
+ * @param {number} x based-X (left-top)
130
+ * @param {number} y based-Y (left-top)
131
+ * @param {number} width
132
+ * @param {boolean} collapsed true: '+'; false: '-'
133
+ */
134
+ function drawGroupHandle(x, y, width, collapsed) {
135
+ fillRect(x, y, width, width);
136
+ strokeRect(x, y, width, width);
137
+ const cx = x + width * 0.5;
138
+ const cy = y + width * 0.5;
139
+ strokeLines([x + width * 0.2, cy, x + width * 0.78, cy]);
140
+ if (collapsed) strokeLines([cx, y + width * 0.22, cx, y + width * 0.8]);
141
+ }
111
142
  function drawOrderByArrow(x, y) {
112
143
  var mt = self.style.columnHeaderOrderByArrowMarginTop * self.scale,
113
144
  ml = self.style.columnHeaderOrderByArrowMarginLeft * self.scale,
@@ -164,6 +195,54 @@ export default function (self) {
164
195
  self.ctx.fill();
165
196
  return ml + aw + mr;
166
197
  }
198
+ function drawCellTreeIcon(cell, tree, rowTree) {
199
+ const parentCount = rowTree ? tree.parentCount : 0;
200
+ const iconSize = self.style.cellTreeIconWidth * self.scale,
201
+ marginTop = self.style.cellTreeIconMarginTop * self.scale,
202
+ marginRight = self.style.cellTreeIconMarginRight * self.scale,
203
+ marginLeft =
204
+ self.style.cellTreeIconMarginLeft * self.scale +
205
+ parentCount * (iconSize + cell.paddingLeft);
206
+
207
+ let x = cell.x + cell.paddingLeft + self.canvasOffsetLeft + marginLeft,
208
+ y = cell.y + self.canvasOffsetTop + marginTop;
209
+
210
+ if (tree.icon) {
211
+ self.ctx.beginPath();
212
+ const oldFillStyle = self.ctx.fillStyle;
213
+ const oldStrokeStyle = self.ctx.strokeStyle;
214
+
215
+ if (cell.hovered && self.hovers.onCellTreeIcon) {
216
+ self.ctx.fillStyle = self.style.cellTreeIconHoverFillColor;
217
+ } else {
218
+ self.ctx.fillStyle = self.style.cellTreeIconFillColor;
219
+ }
220
+
221
+ self.ctx.fillRect(x, y, iconSize, iconSize);
222
+ self.ctx.strokeStyle = self.style.cellTreeIconBorderColor;
223
+ self.ctx.rect(x, y, iconSize, iconSize);
224
+ self.ctx.stroke();
225
+
226
+ self.ctx.beginPath();
227
+ if (tree.expand) {
228
+ self.ctx.moveTo(x + 3, y + iconSize * 0.5);
229
+ self.ctx.lineTo(x + iconSize - 3, y + iconSize * 0.5);
230
+ } else {
231
+ self.ctx.moveTo(x + 2, y + iconSize * 0.5);
232
+ self.ctx.lineTo(x + iconSize - 2, y + iconSize * 0.5);
233
+ self.ctx.moveTo(x + iconSize * 0.5, y + 2);
234
+ self.ctx.lineTo(x + iconSize * 0.5, y + iconSize - 2);
235
+ }
236
+ self.ctx.lineWidth = self.style.cellTreeIconLineWidth;
237
+ self.ctx.strokeStyle = self.style.cellTreeIconLineColor;
238
+ self.ctx.stroke();
239
+
240
+ self.ctx.strokeStyle = oldStrokeStyle;
241
+ self.ctx.fillStyle = oldFillStyle;
242
+ }
243
+
244
+ return marginLeft + iconSize + marginRight;
245
+ }
167
246
  function drawFilterButtonArrow(x, y) {
168
247
  var mt =
169
248
  ((self.style.filterButtonHeight - self.style.filterButtonArrowHeight) /
@@ -460,7 +539,7 @@ export default function (self) {
460
539
  height: cell.calculatedLineHeight * lines.length,
461
540
  };
462
541
  }
463
- function drawText(cell) {
542
+ function drawText(cell, treeCellPadding = 0) {
464
543
  var ll = cell.text.lines.length,
465
544
  h = cell.fontHeight * cell.lineHeight,
466
545
  x,
@@ -476,12 +555,18 @@ export default function (self) {
476
555
  0.5,
477
556
  0,
478
557
  ) + h,
479
- hPos = cell.paddingLeft + cell.treeArrowWidth + cell.orderByArrowWidth;
558
+ hPos =
559
+ cell.paddingLeft +
560
+ treeCellPadding +
561
+ cell.treeArrowWidth +
562
+ cell.orderByArrowWidth;
480
563
  if (cell.horizontalAlignment === 'right') {
481
- hPos = cell.paddingLeft + cell.paddedWidth - line.width;
564
+ hPos =
565
+ cell.paddingLeft + treeCellPadding + cell.paddedWidth - line.width;
482
566
  } else if (cell.horizontalAlignment === 'center') {
483
567
  hPos =
484
568
  cell.paddingLeft +
569
+ treeCellPadding +
485
570
  (cell.paddedWidth + cell.paddingRight) / 2 -
486
571
  line.width / 2;
487
572
  }
@@ -538,6 +623,7 @@ export default function (self) {
538
623
  s = self.getSchema(),
539
624
  x = 0,
540
625
  n = Math.min(self.frozenColumn, s.length),
626
+ collapsedGroups = self.getCollapsedColumnGroups(),
541
627
  column;
542
628
  hiddenFrozenColumnCount = 0;
543
629
  while (x < n) {
@@ -545,7 +631,15 @@ export default function (self) {
545
631
  if (column.hidden) {
546
632
  hiddenFrozenColumnCount += 1;
547
633
  } else {
548
- w += self.getColumnWidth(x);
634
+ const isCollapsed =
635
+ collapsedGroups.findIndex(
636
+ (group) => x >= group.from && x <= group.to,
637
+ ) >= 0;
638
+ if (isCollapsed) {
639
+ hiddenFrozenColumnCount += 1;
640
+ } else {
641
+ w += self.getColumnWidth(x);
642
+ }
549
643
  }
550
644
  x += 1;
551
645
  }
@@ -615,6 +709,16 @@ export default function (self) {
615
709
  u = self.currentCell || {},
616
710
  columnHeaderCellHeight = self.getColumnHeaderCellHeight(),
617
711
  rowHeaderCellWidth = self.getRowHeaderCellWidth(),
712
+ rowGroupsAreaWidth = self.getRowGroupAreaWidth(),
713
+ columnGroupsAreaHeight = self.getColumnGroupAreaHeight(),
714
+ /** key: boundRowIndex, value: `{y,h}` */
715
+ rowGroupsRectInfo = {},
716
+ /** value: `{y,h}` */
717
+ rowGroupsFrozenInfo = {},
718
+ /** key: columnIndex, value: `{x,w}` */
719
+ columnGroupsRectInfo = {},
720
+ collapsedColumnGroups = self.getCollapsedColumnGroups(),
721
+ collapsedRowGroups = self.getCollapsedRowGroups(),
618
722
  cellHeight = self.style.cellHeight;
619
723
  drawCount += 1;
620
724
  p = performance.now();
@@ -623,6 +727,31 @@ export default function (self) {
623
727
  if (viewData.length > self.orders.rows.length) {
624
728
  self.createRowOrders();
625
729
  }
730
+ function saveRowGroupsRectInfo(cell) {
731
+ let index = cell.boundRowIndex;
732
+ if (index >= -1 === false)
733
+ if (cell.rowIndex === -1) index = -1;
734
+ else return;
735
+ if (rowGroupsRectInfo[index]) return;
736
+ rowGroupsRectInfo[index] = { y: cell.y, h: cell.height };
737
+ }
738
+ function saveColumnGroupsRectInfo(cell) {
739
+ const index = cell.columnIndex;
740
+ if (columnGroupsRectInfo[index]) return;
741
+ columnGroupsRectInfo[index] = { x: cell.x, w: cell.width };
742
+ }
743
+ /**
744
+ * @param {number} columnIndex
745
+ * @returns {boolean}
746
+ */
747
+ function isColumnCollapsedByGroup(columnIndex) {
748
+ return (
749
+ collapsedColumnGroups.findIndex(
750
+ (group) => columnIndex >= group.from && columnIndex <= group.to,
751
+ ) >= 0
752
+ );
753
+ }
754
+
626
755
  function drawScrollBars() {
627
756
  var drawCorner,
628
757
  en = self.scrollBox.entities,
@@ -976,6 +1105,15 @@ export default function (self) {
976
1105
  activeHeader: activeHeader,
977
1106
  value:
978
1107
  isHeader && !isRowHeader ? header.title || header.name : rawValue,
1108
+ isRowTree:
1109
+ rowOrderIndex >= 0 &&
1110
+ columnOrderIndex == self.cellTree.rowTreeColIndex &&
1111
+ self.cellTree.rows.length > 0 &&
1112
+ self.cellTree.rows[rowOrderIndex].icon,
1113
+ isColumnTree:
1114
+ columnOrderIndex >= 0 &&
1115
+ self.cellTree.columns[rowOrderIndex] &&
1116
+ self.cellTree.columns[rowOrderIndex][columnOrderIndex].icon,
979
1117
  };
980
1118
  cell.calculatedLineHeight =
981
1119
  cell.fontHeight * cell.lineHeight + cell.lineSpacing;
@@ -987,6 +1125,8 @@ export default function (self) {
987
1125
  ? self.sizes.columns.cornerCell
988
1126
  : self.sizes.columns[headerIndex];
989
1127
  self.visibleCells.unshift(cell);
1128
+ saveRowGroupsRectInfo(cell);
1129
+ saveColumnGroupsRectInfo(cell);
990
1130
  if (self.dispatchEvent('beforerendercell', ev)) {
991
1131
  return;
992
1132
  }
@@ -1129,7 +1269,53 @@ export default function (self) {
1129
1269
  if (cell.innerHTML || header.type === 'html') {
1130
1270
  drawHtml(cell);
1131
1271
  } else {
1132
- drawText(cell);
1272
+ let treeCellPadding = 0,
1273
+ isDrawText = true;
1274
+ if (
1275
+ cell.columnIndex == self.cellTree.rowTreeColIndex &&
1276
+ !cell.isColumnHeader &&
1277
+ self.cellTree.rows.length > 0 &&
1278
+ Object.keys(self.cellTree.rows[cell.rowIndex]).length > 1
1279
+ )
1280
+ treeCellPadding = drawCellTreeIcon(
1281
+ cell,
1282
+ self.cellTree.rows[cell.rowIndex],
1283
+ true,
1284
+ );
1285
+
1286
+ if (
1287
+ !cell.isRowHeader &&
1288
+ cell.rowIndex > 0 &&
1289
+ self.cellTree.columns[cell.rowIndex - 1] &&
1290
+ self.cellTree.columns[cell.rowIndex - 1][cell.columnIndex]
1291
+ .icon
1292
+ ) {
1293
+ for (let r = cell.rowIndex - 1; r >= 0; r--) {
1294
+ if (!self.cellTree.columns[r]) break;
1295
+ if (!self.cellTree.columns[r][cell.columnIndex].icon) break;
1296
+ if (!self.cellTree.columns[r][cell.columnIndex].expand) {
1297
+ isDrawText = false;
1298
+ break;
1299
+ }
1300
+ }
1301
+ }
1302
+ if (
1303
+ isDrawText &&
1304
+ !cell.isRowHeader &&
1305
+ self.cellTree.columns[cell.rowIndex] &&
1306
+ self.cellTree.columns[cell.rowIndex][cell.columnIndex].icon
1307
+ ) {
1308
+ if (
1309
+ self.viewData.length > 0 &&
1310
+ self.viewData[cell.rowIndex][cell.columnIndex].length > 0
1311
+ )
1312
+ treeCellPadding = drawCellTreeIcon(
1313
+ cell,
1314
+ self.cellTree.columns[cell.rowIndex][cell.columnIndex],
1315
+ false,
1316
+ );
1317
+ }
1318
+ if (isDrawText) drawText(cell, treeCellPadding);
1133
1319
  }
1134
1320
 
1135
1321
  if (wrap && cell.text && cell.text.height > rowHeight) {
@@ -1171,10 +1357,18 @@ export default function (self) {
1171
1357
  const previousRowNumber = self.getBoundRowIndexFromViewRowIndex(
1172
1358
  rowOrderIndex - 1,
1173
1359
  );
1174
- const hasRowGap =
1360
+ let hasRowGap =
1175
1361
  previousRowNumber !== undefined &&
1176
1362
  cell.boundRowIndex > 0 &&
1177
1363
  cell.boundRowIndex - previousRowNumber > 1;
1364
+ if (hasRowGap && collapsedRowGroups.length > 0) {
1365
+ hasRowGap =
1366
+ collapsedRowGroups.find(
1367
+ (group) =>
1368
+ group.from === previousRowNumber &&
1369
+ group.to === cell.boundRowIndex,
1370
+ ) >= 0;
1371
+ }
1178
1372
 
1179
1373
  if (hasRowGap) {
1180
1374
  const barHeight = self.style.rowHeaderCellRowNumberGapHeight;
@@ -1237,9 +1431,10 @@ export default function (self) {
1237
1431
  ? self.originalData.length + 1
1238
1432
  : rowOrderIndex + 1;
1239
1433
 
1240
- const rowHeaderValue = self.hasActiveFilters()
1241
- ? filteredRowNumber
1242
- : rowIndex + 1;
1434
+ const rowHeaderValue =
1435
+ self.hasActiveFilters() || self.hasCollapsedRowGroup()
1436
+ ? filteredRowNumber
1437
+ : rowIndex + 1;
1243
1438
 
1244
1439
  const rowHeaderCell = { rowHeaderCell: rowHeaderValue };
1245
1440
  const headerDescription = {
@@ -1271,7 +1466,7 @@ export default function (self) {
1271
1466
  for (o = start; o < end; o += 1) {
1272
1467
  i = self.orders.columns[o];
1273
1468
  header = schema[i];
1274
- if (!header.hidden) {
1469
+ if (!header.hidden && !isColumnCollapsedByGroup(o)) {
1275
1470
  d = {
1276
1471
  title: header.title,
1277
1472
  name: header.name,
@@ -1383,12 +1578,14 @@ export default function (self) {
1383
1578
  columnOrderIndex < g;
1384
1579
  columnOrderIndex += 1
1385
1580
  ) {
1386
- headerIndex = self.orders.columns[columnOrderIndex];
1387
- x += drawCell(rowData, rowOrderIndex, rowIndex)(
1388
- schema[headerIndex],
1389
- headerIndex,
1390
- columnOrderIndex,
1391
- );
1581
+ if (!isColumnCollapsedByGroup(columnOrderIndex)) {
1582
+ headerIndex = self.orders.columns[columnOrderIndex];
1583
+ x += drawCell(rowData, rowOrderIndex, rowIndex)(
1584
+ schema[headerIndex],
1585
+ headerIndex,
1586
+ columnOrderIndex,
1587
+ );
1588
+ }
1392
1589
  if (x > self.width) {
1393
1590
  self.scrollIndexRight = columnOrderIndex;
1394
1591
  self.scrollPixelRight = x;
@@ -1405,12 +1602,14 @@ export default function (self) {
1405
1602
  columnOrderIndex < self.frozenColumn;
1406
1603
  columnOrderIndex += 1
1407
1604
  ) {
1408
- headerIndex = self.orders.columns[columnOrderIndex];
1409
- x += drawCell(rowData, rowOrderIndex, rowIndex)(
1410
- schema[headerIndex],
1411
- headerIndex,
1412
- columnOrderIndex,
1413
- );
1605
+ if (!isColumnCollapsedByGroup(columnOrderIndex)) {
1606
+ headerIndex = self.orders.columns[columnOrderIndex];
1607
+ x += drawCell(rowData, rowOrderIndex, rowIndex)(
1608
+ schema[headerIndex],
1609
+ headerIndex,
1610
+ columnOrderIndex,
1611
+ );
1612
+ }
1414
1613
  if (x > self.width) {
1415
1614
  break;
1416
1615
  }
@@ -1439,7 +1638,18 @@ export default function (self) {
1439
1638
  scrollTop: self.scrollBox.scrollTop,
1440
1639
  scrollLeft: self.scrollBox.scrollLeft,
1441
1640
  rowIndex: rowOrderIndex,
1641
+ columnGroupsAreaHeight: columnGroupsAreaHeight,
1642
+ rowGroupsAreaWidth: rowGroupsAreaWidth,
1442
1643
  };
1644
+ if (self.intf.parentNode) {
1645
+ const {
1646
+ columnGroupsAreaHeight,
1647
+ rowGroupsAreaWidth,
1648
+ } = self.intf.parentNode;
1649
+ treeGrid.parentNode.columnGroupsAreaHeight +=
1650
+ columnGroupsAreaHeight || 0;
1651
+ treeGrid.parentNode.rowGroupsAreaWidth += rowGroupsAreaWidth || 0;
1652
+ }
1443
1653
  self.visibleCells.unshift({
1444
1654
  rowIndex: rowOrderIndex,
1445
1655
  columnIndex: 0,
@@ -1464,6 +1674,7 @@ export default function (self) {
1464
1674
  self.visibleRows = [];
1465
1675
  schema = self.getSchema();
1466
1676
  self.visibleCells = [];
1677
+ self.visibleGroups = [];
1467
1678
  self.canvasOffsetTop = self.isChildGrid ? self.parentNode.offsetTop : 0.5;
1468
1679
  self.canvasOffsetLeft = self.isChildGrid
1469
1680
  ? self.parentNode.offsetLeft
@@ -1477,6 +1688,379 @@ export default function (self) {
1477
1688
  self.ctx.fillStyle = self.style.gridBackgroundColor;
1478
1689
  fillRect(0, 0, w, h);
1479
1690
  }
1691
+ function initGroupArea() {
1692
+ self.ctx.translate(rowGroupsAreaWidth, columnGroupsAreaHeight);
1693
+ }
1694
+ function drawGroupArea() {
1695
+ const mx = rowGroupsAreaWidth;
1696
+ const my = columnGroupsAreaHeight;
1697
+ const frozenColumnsWidth = getFrozenColumnsWidth();
1698
+ const frozenRowsHeight =
1699
+ rowGroupsFrozenInfo.y + rowGroupsFrozenInfo.h - columnHeaderCellHeight;
1700
+ const onTheLeft = self.attributes.columnGroupIndicatorPosition === 'left';
1701
+ const onTheTop = self.attributes.rowGroupIndicatorPosition === 'top';
1702
+
1703
+ /** @type {CanvasRenderingContext2D} */
1704
+ const ctx = self.ctx;
1705
+ ctx.save();
1706
+ ctx.fillStyle = self.style.groupingAreaBackgroundColor;
1707
+ fillRect(0, -my, w, my);
1708
+ fillRect(-mx, -my, mx, h);
1709
+ ctx.restore();
1710
+
1711
+ //#region Columns Grouping
1712
+ /** it extends `self.groupedRows` */
1713
+ const groupedColumns = [];
1714
+ for (let row = 0; row < self.groupedColumns.length; row++) {
1715
+ const groups = self.groupedColumns[row];
1716
+ for (let j = 0; j < groups.length; j++) {
1717
+ groupedColumns.push(Object.assign({ row }, groups[j]));
1718
+ }
1719
+ }
1720
+ if (groupedColumns.length > 0) {
1721
+ const rowHeight = self.style.columnGroupRowHeight;
1722
+ const toggleHandleSize = rowHeight * 0.5;
1723
+ const toggleHandlePadding = (rowHeight - toggleHandleSize) * 0.5;
1724
+
1725
+ for (let i = 0; i < groupedColumns.length; i++) {
1726
+ const group = groupedColumns[i];
1727
+ const { row, collapsed } = group;
1728
+
1729
+ const topY = -my + row * rowHeight;
1730
+ const centerY = topY + rowHeight * 0.5;
1731
+ const bottomY = topY + rowHeight - toggleHandlePadding;
1732
+ const leftmostX =
1733
+ rowHeaderCellWidth - toggleHandleSize - toggleHandlePadding;
1734
+
1735
+ const drawGroupHandleAtX = (x) =>
1736
+ drawGroupHandle(
1737
+ x,
1738
+ topY + toggleHandlePadding,
1739
+ toggleHandleSize,
1740
+ group.collapsed,
1741
+ );
1742
+ const pushToVisibleGroups = (leftX, rightX) =>
1743
+ self.visibleGroups.push({
1744
+ type: 'c',
1745
+ collapsed,
1746
+ from: group.from,
1747
+ to: group.to,
1748
+ row,
1749
+ x: leftX + mx,
1750
+ y: topY + my,
1751
+ x2: rightX + mx,
1752
+ y2: bottomY + my,
1753
+ });
1754
+
1755
+ //#region check the relationship between this group and frozen columns
1756
+ const crossTheFrozen =
1757
+ group.from < self.frozenColumn && group.to >= self.frozenColumn;
1758
+ const notInFrozen = group.from >= self.frozenColumn;
1759
+ //#endregion
1760
+
1761
+ if (collapsed) {
1762
+ let leftX = leftmostX + toggleHandleSize;
1763
+ // This group is not sticking on the first column
1764
+ if (group.from > 0) {
1765
+ let colIndex = group.to + 1;
1766
+ let col = columnGroupsRectInfo[colIndex];
1767
+ if (!col) {
1768
+ colIndex = group.from - 1;
1769
+ col = columnGroupsRectInfo[colIndex];
1770
+ if (!col) continue; // don't draw this group indicator because it is invisible
1771
+ leftX = col.x + col.w - toggleHandleSize * 0.5;
1772
+ } else {
1773
+ leftX = col.x + toggleHandlePadding;
1774
+ }
1775
+ if (colIndex >= self.frozenColumn) {
1776
+ const compare =
1777
+ frozenColumnsWidth + rowHeaderCellWidth - toggleHandlePadding;
1778
+ // don't draw this group indicator because it is hidden by frozen columns
1779
+ if (leftX < compare) continue;
1780
+ }
1781
+ }
1782
+ const rightX = leftX + toggleHandleSize;
1783
+ ctx.save();
1784
+ ctx.strokeStyle = self.style.groupIndicatorColor;
1785
+ ctx.fillStyle = self.style.groupIndicatorBackgroundColor;
1786
+ drawGroupHandleAtX(leftX);
1787
+ ctx.restore();
1788
+ pushToVisibleGroups(leftX, rightX);
1789
+ } // end of collapsed group
1790
+
1791
+ /** @type {number} pointer for loop */
1792
+ let ptr;
1793
+ let left = columnGroupsRectInfo[group.from];
1794
+ let right = columnGroupsRectInfo[group.to];
1795
+
1796
+ let containsBegining = true;
1797
+ let containsEnd = true;
1798
+ ptr = group.from;
1799
+ while (!left && ptr < group.to) {
1800
+ left = columnGroupsRectInfo[++ptr];
1801
+ containsBegining = false;
1802
+ }
1803
+ ptr = group.to;
1804
+ while (!right && ptr > group.from) {
1805
+ right = columnGroupsRectInfo[--ptr];
1806
+ containsEnd = false;
1807
+ }
1808
+ if (!left || !right) continue;
1809
+ let rightX = right.x + right.w;
1810
+ let leftX = left.x;
1811
+ if (crossTheFrozen) {
1812
+ const rightCompare = columnGroupsRectInfo[self.frozenColumn - 1];
1813
+ if (rightCompare) {
1814
+ let compareX = rightCompare.x + rightCompare.w;
1815
+ if (!onTheLeft) compareX += toggleHandleSize;
1816
+ if (compareX >= rightX) {
1817
+ right = rightCompare;
1818
+ rightX = right.x + right.w;
1819
+ containsEnd = false;
1820
+ }
1821
+ }
1822
+ }
1823
+
1824
+ let minLeftX =
1825
+ rowHeaderCellWidth + (notInFrozen ? frozenColumnsWidth : 0);
1826
+ if (rightX + (onTheLeft ? 0 : toggleHandleSize) < minLeftX) continue;
1827
+
1828
+ rightX -= toggleHandlePadding;
1829
+ leftX += toggleHandlePadding;
1830
+
1831
+ ctx.save();
1832
+ ctx.strokeStyle = self.style.groupIndicatorColor;
1833
+ ctx.fillStyle = self.style.groupIndicatorBackgroundColor;
1834
+ const lineCoords = [];
1835
+
1836
+ if (onTheLeft) {
1837
+ // avoid lines from two groups be overlapping
1838
+ minLeftX += toggleHandlePadding * 2;
1839
+ if (leftX < minLeftX) leftX = minLeftX;
1840
+ if (group.from === 0) leftX -= toggleHandlePadding * 2;
1841
+ if (rightX >= leftX) {
1842
+ lineCoords.push(leftX, centerY, rightX, centerY);
1843
+ if (containsEnd) lineCoords.push(rightX, bottomY);
1844
+ } else {
1845
+ rightX = leftX;
1846
+ }
1847
+
1848
+ leftX -= toggleHandleSize;
1849
+ drawGroupHandleAtX(leftX);
1850
+ // add more clickable area into `visibleGroups`
1851
+ rightX += toggleHandlePadding - 1;
1852
+ } else {
1853
+ // handle on the right
1854
+ if (leftX < minLeftX) leftX = minLeftX;
1855
+ if (group.from === 0) leftX -= toggleHandlePadding * 2;
1856
+ if (containsEnd) {
1857
+ if (group.to === self.frozenColumn - 1) {
1858
+ rightX -= toggleHandleSize;
1859
+ } else {
1860
+ rightX += toggleHandlePadding * 2;
1861
+ }
1862
+ drawGroupHandleAtX(rightX);
1863
+ }
1864
+ if (leftX > rightX) {
1865
+ leftX = rightX;
1866
+ } else {
1867
+ if (group.from === 0) {
1868
+ containsBegining = true;
1869
+ leftX = leftmostX + toggleHandleSize;
1870
+ }
1871
+ if (containsBegining) lineCoords.push(leftX, bottomY);
1872
+ lineCoords.push(leftX, centerY, rightX, centerY);
1873
+ }
1874
+
1875
+ // add more clickable area into `visibleGroups`
1876
+ leftX -= toggleHandlePadding + 1;
1877
+ if (containsEnd) rightX += toggleHandleSize;
1878
+ }
1879
+ strokeLines(lineCoords);
1880
+ ctx.restore();
1881
+ pushToVisibleGroups(leftX, rightX);
1882
+ }
1883
+ }
1884
+ //#endregion Columns Grouping
1885
+
1886
+ //#region Rows Grouping
1887
+ /** it extends `self.groupedRows` */
1888
+ const groupedRows = [];
1889
+ for (let col = 0; col < self.groupedRows.length; col++) {
1890
+ const groups = self.groupedRows[col];
1891
+ for (let j = 0; j < groups.length; j++) {
1892
+ groupedRows.push(Object.assign({ col }, groups[j]));
1893
+ }
1894
+ }
1895
+ if (groupedRows.length > 0) {
1896
+ const colWidth = self.style.rowGroupColumnWidth;
1897
+ const toggleHandleSize = colWidth * 0.5;
1898
+ const toggleHandlePadding = (colWidth - toggleHandleSize) * 0.5;
1899
+
1900
+ for (let i = 0; i < groupedRows.length; i++) {
1901
+ const group = groupedRows[i];
1902
+ const { col, collapsed } = group;
1903
+
1904
+ const leftX = -mx + col * colWidth;
1905
+ const centerX = leftX + colWidth * 0.5;
1906
+ const rightX = leftX + colWidth - toggleHandlePadding;
1907
+ const topmostY =
1908
+ columnHeaderCellHeight - toggleHandleSize - toggleHandlePadding;
1909
+
1910
+ const drawGroupHandleAtY = (y) =>
1911
+ drawGroupHandle(
1912
+ leftX + toggleHandlePadding,
1913
+ y,
1914
+ toggleHandleSize,
1915
+ group.collapsed,
1916
+ );
1917
+ const pushToVisibleGroups = (topY, bottomY) =>
1918
+ self.visibleGroups.push({
1919
+ type: 'r',
1920
+ collapsed,
1921
+ from: group.from,
1922
+ to: group.to,
1923
+ col,
1924
+ x: leftX + mx,
1925
+ y: topY + my,
1926
+ x2: rightX + mx,
1927
+ y2: bottomY + my,
1928
+ });
1929
+
1930
+ //#region check the relationship between this group and frozen columns
1931
+ const crossTheFrozen =
1932
+ group.from < self.frozenRow && group.to >= self.frozenRow;
1933
+ const notInFrozen = group.from >= self.frozenRow;
1934
+ //#endregion
1935
+
1936
+ if (collapsed) {
1937
+ let topY = topmostY + toggleHandleSize;
1938
+ // This group is not sticking on the first column
1939
+ if (group.from > 0) {
1940
+ let rowIndex = group.to + 1;
1941
+ let row = rowGroupsRectInfo[rowIndex];
1942
+ if (!row) {
1943
+ rowIndex = group.from - 1;
1944
+ row = rowGroupsRectInfo[rowIndex];
1945
+ if (!row) continue; // don't draw this group indicator because it is invisible
1946
+ topY = row.y + row.h - toggleHandleSize * 0.5;
1947
+ } else {
1948
+ topY = row.y;
1949
+ }
1950
+ if (rowIndex >= self.frozenRow) {
1951
+ const compare =
1952
+ frozenRowsHeight +
1953
+ columnHeaderCellHeight -
1954
+ toggleHandlePadding;
1955
+ // don't draw this group indicator because it is hidden by frozen columns
1956
+ if (topY < compare) continue;
1957
+ }
1958
+ }
1959
+ const bottomY = topY + toggleHandleSize;
1960
+ ctx.save();
1961
+ ctx.strokeStyle = self.style.groupIndicatorColor;
1962
+ ctx.fillStyle = self.style.groupIndicatorBackgroundColor;
1963
+ drawGroupHandleAtY(topY);
1964
+ ctx.restore();
1965
+ pushToVisibleGroups(topY, bottomY);
1966
+ } // end of collapsed group
1967
+
1968
+ /** @type {number} pointer for loop */
1969
+ let ptr;
1970
+ let top = rowGroupsRectInfo[group.from];
1971
+ let bottom = rowGroupsRectInfo[group.to];
1972
+
1973
+ let containsBegining = true;
1974
+ let containsEnd = true;
1975
+ ptr = group.from;
1976
+ while (!top && ptr < group.to) {
1977
+ top = rowGroupsRectInfo[++ptr];
1978
+ containsBegining = false;
1979
+ }
1980
+ ptr = group.to;
1981
+ while (!bottom && ptr > group.from) {
1982
+ bottom = rowGroupsRectInfo[--ptr];
1983
+ containsEnd = false;
1984
+ }
1985
+ if (!top || !bottom) continue;
1986
+ let bottomY = bottom.y + bottom.h;
1987
+ let topY = top.y;
1988
+ if (crossTheFrozen) {
1989
+ const bottomCompare = rowGroupsRectInfo[self.frozenRow - 1];
1990
+ if (bottomCompare) {
1991
+ let compareY = bottomCompare.y + bottomCompare.h;
1992
+ if (!onTheTop) compareY += toggleHandleSize;
1993
+ if (compareY >= bottomY) {
1994
+ bottom = bottomCompare;
1995
+ bottomY = bottom.y + bottom.h;
1996
+ containsEnd = false;
1997
+ }
1998
+ }
1999
+ }
2000
+
2001
+ let minTopY =
2002
+ columnHeaderCellHeight + (notInFrozen ? frozenRowsHeight : 0);
2003
+ if (bottomY + (onTheTop ? 0 : toggleHandleSize) < minTopY) continue;
2004
+
2005
+ bottomY -= toggleHandlePadding;
2006
+ topY += toggleHandlePadding;
2007
+
2008
+ ctx.save();
2009
+ ctx.strokeStyle = self.style.groupIndicatorColor;
2010
+ ctx.fillStyle = self.style.groupIndicatorBackgroundColor;
2011
+ const lineCoords = [];
2012
+
2013
+ if (onTheTop) {
2014
+ // avoid lines from two groups be overlapping
2015
+ minTopY += toggleHandlePadding * 2;
2016
+ if (topY < minTopY) topY = minTopY;
2017
+ if (group.from === 0) topY -= toggleHandlePadding * 2;
2018
+ if (bottomY >= topY) {
2019
+ lineCoords.push(centerX, topY, centerX, bottomY);
2020
+ if (containsEnd) lineCoords.push(rightX, bottomY);
2021
+ } else {
2022
+ bottomY = topY;
2023
+ }
2024
+
2025
+ topY -= toggleHandleSize;
2026
+ drawGroupHandleAtY(topY);
2027
+
2028
+ // add more clickable area into `visibleGroups`
2029
+ bottomY += toggleHandlePadding - 1;
2030
+ } else {
2031
+ // handle on the bottom
2032
+ if (topY < minTopY) topY = minTopY;
2033
+ if (group.from === 0) topY -= toggleHandlePadding * 2;
2034
+ if (containsEnd) {
2035
+ if (group.to === self.frozenRow - 1) {
2036
+ bottomY -= toggleHandleSize;
2037
+ } else {
2038
+ // bottomY += toggleHandlePadding * 2;
2039
+ }
2040
+ drawGroupHandleAtY(bottomY);
2041
+ }
2042
+ if (topY > bottomY) {
2043
+ topY = bottomY;
2044
+ } else {
2045
+ if (group.from === 0) {
2046
+ containsBegining = true;
2047
+ topY = topmostY + toggleHandleSize;
2048
+ }
2049
+ if (containsBegining) lineCoords.push(rightX, topY);
2050
+ lineCoords.push(centerX, topY, centerX, bottomY);
2051
+ // add more clickable area into `visibleGroups`
2052
+ topY -= toggleHandlePadding + 1;
2053
+ }
2054
+ // add more clickable area into `visibleGroups`
2055
+ if (containsEnd) bottomY += toggleHandleSize;
2056
+ }
2057
+ strokeLines(lineCoords);
2058
+ ctx.restore();
2059
+ pushToVisibleGroups(topY, bottomY);
2060
+ }
2061
+ }
2062
+ //#endregion Rows Grouping
2063
+ }
1480
2064
  function drawFrozenRows() {
1481
2065
  var rowOrderIndex,
1482
2066
  ln = Math.min(viewData.length, self.frozenRow);
@@ -1535,6 +2119,12 @@ export default function (self) {
1535
2119
  rowOrderIndex = self.orders.rows[rowIndex];
1536
2120
  self.scrollIndexBottom = rowIndex;
1537
2121
  self.scrollPixelBottom = y;
2122
+ if (
2123
+ self.cellTree.rows.length > 0 &&
2124
+ Object.keys(self.cellTree.rows[rowOrderIndex]).length > 0 &&
2125
+ self.cellTree.rows[rowOrderIndex].hide
2126
+ )
2127
+ continue;
1538
2128
  if (!drawRow(rowOrderIndex, rowIndex)) {
1539
2129
  break;
1540
2130
  }
@@ -1550,12 +2140,14 @@ export default function (self) {
1550
2140
  columnOrderIndex < g;
1551
2141
  columnOrderIndex += 1
1552
2142
  ) {
1553
- headerIndex = self.orders.columns[columnOrderIndex];
1554
- x += drawCell(self.newRow, viewData.length, viewData.length)(
1555
- schema[headerIndex],
1556
- headerIndex,
1557
- columnOrderIndex,
1558
- );
2143
+ if (!isColumnCollapsedByGroup(columnOrderIndex)) {
2144
+ headerIndex = self.orders.columns[columnOrderIndex];
2145
+ x += drawCell(self.newRow, viewData.length, viewData.length)(
2146
+ schema[headerIndex],
2147
+ headerIndex,
2148
+ columnOrderIndex,
2149
+ );
2150
+ }
1559
2151
  if (x > self.width + self.scrollBox.scrollLeft) {
1560
2152
  break;
1561
2153
  }
@@ -1767,6 +2359,7 @@ export default function (self) {
1767
2359
  self.ctx.restore();
1768
2360
  }
1769
2361
  function drawFrozenMarkers() {
2362
+ if (self.dragStartObject !== undefined) return;
1770
2363
  var my = self.lastFrozenRowPixel - self.style.frozenMarkerWidth,
1771
2364
  mx = self.lastFrozenColumnPixel - self.style.frozenMarkerBorderWidth,
1772
2365
  xHover =
@@ -1801,14 +2394,16 @@ export default function (self) {
1801
2394
  : self.style.frozenMarkerBorderColor;
1802
2395
  fillRect(0, my, self.width, self.style.frozenMarkerWidth);
1803
2396
  strokeRect(0, my, self.width, self.style.frozenMarkerWidth);
2397
+ const height =
2398
+ self.style.frozenMarkerWidth + self.style.frozenMarkerBorderWidth;
1804
2399
  self.visibleCells.unshift({
1805
2400
  x: 0,
1806
2401
  y: my,
1807
- height:
1808
- self.style.frozenMarkerWidth + self.style.frozenMarkerBorderWidth,
2402
+ height,
1809
2403
  width: self.width,
1810
2404
  style: 'frozen-row-marker',
1811
2405
  });
2406
+ rowGroupsFrozenInfo = { y: my, h: height };
1812
2407
  }
1813
2408
  if (self.freezeMarkerPosition) {
1814
2409
  self.ctx.fillStyle = self.style.frozenMarkerActiveColor;
@@ -2047,6 +2642,7 @@ export default function (self) {
2047
2642
  self.ctx.save();
2048
2643
  initDraw();
2049
2644
  drawBackground();
2645
+ initGroupArea();
2050
2646
  drawFrozenRows();
2051
2647
  drawRows();
2052
2648
  drawActiveCell();
@@ -2061,6 +2657,7 @@ export default function (self) {
2061
2657
  if (checkScrollHeight) {
2062
2658
  self.resize(true);
2063
2659
  }
2660
+ drawGroupArea();
2064
2661
  drawDebug();
2065
2662
  drawPerfLines();
2066
2663
  if (self.dispatchEvent('afterdraw', {})) {