Skip to content

Commit e2b7753

Browse files
authored
Merge pull request #124 from ibdafna/merged_cells
Cell merging
2 parents 6b674b8 + c130402 commit e2b7753

File tree

8 files changed

+1054
-77
lines changed

8 files changed

+1054
-77
lines changed

examples/example-datagrid/src/index.ts

+78-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import 'es6-promise/auto'; // polyfill Promise on IE
1111

1212
import {
13-
BasicKeyHandler, BasicMouseHandler, BasicSelectionModel, CellRenderer,
13+
BasicKeyHandler, BasicMouseHandler, BasicSelectionModel, CellRenderer, CellGroup,
1414
DataGrid, DataModel, JSONModel, TextRenderer, MutableDataModel, CellEditor, ICellEditor
1515
} from '@lumino/datagrid';
1616

@@ -25,6 +25,74 @@ import {
2525
import '../style/index.css';
2626

2727

28+
class MergedCellModel extends DataModel {
29+
rowCount(region: DataModel.RowRegion): number {
30+
return region === "body" ? 20 : 3;
31+
}
32+
33+
columnCount(region: DataModel.ColumnRegion): number {
34+
return region === "body" ? 6 : 3;
35+
}
36+
37+
data(region: DataModel.CellRegion, row: number, column: number): any {
38+
if (region === "row-header") {
39+
return `R: ${row}, ${column}`;
40+
}
41+
if (region === "column-header") {
42+
return `C: ${row}, ${column}`;
43+
}
44+
if (region === "corner-header") {
45+
return `N: ${row}, ${column}`;
46+
}
47+
return `(${row}, ${column})`;
48+
}
49+
50+
groupCount(region: DataModel.RowRegion): number {
51+
if (region === "body") {
52+
return 3;
53+
} else if (region === "column-header") {
54+
return 1;
55+
} else if (region === "row-header") {
56+
return 2;
57+
} else if (region === "corner-header") {
58+
return 1;
59+
}
60+
return 0;
61+
}
62+
63+
group(region: DataModel.CellRegion, groupIndex: number): CellGroup | null {
64+
if (region === "body") {
65+
return [
66+
{ r1: 1, c1: 1, r2: 2, c2: 2 },
67+
{ r1: 5, c1: 1, r2: 5, c2: 2 },
68+
{ r1: 3, c1: 5, r2: 4, c2: 5 },
69+
][groupIndex];
70+
}
71+
72+
if (region === "column-header") {
73+
return [{ r1: 0, c1: 4, r2: 1, c2: 4 }][
74+
groupIndex
75+
];
76+
}
77+
78+
if (region === "row-header") {
79+
return [
80+
{ r1: 0, c1: 0, r2: 1, c2: 1 },
81+
{ r1: 4, c1: 0, r2: 5, c2: 0 },
82+
][groupIndex];
83+
}
84+
85+
if (region === "corner-header") {
86+
return [{ r1: 0, c1: 0, r2: 1, c2: 1 }][
87+
groupIndex
88+
];
89+
}
90+
91+
return null;
92+
}
93+
}
94+
95+
2896
class LargeDataModel extends DataModel {
2997

3098
rowCount(region: DataModel.RowRegion): number {
@@ -397,6 +465,7 @@ function main(): void {
397465
let model4 = new RandomDataModel(80, 80);
398466
let model5 = new JSONModel(Data.cars);
399467
let model6 = new MutableJSONModel(Data.editable_test_data);
468+
let model7 = new MergedCellModel();
400469

401470
let blueStripeStyle: DataGrid.Style = {
402471
...DataGrid.defaultStyle,
@@ -518,13 +587,20 @@ function main(): void {
518587
let grid7 = new DataGrid();
519588
grid7.dataModel = model6;
520589

590+
let grid8 = new DataGrid();
591+
grid8.dataModel = model7;
592+
grid8.keyHandler = new BasicKeyHandler();
593+
grid8.mouseHandler = new BasicMouseHandler();
594+
grid8.selectionModel = new BasicSelectionModel({ dataModel: model7, selectionMode: 'cell' });
595+
521596
let wrapper1 = createWrapper(grid1, 'Trillion Rows/Cols');
522597
let wrapper2 = createWrapper(grid2, 'Streaming Rows');
523598
let wrapper3 = createWrapper(grid3, 'Random Ticks 1');
524599
let wrapper4 = createWrapper(grid4, 'Random Ticks 2');
525600
let wrapper5 = createWrapper(grid5, 'JSON Data');
526601
let wrapper6 = createWrapper(grid6, 'Editable Grid');
527602
let wrapper7 = createWrapper(grid7, 'Copy');
603+
let wrapper8 = createWrapper(grid8, 'Merged Cells');
528604

529605
let dock = new DockPanel();
530606
dock.id = 'dock';
@@ -536,6 +612,7 @@ function main(): void {
536612
dock.addWidget(wrapper5, { mode: 'split-bottom', ref: wrapper2 });
537613
dock.addWidget(wrapper6, { mode: 'tab-before', ref: wrapper1 });
538614
dock.addWidget(wrapper7, { mode: 'split-bottom', ref: wrapper6 });
615+
dock.addWidget(wrapper8, { mode: 'tab-after', ref: wrapper1 });
539616
dock.activateWidget(wrapper6);
540617

541618
window.onresize = () => { dock.update(); };

packages/datagrid/src/basicmousehandler.ts

+41
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ import {
3535
CellEditor
3636
} from './celleditor';
3737

38+
import {
39+
CellGroup
40+
} from './cellgroup';
41+
3842
/**
3943
* A basic implementation of a data grid mouse handler.
4044
*
@@ -310,6 +314,15 @@ class BasicMouseHandler implements DataGrid.IMouseHandler {
310314
} else if (region === 'row-header') {
311315
r1 = accel ? row : shift ? model.cursorRow : row;
312316
r2 = row;
317+
318+
const selectionGroup: CellGroup = {r1: r1, c1: 0, r2: r2, c2: 0};
319+
const joinedGroup = CellGroup.joinCellGroupsIntersectingAtAxis(grid.dataModel!, ["row-header", "body"], "row", selectionGroup);
320+
// Check if there are any merges
321+
if (joinedGroup.r1 != Number.MAX_VALUE) {
322+
r1 = joinedGroup.r1;
323+
r2 = joinedGroup.r2;
324+
}
325+
313326
c1 = 0;
314327
c2 = Infinity;
315328
cursorRow = accel ? row : shift ? model.cursorRow : row;
@@ -320,6 +333,15 @@ class BasicMouseHandler implements DataGrid.IMouseHandler {
320333
r2 = Infinity;
321334
c1 = accel ? column : shift ? model.cursorColumn : column;
322335
c2 = column;
336+
337+
const selectionGroup: CellGroup = {r1: 0, c1: c1, r2: 0, c2: c2};
338+
const joinedGroup = CellGroup.joinCellGroupsIntersectingAtAxis(grid.dataModel!, ["column-header", "body"], "column", selectionGroup);
339+
// Check if there are any merges
340+
if (joinedGroup.c1 != Number.MAX_VALUE) {
341+
c1 = joinedGroup.c1;
342+
c2 = joinedGroup.c2;
343+
}
344+
323345
cursorRow = accel ? 0 : shift ? model.cursorRow : 0;
324346
cursorColumn = accel ? column : shift ? model.cursorColumn : column;
325347
clear = accel ? 'none' : shift ? 'current' : 'all';
@@ -464,13 +486,32 @@ class BasicMouseHandler implements DataGrid.IMouseHandler {
464486
if (data.region === 'row-header' || mode === 'row') {
465487
r1 = data.row;
466488
r2 = grid.rowAt('body', vy);
489+
490+
const selectionGroup: CellGroup = {r1: r1, c1: 0, r2: r2, c2: 0};
491+
const joinedGroup = CellGroup.joinCellGroupsIntersectingAtAxis(grid.dataModel!, ["row-header", "body"], "row", selectionGroup);
492+
// Check if there are any merges
493+
if (joinedGroup.r1 != Number.MAX_VALUE) {
494+
r1 = Math.min(r1, joinedGroup.r1);
495+
r2 = Math.max(r2, joinedGroup.r2);
496+
}
497+
498+
467499
c1 = 0;
468500
c2 = Infinity;
469501
} else if (data.region === 'column-header' || mode === 'column') {
470502
r1 = 0;
471503
r2 = Infinity;
472504
c1 = data.column;
473505
c2 = grid.columnAt('body', vx);
506+
507+
const selectionGroup: CellGroup = {r1: 0, c1: c1, r2: 0, c2: c2};
508+
const joinedGroup = CellGroup.joinCellGroupsIntersectingAtAxis(grid.dataModel!, ["column-header", "body"], "column", selectionGroup);
509+
// Check if there are any merges
510+
if (joinedGroup.c1 != Number.MAX_VALUE) {
511+
c1 = joinedGroup.c1;
512+
c2 = joinedGroup.c2;
513+
}
514+
474515
} else {
475516
r1 = cursorRow;
476517
r2 = grid.rowAt('body', vy);

packages/datagrid/src/celleditor.ts

+27-5
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ import {
2929
Notification
3030
} from './notification';
3131

32+
import { CellGroup
33+
} from './cellgroup';
34+
3235
/**
3336
* A response object returned from cell input validator
3437
*/
@@ -582,12 +585,31 @@ abstract class CellEditor implements ICellEditor, IDisposable {
582585
*/
583586
protected getCellInfo(cell: CellEditor.CellConfig): Private.ICellInfo {
584587
const { grid, row, column } = cell;
585-
const data = grid.dataModel!.data('body', row, column);
588+
let data, columnX, rowY, width, height;
589+
const cellGroup = CellGroup.getGroup(grid.dataModel!, "body", row, column);
590+
591+
if (cellGroup) {
592+
columnX = grid.headerWidth - grid.scrollX + grid.columnOffset('body', cellGroup.c1);
593+
rowY = grid.headerHeight - grid.scrollY + grid.rowOffset('body', cellGroup.r1);
594+
width = 0;
595+
height = 0;
596+
597+
for (let r = cellGroup.r1; r <= cellGroup.r2; r++) {
598+
height += grid.rowSize('body', r);
599+
}
586600

587-
const columnX = grid.headerWidth - grid.scrollX + grid.columnOffset('body', column);
588-
const rowY = grid.headerHeight - grid.scrollY + grid.rowOffset('body', row);
589-
const width = grid.columnSize('body', column);
590-
const height = grid.rowSize('body', row);
601+
for (let c = cellGroup.c1; c <= cellGroup.c2; c++) {
602+
width += grid.columnSize('body', c);
603+
}
604+
605+
data = grid.dataModel!.data('body', cellGroup.r1, cellGroup.c1);
606+
} else {
607+
columnX = grid.headerWidth - grid.scrollX + grid.columnOffset('body', column);
608+
rowY = grid.headerHeight - grid.scrollY + grid.rowOffset('body', row);
609+
width = grid.columnSize('body', column);
610+
height = grid.rowSize('body', row);
611+
data = grid.dataModel!.data('body', row, column);
612+
}
591613

592614
return {
593615
grid: grid,

packages/datagrid/src/celleditorcontroller.ts

+14-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ import {
2020
ICellEditResponse
2121
} from './celleditor';
2222

23+
import {
24+
CellGroup
25+
} from './cellgroup';
26+
2327
import {
2428
DataModel, MutableDataModel
2529
} from './datamodel';
@@ -183,7 +187,16 @@ class CellEditorController implements ICellEditorController {
183187

184188
const grid = cell.grid;
185189
const dataModel = grid.dataModel as MutableDataModel;
186-
dataModel.setData('body', cell.row, cell.column, response.value);
190+
let row = cell.row;
191+
let column = cell.column;
192+
193+
const cellGroup = CellGroup.getGroup(grid.dataModel!, "body", row, column);
194+
if (cellGroup) {
195+
row = cellGroup.r1;
196+
column = cellGroup.c1;
197+
}
198+
199+
dataModel.setData('body', row, column, response.value);
187200
grid.viewport.node.focus();
188201
if (response.cursorMovement !== 'none') {
189202
grid.moveCursor(response.cursorMovement);

0 commit comments

Comments
 (0)