canvas-datagrid 0.25.3 → 0.26.0

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
@@ -2,6 +2,8 @@
2
2
  /*globals XMLSerializer: false, define: true, Blob: false, MutationObserver: false, requestAnimationFrame: false, performance: false, btoa: false*/
3
3
  'use strict';
4
4
 
5
+ import { isCellSelected } from './selections/util';
6
+
5
7
  export default function (self) {
6
8
  var perfCounters = [],
7
9
  cachedImagesDrawn = false,
@@ -108,6 +110,98 @@ export default function (self) {
108
110
  '</svg>\n',
109
111
  );
110
112
  }
113
+ /**
114
+ * @param {number[]} coords [x0,y0, x1,y1, x2,y2, ...]
115
+ * @param {boolean} [fill] fill the area that construct by these lines but not stroke
116
+ */
117
+ function drawLines(coords, fill) {
118
+ if (coords.length < 4) return;
119
+ self.ctx.beginPath();
120
+ self.ctx.moveTo(
121
+ coords[0] + self.canvasOffsetLeft,
122
+ coords[1] + self.canvasOffsetTop,
123
+ );
124
+ for (let i = 2; i < coords.length; i += 2) {
125
+ const x = coords[i] + self.canvasOffsetLeft;
126
+ const y = coords[i + 1] + self.canvasOffsetTop;
127
+ self.ctx.lineTo(x, y);
128
+ }
129
+ if (fill) self.ctx.fill();
130
+ else self.ctx.stroke();
131
+ }
132
+ /**
133
+ * @param {number} x based-X (left-top)
134
+ * @param {number} y based-Y (left-top)
135
+ * @param {number} width
136
+ * @param {boolean} collapsed true: '+'; false: '-'
137
+ */
138
+ function drawGroupHandle(x, y, width, collapsed) {
139
+ fillRect(x, y, width, width);
140
+ strokeRect(x, y, width, width);
141
+ const cx = x + width * 0.5;
142
+ const cy = y + width * 0.5;
143
+ drawLines([x + width * 0.2, cy, x + width * 0.78, cy]);
144
+ if (collapsed) drawLines([cx, y + width * 0.22, cx, y + width * 0.8]);
145
+ }
146
+ /**
147
+ * @param {number} x
148
+ * @param {number} y
149
+ * @param {number} size
150
+ * @param {string} dir Direction of the triangle, one of the 't','b','l' and 'r'
151
+ * @param {boolean} [active]
152
+ */
153
+ function drawUnhideIndicator(x, y, size, dir, active) {
154
+ const minPadding = size * 0.2;
155
+ const maxPadding = size * 0.3;
156
+ /** The long edge width of the triangle */
157
+ const longEdge = size - 2 * minPadding;
158
+ /** The median width of the triangle */
159
+ const median = size - 2 * maxPadding;
160
+ const halfLongEdge = longEdge * 0.5;
161
+ let x0, y0;
162
+ let coords, borderCoords;
163
+ switch (dir) {
164
+ case 'r':
165
+ x0 = x + maxPadding;
166
+ y0 = y + minPadding;
167
+ borderCoords = [x, y, x + size, y, x + size, y + size, x, y + size];
168
+ coords = [x0, y0, x0, y0 + longEdge, x0 + median, y0 + halfLongEdge];
169
+ break;
170
+ case 'l':
171
+ x0 = x + size - maxPadding;
172
+ y0 = y + minPadding;
173
+ borderCoords = [x + size, y, x, y, x, y + size, x + size, y + size];
174
+ coords = [x0, y0, x0, y0 + longEdge, x0 - median, y0 + halfLongEdge];
175
+ break;
176
+ case 't':
177
+ x0 = x + minPadding;
178
+ y0 = y + size - maxPadding;
179
+ borderCoords = [x, y + size, x, y, x + size, y, x + size, y + size];
180
+ coords = [x0, y0, x0 + longEdge, y0, x0 + halfLongEdge, y0 - median];
181
+ break;
182
+ case 'b':
183
+ x0 = x + minPadding;
184
+ y0 = y + maxPadding;
185
+ borderCoords = [x, y, x, y + size, x + size, y + size, x + size, y];
186
+ coords = [x0, y0, x0 + longEdge, y0, x0 + halfLongEdge, y0 + median];
187
+ break;
188
+ }
189
+
190
+ if (active) {
191
+ self.ctx.strokeStyle = self.style.unhideIndicatorBorderColor;
192
+ self.ctx.lineWidth = 2;
193
+ drawLines(borderCoords);
194
+
195
+ self.ctx.fillStyle = self.style.unhideIndicatorBackgroundColor;
196
+ let offset = dir === 'r' || dir === 'b' ? 1 : 0;
197
+ if (dir === 'l' || dir === 'r')
198
+ fillRect(x - offset, y, size + offset, size);
199
+ else fillRect(x, y - offset, size, size + offset);
200
+ }
201
+
202
+ self.ctx.fillStyle = self.style.unhideIndicatorColor;
203
+ drawLines(coords, true);
204
+ }
111
205
  function drawOrderByArrow(x, y) {
112
206
  var mt = self.style.columnHeaderOrderByArrowMarginTop * self.scale,
113
207
  ml = self.style.columnHeaderOrderByArrowMarginLeft * self.scale,
@@ -122,15 +216,15 @@ export default function (self) {
122
216
  x = x + ml;
123
217
  y = y + mt;
124
218
  if (self.orderDirection === 'asc') {
125
- self.ctx.moveTo(x, y);
126
- self.ctx.lineTo(x + aw, y);
127
- self.ctx.lineTo(x + aw * 0.5, y + ah);
128
- self.ctx.moveTo(x, y);
129
- } else {
130
219
  self.ctx.lineTo(x, y + ah);
131
220
  self.ctx.lineTo(x + aw, y + ah);
132
221
  self.ctx.lineTo(x + aw * 0.5, y);
133
222
  self.ctx.lineTo(x, y + ah);
223
+ } else {
224
+ self.ctx.moveTo(x, y);
225
+ self.ctx.lineTo(x + aw, y);
226
+ self.ctx.lineTo(x + aw * 0.5, y + ah);
227
+ self.ctx.moveTo(x, y);
134
228
  }
135
229
  self.ctx.stroke();
136
230
  self.ctx.fill();
@@ -164,6 +258,82 @@ export default function (self) {
164
258
  self.ctx.fill();
165
259
  return ml + aw + mr;
166
260
  }
261
+ function drawCellTreeIcon(cell, tree, rowTree) {
262
+ const parentCount = rowTree ? tree.parentCount : 0;
263
+ const iconSize = self.style.cellTreeIconWidth * self.scale,
264
+ marginTop = self.style.cellTreeIconMarginTop * self.scale,
265
+ marginRight = self.style.cellTreeIconMarginRight * self.scale,
266
+ marginLeft =
267
+ self.style.cellTreeIconMarginLeft * self.scale +
268
+ parentCount * (iconSize + cell.paddingLeft);
269
+
270
+ let x = cell.x + cell.paddingLeft + self.canvasOffsetLeft + marginLeft,
271
+ y = cell.y + self.canvasOffsetTop + marginTop;
272
+
273
+ if (tree.icon) {
274
+ self.ctx.beginPath();
275
+ const oldFillStyle = self.ctx.fillStyle;
276
+ const oldStrokeStyle = self.ctx.strokeStyle;
277
+
278
+ if (cell.hovered && self.hovers.onCellTreeIcon) {
279
+ self.ctx.fillStyle = self.style.cellTreeIconHoverFillColor;
280
+ } else {
281
+ self.ctx.fillStyle = self.style.cellTreeIconFillColor;
282
+ }
283
+
284
+ self.ctx.fillRect(x, y, iconSize, iconSize);
285
+ self.ctx.strokeStyle = self.style.cellTreeIconBorderColor;
286
+ self.ctx.rect(x, y, iconSize, iconSize);
287
+ self.ctx.stroke();
288
+
289
+ self.ctx.beginPath();
290
+ if (tree.expand) {
291
+ self.ctx.moveTo(x + 3, y + iconSize * 0.5);
292
+ self.ctx.lineTo(x + iconSize - 3, y + iconSize * 0.5);
293
+ } else {
294
+ self.ctx.moveTo(x + 2, y + iconSize * 0.5);
295
+ self.ctx.lineTo(x + iconSize - 2, y + iconSize * 0.5);
296
+ self.ctx.moveTo(x + iconSize * 0.5, y + 2);
297
+ self.ctx.lineTo(x + iconSize * 0.5, y + iconSize - 2);
298
+ }
299
+ self.ctx.lineWidth = self.style.cellTreeIconLineWidth;
300
+ self.ctx.strokeStyle = self.style.cellTreeIconLineColor;
301
+ self.ctx.stroke();
302
+
303
+ self.ctx.strokeStyle = oldStrokeStyle;
304
+ self.ctx.fillStyle = oldFillStyle;
305
+ }
306
+
307
+ return marginLeft + iconSize + marginRight;
308
+ }
309
+ function drawFilterButtonArrow(x, y) {
310
+ var mt =
311
+ ((self.style.filterButtonHeight - self.style.filterButtonArrowHeight) /
312
+ 2) *
313
+ self.scale,
314
+ ml =
315
+ ((self.style.filterButtonWidth - self.style.filterButtonArrowWidth) /
316
+ 2) *
317
+ self.scale,
318
+ aw = self.style.filterButtonArrowWidth * self.scale,
319
+ ah = self.style.filterButtonArrowHeight * self.scale;
320
+ x += self.canvasOffsetLeft;
321
+ y += self.canvasOffsetTop;
322
+ self.ctx.fillStyle = self.style.filterButtonArrowColor;
323
+ self.ctx.strokeStyle = self.style.filterButtonArrowBorderColor;
324
+ self.ctx.beginPath();
325
+ x = x + ml;
326
+ y = y + mt;
327
+
328
+ self.ctx.moveTo(x, y);
329
+ self.ctx.lineTo(x + aw, y);
330
+ self.ctx.lineTo(x + aw * 0.5, y + ah);
331
+ self.ctx.moveTo(x, y);
332
+
333
+ self.ctx.stroke();
334
+ self.ctx.fill();
335
+ return ml + aw;
336
+ }
167
337
  function radiusRect(x, y, w, h, radius) {
168
338
  x += self.canvasOffsetLeft;
169
339
  y += self.canvasOffsetTop;
@@ -362,9 +532,10 @@ export default function (self) {
362
532
  elWidth = self.ctx.measureText(' ' + et).width;
363
533
  for (x = 0; x < words.length; x += 1) {
364
534
  word = words[x];
365
- var measure = self.ctx.measureText(word + splitChar);
535
+ var curSplitChar = word[word.length - 1] === '-' ? '' : splitChar;
536
+ var measure = self.ctx.measureText(word + curSplitChar);
366
537
  if (line.width + measure.width + elWidth < cell.paddedWidth) {
367
- line.value += word + splitChar;
538
+ line.value += word + curSplitChar;
368
539
  line.width += measure.width;
369
540
  continue;
370
541
  }
@@ -373,13 +544,17 @@ export default function (self) {
373
544
  // then back up and re-read new split set
374
545
  // this behavior seems right, it might not be
375
546
  if (/\w-\w/.test(word) && cell.paddedWidth < measure.width) {
376
- words.splice(x, 1, word.split('-')[0] + '-', word.split('-')[1]);
547
+ var arr = word.split('-');
548
+ arr = arr.map((item, index) => {
549
+ return index === arr.length - 1 ? item : item + '-';
550
+ });
551
+ words.splice(x, 1, ...arr);
377
552
  x -= 1;
378
553
  continue;
379
554
  }
380
555
  line = {
381
556
  width: measure.width,
382
- value: word + splitChar,
557
+ value: word + curSplitChar,
383
558
  };
384
559
  if (x === 0) {
385
560
  lines = [];
@@ -427,35 +602,58 @@ export default function (self) {
427
602
  height: cell.calculatedLineHeight * lines.length,
428
603
  };
429
604
  }
430
- function drawText(cell) {
605
+ function drawText(cell, treeCellPadding = 0) {
431
606
  var ll = cell.text.lines.length,
432
607
  h = cell.fontHeight * cell.lineHeight,
433
608
  x,
434
609
  line,
435
610
  wrap = self.style.cellWhiteSpace !== 'nowrap',
436
611
  textHeight = 0;
612
+ // text supplied by a rendertext handler may omit width/height (#460)
613
+ var textBlockHeight =
614
+ cell.text.height === undefined
615
+ ? cell.calculatedLineHeight * ll
616
+ : cell.text.height;
437
617
  for (x = 0; x < cell.text.lines.length; x += 1) {
438
618
  line = cell.text.lines[x];
619
+ if (line.width === undefined) {
620
+ line.width = self.ctx.measureText(
621
+ line.value === undefined || line.value === null
622
+ ? ''
623
+ : String(line.value),
624
+ ).width;
625
+ }
439
626
  var vPos =
440
627
  Math.max(
441
628
  (cell.height -
442
- (wrap ? cell.text.height : cell.calculatedLineHeight)) *
629
+ (wrap ? textBlockHeight : cell.calculatedLineHeight)) *
443
630
  0.5,
444
631
  0,
445
632
  ) + h,
446
- hPos = cell.paddingLeft + cell.treeArrowWidth + cell.orderByArrowWidth;
633
+ hPos =
634
+ cell.paddingLeft +
635
+ treeCellPadding +
636
+ cell.treeArrowWidth +
637
+ cell.orderByArrowWidth;
447
638
  if (cell.horizontalAlignment === 'right') {
448
- hPos = cell.paddingLeft + cell.paddedWidth - line.width;
639
+ hPos =
640
+ cell.paddingLeft + treeCellPadding + cell.paddedWidth - line.width;
449
641
  } else if (cell.horizontalAlignment === 'center') {
450
642
  hPos =
451
643
  cell.paddingLeft +
644
+ treeCellPadding +
452
645
  (cell.paddedWidth + cell.paddingRight) / 2 -
453
646
  line.width / 2;
454
647
  }
455
648
  if (cell.verticalAlignment === 'top') {
456
649
  vPos = cell.calculatedLineHeight;
457
650
  } else if (cell.verticalAlignment === 'bottom') {
458
- vPos = cell.height - cell.paddingBottom - cell.text.height;
651
+ // baseline of the last line sits on the bottom padding (#530)
652
+ vPos =
653
+ cell.height -
654
+ cell.paddingBottom -
655
+ textBlockHeight +
656
+ cell.calculatedLineHeight;
459
657
  }
460
658
  line.height = h + cell.lineSpacing;
461
659
  line.offsetLeft = hPos;
@@ -505,6 +703,7 @@ export default function (self) {
505
703
  s = self.getSchema(),
506
704
  x = 0,
507
705
  n = Math.min(self.frozenColumn, s.length),
706
+ collapsedGroups = self.getCollapsedColumnGroups(),
508
707
  column;
509
708
  hiddenFrozenColumnCount = 0;
510
709
  while (x < n) {
@@ -512,7 +711,15 @@ export default function (self) {
512
711
  if (column.hidden) {
513
712
  hiddenFrozenColumnCount += 1;
514
713
  } else {
515
- w += self.getColummnWidth(x);
714
+ const isCollapsed =
715
+ collapsedGroups.findIndex(
716
+ (group) => x >= group.from && x <= group.to,
717
+ ) >= 0;
718
+ if (isCollapsed) {
719
+ hiddenFrozenColumnCount += 1;
720
+ } else {
721
+ w += self.getColumnWidth(x);
722
+ }
516
723
  }
517
724
  x += 1;
518
725
  }
@@ -568,43 +775,100 @@ export default function (self) {
568
775
  c,
569
776
  h,
570
777
  w,
571
- s,
572
- r,
573
- rd,
778
+ schema,
779
+ rowIndex,
780
+ rowData,
574
781
  aCell,
575
- data = self.data || [],
782
+ viewData = self.viewData || [],
576
783
  bc = self.style.gridBorderCollapse === 'collapse',
577
784
  selectionBorders = [],
578
785
  moveBorders = [],
579
786
  selectionHandles = [],
580
787
  rowHeaders = [],
581
- l = data.length,
788
+ l = viewData.length,
582
789
  u = self.currentCell || {},
583
790
  columnHeaderCellHeight = self.getColumnHeaderCellHeight(),
584
791
  rowHeaderCellWidth = self.getRowHeaderCellWidth(),
585
- cellHeight = self.style.cellHeight;
792
+ rowGroupsAreaWidth = self.getRowGroupAreaWidth(),
793
+ columnGroupsAreaHeight = self.getColumnGroupAreaHeight(),
794
+ /** key: boundRowIndex, value: `{y,h}` */
795
+ rowGroupsRectInfo = {},
796
+ /** value: `{y,h}` */
797
+ rowGroupsFrozenInfo = {},
798
+ /** key: columnIndex, value: `{x,w}` */
799
+ columnGroupsRectInfo = {},
800
+ collapsedColumnGroups = self.getCollapsedColumnGroups(),
801
+ collapsedRowGroups = self.getCollapsedRowGroups(),
802
+ cellHeight = self.style.cellHeight,
803
+ currentRowIndexOffset = 0,
804
+ /** @type {Array<{from:number,plus:number}>} */
805
+ rowIndexOffsetByHiddenRows = self.hiddenRowRanges
806
+ .map((range) => ({
807
+ from: range[0],
808
+ plus: range[1] - range[0] + 1,
809
+ }))
810
+ .sort((a, b) => a.from - b.from);
586
811
  drawCount += 1;
587
812
  p = performance.now();
588
813
  self.visibleRowHeights = [];
589
814
  // if data length has changed, there is no way to know
590
- if (data.length > self.orders.rows.length) {
815
+ if (viewData.length > self.orders.rows.length) {
591
816
  self.createRowOrders();
592
817
  }
818
+ function saveRowGroupsRectInfo(cell) {
819
+ let index = cell.boundRowIndex;
820
+ if (index >= -1 === false)
821
+ if (cell.rowIndex === -1) index = -1;
822
+ else return;
823
+ if (rowGroupsRectInfo[index]) return;
824
+ rowGroupsRectInfo[index] = { y: cell.y, h: cell.height };
825
+ }
826
+ function saveColumnGroupsRectInfo(cell) {
827
+ const index = cell.columnIndex;
828
+ if (columnGroupsRectInfo[index]) return;
829
+ columnGroupsRectInfo[index] = { x: cell.x, w: cell.width };
830
+ }
831
+ /**
832
+ * @param {number} columnIndex
833
+ * @returns {boolean}
834
+ */
835
+ function isColumnCollapsedByGroup(columnIndex) {
836
+ return (
837
+ collapsedColumnGroups.findIndex(
838
+ (group) => columnIndex >= group.from && columnIndex <= group.to,
839
+ ) >= 0
840
+ );
841
+ }
842
+
593
843
  function drawScrollBars() {
594
844
  var drawCorner,
595
845
  en = self.scrollBox.entities,
596
846
  m = self.style.scrollBarBoxMargin * 2;
597
847
  self.ctx.strokeStyle = self.style.scrollBarBorderColor;
598
848
  self.ctx.lineWidth = self.style.scrollBarBorderWidth;
599
- en.horizontalBox.x =
600
- rowHeaderCellWidth +
601
- self.style.scrollBarBoxMargin +
602
- (en.horizontalBar.width - self.scrollBox.scrollBoxWidth) *
603
- (self.scrollBox.scrollLeft / self.scrollBox.scrollWidth);
849
+ if (self.frozenColumn > 0) {
850
+ en.horizontalBox.x =
851
+ rowHeaderCellWidth +
852
+ self.style.scrollBarBoxMargin +
853
+ self.scrollCache.x[self.frozenColumn - 1] +
854
+ (en.horizontalBar.width -
855
+ self.scrollCache.x[self.frozenColumn - 1] -
856
+ self.scrollBox.scrollBoxWidth) *
857
+ (self.scrollBox.scrollLeft / self.scrollBox.scrollWidth);
858
+ } else {
859
+ en.horizontalBox.x =
860
+ rowHeaderCellWidth +
861
+ self.style.scrollBarBoxMargin +
862
+ (en.horizontalBar.width - self.scrollBox.scrollBoxWidth) *
863
+ (self.scrollBox.scrollLeft / self.scrollBox.scrollWidth);
864
+ }
604
865
  en.verticalBox.y =
605
866
  columnHeaderCellHeight +
606
867
  self.style.scrollBarBoxMargin +
607
- (en.verticalBar.height - self.scrollBox.scrollBoxHeight) *
868
+ self.scrollCache.y[self.frozenRow] +
869
+ (en.verticalBar.height -
870
+ self.scrollBox.scrollBoxHeight -
871
+ self.scrollCache.y[self.frozenRow]) *
608
872
  (self.scrollBox.scrollTop / self.scrollBox.scrollHeight);
609
873
  if (self.scrollBox.horizontalBarVisible) {
610
874
  self.ctx.fillStyle = self.style.scrollBarBackgroundColor;
@@ -698,13 +962,17 @@ export default function (self) {
698
962
  selectionHandles.push([cell, 'tr']);
699
963
  cell.selectionHandle = 'tr';
700
964
  }
701
- if (
702
- cell.selectionBorderTop &&
703
- cell.selectionBorderLeft &&
704
- self.mobile
705
- ) {
706
- selectionHandles.push([cell, 'tl']);
707
- cell.selectionHandle = 'tl';
965
+ if (cell.selectionBorderTop && cell.selectionBorderLeft) {
966
+ if (self.mobile) {
967
+ selectionHandles.push([cell, 'tl']);
968
+ cell.selectionHandle = 'tl';
969
+ }
970
+ if (self.fillOverlay.handle) {
971
+ self.fillOverlay.snapTo = {
972
+ x: cell.x,
973
+ y: cell.y,
974
+ };
975
+ }
708
976
  }
709
977
  if (
710
978
  cell.selectionBorderBottom &&
@@ -721,6 +989,22 @@ export default function (self) {
721
989
  ) {
722
990
  selectionHandles.push([cell, 'br']);
723
991
  cell.selectionHandle = 'br';
992
+
993
+ if (self.fillOverlay.handle) {
994
+ self.fillOverlay.handle.x = cell.x + cell.width;
995
+ self.fillOverlay.handle.y = cell.y + cell.height;
996
+ }
997
+ }
998
+
999
+ if (self.fillOverlay.handle) {
1000
+ // Some corners may not be displaying, so we get at least
1001
+ // one correct axis in order to display a fill overlay.
1002
+ if (self.fillOverlay.snapTo.x === -1 && cell.selectionBorderLeft) {
1003
+ self.fillOverlay.snapTo.x = cell.x;
1004
+ }
1005
+ if (self.fillOverlay.snapTo.y === -1 && cell.selectionBorderTop) {
1006
+ self.fillOverlay.snapTo.y = cell.y;
1007
+ }
724
1008
  }
725
1009
  }
726
1010
  }
@@ -730,21 +1014,23 @@ export default function (self) {
730
1014
  propPrefix,
731
1015
  offsetPoint,
732
1016
  ) {
733
- offsetPoint = offsetPoint || { x: 0, y: 0 };
734
1017
  cell.selectionBorder = '';
735
- if (
736
- !cell.isRowHeader &&
737
- self.selections[cell.rowIndex + -offsetPoint.y] &&
738
- self.selections[cell.rowIndex + -offsetPoint.y].indexOf(
739
- cell.columnIndex + -offsetPoint.x,
740
- ) !== -1
741
- ) {
1018
+ // TODO: optimize it by `getSelectionStateFromCells`
1019
+ const isSelected = offsetPoint
1020
+ ? (row, col) =>
1021
+ isCellSelected(
1022
+ self.selections,
1023
+ row - offsetPoint.y,
1024
+ col - offsetPoint.x,
1025
+ )
1026
+ : (row, col) => isCellSelected(self.selections, row, col);
1027
+ const lastColumnIndex = Math.max(schema.length - 1, 0);
1028
+ const lastRowIndex = Math.max(viewData.length - 1, 0);
1029
+ const { isRowHeader, rowIndex, columnIndex } = cell;
1030
+
1031
+ if (!isRowHeader && isSelected(rowIndex, columnIndex)) {
742
1032
  if (
743
- (!self.selections[cell.rowIndex - 1 + -offsetPoint.y] ||
744
- self.selections[cell.rowIndex - 1 + -offsetPoint.y].indexOf(
745
- cell.columnIndex + -offsetPoint.x,
746
- ) === -1 ||
747
- cell.rowIndex === 0) &&
1033
+ (!isSelected(rowIndex - 1, columnIndex) || rowIndex === 0) &&
748
1034
  !cell.isHeader
749
1035
  ) {
750
1036
  drawArray.push([cell, 't']);
@@ -752,32 +1038,21 @@ export default function (self) {
752
1038
  cell[propPrefix + 'Border'] += 't';
753
1039
  }
754
1040
  if (
755
- !self.selections[cell.rowIndex + 1 + -offsetPoint.y] ||
756
- self.selections[cell.rowIndex + 1 + -offsetPoint.y].indexOf(
757
- cell.columnIndex + -offsetPoint.x,
758
- ) === -1
1041
+ rowIndex >= lastRowIndex ||
1042
+ !isSelected(rowIndex + 1, columnIndex)
759
1043
  ) {
760
1044
  drawArray.push([cell, 'b']);
761
1045
  cell[propPrefix + 'BorderBottom'] = true;
762
1046
  cell[propPrefix + 'Border'] += 'b';
763
1047
  }
764
- if (
765
- !self.selections[cell.rowIndex + -offsetPoint.y] ||
766
- cell.columnIndex === 0 ||
767
- self.selections[cell.rowIndex + -offsetPoint.y].indexOf(
768
- cell.columnIndex - 1 + -offsetPoint.x,
769
- ) === -1
770
- ) {
1048
+ if (columnIndex === 0 || !isSelected(rowIndex, columnIndex - 1)) {
771
1049
  drawArray.push([cell, 'l']);
772
1050
  cell[propPrefix + 'BorderLeft'] = true;
773
1051
  cell[propPrefix + 'Border'] += 'l';
774
1052
  }
775
1053
  if (
776
- !self.selections[cell.rowIndex + -offsetPoint.y] ||
777
- cell.columnIndex === s.length ||
778
- self.selections[cell.rowIndex + -offsetPoint.y].indexOf(
779
- cell.columnIndex + 1 + -offsetPoint.x,
780
- ) === -1
1054
+ columnIndex >= lastColumnIndex ||
1055
+ !isSelected(rowIndex, columnIndex + 1)
781
1056
  ) {
782
1057
  drawArray.push([cell, 'r']);
783
1058
  cell[propPrefix + 'BorderRight'] = true;
@@ -785,7 +1060,11 @@ export default function (self) {
785
1060
  }
786
1061
  }
787
1062
  }
788
- function drawCell(d, rowOrderIndex, rowIndex) {
1063
+ function drawCell(rowData, rowOrderIndex, rowIndex) {
1064
+ const isActiveRowHeader =
1065
+ self.orders.rows[self.activeCell.rowIndex] === rowOrderIndex ||
1066
+ self.isRowSelected(rowOrderIndex);
1067
+
789
1068
  return function drawEach(header, headerIndex, columnOrderIndex) {
790
1069
  if (header.hidden) {
791
1070
  return 0;
@@ -797,38 +1076,55 @@ export default function (self) {
797
1076
  isCorner = /cornerCell/.test(cellStyle),
798
1077
  isRowHeader = 'rowHeaderCell' === cellStyle,
799
1078
  isColumnHeader = 'columnHeaderCell' === cellStyle,
1079
+ isFilterable =
1080
+ self.filterable.rows.includes(rowIndex) &&
1081
+ self.filterable.columns.includes(headerIndex),
800
1082
  wrap = self.style.cellWhiteSpace !== 'nowrap',
801
- selected =
802
- self.selections[rowOrderIndex] &&
803
- self.selections[rowOrderIndex].indexOf(columnOrderIndex) !== -1,
1083
+ // TODO: optimize it by `getSelectionStateFromCells`
1084
+ selected = isCellSelected(
1085
+ self.selections,
1086
+ rowOrderIndex,
1087
+ columnOrderIndex,
1088
+ ),
804
1089
  hovered =
805
1090
  self.hovers.rowIndex === rowOrderIndex &&
806
- self.hovers.columnIndex === columnOrderIndex,
1091
+ (self.attributes.hoverMode === 'row' ||
1092
+ self.hovers.columnIndex === columnOrderIndex),
1093
+ openedFilter =
1094
+ self.selectedFilterButton.rowIndex == rowIndex &&
1095
+ self.selectedFilterButton.columnIndex == headerIndex,
807
1096
  active =
808
1097
  self.activeCell.rowIndex === rowOrderIndex &&
809
1098
  self.activeCell.columnIndex === columnOrderIndex,
810
1099
  isColumnHeaderCellCap = cellStyle === 'columnHeaderCellCap',
811
- rawValue = d ? d[header.name] : undefined,
1100
+ rawValue = rowData ? rowData[header.name] : undefined,
812
1101
  isGrid = header.type === 'canvas-datagrid',
813
- activeHeader =
814
- (self.orders.rows[self.activeCell.rowIndex] === rowOrderIndex ||
815
- self.orders.columns[self.activeCell.columnIndex] ===
816
- headerIndex) &&
817
- (columnOrderIndex === -1 || rowOrderIndex === -1)
818
- ? isRowHeader
819
- ? 'activeRowHeaderCell'
820
- : 'activeColumnHeaderCell'
821
- : false,
822
1102
  val,
823
1103
  f = self.formatters[header.type || 'string'],
824
1104
  orderByArrowSize = 0,
825
1105
  treeArrowSize = 0,
826
- cellWidth = self.sizes.columns[headerIndex] || header.width,
1106
+ cellWidth =
1107
+ headerIndex >= 0
1108
+ ? self.getColumnWidth(headerIndex)
1109
+ : self.sizes.columns[headerIndex] || header.width,
827
1110
  ev = {
828
1111
  value: rawValue,
829
- row: d,
1112
+ row: rowData,
830
1113
  header: header,
831
1114
  };
1115
+
1116
+ const isActiveColumnHeader =
1117
+ self.orders.columns[self.activeCell.columnIndex] === headerIndex ||
1118
+ self.isColumnSelected(self.activeCell.viewColumnIndex);
1119
+ let activeHeader = false;
1120
+ if (
1121
+ (isActiveColumnHeader || isActiveRowHeader) &&
1122
+ (columnOrderIndex === -1 || rowOrderIndex === -1)
1123
+ )
1124
+ activeHeader = isRowHeader
1125
+ ? 'activeRowHeaderCell'
1126
+ : 'activeColumnHeaderCell';
1127
+
832
1128
  if (isColumnHeaderCellCap) {
833
1129
  cellWidth = w - x;
834
1130
  }
@@ -836,7 +1132,9 @@ export default function (self) {
836
1132
  if (cellWidth === undefined) {
837
1133
  cellWidth = self.style.cellWidth;
838
1134
  }
839
- cellWidth = cellWidth * self.scale;
1135
+ if (headerIndex < 0) {
1136
+ cellWidth = cellWidth * self.scale;
1137
+ }
840
1138
  if (x + cellWidth + self.style.cellBorderWidth < 0) {
841
1139
  x += cellWidth + self.style.cellBorderWidth;
842
1140
  }
@@ -889,18 +1187,30 @@ export default function (self) {
889
1187
  offsetHeight: cellHeight,
890
1188
  parentNode: self.intf.parentNode,
891
1189
  offsetParent: self.intf.parentNode,
892
- data: d,
1190
+ data: rowData,
893
1191
  isCorner: isCorner,
894
1192
  isHeader: isHeader,
895
1193
  isColumnHeader: isColumnHeader,
896
1194
  isColumnHeaderCellCap: isColumnHeaderCellCap,
897
1195
  isRowHeader: isRowHeader,
1196
+ isFilterable: isFilterable,
1197
+ openedFilter: openedFilter,
898
1198
  rowOpen: rowOpen,
899
1199
  header: header,
1200
+
900
1201
  columnIndex: columnOrderIndex,
901
1202
  rowIndex: rowOrderIndex,
1203
+
1204
+ viewRowIndex: rowOrderIndex,
1205
+ viewColumnIndex: columnOrderIndex,
1206
+
1207
+ boundRowIndex: self.getBoundRowIndexFromViewRowIndex(rowOrderIndex),
1208
+ boundColumnIndex:
1209
+ self.getBoundColumnIndexFromViewColumnIndex(columnOrderIndex),
1210
+
902
1211
  sortColumnIndex: headerIndex,
903
1212
  sortRowIndex: rowIndex,
1213
+
904
1214
  isGrid: isGrid,
905
1215
  isNormal: !isGrid && !isCorner && !isHeader,
906
1216
  gridId: (self.attributes.name || '') + rowIndex + ':' + headerIndex,
@@ -909,6 +1219,15 @@ export default function (self) {
909
1219
  activeHeader: activeHeader,
910
1220
  value:
911
1221
  isHeader && !isRowHeader ? header.title || header.name : rawValue,
1222
+ isRowTree:
1223
+ rowOrderIndex >= 0 &&
1224
+ columnOrderIndex == self.cellTree.rowTreeColIndex &&
1225
+ self.cellTree.rows.length > 0 &&
1226
+ self.cellTree.rows[rowOrderIndex].icon,
1227
+ isColumnTree:
1228
+ columnOrderIndex >= 0 &&
1229
+ self.cellTree.columns[rowOrderIndex] &&
1230
+ self.cellTree.columns[rowOrderIndex][columnOrderIndex].icon,
912
1231
  };
913
1232
  cell.calculatedLineHeight =
914
1233
  cell.fontHeight * cell.lineHeight + cell.lineSpacing;
@@ -920,6 +1239,8 @@ export default function (self) {
920
1239
  ? self.sizes.columns.cornerCell
921
1240
  : self.sizes.columns[headerIndex];
922
1241
  self.visibleCells.unshift(cell);
1242
+ saveRowGroupsRectInfo(cell);
1243
+ saveColumnGroupsRectInfo(cell);
923
1244
  if (self.dispatchEvent('beforerendercell', ev)) {
924
1245
  return;
925
1246
  }
@@ -994,9 +1315,8 @@ export default function (self) {
994
1315
  if (self.dispatchEvent('beforecreatecellgrid', ev)) {
995
1316
  return;
996
1317
  }
997
- self.childGrids[cell.gridId] = self.createGrid(
998
- cellGridAttributes,
999
- );
1318
+ self.childGrids[cell.gridId] =
1319
+ self.createGrid(cellGridAttributes);
1000
1320
  self.sizes.rows[rowIndex] =
1001
1321
  self.sizes.rows[rowIndex] || self.style.cellGridHeight;
1002
1322
  checkScrollHeight = true;
@@ -1040,9 +1360,8 @@ export default function (self) {
1040
1360
  ' add a cellFormater',
1041
1361
  );
1042
1362
  }
1043
- cell.formattedValue = (val !== undefined && val !== null
1044
- ? val
1045
- : ''
1363
+ cell.formattedValue = (
1364
+ val !== undefined && val !== null ? val : ''
1046
1365
  ).toString();
1047
1366
  if (
1048
1367
  self.columnFilters &&
@@ -1051,10 +1370,7 @@ export default function (self) {
1051
1370
  ) {
1052
1371
  cell.formattedValue = self.attributes.filterTextPrefix + val;
1053
1372
  }
1054
- self.ctx.font =
1055
- self.style[cellStyle + 'FontHeight'] * self.scale +
1056
- 'px ' +
1057
- self.style[cellStyle + 'FontName'];
1373
+ self.ctx.font = self.getScaledFont(cellStyle + 'Font');
1058
1374
  if (!self.dispatchEvent('formattext', ev)) {
1059
1375
  cell.text = wrapText(cell, ' ');
1060
1376
  }
@@ -1062,7 +1378,53 @@ export default function (self) {
1062
1378
  if (cell.innerHTML || header.type === 'html') {
1063
1379
  drawHtml(cell);
1064
1380
  } else {
1065
- drawText(cell);
1381
+ let treeCellPadding = 0,
1382
+ isDrawText = true;
1383
+ if (
1384
+ cell.columnIndex == self.cellTree.rowTreeColIndex &&
1385
+ !cell.isColumnHeader &&
1386
+ self.cellTree.rows.length > 0 &&
1387
+ Object.keys(self.cellTree.rows[cell.rowIndex]).length > 1
1388
+ )
1389
+ treeCellPadding = drawCellTreeIcon(
1390
+ cell,
1391
+ self.cellTree.rows[cell.rowIndex],
1392
+ true,
1393
+ );
1394
+
1395
+ if (
1396
+ !cell.isRowHeader &&
1397
+ cell.rowIndex > 0 &&
1398
+ self.cellTree.columns[cell.rowIndex - 1] &&
1399
+ self.cellTree.columns[cell.rowIndex - 1][cell.columnIndex]
1400
+ .icon
1401
+ ) {
1402
+ for (let r = cell.rowIndex - 1; r >= 0; r--) {
1403
+ if (!self.cellTree.columns[r]) break;
1404
+ if (!self.cellTree.columns[r][cell.columnIndex].icon) break;
1405
+ if (!self.cellTree.columns[r][cell.columnIndex].expand) {
1406
+ isDrawText = false;
1407
+ break;
1408
+ }
1409
+ }
1410
+ }
1411
+ if (
1412
+ isDrawText &&
1413
+ !cell.isRowHeader &&
1414
+ self.cellTree.columns[cell.rowIndex] &&
1415
+ self.cellTree.columns[cell.rowIndex][cell.columnIndex].icon
1416
+ ) {
1417
+ if (
1418
+ self.viewData.length > 0 &&
1419
+ self.viewData[cell.rowIndex][cell.columnIndex].length > 0
1420
+ )
1421
+ treeCellPadding = drawCellTreeIcon(
1422
+ cell,
1423
+ self.cellTree.columns[cell.rowIndex][cell.columnIndex],
1424
+ false,
1425
+ );
1426
+ }
1427
+ if (isDrawText) drawText(cell, treeCellPadding);
1066
1428
  }
1067
1429
 
1068
1430
  if (wrap && cell.text && cell.text.height > rowHeight) {
@@ -1082,31 +1444,283 @@ export default function (self) {
1082
1444
  createBorderOverlayArray(cell, moveBorders, 'move', self.moveOffset);
1083
1445
  }
1084
1446
  self.ctx.restore();
1447
+
1448
+ if (isFilterable) {
1449
+ drawFilterButton(cell);
1450
+ }
1451
+
1452
+ // Gaps may occur in row numbers between consecutively rendered rows
1453
+ // when we are filtering. We draw attention to this by drawing a thick
1454
+ // border overlapping the two consecutive row headers. If sorting, visible
1455
+ // row numbers stay the same (i.e. they don't correspond to the underlying
1456
+ // data's row number), so we do not show row gaps in that case.
1457
+ const isSorting =
1458
+ self.orderings.columns && self.orderings.columns.length > 0;
1459
+
1460
+ if (
1461
+ isRowHeader &&
1462
+ self.attributes.showRowNumbers &&
1463
+ self.attributes.showRowNumberGaps &&
1464
+ isSorting === false
1465
+ ) {
1466
+ const previousRowNumber = self.getBoundRowIndexFromViewRowIndex(
1467
+ rowOrderIndex - 1,
1468
+ );
1469
+ let hasRowGap =
1470
+ previousRowNumber !== undefined &&
1471
+ cell.boundRowIndex > 0 &&
1472
+ cell.boundRowIndex - previousRowNumber > 1;
1473
+ if (hasRowGap && collapsedRowGroups.length > 0) {
1474
+ hasRowGap =
1475
+ collapsedRowGroups.find(
1476
+ (group) =>
1477
+ group.from === previousRowNumber &&
1478
+ group.to === cell.boundRowIndex,
1479
+ ) >= 0;
1480
+ }
1481
+ // We don't treat the row index difference from hidden rows as the row gap.
1482
+ if (hasRowGap && self.hiddenRowRanges.length > 0) {
1483
+ for (let i = 0; i < self.hiddenRowRanges.length; i++) {
1484
+ const [beginRowIndex, endRowIndex] = self.hiddenRowRanges[i];
1485
+ if (
1486
+ cell.boundRowIndex === endRowIndex + 1 &&
1487
+ previousRowNumber === beginRowIndex - 1
1488
+ ) {
1489
+ hasRowGap = false;
1490
+ break;
1491
+ }
1492
+ }
1493
+ }
1494
+
1495
+ if (hasRowGap) {
1496
+ const barHeight = self.style.rowHeaderCellRowNumberGapHeight;
1497
+ const barColor = self.style.rowHeaderCellRowNumberGapColor;
1498
+
1499
+ self.ctx.save();
1500
+
1501
+ self.ctx.fillStyle = barColor;
1502
+
1503
+ fillRect(cell.x, cell.y - barHeight / 2, cell.width, barHeight);
1504
+
1505
+ self.ctx.restore();
1506
+ }
1507
+ }
1508
+
1509
+ //#region draw unhide indicator for column headers
1510
+ if (isColumnHeader && self.attributes.showUnhideColumnsIndicator) {
1511
+ const hovered = self.hovers.unhideIndicator;
1512
+ const size = self.style.unhideIndicatorSize;
1513
+ const cellX = x;
1514
+ const topY = cell.y + Math.max(0.5 * (cell.height - size), 0);
1515
+
1516
+ const isActive = (orderIndex) =>
1517
+ hovered &&
1518
+ (hovered.dir === 'l' || hovered.dir === 'r') &&
1519
+ orderIndex >= hovered.orderIndex0 &&
1520
+ orderIndex <= hovered.orderIndex1;
1521
+ const isHiddenColumn = (columnIndex) =>
1522
+ columnIndex >= 0 &&
1523
+ schema[columnIndex] &&
1524
+ schema[columnIndex].hidden;
1525
+
1526
+ let orderIndex0, orderIndex1;
1527
+ const drawIndicator = (leftX, dir, active) => {
1528
+ self.visibleUnhideIndicators.push({
1529
+ x: leftX - 1,
1530
+ y: topY - 1,
1531
+ x2: leftX + size + 2,
1532
+ y2: topY + size + 2,
1533
+ orderIndex0,
1534
+ orderIndex1,
1535
+ dir,
1536
+ });
1537
+ if (!active) {
1538
+ const line = cell.text && cell.text.lines && cell.text.lines[0];
1539
+ if (line) {
1540
+ const iconsWidth = orderByArrowSize + treeArrowSize;
1541
+ const lineX0 = iconsWidth > 0 ? iconsWidth : line.x;
1542
+ const lineX1 = line.x + line.width;
1543
+ if (leftX + size >= lineX0 && leftX <= lineX1) return;
1544
+ }
1545
+ }
1546
+ drawUnhideIndicator(leftX, topY, size, dir, active);
1547
+ }; // end of drawIndicator
1548
+
1549
+ let orderIndexPtr = columnOrderIndex - 1;
1550
+ const prevColumnIndex = self.orders.columns[orderIndexPtr];
1551
+ if (isHiddenColumn(prevColumnIndex)) {
1552
+ const active = isActive(prevColumnIndex);
1553
+ orderIndex0 = orderIndexPtr;
1554
+ orderIndex1 = orderIndexPtr;
1555
+ while (--orderIndexPtr >= 0) {
1556
+ if (isHiddenColumn(self.orders.columns[orderIndexPtr]))
1557
+ orderIndex0 = orderIndexPtr;
1558
+ else break;
1559
+ }
1560
+ drawIndicator(cellX, 'r', active);
1561
+ }
1562
+
1563
+ orderIndexPtr = columnOrderIndex + 1;
1564
+ const nextColumnIndex = self.orders.columns[orderIndexPtr];
1565
+ if (isHiddenColumn(nextColumnIndex)) {
1566
+ const active = isActive(nextColumnIndex);
1567
+ orderIndex0 = orderIndexPtr;
1568
+ orderIndex1 = orderIndexPtr;
1569
+ while (++orderIndexPtr < self.orders.columns.length) {
1570
+ if (isHiddenColumn(self.orders.columns[orderIndexPtr]))
1571
+ orderIndex1 = orderIndexPtr;
1572
+ else break;
1573
+ }
1574
+ const indicatorX = x + cell.width - size;
1575
+ drawIndicator(indicatorX, 'l', active);
1576
+ }
1577
+ }
1578
+ //#endregion draw unhide indicator for column headers
1579
+
1580
+ //#region draw unhide indicator for row headers
1581
+ if (
1582
+ isRowHeader &&
1583
+ self.attributes.showUnhideRowsIndicator &&
1584
+ self.hiddenRowRanges.length > 0
1585
+ ) {
1586
+ // Leo's comment:
1587
+ // from the first row to the last row, `rowIndex` is from 0 to the count of rows
1588
+ // but `boundRowIndex` can be disordered if there are any ordered columns or filtered columns
1589
+ // Like this statement:
1590
+ // console.log(rowIndex, cell.boundRowIndex, cell.formattedValue);
1591
+ // can output the result like this:
1592
+ // 0 1 '2'
1593
+ // 1 3 '4'
1594
+ const hovered = self.hovers.unhideIndicator;
1595
+ const size = self.style.unhideIndicatorSize;
1596
+ const leftX = cell.x + cell.width - size - 2;
1597
+ const cellY = y;
1598
+ const topIndicators = {};
1599
+ const bottomIndicators = {};
1600
+ self.hiddenRowRanges.forEach((it) => {
1601
+ topIndicators[it[0] - 1] = it;
1602
+ bottomIndicators[it[1] + 1] = it;
1603
+ });
1604
+
1605
+ const rowIndex = cell.rowIndex + currentRowIndexOffset;
1606
+ const isActive = () =>
1607
+ hovered &&
1608
+ (hovered.dir === 't' || hovered.dir === 'b') &&
1609
+ rowIndex >= hovered.orderIndex0 - 1 &&
1610
+ rowIndex <= hovered.orderIndex1 + 1;
1611
+
1612
+ let orderIndex0, orderIndex1;
1613
+ const drawIndicator = (topY, dir, active) => {
1614
+ self.visibleUnhideIndicators.push({
1615
+ x: leftX - 1,
1616
+ y: topY - 1,
1617
+ x2: leftX + size + 2,
1618
+ y2: topY + size + 2,
1619
+ orderIndex0,
1620
+ orderIndex1,
1621
+ dir,
1622
+ });
1623
+ drawUnhideIndicator(leftX, topY, size, dir, active);
1624
+ }; // end of drawIndicator
1625
+
1626
+ let matched = topIndicators[rowIndex];
1627
+ if (matched) {
1628
+ const indicatorY = cellY + cell.height - size;
1629
+ [orderIndex0, orderIndex1] = matched;
1630
+ drawIndicator(indicatorY, 't', isActive());
1631
+ }
1632
+ matched = bottomIndicators[rowIndex];
1633
+ if (matched) {
1634
+ const indicatorY = cellY;
1635
+ [orderIndex0, orderIndex1] = matched;
1636
+ drawIndicator(indicatorY, 'b', isActive());
1637
+ }
1638
+ }
1639
+ //#endregion draw unhide indicator for row headers
1640
+
1085
1641
  x += cell.width + (bc ? 0 : self.style.cellBorderWidth);
1086
1642
  return cell.width;
1087
- };
1643
+ }; // end of drawEach
1644
+ }
1645
+ function drawFilterButton(cell, ev) {
1646
+ var posX = cell.x + cell.width - self.style.filterButtonWidth - 1;
1647
+ var posY = cell.y + cell.height - self.style.filterButtonHeight - 2;
1648
+ if (self.dispatchEvent('beforerenderfilterbutton', ev)) {
1649
+ return;
1650
+ }
1651
+ self.ctx.save();
1652
+ self.ctx.strokeStyle = self.style.filterButtonBorderColor;
1653
+ self.ctx.fillStyle = self.style.filterButtonBackgroundColor;
1654
+ if (cell.openedFilter) {
1655
+ self.ctx.fillStyle = self.style.filterButtonActiveBackgroundColor;
1656
+ } else if (cell.hovered && self.hovers.onFilterButton) {
1657
+ self.ctx.fillStyle = self.style.filterButtonHoverBackgroundColor;
1658
+ }
1659
+ radiusRect(
1660
+ posX,
1661
+ posY,
1662
+ self.style.filterButtonWidth,
1663
+ self.style.filterButtonHeight,
1664
+ self.style.filterButtonBorderRadius,
1665
+ );
1666
+ self.ctx.stroke();
1667
+ self.ctx.fill();
1668
+ drawFilterButtonArrow(posX, posY);
1669
+ self.ctx.clip();
1670
+ self.dispatchEvent('afterrenderfilterbutton', ev);
1671
+ self.ctx.restore();
1088
1672
  }
1089
- function drawRowHeader(rowData, index, rowOrderIndex) {
1090
- var a, i;
1673
+ function drawRowHeader(rowData, rowIndex, rowOrderIndex) {
1091
1674
  if (self.attributes.showRowHeaders) {
1092
1675
  x = 0;
1093
- i = index + 1;
1094
- rowHeaderCell = { rowHeaderCell: i };
1095
- a = {
1676
+
1677
+ // When filtering we'd like to display the actual row numbers,
1678
+ // as it is in the unfiltered data, instead of simply the viewed
1679
+ // row index + 1. If rowIndex > viewData.length, it's a new row
1680
+ // added to the end, and we want to render that new row's number
1681
+ let filteredRowNumber;
1682
+ if (self.viewData && rowIndex < self.viewData.length)
1683
+ filteredRowNumber =
1684
+ self.getBoundRowIndexFromViewRowIndex(rowIndex) + 1;
1685
+ else
1686
+ filteredRowNumber = self.originalData
1687
+ ? self.originalData.length + 1
1688
+ : rowOrderIndex + 1;
1689
+
1690
+ let rowHeaderValue =
1691
+ self.hasActiveFilters() || self.hasCollapsedRowGroup()
1692
+ ? filteredRowNumber
1693
+ : rowIndex + 1;
1694
+ rowHeaderValue += currentRowIndexOffset;
1695
+
1696
+ const rowHeaderCell = { rowHeaderCell: rowHeaderValue };
1697
+ const headerDescription = {
1096
1698
  name: 'rowHeaderCell',
1097
1699
  width: self.sizes.columns[-1] || self.style.rowHeaderCellWidth,
1098
1700
  style: 'rowHeaderCell',
1099
1701
  type: 'string',
1100
- data: i,
1702
+ data: rowHeaderValue,
1101
1703
  index: -1,
1102
1704
  };
1103
- rowOpen = self.openChildren[index];
1104
- drawCell(rowHeaderCell, index, rowOrderIndex)(a, -1, -1);
1705
+ rowOpen = self.openChildren[rowIndex];
1706
+ drawCell(rowHeaderCell, rowOrderIndex, rowIndex)(
1707
+ headerDescription,
1708
+ -1,
1709
+ -1,
1710
+ );
1711
+
1712
+ if (
1713
+ rowIndexOffsetByHiddenRows[0] &&
1714
+ rowHeaderValue >= rowIndexOffsetByHiddenRows[0].from
1715
+ ) {
1716
+ const { plus } = rowIndexOffsetByHiddenRows.shift();
1717
+ currentRowIndexOffset += plus;
1718
+ }
1105
1719
  }
1106
1720
  }
1107
1721
  function drawHeaders() {
1108
1722
  var d,
1109
- g = s.length,
1723
+ g = schema.length,
1110
1724
  i,
1111
1725
  o,
1112
1726
  columnHeaderCell,
@@ -1116,8 +1730,8 @@ export default function (self) {
1116
1730
  end = Math.min(end, g);
1117
1731
  for (o = start; o < end; o += 1) {
1118
1732
  i = self.orders.columns[o];
1119
- header = s[i];
1120
- if (!header.hidden) {
1733
+ header = schema[i];
1734
+ if (!header.hidden && !isColumnCollapsedByGroup(o)) {
1121
1735
  d = {
1122
1736
  title: header.title,
1123
1737
  name: header.name,
@@ -1182,7 +1796,7 @@ export default function (self) {
1182
1796
  isColumnHeaderCell: true,
1183
1797
  isColumnHeaderCellCap: true,
1184
1798
  type: 'string',
1185
- index: s.length,
1799
+ index: schema.length,
1186
1800
  };
1187
1801
  drawCell({ endCap: '' }, -1, -1)(c, -1, -1);
1188
1802
  }
@@ -1201,20 +1815,20 @@ export default function (self) {
1201
1815
  }
1202
1816
  }
1203
1817
  }
1204
- function drawRow(r, d) {
1205
- var i,
1818
+ function drawRow(rowOrderIndex, rowIndex) {
1819
+ var headerIndex,
1206
1820
  treeHeight,
1207
1821
  rowSansTreeHeight,
1208
- o,
1209
- g = s.length;
1822
+ columnOrderIndex,
1823
+ g = schema.length;
1210
1824
  if (y - cellHeight * 2 > h) {
1211
1825
  return false;
1212
1826
  }
1213
- rd = data[r];
1214
- rowOpen = self.openChildren[r];
1827
+ rowData = viewData[rowOrderIndex];
1828
+ rowOpen = self.openChildren[rowOrderIndex];
1215
1829
  rowSansTreeHeight =
1216
- (self.sizes.rows[r] || self.style.cellHeight) * self.scale;
1217
- treeHeight = (rowOpen ? self.sizes.trees[r] : 0) * self.scale;
1830
+ (self.sizes.rows[rowOrderIndex] || self.style.cellHeight) * self.scale;
1831
+ treeHeight = (rowOpen ? self.sizes.trees[rowOrderIndex] : 0) * self.scale;
1218
1832
  rowHeight = rowSansTreeHeight + treeHeight;
1219
1833
  if (y < -rowHeight) {
1220
1834
  return false;
@@ -1224,23 +1838,43 @@ export default function (self) {
1224
1838
  }
1225
1839
  cellHeight = rowHeight;
1226
1840
  //draw normal columns
1227
- for (o = self.scrollIndexLeft; o < g; o += 1) {
1228
- i = self.orders.columns[o];
1229
- x += drawCell(rd, r, d)(s[i], i, o);
1841
+ for (
1842
+ columnOrderIndex = self.scrollIndexLeft;
1843
+ columnOrderIndex < g;
1844
+ columnOrderIndex += 1
1845
+ ) {
1846
+ if (!isColumnCollapsedByGroup(columnOrderIndex)) {
1847
+ headerIndex = self.orders.columns[columnOrderIndex];
1848
+ x += drawCell(rowData, rowOrderIndex, rowIndex)(
1849
+ schema[headerIndex],
1850
+ headerIndex,
1851
+ columnOrderIndex,
1852
+ );
1853
+ }
1230
1854
  if (x > self.width) {
1231
- self.scrollIndexRight = o;
1855
+ self.scrollIndexRight = columnOrderIndex;
1232
1856
  self.scrollPixelRight = x;
1233
1857
  break;
1234
1858
  }
1235
1859
  }
1236
1860
  //draw frozen columns
1237
- x = 0;
1861
+ x = 1;
1238
1862
  if (self.attributes.showRowHeaders) {
1239
1863
  x += rowHeaderCellWidth;
1240
1864
  }
1241
- for (o = 0; o < self.frozenColumn; o += 1) {
1242
- i = self.orders.columns[o];
1243
- x += drawCell(rd, r, d)(s[i], i, o);
1865
+ for (
1866
+ columnOrderIndex = 0;
1867
+ columnOrderIndex < self.frozenColumn;
1868
+ columnOrderIndex += 1
1869
+ ) {
1870
+ if (!isColumnCollapsedByGroup(columnOrderIndex)) {
1871
+ headerIndex = self.orders.columns[columnOrderIndex];
1872
+ x += drawCell(rowData, rowOrderIndex, rowIndex)(
1873
+ schema[headerIndex],
1874
+ headerIndex,
1875
+ columnOrderIndex,
1876
+ );
1877
+ }
1244
1878
  if (x > self.width) {
1245
1879
  break;
1246
1880
  }
@@ -1253,8 +1887,8 @@ export default function (self) {
1253
1887
  self.scrollPixelLeft +
1254
1888
  self.style.cellBorderWidth;
1255
1889
  // don't draw a tree for the new row
1256
- treeGrid = self.childGrids[r];
1257
- if (r !== data.length && rowOpen) {
1890
+ treeGrid = self.childGrids[rowOrderIndex];
1891
+ if (rowOrderIndex !== viewData.length && rowOpen) {
1258
1892
  treeGrid.visible = true;
1259
1893
  treeGrid.parentNode = {
1260
1894
  offsetTop: y + rowSansTreeHeight + self.canvasOffsetTop,
@@ -1268,10 +1902,19 @@ export default function (self) {
1268
1902
  nodeType: 'canvas-datagrid-tree',
1269
1903
  scrollTop: self.scrollBox.scrollTop,
1270
1904
  scrollLeft: self.scrollBox.scrollLeft,
1271
- rowIndex: r,
1905
+ rowIndex: rowOrderIndex,
1906
+ columnGroupsAreaHeight: columnGroupsAreaHeight,
1907
+ rowGroupsAreaWidth: rowGroupsAreaWidth,
1272
1908
  };
1909
+ if (self.intf.parentNode) {
1910
+ const { columnGroupsAreaHeight, rowGroupsAreaWidth } =
1911
+ self.intf.parentNode;
1912
+ treeGrid.parentNode.columnGroupsAreaHeight +=
1913
+ columnGroupsAreaHeight || 0;
1914
+ treeGrid.parentNode.rowGroupsAreaWidth += rowGroupsAreaWidth || 0;
1915
+ }
1273
1916
  self.visibleCells.unshift({
1274
- rowIndex: r,
1917
+ rowIndex: rowOrderIndex,
1275
1918
  columnIndex: 0,
1276
1919
  y: treeGrid.parentNode.offsetTop,
1277
1920
  x: treeGrid.parentNode.offsetLeft,
@@ -1283,51 +1926,432 @@ export default function (self) {
1283
1926
  treeGrid.draw();
1284
1927
  } else if (treeGrid) {
1285
1928
  treeGrid.parentNode.offsetHeight = 0;
1286
- delete self.sizes.trees[r];
1929
+ delete self.sizes.trees[rowOrderIndex];
1287
1930
  }
1288
- rowHeaders.push([rd, r, d, y, rowHeight]);
1289
- self.visibleRowHeights[r] = rowHeight;
1931
+ rowHeaders.push([rowData, rowOrderIndex, rowIndex, y, rowHeight]);
1932
+ self.visibleRowHeights[rowOrderIndex] = rowHeight;
1290
1933
  y += cellHeight + (bc ? 0 : self.style.cellBorderWidth);
1291
1934
  return true;
1292
1935
  }
1293
1936
  function initDraw() {
1294
1937
  self.visibleRows = [];
1295
- s = self.getSchema();
1938
+ schema = self.getSchema();
1296
1939
  self.visibleCells = [];
1940
+ self.visibleGroups = [];
1941
+ self.visibleUnhideIndicators = [];
1297
1942
  self.canvasOffsetTop = self.isChildGrid ? self.parentNode.offsetTop : 0.5;
1298
1943
  self.canvasOffsetLeft = self.isChildGrid
1299
1944
  ? self.parentNode.offsetLeft
1300
1945
  : -0.5;
1301
1946
  h = self.height;
1302
1947
  w = self.width;
1948
+
1949
+ // patch for first row being hidden
1950
+ const firstRowIndexOffset = rowIndexOffsetByHiddenRows[0];
1951
+ if (firstRowIndexOffset && firstRowIndexOffset.from === 0) {
1952
+ currentRowIndexOffset = firstRowIndexOffset.plus;
1953
+ rowIndexOffsetByHiddenRows.shift();
1954
+ }
1303
1955
  }
1304
1956
  function drawBackground() {
1305
1957
  radiusRect(0, 0, w, h, 0);
1306
1958
  self.ctx.clip();
1959
+ self.ctx.clearRect(0, 0, w, h);
1307
1960
  self.ctx.fillStyle = self.style.gridBackgroundColor;
1308
1961
  fillRect(0, 0, w, h);
1309
1962
  }
1963
+ function initGroupArea() {
1964
+ self.ctx.translate(rowGroupsAreaWidth, columnGroupsAreaHeight);
1965
+ }
1966
+ function drawGroupArea() {
1967
+ const mx = rowGroupsAreaWidth;
1968
+ const my = columnGroupsAreaHeight;
1969
+ const frozenColumnsWidth = getFrozenColumnsWidth();
1970
+ const frozenRowsHeight =
1971
+ rowGroupsFrozenInfo.y + rowGroupsFrozenInfo.h - columnHeaderCellHeight;
1972
+ const onTheLeft = self.attributes.columnGroupIndicatorPosition === 'left';
1973
+ const onTheTop = self.attributes.rowGroupIndicatorPosition === 'top';
1974
+
1975
+ /** @type {CanvasRenderingContext2D} */
1976
+ const ctx = self.ctx;
1977
+ ctx.save();
1978
+ ctx.fillStyle = self.style.groupingAreaBackgroundColor;
1979
+ fillRect(0, -my, w, my);
1980
+ fillRect(-mx, -my, mx, h);
1981
+ ctx.restore();
1982
+
1983
+ //#region Columns Grouping
1984
+ /** it extends `self.groupedRows` */
1985
+ const groupedColumns = [];
1986
+ for (let row = 0; row < self.groupedColumns.length; row++) {
1987
+ const groups = self.groupedColumns[row];
1988
+ for (let j = 0; j < groups.length; j++) {
1989
+ groupedColumns.push(Object.assign({ row }, groups[j]));
1990
+ }
1991
+ }
1992
+ if (groupedColumns.length > 0) {
1993
+ const rowHeight = self.style.columnGroupRowHeight;
1994
+ const toggleHandleSize = rowHeight * 0.5;
1995
+ const toggleHandlePadding = (rowHeight - toggleHandleSize) * 0.5;
1996
+
1997
+ for (let i = 0; i < groupedColumns.length; i++) {
1998
+ const group = groupedColumns[i];
1999
+ const { row, collapsed } = group;
2000
+
2001
+ const topY = -my + row * rowHeight;
2002
+ const centerY = topY + rowHeight * 0.5;
2003
+ const bottomY = topY + rowHeight - toggleHandlePadding;
2004
+ const leftmostX =
2005
+ rowHeaderCellWidth - toggleHandleSize - toggleHandlePadding;
2006
+
2007
+ const drawGroupHandleAtX = (x) =>
2008
+ drawGroupHandle(
2009
+ x,
2010
+ topY + toggleHandlePadding,
2011
+ toggleHandleSize,
2012
+ group.collapsed,
2013
+ );
2014
+ const pushToVisibleGroups = (leftX, rightX) =>
2015
+ self.visibleGroups.push({
2016
+ type: 'c',
2017
+ collapsed,
2018
+ from: group.from,
2019
+ to: group.to,
2020
+ row,
2021
+ x: leftX + mx,
2022
+ y: topY + my,
2023
+ x2: rightX + mx,
2024
+ y2: bottomY + my,
2025
+ });
2026
+
2027
+ //#region check the relationship between this group and frozen columns
2028
+ const crossTheFrozen =
2029
+ group.from < self.frozenColumn && group.to >= self.frozenColumn;
2030
+ const notInFrozen = group.from >= self.frozenColumn;
2031
+ //#endregion
2032
+
2033
+ if (collapsed) {
2034
+ let leftX = leftmostX + toggleHandleSize;
2035
+ // This group is not sticking on the first column
2036
+ if (group.from > 0) {
2037
+ let colIndex = group.to + 1;
2038
+ let col = columnGroupsRectInfo[colIndex];
2039
+ if (!col) {
2040
+ colIndex = group.from - 1;
2041
+ col = columnGroupsRectInfo[colIndex];
2042
+ if (!col) continue; // don't draw this group indicator because it is invisible
2043
+ leftX = col.x + col.w - toggleHandleSize * 0.5;
2044
+ } else {
2045
+ leftX = col.x + toggleHandlePadding;
2046
+ }
2047
+ if (colIndex >= self.frozenColumn) {
2048
+ const compare =
2049
+ frozenColumnsWidth + rowHeaderCellWidth - toggleHandlePadding;
2050
+ // don't draw this group indicator because it is hidden by frozen columns
2051
+ if (leftX < compare) continue;
2052
+ }
2053
+ }
2054
+ const rightX = leftX + toggleHandleSize;
2055
+ ctx.save();
2056
+ ctx.strokeStyle = self.style.groupIndicatorColor;
2057
+ ctx.fillStyle = self.style.groupIndicatorBackgroundColor;
2058
+ drawGroupHandleAtX(leftX);
2059
+ ctx.restore();
2060
+ pushToVisibleGroups(leftX, rightX);
2061
+ } // end of collapsed group
2062
+
2063
+ /** @type {number} pointer for loop */
2064
+ let ptr;
2065
+ let left = columnGroupsRectInfo[group.from];
2066
+ let right = columnGroupsRectInfo[group.to];
2067
+
2068
+ let containsBegining = true;
2069
+ let containsEnd = true;
2070
+ ptr = group.from;
2071
+ while (!left && ptr < group.to) {
2072
+ left = columnGroupsRectInfo[++ptr];
2073
+ containsBegining = false;
2074
+ }
2075
+ ptr = group.to;
2076
+ while (!right && ptr > group.from) {
2077
+ right = columnGroupsRectInfo[--ptr];
2078
+ containsEnd = false;
2079
+ }
2080
+ if (!left || !right) continue;
2081
+ let rightX = right.x + right.w;
2082
+ let leftX = left.x;
2083
+ if (crossTheFrozen) {
2084
+ const rightCompare = columnGroupsRectInfo[self.frozenColumn - 1];
2085
+ if (rightCompare) {
2086
+ let compareX = rightCompare.x + rightCompare.w;
2087
+ if (!onTheLeft) compareX += toggleHandleSize;
2088
+ if (compareX >= rightX) {
2089
+ right = rightCompare;
2090
+ rightX = right.x + right.w;
2091
+ containsEnd = false;
2092
+ }
2093
+ }
2094
+ }
2095
+
2096
+ let minLeftX =
2097
+ rowHeaderCellWidth + (notInFrozen ? frozenColumnsWidth : 0);
2098
+ if (rightX + (onTheLeft ? 0 : toggleHandleSize) < minLeftX) continue;
2099
+
2100
+ rightX -= toggleHandlePadding;
2101
+ leftX += toggleHandlePadding;
2102
+
2103
+ ctx.save();
2104
+ ctx.strokeStyle = self.style.groupIndicatorColor;
2105
+ ctx.fillStyle = self.style.groupIndicatorBackgroundColor;
2106
+ const lineCoords = [];
2107
+
2108
+ if (onTheLeft) {
2109
+ // avoid lines from two groups be overlapping
2110
+ minLeftX += toggleHandlePadding * 2;
2111
+ if (leftX < minLeftX) leftX = minLeftX;
2112
+ if (group.from === 0) leftX -= toggleHandlePadding * 2;
2113
+ if (rightX >= leftX) {
2114
+ lineCoords.push(leftX, centerY, rightX, centerY);
2115
+ if (containsEnd) lineCoords.push(rightX, bottomY);
2116
+ } else {
2117
+ rightX = leftX;
2118
+ }
2119
+
2120
+ leftX -= toggleHandleSize;
2121
+ drawGroupHandleAtX(leftX);
2122
+ // add more clickable area into `visibleGroups`
2123
+ rightX += toggleHandlePadding - 1;
2124
+ } else {
2125
+ // handle on the right
2126
+ if (leftX < minLeftX) leftX = minLeftX;
2127
+ if (group.from === 0) leftX -= toggleHandlePadding * 2;
2128
+ if (containsEnd) {
2129
+ if (group.to === self.frozenColumn - 1) {
2130
+ rightX -= toggleHandleSize;
2131
+ } else {
2132
+ rightX += toggleHandlePadding * 2;
2133
+ }
2134
+ drawGroupHandleAtX(rightX);
2135
+ }
2136
+ if (leftX > rightX) {
2137
+ leftX = rightX;
2138
+ } else {
2139
+ if (group.from === 0) {
2140
+ containsBegining = true;
2141
+ leftX = leftmostX + toggleHandleSize;
2142
+ }
2143
+ if (containsBegining) lineCoords.push(leftX, bottomY);
2144
+ lineCoords.push(leftX, centerY, rightX, centerY);
2145
+ }
2146
+
2147
+ // add more clickable area into `visibleGroups`
2148
+ leftX -= toggleHandlePadding + 1;
2149
+ if (containsEnd) rightX += toggleHandleSize;
2150
+ }
2151
+ drawLines(lineCoords);
2152
+ ctx.restore();
2153
+ pushToVisibleGroups(leftX, rightX);
2154
+ }
2155
+ }
2156
+ //#endregion Columns Grouping
2157
+
2158
+ //#region Rows Grouping
2159
+ /** it extends `self.groupedRows` */
2160
+ const groupedRows = [];
2161
+ for (let col = 0; col < self.groupedRows.length; col++) {
2162
+ const groups = self.groupedRows[col];
2163
+ for (let j = 0; j < groups.length; j++) {
2164
+ groupedRows.push(Object.assign({ col }, groups[j]));
2165
+ }
2166
+ }
2167
+ if (groupedRows.length > 0) {
2168
+ const colWidth = self.style.rowGroupColumnWidth;
2169
+ const toggleHandleSize = colWidth * 0.5;
2170
+ const toggleHandlePadding = (colWidth - toggleHandleSize) * 0.5;
2171
+
2172
+ for (let i = 0; i < groupedRows.length; i++) {
2173
+ const group = groupedRows[i];
2174
+ const { col, collapsed } = group;
2175
+
2176
+ const leftX = -mx + col * colWidth;
2177
+ const centerX = leftX + colWidth * 0.5;
2178
+ const rightX = leftX + colWidth - toggleHandlePadding;
2179
+ const topmostY =
2180
+ columnHeaderCellHeight - toggleHandleSize - toggleHandlePadding;
2181
+
2182
+ const drawGroupHandleAtY = (y) =>
2183
+ drawGroupHandle(
2184
+ leftX + toggleHandlePadding,
2185
+ y,
2186
+ toggleHandleSize,
2187
+ group.collapsed,
2188
+ );
2189
+ const pushToVisibleGroups = (topY, bottomY) =>
2190
+ self.visibleGroups.push({
2191
+ type: 'r',
2192
+ collapsed,
2193
+ from: group.from,
2194
+ to: group.to,
2195
+ col,
2196
+ x: leftX + mx,
2197
+ y: topY + my,
2198
+ x2: rightX + mx,
2199
+ y2: bottomY + my,
2200
+ });
2201
+
2202
+ //#region check the relationship between this group and frozen columns
2203
+ const crossTheFrozen =
2204
+ group.from < self.frozenRow && group.to >= self.frozenRow;
2205
+ const notInFrozen = group.from >= self.frozenRow;
2206
+ //#endregion
2207
+
2208
+ if (collapsed) {
2209
+ let topY = topmostY + toggleHandleSize;
2210
+ // This group is not sticking on the first column
2211
+ if (group.from > 0) {
2212
+ let rowIndex = group.to + 1;
2213
+ let row = rowGroupsRectInfo[rowIndex];
2214
+ if (!row) {
2215
+ rowIndex = group.from - 1;
2216
+ row = rowGroupsRectInfo[rowIndex];
2217
+ if (!row) continue; // don't draw this group indicator because it is invisible
2218
+ topY = row.y + row.h - toggleHandleSize * 0.5;
2219
+ } else {
2220
+ topY = row.y;
2221
+ }
2222
+ if (rowIndex >= self.frozenRow) {
2223
+ const compare =
2224
+ frozenRowsHeight +
2225
+ columnHeaderCellHeight -
2226
+ toggleHandlePadding;
2227
+ // don't draw this group indicator because it is hidden by frozen columns
2228
+ if (topY < compare) continue;
2229
+ }
2230
+ }
2231
+ const bottomY = topY + toggleHandleSize;
2232
+ ctx.save();
2233
+ ctx.strokeStyle = self.style.groupIndicatorColor;
2234
+ ctx.fillStyle = self.style.groupIndicatorBackgroundColor;
2235
+ drawGroupHandleAtY(topY);
2236
+ ctx.restore();
2237
+ pushToVisibleGroups(topY, bottomY);
2238
+ } // end of collapsed group
2239
+
2240
+ /** @type {number} pointer for loop */
2241
+ let ptr;
2242
+ let top = rowGroupsRectInfo[group.from];
2243
+ let bottom = rowGroupsRectInfo[group.to];
2244
+
2245
+ let containsBegining = true;
2246
+ let containsEnd = true;
2247
+ ptr = group.from;
2248
+ while (!top && ptr < group.to) {
2249
+ top = rowGroupsRectInfo[++ptr];
2250
+ containsBegining = false;
2251
+ }
2252
+ ptr = group.to;
2253
+ while (!bottom && ptr > group.from) {
2254
+ bottom = rowGroupsRectInfo[--ptr];
2255
+ containsEnd = false;
2256
+ }
2257
+ if (!top || !bottom) continue;
2258
+ let bottomY = bottom.y + bottom.h;
2259
+ let topY = top.y;
2260
+ if (crossTheFrozen) {
2261
+ const bottomCompare = rowGroupsRectInfo[self.frozenRow - 1];
2262
+ if (bottomCompare) {
2263
+ let compareY = bottomCompare.y + bottomCompare.h;
2264
+ if (!onTheTop) compareY += toggleHandleSize;
2265
+ if (compareY >= bottomY) {
2266
+ bottom = bottomCompare;
2267
+ bottomY = bottom.y + bottom.h;
2268
+ containsEnd = false;
2269
+ }
2270
+ }
2271
+ }
2272
+
2273
+ let minTopY =
2274
+ columnHeaderCellHeight + (notInFrozen ? frozenRowsHeight : 0);
2275
+ if (bottomY + (onTheTop ? 0 : toggleHandleSize) < minTopY) continue;
2276
+
2277
+ bottomY -= toggleHandlePadding;
2278
+ topY += toggleHandlePadding;
2279
+
2280
+ ctx.save();
2281
+ ctx.strokeStyle = self.style.groupIndicatorColor;
2282
+ ctx.fillStyle = self.style.groupIndicatorBackgroundColor;
2283
+ const lineCoords = [];
2284
+
2285
+ if (onTheTop) {
2286
+ // avoid lines from two groups be overlapping
2287
+ minTopY += toggleHandlePadding * 2;
2288
+ if (topY < minTopY) topY = minTopY;
2289
+ if (group.from === 0) topY -= toggleHandlePadding * 2;
2290
+ if (bottomY >= topY) {
2291
+ lineCoords.push(centerX, topY, centerX, bottomY);
2292
+ if (containsEnd) lineCoords.push(rightX, bottomY);
2293
+ } else {
2294
+ bottomY = topY;
2295
+ }
2296
+
2297
+ topY -= toggleHandleSize;
2298
+ drawGroupHandleAtY(topY);
2299
+
2300
+ // add more clickable area into `visibleGroups`
2301
+ bottomY += toggleHandlePadding - 1;
2302
+ } else {
2303
+ // handle on the bottom
2304
+ if (topY < minTopY) topY = minTopY;
2305
+ if (group.from === 0) topY -= toggleHandlePadding * 2;
2306
+ if (containsEnd) {
2307
+ if (group.to === self.frozenRow - 1) {
2308
+ bottomY -= toggleHandleSize;
2309
+ } else {
2310
+ // bottomY += toggleHandlePadding * 2;
2311
+ }
2312
+ drawGroupHandleAtY(bottomY);
2313
+ }
2314
+ if (topY > bottomY) {
2315
+ topY = bottomY;
2316
+ } else {
2317
+ if (group.from === 0) {
2318
+ containsBegining = true;
2319
+ topY = topmostY + toggleHandleSize;
2320
+ }
2321
+ if (containsBegining) lineCoords.push(rightX, topY);
2322
+ lineCoords.push(centerX, topY, centerX, bottomY);
2323
+ // add more clickable area into `visibleGroups`
2324
+ topY -= toggleHandlePadding + 1;
2325
+ }
2326
+ // add more clickable area into `visibleGroups`
2327
+ if (containsEnd) bottomY += toggleHandleSize;
2328
+ }
2329
+ drawLines(lineCoords);
2330
+ ctx.restore();
2331
+ pushToVisibleGroups(topY, bottomY);
2332
+ }
2333
+ }
2334
+ //#endregion Rows Grouping
2335
+ }
1310
2336
  function drawFrozenRows() {
1311
- var n,
1312
- ln = Math.min(data.length, self.frozenRow);
2337
+ var rowOrderIndex,
2338
+ ln = Math.min(viewData.length, self.frozenRow);
1313
2339
  x =
1314
2340
  -self.scrollBox.scrollLeft +
1315
2341
  self.scrollPixelLeft +
1316
2342
  self.style.cellBorderWidth;
1317
2343
  y = columnHeaderCellHeight;
1318
- for (r = 0; r < ln; r += 1) {
1319
- n = self.orders.rows[r];
1320
- if (!drawRow(n, r)) {
2344
+ for (rowIndex = 0; rowIndex < ln; rowIndex += 1) {
2345
+ rowOrderIndex = self.orders.rows[rowIndex];
2346
+ if (!drawRow(rowOrderIndex, rowIndex)) {
1321
2347
  break;
1322
2348
  }
1323
2349
  }
1324
2350
  if (self.attributes.allowFreezingRows) {
1325
2351
  // HACK great, another stupid magic number.
1326
2352
  // Background will appear as a 0.5px artifact behind the row freeze bar without this hack
1327
- y +=
1328
- self.style.frozenMarkerBorderWidth +
1329
- self.style.frozenMarkerWidth -
1330
- 0.4999999999;
2353
+ if (y > columnHeaderCellHeight) y += self.style.frozenMarkerWidth;
2354
+ y += self.style.frozenMarkerBorderWidth - 0.4999999999;
1331
2355
  }
1332
2356
  self.lastFrozenRowPixel = y;
1333
2357
  }
@@ -1343,10 +2367,10 @@ export default function (self) {
1343
2367
  );
1344
2368
  self.ctx.clip();
1345
2369
  }
1346
- var o,
1347
- n,
1348
- i,
1349
- g = s.length;
2370
+ var columnOrderIndex,
2371
+ rowOrderIndex,
2372
+ headerIndex,
2373
+ g = schema.length;
1350
2374
  x =
1351
2375
  -self.scrollBox.scrollLeft +
1352
2376
  self.scrollPixelLeft +
@@ -1357,11 +2381,21 @@ export default function (self) {
1357
2381
  self.scrollPixelTop +
1358
2382
  self.style.cellBorderWidth;
1359
2383
  }
1360
- for (r = self.frozenRow + self.scrollIndexTop; r < l; r += 1) {
1361
- n = self.orders.rows[r];
1362
- self.scrollIndexBottom = r;
2384
+ for (
2385
+ rowIndex = self.frozenRow + self.scrollIndexTop;
2386
+ rowIndex < l;
2387
+ rowIndex += 1
2388
+ ) {
2389
+ rowOrderIndex = self.orders.rows[rowIndex];
2390
+ self.scrollIndexBottom = rowIndex;
1363
2391
  self.scrollPixelBottom = y;
1364
- if (!drawRow(n, r)) {
2392
+ if (
2393
+ self.cellTree.rows.length > 0 &&
2394
+ Object.keys(self.cellTree.rows[rowOrderIndex]).length > 0 &&
2395
+ self.cellTree.rows[rowOrderIndex].hide
2396
+ )
2397
+ continue;
2398
+ if (!drawRow(rowOrderIndex, rowIndex)) {
1365
2399
  break;
1366
2400
  }
1367
2401
  }
@@ -1371,14 +2405,30 @@ export default function (self) {
1371
2405
  }
1372
2406
  rowHeight = cellHeight = self.style.cellHeight;
1373
2407
  rowOpen = false;
1374
- for (o = self.scrollIndexLeft; o < g; o += 1) {
1375
- i = self.orders.columns[o];
1376
- x += drawCell(self.newRow, data.length, data.length)(s[i], i, o);
2408
+ for (
2409
+ columnOrderIndex = self.scrollIndexLeft;
2410
+ columnOrderIndex < g;
2411
+ columnOrderIndex += 1
2412
+ ) {
2413
+ if (!isColumnCollapsedByGroup(columnOrderIndex)) {
2414
+ headerIndex = self.orders.columns[columnOrderIndex];
2415
+ x += drawCell(self.newRow, viewData.length, viewData.length)(
2416
+ schema[headerIndex],
2417
+ headerIndex,
2418
+ columnOrderIndex,
2419
+ );
2420
+ }
1377
2421
  if (x > self.width + self.scrollBox.scrollLeft) {
1378
2422
  break;
1379
2423
  }
1380
2424
  }
1381
- rowHeaders.push([self.newRow, data.length, data.length, y, rowHeight]);
2425
+ rowHeaders.push([
2426
+ self.newRow,
2427
+ viewData.length,
2428
+ viewData.length,
2429
+ y,
2430
+ rowHeight,
2431
+ ]);
1382
2432
  }
1383
2433
  self.ctx.restore();
1384
2434
  }
@@ -1414,6 +2464,7 @@ export default function (self) {
1414
2464
  self.ctx.lineWidth = self.style.reorderMarkerBorderWidth;
1415
2465
  self.ctx.strokeStyle = self.style.reorderMarkerBorderColor;
1416
2466
  if (self.dragMode === 'row-reorder') {
2467
+ b.height = self.getSelectedRowsHeight();
1417
2468
  b.width = w;
1418
2469
  b.x = 0;
1419
2470
  m.width = w;
@@ -1424,6 +2475,7 @@ export default function (self) {
1424
2475
  self.ctx.lineWidth = self.style.reorderMarkerIndexBorderWidth;
1425
2476
  self.ctx.strokeStyle = self.style.reorderMarkerIndexBorderColor;
1426
2477
  if (
2478
+ !self.isRowSelected(self.reorderTarget.sortRowIndex) &&
1427
2479
  self.currentCell.rowIndex !== self.reorderObject.rowIndex &&
1428
2480
  self.currentCell.rowIndex > -1 &&
1429
2481
  self.currentCell.rowIndex < l
@@ -1436,6 +2488,7 @@ export default function (self) {
1436
2488
  );
1437
2489
  }
1438
2490
  } else if (self.dragMode === 'column-reorder' && self.reorderObject) {
2491
+ b.width = self.getSelectedColumnsWidth();
1439
2492
  b.height = h;
1440
2493
  b.y = 0;
1441
2494
  m.height = h;
@@ -1447,10 +2500,12 @@ export default function (self) {
1447
2500
  self.ctx.lineWidth = self.style.reorderMarkerIndexBorderWidth;
1448
2501
  self.ctx.strokeStyle = self.style.reorderMarkerIndexBorderColor;
1449
2502
  if (
2503
+ !self.isColumnSelected(self.reorderTarget.sortColumnIndex) &&
1450
2504
  self.currentCell.sortColumnIndex !==
1451
2505
  self.reorderObject.sortColumnIndex &&
2506
+ self.reorderTarget.columnIndex !== self.reorderObject.columnIndex &&
1452
2507
  self.currentCell.sortColumnIndex > -1 &&
1453
- self.currentCell.sortColumnIndex < s.length
2508
+ self.currentCell.sortColumnIndex < schema.length
1454
2509
  ) {
1455
2510
  addBorderLine(
1456
2511
  m,
@@ -1461,16 +2516,43 @@ export default function (self) {
1461
2516
  }
1462
2517
  }
1463
2518
  }
2519
+ function drawResizeMarkers() {
2520
+ if (
2521
+ !self.attributes.resizeAfterDragged ||
2522
+ !self.draggingItem ||
2523
+ !self.pendingDragResize ||
2524
+ (self.dragMode != 'ew-resize' && self.dragMode != 'ns-resize')
2525
+ ) {
2526
+ return;
2527
+ }
2528
+
2529
+ const resizingRow = self.dragMode == 'ns-resize';
2530
+ const height = resizingRow ? self.style.resizeMarkerSize : self.height;
2531
+ const width = resizingRow ? self.width : self.style.resizeMarkerSize;
2532
+ const minX =
2533
+ self.dragStart.x -
2534
+ (self.resizingStartingWidth - self.style.minColumnWidth);
2535
+ const minY =
2536
+ self.dragStart.y -
2537
+ (self.resizingStartingHeight - self.style.minRowHeight);
2538
+ const x = resizingRow ? 0 : Math.max(self.pendingDragResize.x, minX);
2539
+ const y = !resizingRow ? 0 : Math.max(self.pendingDragResize.y, minY);
2540
+
2541
+ self.ctx.fillStyle = self.style.resizeMarkerColor;
2542
+ fillRect(x, y, width, height);
2543
+ }
1464
2544
  function drawBorder() {
1465
2545
  self.ctx.lineWidth = self.style.gridBorderWidth;
1466
2546
  self.ctx.strokeStyle = self.style.gridBorderColor;
1467
2547
  strokeRect(0, 0, self.width, self.height);
1468
2548
  }
1469
2549
  function drawSelectionBorders() {
1470
- self.ctx.lineWidth = self.style.selectionOverlayBorderWidth;
1471
- self.ctx.strokeStyle = self.style.selectionOverlayBorderColor;
1472
2550
  function dsb(c) {
1473
- addBorderLine(c[0], c[1]);
2551
+ if (!self.fillOverlay.handle) {
2552
+ self.ctx.lineWidth = self.style.selectionOverlayBorderWidth;
2553
+ self.ctx.strokeStyle = self.style.selectionOverlayBorderColor;
2554
+ addBorderLine(c[0], c[1]);
2555
+ }
1474
2556
  }
1475
2557
  selectionBorders
1476
2558
  .filter(function (c) {
@@ -1513,9 +2595,14 @@ export default function (self) {
1513
2595
  })
1514
2596
  .forEach(dsb);
1515
2597
  self.ctx.restore();
2598
+
2599
+ drawFillOverlay();
1516
2600
  }
1517
2601
  function drawSelectionHandles() {
1518
- if (self.mobile || self.attributes.allowMovingSelection) {
2602
+ if (
2603
+ (self.mobile || self.attributes.allowMovingSelection) &&
2604
+ self.attributes.editable
2605
+ ) {
1519
2606
  self.ctx.lineWidth = self.style.selectionHandleBorderWidth;
1520
2607
  self.ctx.strokeStyle = self.style.selectionHandleBorderColor;
1521
2608
  self.ctx.fillStyle = self.style.selectionHandleColor;
@@ -1536,6 +2623,62 @@ export default function (self) {
1536
2623
  });
1537
2624
  }
1538
2625
  }
2626
+ function drawFillOverlay() {
2627
+ if (!self.fillOverlay.handle || !self.fillOverlay.snapTo) {
2628
+ return;
2629
+ }
2630
+
2631
+ self.ctx.save();
2632
+
2633
+ const overlay = self.fillOverlay;
2634
+ const handle = overlay.handle;
2635
+ const toX = overlay.snapTo.x;
2636
+ const toY = overlay.snapTo.y;
2637
+
2638
+ // The cell that the cursor is moving over. This may be unavailable
2639
+ // when the cursor is outside the grid or is pointing to something else.
2640
+ //
2641
+ // When unavailable, we use the actual position of the cursor to draw
2642
+ // the overlay.
2643
+ const snap = overlay.snap;
2644
+
2645
+ // Calculate the X, Y coordinates of the cursor with snap positions and
2646
+ // the direction of the movement in mind.
2647
+ //
2648
+ // When the movement is horizontal, we get the Y coordinate from the handle,
2649
+ // and vice-versa.
2650
+ const cursorX =
2651
+ overlay.direction === 'x'
2652
+ ? snap
2653
+ ? snap.x + (overlay.x < toX ? 0 : snap.width)
2654
+ : overlay.x
2655
+ : handle.x;
2656
+ const cursorY =
2657
+ overlay.direction === undefined || overlay.direction === 'y'
2658
+ ? snap
2659
+ ? snap.y + (overlay.y < toY ? 0 : snap.height)
2660
+ : overlay.y
2661
+ : handle.y;
2662
+
2663
+ // The final coordinates of the rect. We use 'minX' and 'minY' to avoid
2664
+ // drawing on the frozen area.
2665
+ const x = Math.max(Math.min(toX, cursorX), overlay.minX);
2666
+ const y = Math.max(Math.min(toY, cursorY), overlay.minY);
2667
+
2668
+ // Width and height of the rect are the difference between the
2669
+ // coordinates of both the rect and the cursor. The user may be pointing
2670
+ // behind and/or upwards of the rect, so we need to take that into
2671
+ // account.
2672
+ const width = Math.max(toX, cursorX) - x;
2673
+ const height = Math.max(toY, cursorY) - y;
2674
+
2675
+ self.ctx.strokeStyle = self.style.fillOverlayBorderColor;
2676
+ self.ctx.lineWidth = self.style.fillOverlayBorderWidth;
2677
+ self.ctx.setLineDash([3, 3]);
2678
+ strokeRect(x, y, width, height);
2679
+ self.ctx.setLineDash([]);
2680
+ self.ctx.restore();
2681
+ }
1539
2682
  function drawActiveCell() {
1540
2683
  if (!aCell) {
1541
2684
  return;
@@ -1570,77 +2713,171 @@ export default function (self) {
1570
2713
  }
1571
2714
  function drawFrozenMarkers() {
1572
2715
  var my = self.lastFrozenRowPixel - self.style.frozenMarkerWidth,
1573
- mx = self.lastFrozenColumnPixel - self.style.frozenMarkerBorderWidth,
2716
+ mx = self.lastFrozenColumnPixel - self.style.frozenMarkerWidth,
1574
2717
  xHover =
1575
2718
  self.currentCell && self.currentCell.style === 'frozen-row-marker',
1576
2719
  yHover =
1577
2720
  self.currentCell && self.currentCell.style === 'frozen-column-marker';
2721
+ let rowHeaderCellWidth = self.getRowHeaderCellWidth();
1578
2722
  self.ctx.lineWidth = self.style.frozenMarkerBorderWidth;
1579
2723
  if (self.attributes.allowFreezingColumns) {
1580
- self.ctx.fillStyle = yHover
1581
- ? self.style.frozenMarkerHoverColor
1582
- : self.style.frozenMarkerColor;
1583
- self.ctx.strokeStyle = yHover
1584
- ? self.style.frozenMarkerHoverBorderColor
1585
- : self.style.frozenMarkerBorderColor;
1586
- fillRect(mx, 0, self.style.frozenMarkerWidth, self.height);
1587
- strokeRect(mx, 0, self.style.frozenMarkerWidth, self.height);
1588
- self.visibleCells.unshift({
1589
- x: mx,
1590
- y: 0,
1591
- height: self.height,
1592
- width:
1593
- self.style.frozenMarkerWidth + self.style.frozenMarkerBorderWidth,
1594
- style: 'frozen-column-marker',
1595
- });
1596
- }
1597
- if (self.attributes.allowFreezingRows) {
1598
- self.ctx.fillStyle = xHover
1599
- ? self.style.frozenMarkerHoverColor
1600
- : self.style.frozenMarkerColor;
1601
- self.ctx.strokeStyle = xHover
1602
- ? self.style.frozenMarkerHoverBorderColor
1603
- : self.style.frozenMarkerBorderColor;
1604
- fillRect(0, my, self.width, self.style.frozenMarkerWidth);
1605
- strokeRect(0, my, self.width, self.style.frozenMarkerWidth);
1606
- self.visibleCells.unshift({
1607
- x: 0,
1608
- y: my,
1609
- height:
1610
- self.style.frozenMarkerWidth + self.style.frozenMarkerBorderWidth,
1611
- width: self.width,
1612
- style: 'frozen-row-marker',
1613
- });
1614
- }
1615
- if (self.freezeMarkerPosition) {
1616
- self.ctx.fillStyle = self.style.frozenMarkerActiveColor;
1617
- self.ctx.strokeStyle = self.style.frozenMarkerActiveBorderColor;
1618
- if (self.dragMode === 'frozen-column-marker') {
2724
+ if (
2725
+ !self.freezeMarkerPosition ||
2726
+ (self.freezeMarkerPosition && self.dragMode === 'frozen-row-marker')
2727
+ ) {
2728
+ if (mx > self.style.rowHeaderCellWidth) {
2729
+ self.ctx.fillStyle = self.style.frozenMarkerColor;
2730
+ self.ctx.strokeStyle = self.style.frozenMarkerBorderColor;
2731
+ fillRect(mx, 0, self.style.frozenMarkerWidth, self.height);
2732
+ strokeRect(mx, 0, self.style.frozenMarkerWidth, self.height);
2733
+ }
2734
+ self.ctx.fillStyle = yHover
2735
+ ? self.style.frozenMarkerHoverColor
2736
+ : self.style.frozenMarkerHeaderColor;
2737
+ self.ctx.strokeStyle = yHover
2738
+ ? self.style.frozenMarkerHoverBorderColor
2739
+ : self.style.frozenMarkerHeaderColor;
1619
2740
  fillRect(
1620
- self.freezeMarkerPosition.x,
2741
+ mx,
1621
2742
  0,
1622
2743
  self.style.frozenMarkerWidth,
1623
- self.height,
2744
+ self.style.columnHeaderCellHeight,
1624
2745
  );
1625
2746
  strokeRect(
1626
- self.freezeMarkerPosition.x,
2747
+ mx,
1627
2748
  0,
1628
2749
  self.style.frozenMarkerWidth,
1629
- self.height,
2750
+ self.style.columnHeaderCellHeight,
1630
2751
  );
1631
- } else {
2752
+ }
2753
+
2754
+ if (
2755
+ !self.dragStartObject ||
2756
+ (self.dragStartObject &&
2757
+ self.dragStartObject.style !== 'rowHeaderCell')
2758
+ ) {
2759
+ self.visibleCells.unshift({
2760
+ x: mx,
2761
+ y: 0,
2762
+ height: self.height,
2763
+ width: self.style.frozenMarkerWidth, // + self.style.frozenMarkerBorderWidth,
2764
+ style: 'frozen-column-marker',
2765
+ });
2766
+ }
2767
+ }
2768
+ if (self.attributes.allowFreezingRows) {
2769
+ if (
2770
+ !self.freezeMarkerPosition ||
2771
+ (self.freezeMarkerPosition &&
2772
+ self.dragMode === 'frozen-column-marker')
2773
+ ) {
2774
+ if (my > rowHeaderCellWidth) {
2775
+ self.ctx.fillStyle = self.style.frozenMarkerColor;
2776
+ self.ctx.strokeStyle = self.style.frozenMarkerBorderColor;
2777
+ fillRect(0, my, self.width, self.style.frozenMarkerWidth);
2778
+ strokeRect(0, my, self.width, self.style.frozenMarkerWidth);
2779
+ }
2780
+ self.ctx.fillStyle = xHover
2781
+ ? self.style.frozenMarkerHoverColor
2782
+ : self.style.frozenMarkerHeaderColor;
2783
+ self.ctx.strokeStyle = xHover
2784
+ ? self.style.frozenMarkerHoverBorderColor
2785
+ : self.style.frozenMarkerHeaderColor;
2786
+ fillRect(0, my, rowHeaderCellWidth, self.style.frozenMarkerWidth);
2787
+ strokeRect(0, my, rowHeaderCellWidth, self.style.frozenMarkerWidth);
2788
+ }
2789
+ var height =
2790
+ self.style.frozenMarkerWidth + self.style.frozenMarkerBorderWidth;
2791
+ if (
2792
+ !self.dragStartObject ||
2793
+ (self.dragStartObject &&
2794
+ self.dragStartObject.style !== 'columnHeaderCell')
2795
+ ) {
2796
+ self.visibleCells.unshift({
2797
+ x: 0,
2798
+ y: my,
2799
+ height: height,
2800
+ width: self.width,
2801
+ style: 'frozen-row-marker',
2802
+ });
2803
+ }
2804
+ rowGroupsFrozenInfo = { y: my, h: height };
2805
+ }
2806
+ if (self.freezeMarkerPosition) {
2807
+ self.ctx.fillStyle = self.style.frozenMarkerActiveColor;
2808
+ self.ctx.strokeStyle = self.style.frozenMarkerActiveBorderColor;
2809
+ if (self.dragMode === 'frozen-column-marker') {
2810
+ let posX = self.freezeMarkerPosition.x;
2811
+ let nearCell = self.getCellAt(posX, 0);
2812
+ if (self.freezeMarkerPosition.isGrab) posX = mx;
2813
+ if (posX > rowHeaderCellWidth) {
2814
+ let x =
2815
+ nearCell.x -
2816
+ self.style.frozenMarkerWidth -
2817
+ self.style.frozenMarkerBorderWidth;
2818
+ if (posX > x + nearCell.width / 2) x = x + nearCell.width;
2819
+ self.ctx.fillStyle = self.style.frozenMarkerColor;
2820
+ self.ctx.strokeStyle = self.style.frozenMarkerBorderColor;
2821
+ fillRect(x, 0, self.style.frozenMarkerWidth, self.height);
2822
+ strokeRect(x, 0, self.style.frozenMarkerWidth, self.height);
2823
+ self.ctx.fillStyle = self.style.frozenMarkerHeaderColor;
2824
+ self.ctx.strokeStyle = self.style.frozenMarkerHeaderColor;
2825
+ fillRect(
2826
+ x,
2827
+ 0,
2828
+ self.style.frozenMarkerWidth,
2829
+ self.style.columnHeaderCellHeight,
2830
+ );
2831
+ strokeRect(
2832
+ x,
2833
+ 0,
2834
+ self.style.frozenMarkerWidth,
2835
+ self.style.columnHeaderCellHeight,
2836
+ );
2837
+ }
2838
+ self.ctx.fillStyle = self.style.frozenMarkerActiveHeaderColor;
2839
+ self.ctx.strokeStyle = self.style.frozenMarkerActiveBorderColor;
1632
2840
  fillRect(
2841
+ posX,
1633
2842
  0,
1634
- self.freezeMarkerPosition.y,
1635
- self.width,
1636
2843
  self.style.frozenMarkerWidth,
2844
+ self.style.columnHeaderCellHeight,
1637
2845
  );
1638
2846
  strokeRect(
2847
+ posX,
1639
2848
  0,
1640
- self.freezeMarkerPosition.y,
1641
- self.width,
1642
2849
  self.style.frozenMarkerWidth,
2850
+ self.style.columnHeaderCellHeight,
1643
2851
  );
2852
+ self.ctx.fillStyle = self.style.frozenMarkerActiveColor;
2853
+ fillRect(posX, 0, self.style.frozenMarkerWidth, self.height);
2854
+ strokeRect(posX, 0, self.style.frozenMarkerWidth, self.height);
2855
+ } else {
2856
+ let posY = self.freezeMarkerPosition.y;
2857
+ let nearCell = self.getCellAt(0, posY);
2858
+ if (self.freezeMarkerPosition.isGrab) posY = my;
2859
+ if (posY > self.style.columnHeaderCellHeight) {
2860
+ let y =
2861
+ nearCell.y -
2862
+ self.style.frozenMarkerWidth -
2863
+ self.style.frozenMarkerBorderWidth;
2864
+ if (posY > y + nearCell.height / 2) y = y + nearCell.height;
2865
+ self.ctx.fillStyle = self.style.frozenMarkerColor;
2866
+ self.ctx.strokeStyle = self.style.frozenMarkerBorderColor;
2867
+ fillRect(0, y, self.width, self.style.frozenMarkerWidth);
2868
+ strokeRect(0, y, self.width, self.style.frozenMarkerWidth);
2869
+ self.ctx.fillStyle = self.style.frozenMarkerHeaderColor;
2870
+ self.ctx.strokeStyle = self.style.frozenMarkerHeaderColor;
2871
+ fillRect(0, y, rowHeaderCellWidth, self.style.frozenMarkerWidth);
2872
+ strokeRect(0, y, rowHeaderCellWidth, self.style.frozenMarkerWidth);
2873
+ }
2874
+ self.ctx.fillStyle = self.style.frozenMarkerActiveHeaderColor;
2875
+ self.ctx.strokeStyle = self.style.frozenMarkerActiveBorderColor;
2876
+ fillRect(0, posY, rowHeaderCellWidth, self.style.frozenMarkerWidth);
2877
+ strokeRect(0, posY, rowHeaderCellWidth, self.style.frozenMarkerWidth);
2878
+ self.ctx.fillStyle = self.style.frozenMarkerActiveColor;
2879
+ fillRect(0, posY, self.width, self.style.frozenMarkerWidth);
2880
+ strokeRect(0, posY, self.width, self.style.frozenMarkerWidth);
1644
2881
  }
1645
2882
  }
1646
2883
  }
@@ -1849,6 +3086,7 @@ export default function (self) {
1849
3086
  self.ctx.save();
1850
3087
  initDraw();
1851
3088
  drawBackground();
3089
+ initGroupArea();
1852
3090
  drawFrozenRows();
1853
3091
  drawRows();
1854
3092
  drawActiveCell();
@@ -1857,12 +3095,14 @@ export default function (self) {
1857
3095
  drawSelectionHandles();
1858
3096
  drawReorderMarkers();
1859
3097
  drawMoveMarkers();
3098
+ drawResizeMarkers();
1860
3099
  drawBorder();
1861
3100
  drawSelectionBorders();
1862
3101
  drawScrollBars();
1863
3102
  if (checkScrollHeight) {
1864
3103
  self.resize(true);
1865
3104
  }
3105
+ drawGroupArea();
1866
3106
  drawDebug();
1867
3107
  drawPerfLines();
1868
3108
  if (self.dispatchEvent('afterdraw', {})) {