@jbrowse/core 5.0.0-beta.10 → 5.0.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +113 -5
  2. package/esm/BaseFeatureWidget/BaseFeatureDetail/ArrayValue.js +0 -2
  3. package/esm/BaseFeatureWidget/BaseFeatureDetail/BasicValue.js +22 -6
  4. package/esm/BaseFeatureWidget/BaseFeatureDetail/jexlFilterActions.js +3 -4
  5. package/esm/BaseFeatureWidget/SequenceFeatureDetails/useSequenceData.js +8 -5
  6. package/esm/CorePlugin.js +32 -1
  7. package/esm/ReExports/coreModules.generated.js +248 -246
  8. package/esm/ReExports/coreWorkerModules.generated.js +246 -244
  9. package/esm/ReExports/list.js +3 -2
  10. package/esm/configuration/index.d.ts +2 -0
  11. package/esm/configuration/index.js +1 -0
  12. package/esm/configuration/plot.d.ts +61 -0
  13. package/esm/configuration/plot.js +142 -0
  14. package/esm/pluggableElementTypes/DisplayType.d.ts +7 -0
  15. package/esm/pluggableElementTypes/DisplayType.js +6 -0
  16. package/esm/pluggableElementTypes/WidgetType.d.ts +6 -0
  17. package/esm/pluggableElementTypes/WidgetType.js +6 -0
  18. package/esm/pluggableElementTypes/index.js +1 -8
  19. package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +57 -1
  20. package/esm/pluggableElementTypes/models/BaseDisplayModel.js +84 -2
  21. package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +2 -2
  22. package/esm/pluggableElementTypes/models/BaseViewModel.js +1 -1
  23. package/esm/ui/JexlFilterDialog.d.ts +1 -1
  24. package/esm/ui/JexlFilterDialog.js +2 -3
  25. package/esm/util/TimeTraveller.d.ts +3 -0
  26. package/esm/util/TimeTraveller.js +34 -2
  27. package/esm/util/baseDisplayConfig.d.ts +8 -0
  28. package/esm/util/baseDisplayConfig.js +27 -0
  29. package/esm/util/cachedSetup.d.ts +16 -6
  30. package/esm/util/cachedSetup.js +108 -38
  31. package/esm/util/cellMatches.d.ts +23 -0
  32. package/esm/util/cellMatches.js +264 -0
  33. package/esm/util/cellsStep.d.ts +29 -0
  34. package/esm/util/cellsStep.js +437 -0
  35. package/esm/util/copyToClipboard.js +8 -1
  36. package/esm/util/createAbortRotation.js +3 -3
  37. package/esm/util/featureTransforms.d.ts +7 -6
  38. package/esm/util/featureTransforms.js +131 -479
  39. package/esm/util/fetchPhases.d.ts +1 -1
  40. package/esm/util/formatBytes.js +1 -1
  41. package/esm/util/formatGuessers.d.ts +25 -14
  42. package/esm/util/formatGuessers.js +42 -27
  43. package/esm/util/formatRelativeTime.js +1 -1
  44. package/esm/util/getLocationUri.d.ts +2 -1
  45. package/esm/util/getLocationUri.js +8 -2
  46. package/esm/util/groupKeys.d.ts +4 -5
  47. package/esm/util/groupKeys.js +7 -6
  48. package/esm/util/intervals.js +9 -4
  49. package/esm/util/io/index.js +3 -1
  50. package/esm/util/jexlFilters.d.ts +31 -49
  51. package/esm/util/jexlFilters.js +29 -39
  52. package/esm/util/locString.js +5 -2
  53. package/esm/util/markEncoding.js +48 -8
  54. package/esm/util/markEncodingTypes.d.ts +8 -1
  55. package/esm/util/springAnimate.js +1 -1
  56. package/esm/util/stepTables.d.ts +14 -0
  57. package/esm/util/stepTables.js +38 -4
  58. package/esm/util/tabix.d.ts +6 -2
  59. package/esm/util/tabix.js +2 -2
  60. package/esm/util/tracks.d.ts +3 -30
  61. package/esm/util/tracks.js +4 -39
  62. package/esm/util/unknownSnapshotKeys.d.ts +3 -1
  63. package/esm/util/unknownSnapshotKeys.js +5 -11
  64. package/esm/util/usePanZoom.js +10 -3
  65. package/esm/util/useRafCommit.js +1 -0
  66. package/esm/util/withFeatureDetails.js +4 -0
  67. package/package.json +9 -8
