ag-charts-community
Version:
Advanced Charting / Charts supporting Javascript / Typescript / React / Angular / Vue
164 lines (163 loc) • 8.86 kB
TypeScript
import type { ChartAxis } from '../chartAxis';
import type { DataModel } from '../data/dataModel';
import type { ProcessedData, ScopeProvider } from '../data/dataModelTypes';
import { type AggregationFilterBase, type AggregationManager } from './aggregationManager';
import type { BucketLookupFeature, DatumRangeReader } from './seriesTypes';
export type { BucketLookupFeature } from './seriesTypes';
interface ExtremesFilter extends AggregationFilterBase {
indexData: Uint32Array;
}
interface SplitFilter extends AggregationFilterBase {
positiveIndexData: Uint32Array;
negativeIndexData: Uint32Array;
}
interface BucketLookupManagerOpts<TFilter extends AggregationFilterBase> {
series: ScopeProvider;
/** Resolved at lookup time — accessor pattern lets the series mutate axis/data references freely. */
getXAxis: () => ChartAxis | undefined;
getDataModel: () => DataModel<any, any, any> | undefined;
getProcessedData: () => ProcessedData<any> | undefined;
aggregationManager: AggregationManager<TFilter>;
/** `'value'` for series whose xValue column is the X coordinate (line); `'key'` for keyed series (bar/area/ohlc/range-*). */
domainKey: 'value' | 'key';
getSelection: () => Uint8Array | undefined;
/**
* AGGREGATION_INDEX_* slots whose stored datum index is treated as the
* canonical "selected" representative for its bucket. When set, the
* selection predicate additionally requires `datumIndex` to match one of
* the configured slots — preventing the visual multiplication where a
* single selected datum surfaces as up to four styled extrema markers per
* bucket. When undefined, falls back to any-membership semantics
* (composite-node series like OHLC/range-bar that already render one node
* per bucket).
*/
canonicalExtremaSlots?: readonly number[];
}
interface BucketingInputs {
xValues: any[];
d0: number;
d1: number;
xNeedsValueOf: boolean;
}
/**
* Per-render-frame reader cache shared by both extremes and split managers.
* Holds both the bucket-selected hot-path reader and the range reader keyed
* on (`processedData`, filter) — both are invalidated together because both
* close over the same resolved bucketing context.
*/
declare class LookupCache<TFilter> {
processedData?: ProcessedData<any>;
filter?: TFilter;
selectedReader?: (datumIndex: number) => boolean;
rangeReader?: DatumRangeReader;
has(processedData: ProcessedData<any>, filter: TFilter): boolean;
set(processedData: ProcessedData<any>, filter: TFilter, selectedReader: (datumIndex: number) => boolean, rangeReader: DatumRangeReader): void;
clear(): void;
}
/**
* Per-filter epoch tracking: filters are populated for a given selection
* epoch exactly once, and skipped on subsequent visits unless their epoch
* tag is stale. Combined with WeakMap-keyed identity, this lets us
* distinguish "selection changed, repopulate every level" from "filter set
* grew (zoom demand), only populate the new entries" without an explicit
* callback split on `AggregationManager`.
*
* The sparse selection list is rebuilt once per selection-change and reused
* across every level whose epoch tag is behind. Cost per level is
* `O(|selection|)` — typical user selections have Hamming weight ≪ N, so
* pan/zoom adding a finer aggregation level is cheap regardless of dataset
* size.
*/
declare abstract class AbstractBucketLookupManager<TFilter extends AggregationFilterBase> {
protected readonly opts: BucketLookupManagerOpts<TFilter>;
private selectionEpoch;
private sparseSelection?;
private readonly populatedEpochs;
protected readonly cache: LookupCache<TFilter>;
constructor(opts: BucketLookupManagerOpts<TFilter>);
/** Render-pass entrypoint — series pushes the resolved filter directly, skipping the lazy axis-poll path. */
setActiveFilter(processedData: ProcessedData<any>, filter: AggregationFilterBase | undefined): void;
/**
* Selection-change entrypoint (called from `Series` on
* `data-selection-change`). Bumps the epoch so every existing filter
* becomes stale, rebuilds the sparse selection list from the live
* bitset, then walks the filter list populating any whose epoch tag
* doesn't match the new value.
*/
refresh(): void;
/**
* Filter-set-mutation entrypoint (subscribed to
* `aggregationManager.events.on('filtersChanged', …)`). Doesn't bump
* the epoch — existing filters' SELECTED slots are still valid for the
* current selection — but newly-added filter objects aren't yet in
* `populatedEpochs`, so they get populated on this pass.
*/
private populateStaleFilters;
isBucketSelected(datumIndex: number): boolean | undefined;
getRangeReader(): DatumRangeReader | undefined;
getIndexSet(_datumIndex: number): Iterable<number> | undefined;
/** Lazy fallback for callers that haven't primed the cache via {@link setActiveFilter} (e.g. drag-select). */
protected ensureReaders(): LookupCache<TFilter> | undefined;
protected invalidateCache(): void;
protected abstract populateCache(filter: TFilter, dataModel: DataModel<any, any, any>, processedData: ProcessedData<any>, xAxis: ChartAxis): LookupCache<TFilter> | undefined;
protected abstract clearSelectedSlot(filter: TFilter): void;
protected abstract populateFilter(filter: TFilter, sparse: Uint32Array, inputs: BucketingInputs): void;
}
/**
* Bucket lookup roll-up for series whose aggregation filter exposes a single
* `indexData` array (line, area, OHLC/candlestick, range-bar, range-area).
*/
export declare class BucketLookupManager<TFilter extends ExtremesFilter> extends AbstractBucketLookupManager<TFilter> implements BucketLookupFeature {
protected clearSelectedSlot(filter: TFilter): void;
protected populateFilter(filter: TFilter, sparse: Uint32Array, inputs: BucketingInputs): void;
protected populateCache(filter: TFilter, dataModel: DataModel<any, any, any>, processedData: ProcessedData<any>, xAxis: ChartAxis): LookupCache<TFilter>;
}
interface SplitBucketLookupManagerOpts<TFilter extends SplitFilter> extends BucketLookupManagerOpts<TFilter> {
/**
* Returns the data-model column id resolving to the y-end values used to
* discriminate positive/negative arms. Bar's column varies (`yValue-end`
* vs `yValue-raw` depending on stacking), so it's a getter.
*/
getYColumnId: (dataModel: DataModel<any, any, any>, processedData: ProcessedData<any>) => string;
}
/**
* Bucket lookup roll-up for bar-style series whose aggregation filter splits
* each bucket into positive and negative arms. Selection lookup is sign-aware
* (reads only the matching arm) and the range reader matches the picked
* representative against the extrema indices in either arm to determine which
* bucket bounds to return.
*/
export declare class SplitBucketLookupManager<TFilter extends SplitFilter> extends AbstractBucketLookupManager<TFilter> implements BucketLookupFeature {
private readonly splitOpts;
constructor(splitOpts: SplitBucketLookupManagerOpts<TFilter>);
protected clearSelectedSlot(filter: TFilter): void;
protected populateFilter(filter: TFilter, sparse: Uint32Array, inputs: BucketingInputs): void;
protected populateCache(filter: TFilter, dataModel: DataModel<any, any, any>, processedData: ProcessedData<any>, xAxis: ChartAxis): LookupCache<TFilter>;
}
interface IndexSetBucketLookupManagerOpts {
/** Cluster-keyed map of representative datum index to underlying datum indices. */
getIndexSetMap: () => Map<number, number[]> | undefined;
getSelection: () => Uint8Array | undefined;
}
/**
* Bucket lookup roll-up for cluster-based aggregation (bubble/scatter). Each
* rendered marker stands in for an arbitrary group of datums whose underlying
* indices are non-contiguous, so neither the extremes-based range reader nor
* the per-bucket SELECTED slot model from {@link BucketLookupManager} apply
* here — the selection bit per cluster is read by walking the cluster's
* index list against the per-series selection bitset.
*
* No precomputed roll-up: clusters are typically small (a few datums each)
* and each marker render performs at most one lookup per cluster, so an
* O(cluster-size) scan beats maintaining a parallel cache that has to be
* invalidated on every selection change.
*/
export declare class IndexSetBucketLookupManager implements BucketLookupFeature {
private readonly opts;
constructor(opts: IndexSetBucketLookupManagerOpts);
isBucketSelected(datumIndex: number): boolean | undefined;
getRangeReader(): DatumRangeReader | undefined;
getIndexSet(datumIndex: number): Iterable<number> | undefined;
refresh(): void;
setActiveFilter(): void;
}