@jbrowse/core 5.0.0-beta.10 → 5.0.0-beta.13
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 +210 -80
- package/esm/BaseFeatureWidget/BaseFeatureDetail/ArrayValue.js +0 -2
- package/esm/BaseFeatureWidget/BaseFeatureDetail/Attributes.d.ts +3 -0
- package/esm/BaseFeatureWidget/BaseFeatureDetail/Attributes.js +14 -1
- package/esm/BaseFeatureWidget/BaseFeatureDetail/BasicValue.js +22 -6
- package/esm/BaseFeatureWidget/BaseFeatureDetail/jexlFilterActions.js +3 -4
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeatureDetails.d.ts +2 -0
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeatureDetails.js +5 -5
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/SequenceFeaturePanel.js +1 -1
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/dialogs/FeatureSequenceDialog.d.ts +3 -1
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/dialogs/FeatureSequenceDialog.js +2 -2
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/featureTypeUtil.d.ts +2 -1
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/featureTypeUtil.js +7 -14
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/seqtypes/CDNASequence.js +3 -3
- package/esm/BaseFeatureWidget/SequenceFeatureDetails/useSequenceData.js +8 -5
- package/esm/BaseFeatureWidget/stateModelFactory.d.ts +7 -0
- package/esm/BaseFeatureWidget/stateModelFactory.js +11 -0
- package/esm/BaseFeatureWidget/types.d.ts +2 -0
- package/esm/CorePlugin.js +32 -1
- package/esm/PluginManager.d.ts +6 -3
- package/esm/PluginManager.js +6 -3
- package/esm/ReExports/coreModules.generated.js +291 -283
- package/esm/ReExports/coreWorkerModules.generated.js +288 -281
- package/esm/ReExports/list.js +10 -2
- package/esm/TextSearch/TextSearchManager.d.ts +3 -3
- package/esm/TextSearch/TextSearchManager.js +6 -13
- package/esm/configuration/configurationSchema.d.ts +33 -95
- package/esm/configuration/configurationSchema.js +50 -218
- package/esm/configuration/configurationSlot.d.ts +18 -64
- package/esm/configuration/configurationSlot.js +31 -97
- package/esm/configuration/formatDetailsConfigSchema.js +2 -0
- package/esm/configuration/getConf.d.ts +7 -36
- package/esm/configuration/getConf.js +7 -37
- package/esm/configuration/index.d.ts +5 -2
- package/esm/configuration/index.js +3 -1
- package/esm/configuration/partitionAdvanced.d.ts +12 -0
- package/esm/configuration/partitionAdvanced.js +42 -0
- package/esm/configuration/plot.d.ts +67 -0
- package/esm/configuration/plot.js +175 -0
- package/esm/configuration/readConfObject.d.ts +15 -17
- package/esm/configuration/readConfObject.js +19 -50
- package/esm/configuration/schemaTypes.js +5 -1
- package/esm/configuration/snapshotPreprocess.js +1 -3
- package/esm/configuration/types.d.ts +60 -159
- package/esm/pluggableElementTypes/DisplayType.d.ts +14 -0
- package/esm/pluggableElementTypes/DisplayType.js +13 -0
- package/esm/pluggableElementTypes/RpcMethodType.d.ts +7 -0
- package/esm/pluggableElementTypes/RpcMethodType.js +9 -0
- package/esm/pluggableElementTypes/WidgetType.d.ts +6 -0
- package/esm/pluggableElementTypes/WidgetType.js +6 -0
- package/esm/pluggableElementTypes/addMenuItems.d.ts +2 -1
- package/esm/pluggableElementTypes/addMenuItems.js +2 -1
- package/esm/pluggableElementTypes/index.js +1 -8
- package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +58 -1
- package/esm/pluggableElementTypes/models/BaseDisplayModel.js +84 -2
- package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +2 -4
- package/esm/pluggableElementTypes/models/BaseTrackModel.js +2 -4
- package/esm/pluggableElementTypes/models/BaseViewModel.d.ts +2 -2
- package/esm/pluggableElementTypes/models/BaseViewModel.js +1 -1
- package/esm/pluggableElementTypes/models/baseTrackConfig.js +3 -0
- package/esm/rpc/RpcRegistry.d.ts +2 -1
- package/esm/rpc/byteBudget.d.ts +7 -0
- package/esm/rpc/byteBudget.js +10 -0
- package/esm/rpc/coreRpcMethods.d.ts +1 -1
- package/esm/rpc/coreRpcMethods.js +1 -1
- package/esm/rpc/explainTransferError.js +3 -3
- package/esm/rpc/methods/CoreLoadTrackCode.d.ts +17 -0
- package/esm/rpc/methods/CoreLoadTrackCode.js +24 -0
- package/esm/ui/AppLogo.js +43 -3
- package/esm/ui/JexlFilterDialog.d.ts +1 -1
- package/esm/ui/JexlFilterDialog.js +2 -3
- package/esm/ui/buildExtraTrackMenuItems.d.ts +2 -2
- package/esm/ui/colors.d.ts +41 -30
- package/esm/ui/colors.js +81 -44
- package/esm/ui/multiTrackMenuItems.d.ts +2 -2
- package/esm/util/TimeTraveller.d.ts +3 -0
- package/esm/util/TimeTraveller.js +34 -2
- package/esm/util/alignedBytes.d.ts +19 -0
- package/esm/util/alignedBytes.js +37 -0
- package/esm/util/analytics.d.ts +5 -6
- package/esm/util/analytics.js +5 -3
- package/esm/util/baseDisplayConfig.d.ts +8 -0
- package/esm/util/baseDisplayConfig.js +27 -0
- package/esm/util/bezierConnector.js +5 -1
- package/esm/util/cachedSetup.d.ts +16 -6
- package/esm/util/cachedSetup.js +108 -38
- package/esm/util/categoricalField.d.ts +23 -3
- package/esm/util/categoricalField.js +36 -3
- package/esm/util/cellMatches.d.ts +23 -0
- package/esm/util/cellMatches.js +264 -0
- package/esm/util/cellsStep.d.ts +27 -0
- package/esm/util/cellsStep.js +439 -0
- package/esm/util/color-bits/parse.js +22 -11
- package/esm/util/colorRamp.js +6 -0
- package/esm/util/colorScale.d.ts +9 -2
- package/esm/util/colorScale.js +33 -2
- package/esm/util/colorSchemes.d.ts +4 -2
- package/esm/util/colorSchemes.js +4 -1
- 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/isoformRank.d.ts +39 -0
- package/esm/util/isoformRank.js +118 -0
- 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 +11 -2
- package/esm/util/quantileExtent.d.ts +22 -0
- package/esm/util/quantileExtent.js +24 -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/systemColorScheme.d.ts +4 -0
- package/esm/util/systemColorScheme.js +21 -0
- package/esm/util/tabix.d.ts +7 -3
- package/esm/util/tabix.js +3 -3
- package/esm/util/temporaryAssembly.d.ts +2 -2
- package/esm/util/temporaryAssembly.js +2 -3
- package/esm/util/trackConfigDelta.d.ts +1 -1
- package/esm/util/tracks.d.ts +28 -45
- package/esm/util/tracks.js +71 -82
- package/esm/util/types/index.d.ts +15 -7
- package/esm/util/types/index.js +21 -2
- 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/useRowVirtualScroll.d.ts +1 -0
- package/esm/util/withFeatureDetails.js +4 -0
- package/package.json +13 -9
- package/esm/rpc/methods/CoreLoadAdapterCode.d.ts +0 -13
- package/esm/rpc/methods/CoreLoadAdapterCode.js +0 -16
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
// The `cells` step: each row's runs against the reference it was fanned out
|
|
2
|
+
// of, and the texts its walk reads, which the fused identity kernel in
|
|
3
|
+
// cellMatches.ts walks too. Imported by relative path only.
|
|
4
|
+
import { DASH, SPACE, firstDrawn, isGapByte, isUnknownBase, lastDrawn, sameBase, } from './alignedBytes.js';
|
|
5
|
+
import { DerivedTable, numberReaderOf, readerOf, valueAt, } from './featureTable.js';
|
|
6
|
+
import { FannedTable, boundsThrough } from './stepTables.js';
|
|
7
|
+
export const DEFAULT_CELLS_FIELD = 'seq';
|
|
8
|
+
const CELL_STATES = ['match', 'mismatch', 'gap', 'insertion'];
|
|
9
|
+
export const MATCH = 0;
|
|
10
|
+
export const MISMATCH = 1;
|
|
11
|
+
const GAP = 2;
|
|
12
|
+
const INSERTION = 3;
|
|
13
|
+
export const NO_STATE = 255;
|
|
14
|
+
// A reference base's cell: no state outside the drawn columns, else the row's
|
|
15
|
+
// gap, or its base matched or not regardless of case.
|
|
16
|
+
export function cellState(refByte, rowByte, drawn) {
|
|
17
|
+
return !drawn
|
|
18
|
+
? NO_STATE
|
|
19
|
+
: isGapByte(rowByte)
|
|
20
|
+
? GAP
|
|
21
|
+
: sameBase(refByte, rowByte)
|
|
22
|
+
? MATCH
|
|
23
|
+
: MISMATCH;
|
|
24
|
+
}
|
|
25
|
+
// Whether a cell counts toward identity: matched or not against a known
|
|
26
|
+
// reference base.
|
|
27
|
+
export function comparesBase(state, refByte) {
|
|
28
|
+
return state <= MISMATCH && !isUnknownBase(refByte);
|
|
29
|
+
}
|
|
30
|
+
// What the cells of one run share besides their state: whether they are
|
|
31
|
+
// compared, so a run's `match` is one value.
|
|
32
|
+
export function runKey(state, refByte) {
|
|
33
|
+
return state <= MISMATCH && isUnknownBase(refByte) ? 1 : 0;
|
|
34
|
+
}
|
|
35
|
+
// A mismatch always starts a run of its own, so each differing base is one
|
|
36
|
+
// instance and a text over `base` letters every one.
|
|
37
|
+
export function opensRun(state, key, runState, openKey) {
|
|
38
|
+
return state !== runState || key !== openKey || state === MISMATCH;
|
|
39
|
+
}
|
|
40
|
+
// The row a cell step reads against: past any table that only reordered or
|
|
41
|
+
// annotated the rows, the one they were fanned out of.
|
|
42
|
+
function containerOf(table) {
|
|
43
|
+
let at = table;
|
|
44
|
+
let toBase;
|
|
45
|
+
while (at instanceof DerivedTable && !at.madeFrom) {
|
|
46
|
+
toBase = at.parentRow ? compose(toBase, at.parentRow) : toBase;
|
|
47
|
+
at = at.parent;
|
|
48
|
+
}
|
|
49
|
+
if (!(at instanceof DerivedTable)) {
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
made: at,
|
|
54
|
+
toMade: toBase,
|
|
55
|
+
table: at.parent,
|
|
56
|
+
row: at.parentRow ? compose(toBase, at.parentRow) : toBase,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function compose(outer, inner) {
|
|
60
|
+
if (!outer) {
|
|
61
|
+
return inner;
|
|
62
|
+
}
|
|
63
|
+
const out = new Uint32Array(outer.length);
|
|
64
|
+
for (let i = 0; i < outer.length; i++) {
|
|
65
|
+
out[i] = inner[outer[i]];
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
}
|
|
69
|
+
// Below this length a character loop copies a string faster than
|
|
70
|
+
// `encodeInto`, as the MAF packer measured.
|
|
71
|
+
const ASCII_COPY_MAX_LENGTH = 64;
|
|
72
|
+
const encoder = new TextEncoder();
|
|
73
|
+
// A column's text as bytes, one per character: an adapter's own, or the
|
|
74
|
+
// strings a feature holds copied into one buffer.
|
|
75
|
+
function textOf(column, n) {
|
|
76
|
+
if (column.kind === 'text') {
|
|
77
|
+
return column;
|
|
78
|
+
}
|
|
79
|
+
const read = readerOf(column);
|
|
80
|
+
const texts = new Array(n);
|
|
81
|
+
let total = 0;
|
|
82
|
+
for (let i = 0; i < n; i++) {
|
|
83
|
+
const v = read(i);
|
|
84
|
+
if (typeof v === 'string') {
|
|
85
|
+
texts[i] = v;
|
|
86
|
+
total += v.length;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const bytes = new Uint8Array(total);
|
|
90
|
+
const offset = new Uint32Array(n);
|
|
91
|
+
const length = new Uint32Array(n);
|
|
92
|
+
let pos = 0;
|
|
93
|
+
for (let i = 0; i < n; i++) {
|
|
94
|
+
const text = texts[i];
|
|
95
|
+
if (text === undefined) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
offset[i] = pos;
|
|
99
|
+
if (text.length <= ASCII_COPY_MAX_LENGTH) {
|
|
100
|
+
for (let c = 0; c < text.length; c++) {
|
|
101
|
+
bytes[pos + c] = text.charCodeAt(c);
|
|
102
|
+
}
|
|
103
|
+
length[i] = text.length;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
length[i] = encoder.encodeInto(text, bytes.subarray(pos, pos + text.length)).written;
|
|
107
|
+
}
|
|
108
|
+
pos += text.length;
|
|
109
|
+
}
|
|
110
|
+
return { kind: 'text', bytes, offset, length, at: undefined };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The texts a `cells` walk reads, by the row of the table it walked: each
|
|
114
|
+
* row's own, and the reference it is read against, which a row fanned out of
|
|
115
|
+
* a container reads off the container.
|
|
116
|
+
*/
|
|
117
|
+
class WalkedTexts {
|
|
118
|
+
row;
|
|
119
|
+
reference;
|
|
120
|
+
refRow;
|
|
121
|
+
constructor(row, ref, refRow) {
|
|
122
|
+
this.row = row;
|
|
123
|
+
this.reference = ref;
|
|
124
|
+
this.refRow = refRow;
|
|
125
|
+
}
|
|
126
|
+
rowOf(r) {
|
|
127
|
+
const { at } = this.row;
|
|
128
|
+
return at ? at[r] : r;
|
|
129
|
+
}
|
|
130
|
+
refOf(r) {
|
|
131
|
+
const p = this.refRow ? this.refRow[r] : r;
|
|
132
|
+
const { at } = this.reference;
|
|
133
|
+
return at ? at[p] : p;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export function walkedTexts(table, field) {
|
|
137
|
+
const row = textOf(table.column(field), table.length);
|
|
138
|
+
const container = containerOf(table);
|
|
139
|
+
const made = container?.made;
|
|
140
|
+
if (container && !(made instanceof FannedTable && made.hasKept)) {
|
|
141
|
+
const { table: base } = container;
|
|
142
|
+
return new WalkedTexts(row, textOf(base.column(field), base.length), container.row);
|
|
143
|
+
}
|
|
144
|
+
// A container a flatten kept with nothing to fan out is the row itself, so
|
|
145
|
+
// its reference is its own container's, as its `parent()` says.
|
|
146
|
+
const byParent = (r) => table.row(r).parent?.()?.get(field);
|
|
147
|
+
const readContainer = container
|
|
148
|
+
? readerOf(container.table.column(field))
|
|
149
|
+
: undefined;
|
|
150
|
+
const read = readContainer && made instanceof FannedTable
|
|
151
|
+
? (r) => {
|
|
152
|
+
const { toMade, row: refRow } = container;
|
|
153
|
+
return made.isKept(toMade ? toMade[r] : r)
|
|
154
|
+
? byParent(r)
|
|
155
|
+
: readContainer(refRow ? refRow[r] : r);
|
|
156
|
+
}
|
|
157
|
+
: byParent;
|
|
158
|
+
return new WalkedTexts(row, textOf({ kind: 'value', read }, table.length));
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* The runs a `cells` step answers, one per stretch of one state against the
|
|
162
|
+
* reference, typed: each run's span, its state and the text column it starts
|
|
163
|
+
* at, with `base`, `match` and `length` read back out of the texts when asked
|
|
164
|
+
* for.
|
|
165
|
+
*/
|
|
166
|
+
class CellTable extends DerivedTable {
|
|
167
|
+
start;
|
|
168
|
+
end;
|
|
169
|
+
state;
|
|
170
|
+
textAt;
|
|
171
|
+
texts;
|
|
172
|
+
firstOfRow;
|
|
173
|
+
derived = new Map();
|
|
174
|
+
constructor(parent, parentRow, lanes, texts) {
|
|
175
|
+
super(parent, parentRow);
|
|
176
|
+
this.start = lanes.start;
|
|
177
|
+
this.end = lanes.end;
|
|
178
|
+
this.state = lanes.state;
|
|
179
|
+
this.textAt = lanes.textAt;
|
|
180
|
+
this.texts = texts;
|
|
181
|
+
}
|
|
182
|
+
get madeFrom() {
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
laneOf(name, build) {
|
|
186
|
+
let column = this.derived.get(name);
|
|
187
|
+
if (!column) {
|
|
188
|
+
column = build();
|
|
189
|
+
this.derived.set(name, column);
|
|
190
|
+
}
|
|
191
|
+
return column;
|
|
192
|
+
}
|
|
193
|
+
own(name) {
|
|
194
|
+
switch (name) {
|
|
195
|
+
case 'start': {
|
|
196
|
+
return { kind: 'number', values: this.start, at: undefined };
|
|
197
|
+
}
|
|
198
|
+
case 'end': {
|
|
199
|
+
return { kind: 'number', values: this.end, at: undefined };
|
|
200
|
+
}
|
|
201
|
+
case 'state': {
|
|
202
|
+
return {
|
|
203
|
+
kind: 'category',
|
|
204
|
+
codes: this.state,
|
|
205
|
+
labels: CELL_STATES,
|
|
206
|
+
at: undefined,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
case 'match': {
|
|
210
|
+
return this.laneOf(name, () => {
|
|
211
|
+
const values = new Float32Array(this.length);
|
|
212
|
+
for (let i = 0; i < this.length; i++) {
|
|
213
|
+
const s = this.state[i];
|
|
214
|
+
values[i] = comparesBase(s, this.refByte(i))
|
|
215
|
+
? s === MATCH
|
|
216
|
+
? 1
|
|
217
|
+
: 0
|
|
218
|
+
: Number.NaN;
|
|
219
|
+
}
|
|
220
|
+
return { kind: 'number', values, at: undefined, nanIsAbsent: true };
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
case 'length': {
|
|
224
|
+
return this.laneOf(name, () => {
|
|
225
|
+
const values = new Float32Array(this.length).fill(Number.NaN);
|
|
226
|
+
for (let i = 0; i < this.length; i++) {
|
|
227
|
+
if (this.state[i] === INSERTION) {
|
|
228
|
+
values[i] = this.inserted(i).length;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return { kind: 'number', values, at: undefined, nanIsAbsent: true };
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
case 'base': {
|
|
235
|
+
return this.laneOf(name, () => {
|
|
236
|
+
const values = new Array(this.length);
|
|
237
|
+
for (let i = 0; i < this.length; i++) {
|
|
238
|
+
const s = this.state[i];
|
|
239
|
+
values[i] =
|
|
240
|
+
s === MISMATCH
|
|
241
|
+
? this.mismatched(i)
|
|
242
|
+
: s === INSERTION
|
|
243
|
+
? this.inserted(i)
|
|
244
|
+
: undefined;
|
|
245
|
+
}
|
|
246
|
+
return { kind: 'value', read: i => values[i] };
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
default: {
|
|
250
|
+
return undefined;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
refByte(i) {
|
|
255
|
+
const { reference: ref } = this.texts;
|
|
256
|
+
const f = this.texts.refOf(this.parentOf(i));
|
|
257
|
+
return ref.bytes[ref.offset[f] + this.textAt[i]];
|
|
258
|
+
}
|
|
259
|
+
mismatched(i) {
|
|
260
|
+
const { bytes, offset } = this.texts.row;
|
|
261
|
+
const k = this.texts.rowOf(this.parentOf(i));
|
|
262
|
+
return String.fromCharCode(bytes[offset[k] + this.textAt[i]]);
|
|
263
|
+
}
|
|
264
|
+
// The bases a row holds where the reference has none, from the column the
|
|
265
|
+
// insertion starts at to the reference's next base.
|
|
266
|
+
inserted(i) {
|
|
267
|
+
const { row, reference: ref } = this.texts;
|
|
268
|
+
const r = this.parentOf(i);
|
|
269
|
+
const k = this.texts.rowOf(r);
|
|
270
|
+
const f = this.texts.refOf(r);
|
|
271
|
+
const rowAt = row.offset[k];
|
|
272
|
+
const refAt = ref.offset[f];
|
|
273
|
+
const end = Math.min(row.length[k], ref.length[f]);
|
|
274
|
+
let out = '';
|
|
275
|
+
for (let col = this.textAt[i]; col < end && ref.bytes[refAt + col] === DASH; col++) {
|
|
276
|
+
const b = row.bytes[rowAt + col];
|
|
277
|
+
if (!isGapByte(b)) {
|
|
278
|
+
out += String.fromCharCode(b);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return out;
|
|
282
|
+
}
|
|
283
|
+
id(i) {
|
|
284
|
+
const p = this.parentOf(i);
|
|
285
|
+
this.firstOfRow ??= firstRowIndex(this.parentRow, this.parent.length);
|
|
286
|
+
return `${this.parent.row(p).id()}#${i - this.firstOfRow[p]}`;
|
|
287
|
+
}
|
|
288
|
+
json(i) {
|
|
289
|
+
const out = {
|
|
290
|
+
...this.parent.row(this.parentOf(i)).toJSON(),
|
|
291
|
+
start: this.start[i],
|
|
292
|
+
end: this.end[i],
|
|
293
|
+
state: CELL_STATES[this.state[i]],
|
|
294
|
+
};
|
|
295
|
+
for (const name of ['base', 'match', 'length']) {
|
|
296
|
+
const v = valueAt(this.column(name), i);
|
|
297
|
+
if (v !== undefined) {
|
|
298
|
+
out[name] = v;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return out;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// The lanes a `cells` walk writes, doubled as a row would overrun them.
|
|
305
|
+
class RunLanes {
|
|
306
|
+
start;
|
|
307
|
+
end;
|
|
308
|
+
state;
|
|
309
|
+
textAt;
|
|
310
|
+
parentRow;
|
|
311
|
+
length = 0;
|
|
312
|
+
constructor(capacity) {
|
|
313
|
+
this.start = new Uint32Array(capacity);
|
|
314
|
+
this.end = new Uint32Array(capacity);
|
|
315
|
+
this.state = new Uint8Array(capacity);
|
|
316
|
+
this.textAt = new Uint32Array(capacity);
|
|
317
|
+
this.parentRow = new Uint32Array(capacity);
|
|
318
|
+
}
|
|
319
|
+
reserve(more) {
|
|
320
|
+
const need = this.length + more;
|
|
321
|
+
if (need <= this.start.length) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
const size = Math.max(this.start.length * 2, need);
|
|
325
|
+
const widen = (a) => {
|
|
326
|
+
const next = new a.constructor(size);
|
|
327
|
+
next.set(a);
|
|
328
|
+
return next;
|
|
329
|
+
};
|
|
330
|
+
this.start = widen(this.start);
|
|
331
|
+
this.end = widen(this.end);
|
|
332
|
+
this.state = widen(this.state);
|
|
333
|
+
this.textAt = widen(this.textAt);
|
|
334
|
+
this.parentRow = widen(this.parentRow);
|
|
335
|
+
}
|
|
336
|
+
// One row's runs against its reference: a run per column is the most it
|
|
337
|
+
// can answer, so the lanes grow once and the walk writes through locals.
|
|
338
|
+
walk(r, startPos, rowBytes, rowAt, rowLen, refBytes, refAt, refLen) {
|
|
339
|
+
this.reserve(refLen + 1);
|
|
340
|
+
const { start, end, state, textAt, parentRow } = this;
|
|
341
|
+
let n = this.length;
|
|
342
|
+
let pos = startPos;
|
|
343
|
+
const first = firstDrawn(rowBytes, rowAt, rowLen);
|
|
344
|
+
const last = lastDrawn(rowBytes, rowAt, rowLen, first);
|
|
345
|
+
let runStart = -1;
|
|
346
|
+
let runState = NO_STATE;
|
|
347
|
+
let openKey = -1;
|
|
348
|
+
let runCol = 0;
|
|
349
|
+
let insertAt = -1;
|
|
350
|
+
for (let col = 0; col < refLen; col++) {
|
|
351
|
+
const refByte = refBytes[refAt + col];
|
|
352
|
+
const rowByte = col < rowLen ? rowBytes[rowAt + col] : SPACE;
|
|
353
|
+
if (refByte === DASH) {
|
|
354
|
+
if (insertAt < 0 && col < rowLen && !isGapByte(rowByte)) {
|
|
355
|
+
insertAt = col;
|
|
356
|
+
}
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
if (insertAt >= 0) {
|
|
360
|
+
start[n] = pos;
|
|
361
|
+
end[n] = pos;
|
|
362
|
+
state[n] = INSERTION;
|
|
363
|
+
textAt[n] = insertAt;
|
|
364
|
+
parentRow[n] = r;
|
|
365
|
+
n++;
|
|
366
|
+
insertAt = -1;
|
|
367
|
+
}
|
|
368
|
+
const st = cellState(refByte, rowByte, col >= first && col <= last && col < rowLen);
|
|
369
|
+
const key = runKey(st, refByte);
|
|
370
|
+
if (opensRun(st, key, runState, openKey)) {
|
|
371
|
+
if (runStart >= 0) {
|
|
372
|
+
start[n] = runStart;
|
|
373
|
+
end[n] = pos;
|
|
374
|
+
state[n] = runState;
|
|
375
|
+
textAt[n] = runCol;
|
|
376
|
+
parentRow[n] = r;
|
|
377
|
+
n++;
|
|
378
|
+
}
|
|
379
|
+
runStart = st === NO_STATE ? -1 : pos;
|
|
380
|
+
runState = st;
|
|
381
|
+
openKey = key;
|
|
382
|
+
runCol = col;
|
|
383
|
+
}
|
|
384
|
+
pos++;
|
|
385
|
+
}
|
|
386
|
+
if (runStart >= 0) {
|
|
387
|
+
start[n] = runStart;
|
|
388
|
+
end[n] = pos;
|
|
389
|
+
state[n] = runState;
|
|
390
|
+
textAt[n] = runCol;
|
|
391
|
+
parentRow[n] = r;
|
|
392
|
+
n++;
|
|
393
|
+
}
|
|
394
|
+
this.length = n;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
// Where each parent row's children start, for children written parent by
|
|
398
|
+
// parent in order.
|
|
399
|
+
function firstRowIndex(parentRow, parents) {
|
|
400
|
+
const first = new Uint32Array(parents);
|
|
401
|
+
for (let i = parentRow.length - 1; i >= 0; i--) {
|
|
402
|
+
first[parentRow[i]] = i;
|
|
403
|
+
}
|
|
404
|
+
return first;
|
|
405
|
+
}
|
|
406
|
+
export function cells({ table, bounds }, step) {
|
|
407
|
+
const { field = DEFAULT_CELLS_FIELD } = step;
|
|
408
|
+
const texts = walkedTexts(table, field);
|
|
409
|
+
const readStart = numberReaderOf(table.column('start'));
|
|
410
|
+
const { bytes: rowBytes, offset: rowOffset, length: rowLength } = texts.row;
|
|
411
|
+
const { bytes: refBytes, offset: refOffset, length: refLength, } = texts.reference;
|
|
412
|
+
// A run per reference base is the most a row can answer and far past what
|
|
413
|
+
// one does, so the lanes start at an eighth of it.
|
|
414
|
+
let bound = 0;
|
|
415
|
+
for (let r = 0; r < table.length; r++) {
|
|
416
|
+
bound += refLength[texts.refOf(r)] + 1;
|
|
417
|
+
}
|
|
418
|
+
const lanes = new RunLanes(Math.max(1024, bound >>> 3));
|
|
419
|
+
for (let r = 0; r < table.length; r++) {
|
|
420
|
+
const f = texts.refOf(r);
|
|
421
|
+
const refLen = refLength[f];
|
|
422
|
+
const pos = readStart(r);
|
|
423
|
+
if (refLen > 0 && pos >= 0) {
|
|
424
|
+
const k = texts.rowOf(r);
|
|
425
|
+
lanes.walk(r, pos, rowBytes, rowOffset[k], rowLength[k], refBytes, refOffset[f], refLen);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
const n = lanes.length;
|
|
429
|
+
const rows = lanes.parentRow.subarray(0, n);
|
|
430
|
+
return {
|
|
431
|
+
table: new CellTable(table, rows, {
|
|
432
|
+
start: lanes.start.subarray(0, n),
|
|
433
|
+
end: lanes.end.subarray(0, n),
|
|
434
|
+
state: lanes.state.subarray(0, n),
|
|
435
|
+
textAt: lanes.textAt.subarray(0, n),
|
|
436
|
+
}, texts),
|
|
437
|
+
bounds: boundsThrough(bounds, rows),
|
|
438
|
+
};
|
|
439
|
+
}
|
|
@@ -239,16 +239,27 @@ export function parseColor(color) {
|
|
|
239
239
|
}
|
|
240
240
|
throw new Error(`Color.parse(): invalid CSS color: "${color}"`);
|
|
241
241
|
}
|
|
242
|
+
/** A channel's leading number, throwing where it has none, as `rgb(a,b,1)`. */
|
|
243
|
+
function parseNumber(value) {
|
|
244
|
+
const n = Number.parseFloat(value);
|
|
245
|
+
if (Number.isNaN(n)) {
|
|
246
|
+
throw new TypeError(`Color.parse(): invalid CSS color channel: "${value}"`);
|
|
247
|
+
}
|
|
248
|
+
return n;
|
|
249
|
+
}
|
|
242
250
|
/**
|
|
243
|
-
* Accepts: "50%", "128"
|
|
251
|
+
* Accepts: "50%", "128", "none"
|
|
244
252
|
* https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb#values
|
|
245
253
|
* @returns a value in the 0 to 255 range
|
|
246
254
|
*/
|
|
247
255
|
function parseColorChannel(channel) {
|
|
256
|
+
if (channel === 'none') {
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
248
259
|
if (channel.charCodeAt(channel.length - 1) === PERCENT) {
|
|
249
|
-
return Math.round((
|
|
260
|
+
return Math.round((parseNumber(channel) / 100) * 255);
|
|
250
261
|
}
|
|
251
|
-
return Math.round(
|
|
262
|
+
return Math.round(parseNumber(channel));
|
|
252
263
|
}
|
|
253
264
|
/**
|
|
254
265
|
* Accepts: "50%", ".5", "0.5"
|
|
@@ -268,9 +279,9 @@ function parseAlphaValue(channel) {
|
|
|
268
279
|
return 0;
|
|
269
280
|
}
|
|
270
281
|
if (channel.charCodeAt(channel.length - 1) === PERCENT) {
|
|
271
|
-
return
|
|
282
|
+
return parseNumber(channel) / 100;
|
|
272
283
|
}
|
|
273
|
-
return
|
|
284
|
+
return parseNumber(channel);
|
|
274
285
|
}
|
|
275
286
|
/**
|
|
276
287
|
* Accepts: "360", "360deg", "400grad", "6.28rad", "1turn", "none"
|
|
@@ -302,7 +313,7 @@ function parseAngle(angle) {
|
|
|
302
313
|
factor = 360;
|
|
303
314
|
}
|
|
304
315
|
}
|
|
305
|
-
const turns =
|
|
316
|
+
const turns = parseNumber(angle) / factor;
|
|
306
317
|
// Hue is periodic, and every caller of this is a hue. `hueToRGB` corrects its
|
|
307
318
|
// argument by at most one turn, so it reads `hsl(-720deg)` as a hue two turns
|
|
308
319
|
// below the wheel rather than as red — folding here is what makes the three
|
|
@@ -317,7 +328,7 @@ function parsePercentage(value) {
|
|
|
317
328
|
if (value.charCodeAt(0) === N) {
|
|
318
329
|
return 0;
|
|
319
330
|
}
|
|
320
|
-
return
|
|
331
|
+
return parseNumber(value) / 100;
|
|
321
332
|
}
|
|
322
333
|
/**
|
|
323
334
|
* Accepts: "1.0", "100%", "none"
|
|
@@ -328,9 +339,9 @@ function parsePercentageOrValue(value) {
|
|
|
328
339
|
return 0;
|
|
329
340
|
}
|
|
330
341
|
if (value.charCodeAt(value.length - 1) === PERCENT) {
|
|
331
|
-
return
|
|
342
|
+
return parseNumber(value) / 100;
|
|
332
343
|
}
|
|
333
|
-
return
|
|
344
|
+
return parseNumber(value);
|
|
334
345
|
}
|
|
335
346
|
/**
|
|
336
347
|
* Accepts: "100", "100%", "none"
|
|
@@ -341,9 +352,9 @@ function parsePercentageFor(value, range) {
|
|
|
341
352
|
return 0;
|
|
342
353
|
}
|
|
343
354
|
if (value.charCodeAt(value.length - 1) === PERCENT) {
|
|
344
|
-
return (
|
|
355
|
+
return (parseNumber(value) / 100) * range;
|
|
345
356
|
}
|
|
346
|
-
return
|
|
357
|
+
return parseNumber(value);
|
|
347
358
|
}
|
|
348
359
|
// HSL functions
|
|
349
360
|
function hueToRGB(p, q, t) {
|
package/esm/util/colorRamp.js
CHANGED
|
@@ -90,6 +90,11 @@ const PURPLEORANGE_STOPS = [
|
|
|
90
90
|
[179, 88, 6, 255],
|
|
91
91
|
[127, 59, 8, 255],
|
|
92
92
|
];
|
|
93
|
+
const REDGREYBLUE_STOPS = [
|
|
94
|
+
[199, 67, 56, 255],
|
|
95
|
+
[140, 140, 140, 255],
|
|
96
|
+
[47, 102, 176, 255],
|
|
97
|
+
];
|
|
93
98
|
const SCHEME_STOPS = {
|
|
94
99
|
viridis: VIRIDIS_STOPS,
|
|
95
100
|
juicebox: JUICEBOX_STOPS,
|
|
@@ -101,6 +106,7 @@ const SCHEME_STOPS = {
|
|
|
101
106
|
cividis: stopsFromHex(CIVIDIS_HEX_SPEC),
|
|
102
107
|
redblue: REDBLUE_STOPS,
|
|
103
108
|
purpleorange: PURPLEORANGE_STOPS,
|
|
109
|
+
redgreyblue: REDGREYBLUE_STOPS,
|
|
104
110
|
};
|
|
105
111
|
function luminanceOverWhite([r, g, b, a]) {
|
|
106
112
|
const over = (c) => 255 + (c - 255) * (a / 255);
|
package/esm/util/colorScale.d.ts
CHANGED
|
@@ -36,8 +36,8 @@ export interface FieldPreset<S extends string = ColorScaleName> {
|
|
|
36
36
|
export type FieldPresets<S extends string = ColorScaleName> = Readonly<Record<string, FieldPreset<S>>>;
|
|
37
37
|
/**
|
|
38
38
|
* The fields whose values have names, an order and colours of their own on
|
|
39
|
-
* every display, beneath each display's own presets.
|
|
40
|
-
*
|
|
39
|
+
* every display, beneath each display's own presets. The synteny ribbons
|
|
40
|
+
* paint strand from this range too. The SV classes are
|
|
41
41
|
* `svClassOf`'s, deletion red and duplication blue as dbVar and gnomAD-SV
|
|
42
42
|
* paint them, insertion the pileup's purple, and the rest kept apart under
|
|
43
43
|
* deuteranopia and protanopia. A record with no class files under `''`, so
|
|
@@ -143,6 +143,13 @@ export interface ColorSlots {
|
|
|
143
143
|
domainQuantile?: number;
|
|
144
144
|
labels?: readonly unknown[];
|
|
145
145
|
}
|
|
146
|
+
/** A quantitative scale's ends among the members `member` reads, each a number or unset. */
|
|
147
|
+
export declare function scaleEndsOf(member: (name: string) => unknown): ScaleEnds;
|
|
148
|
+
/**
|
|
149
|
+
* The members `colorProblems` reads among the ones `member` reads, each of the
|
|
150
|
+
* type it reads or unset, so a config snapshot and a config node answer alike.
|
|
151
|
+
*/
|
|
152
|
+
export declare function colorSlotsOf(member: (name: string) => unknown): ColorSlots;
|
|
146
153
|
/**
|
|
147
154
|
* What a colour object's slots say together that no single slot can refuse,
|
|
148
155
|
* since the config editor writes one slot at a time (ADR-133), read with its
|
package/esm/util/colorScale.js
CHANGED
|
@@ -20,8 +20,8 @@ export const IDENTITY_SCALE = 'identity';
|
|
|
20
20
|
const CATEGORICAL_PRESET = { scale: 'categorical' };
|
|
21
21
|
/**
|
|
22
22
|
* The fields whose values have names, an order and colours of their own on
|
|
23
|
-
* every display, beneath each display's own presets.
|
|
24
|
-
*
|
|
23
|
+
* every display, beneath each display's own presets. The synteny ribbons
|
|
24
|
+
* paint strand from this range too. The SV classes are
|
|
25
25
|
* `svClassOf`'s, deletion red and duplication blue as dbVar and gnomAD-SV
|
|
26
26
|
* paint them, insertion the pileup's purple, and the rest kept apart under
|
|
27
27
|
* deuteranopia and protanopia. A record with no class files under `''`, so
|
|
@@ -165,6 +165,37 @@ export function scaleEndProblems({ domainMin, domainMax, domainQuantile }, { rev
|
|
|
165
165
|
export function noticeLines(setting, problems) {
|
|
166
166
|
return problems.map(({ slot, message }) => `${setting}.${slot}: ${message}`);
|
|
167
167
|
}
|
|
168
|
+
function numberOf(value) {
|
|
169
|
+
return typeof value === 'number' ? value : undefined;
|
|
170
|
+
}
|
|
171
|
+
function stringOf(value) {
|
|
172
|
+
return typeof value === 'string' ? value : undefined;
|
|
173
|
+
}
|
|
174
|
+
function listOf(value) {
|
|
175
|
+
return Array.isArray(value) ? value : undefined;
|
|
176
|
+
}
|
|
177
|
+
/** A quantitative scale's ends among the members `member` reads, each a number or unset. */
|
|
178
|
+
export function scaleEndsOf(member) {
|
|
179
|
+
return {
|
|
180
|
+
domainMin: numberOf(member('domainMin')),
|
|
181
|
+
domainMax: numberOf(member('domainMax')),
|
|
182
|
+
domainQuantile: numberOf(member('domainQuantile')),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* The members `colorProblems` reads among the ones `member` reads, each of the
|
|
187
|
+
* type it reads or unset, so a config snapshot and a config node answer alike.
|
|
188
|
+
*/
|
|
189
|
+
export function colorSlotsOf(member) {
|
|
190
|
+
return {
|
|
191
|
+
...scaleEndsOf(member),
|
|
192
|
+
field: stringOf(member('field')) ?? '',
|
|
193
|
+
scale: stringOf(member('scale')),
|
|
194
|
+
domain: listOf(member('domain')),
|
|
195
|
+
range: listOf(member('range')),
|
|
196
|
+
labels: listOf(member('labels')),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
168
199
|
function pinned(entry) {
|
|
169
200
|
return entry !== '' && Number.isFinite(Number(entry));
|
|
170
201
|
}
|
|
@@ -7,9 +7,11 @@
|
|
|
7
7
|
* transparent to red, as Juicebox paints contacts; `fall` runs white through
|
|
8
8
|
* yellow and red to black, as HiGlass does; `reds` and `blues` are
|
|
9
9
|
* ColorBrewer's, from white; `redblue` and `purpleorange` diverge through
|
|
10
|
-
* white, ColorBrewer's RdBu and PuOr
|
|
10
|
+
* white, ColorBrewer's RdBu and PuOr; `redgreyblue` diverges through a grey
|
|
11
|
+
* middle, which stays visible on a white page where a white middle would not,
|
|
12
|
+
* as the MAF display's identity ramp paints it.
|
|
11
13
|
*/
|
|
12
|
-
export declare const COLOR_SCHEMES: readonly ['viridis', 'magma', 'inferno', 'cividis', 'juicebox', 'fall', 'reds', 'blues', 'redblue', 'purpleorange'];
|
|
14
|
+
export declare const COLOR_SCHEMES: readonly ['viridis', 'magma', 'inferno', 'cividis', 'juicebox', 'fall', 'reds', 'blues', 'redblue', 'purpleorange', 'redgreyblue'];
|
|
13
15
|
/** #api */
|
|
14
16
|
export type ColorSchemeName = (typeof COLOR_SCHEMES)[number];
|
|
15
17
|
/**
|
package/esm/util/colorSchemes.js
CHANGED
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* transparent to red, as Juicebox paints contacts; `fall` runs white through
|
|
8
8
|
* yellow and red to black, as HiGlass does; `reds` and `blues` are
|
|
9
9
|
* ColorBrewer's, from white; `redblue` and `purpleorange` diverge through
|
|
10
|
-
* white, ColorBrewer's RdBu and PuOr
|
|
10
|
+
* white, ColorBrewer's RdBu and PuOr; `redgreyblue` diverges through a grey
|
|
11
|
+
* middle, which stays visible on a white page where a white middle would not,
|
|
12
|
+
* as the MAF display's identity ramp paints it.
|
|
11
13
|
*/
|
|
12
14
|
export const COLOR_SCHEMES = [
|
|
13
15
|
'viridis',
|
|
@@ -20,6 +22,7 @@ export const COLOR_SCHEMES = [
|
|
|
20
22
|
'blues',
|
|
21
23
|
'redblue',
|
|
22
24
|
'purpleorange',
|
|
25
|
+
'redgreyblue',
|
|
23
26
|
];
|
|
24
27
|
/**
|
|
25
28
|
* #api
|
|
@@ -7,7 +7,14 @@ export default async function copyToClipboard(text, options = {}) {
|
|
|
7
7
|
// navigator.clipboard is only present in secure contexts; isSecureContext is
|
|
8
8
|
// the real gate, so insecure http:// falls through to execCommand below
|
|
9
9
|
if (format === 'text/plain' && window.isSecureContext) {
|
|
10
|
-
|
|
10
|
+
try {
|
|
11
|
+
await navigator.clipboard.writeText(text);
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
if (!execCommandCopy(text, format)) {
|
|
15
|
+
throw e;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
11
18
|
}
|
|
12
19
|
else if (!execCommandCopy(text, format)) {
|
|
13
20
|
throw new Error('the browser rejected the clipboard write');
|
|
@@ -81,9 +81,9 @@ export function createAbortRotation(self, report) {
|
|
|
81
81
|
// Opened before the superseded slot retires, so the window is never idle
|
|
82
82
|
// across the handover and the label on screen survives it (ADR-080).
|
|
83
83
|
// Retired here rather than left to the superseded run's `finally`, which
|
|
84
|
-
// comes only when its work settles — and a CRAM record read or a shared
|
|
85
|
-
// parse the
|
|
86
|
-
// reading summed into this fetch's bar the whole time.
|
|
84
|
+
// comes only when its work settles — and a CRAM record read, or a shared
|
|
85
|
+
// `cachedSetup` parse the replacement rejoins, runs on past the abort,
|
|
86
|
+
// its last reading summed into this fetch's bar the whole time.
|
|
87
87
|
const stream = statusWindow.open({ isCurrent });
|
|
88
88
|
openStream?.clear();
|
|
89
89
|
openStream = stream;
|