@@ -1,6 +1,12 @@
1
1
  import type { BaseOptions } from '../data_adapters/BaseAdapter/types.ts';
2
2
  /** byte-granularity progress reporter to hand an index reader's `onProgress` */
3
3
  type OnProgress = (current: number, total?: number) => void;
4
+ /**
5
+ * How long a setup with no live waiter keeps running before its signal aborts:
6
+ * `createAbortRotation.begin()` aborts the superseded call before it posts the
7
+ * replacement, which joins within this window instead of restarting from byte 0.
8
+ */
9
+ export declare const ABANDONED_SETUP_GRACE_MS = 100;
4
10
  /**
5
11
  * Memoize an adapter's one-time setup (open the file, read the header, build a
6
12
  * parser, download and parse a whole file) into a loader every method can
@@ -17,11 +23,13 @@ type OnProgress = (current: number, total?: number) => void;
17
23
  * moment that fetch was superseded — the display's latest-wins guard gates the
18
24
  * old callback off — so the fetch replacing it awaited a multi-GB parse behind
19
25
  * a blank loading overlay.
20
- * - **`signal` is withheld from the shared work.** Honoring one caller's
21
- * cancel would abort a parse the caller replacing it is already waiting on,
22
- * and reject them both. A superseded fetch just stops listening. Cancellation
23
- * stays with the per-call work (indexed range queries), which no one else is
24
- * waiting on.
26
+ * - **Cancellation is shared, not per caller.** The setup gets one signal that
27
+ * aborts once its last live waiter has aborted and nobody rejoins within
28
+ * {@link ABANDONED_SETUP_GRACE_MS}, so a superseded fetch cannot cancel a
29
+ * parse its replacement is waiting on, yet a download nobody awaits stops.
30
+ * Each caller's own await rejects the moment its own signal aborts. An
31
+ * abandoned setup leaves the memo first, so the next call starts afresh and
32
+ * an abandoned run's late statuses reach no one.
25
33
  * - **`label` is shown only while the *first* attempt is in flight.** Re-entry
26
34
  * on pan/zoom (every getFeatures and byte estimate awaits the loader) would
27
35
  * otherwise re-flash "Downloading index" over an index that is already
@@ -32,7 +40,9 @@ type OnProgress = (current: number, total?: number) => void;
32
40
  * a whole-file fetch driving its own download bar — since two labels for one
33
41
  * download is worse than one, and phases nest (see `openPhase` in progress.ts).
34
42
  * `onProgress` is for handing to an index reader that can upgrade the label to a
35
- * determinate bar.
43
+ * determinate bar; like the status, it fans out to every live waiter. It is
44
+ * undefined without a `label`, so a setup that narrates itself never opts its
45
+ * reads into streamed progress.
36
46
  */
