ag-charts-community
Version:
Advanced Charting / Charts supporting Javascript / Typescript / React / Angular / Vue
306 lines (305 loc) • 12.1 kB
TypeScript
/**
* Represents a splice operation to be applied to an array.
* Designed for direct use with Array.splice() for efficient mutations.
*/
export interface SpliceOperation {
/** Index at which to start the splice (in current array state) */
index: number;
/** Number of elements to delete from this position */
deleteCount: number;
/** Number of new elements to insert at this position */
insertCount: number;
/** Optional source indices for preserved elements (for tracking) */
sourceIndices?: number[];
}
/**
* Tracks transformations from original array indices to final array indices.
* Optimized for efficient array mutations using splice operations.
*/
export interface IndexTransformationMap {
/** Original array length before any transactions */
originalLength: number;
/** Final array length after all transactions */
finalLength: number;
/**
* Splice operations to transform the array.
* Applied from back to front to avoid index shifting issues.
* Each operation represents a single splice call.
*/
spliceOps: SpliceOperation[];
/** Set of removed original indices */
removedIndices: Set<number>;
/** Set of updated final indices (indices in the final array that have updated items) */
updatedIndices: Set<number>;
/** Total number of prepended items */
totalPrependCount: number;
/** Total number of appended items */
totalAppendCount: number;
}
/**
* Helper functions to compute derived properties from IndexTransformationMap.
* These avoid storing redundant computed values and enable optimization checks.
*/
/**
* Check if the change is append-only (no prepends, no removals).
*
* **Optimization:** Append-only operations don't shift existing indices,
* so preserved items don't need to be marked as "moved".
*
* @param indexMap - The index transformation map to check
* @returns True if only appends occurred
*
* @example
* ```typescript
* if (isAppendOnly(changeDesc.indexMap)) {
* // Skip tracking moved items - nothing moved
* }
* ```
*/
export declare function isAppendOnly(indexMap: IndexTransformationMap): boolean;
/**
* Check if the change is prepend-only (no appends, no removals).
*
* **Optimization:** Prepend-only operations shift ALL existing items by a fixed offset,
* so we can bulk-mark them as moved without iteration.
*
* @param indexMap - The index transformation map to check
* @returns True if only prepends occurred
*
* @example
* ```typescript
* if (isPrependOnly(changeDesc.indexMap)) {
* // All items shifted by totalPrependCount
* markAllAsMoved(changeDesc.indexMap.totalPrependCount);
* }
* ```
*/
export declare function isPrependOnly(indexMap: IndexTransformationMap): boolean;
/**
* Check if no items were removed (may have prepends/appends).
*
* **Optimization:** When no removals occurred, items are either:
* - In their original positions (if no prepends)
* - Shifted by a fixed offset (if prepends occurred)
*
* @param indexMap - The index transformation map to check
* @returns True if no removals occurred
*
* @example
* ```typescript
* if (hasNoRemovals(changeDesc.indexMap) && indexMap.totalPrependCount > 0) {
* // All original items shifted by prepend count
* }
* ```
*/
export declare function hasNoRemovals(indexMap: IndexTransformationMap): boolean;
/** Check if only in-place updates occurred (no adds/removes). */
export declare function isUpdateOnly(indexMap: IndexTransformationMap): boolean;
/**
* Check if only removals occurred (no prepends, appends, or other insertions).
*
* **Optimization:** Removal-only operations preserve sort order and uniqueness:
* - Removing values can't create duplicates (only reduce them)
* - Removing values can't change ascending to descending order
* - KEY_SORT_ORDERS metadata can be preserved
*/
export declare function hasOnlyRemovals(indexMap: IndexTransformationMap): boolean;
/** Check if all removals are contiguous starting at index 0. */
export declare function hasContiguousRemovalsAtStart(indexMap: IndexTransformationMap): boolean;
/** Returns the count of contiguous removals from index 0, or 0 if removals are absent/non-contiguous. */
export declare function contiguousRemovalCountAtStart(removedIndices: Set<number>): number;
/** Check for rolling window pattern: contiguous removals at start + appends at end. */
export declare function isRollingWindow(indexMap: IndexTransformationMap): boolean;
/**
* Abstract description of changes to be applied to source data.
* Provides precise index mapping for optimized incremental updates.
*
* **Responsibilities:**
* - Describes what changed (splice operations, index mappings, prepend/append counts)
* - Provides transformation methods for applying changes to arrays
* - Enables iteration over preserved/moved elements
*
* **Usage Pattern:**
* 1. DataSet builds DataChangeDescription from pending transactions
* 2. DataController passes it to DataModel for incremental updates
* 3. DataModel uses methods below to transform keys, columns, and invalidity arrays
*
* **Design Note:**
* This class intentionally separates "change description" from "transaction management" (DataSet)
* and "data processing" (DataModel). This enables:
* - Multiple consumers (DataController, DataModel)
* - Independent testing
* - Clear separation of concerns
*/
export declare class DataChangeDescription {
/**
* Map from original to final indices.
*
* Contains splice operations, removed indices, and counts needed for transformations.
* Access directly for low-level operations (e.g., updating banded domains).
*/
readonly indexMap: IndexTransformationMap;
private readonly prependValues;
private readonly appendValues;
private readonly insertionValues;
constructor(indexMap: IndexTransformationMap, insertions: {
prependValues: unknown[];
appendValues: unknown[];
insertionValues: unknown[];
});
/**
* Get all indices that were removed from the original array, sorted ascending.
*
* @returns Array of removed indices (e.g., [2, 5, 8])
*
* @example
* ```typescript
* const removed = changeDesc.getRemovedIndices();
* console.log(`Removed ${removed.length} items at indices: ${removed}`);
* ```
*/
getRemovedIndices(): number[];
/**
* Get all indices that were updated in the final array, sorted ascending.
*
* @returns Array of updated indices (e.g., [1, 3, 7])
*
* @example
* ```typescript
* const updated = changeDesc.getUpdatedIndices();
* console.log(`Updated ${updated.length} items at indices: ${updated}`);
* ```
*/
getUpdatedIndices(): number[];
/**
* Iterate over preserved elements, mapping source index to destination index.
* Only calls callback for elements that were NOT removed.
*
* **Use this for:**
* - Tracking which elements moved (when sourceIndex !== destIndex)
* - Generating diff metadata (added/removed/moved items)
* - Understanding index shifts caused by prepends/removes
*
* @param callback - Called for each preserved element with (sourceIndex, destIndex)
*
* @example Detecting moved items
* ```typescript
* const movedItems = new Set<number>();
* changeDesc.forEachPreservedIndex((srcIdx, destIdx) => {
* if (srcIdx !== destIdx) {
* movedItems.add(destIdx);
* }
* });
* ```
*/
forEachPreservedIndex(callback: (sourceIndex: number, destIndex: number) => void): void;
/**
* Get the values that were prepended to the beginning of the array.
*
* These values are stored during change description construction and can be used
* to avoid reprocessing prepended data.
*
* @returns Array of prepended values in order
*
* @example Processing prepended data
* ```typescript
* const prependedData = changeDesc.getPrependedValues<DataRow>();
* for (const row of prependedData) {
* processRow(row);
* }
* ```
*/
getPrependedValues<T = unknown>(): T[];
/**
* Get the values that were appended to the end of the array.
*
* These values are stored during change description construction and can be used
* to avoid reprocessing appended data.
*
* @returns Array of appended values in order
*
* @example Processing appended data
* ```typescript
* const appendedData = changeDesc.getAppendedValues<DataRow>();
* for (const row of appendedData) {
* processRow(row);
* }
* ```
*/
getAppendedValues<T = unknown>(): T[];
/**
* Get the values that were inserted at arbitrary indices.
*
* These values are stored during change description construction and can be used
* to avoid reprocessing inserted data.
*
* @returns Array of insertion values in the order they appear in splice operations
*
* @example Processing inserted data
* ```typescript
* const insertedData = changeDesc.getInsertionValues<DataRow>();
* for (const row of insertedData) {
* processRow(row);
* }
* ```
*/
getInsertionValues<T = unknown>(): T[];
/**
* Applies the transformation to an array in-place using native Array operations.
* This is a zero-copy operation that mutates the array directly.
*
* **Use this for:**
* - Transforming processed data arrays (keys, columns, invalidity)
* - Applying prepends, removals, and appends in a single pass
* - Maintaining synchronization between data and processed arrays
*
* **How it works:**
* 1. Applies splice operations in order (prepends, removals, appends)
* 2. Calls processInsertion callback for each inserted element
* 3. Mutates the array in-place for zero-copy efficiency
*
* @param array - The array to transform in-place (will be mutated)
* @param processInsertion - Callback to generate values for inserted indices
*
* @example Transforming a column array
* ```typescript
* // Transform processed column to match new data layout
* const insertionCache = new Map(); // Pre-computed processed values
* changeDesc.applyToArray(columnArray, (destIndex) => {
* return insertionCache.get(destIndex) ?? defaultValue;
* });
* ```
*
* @example Transforming an invalidity array
* ```typescript
* // Transform invalidity flags to match new data
* changeDesc.applyToArray(invalidityArray, (destIndex) => {
* const cached = insertionCache.get(destIndex);
* return cached?.hasInvalidKey ?? false;
* });
* ```
*/
applyToArray<T>(array: T[], processInsertion: (destIndex: number) => T, onRemove?: (removed: T[], op: SpliceOperation) => void): void;
/**
* Applies the transformation to a `Uint8Array`, returning a **new** typed array.
* TypedArrays are fixed-length so in-place splice is not possible.
*
* **Fast path** (rolling window, prepend+append, removals-only): copies preserved
* blocks via `TypedArray.set()` + `subarray()` — maps to C-level memcpy in V8.
*
* **Slow path** (mid-array insertions): element-by-element copy via
* `forEachPreservedIndex()` with insertion-aware destination adjustment.
*
* @param arr - Source typed array to transform
* @param defaultValue - Value for newly inserted positions (default 0)
* @returns A new Uint8Array with transformations applied, or the original
* `arr` reference when no structural changes occurred (same length,
* no removals, no splice ops)
*/
applyToTypedArray(arr: Uint8Array, defaultValue?: number): Uint8Array;
/**
* Collect mid-array insertion positions from splice ops (excluding prepend and append).
* Returns sorted ascending by destination index.
*/
private collectMidArrayInsertions;
}