@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.
- package/README.md +113 -5
- package/esm/BaseFeatureWidget/BaseFeatureDetail/ArrayValue.js +0 -2
- package/esm/BaseFeatureWidget/BaseFeatureDetail/BasicValue.js +22 -6
- package/esm/BaseFeatureWidget/BaseFeatureDetail/jexlFilterActions.js +3 -4
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/useSequenceData.js +8 -5
- package/esm/CorePlugin.js +32 -1
- package/esm/ReExports/coreModules.generated.js +248 -246
- package/esm/ReExports/coreWorkerModules.generated.js +246 -244
- package/esm/ReExports/list.js +3 -2
- package/esm/configuration/index.d.ts +2 -0
- package/esm/configuration/index.js +1 -0
- package/esm/configuration/plot.d.ts +61 -0
- package/esm/configuration/plot.js +142 -0
- package/esm/pluggableElementTypes/DisplayType.d.ts +7 -0
- package/esm/pluggableElementTypes/DisplayType.js +6 -0
- package/esm/pluggableElementTypes/WidgetType.d.ts +6 -0
- package/esm/pluggableElementTypes/WidgetType.js +6 -0
- package/esm/pluggableElementTypes/index.js +1 -8
- package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +57 -1
- package/esm/pluggableElementTypes/models/BaseDisplayModel.js +84 -2
- package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +2 -2
- package/esm/pluggableElementTypes/models/BaseViewModel.js +1 -1
- package/esm/ui/JexlFilterDialog.d.ts +1 -1
- package/esm/ui/JexlFilterDialog.js +2 -3
- package/esm/util/TimeTraveller.d.ts +3 -0
- package/esm/util/TimeTraveller.js +34 -2
- package/esm/util/baseDisplayConfig.d.ts +8 -0
- package/esm/util/baseDisplayConfig.js +27 -0
- package/esm/util/cachedSetup.d.ts +16 -6
- package/esm/util/cachedSetup.js +108 -38
- package/esm/util/cellMatches.d.ts +23 -0
- package/esm/util/cellMatches.js +264 -0
- package/esm/util/cellsStep.d.ts +29 -0
- package/esm/util/cellsStep.js +437 -0
- package/esm/util/copyToClipboard.js +8 -1
- package/esm/util/createAbortRotation.js +3 -3
- package/esm/util/featureTransforms.d.ts +7 -6
- package/esm/util/featureTransforms.js +131 -479
- package/esm/util/fetchPhases.d.ts +1 -1
- package/esm/util/formatBytes.js +1 -1
- package/esm/util/formatGuessers.d.ts +25 -14
- package/esm/util/formatGuessers.js +42 -27
- package/esm/util/formatRelativeTime.js +1 -1
- package/esm/util/getLocationUri.d.ts +2 -1
- package/esm/util/getLocationUri.js +8 -2
- package/esm/util/groupKeys.d.ts +4 -5
- package/esm/util/groupKeys.js +7 -6
- package/esm/util/intervals.js +9 -4
- package/esm/util/io/index.js +3 -1
- package/esm/util/jexlFilters.d.ts +31 -49
- package/esm/util/jexlFilters.js +29 -39
- package/esm/util/locString.js +5 -2
- package/esm/util/markEncoding.js +48 -8
- package/esm/util/markEncodingTypes.d.ts +8 -1
- package/esm/util/springAnimate.js +1 -1
- package/esm/util/stepTables.d.ts +14 -0
- package/esm/util/stepTables.js +38 -4
- package/esm/util/tabix.d.ts +6 -2
- package/esm/util/tabix.js +2 -2
- package/esm/util/tracks.d.ts +3 -30
- package/esm/util/tracks.js +4 -39
- package/esm/util/unknownSnapshotKeys.d.ts +3 -1
- package/esm/util/unknownSnapshotKeys.js +5 -11
- package/esm/util/usePanZoom.js +10 -3
- package/esm/util/useRafCommit.js +1 -0
- package/esm/util/withFeatureDetails.js +4 -0
- package/package.json +9 -8
|
@@ -1,42 +1,23 @@
|
|
|
1
1
|
import { binnedAggregate, fusesBinAggregate } from './binnedAggregate.js';
|
|
2
2
|
import { categoricalField } from './categoricalField.js';
|
|
3
|
+
import { binnedCellMatches, fusesAfterCells, fusesCellMatches, } from './cellMatches.js';
|
|
4
|
+
import { cells } from './cellsStep.js';
|
|
3
5
|
import { DerivedTable, NO_COLUMN, WithTable, asTable, numberReaderOf, readerOf, selectRows, throughIndex, valueAt, withColumns, } from './featureTable.js';
|
|
4
6
|
import { fieldReader, isPlainFieldRef } from './fieldReader.js';
|
|
5
|
-
import { stringToJexlExpression } from './jexlStrings.js';
|
|
7
|
+
import { isJexl, stringToJexlExpression } from './jexlStrings.js';
|
|
6
8
|
import SimpleFeature, { buildJexlContext } from './simpleFeature.js';
|
|
7
|
-
import { BIN_OVERLAP_FIELD, DEFAULT_BIN_AS, MadeTable, OpSums, binSize, groupKey, intervalsOf, isWeighted, madeGroups, stepNumberReader, stepReader, } from './stepTables.js';
|
|
9
|
+
import { BIN_OVERLAP_FIELD, DEFAULT_BIN_AS, FannedTable, MadeTable, OpSums, binSize, boundsThrough, groupKey, intervalsOf, isWeighted, madeGroups, stepNumberReader, stepReader, } from './stepTables.js';
|
|
8
10
|
import { junctionEnds, svClassOfAlt, svClassOfToken } from './svAlt.js';
|
|
9
11
|
export { BIN_OVERLAP_FIELD, DEFAULT_BIN_AS } from './stepTables.js';
|
|
10
12
|
export const DEFAULT_BIN_FIELD = 'start';
|
|
11
13
|
export const DEFAULT_FLATTEN_FIELD = 'subfeatures';
|
|
12
|
-
export
|
|
14
|
+
export { DEFAULT_CELLS_FIELD } from './cellsStep.js';
|
|
13
15
|
export const DEFAULT_COVERAGE_AS = 'coverage';
|
|
14
16
|
export const DEFAULT_PILEUP_AS = 'row';
|
|
15
17
|
export const DEFAULT_PILEUP_FIELDS = ['start', 'end'];
|
|
16
18
|
function oneSection(n) {
|
|
17
19
|
return Uint32Array.of(0, n);
|
|
18
20
|
}
|
|
19
|
-
// Section starts through a step whose rows came from its input's in order:
|
|
20
|
-
// each section starts at the first row whose parent row is in it.
|
|
21
|
-
function boundsThrough(bounds, parentRow) {
|
|
22
|
-
const out = new Uint32Array(bounds.length);
|
|
23
|
-
let lo = 0;
|
|
24
|
-
for (let s = 0; s < bounds.length; s++) {
|
|
25
|
-
const edge = bounds[s];
|
|
26
|
-
let hi = parentRow.length;
|
|
27
|
-
while (lo < hi) {
|
|
28
|
-
const mid = (lo + hi) >>> 1;
|
|
29
|
-
if (parentRow[mid] < edge) {
|
|
30
|
-
lo = mid + 1;
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
hi = mid;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
out[s] = lo;
|
|
37
|
-
}
|
|
38
|
-
return out;
|
|
39
|
-
}
|
|
40
21
|
function expression(expr, jexl) {
|
|
41
22
|
if (!jexl) {
|
|
42
23
|
throw new Error(`a jexl transform needs a jexl instance (${expr})`);
|
|
@@ -94,19 +75,6 @@ function fannedEntries(items) {
|
|
|
94
75
|
? Object.entries(items)
|
|
95
76
|
: [];
|
|
96
77
|
}
|
|
97
|
-
/**
|
|
98
|
-
* The entries a `flatten` fanned out, one row each over the container row it
|
|
99
|
-
* came from. A container kept with nothing to fan out is a row of its own
|
|
100
|
-
* that reads everything off itself.
|
|
101
|
-
*/
|
|
102
|
-
class FannedTable extends DerivedTable {
|
|
103
|
-
get madeFrom() {
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
106
|
-
row(i) {
|
|
107
|
-
return this.isKept(i) ? this.parent.row(this.parentOf(i)) : super.row(i);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
78
|
/**
|
|
111
79
|
* The entries a `flatten` fanned out of a list or record a row holds. An
|
|
112
80
|
* entry answers a field it carries — a feature its own, a record its own key,
|
|
@@ -421,425 +389,6 @@ function flattenValues({ table, bounds }, step) {
|
|
|
421
389
|
bounds: boundsThrough(bounds, rows),
|
|
422
390
|
};
|
|
423
391
|
}
|
|
424
|
-
const DASH = 45;
|
|
425
|
-
const SPACE = 32;
|
|
426
|
-
const LOWER_BIT = 0x20;
|
|
427
|
-
const CELL_STATES = ['match', 'mismatch', 'gap', 'insertion'];
|
|
428
|
-
const MATCH = 0;
|
|
429
|
-
const MISMATCH = 1;
|
|
430
|
-
const GAP = 2;
|
|
431
|
-
const INSERTION = 3;
|
|
432
|
-
const NO_STATE = 255;
|
|
433
|
-
function isGapByte(b) {
|
|
434
|
-
return b === DASH || b === SPACE;
|
|
435
|
-
}
|
|
436
|
-
// The row a cell step reads against: past any table that only reordered or
|
|
437
|
-
// annotated the rows, the one they were fanned out of.
|
|
438
|
-
function containerOf(table) {
|
|
439
|
-
let at = table;
|
|
440
|
-
let toBase;
|
|
441
|
-
while (at instanceof DerivedTable && !at.madeFrom) {
|
|
442
|
-
toBase = at.parentRow ? compose(toBase, at.parentRow) : toBase;
|
|
443
|
-
at = at.parent;
|
|
444
|
-
}
|
|
445
|
-
if (!(at instanceof DerivedTable)) {
|
|
446
|
-
return undefined;
|
|
447
|
-
}
|
|
448
|
-
return {
|
|
449
|
-
made: at,
|
|
450
|
-
toMade: toBase,
|
|
451
|
-
table: at.parent,
|
|
452
|
-
row: at.parentRow ? compose(toBase, at.parentRow) : toBase,
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
|
-
function compose(outer, inner) {
|
|
456
|
-
if (!outer) {
|
|
457
|
-
return inner;
|
|
458
|
-
}
|
|
459
|
-
const out = new Uint32Array(outer.length);
|
|
460
|
-
for (let i = 0; i < outer.length; i++) {
|
|
461
|
-
out[i] = inner[outer[i]];
|
|
462
|
-
}
|
|
463
|
-
return out;
|
|
464
|
-
}
|
|
465
|
-
// Below this length a character loop copies a string faster than
|
|
466
|
-
// `encodeInto`, as the MAF packer measured.
|
|
467
|
-
const ASCII_COPY_MAX_LENGTH = 64;
|
|
468
|
-
const encoder = new TextEncoder();
|
|
469
|
-
// A column's text as bytes, one per character: an adapter's own, or the
|
|
470
|
-
// strings a feature holds copied into one buffer.
|
|
471
|
-
function textOf(column, n) {
|
|
472
|
-
if (column.kind === 'text') {
|
|
473
|
-
return column;
|
|
474
|
-
}
|
|
475
|
-
const read = readerOf(column);
|
|
476
|
-
const texts = new Array(n);
|
|
477
|
-
let total = 0;
|
|
478
|
-
for (let i = 0; i < n; i++) {
|
|
479
|
-
const v = read(i);
|
|
480
|
-
if (typeof v === 'string') {
|
|
481
|
-
texts[i] = v;
|
|
482
|
-
total += v.length;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
const bytes = new Uint8Array(total);
|
|
486
|
-
const offset = new Uint32Array(n);
|
|
487
|
-
const length = new Uint32Array(n);
|
|
488
|
-
let pos = 0;
|
|
489
|
-
for (let i = 0; i < n; i++) {
|
|
490
|
-
const text = texts[i];
|
|
491
|
-
if (text === undefined) {
|
|
492
|
-
continue;
|
|
493
|
-
}
|
|
494
|
-
offset[i] = pos;
|
|
495
|
-
if (text.length <= ASCII_COPY_MAX_LENGTH) {
|
|
496
|
-
for (let c = 0; c < text.length; c++) {
|
|
497
|
-
bytes[pos + c] = text.charCodeAt(c);
|
|
498
|
-
}
|
|
499
|
-
length[i] = text.length;
|
|
500
|
-
}
|
|
501
|
-
else {
|
|
502
|
-
length[i] = encoder.encodeInto(text, bytes.subarray(pos, pos + text.length)).written;
|
|
503
|
-
}
|
|
504
|
-
pos += text.length;
|
|
505
|
-
}
|
|
506
|
-
return { kind: 'text', bytes, offset, length, at: undefined };
|
|
507
|
-
}
|
|
508
|
-
/**
|
|
509
|
-
* The texts a `cells` walk reads, by the row of the table it walked: each
|
|
510
|
-
* row's own, and the reference it is read against, which a row fanned out of
|
|
511
|
-
* a container reads off the container.
|
|
512
|
-
*/
|
|
513
|
-
class WalkedTexts {
|
|
514
|
-
row;
|
|
515
|
-
reference;
|
|
516
|
-
refRow;
|
|
517
|
-
constructor(row, ref, refRow) {
|
|
518
|
-
this.row = row;
|
|
519
|
-
this.reference = ref;
|
|
520
|
-
this.refRow = refRow;
|
|
521
|
-
}
|
|
522
|
-
rowOf(r) {
|
|
523
|
-
const { at } = this.row;
|
|
524
|
-
return at ? at[r] : r;
|
|
525
|
-
}
|
|
526
|
-
refOf(r) {
|
|
527
|
-
const p = this.refRow ? this.refRow[r] : r;
|
|
528
|
-
const { at } = this.reference;
|
|
529
|
-
return at ? at[p] : p;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
function walkedTexts(table, field) {
|
|
533
|
-
const row = textOf(table.column(field), table.length);
|
|
534
|
-
const container = containerOf(table);
|
|
535
|
-
const made = container?.made;
|
|
536
|
-
if (container && !(made instanceof FannedTable && made.hasKept)) {
|
|
537
|
-
const { table: base } = container;
|
|
538
|
-
return new WalkedTexts(row, textOf(base.column(field), base.length), container.row);
|
|
539
|
-
}
|
|
540
|
-
// A container a flatten kept with nothing to fan out is the row itself, so
|
|
541
|
-
// its reference is its own container's, as its `parent()` says.
|
|
542
|
-
const byParent = (r) => table.row(r).parent?.()?.get(field);
|
|
543
|
-
const readContainer = container
|
|
544
|
-
? readerOf(container.table.column(field))
|
|
545
|
-
: undefined;
|
|
546
|
-
const read = readContainer && made instanceof FannedTable
|
|
547
|
-
? (r) => {
|
|
548
|
-
const { toMade, row: refRow } = container;
|
|
549
|
-
return made.isKept(toMade ? toMade[r] : r)
|
|
550
|
-
? byParent(r)
|
|
551
|
-
: readContainer(refRow ? refRow[r] : r);
|
|
552
|
-
}
|
|
553
|
-
: byParent;
|
|
554
|
-
return new WalkedTexts(row, textOf({ kind: 'value', read }, table.length));
|
|
555
|
-
}
|
|
556
|
-
/**
|
|
557
|
-
* The runs a `cells` step answers, one per stretch of one state against the
|
|
558
|
-
* reference, typed: each run's span, its state and the text column it starts
|
|
559
|
-
* at, with `base`, `match` and `length` read back out of the texts when asked
|
|
560
|
-
* for.
|
|
561
|
-
*/
|
|
562
|
-
class CellTable extends DerivedTable {
|
|
563
|
-
start;
|
|
564
|
-
end;
|
|
565
|
-
state;
|
|
566
|
-
textAt;
|
|
567
|
-
texts;
|
|
568
|
-
firstOfRow;
|
|
569
|
-
derived = new Map();
|
|
570
|
-
constructor(parent, parentRow, lanes, texts) {
|
|
571
|
-
super(parent, parentRow);
|
|
572
|
-
this.start = lanes.start;
|
|
573
|
-
this.end = lanes.end;
|
|
574
|
-
this.state = lanes.state;
|
|
575
|
-
this.textAt = lanes.textAt;
|
|
576
|
-
this.texts = texts;
|
|
577
|
-
}
|
|
578
|
-
get madeFrom() {
|
|
579
|
-
return true;
|
|
580
|
-
}
|
|
581
|
-
laneOf(name, build) {
|
|
582
|
-
let column = this.derived.get(name);
|
|
583
|
-
if (!column) {
|
|
584
|
-
column = build();
|
|
585
|
-
this.derived.set(name, column);
|
|
586
|
-
}
|
|
587
|
-
return column;
|
|
588
|
-
}
|
|
589
|
-
own(name) {
|
|
590
|
-
switch (name) {
|
|
591
|
-
case 'start': {
|
|
592
|
-
return { kind: 'number', values: this.start, at: undefined };
|
|
593
|
-
}
|
|
594
|
-
case 'end': {
|
|
595
|
-
return { kind: 'number', values: this.end, at: undefined };
|
|
596
|
-
}
|
|
597
|
-
case 'state': {
|
|
598
|
-
return {
|
|
599
|
-
kind: 'category',
|
|
600
|
-
codes: this.state,
|
|
601
|
-
labels: CELL_STATES,
|
|
602
|
-
at: undefined,
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
case 'match': {
|
|
606
|
-
return this.laneOf(name, () => {
|
|
607
|
-
const values = new Float32Array(this.length);
|
|
608
|
-
for (let i = 0; i < this.length; i++) {
|
|
609
|
-
const s = this.state[i];
|
|
610
|
-
values[i] = s === MATCH ? 1 : s === MISMATCH ? 0 : Number.NaN;
|
|
611
|
-
}
|
|
612
|
-
return { kind: 'number', values, at: undefined, nanIsAbsent: true };
|
|
613
|
-
});
|
|
614
|
-
}
|
|
615
|
-
case 'length': {
|
|
616
|
-
return this.laneOf(name, () => {
|
|
617
|
-
const values = new Float32Array(this.length).fill(Number.NaN);
|
|
618
|
-
for (let i = 0; i < this.length; i++) {
|
|
619
|
-
if (this.state[i] === INSERTION) {
|
|
620
|
-
values[i] = this.inserted(i).length;
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
return { kind: 'number', values, at: undefined, nanIsAbsent: true };
|
|
624
|
-
});
|
|
625
|
-
}
|
|
626
|
-
case 'base': {
|
|
627
|
-
return this.laneOf(name, () => {
|
|
628
|
-
const values = new Array(this.length);
|
|
629
|
-
for (let i = 0; i < this.length; i++) {
|
|
630
|
-
const s = this.state[i];
|
|
631
|
-
values[i] =
|
|
632
|
-
s === MISMATCH
|
|
633
|
-
? this.mismatched(i)
|
|
634
|
-
: s === INSERTION
|
|
635
|
-
? this.inserted(i)
|
|
636
|
-
: undefined;
|
|
637
|
-
}
|
|
638
|
-
return { kind: 'value', read: i => values[i] };
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
default: {
|
|
642
|
-
return undefined;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
mismatched(i) {
|
|
647
|
-
const { bytes, offset } = this.texts.row;
|
|
648
|
-
const k = this.texts.rowOf(this.parentOf(i));
|
|
649
|
-
return String.fromCharCode(bytes[offset[k] + this.textAt[i]]);
|
|
650
|
-
}
|
|
651
|
-
// The bases a row holds where the reference has none, from the column the
|
|
652
|
-
// insertion starts at to the reference's next base.
|
|
653
|
-
inserted(i) {
|
|
654
|
-
const { row, reference: ref } = this.texts;
|
|
655
|
-
const r = this.parentOf(i);
|
|
656
|
-
const k = this.texts.rowOf(r);
|
|
657
|
-
const f = this.texts.refOf(r);
|
|
658
|
-
const rowAt = row.offset[k];
|
|
659
|
-
const refAt = ref.offset[f];
|
|
660
|
-
const end = Math.min(row.length[k], ref.length[f]);
|
|
661
|
-
let out = '';
|
|
662
|
-
for (let col = this.textAt[i]; col < end && ref.bytes[refAt + col] === DASH; col++) {
|
|
663
|
-
const b = row.bytes[rowAt + col];
|
|
664
|
-
if (!isGapByte(b)) {
|
|
665
|
-
out += String.fromCharCode(b);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
return out;
|
|
669
|
-
}
|
|
670
|
-
id(i) {
|
|
671
|
-
const p = this.parentOf(i);
|
|
672
|
-
this.firstOfRow ??= firstRowIndex(this.parentRow, this.parent.length);
|
|
673
|
-
return `${this.parent.row(p).id()}#${i - this.firstOfRow[p]}`;
|
|
674
|
-
}
|
|
675
|
-
json(i) {
|
|
676
|
-
const out = {
|
|
677
|
-
...this.parent.row(this.parentOf(i)).toJSON(),
|
|
678
|
-
start: this.start[i],
|
|
679
|
-
end: this.end[i],
|
|
680
|
-
state: CELL_STATES[this.state[i]],
|
|
681
|
-
};
|
|
682
|
-
for (const name of ['base', 'match', 'length']) {
|
|
683
|
-
const v = valueAt(this.column(name), i);
|
|
684
|
-
if (v !== undefined) {
|
|
685
|
-
out[name] = v;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
return out;
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
// The lanes a `cells` walk writes, doubled as a row would overrun them.
|
|
692
|
-
class RunLanes {
|
|
693
|
-
start;
|
|
694
|
-
end;
|
|
695
|
-
state;
|
|
696
|
-
textAt;
|
|
697
|
-
parentRow;
|
|
698
|
-
length = 0;
|
|
699
|
-
constructor(capacity) {
|
|
700
|
-
this.start = new Uint32Array(capacity);
|
|
701
|
-
this.end = new Uint32Array(capacity);
|
|
702
|
-
this.state = new Uint8Array(capacity);
|
|
703
|
-
this.textAt = new Uint32Array(capacity);
|
|
704
|
-
this.parentRow = new Uint32Array(capacity);
|
|
705
|
-
}
|
|
706
|
-
reserve(more) {
|
|
707
|
-
const need = this.length + more;
|
|
708
|
-
if (need <= this.start.length) {
|
|
709
|
-
return;
|
|
710
|
-
}
|
|
711
|
-
const size = Math.max(this.start.length * 2, need);
|
|
712
|
-
const widen = (a) => {
|
|
713
|
-
const next = new a.constructor(size);
|
|
714
|
-
next.set(a);
|
|
715
|
-
return next;
|
|
716
|
-
};
|
|
717
|
-
this.start = widen(this.start);
|
|
718
|
-
this.end = widen(this.end);
|
|
719
|
-
this.state = widen(this.state);
|
|
720
|
-
this.textAt = widen(this.textAt);
|
|
721
|
-
this.parentRow = widen(this.parentRow);
|
|
722
|
-
}
|
|
723
|
-
// One row's runs against its reference: a run per column is the most it
|
|
724
|
-
// can answer, so the lanes grow once and the walk writes through locals.
|
|
725
|
-
walk(r, startPos, rowBytes, rowAt, rowLen, refBytes, refAt, refLen) {
|
|
726
|
-
this.reserve(refLen + 1);
|
|
727
|
-
const { start, end, state, textAt, parentRow } = this;
|
|
728
|
-
let n = this.length;
|
|
729
|
-
let pos = startPos;
|
|
730
|
-
// The columns carrying the row's own sequence: a gap run reaching either
|
|
731
|
-
// end of the row measures where the block was cut, not the alignment, so
|
|
732
|
-
// it is no cell either.
|
|
733
|
-
let first = 0;
|
|
734
|
-
while (first < rowLen && isGapByte(rowBytes[rowAt + first])) {
|
|
735
|
-
first++;
|
|
736
|
-
}
|
|
737
|
-
let last = rowLen - 1;
|
|
738
|
-
while (last > first && isGapByte(rowBytes[rowAt + last])) {
|
|
739
|
-
last--;
|
|
740
|
-
}
|
|
741
|
-
let runStart = -1;
|
|
742
|
-
let runState = NO_STATE;
|
|
743
|
-
let runBase = -1;
|
|
744
|
-
let runCol = 0;
|
|
745
|
-
let insertAt = -1;
|
|
746
|
-
for (let col = 0; col < refLen; col++) {
|
|
747
|
-
const refByte = refBytes[refAt + col];
|
|
748
|
-
const rowByte = col < rowLen ? rowBytes[rowAt + col] : SPACE;
|
|
749
|
-
if (refByte === DASH) {
|
|
750
|
-
if (insertAt < 0 && col < rowLen && !isGapByte(rowByte)) {
|
|
751
|
-
insertAt = col;
|
|
752
|
-
}
|
|
753
|
-
continue;
|
|
754
|
-
}
|
|
755
|
-
if (insertAt >= 0) {
|
|
756
|
-
start[n] = pos;
|
|
757
|
-
end[n] = pos;
|
|
758
|
-
state[n] = INSERTION;
|
|
759
|
-
textAt[n] = insertAt;
|
|
760
|
-
parentRow[n] = r;
|
|
761
|
-
n++;
|
|
762
|
-
insertAt = -1;
|
|
763
|
-
}
|
|
764
|
-
const drawn = col >= first && col <= last && col < rowLen;
|
|
765
|
-
const st = !drawn
|
|
766
|
-
? NO_STATE
|
|
767
|
-
: isGapByte(rowByte)
|
|
768
|
-
? GAP
|
|
769
|
-
: (refByte | LOWER_BIT) === (rowByte | LOWER_BIT)
|
|
770
|
-
? MATCH
|
|
771
|
-
: MISMATCH;
|
|
772
|
-
const base = st === MISMATCH ? rowByte : -1;
|
|
773
|
-
if (st !== runState || base !== runBase) {
|
|
774
|
-
if (runStart >= 0) {
|
|
775
|
-
start[n] = runStart;
|
|
776
|
-
end[n] = pos;
|
|
777
|
-
state[n] = runState;
|
|
778
|
-
textAt[n] = runCol;
|
|
779
|
-
parentRow[n] = r;
|
|
780
|
-
n++;
|
|
781
|
-
}
|
|
782
|
-
runStart = st === NO_STATE ? -1 : pos;
|
|
783
|
-
runState = st;
|
|
784
|
-
runBase = base;
|
|
785
|
-
runCol = col;
|
|
786
|
-
}
|
|
787
|
-
pos++;
|
|
788
|
-
}
|
|
789
|
-
if (runStart >= 0) {
|
|
790
|
-
start[n] = runStart;
|
|
791
|
-
end[n] = pos;
|
|
792
|
-
state[n] = runState;
|
|
793
|
-
textAt[n] = runCol;
|
|
794
|
-
parentRow[n] = r;
|
|
795
|
-
n++;
|
|
796
|
-
}
|
|
797
|
-
this.length = n;
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
// Where each parent row's children start, for children written parent by
|
|
801
|
-
// parent in order.
|
|
802
|
-
function firstRowIndex(parentRow, parents) {
|
|
803
|
-
const first = new Uint32Array(parents);
|
|
804
|
-
for (let i = parentRow.length - 1; i >= 0; i--) {
|
|
805
|
-
first[parentRow[i]] = i;
|
|
806
|
-
}
|
|
807
|
-
return first;
|
|
808
|
-
}
|
|
809
|
-
function cells({ table, bounds }, step) {
|
|
810
|
-
const { field = DEFAULT_CELLS_FIELD } = step;
|
|
811
|
-
const texts = walkedTexts(table, field);
|
|
812
|
-
const readStart = numberReaderOf(table.column('start'));
|
|
813
|
-
const { bytes: rowBytes, offset: rowOffset, length: rowLength } = texts.row;
|
|
814
|
-
const { bytes: refBytes, offset: refOffset, length: refLength, } = texts.reference;
|
|
815
|
-
// A run per reference base is the most a row can answer and far past what
|
|
816
|
-
// one does, so the lanes start at an eighth of it.
|
|
817
|
-
let bound = 0;
|
|
818
|
-
for (let r = 0; r < table.length; r++) {
|
|
819
|
-
bound += refLength[texts.refOf(r)] + 1;
|
|
820
|
-
}
|
|
821
|
-
const lanes = new RunLanes(Math.max(1024, bound >>> 3));
|
|
822
|
-
for (let r = 0; r < table.length; r++) {
|
|
823
|
-
const f = texts.refOf(r);
|
|
824
|
-
const refLen = refLength[f];
|
|
825
|
-
const pos = readStart(r);
|
|
826
|
-
if (refLen > 0 && pos >= 0) {
|
|
827
|
-
const k = texts.rowOf(r);
|
|
828
|
-
lanes.walk(r, pos, rowBytes, rowOffset[k], rowLength[k], refBytes, refOffset[f], refLen);
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
const n = lanes.length;
|
|
832
|
-
const rows = lanes.parentRow.subarray(0, n);
|
|
833
|
-
return {
|
|
834
|
-
table: new CellTable(table, rows, {
|
|
835
|
-
start: lanes.start.subarray(0, n),
|
|
836
|
-
end: lanes.end.subarray(0, n),
|
|
837
|
-
state: lanes.state.subarray(0, n),
|
|
838
|
-
textAt: lanes.textAt.subarray(0, n),
|
|
839
|
-
}, texts),
|
|
840
|
-
bounds: boundsThrough(bounds, rows),
|
|
841
|
-
};
|
|
842
|
-
}
|
|
843
392
|
function statedMate(f) {
|
|
844
393
|
const mate = f.get('mate');
|
|
845
394
|
return mate?.refName !== undefined && mate.start !== undefined
|
|
@@ -867,23 +416,34 @@ function mateFields(f, alt, mate, ownDirection, alleleIndex = 0) {
|
|
|
867
416
|
...(svType ? { svType } : {}),
|
|
868
417
|
};
|
|
869
418
|
}
|
|
419
|
+
// A paired read's mate, where the file places one: the sequence and position
|
|
420
|
+
// its next segment aligns to, one base of it.
|
|
421
|
+
function pairedMate(f) {
|
|
422
|
+
const refName = f.get('next_ref');
|
|
423
|
+
const pos = f.get('next_pos');
|
|
424
|
+
return typeof refName === 'string' && typeof pos === 'number' && pos >= 0
|
|
425
|
+
? { refName, start: pos, end: pos + 1, mateDirection: 0 }
|
|
426
|
+
: undefined;
|
|
427
|
+
}
|
|
870
428
|
/**
|
|
871
429
|
* #api
|
|
872
430
|
* How a record states its other end, or undefined where it states none: the
|
|
873
|
-
* `mate` a paired adapter fills (BEDPE, STAR-Fusion),
|
|
874
|
-
* and symbolic-SV readers resolve
|
|
875
|
-
* this names one for, so a caller
|
|
876
|
-
* track wants asks here rather than
|
|
431
|
+
* `mate` a paired adapter fills (BEDPE, STAR-Fusion), an `ALT` the breakend
|
|
432
|
+
* and symbolic-SV readers resolve, or a read's `next_ref` and `next_pos`. The
|
|
433
|
+
* `mate` step admits exactly the features this names one for, so a caller
|
|
434
|
+
* deciding whether links are the picture a track wants asks here rather than
|
|
435
|
+
* re-reading the fields.
|
|
877
436
|
*/
|
|
878
437
|
export function matedBy(f) {
|
|
879
438
|
if (statedMate(f)) {
|
|
880
439
|
return 'mate';
|
|
881
440
|
}
|
|
882
441
|
const alts = f.get('ALT');
|
|
883
|
-
|
|
884
|
-
alts.some((alt, i) => junctionEnds(f, alt, i))
|
|
885
|
-
|
|
886
|
-
|
|
442
|
+
if (Array.isArray(alts) &&
|
|
443
|
+
alts.some((alt, i) => junctionEnds(f, alt, i))) {
|
|
444
|
+
return 'alt';
|
|
445
|
+
}
|
|
446
|
+
return pairedMate(f) ? 'pair' : undefined;
|
|
887
447
|
}
|
|
888
448
|
/**
|
|
889
449
|
* The ends a `mate` step admitted: each a row of the record it came from, with
|
|
@@ -923,9 +483,7 @@ function mates({ table, bounds }) {
|
|
|
923
483
|
for (let s = 0; s + 1 < bounds.length; s++) {
|
|
924
484
|
out[s] = parentRow.length;
|
|
925
485
|
const seen = new Set();
|
|
926
|
-
const admit = (i,
|
|
927
|
-
const here = `${f.get('refName')}:${f.get('start')}-${f.get('end')}`;
|
|
928
|
-
const there = `${mate.refName}:${mate.start}-${mate.end}`;
|
|
486
|
+
const admit = (i, here, there, written, id) => {
|
|
929
487
|
const key = here < there ? `${here}|${there}` : `${there}|${here}`;
|
|
930
488
|
if (!seen.has(key)) {
|
|
931
489
|
seen.add(key);
|
|
@@ -934,17 +492,26 @@ function mates({ table, bounds }) {
|
|
|
934
492
|
ids.push(id);
|
|
935
493
|
}
|
|
936
494
|
};
|
|
495
|
+
const span = (f, mate) => [
|
|
496
|
+
`${f.get('refName')}:${f.get('start')}-${f.get('end')}`,
|
|
497
|
+
`${mate.refName}:${mate.start}-${mate.end}`,
|
|
498
|
+
];
|
|
937
499
|
for (let i = bounds[s]; i < bounds[s + 1]; i++) {
|
|
938
500
|
const f = table.row(i);
|
|
939
501
|
const stated = statedMate(f);
|
|
940
502
|
if (stated) {
|
|
941
503
|
const ends = junctionEnds(f);
|
|
942
504
|
const mate = { ...stated, mateDirection: ends.mate.keeps };
|
|
943
|
-
admit(i, f, mate, mateFields(f, undefined, mate, ends.own.keeps));
|
|
505
|
+
admit(i, ...span(f, mate), mateFields(f, undefined, mate, ends.own.keeps));
|
|
944
506
|
continue;
|
|
945
507
|
}
|
|
946
508
|
const alts = f.get('ALT');
|
|
947
509
|
if (!Array.isArray(alts)) {
|
|
510
|
+
// each read of a pair names the other's start, so the pair is one key
|
|
511
|
+
const pair = pairedMate(f);
|
|
512
|
+
if (pair) {
|
|
513
|
+
admit(i, `${f.get('refName')}:${f.get('start')}`, `${pair.refName}:${pair.start}`, { mate: pair, mateDirection: 0 });
|
|
514
|
+
}
|
|
948
515
|
continue;
|
|
949
516
|
}
|
|
950
517
|
for (const [a, alt] of alts.entries()) {
|
|
@@ -958,7 +525,7 @@ function mates({ table, bounds }) {
|
|
|
958
525
|
end: ends.mate.pos + 1,
|
|
959
526
|
mateDirection: ends.mate.keeps,
|
|
960
527
|
};
|
|
961
|
-
admit(i, f, mate, mateFields(f, alt, mate, ends.own.keeps, a), alts.length > 1 ? `${f.id()}#${a}` : undefined);
|
|
528
|
+
admit(i, ...span(f, mate), mateFields(f, alt, mate, ends.own.keeps, a), alts.length > 1 ? `${f.id()}#${a}` : undefined);
|
|
962
529
|
}
|
|
963
530
|
}
|
|
964
531
|
}
|
|
@@ -1350,12 +917,24 @@ function runSteps(staged, steps, jexl) {
|
|
|
1350
917
|
for (let k = 0; k < list.length; k++) {
|
|
1351
918
|
const step = list[k];
|
|
1352
919
|
const next = list[k + 1];
|
|
1353
|
-
const
|
|
920
|
+
const after = list[k + 2];
|
|
921
|
+
const matches = step.type === 'cells' &&
|
|
922
|
+
next?.type === 'bin' &&
|
|
923
|
+
after?.type === 'aggregate' &&
|
|
924
|
+
fusesCellMatches(step, next, after)
|
|
925
|
+
? binnedCellMatches(current, step, next, after)
|
|
926
|
+
: undefined;
|
|
927
|
+
const fused = !matches &&
|
|
928
|
+
step.type === 'bin' &&
|
|
1354
929
|
next?.type === 'aggregate' &&
|
|
1355
930
|
fusesBinAggregate(step, next)
|
|
1356
931
|
? binnedAggregate(current, step, next)
|
|
1357
932
|
: undefined;
|
|
1358
|
-
if (
|
|
933
|
+
if (matches) {
|
|
934
|
+
current = matches;
|
|
935
|
+
k += 2;
|
|
936
|
+
}
|
|
937
|
+
else if (fused) {
|
|
1359
938
|
current = fused;
|
|
1360
939
|
k++;
|
|
1361
940
|
}
|
|
@@ -1365,10 +944,83 @@ function runSteps(staged, steps, jexl) {
|
|
|
1365
944
|
}
|
|
1366
945
|
return current;
|
|
1367
946
|
}
|
|
947
|
+
/**
|
|
948
|
+
* Each layer's steps over `steps` run once for all of them. A trailing
|
|
949
|
+
* `cells` runs inside each layer whose own steps start with the bin and
|
|
950
|
+
* aggregate it fuses with, so `runSteps` meets the three together, and once
|
|
951
|
+
* for the other layers.
|
|
952
|
+
*/
|
|
953
|
+
function runLayers(staged, steps, layers, jexl, keep = s => s) {
|
|
954
|
+
const list = steps ?? [];
|
|
955
|
+
const last = list.at(-1);
|
|
956
|
+
const trailing = last?.type === 'cells' && layers.some(own => fusesAfterCells(last, own))
|
|
957
|
+
? last
|
|
958
|
+
: undefined;
|
|
959
|
+
if (!trailing) {
|
|
960
|
+
const shared = keep(runSteps(staged, list, jexl), list);
|
|
961
|
+
return layers.map(own => keep(runSteps(shared, own, jexl), own));
|
|
962
|
+
}
|
|
963
|
+
const ahead = runSteps(staged, list.slice(0, -1), jexl);
|
|
964
|
+
let shared;
|
|
965
|
+
return layers.map(own => fusesAfterCells(trailing, own)
|
|
966
|
+
? keep(runSteps(ahead, [trailing, ...own], jexl), [...list, ...own])
|
|
967
|
+
: keep(runSteps((shared ??= keep(runStep(ahead, trailing, jexl), list)), own, jexl), own));
|
|
968
|
+
}
|
|
1368
969
|
function rowOf(value) {
|
|
1369
970
|
const row = Number(value);
|
|
1370
971
|
return row > 0 ? Math.floor(row) : 0;
|
|
1371
972
|
}
|
|
973
|
+
/**
|
|
974
|
+
* A section's rows behind a step that made them from nothing, a coverage's
|
|
975
|
+
* runs or an aggregate's groups, still answer the field their section was
|
|
976
|
+
* split on, as a ggplot2 stat keeps its facet variable: one column of each
|
|
977
|
+
* section's key, built the first time a channel reads it, and a parent that
|
|
978
|
+
* carries the field itself is read instead.
|
|
979
|
+
*/
|
|
980
|
+
class SectionTable extends DerivedTable {
|
|
981
|
+
field;
|
|
982
|
+
labels;
|
|
983
|
+
bounds;
|
|
984
|
+
constructor(parent, field, labels, bounds) {
|
|
985
|
+
super(parent, undefined);
|
|
986
|
+
this.field = field;
|
|
987
|
+
this.labels = labels;
|
|
988
|
+
this.bounds = bounds;
|
|
989
|
+
}
|
|
990
|
+
own(field) {
|
|
991
|
+
if (field !== this.field || this.parent.column(field).kind !== 'none') {
|
|
992
|
+
return undefined;
|
|
993
|
+
}
|
|
994
|
+
const { labels } = this;
|
|
995
|
+
const codes = new Uint32Array(this.length);
|
|
996
|
+
for (let s = 0; s < labels.length; s++) {
|
|
997
|
+
codes.fill(s, this.bounds[s], this.bounds[s + 1]);
|
|
998
|
+
}
|
|
999
|
+
// the section of the rows with nothing in the field answers nothing
|
|
1000
|
+
return labels.includes('')
|
|
1001
|
+
? { kind: 'value', read: i => labels[codes[i]] || undefined }
|
|
1002
|
+
: { kind: 'category', codes, labels, at: undefined };
|
|
1003
|
+
}
|
|
1004
|
+
json(i) {
|
|
1005
|
+
const out = super.json(i);
|
|
1006
|
+
const label = valueAt(this.column(this.field), i);
|
|
1007
|
+
return label === undefined || this.field in out
|
|
1008
|
+
? out
|
|
1009
|
+
: { ...out, [this.field]: label };
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
function makesRows(steps) {
|
|
1013
|
+
return steps?.some(s => s.type === 'aggregate' || s.type === 'coverage');
|
|
1014
|
+
}
|
|
1015
|
+
// The staged rows still naming their section's value where a step made them
|
|
1016
|
+
function keepingSection(staged, steps, field, labels) {
|
|
1017
|
+
return !isJexl(field) && makesRows(steps)
|
|
1018
|
+
? {
|
|
1019
|
+
...staged,
|
|
1020
|
+
table: new SectionTable(staged.table, field, labels, staged.bounds),
|
|
1021
|
+
}
|
|
1022
|
+
: staged;
|
|
1023
|
+
}
|
|
1372
1024
|
/**
|
|
1373
1025
|
* #api
|
|
1374
1026
|
* A faceted request's layers: the rows split on the facet's field, the
|
|
@@ -1444,9 +1096,10 @@ export function facetLayers(input, facet, layers, jexl) {
|
|
|
1444
1096
|
for (let i = 0; i < n; i++) {
|
|
1445
1097
|
order[next[sectionOf[keyOfRow[i]]]++] = i;
|
|
1446
1098
|
}
|
|
1447
|
-
const
|
|
1448
|
-
const
|
|
1449
|
-
|
|
1099
|
+
const labels = ordered.map(k => keys[k]);
|
|
1100
|
+
const layered = runLayers({ table: selectRows(table, order), bounds }, sectionSteps, layers.map(({ transform }) => transform), jexl, (staged, steps) => keepingSection(staged, steps, field, labels));
|
|
1101
|
+
const placed = layers.map(({ row }, l) => {
|
|
1102
|
+
const out = layered[l];
|
|
1450
1103
|
const readRow = row === undefined
|
|
1451
1104
|
? undefined
|
|
1452
1105
|
: isPlainFieldRef(row)
|
|
@@ -1556,12 +1209,11 @@ function partitionPoint(steps, field) {
|
|
|
1556
1209
|
export function layerTables(input, request, jexl) {
|
|
1557
1210
|
const { transform = [], facet, layers } = request;
|
|
1558
1211
|
if (!facet) {
|
|
1559
|
-
const
|
|
1212
|
+
const table = asTable(input);
|
|
1213
|
+
const layered = runLayers({ table, bounds: oneSection(table.length) }, transform, layers.map(layer => layer.transform), jexl);
|
|
1560
1214
|
return {
|
|
1561
|
-
layers: layers.map(layer => ({
|
|
1562
|
-
table:
|
|
1563
|
-
? runTransforms(shared, layer.transform, jexl)
|
|
1564
|
-
: shared,
|
|
1215
|
+
layers: layers.map((layer, l) => ({
|
|
1216
|
+
table: layered[l].table,
|
|
1565
1217
|
row: layer.row,
|
|
1566
1218
|
})),
|
|
1567
1219
|
sections: undefined,
|