37
47
  export declare function cachedSetup<T>({ setup, label, }: {
38
48
  setup: (opts: BaseOptions, onProgress?: OnProgress) => Promise<T>;
@@ -1,4 +1,11 @@
1
+ import { checkAbortSignal, makeAbortError } from './aborting.js';
1
2
  import { downloadStatus } from './progress.js';
3
+ /**
4
+ * How long a setup with no live waiter keeps running before its signal aborts:
5
+ * `createAbortRotation.begin()` aborts the superseded call before it posts the
6
+ * replacement, which joins within this window instead of restarting from byte 0.
7
+ */
8
+ export const ABANDONED_SETUP_GRACE_MS = 100;
2
9
  /**
3
10
  * Memoize an adapter's one-time setup (open the file, read the header, build a
4
11
  * parser, download and parse a whole file) into a loader every method can
@@ -15,11 +22,13 @@ import { downloadStatus } from './progress.js';
15
22
  * moment that fetch was superseded — the display's latest-wins guard gates the
16
23
  * old callback off — so the fetch replacing it awaited a multi-GB parse behind
17
24
  * a blank loading overlay.
18
- * - **`signal` is withheld from the shared work.** Honoring one caller's
19
- * cancel would abort a parse the caller replacing it is already waiting on,
20
- * and reject them both. A superseded fetch just stops listening. Cancellation
21
- * stays with the per-call work (indexed range queries), which no one else is
22
- * waiting on.
25
+ * - **Cancellation is shared, not per caller.** The setup gets one signal that
26
+ * aborts once its last live waiter has aborted and nobody rejoins within
27
+ * {@link ABANDONED_SETUP_GRACE_MS}, so a superseded fetch cannot cancel a
28
+ * parse its replacement is waiting on, yet a download nobody awaits stops.
29
+ * Each caller's own await rejects the moment its own signal aborts. An
30
+ * abandoned setup leaves the memo first, so the next call starts afresh and
31
+ * an abandoned run's late statuses reach no one.
23
32
  * - **`label` is shown only while the *first* attempt is in flight.** Re-entry
24
33
  * on pan/zoom (every getFeatures and byte estimate awaits the loader) would
25
34
  * otherwise re-flash "Downloading index" over an index that is already
@@ -30,48 +39,109 @@ import { downloadStatus } from './progress.js';
30
39
  * a whole-file fetch driving its own download bar — since two labels for one
31
40
  * download is worse than one, and phases nest (see `openPhase` in progress.ts).
32
41
  * `onProgress` is for handing to an index reader that can upgrade the label to a
33
- * determinate bar.
42
+ * determinate bar; like the status, it fans out to every live waiter. It is
43
+ * undefined without a `label`, so a setup that narrates itself never opts its
44
+ * reads into streamed progress.
34
45
  */
35
46
  export function cachedSetup({ setup, label, }) {
36
- const waiting = new Set();
37
- let cached;
38
- let ready = false;
39
- const run = (opts, onProgress) => {
40
- cached ??= setup({
41
- ...opts,
42
- signal: undefined,
43
- statusCallback: status => {
44
- for (const cb of waiting) {
45
- cb(status);
47
+ let flight;
48
+ let resident;
49
+ const launch = (started, opts) => setup({
50
+ ...opts,
51
+ signal: started.controller.signal,
52
+ statusCallback: status => {
53
+ for (const cb of started.waiting.keys()) {
54
+ cb(status);
55
+ }
56
+ },
57
+ }, label === undefined
58
+ ? undefined
59
+ : (current, total) => {
60
+ for (const report of started.reporters.keys()) {
61
+ report(current, total);
62
+ }
63
+ }).then(value => {
64
+ clearTimeout(started.grace);
65
+ if (flight === started) {
66
+ resident = { value };
67
+ }
68
+ return value;
69
+ }, (e) => {
70
+ clearTimeout(started.grace);
71
+ if (flight === started) {
72
+ flight = undefined;
73
+ }
74
+ throw e;
75
+ });
76
+ const abandon = (started) => {
77
+ if (flight === started) {
78
+ flight = undefined;
79
+ started.controller.abort();
80
+ }
81
+ };
82
+ const join = (opts, onProgress) => {
83
+ const { signal, statusCallback } = opts;
84
+ const started = (flight ??= {
85
+ controller: new AbortController(),
86
+ waiting: new Map(),
87
+ reporters: new Map(),
88
+ live: 0,
89
+ });
90
+ clearTimeout(started.grace);
91
+ started.live++;
92
+ count(started.waiting, statusCallback);
93
+ count(started.reporters, onProgress);
94
+ const promise = (started.promise ??= launch(started, opts));
95
+ return new Promise((resolve, reject) => {
96
+ let joined = true;
97
+ const leave = () => {
98
+ if (joined) {
99
+ joined = false;
100
+ signal?.removeEventListener('abort', onAbort);
101
+ started.live--;
102
+ uncount(started.waiting, statusCallback);
103
+ uncount(started.reporters, onProgress);
104
+ }
105
+ };
106
+ const onAbort = () => {
107
+ leave();
108
+ reject(makeAbortError());
109
+ if (started.live === 0) {
110
+ started.grace = setTimeout(() => {
111
+ abandon(started);
112
+ }, ABANDONED_SETUP_GRACE_MS);
46
113
  }
47
- },
48
- }, onProgress)
49
- .then(result => {
50
- ready = true;
51
- return result;
52
- })
53
- .catch((e) => {
54
- cached = undefined;
55
- throw e;
114
+ };
115
+ signal?.addEventListener('abort', onAbort, { once: true });
116
+ promise.finally(leave).then(resolve, reject);
56
117
  });
57
- return cached;
58
118
  };
59
119
  return async (opts = {}) => {
60
- const { statusCallback } = opts;
61
- if (statusCallback) {
62
- waiting.add(statusCallback);
120
+ if (resident) {
121
+ return resident.value;
63
122
  }
64
- try {
65
- return await (label === undefined || ready
66
- ? run(opts)
67
- : downloadStatus(label, statusCallback, onProgress => run(opts, onProgress)));
123
+ checkAbortSignal(opts.signal);
124
+ const wait = (onProgress) => join(opts, onProgress);
125
+ return label === undefined
126
+ ? wait()
127
+ : downloadStatus(label, opts.statusCallback, wait);
128
+ };
129
+ }
130
+ function count(counts, key) {
131
+ if (key) {
132
+ counts.set(key, (counts.get(key) ?? 0) + 1);
133
+ }
134
+ }
135
+ function uncount(counts, key) {
136
+ if (key) {
137
+ const left = counts.get(key) - 1;
138
+ if (left > 0) {
139
+ counts.set(key, left);
68
140
  }
69
- finally {
70
- if (statusCallback) {
71
- waiting.delete(statusCallback);
72
- }
141
+ else {
142
+ counts.delete(key);
73
143
  }
74
- };
144
+ }
75
145
  }
76
146
  /**
77
147
  * {@link cachedSetup} taking a bare function instead of an options object.
@@ -0,0 +1,23 @@
1
+ import type { AggregateStep, BinStep, CellsStep, TransformStep } from './markEncodingTypes.ts';
2
+ import type { Staged } from './stepTables.ts';
3
+ /**
4
+ * Whether a `cells` step, an interval `bin` and an `aggregate` run as one
5
+ * walk: the bin cuts the runs' own extents at whole-base edges it writes as
6
+ * `start` and `end`, the aggregate groups by those, and every op is the sum or
7
+ * mean of `match` weighted by `overlap`, so a bin's answer is its bases
8
+ * matched and compared.
9
+ */
10
+ export declare function fusesCellMatches(_cells: CellsStep, bin: BinStep, agg: AggregateStep): boolean;
11
+ /** Whether a step list starts with the bin and aggregate a `cells` fuses with. */
12
+ export declare function fusesAfterCells(cells: CellsStep, steps: readonly TransformStep[] | undefined): boolean;
13
+ /**
14
+ * `cells`, then a `bin` over the runs' extents and the `aggregate` of their
15
+ * `match` weighted by `overlap`, in one walk with no run table: each bin's
16
+ * bases matched and compared, counted straight off the texts. Every run
17
+ * meets its bins in the order its lanes would hold it, so the groups come out
18
+ * where the three steps put them, and a weighted sum of 0s and 1s is an
19
+ * integer, so each sum is theirs to the bit. Undefined where a row's start is
20
+ * not a whole base inside a Uint32 or the bins spread too thinly for a dense
21
+ * index, and the steps run as written.
22
+ */
23
+ export declare function binnedCellMatches({ table, bounds }: Staged, cells: CellsStep, bin: BinStep, agg: AggregateStep): Staged | undefined;
@@ -0,0 +1,264 @@
1
+ // A `cells` step, the interval `bin` behind it and the `aggregate` of its
2
+ // matches as one walk over the rows' bytes, which `runSteps` in
3
+ // featureTransforms.ts reaches by relative path only.
4
+ import { fusesBinAggregate } from './binnedAggregate.js';
5
+ import { DASH, DEFAULT_CELLS_FIELD, MATCH, MISMATCH, NO_STATE, SPACE, cellState, firstDrawn, isGapByte, lastDrawn, walkedTexts, } from './cellsStep.js';
6
+ import { numberReaderOf, readerOf } from './featureTable.js';
7
+ import { BIN_OVERLAP_FIELD, DEFAULT_BIN_AS, OpSums, groupKey, madeGroups, } from './stepTables.js';
8
+ const isEdges = (pair) => pair?.length === 2 && pair[0] === 'start' && pair[1] === 'end';
9
+ /**
10
+ * Whether a `cells` step, an interval `bin` and an `aggregate` run as one
11
+ * walk: the bin cuts the runs' own extents at whole-base edges it writes as
12
+ * `start` and `end`, the aggregate groups by those, and every op is the sum or
13
+ * mean of `match` weighted by `overlap`, so a bin's answer is its bases
14
+ * matched and compared.
15
+ */
16
+ export function fusesCellMatches(_cells, bin, agg) {
17
+ return (fusesBinAggregate(bin, agg) &&
18
+ isEdges(bin.fields) &&
19
+ isEdges(bin.as ?? DEFAULT_BIN_AS) &&
20
+ Number.isInteger(bin.step) &&
21
+ bin.step > 0 &&
22
+ agg.ops.length > 0 &&
23
+ agg.ops.every(op => (op.op === 'mean' || op.op === 'sum') &&
24
+ op.field === 'match' &&
25
+ op.weight === BIN_OVERLAP_FIELD));
26
+ }
27
+ /** Whether a step list starts with the bin and aggregate a `cells` fuses with. */
28
+ export function fusesAfterCells(cells, steps) {
29
+ const [bin, agg] = steps ?? [];
30
+ return (bin?.type === 'bin' &&
31
+ agg?.type === 'aggregate' &&
32
+ fusesCellMatches(cells, bin, agg));
33
+ }
34
+ // Where the widest section spans more bins than this over the bins all the
35
+ // rows could reach, its dense index costs more than the runs would, and the
36
+ // steps run as written.
37
+ const SPARSE_BINS_SLACK = 65536;
38
+ // A run's start is a Uint32 lane in the unfused `cells`.
39
+ const MAX_POSITION = 2 ** 32;
40
+ /**
41
+ * The groups the pieces of a section's runs meet, numbered in the order they
42
+ * meet them, over one dense index of the section's bins that each section
43
+ * reuses; with each bin's bases matched and compared.
44
+ */
45
+ class SectionBins {
46
+ groupOfBin;
47
+ matched;
48
+ compared;
49
+ groupRow;
50
+ groupBin;
51
+ groupSection;
52
+ groupMatched;
53
+ groupCompared;
54
+ groups = 0;
55
+ low = 0;
56
+ section = 0;
57
+ sectionStart = 0;
58
+ constructor(span, capacity) {
59
+ this.groupOfBin = new Int32Array(span).fill(-1);
60
+ this.matched = new Float64Array(span);
61
+ this.compared = new Float64Array(span);
62
+ this.groupRow = new Uint32Array(capacity);
63
+ this.groupBin = new Float64Array(capacity);
64
+ this.groupSection = new Uint32Array(capacity);
65
+ this.groupMatched = new Float64Array(capacity);
66
+ this.groupCompared = new Float64Array(capacity);
67
+ }
68
+ open(section, low) {
69
+ this.section = section;
70
+ this.low = low;
71
+ this.sectionStart = this.groups;
72
+ }
73
+ // A piece's bin from `from` to `to`, a new group where no piece met it.
74
+ touch(from, to, r) {
75
+ const { groupOfBin, low } = this;
76
+ for (let b = from; b <= to; b++) {
77
+ if (groupOfBin[b - low] < 0) {
78
+ const g = this.groups++;
79
+ groupOfBin[b - low] = g;
80
+ this.groupRow[g] = r;
81
+ this.groupBin[g] = b;
82
+ this.groupSection[g] = this.section;
83
+ }
84
+ }
85
+ }
86
+ // The section's counts onto its groups, and its bins cleared for the next.
87
+ close() {
88
+ const { groupOfBin, matched, compared, low } = this;
89
+ for (let g = this.sectionStart; g < this.groups; g++) {
90
+ const k = this.groupBin[g] - low;
91
+ this.groupMatched[g] = matched[k];
92
+ this.groupCompared[g] = compared[k];
93
+ matched[k] = 0;
94
+ compared[k] = 0;
95
+ groupOfBin[k] = -1;
96
+ }
97
+ }
98
+ /**
99
+ * One row's runs as `cells` writes them, each met by its bins as the bin
100
+ * step cuts it and in the order the lanes hold them: a run when the next
101
+ * column's state differs, an insertion at the reference base after it,
102
+ * ahead of the run still open. The bases a run of matches or mismatches
103
+ * holds are counted in their bins as the walk passes them.
104
+ */
105
+ walk(r, startPos, size, rowBytes, rowAt, rowLen, refBytes, refAt, refLen) {
106
+ const { matched, compared, low } = this;
107
+ const first = firstDrawn(rowBytes, rowAt, rowLen);
108
+ const last = lastDrawn(rowBytes, rowAt, rowLen, first);
109
+ let pos = startPos;
110
+ let bin = Math.floor(pos / size);
111
+ let edge = bin * size + size;
112
+ let m = 0;
113
+ let c = 0;
114
+ let runOpen = false;
115
+ let runBin = 0;
116
+ let runState = NO_STATE;
117
+ let runBase = -1;
118
+ let inserting = false;
119
+ for (let col = 0; col < refLen; col++) {
120
+ const refByte = refBytes[refAt + col];
121
+ const rowByte = col < rowLen ? rowBytes[rowAt + col] : SPACE;
122
+ if (refByte === DASH) {
123
+ if (!inserting && col < rowLen && !isGapByte(rowByte)) {
124
+ inserting = true;
125
+ }
126
+ continue;
127
+ }
128
+ if (inserting) {
129
+ this.touch(bin, bin, r);
130
+ inserting = false;
131
+ }
132
+ const st = cellState(refByte, rowByte, col >= first && col <= last && col < rowLen);
133
+ const base = st === MISMATCH ? rowByte : -1;
134
+ if (st !== runState || base !== runBase) {
135
+ if (runOpen) {
136
+ this.touch(runBin, pos > edge - size ? bin : bin - 1, r);
137
+ }
138
+ runOpen = st !== NO_STATE;
139
+ runBin = bin;
140
+ runState = st;
141
+ runBase = base;
142
+ }
143
+ if (st === MATCH) {
144
+ m++;
145
+ c++;
146
+ }
147
+ else if (st === MISMATCH) {
148
+ c++;
149
+ }
150
+ pos++;
151
+ if (pos === edge) {
152
+ if (c > 0) {
153
+ matched[bin - low] += m;
154
+ compared[bin - low] += c;
155
+ m = 0;
156
+ c = 0;
157
+ }
158
+ bin++;
159
+ edge += size;
160
+ }
161
+ }
162
+ if (runOpen) {
163
+ this.touch(runBin, pos > edge - size ? bin : bin - 1, r);
164
+ }
165
+ if (c > 0) {
166
+ matched[bin - low] += m;
167
+ compared[bin - low] += c;
168
+ }
169
+ }
170
+ }
171
+ /**
172
+ * `cells`, then a `bin` over the runs' extents and the `aggregate` of their
173
+ * `match` weighted by `overlap`, in one walk with no run table: each bin's
174
+ * bases matched and compared, counted straight off the texts. Every run
175
+ * meets its bins in the order its lanes would hold it, so the groups come out
176
+ * where the three steps put them, and a weighted sum of 0s and 1s is an
177
+ * integer, so each sum is theirs to the bit. Undefined where a row's start is
178
+ * not a whole base inside a Uint32 or the bins spread too thinly for a dense
179
+ * index, and the steps run as written.
180
+ */
181
+ export function binnedCellMatches({ table, bounds }, cells, bin, agg) {
182
+ const { field = DEFAULT_CELLS_FIELD } = cells;
183
+ const texts = walkedTexts(table, field);
184
+ const readStart = numberReaderOf(table.column('start'));
185
+ const size = bin.step;
186
+ const sections = bounds.length - 1;
187
+ const lowBin = new Float64Array(sections);
188
+ let widest = 0;
189
+ let reach = 0;
190
+ let spans = 0;
191
+ for (let s = 0; s < sections; s++) {
192
+ let low = Infinity;
193
+ let high = -Infinity;
194
+ for (let r = bounds[s]; r < bounds[s + 1]; r++) {
195
+ const refLen = texts.reference.length[texts.refOf(r)];
196
+ const pos = readStart(r);
197
+ if (refLen > 0 && pos >= 0) {
198
+ if (!Number.isInteger(pos) || pos + refLen >= MAX_POSITION) {
199
+ return undefined;
200
+ }
201
+ const lo = Math.floor(pos / size);
202
+ const hi = Math.floor((pos + refLen) / size);
203
+ low = Math.min(low, lo);
204
+ high = Math.max(high, hi);
205
+ reach += hi - lo + 1;
206
+ }
207
+ }
208
+ lowBin[s] = low;
209
+ const span = high >= low ? high - low + 1 : 0;
210
+ widest = Math.max(widest, span);
211
+ spans += span;
212
+ }
213
+ if (widest > 2 * reach + SPARSE_BINS_SLACK) {
214
+ return undefined;
215
+ }
216
+ const { bytes: rowBytes, offset: rowOffset, length: rowLength } = texts.row;
217
+ const { bytes: refBytes, offset: refOffset, length: refLength, } = texts.reference;
218
+ const walk = new SectionBins(widest, Math.min(spans, reach));
219
+ for (let s = 0; s < sections; s++) {
220
+ walk.open(s, lowBin[s]);
221
+ for (let r = bounds[s]; r < bounds[s + 1]; r++) {
222
+ const f = texts.refOf(r);
223
+ const refLen = refLength[f];
224
+ const pos = readStart(r);
225
+ if (refLen > 0 && pos >= 0) {
226
+ const k = texts.rowOf(r);
227
+ walk.walk(r, pos, size, rowBytes, rowOffset[k], rowLength[k], refBytes, refOffset[f], refLen);
228
+ }
229
+ }
230
+ walk.close();
231
+ }
232
+ const { groups, groupRow, groupBin, groupSection } = walk;
233
+ const ops = agg.ops.map(op => {
234
+ const sums = new OpSums(op, groups);
235
+ sums.sum.set(walk.groupMatched.subarray(0, groups));
236
+ sums.weight.set(walk.groupCompared.subarray(0, groups));
237
+ return sums.column(groups);
238
+ });
239
+ const start = new Float64Array(groups);
240
+ const end = new Float64Array(groups);
241
+ for (let g = 0; g < groups; g++) {
242
+ start[g] = groupBin[g] * size;
243
+ end[g] = start[g] + size;
244
+ }
245
+ const readRef = readerOf(table.column('refName'));
246
+ const refNames = new Array(groups);
247
+ for (let g = 0; g < groups; g++) {
248
+ refNames[g] = readRef(groupRow[g]);
249
+ }
250
+ return madeGroups({
251
+ groups,
252
+ keys: (agg.groupby ?? []).map((field) => [
253
+ field,
254
+ Array.from(field === 'start' ? start : end, groupKey),
255
+ ]),
256
+ ops,
257
+ step: agg,
258
+ refNames,
259
+ start,
260
+ end,
261
+ sectionOf: groupSection,
262
+ sections: bounds.length,
263
+ });
264
+ }
@@ -0,0 +1,29 @@
1
+ import type { FeatureTable, TextColumn } from './featureTable.ts';
2
+ import type { CellsStep } from './markEncodingTypes.ts';
3
+ import type { Staged } from './stepTables.ts';
4
+ export declare const DEFAULT_CELLS_FIELD = "seq";
5
+ export declare const DASH = 45;
6
+ export declare const SPACE = 32;
7
+ export declare const MATCH = 0;
8
+ export declare const MISMATCH = 1;
9
+ export declare const NO_STATE = 255;
10
+ export declare function isGapByte(b: number): b is 32 | 45;
11
+ export declare function firstDrawn(bytes: Uint8Array, at: number, len: number): number;
12
+ export declare function lastDrawn(bytes: Uint8Array, at: number, len: number, first: number): number;
13
+ export declare function cellState(refByte: number, rowByte: number, drawn: boolean): 0 | 1 | 2 | 255;
14
+ /**
15
+ * The texts a `cells` walk reads, by the row of the table it walked: each
16
+ * row's own, and the reference it is read against, which a row fanned out of
17
+ * a container reads off the container.
18
+ */
19
+ declare class WalkedTexts {
20
+ readonly row: TextColumn;
21
+ readonly reference: TextColumn;
22
+ private readonly refRow;
23
+ constructor(row: TextColumn, ref: TextColumn, refRow?: Uint32Array);
24
+ rowOf(r: number): number;
25
+ refOf(r: number): number;
26
+ }
27
+ export declare function walkedTexts(table: FeatureTable, field: string): WalkedTexts;
28
+ export declare function cells({ table, bounds }: Staged, step: CellsStep): Staged;
29
+ export {};