Skip to content
Merged
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
8 changes: 8 additions & 0 deletions src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ export class ArrowModel extends DataModel {
return this._schema;
}

get numRows(): number {
return this._numRows;
}

get numCols(): number {
return this._numCols;
}

columnCount(region: DataModel.ColumnRegion): number {
if (region === "body") {
return this._numCols;
Expand Down
91 changes: 87 additions & 4 deletions src/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,84 @@ import {
DataGrid,
TextRenderer,
} from "@lumino/datagrid";
import { Debouncer } from "@lumino/polling";
import { Panel } from "@lumino/widgets";
import type { DocumentRegistry, IDocumentWidget } from "@jupyterlab/docregistry";
import type * as DataGridModule from "@lumino/datagrid";
import type { IMessageHandler, Message } from "@lumino/messaging";

import { FileType } from "./file-types";
import { ArrowModel } from "./model";
import { createToolbar } from "./toolbar";
import type { FileInfo, FileReadOptions } from "./file-options";

/**
* DataGrid that intercepts scroll-request messages and debounce them on large changes.
*/
class DebouncedDataGrid extends DataGrid {
constructor(options: DataGrid.IOptions & { scrollThreshold: number; debounceDelay: number }) {
super(options);

this._scrollThresholdY = options.scrollThreshold;
this._scrollThresholdX = options.scrollThreshold;

this._scrollDebouncer = new Debouncer((handler: IMessageHandler, msg: Message) => {
super.messageHook(handler, msg);
}, options.debounceDelay);
}

setScrollThresholds(thresholdY: number, thresholdX: number): void {
this._scrollThresholdY = thresholdY;
this._scrollThresholdX = thresholdX;
}

messageHook(handler: IMessageHandler, msg: Message): boolean {
if (handler === this.viewport && msg.type === "scroll-request") {
// Calculate the percentage change in vertical scroll position
const scrollChangeY = Math.abs(this.scrollY - this._previousScrollY);
const scrollChangePercentY = this.maxScrollY > 0 ? scrollChangeY / this.maxScrollY : 0;

// Calculate the percentage change in horizontal scroll position
const scrollChangeX = Math.abs(this.scrollX - this._previousScrollX);
const scrollChangePercentX = this.maxScrollX > 0 ? scrollChangeX / this.maxScrollX : 0;

// Check if either direction exceeds its threshold
const shouldDebounceY = scrollChangePercentY > this._scrollThresholdY;
const shouldDebounceX = scrollChangePercentX > this._scrollThresholdX;

if (shouldDebounceY || shouldDebounceX) {
// Large scroll change - debounce it
void this._scrollDebouncer.invoke(handler, msg);
this._previousScrollY = this.scrollY;
this._previousScrollX = this.scrollX;
// Don't process immediately, return false to stop propagation
return false;
} else {
// Small scroll change - process immediately
this._previousScrollY = this.scrollY;
this._previousScrollX = this.scrollX;
return super.messageHook(handler, msg);
}
}

return super.messageHook(handler, msg);
}

dispose(): void {
this._scrollDebouncer.dispose();
super.dispose();
}

private _scrollDebouncer: Debouncer<void>;
private _previousScrollY: number = 0;
private _previousScrollX: number = 0;
private _scrollThresholdY: number;
private _scrollThresholdX: number;
}

export namespace ArrowGridViewer {
export interface Options {
path: string;
export interface Options extends ArrowModel.LoadingOptions {
debounceDelay?: number;
}
}

Expand All @@ -31,13 +97,20 @@ export class ArrowGridViewer extends Panel {
this.addClass("arrow-viewer");

this._defaultStyle = DataGrid.defaultStyle;
this._grid = new DataGrid({

// Start with a conservative default threshold that will be updated when model is loaded
const defaultScrollThreshold = 0.01;
const debounceDelay = options.debounceDelay ?? 300;

this._grid = new DebouncedDataGrid({
defaultSizes: {
rowHeight: 24,
columnWidth: 144,
rowHeaderWidth: 64,
columnHeaderHeight: 36,
},
scrollThreshold: defaultScrollThreshold,
debounceDelay,
});
this._grid.addClass("arrow-grid-viewer");
this._grid.headerVisibility = "all";
Expand All @@ -51,6 +124,7 @@ export class ArrowGridViewer extends Panel {
};

this.addWidget(this._grid);

this._ready = this.initialize();
}

Expand Down Expand Up @@ -116,10 +190,19 @@ export class ArrowGridViewer extends Panel {

private async _updateGrid() {
try {
const dataModel = await ArrowModel.fromRemoteFileInfo({ path: this.path });
const dataModel = await ArrowModel.fromRemoteFileInfo(this._options);
await dataModel.ready;
this._grid.dataModel = dataModel;
this._grid.selectionModel = new BasicSelectionModel({ dataModel });

// Calculate scroll debounce thresholds based on chunk sizes
const rowChunkSize = this._options.rowChunkSize ?? 256;
const colChunkSize = this._options.colChunkSize ?? 24;
const numRows = dataModel.numRows;
const numCols = dataModel.numCols;
const scrollThresholdY = numRows > 0 ? rowChunkSize / numRows : 0.01;
const scrollThresholdX = numCols > 0 ? colChunkSize / numCols : 0.01;
(this._grid as DebouncedDataGrid).setScrollThresholds(scrollThresholdY, scrollThresholdX);
} catch (error) {
const trans = Dialog.translator.load("jupyterlab");
const buttons = [
Expand Down
Loading