@1771technologies/lytenyte-pro
Version:
Blazingly fast headless React data grid with 100s of features.
103 lines (102 loc) • 3.45 kB
JavaScript
/**
* A tree structure for efficient nested range operations.
*
* Maintains a hierarchical representation of ranges where parent ranges contain their
* child ranges. Provides methods for querying ranges that contain specific row indices.
*
* @remarks
* The tree is constructed by:
* 1. Sorting ranges by start index
* 2. Using the first range as root
* 3. Inserting subsequent ranges into their appropriate parent nodes
*
* @example
* ```typescript
* const ranges = [
* { rowStart: 0, rowEnd: 100, path: 'root' },
* { rowStart: 20, rowEnd: 50, path: 'child1' },
* { rowStart: 30, rowEnd: 40, path: 'grandchild' }
* ];
*
* const tree = new RangeTree(ranges);
* const rangesAtRow25 = tree.findRangesForRowIndex(25);
* // Returns [root, child1]
* ```
*/
export class RangeTree {
/** Root node of the range tree */
root;
/**
* Creates a new range tree from an array of flattened ranges.
*
* @param ranges - Array of ranges to organize into a tree structure
*/
constructor(ranges) {
const sortedRanges = ranges.toSorted((left, right) => left.rowStart - right.rowStart);
const root = { range: sortedRanges[0], children: [] };
for (let i = 1; i < sortedRanges.length; i++) {
insert(root, sortedRanges[i]);
}
this.root = root;
}
/**
* Finds all ranges that contain the specified row index.
*
* Traverses the tree from root to leaves, collecting all ranges that contain
* the target index. Returns ranges in order from outermost to innermost.
*
* @param index - Row index to search for
* @returns Array of ranges containing the index, ordered from outer to inner
*/
findRangesForRowIndex(index) {
const ranges = [];
let currentNode = this.root;
while (currentNode) {
if (index >= currentNode.range.rowStart && index < currentNode.range.rowEnd) {
ranges.push(currentNode.range);
currentNode = currentNode.children.find((child) => index >= child.range.rowStart && index < child.range.rowEnd);
}
else {
break;
}
}
return ranges;
}
}
/**
* Inserts a range into the appropriate position in the range tree.
*
* Recursively finds the most deeply nested existing range that contains the new range,
* then adds the new range as a child of that node.
*
* @param node - Current node to check for insertion
* @param range - Range to insert into the tree
*/
function insert(node, range) {
// Check each child to see if it contains this range
for (const child of node.children) {
if (containsRange(child.range, range)) {
insert(child, range);
return;
}
}
// If we get here, this node is the parent
node.children.push({
range,
children: [],
});
}
/**
* Determines if one range fully contains another range.
*
* A range contains another range if its start index is less than or equal to
* the inner range's start AND its end index is greater than or equal to
* the inner range's end.
*
* @param outer - The potentially containing range
* @param inner - The potentially contained range
* @returns true if outer fully contains inner, false otherwise
*/
function containsRange(outer, inner) {
return outer.rowStart <= inner.rowStart && outer.rowEnd >= inner.rowEnd;
}