Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

row grouping #2

Draft
wants to merge 1 commit into
base: row-grouping-perf
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/core/src/docs/examples/row-grouping.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export const RowGrouping: React.VFC<any> = (p: { freezeColumns: number }) => {
columns={cols}
// verticalBorder={false}
rows={rows}
smoothScrollY={true}
/>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ export function blitLastFrame(
mappedColumns: readonly MappedGridColumn[],
effectiveCols: readonly MappedGridColumn[],
getRowHeight: number | ((r: number) => number),
doubleBuffer: boolean
doubleBuffer: boolean,
stickyTopHeight: number
): {
regions: Rectangle[];
} {
Expand Down Expand Up @@ -81,6 +82,8 @@ export function blitLastFrame(
const freezeTrailingRowsHeight =
freezeTrailingRows > 0 ? getFreezeTrailingHeight(rows, freezeTrailingRows, getRowHeight) : 0;

totalHeaderHeight += stickyTopHeight;

const blitWidth = width - stickyWidth - Math.abs(deltaX);
const blitHeight = height - totalHeaderHeight - freezeTrailingRowsHeight - Math.abs(deltaY) - 1;

Expand Down Expand Up @@ -200,7 +203,8 @@ export function blitResizedCol(
height: number,
totalHeaderHeight: number,
effectiveCols: readonly MappedGridColumn[],
resizedIndex: number
resizedIndex: number,
stickyTopHeight: number
) {
const drawRegions: Rectangle[] = [];

Expand All @@ -215,6 +219,8 @@ export function blitResizedCol(
return drawRegions;
}

totalHeaderHeight += stickyTopHeight;

walkColumns(effectiveCols, cellYOffset, translateX, translateY, totalHeaderHeight, (c, drawX, _drawY, clipX) => {
if (c.sourceIndex === resizedIndex) {
const x = Math.max(drawX, clipX) + 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,9 @@ export function drawCells(
renderStateProvider: RenderStateProvider,
getCellRenderer: GetCellRendererCallback,
overrideCursor: (cursor: React.CSSProperties["cursor"]) => void,
minimumCellWidth: number
minimumCellWidth: number,
stickyRows?: number[],
stickyRegionHeight?: number
): Rectangle[] | undefined {
let toDraw = damage?.size ?? Number.MAX_SAFE_INTEGER;
const frameTime = performance.now();
Expand Down Expand Up @@ -369,7 +371,7 @@ export function drawCells(
// because technically the bottom right corner of the outline are on other cells.
ctx.fillRect(
cellX + 1,
drawY + 1,
drawY + 1 + (stickyRegionHeight ?? 0),
cellWidth - (isLastColumn ? 2 : 1),
rh - (isLastRow ? 2 : 1)
);
Expand Down Expand Up @@ -445,7 +447,9 @@ export function drawCells(
}

return toDraw <= 0;
}
},
stickyRows,
totalHeaderHeight
);

ctx.restore();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ export function drawBlanks(
hasAppendRow: boolean,
drawRegions: readonly Rectangle[],
damage: CellSet | undefined,
theme: FullTheme
theme: FullTheme,
stickyRows?: number[]
): void {
if (
damage !== undefined ||
Expand Down Expand Up @@ -96,7 +97,8 @@ export function drawBlanks(
ctx.fillStyle = blankTheme.accentLight;
ctx.fillRect(drawX, drawY, 10_000, rh);
}
}
},
stickyRows
);

ctx.restore();
Expand Down Expand Up @@ -144,6 +146,15 @@ export function overdrawStickyBoundaries(
ctx.strokeStyle = hStroke;
ctx.stroke();
}

// stikcy rows
// const hStroke = vColor === hColor && vStroke !== undefined ? vStroke : blendCache(hColor, theme.bgCell);
// const h = getRowHeight(0);
// ctx.beginPath();
// ctx.moveTo(0, h + 0.5);
// ctx.lineTo(width, h + 0.5);
// ctx.strokeStyle = hStroke;
// ctx.stroke();
}

const getMinMaxXY = (drawRegions: Rectangle[] | undefined, width: number, height: number) => {
Expand Down Expand Up @@ -294,7 +305,8 @@ export function drawGridLines(
freezeTrailingRows: number,
rows: number,
theme: FullTheme,
verticalOnly: boolean = false
verticalOnly: boolean = false,
stickyTopHeight: number = 0
) {
if (spans !== undefined) {
ctx.beginPath();
Expand All @@ -308,7 +320,7 @@ export function drawGridLines(
const hColor = theme.horizontalBorderColor ?? theme.borderColor;
const vColor = theme.borderColor;

const { minX, maxX, minY, maxY } = getMinMaxXY(drawRegions, width, height);
const { minX, maxX, maxY, minY } = getMinMaxXY(drawRegions, width, height);

const toDraw: { x1: number; y1: number; x2: number; y2: number; color: string }[] = [];

Expand Down Expand Up @@ -346,7 +358,7 @@ export function drawGridLines(
const target = freezeY;
while (y + translateY < target) {
const ty = y + translateY;
if (ty >= minY && ty <= maxY - 1) {
if (ty >= minY && ty <= maxY - 1 && ty >= totalHeaderHeight + stickyTopHeight) {
const rowTheme = getRowThemeOverride?.(row);
toDraw.push({
x1: minX,
Expand Down
69 changes: 61 additions & 8 deletions packages/core/src/internal/data-grid/render/data-grid-render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { drawGridHeaders } from "./data-grid-render.header.js";
import { drawGridLines, overdrawStickyBoundaries, drawBlanks, drawExtraRowThemes } from "./data-grid-render.lines.js";
import { blitLastFrame, blitResizedCol, computeCanBlit } from "./data-grid-render.blit.js";
import { drawHighlightRings, drawFillHandle, drawColumnResizeOutline } from "./data-grid.render.rings.js";
import { findLastIndex } from "lodash";

// Future optimization opportunities
// - Create a cache of a buffer used to render the full view of a partially displayed column so that when
Expand Down Expand Up @@ -255,6 +256,34 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) {

const effectiveCols = getEffectiveColumns(mappedColumns, cellXOffset, width, dragAndDropState, translateX);

const stickyRows = [0, 10, 30];

let stickyRegionHeight = 0;
const startRow = cellYOffset;
const drawY = totalHeaderHeight + translateY;
// find the sticky row that fits
const stickyRowIndex = findLastIndex(stickyRows, r => startRow >= r);
if (stickyRowIndex !== -1) {
const stickyRow = stickyRows[stickyRowIndex];
const stickyRowHeight = getRowHeight(stickyRow);
const stickyRowBottom = stickyRowHeight;

const nextStickyRow = stickyRows[stickyRowIndex + 1];
if (nextStickyRow === undefined) {
stickyRegionHeight = stickyRowHeight;
} else {
const startRowHeight = getRowHeight(startRow);
const startRowVisibleHeight = startRowHeight + (drawY - (totalHeaderHeight ?? 0));

let nextStickyRowTop = startRowVisibleHeight;
for (let i = startRow + 1; i < nextStickyRow; i++) {
nextStickyRowTop += getRowHeight(i);
}

stickyRegionHeight = nextStickyRowTop < stickyRowBottom ? nextStickyRowTop : stickyRowHeight;
}
}

let drawRegions: Rectangle[] = [];

const mustDrawFocusOnHeader = drawFocus && selection.current?.cell[1] === cellYOffset && translateY === 0;
Expand Down Expand Up @@ -337,7 +366,8 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) {
freezeTrailingRows,
rows,
highlightRegions,
theme
theme,
stickyRegionHeight
);
}

Expand Down Expand Up @@ -437,7 +467,9 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) {
renderStateProvider,
getCellRenderer,
overrideCursor,
minimumCellWidth
minimumCellWidth,
stickyRows,
stickyRegionHeight
);

const selectionCurrent = selection.current;
Expand Down Expand Up @@ -534,7 +566,8 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) {
mappedColumns,
effectiveCols,
rowHeight,
doubleBuffer
doubleBuffer,
stickyRegionHeight
);
drawRegions = regions;
} else if (canBlit !== false) {
Expand All @@ -550,10 +583,24 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) {
height,
totalHeaderHeight,
effectiveCols,
resizedCol
resizedCol,
stickyRegionHeight
);
}

if (drawRegions.length > 0) {
drawRegions.push({ x: 0, y: totalHeaderHeight * dpr, width: width * dpr, height: 55 * dpr });
}

drawRegions = [
{
x: 0,
y: 0,
width: width,
height: height,
},
];

overdrawStickyBoundaries(
targetCtx,
effectiveCols,
Expand Down Expand Up @@ -582,7 +629,8 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) {
freezeTrailingRows,
rows,
highlightRegions,
theme
theme,
stickyRegionHeight
);

// the overdraw may have nuked out our focus ring right edge.
Expand Down Expand Up @@ -656,7 +704,9 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) {
renderStateProvider,
getCellRenderer,
overrideCursor,
minimumCellWidth
minimumCellWidth,
stickyRows,
stickyRegionHeight
);

drawBlanks(
Expand All @@ -678,7 +728,8 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) {
hasAppendRow,
drawRegions,
damage,
theme
theme,
stickyRows
);

drawExtraRowThemes(
Expand Down Expand Up @@ -716,7 +767,9 @@ export function drawGrid(arg: DrawGridArg, lastArg: DrawGridArg | undefined) {
verticalBorder,
freezeTrailingRows,
rows,
theme
theme,
false,
stickyRegionHeight
);

highlightRedraw?.();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { findLastIndex } from "lodash";
import { type Item, type Rectangle } from "../data-grid-types.js";
import { type MappedGridColumn, isGroupEqual } from "./data-grid-lib.js";

Expand Down Expand Up @@ -26,7 +27,9 @@ export function walkRowsInCol(
freezeTrailingRows: number,
hasAppendRow: boolean,
skipToY: number | undefined,
cb: WalkRowsCallback
cb: WalkRowsCallback,
stickyRows?: number[],
totalHeaderHeight?: number
): void {
skipToY = skipToY ?? drawY;
let y = drawY;
Expand All @@ -52,6 +55,56 @@ export function walkRowsInCol(
y -= rh;
cb(y, row, rh, true, hasAppendRow && row === rows - 1);
}

y = 0; // with start row it is possible to identify the sticky rows
if (stickyRows) {
// find the sticky row that fits
const stickyRowIndex = findLastIndex(stickyRows, r => startRow >= r);
if (stickyRowIndex !== -1) {
const stickyRow = stickyRows[stickyRowIndex];
const stickyRowHeight = getRowHeight(stickyRow);
const stickyRowBottom = stickyRowHeight;

const nextStickyRow = stickyRows[stickyRowIndex + 1];
if (nextStickyRow === undefined) {
cb(totalHeaderHeight ?? drawY, stickyRow, stickyRowHeight, true, false);
} else {
const nextStickyRowHeight = getRowHeight(nextStickyRow);

const startRowHeight = getRowHeight(startRow);
const startRowVisibleHeight = startRowHeight + (drawY - (totalHeaderHeight ?? 0));

let nextStickyRowTop = startRowVisibleHeight;
for (let i = startRow + 1; i < nextStickyRow; i++) {
nextStickyRowTop += getRowHeight(i);
}

if (nextStickyRowTop < stickyRowBottom) {
let offsetY = totalHeaderHeight ?? drawY;
offsetY -= stickyRowBottom - nextStickyRowTop;
cb(offsetY, stickyRow, stickyRowHeight, true, false);
// offsetY += stickyRowHeight;
// cb(offsetY, nextStickyRow, nextStickyRowHeight, true, false);
} else {
cb(totalHeaderHeight ?? drawY, stickyRow, stickyRowHeight, true, false);
}

// console.log("stickyRows", {
// startRowHeight,
// startRowVisibleHeight,
// stickyRowHeight,
// stickyRowBottom,
// nextStickyRow,
// nextStickyRowHeight,
// nextStickyRowTop,
// drawYx: drawY - (totalHeaderHeight ?? 0),
// drawY,
// startRow,
// totalHeaderHeight,
// });
}
}
}
}

export type WalkColsCallback = (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ export function drawHighlightRings(
freezeTrailingRows: number,
rows: number,
allHighlightRegions: readonly Highlight[] | undefined,
theme: FullTheme
theme: FullTheme,
stickyRegionHeight: number
): (() => void) | undefined {
const highlightRegions = allHighlightRegions?.filter(x => x.style !== "no-outline");

Expand All @@ -33,7 +34,7 @@ export function drawHighlightRings(
const freezeLeft = getStickyWidth(mappedColumns);
const freezeBottom = getFreezeTrailingHeight(rows, freezeTrailingRows, rowHeight);
const splitIndicies = [freezeColumns, 0, mappedColumns.length, rows - freezeTrailingRows] as const;
const splitLocations = [freezeLeft, 0, width, height - freezeBottom] as const;
const splitLocations = [freezeLeft, headerHeight + stickyRegionHeight, width, height - freezeBottom] as const;

const drawRects = highlightRegions.map(h => {
const r = h.range;
Expand Down