Skip to content

Commit

Permalink
Hide excluded rows
Browse files Browse the repository at this point in the history
When rows models are fetched from the data provider, if any requested
row is not returned, consider that this row has been excluded by EXCLUDE
filtering, and remove its row component. Reposition all remaining rows
when rows are excluded.

Keep track of excluded rows and of the world range that was used to
exclude them.

During fullSearch with EXCLUDE filter, add previously excluded rows to
the request to determine if they should reappear.

When complete update is requested, remove row components that are not
visible because they would otherwise not be updated and their states
could linger at the wrong position.

After a fullSearch update is completed, restart another request in case
new rows have become visible due to space left by newly excluded rows.

Skip coarse update when EXCLUDED filter is applied. This helps prevent
matching states that are only found in finer updates from temporarily
disappearing during the coarse update.

Adjust the limits when moving vertically considering excluded rows.

Signed-off-by: Patrick Tasse <patrick.tasse@gmail.com>
  • Loading branch information
PatrickTasse committed Sep 26, 2024
1 parent a3ce4d4 commit 55a4014
Showing 1 changed file with 68 additions and 7 deletions.
75 changes: 68 additions & 7 deletions timeline-chart/src/layer/time-graph-chart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type TimeGraphRowStyleHook = (row: TimelineChart.TimeGraphRowModel) => Ti

const VISIBLE_ROW_BUFFER = 3; // number of buffer rows above and below visible range
const FINE_RESOLUTION_FACTOR = 1; // fine resolution factor or default to disable coarse update
const EXCLUDED = 4; // EXCLUDED filter expression property key

