UNPKG

ag-charts-community

Version:

Advanced Charting / Charts supporting Javascript / Typescript / React / Angular / Vue

164 lines (163 loc) 8.86 kB
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; }