@jbrowse/core 5.0.0-beta.11 → 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 +97 -75
- package/esm/BaseFeatureWidget/BaseFeatureDetail/Attributes.d.ts +3 -0
- package/esm/BaseFeatureWidget/BaseFeatureDetail/Attributes.js +14 -1
- 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/stateModelFactory.d.ts +7 -0
- package/esm/BaseFeatureWidget/stateModelFactory.js +11 -0
- package/esm/BaseFeatureWidget/types.d.ts +2 -0
- package/esm/PluginManager.d.ts +6 -3
- package/esm/PluginManager.js +6 -3
- package/esm/ReExports/coreModules.generated.js +291 -285
- package/esm/ReExports/coreWorkerModules.generated.js +288 -283
- package/esm/ReExports/list.js +7 -0
- 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 +3 -2
- package/esm/configuration/index.js +2 -1
- package/esm/configuration/partitionAdvanced.d.ts +12 -0
- package/esm/configuration/partitionAdvanced.js +42 -0
- package/esm/configuration/plot.d.ts +6 -0
- package/esm/configuration/plot.js +34 -1
- 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 +7 -0
- package/esm/pluggableElementTypes/DisplayType.js +7 -0
- package/esm/pluggableElementTypes/RpcMethodType.d.ts +7 -0
- package/esm/pluggableElementTypes/RpcMethodType.js +9 -0
- package/esm/pluggableElementTypes/addMenuItems.d.ts +2 -1
- package/esm/pluggableElementTypes/addMenuItems.js +2 -1
- package/esm/pluggableElementTypes/models/BaseDisplayModel.d.ts +3 -2
- package/esm/pluggableElementTypes/models/BaseDisplayModel.js +5 -5
- package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +2 -4
- package/esm/pluggableElementTypes/models/BaseTrackModel.js +2 -4
- 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/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/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/bezierConnector.js +5 -1
- package/esm/util/categoricalField.d.ts +23 -3
- package/esm/util/categoricalField.js +36 -3
- package/esm/util/cellMatches.js +14 -14
- package/esm/util/cellsStep.d.ts +3 -5
- package/esm/util/cellsStep.js +31 -29
- 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/isoformRank.d.ts +39 -0
- package/esm/util/isoformRank.js +118 -0
- package/esm/util/markEncodingTypes.d.ts +3 -1
- package/esm/util/quantileExtent.d.ts +22 -0
- package/esm/util/quantileExtent.js +24 -1
- package/esm/util/systemColorScheme.d.ts +4 -0
- package/esm/util/systemColorScheme.js +21 -0
- package/esm/util/tabix.d.ts +1 -1
- package/esm/util/tabix.js +1 -1
- 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 +25 -15
- package/esm/util/tracks.js +67 -43
- package/esm/util/types/index.d.ts +15 -7
- package/esm/util/types/index.js +21 -2
- package/esm/util/useRowVirtualScroll.d.ts +1 -0
- package/package.json +7 -4
- package/esm/rpc/methods/CoreLoadAdapterCode.d.ts +0 -13
- package/esm/rpc/methods/CoreLoadAdapterCode.js +0 -16
package/esm/util/analytics.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { effectiveRenderer, getGraphicsCapabilities, } from '@jbrowse/render-core/graphicsCapabilities';
|
|
2
2
|
import { readConfObject } from '../configuration/index.js';
|
|
3
3
|
import { isElectron, rIC } from '../util/index.js';
|
|
4
|
+
import { getConfAssemblyNamesOrNone } from './tracks.js';
|
|
4
5
|
// Both writers are fire-and-forget from several call sites, so they swallow
|
|
5
6
|
// their own failures rather than rejecting: a floating rejected promise trips
|
|
6
7
|
// the webpack-dev-server overlay (it listens for unhandledrejection), and an
|
|
@@ -8,7 +9,7 @@ import { isElectron, rIC } from '../util/index.js';
|
|
|
8
9
|
export async function writeAWSAnalytics(rootModel, initialTimestamp, sessionQuery) {
|
|
9
10
|
try {
|
|
10
11
|
const url = 'https://analytics.jbrowse.org/api/v1';
|
|
11
|
-
const multiAssemblyTracks = rootModel.jbrowse.tracks.filter(track =>
|
|
12
|
+
const multiAssemblyTracks = rootModel.jbrowse.tracks.filter(track => getConfAssemblyNamesOrNone(track).length > 1).length;
|
|
12
13
|
// The rung createGpuHal built where a display has built one by idle time,
|
|
13
14
|
// else the ladder's prediction with any page-wide pin applied — so a
|
|
14
15
|
// session that fell back to Canvas2D is counted as Canvas2D rather than as
|
|
@@ -138,7 +139,8 @@ export function doAnalytics(rootModel, initialTimestamp, initialSessionQuery) {
|
|
|
138
139
|
// `[data-app-phase="ready"]`, or a timeout for a page that never shows one
|
|
139
140
|
// (Desktop's start screen)
|
|
140
141
|
function afterAppReady(callback, maxWaitMs = 10_000) {
|
|
141
|
-
const
|
|
142
|
+
const doc = document;
|
|
143
|
+
const isReady = () => doc.querySelector('[data-app-phase="ready"]') !== null;
|
|
142
144
|
if (isReady()) {
|
|
143
145
|
callback();
|
|
144
146
|
return;
|
|
@@ -158,7 +160,7 @@ function afterAppReady(callback, maxWaitMs = 10_000) {
|
|
|
158
160
|
}
|
|
159
161
|
});
|
|
160
162
|
const timer = setTimeout(finish, maxWaitMs);
|
|
161
|
-
observer.observe(
|
|
163
|
+
observer.observe(doc.body, {
|
|
162
164
|
subtree: true,
|
|
163
165
|
childList: true,
|
|
164
166
|
attributeFilter: ['data-app-phase'],
|
|
@@ -19,6 +19,9 @@ const SPAN_FACTOR = 0.3;
|
|
|
19
19
|
// its leading end: unbounded, that hook grows with the arc, and a full-width
|
|
20
20
|
// inversion flings a ~500px curl back across unrelated reads.
|
|
21
21
|
const MAX_HANDLE_PX = 200;
|
|
22
|
+
// The handles are horizontal, so a tall vertical gap (stacked split-view
|
|
23
|
+
// panels) only widens the sideways bulge; past this height it earns no more.
|
|
24
|
+
const MAX_VERTICAL_SPAN_PX = 150;
|
|
22
25
|
// A symmetric cubic's extreme sits 3/4 of the way from its baseline to its
|
|
23
26
|
// control points, so an arc asked to reach a given apex puts its controls 4/3
|
|
24
27
|
// past it. Exported because the sashimi overlay solves the same equation in the
|
|
@@ -62,7 +65,8 @@ function tangentSign(strand, leading, reversed) {
|
|
|
62
65
|
// How much shaping (handle length, bow height) this curve may spend: a fraction
|
|
63
66
|
// of the distance it has to cover.
|
|
64
67
|
function shapingBudget(p1, p2) {
|
|
65
|
-
|
|
68
|
+
const dy = Math.min(Math.abs(p2.y - p1.y), MAX_VERTICAL_SPAN_PX);
|
|
69
|
+
return Math.hypot(p2.x - p1.x, dy) * SPAN_FACTOR;
|
|
66
70
|
}
|
|
67
71
|
// Fades to zero as the endpoints' rows separate, since a cross-row curve
|
|
68
72
|
// already has vertical extent — and via the budget as they close in
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { HeldSlots } from '../ui/colors.ts';
|
|
1
2
|
/** #api */
|
|
2
3
|
export declare const STRAND_FIELD = "strand";
|
|
3
4
|
/**
|
|
@@ -19,7 +20,9 @@ export declare const NO_VALUE_LABEL = "(no value)";
|
|
|
19
20
|
* orders keys (the `domain` first, the rest by `compareGroupKeys`, `''`
|
|
20
21
|
* after them), `label` names a key in a legend, `sectionLabel` on a chip, and
|
|
21
22
|
* `color` paints it. A key's color depends only on the key and the
|
|
22
|
-
* declaration, so every region agrees on it
|
|
23
|
+
* declaration, so every region agrees on it, except that a field dealing its
|
|
24
|
+
* colors (`categoricalField`'s `held`) also steps a key off the colors of the
|
|
25
|
+
* keys met before it.
|
|
23
26
|
*/
|
|
24
27
|
export interface CategoricalField {
|
|
25
28
|
field: string;
|
|
@@ -47,10 +50,27 @@ export declare function keyNames(keys: readonly unknown[], labels?: readonly str
|
|
|
47
50
|
/**
|
|
48
51
|
* #api
|
|
49
52
|
* `labels` names the `domain`'s values in a key, one each in order, where a
|
|
50
|
-
* config spells them for a reader rather than as the data does.
|
|
53
|
+
* config spells them for a reader rather than as the data does. `held` deals
|
|
54
|
+
* each key the domain does not list a color of its own on first sight
|
|
55
|
+
* (`categoricalScale`); the caller keeps it for as long as the colors should
|
|
56
|
+
* hold.
|
|
51
57
|
*/
|
|
52
|
-
export declare function categoricalField(field: string, { domain, range, labels, }?: {
|
|
58
|
+
export declare function categoricalField(field: string, { domain, range, labels, held, }?: {
|
|
53
59
|
domain?: readonly string[];
|
|
54
60
|
range?: readonly string[];
|
|
55
61
|
labels?: readonly string[];
|
|
62
|
+
held?: HeldSlots;
|
|
56
63
|
}): CategoricalField;
|
|
64
|
+
/**
|
|
65
|
+
* #api
|
|
66
|
+
* Deals `keys` their colours in `field.compare` order, so the colours a set
|
|
67
|
+
* of keys first seen together takes do not depend on the order they arrive
|
|
68
|
+
* in. A no-op for a field that deals nothing.
|
|
69
|
+
*/
|
|
70
|
+
export declare function dealKeyColors(field: CategoricalField, keys: Iterable<string>): void;
|
|
71
|
+
/**
|
|
72
|
+
* #api
|
|
73
|
+
* The `HeldSlots` `owner` keeps under `key`, made empty on first ask. Lives as
|
|
74
|
+
* long as `owner` and is never persisted, so a reload deals afresh.
|
|
75
|
+
*/
|
|
76
|
+
export declare function heldSlotsOf(owner: object, key: string): HeldSlots;
|
|
@@ -37,9 +37,12 @@ export function keyNames(keys, labels = []) {
|
|
|
37
37
|
/**
|
|
38
38
|
* #api
|
|
39
39
|
* `labels` names the `domain`'s values in a key, one each in order, where a
|
|
40
|
-
* config spells them for a reader rather than as the data does.
|
|
40
|
+
* config spells them for a reader rather than as the data does. `held` deals
|
|
41
|
+
* each key the domain does not list a color of its own on first sight
|
|
42
|
+
* (`categoricalScale`); the caller keeps it for as long as the colors should
|
|
43
|
+
* hold.
|
|
41
44
|
*/
|
|
42
|
-
export function categoricalField(field, { domain = [], range = [], labels = [], } = {}) {
|
|
45
|
+
export function categoricalField(field, { domain = [], range = [], labels = [], held, } = {}) {
|
|
43
46
|
// a field with a vocabulary of its own reads it on every channel, a facet's
|
|
44
47
|
// sections as well as a colour's key
|
|
45
48
|
const vocabulary = universalPresetOf(field);
|
|
@@ -66,8 +69,38 @@ export function categoricalField(field, { domain = [], range = [], labels = [],
|
|
|
66
69
|
if (key === '') {
|
|
67
70
|
return NO_CATEGORY_COLOR;
|
|
68
71
|
}
|
|
69
|
-
colorOf ??= categoricalColorScale(paired, colors);
|
|
72
|
+
colorOf ??= categoricalColorScale(paired, colors, held);
|
|
70
73
|
return colorOf(key);
|
|
71
74
|
},
|
|
72
75
|
};
|
|
73
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* #api
|
|
79
|
+
* Deals `keys` their colours in `field.compare` order, so the colours a set
|
|
80
|
+
* of keys first seen together takes do not depend on the order they arrive
|
|
81
|
+
* in. A no-op for a field that deals nothing.
|
|
82
|
+
*/
|
|
83
|
+
export function dealKeyColors(field, keys) {
|
|
84
|
+
for (const key of [...new Set(keys)].sort(field.compare)) {
|
|
85
|
+
field.color(key);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const heldByOwner = new WeakMap();
|
|
89
|
+
/**
|
|
90
|
+
* #api
|
|
91
|
+
* The `HeldSlots` `owner` keeps under `key`, made empty on first ask. Lives as
|
|
92
|
+
* long as `owner` and is never persisted, so a reload deals afresh.
|
|
93
|
+
*/
|
|
94
|
+
export function heldSlotsOf(owner, key) {
|
|
95
|
+
let byKey = heldByOwner.get(owner);
|
|
96
|
+
if (byKey === undefined) {
|
|
97
|
+
byKey = new Map();
|
|
98
|
+
heldByOwner.set(owner, byKey);
|
|
99
|
+
}
|
|
100
|
+
let held = byKey.get(key);
|
|
101
|
+
if (held === undefined) {
|
|
102
|
+
held = new Map();
|
|
103
|
+
byKey.set(key, held);
|
|
104
|
+
}
|
|
105
|
+
return held;
|
|
106
|
+
}
|
package/esm/util/cellMatches.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// A `cells` step, the interval `bin` behind it and the `aggregate` of its
|
|
2
2
|
// matches as one walk over the rows' bytes, which `runSteps` in
|
|
3
3
|
// featureTransforms.ts reaches by relative path only.
|
|
4
|
+
import { DASH, SPACE, firstDrawn, isGapByte, lastDrawn, } from './alignedBytes.js';
|
|
4
5
|
import { fusesBinAggregate } from './binnedAggregate.js';
|
|
5
|
-
import {
|
|
6
|
+
import { DEFAULT_CELLS_FIELD, MATCH, NO_STATE, cellState, comparesBase, opensRun, runKey, walkedTexts, } from './cellsStep.js';
|
|
6
7
|
import { numberReaderOf, readerOf } from './featureTable.js';
|
|
7
8
|
import { BIN_OVERLAP_FIELD, DEFAULT_BIN_AS, OpSums, groupKey, madeGroups, } from './stepTables.js';
|
|
8
9
|
const isEdges = (pair) => pair?.length === 2 && pair[0] === 'start' && pair[1] === 'end';
|
|
@@ -97,10 +98,10 @@ class SectionBins {
|
|
|
97
98
|
}
|
|
98
99
|
/**
|
|
99
100
|
* 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
|
|
101
|
-
* column
|
|
102
|
-
* ahead of the run still open. The bases a run
|
|
103
|
-
*
|
|
101
|
+
* step cuts it and in the order the lanes hold them: a run where the next
|
|
102
|
+
* column `opensRun`, an insertion at the reference base after it,
|
|
103
|
+
* ahead of the run still open. The compared bases a run holds are counted
|
|
104
|
+
* in their bins as the walk passes them.
|
|
104
105
|
*/
|
|
105
106
|
walk(r, startPos, size, rowBytes, rowAt, rowLen, refBytes, refAt, refLen) {
|
|
106
107
|
const { matched, compared, low } = this;
|
|
@@ -114,7 +115,7 @@ class SectionBins {
|
|
|
114
115
|
let runOpen = false;
|
|
115
116
|
let runBin = 0;
|
|
116
117
|
let runState = NO_STATE;
|
|
117
|
-
let
|
|
118
|
+
let openKey = -1;
|
|
118
119
|
let inserting = false;
|
|
119
120
|
for (let col = 0; col < refLen; col++) {
|
|
120
121
|
const refByte = refBytes[refAt + col];
|
|
@@ -130,22 +131,21 @@ class SectionBins {
|
|
|
130
131
|
inserting = false;
|
|
131
132
|
}
|
|
132
133
|
const st = cellState(refByte, rowByte, col >= first && col <= last && col < rowLen);
|
|
133
|
-
const
|
|
134
|
-
if (st
|
|
134
|
+
const key = runKey(st, refByte);
|
|
135
|
+
if (opensRun(st, key, runState, openKey)) {
|
|
135
136
|
if (runOpen) {
|
|
136
137
|
this.touch(runBin, pos > edge - size ? bin : bin - 1, r);
|
|
137
138
|
}
|
|
138
139
|
runOpen = st !== NO_STATE;
|
|
139
140
|
runBin = bin;
|
|
140
141
|
runState = st;
|
|
141
|
-
|
|
142
|
+
openKey = key;
|
|
142
143
|
}
|
|
143
|
-
if (st
|
|
144
|
-
m++;
|
|
145
|
-
c++;
|
|
146
|
-
}
|
|
147
|
-
else if (st === MISMATCH) {
|
|
144
|
+
if (comparesBase(st, refByte)) {
|
|
148
145
|
c++;
|
|
146
|
+
if (st === MATCH) {
|
|
147
|
+
m++;
|
|
148
|
+
}
|
|
149
149
|
}
|
|
150
150
|
pos++;
|
|
151
151
|
if (pos === edge) {
|
package/esm/util/cellsStep.d.ts
CHANGED
|
@@ -2,15 +2,13 @@ import type { FeatureTable, TextColumn } from './featureTable.ts';
|
|
|
2
2
|
import type { CellsStep } from './markEncodingTypes.ts';
|
|
3
3
|
import type { Staged } from './stepTables.ts';
|
|
4
4
|
export declare const DEFAULT_CELLS_FIELD = "seq";
|
|
5
|
-
export declare const DASH = 45;
|
|
6
|
-
export declare const SPACE = 32;
|
|
7
5
|
export declare const MATCH = 0;
|
|
8
6
|
export declare const MISMATCH = 1;
|
|
9
7
|
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
8
|
export declare function cellState(refByte: number, rowByte: number, drawn: boolean): 0 | 1 | 2 | 255;
|
|
9
|
+
export declare function comparesBase(state: number, refByte: number): boolean;
|
|
10
|
+
export declare function runKey(state: number, refByte: number): 0 | 1;
|
|
11
|
+
export declare function opensRun(state: number, key: number, runState: number, openKey: number): boolean;
|
|
14
12
|
/**
|
|
15
13
|
* The texts a `cells` walk reads, by the row of the table it walked: each
|
|
16
14
|
* row's own, and the reference it is read against, which a row fanned out of
|
package/esm/util/cellsStep.js
CHANGED
|
@@ -1,38 +1,16 @@
|
|
|
1
1
|
// The `cells` step: each row's runs against the reference it was fanned out
|
|
2
2
|
// of, and the texts its walk reads, which the fused identity kernel in
|
|
3
3
|
// cellMatches.ts walks too. Imported by relative path only.
|
|
4
|
+
import { DASH, SPACE, firstDrawn, isGapByte, isUnknownBase, lastDrawn, sameBase, } from './alignedBytes.js';
|
|
4
5
|
import { DerivedTable, numberReaderOf, readerOf, valueAt, } from './featureTable.js';
|
|
5
6
|
import { FannedTable, boundsThrough } from './stepTables.js';
|
|
6
7
|
export const DEFAULT_CELLS_FIELD = 'seq';
|
|
7
|
-
export const DASH = 45;
|
|
8
|
-
export const SPACE = 32;
|
|
9
|
-
const LOWER_BIT = 0x20;
|
|
10
8
|
const CELL_STATES = ['match', 'mismatch', 'gap', 'insertion'];
|
|
11
9
|
export const MATCH = 0;
|
|
12
10
|
export const MISMATCH = 1;
|
|
13
11
|
const GAP = 2;
|
|
14
12
|
const INSERTION = 3;
|
|
15
13
|
export const NO_STATE = 255;
|
|
16
|
-
export function isGapByte(b) {
|
|
17
|
-
return b === DASH || b === SPACE;
|
|
18
|
-
}
|
|
19
|
-
// The columns carrying the row's own sequence: a gap run reaching either end
|
|
20
|
-
// of the row measures where the block was cut, not the alignment, so it is no
|
|
21
|
-
// cell either.
|
|
22
|
-
export function firstDrawn(bytes, at, len) {
|
|
23
|
-
let first = 0;
|
|
24
|
-
while (first < len && isGapByte(bytes[at + first])) {
|
|
25
|
-
first++;
|
|
26
|
-
}
|
|
27
|
-
return first;
|
|
28
|
-
}
|
|
29
|
-
export function lastDrawn(bytes, at, len, first) {
|
|
30
|
-
let last = len - 1;
|
|
31
|
-
while (last > first && isGapByte(bytes[at + last])) {
|
|
32
|
-
last--;
|
|
33
|
-
}
|
|
34
|
-
return last;
|
|
35
|
-
}
|
|
36
14
|
// A reference base's cell: no state outside the drawn columns, else the row's
|
|
37
15
|
// gap, or its base matched or not regardless of case.
|
|
38
16
|
export function cellState(refByte, rowByte, drawn) {
|
|
@@ -40,10 +18,25 @@ export function cellState(refByte, rowByte, drawn) {
|
|
|
40
18
|
? NO_STATE
|
|
41
19
|
: isGapByte(rowByte)
|
|
42
20
|
? GAP
|
|
43
|
-
: (refByte
|
|
21
|
+
: sameBase(refByte, rowByte)
|
|
44
22
|
? MATCH
|
|
45
23
|
: MISMATCH;
|
|
46
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
|
+
}
|
|
47
40
|
// The row a cell step reads against: past any table that only reordered or
|
|
48
41
|
// annotated the rows, the one they were fanned out of.
|
|
49
42
|
function containerOf(table) {
|
|
@@ -218,7 +211,11 @@ class CellTable extends DerivedTable {
|
|
|
218
211
|
const values = new Float32Array(this.length);
|
|
219
212
|
for (let i = 0; i < this.length; i++) {
|
|
220
213
|
const s = this.state[i];
|
|
221
|
-
values[i] = s
|
|
214
|
+
values[i] = comparesBase(s, this.refByte(i))
|
|
215
|
+
? s === MATCH
|
|
216
|
+
? 1
|
|
217
|
+
: 0
|
|
218
|
+
: Number.NaN;
|
|
222
219
|
}
|
|
223
220
|
return { kind: 'number', values, at: undefined, nanIsAbsent: true };
|
|
224
221
|
});
|
|
@@ -254,6 +251,11 @@ class CellTable extends DerivedTable {
|
|
|
254
251
|
}
|
|
255
252
|
}
|
|
256
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
|
+
}
|
|
257
259
|
mismatched(i) {
|
|
258
260
|
const { bytes, offset } = this.texts.row;
|
|
259
261
|
const k = this.texts.rowOf(this.parentOf(i));
|
|
@@ -342,7 +344,7 @@ class RunLanes {
|
|
|
342
344
|
const last = lastDrawn(rowBytes, rowAt, rowLen, first);
|
|
343
345
|
let runStart = -1;
|
|
344
346
|
let runState = NO_STATE;
|
|
345
|
-
let
|
|
347
|
+
let openKey = -1;
|
|
346
348
|
let runCol = 0;
|
|
347
349
|
let insertAt = -1;
|
|
348
350
|
for (let col = 0; col < refLen; col++) {
|
|
@@ -364,8 +366,8 @@ class RunLanes {
|
|
|
364
366
|
insertAt = -1;
|
|
365
367
|
}
|
|
366
368
|
const st = cellState(refByte, rowByte, col >= first && col <= last && col < rowLen);
|
|
367
|
-
const
|
|
368
|
-
if (st
|
|
369
|
+
const key = runKey(st, refByte);
|
|
370
|
+
if (opensRun(st, key, runState, openKey)) {
|
|
369
371
|
if (runStart >= 0) {
|
|
370
372
|
start[n] = runStart;
|
|
371
373
|
end[n] = pos;
|
|
@@ -376,7 +378,7 @@ class RunLanes {
|
|
|
376
378
|
}
|
|
377
379
|
runStart = st === NO_STATE ? -1 : pos;
|
|
378
380
|
runState = st;
|
|
379
|
-
|
|
381
|
+
openKey = key;
|
|
380
382
|
runCol = col;
|
|
381
383
|
}
|
|
382
384
|
pos++;
|
|
@@ -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
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { Feature, SimpleFeatureSerializedNoId } from './simpleFeature.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Which isoform of a gene a display shows first: the canvas gene glyph's
|
|
4
|
+
* `longestCoding` collapse and the sequence panel's default transcript both
|
|
5
|
+
* rank with this, so the panel opens on the isoform the track drew. Generic
|
|
6
|
+
* over the feature shape because the worker ranks live `Feature`s and the
|
|
7
|
+
* panel ranks their serialized form.
|
|
8
|
+
*/
|
|
9
|
+
export interface IsoformReader<T> {
|
|
10
|
+
type: (feature: T) => string | undefined;
|
|
11
|
+
start: (feature: T) => number;
|
|
12
|
+
end: (feature: T) => number;
|
|
13
|
+
subfeatures: (feature: T) => readonly T[];
|
|
14
|
+
attribute: (feature: T, field: string) => unknown;
|
|
15
|
+
}
|
|
16
|
+
export declare const featureIsoformReader: IsoformReader<Feature>;
|
|
17
|
+
export declare const serializedIsoformReader: IsoformReader<SimpleFeatureSerializedNoId>;
|
|
18
|
+
/**
|
|
19
|
+
* The attribute naming a gene's representative isoform, and the values of it
|
|
20
|
+
* that do, best first. The canvas display's `canonicalTranscriptField` and
|
|
21
|
+
* `canonicalTranscriptTags` slots default to these.
|
|
22
|
+
*/
|
|
23
|
+
export interface CanonicalTranscripts {
|
|
24
|
+
field: string;
|
|
25
|
+
tags: readonly string[];
|
|
26
|
+
}
|
|
27
|
+
export declare const DEFAULT_CANONICAL_TRANSCRIPTS: CanonicalTranscripts;
|
|
28
|
+
export interface IsoformScore {
|
|
29
|
+
canonical: number;
|
|
30
|
+
coding: boolean;
|
|
31
|
+
size: number;
|
|
32
|
+
}
|
|
33
|
+
export declare function isCodingIsoform<T>(read: IsoformReader<T>, feature: T): boolean;
|
|
34
|
+
export declare function isoformScorer<T>(read: IsoformReader<T>, { field, tags }: CanonicalTranscripts): (feature: T) => IsoformScore;
|
|
35
|
+
export declare function rankIsoforms<T>(isoforms: readonly T[], scoreOf: (feature: T) => IsoformScore): T[];
|
|
36
|
+
export interface CanonicalTranscriptsHost {
|
|
37
|
+
canonicalTranscripts: CanonicalTranscripts;
|
|
38
|
+
}
|
|
39
|
+
export declare function isCanonicalTranscriptsHost(thing: unknown): thing is CanonicalTranscriptsHost;
|