export class TimeGraphChart extends TimeGraphChartLayer {
protected rowIds: number[]; // complete ordered list of rowIds
Expand Down Expand Up @@ -80,6 +81,8 @@ export class TimeGraphChart extends TimeGraphChartLayer {
private _mouseWheelHandler: { (ev: WheelEvent): void; (event: Event): void; (event: Event): void };
private _contextMenuHandler: { (e: MouseEvent): void; (event: Event): void };
private _filterExpressionsMap: {[key: number]: string[]} | undefined = undefined;
private _allRowIds: number[]; // full ordered list of row ids including excluded rows
private _excludedRows: Map<number, TimelineChart.TimeGraphRange> = new Map(); // known excluded rows after filtering

private _debouncedMaybeFetchNewData = debounce(() => this.maybeFetchNewData(false), 400);
private _debouncedMaybeFetchNewDataFine = debounce(() => this.maybeFetchNewData(false, true), 400);
Expand Down Expand Up @@ -162,12 +165,13 @@ export class TimeGraphChart extends TimeGraphChartLayer {
}

const moveVertically = (magnitude: number) => {
if (this.rowController.totalHeight <= this.stateController.canvasDisplayHeight) {
const totalHeight = this.rowController.totalHeight - this.rowController.rowHeight * this._excludedRows.size;
if (totalHeight <= this.stateController.canvasDisplayHeight) {
return;
}
let verticalOffset = Math.max(0, this.rowController.verticalOffset + magnitude);
if (this.rowController.totalHeight - verticalOffset <= this.stateController.canvasDisplayHeight) {
verticalOffset = this.rowController.totalHeight - this.stateController.canvasDisplayHeight;
if (totalHeight - verticalOffset <= this.stateController.canvasDisplayHeight) {
verticalOffset = totalHeight - this.stateController.canvasDisplayHeight;
}
this.rowController.verticalOffset = verticalOffset;
}
Expand Down Expand Up @@ -476,7 +480,18 @@ export class TimeGraphChart extends TimeGraphChartLayer {
}
return;
}
this.rowIds = this.providers.rowProvider().rowIds;
if (!fine && this._filterExpressionsMap?.[EXCLUDED]) {
// skip the coarse update when EXCLUDED filter is applied
fine = true;
}
this._allRowIds = this.providers.rowProvider().rowIds;
if (update) {
this._excludedRows.clear();
this.rowIds = this._allRowIds.slice();
} else {
this.rowIds = this._allRowIds.filter(rowId => !this._excludedRows.has(rowId));
}
const visibleRowIds = this.getVisibleRowIds(VISIBLE_ROW_BUFFER);
if (update) {
// update position of existing rows and remove deleted rows
this.rowComponents.forEach((rowComponent, rowId) => {
Expand All @@ -486,7 +501,10 @@ export class TimeGraphChart extends TimeGraphChartLayer {
this.removeChild(rowComponent);
} else {
rowComponent.position.y = this.rowController.rowHeight * index;
rowComponent.providedModel = undefined;
if (!visibleRowIds.includes(rowId)) {
this.rowComponents.delete(rowId);
this.removeChild(rowComponent);
}
}
});
// update selected row
Expand All @@ -503,7 +521,6 @@ export class TimeGraphChart extends TimeGraphChartLayer {
}
});
}
const visibleRowIds = this.getVisibleRowIds(VISIBLE_ROW_BUFFER);
const { viewRange } = this.unitController;
const worldRange = this.stateController.computeWorldRangeFromViewRange();
const resolutionFactor = fine ? FINE_RESOLUTION_FACTOR : this._coarseResolutionFactor;
Expand All @@ -522,6 +539,16 @@ export class TimeGraphChart extends TimeGraphChartLayer {
resolution < rowComponent.providedModel.resolution || !isEqual(rowComponent.providedModel.filterExpressionsMap, this._filterExpressionsMap)
);
});
if (fullSearch && this._filterExpressionsMap?.[EXCLUDED]) {
this._excludedRows.forEach((range, rowId) => {
// add excluded rows to request to determine if they should reappear
if (!isEqual(worldRange, range)) {
rowIds.push(rowId);
}
});
// reorder rows
rowIds.sort((a, b) => this._allRowIds.indexOf(a) - this._allRowIds.indexOf(b));
}
if (rowIds.length > 0) {
const additionalParams: { [key: string]: any } = {};
if (this._filterExpressionsMap) {
Expand Down Expand Up @@ -553,6 +580,10 @@ export class TimeGraphChart extends TimeGraphChartLayer {
rowComponent.providedModel.filterExpressionsMap = this._filterExpressionsMap;
}
}
if (this._filterExpressionsMap?.[EXCLUDED]) {
// restart full search to check if newly visible rows need to be filled after filtering
this._debouncedMaybeFetchNewDataFineFullSearch();
}
} else {
try {
const request = { worldRange, resolution, rowIds, additionalParams, fullSearch };
Expand Down Expand Up @@ -606,6 +637,7 @@ export class TimeGraphChart extends TimeGraphChartLayer {
// response discarded because world range updated
return Promise.reject(new Error("Request for outdated world range"));
}
this.updateExcludedRows(request.rowIds, request.worldRange, rowData.rows);
this.addOrUpdateRows(rowData);
if (this.isNavigating) {
this.selectStateInNavigation();
Expand Down Expand Up @@ -641,6 +673,35 @@ export class TimeGraphChart extends TimeGraphChartLayer {
this.selectedStateChangedHandler.forEach((handler) => handler(this.selectedStateModel));
}

protected updateExcludedRows(requestedRowIds: number[], worldRange: TimelineChart.TimeGraphRange, rows: TimelineChart.TimeGraphRowModel[]) {
let update = false;
requestedRowIds.forEach(rowId => {
if (!rows.find(row => row.id === rowId)) {
// an excluded row is removed
update = true;
this._excludedRows.set(rowId, worldRange);
const rowComponent = this.rowComponents.get(rowId);
if (rowComponent) {
this.rowComponents.delete(rowId);
this.removeChild(rowComponent);
}
} else if (this._excludedRows.delete(rowId)) {
// a previously excluded row reappears
update = true;
}
});
if (update) {
// reposition all rows
this.rowIds = this._allRowIds.filter(rowId => !this._excludedRows.has(rowId));
this.rowComponents.forEach((rowComponent, rowId) => {
const index = this.rowIds.indexOf(rowId);
if (index != -1) {
rowComponent.position.y = this.rowController.rowHeight * index;
}
});
}
}

protected addOrUpdateRows(rowData: { rows: TimelineChart.TimeGraphRowModel[], range: TimelineChart.TimeGraphRange, resolution: number }) {
if (!this.stateController) {
throw 'Add this TimeGraphChart to a container before adding rows.';
Expand Down Expand Up @@ -719,7 +780,7 @@ export class TimeGraphChart extends TimeGraphChartLayer {

protected updateGap(state: TimelineChart.TimeGraphState, rowComponent: TimeGraphRow, gapStyle: any, x: number, lastX?: number, lastTime?: bigint, lastBlank?: boolean) {
/* add gap if there is visible space between states or if there is a time gap between two blank states */
const isFilteredOut = state.data?.tags >= 4;
const isFilteredOut = state.data?.tags >= EXCLUDED;
if (
lastX &&
lastTime &&
Expand Down

0 comments on commit 55a4014

Please sign in to comment.