@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
|
@@ -324,10 +324,8 @@ export function createBaseTrackModel(pm, trackType, baseTrackConfig) {
|
|
|
324
324
|
* completely untouched ones — `reaction` only fires on an actual change.
|
|
325
325
|
*
|
|
326
326
|
* `equals: compareStructural` is load-bearing, not an optimization:
|
|
327
|
-
* `self.configuration` is a re-resolving reference, and
|
|
328
|
-
*
|
|
329
|
-
* but still churns once, and a session without deltas replaces the frozen
|
|
330
|
-
* `jbrowse.tracks` entry, rehydrating a brand-new MST node every write).
|
|
327
|
+
* `self.configuration` is a re-resolving reference, and an undo or a
|
|
328
|
+
* session restore swaps the resolved node for a new working copy.
|
|
331
329
|
* Referential comparison would treat every such swap as a fresh change
|
|
332
330
|
* and re-fire the save, an unbounded debounced loop. Structural
|
|
333
331
|
* comparison settles once the content stops changing.
|
|
@@ -177,6 +177,7 @@ export function createBaseTrackConfig(pluginManager) {
|
|
|
177
177
|
type: 'stringArray',
|
|
178
178
|
description: 'list of which feature attributes to index for text searching',
|
|
179
179
|
defaultValue: ['Name', 'ID', 'symbol'],
|
|
180
|
+
advanced: true,
|
|
180
181
|
},
|
|
181
182
|
/**
|
|
182
183
|
* #slot textSearching.indexingFeatureTypesToExclude
|
|
@@ -185,6 +186,7 @@ export function createBaseTrackConfig(pluginManager) {
|
|
|
185
186
|
type: 'stringArray',
|
|
186
187
|
description: 'list of feature types to exclude in text search index',
|
|
187
188
|
defaultValue: ['CDS', 'exon'],
|
|
189
|
+
advanced: true,
|
|
188
190
|
},
|
|
189
191
|
/**
|
|
190
192
|
* #slot textSearching.indexingFeatureTypesToInclude
|
|
@@ -207,6 +209,7 @@ export function createBaseTrackConfig(pluginManager) {
|
|
|
207
209
|
type: 'stringArray',
|
|
208
210
|
description: 'the only feature types to index; empty means index every type not excluded',
|
|
209
211
|
defaultValue: [],
|
|
212
|
+
advanced: true,
|
|
210
213
|
},
|
|
211
214
|
/**
|
|
212
215
|
* #slot textSearching.textSearchAdapter
|
package/esm/rpc/RpcRegistry.d.ts
CHANGED
package/esm/rpc/byteBudget.d.ts
CHANGED
|
@@ -33,6 +33,13 @@ export interface RegionTooLargeResult {
|
|
|
33
33
|
partial?: boolean;
|
|
34
34
|
}
|
|
35
35
|
export declare function isRegionRefused(result: unknown): result is RegionTooLargeResult;
|
|
36
|
+
/**
|
|
37
|
+
* The thrown form of the same refusal, for an adapter whose limit only a fetch
|
|
38
|
+
* can find, such as a graph's node count. Marked by an own `regionTooLarge`
|
|
39
|
+
* property, which crosses the worker boundary, so an adapter's own error class
|
|
40
|
+
* opts in without importing anything.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isRegionTooLargeError(error: unknown): error is Error;
|
|
36
43
|
/** Undefined, never 0, when no region could be measured. */
|
|
37
44
|
export declare function largestRegionBytes(perRegion: (number | undefined)[]): number | undefined;
|
|
38
45
|
/** A non-positive declared limit means "no opinion" (htsget reports 0). */
|
package/esm/rpc/byteBudget.js
CHANGED
|
@@ -3,6 +3,16 @@ import { updateStatus } from '../util/progress.js';
|
|
|
3
3
|
export function isRegionRefused(result) {
|
|
4
4
|
return (typeof result === 'object' && result !== null && 'regionTooLarge' in result);
|
|
5
5
|
}
|
|
6
|
+
/**
|
|
7
|
+
* The thrown form of the same refusal, for an adapter whose limit only a fetch
|
|
8
|
+
* can find, such as a graph's node count. Marked by an own `regionTooLarge`
|
|
9
|
+
* property, which crosses the worker boundary, so an adapter's own error class
|
|
10
|
+
* opts in without importing anything.
|
|
11
|
+
*/
|
|
12
|
+
export function isRegionTooLargeError(error) {
|
|
13
|
+
return (error instanceof Error &&
|
|
14
|
+
error.regionTooLarge === true);
|
|
15
|
+
}
|
|
6
16
|
/** Undefined, never 0, when no region could be measured. */
|
|
7
17
|
export function largestRegionBytes(perRegion) {
|
|
8
18
|
let largest;
|
|
@@ -6,7 +6,7 @@ export { default as CoreGetEncodedLayers } from './methods/CoreGetEncodedLayers.
|
|
|
6
6
|
export { default as CoreGetEncodedFeature } from './methods/CoreGetEncodedFeature.ts';
|
|
7
7
|
export { default as CoreGetSequence } from './methods/CoreGetSequence.ts';
|
|
8
8
|
export { default as CoreFreeResources } from './methods/CoreFreeResources.ts';
|
|
9
|
-
export { default as
|
|
9
|
+
export { default as CoreLoadTrackCode } from './methods/CoreLoadTrackCode.ts';
|
|
10
10
|
export { default as CoreGetRegionByteEstimate } from './methods/CoreGetRegionByteEstimate.ts';
|
|
11
11
|
export { default as CoreGetFeatureDensity } from './methods/CoreGetFeatureDensity.ts';
|
|
12
12
|
export { default as CoreGetRegions } from './methods/CoreGetRegions.ts';
|
|
@@ -6,7 +6,7 @@ export { default as CoreGetEncodedLayers } from './methods/CoreGetEncodedLayers.
|
|
|
6
6
|
export { default as CoreGetEncodedFeature } from './methods/CoreGetEncodedFeature.js';
|
|
7
7
|
export { default as CoreGetSequence } from './methods/CoreGetSequence.js';
|
|
8
8
|
export { default as CoreFreeResources } from './methods/CoreFreeResources.js';
|
|
9
|
-
export { default as
|
|
9
|
+
export { default as CoreLoadTrackCode } from './methods/CoreLoadTrackCode.js';
|
|
10
10
|
export { default as CoreGetRegionByteEstimate } from './methods/CoreGetRegionByteEstimate.js';
|
|
11
11
|
export { default as CoreGetFeatureDensity } from './methods/CoreGetFeatureDensity.js';
|
|
12
12
|
export { default as CoreGetRegions } from './methods/CoreGetRegions.js';
|
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
// sentence this appends to.
|
|
15
15
|
//
|
|
16
16
|
// Only runs on the error path.
|
|
17
|
-
// The deepest RPC result in the tree is the
|
|
18
|
-
// `perRegionCellData.0.
|
|
19
|
-
//
|
|
17
|
+
// The deepest RPC result in the tree is the multi-sample variant one:
|
|
18
|
+
// `perRegionCellData.0.featureInfo.<index>.genotypeCodes` — four containers
|
|
19
|
+
// down. The alignments result is three (`groups.3.data.mismatchStarts`),
|
|
20
20
|
// synteny two (`instanceData.bp1`, `attributes.identity`) and most are one. The
|
|
21
21
|
// walk is capped rather than unbounded because a payload also carries plain data
|
|
22
22
|
// — a featureIds array of half a million strings — and there is no reason to
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import RpcMethodType from '../../pluggableElementTypes/RpcMethodType.ts';
|
|
2
|
+
import type { RpcExecuteArgs } from '../RpcRegistry.ts';
|
|
3
|
+
/**
|
|
4
|
+
* Load the code a track's first request runs in the worker it is about to use:
|
|
5
|
+
* its adapter classes and the RPC methods its display names. Each arrives by
|
|
6
|
+
* dynamic import on that request otherwise, after the view lays out, the
|
|
7
|
+
* assembly loads and the request's refNames resolve.
|
|
8
|
+
*
|
|
9
|
+
* At launch rather than at worker boot: until `parallelizeChunkLoading` is
|
|
10
|
+
* installed a classic worker loads chunks by serial `importScripts`, and an
|
|
11
|
+
* import there delayed the first data request 360ms at 80ms RTT.
|
|
12
|
+
*/
|
|
13
|
+
export default class CoreLoadTrackCode extends RpcMethodType<'CoreLoadTrackCode'> {
|
|
14
|
+
name: 'CoreLoadTrackCode';
|
|
15
|
+
execute({ adapterTypes, rpcMethods, }: RpcExecuteArgs<'CoreLoadTrackCode'>): Promise<void>;
|
|
16
|
+
serializeArguments(args: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import RpcMethodType from '../../pluggableElementTypes/RpcMethodType.js';
|
|
2
|
+
/**
|
|
3
|
+
* Load the code a track's first request runs in the worker it is about to use:
|
|
4
|
+
* its adapter classes and the RPC methods its display names. Each arrives by
|
|
5
|
+
* dynamic import on that request otherwise, after the view lays out, the
|
|
6
|
+
* assembly loads and the request's refNames resolve.
|
|
7
|
+
*
|
|
8
|
+
* At launch rather than at worker boot: until `parallelizeChunkLoading` is
|
|
9
|
+
* installed a classic worker loads chunks by serial `importScripts`, and an
|
|
10
|
+
* import there delayed the first data request 360ms at 80ms RTT.
|
|
11
|
+
*/
|
|
12
|
+
export default class CoreLoadTrackCode extends RpcMethodType {
|
|
13
|
+
name = 'CoreLoadTrackCode';
|
|
14
|
+
async execute({ adapterTypes, rpcMethods, }) {
|
|
15
|
+
const { pluginManager } = this;
|
|
16
|
+
await Promise.all([
|
|
17
|
+
...adapterTypes.map(type => pluginManager.getAdapterType(type).getAdapterClass()),
|
|
18
|
+
...rpcMethods.map(name => pluginManager.getRpcMethodType(name).preload()),
|
|
19
|
+
]);
|
|
20
|
+
}
|
|
21
|
+
async serializeArguments(args) {
|
|
22
|
+
return args;
|
|
23
|
+
}
|
|
24
|
+
}
|
package/esm/ui/AppLogo.js
CHANGED
|
@@ -1,10 +1,50 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
2
3
|
import { observer } from 'mobx-react';
|
|
3
4
|
import { readConfObject } from '../configuration/index.js';
|
|
5
|
+
import { isUriLocation } from '../util/types/data.js';
|
|
4
6
|
import { LogoFull } from './Logo.js';
|
|
7
|
+
function resolvedUri(location) {
|
|
8
|
+
return location.baseUri
|
|
9
|
+
? new URL(location.uri, location.baseUri).href
|
|
10
|
+
: location.uri;
|
|
11
|
+
}
|
|
12
|
+
// A URI goes straight to the <img>, which needs no CORS; a local path, a
|
|
13
|
+
// blob or a file handle is read into an object URL.
|
|
14
|
+
function useLogoSrc(location) {
|
|
15
|
+
const [objectUrl, setObjectUrl] = useState();
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!location || isUriLocation(location)) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
let url;
|
|
21
|
+
let cancelled = false;
|
|
22
|
+
import('../util/io/index.js')
|
|
23
|
+
.then(({ openLocation }) => openLocation(location).readFile())
|
|
24
|
+
.then(bytes => {
|
|
25
|
+
if (!cancelled) {
|
|
26
|
+
url = URL.createObjectURL(new Blob([bytes]));
|
|
27
|
+
setObjectUrl(url);
|
|
28
|
+
}
|
|
29
|
+
})
|
|
30
|
+
.catch((e) => {
|
|
31
|
+
console.error(e);
|
|
32
|
+
});
|
|
33
|
+
return () => {
|
|
34
|
+
cancelled = true;
|
|
35
|
+
if (url) {
|
|
36
|
+
URL.revokeObjectURL(url);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}, [location]);
|
|
40
|
+
return location && isUriLocation(location)
|
|
41
|
+
? resolvedUri(location)
|
|
42
|
+
: location
|
|
43
|
+
? objectUrl
|
|
44
|
+
: undefined;
|
|
45
|
+
}
|
|
5
46
|
const Logo = observer(function Logo({ session, }) {
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
return logoPath?.uri ? (_jsx("img", { src: logoPath.uri, alt: "Custom logo" })) : (_jsx(LogoFull, { variant: "white" }));
|
|
47
|
+
const src = useLogoSrc(readConfObject(session.configuration, 'logoPath'));
|
|
48
|
+
return src ? (_jsx("img", { src: src, alt: "Custom logo" })) : (_jsx(LogoFull, { variant: "white" }));
|
|
9
49
|
});
|
|
10
50
|
export default Logo;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type PluginManager from '../PluginManager.ts';
|
|
2
|
-
import type {
|
|
2
|
+
import type { AnyTrackConfig } from '../configuration/index.ts';
|
|
3
3
|
import type { AbstractSessionModel, TrackActionView } from '../util/types/index.ts';
|
|
4
4
|
import type { MenuItem } from './MenuTypes.ts';
|
|
5
5
|
export interface ExtraTrackMenuItemsProps {
|
|
6
6
|
session: AbstractSessionModel;
|
|
7
|
-
config:
|
|
7
|
+
config: AnyTrackConfig;
|
|
8
8
|
view?: TrackActionView;
|
|
9
9
|
}
|
|
10
10
|
/** Return the items to add, or nothing when the track isn't yours. */
|
package/esm/ui/colors.d.ts
CHANGED
|
@@ -15,11 +15,9 @@ export declare const paletteColors: {
|
|
|
15
15
|
* The wide qualitative palette: every scheme above, in order, deduped and with
|
|
16
16
|
* the neutrals dropped. ~40 entries.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* would mean a track's automatic colors and the colors it takes when a user
|
|
22
|
-
* palettes it by hand were different palettes for no reason a reader could see.
|
|
18
|
+
* A feature's categorical colour deals from it; a row's deals from
|
|
19
|
+
* `rowPaletteColorAt`, which re-lights tableau10 rather than reaching for
|
|
20
|
+
* another scheme's hues.
|
|
23
21
|
*
|
|
24
22
|
* `tableau10` leads because its hues are the most evenly separated, so a track
|
|
25
23
|
* with a handful of rows spends only that. The tail is what makes the 26
|
|
@@ -31,21 +29,16 @@ export declare const paletteColors: {
|
|
|
31
29
|
* re-palettes), while a by-position painter has no value to hash and wraps.
|
|
32
30
|
*
|
|
33
31
|
* **It holds 14 pairs `similarColors` calls the same** — five schemes' blues,
|
|
34
|
-
* oranges, greens, reds and pinks
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* colors and 38 on 26; the loss is the birthday collision on 40 slots, not the
|
|
38
|
-
* near-twins, and only 3 of the pairs they land on read alike. Pruning to the
|
|
39
|
-
* 30 distinct entries makes that 23 and 24, so it costs ten colors and buys
|
|
40
|
-
* nothing.
|
|
32
|
+
* oranges, greens, reds and pinks. A dealt scale (`categoricalScale`'s `held`)
|
|
33
|
+
* steps over a twin of a color already dealt, so they cost a track only once
|
|
34
|
+
* it has more values than the 30 distinct entries.
|
|
41
35
|
*
|
|
42
|
-
* The near-twins bite
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* with a figure refresh in it.
|
|
36
|
+
* The near-twins bite a `domain` instead, which spends the list in order: 30
|
|
37
|
+
* listed values take 30 colors and 7 of those pairs read alike. Ordering the
|
|
38
|
+
* distinct 30 first and the twins behind them fixes that outright (0 pairs at
|
|
39
|
+
* 30 listed values, 11 at 38 where the twins are reached anyway) and costs no
|
|
40
|
+
* colors — but it moves every hashed color in marks, GWAS, canvas, multi-row
|
|
41
|
+
* and the arrangement dialog, so it is a pass with a figure refresh in it.
|
|
49
42
|
*/
|
|
50
43
|
export declare const categoricalPalette: string[];
|
|
51
44
|
/**
|
|
@@ -60,18 +53,30 @@ export declare const categoricalPalette: string[];
|
|
|
60
53
|
export declare function categoricalValueColor(value: string): string;
|
|
61
54
|
export declare function categoricalValueColor<T>(value: string, palette: readonly T[]): T;
|
|
62
55
|
/**
|
|
63
|
-
* The categorical
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* entries `range` lacks past its end, and any other value takes a slot of the
|
|
67
|
-
* whole list derived from itself that no listed value holds, nor one
|
|
68
|
-
* `isNear` calls the same. A value's entry depends only on the value and the
|
|
69
|
-
* declaration, so every region agrees on it, and adding a listed value moves
|
|
70
|
-
* only the unlisted values on the slots it takes.
|
|
56
|
+
* The slots a categorical scale has dealt the values it met that its `domain`
|
|
57
|
+
* does not list, each recorded on first sight and never dealt again. The
|
|
58
|
+
* caller owns it and hands the same one to every scale that should agree.
|
|
71
59
|
*/
|
|
72
|
-
export
|
|
60
|
+
export type HeldSlots = Map<string, number>;
|
|
61
|
+
/**
|
|
62
|
+
* The categorical rule every scaled channel resolves through. The values
|
|
63
|
+
* `domain` lists take `range` in order, continuing into the `fallback`
|
|
64
|
+
* entries `range` lacks past its end. Any other value takes the slot derived
|
|
65
|
+
* from itself (`categoricalValueColor`'s, with no `domain`) unless a listed
|
|
66
|
+
* value holds it or one `isNear` calls the same, and then another slot derived
|
|
67
|
+
* from itself.
|
|
68
|
+
*
|
|
69
|
+
* With `held`, a value is dealt once, on first sight: its slot avoids every
|
|
70
|
+
* slot `held` already records and their near twins as well, so values met
|
|
71
|
+
* together never share an entry while the list has room, and the slot is
|
|
72
|
+
* recorded so the value keeps it as others arrive. A value nothing else
|
|
73
|
+
* collides with keeps the entry it takes without `held`. With no `domain`, the
|
|
74
|
+
* deal spends `range` before spilling into the `fallback` entries it lacks.
|
|
75
|
+
*/
|
|
76
|
+
export declare function categoricalScale<T>(domain: readonly (string | number)[] | undefined, range: readonly T[], { fallback, isNear, held, }?: {
|
|
73
77
|
fallback?: readonly T[];
|
|
74
78
|
isNear?: (a: T, b: T) => boolean;
|
|
79
|
+
held?: HeldSlots;
|
|
75
80
|
}): (value: string) => T;
|
|
76
81
|
/**
|
|
77
82
|
* Whether two CSS colors read as one: under 0.05 apart in OKLab, where the
|
|
@@ -81,9 +86,10 @@ export declare function similarColors(a: string, b: string): boolean;
|
|
|
81
86
|
/**
|
|
82
87
|
* `categoricalScale` for a color channel: `palette` (the wide palette when
|
|
83
88
|
* empty) spent in `domain` order and continued into the wide palette, and an
|
|
84
|
-
* unlisted value never on a listed value's color or one that reads the same
|
|
89
|
+
* unlisted value never on a listed value's color or one that reads the same,
|
|
90
|
+
* nor, with `held`, on the color of a value met before it.
|
|
85
91
|
*/
|
|
86
|
-
export declare function categoricalColorScale(domain: readonly (string | number)[] | undefined, palette?: readonly string[]): (value: string) => string;
|
|
92
|
+
export declare function categoricalColorScale(domain: readonly (string | number)[] | undefined, palette?: readonly string[], held?: HeldSlots): (value: string) => string;
|
|
87
93
|
/**
|
|
88
94
|
* The color one value of a categorical color channel paints, by
|
|
89
95
|
* `categoricalColorScale`. A value-less feature is neutral grey, and an array
|
|
@@ -104,6 +110,11 @@ export declare function hashString(str: string): number;
|
|
|
104
110
|
export declare function getQueryColor(queryName: string): string;
|
|
105
111
|
/** The palette color for a chromosome at `position` in its assembly. */
|
|
106
112
|
export declare function refNamePaletteColorAt(position: number): string;
|
|
113
|
+
/**
|
|
114
|
+
* The colour the row palette deals the row or value at `position`: tableau10
|
|
115
|
+
* less its grey, then re-lit laps of it, as a karyotype's are.
|
|
116
|
+
*/
|
|
117
|
+
export declare function rowPaletteColorAt(position: number): string;
|
|
107
118
|
/**
|
|
108
119
|
* The color a refName paints, BY POSITION IN THE ASSEMBLY where the caller
|
|
109
120
|
* knows its chromosome order.
|
package/esm/ui/colors.js
CHANGED
|
@@ -98,11 +98,9 @@ export const paletteColors = {
|
|
|
98
98
|
* The wide qualitative palette: every scheme above, in order, deduped and with
|
|
99
99
|
* the neutrals dropped. ~40 entries.
|
|
100
100
|
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* would mean a track's automatic colors and the colors it takes when a user
|
|
105
|
-
* palettes it by hand were different palettes for no reason a reader could see.
|
|
101
|
+
* A feature's categorical colour deals from it; a row's deals from
|
|
102
|
+
* `rowPaletteColorAt`, which re-lights tableau10 rather than reaching for
|
|
103
|
+
* another scheme's hues.
|
|
106
104
|
*
|
|
107
105
|
* `tableau10` leads because its hues are the most evenly separated, so a track
|
|
108
106
|
* with a handful of rows spends only that. The tail is what makes the 26
|
|
@@ -114,21 +112,16 @@ export const paletteColors = {
|
|
|
114
112
|
* re-palettes), while a by-position painter has no value to hash and wraps.
|
|
115
113
|
*
|
|
116
114
|
* **It holds 14 pairs `similarColors` calls the same** — five schemes' blues,
|
|
117
|
-
* oranges, greens, reds and pinks
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
* colors and 38 on 26; the loss is the birthday collision on 40 slots, not the
|
|
121
|
-
* near-twins, and only 3 of the pairs they land on read alike. Pruning to the
|
|
122
|
-
* 30 distinct entries makes that 23 and 24, so it costs ten colors and buys
|
|
123
|
-
* nothing.
|
|
115
|
+
* oranges, greens, reds and pinks. A dealt scale (`categoricalScale`'s `held`)
|
|
116
|
+
* steps over a twin of a color already dealt, so they cost a track only once
|
|
117
|
+
* it has more values than the 30 distinct entries.
|
|
124
118
|
*
|
|
125
|
-
* The near-twins bite
|
|
126
|
-
*
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* with a figure refresh in it.
|
|
119
|
+
* The near-twins bite a `domain` instead, which spends the list in order: 30
|
|
120
|
+
* listed values take 30 colors and 7 of those pairs read alike. Ordering the
|
|
121
|
+
* distinct 30 first and the twins behind them fixes that outright (0 pairs at
|
|
122
|
+
* 30 listed values, 11 at 38 where the twins are reached anyway) and costs no
|
|
123
|
+
* colors — but it moves every hashed color in marks, GWAS, canvas, multi-row
|
|
124
|
+
* and the arrangement dialog, so it is a pass with a figure refresh in it.
|
|
132
125
|
*/
|
|
133
126
|
export const categoricalPalette = [
|
|
134
127
|
...new Set([...tableau10, ...set1, ...dark2, ...set2, ...category10].filter(hex => !NEUTRALS.has(hex.toLowerCase()))),
|
|
@@ -159,19 +152,24 @@ function freeSlot(value, size, taken) {
|
|
|
159
152
|
return slot;
|
|
160
153
|
}
|
|
161
154
|
/**
|
|
162
|
-
* The categorical rule every scaled channel resolves through.
|
|
163
|
-
* `domain`
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
168
|
-
*
|
|
169
|
-
*
|
|
155
|
+
* The categorical rule every scaled channel resolves through. The values
|
|
156
|
+
* `domain` lists take `range` in order, continuing into the `fallback`
|
|
157
|
+
* entries `range` lacks past its end. Any other value takes the slot derived
|
|
158
|
+
* from itself (`categoricalValueColor`'s, with no `domain`) unless a listed
|
|
159
|
+
* value holds it or one `isNear` calls the same, and then another slot derived
|
|
160
|
+
* from itself.
|
|
161
|
+
*
|
|
162
|
+
* With `held`, a value is dealt once, on first sight: its slot avoids every
|
|
163
|
+
* slot `held` already records and their near twins as well, so values met
|
|
164
|
+
* together never share an entry while the list has room, and the slot is
|
|
165
|
+
* recorded so the value keeps it as others arrive. A value nothing else
|
|
166
|
+
* collides with keeps the entry it takes without `held`. With no `domain`, the
|
|
167
|
+
* deal spends `range` before spilling into the `fallback` entries it lacks.
|
|
170
168
|
*/
|
|
171
|
-
export function categoricalScale(domain, range, { fallback = [], isNear, } = {}) {
|
|
169
|
+
export function categoricalScale(domain, range, { fallback = [], isNear, held, } = {}) {
|
|
172
170
|
const base = range.length ? range : fallback;
|
|
173
171
|
const listed = [...new Set((domain ?? []).map(String))].filter(v => v !== '');
|
|
174
|
-
if (listed.length === 0) {
|
|
172
|
+
if (listed.length === 0 && !held) {
|
|
175
173
|
return value => base[valueSlot(value, base.length)];
|
|
176
174
|
}
|
|
177
175
|
const seen = new Set(base.map(entryKey));
|
|
@@ -183,19 +181,44 @@ export function categoricalScale(domain, range, { fallback = [], isNear, } = {})
|
|
|
183
181
|
}),
|
|
184
182
|
];
|
|
185
183
|
const size = entries.length;
|
|
184
|
+
const twins = [];
|
|
185
|
+
const twinsOf = (slot) => (twins[slot] ??= entries.flatMap((entry, i) => i === slot || isNear?.(entries[slot], entry) ? [i] : []));
|
|
186
186
|
const rank = new Map(listed.map((value, i) => [value, i]));
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
187
|
+
const listedTaken = new Set(listed.flatMap((_, i) => twinsOf(i % size)));
|
|
188
|
+
const tiers = listed.length > 0
|
|
189
|
+
? [[0, size]]
|
|
190
|
+
: [[0, base.length], [base.length, size]];
|
|
191
|
+
const deal = (value, taken) => {
|
|
192
|
+
for (const [from, to] of tiers) {
|
|
193
|
+
const inTier = from === 0 && to === size
|
|
194
|
+
? taken
|
|
195
|
+
: new Set([...taken].filter(s => s >= from && s < to).map(s => s - from));
|
|
196
|
+
if (inTier.size < to - from) {
|
|
197
|
+
return from + freeSlot(value, to - from, inTier);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return valueSlot(value, tiers[0][1]);
|
|
201
|
+
};
|
|
192
202
|
return value => {
|
|
193
203
|
const i = rank.get(value);
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
204
|
+
if (i !== undefined) {
|
|
205
|
+
return entries[i % size];
|
|
206
|
+
}
|
|
207
|
+
if (!held) {
|
|
208
|
+
return entries[deal(value, listedTaken)];
|
|
209
|
+
}
|
|
210
|
+
let slot = held.get(value);
|
|
211
|
+
if (slot === undefined) {
|
|
212
|
+
const taken = new Set(listedTaken);
|
|
213
|
+
for (const s of held.values()) {
|
|
214
|
+
for (const twin of twinsOf(s)) {
|
|
215
|
+
taken.add(twin);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
slot = deal(value, taken);
|
|
219
|
+
held.set(value, slot);
|
|
220
|
+
}
|
|
221
|
+
return entries[slot];
|
|
199
222
|
};
|
|
200
223
|
}
|
|
201
224
|
const oklabByColor = new Map();
|
|
@@ -219,12 +242,14 @@ export function similarColors(a, b) {
|
|
|
219
242
|
/**
|
|
220
243
|
* `categoricalScale` for a color channel: `palette` (the wide palette when
|
|
221
244
|
* empty) spent in `domain` order and continued into the wide palette, and an
|
|
222
|
-
* unlisted value never on a listed value's color or one that reads the same
|
|
245
|
+
* unlisted value never on a listed value's color or one that reads the same,
|
|
246
|
+
* nor, with `held`, on the color of a value met before it.
|
|
223
247
|
*/
|
|
224
|
-
export function categoricalColorScale(domain, palette = []) {
|
|
248
|
+
export function categoricalColorScale(domain, palette = [], held) {
|
|
225
249
|
return categoricalScale(domain, palette, {
|
|
226
250
|
fallback: categoricalPalette,
|
|
227
251
|
isNear: similarColors,
|
|
252
|
+
held,
|
|
228
253
|
});
|
|
229
254
|
}
|
|
230
255
|
const MAX_CACHED_SCALES = 16;
|
|
@@ -316,11 +341,23 @@ const PALETTE_LAP_TONES = [
|
|
|
316
341
|
// and pinks all pin against the top of the sRGB gamut and converge there.
|
|
317
342
|
{ lightnessShift: 0.18, chromaScale: 0.8 },
|
|
318
343
|
];
|
|
344
|
+
// The colour at `position` in `hexes`, each lap past the end re-lit.
|
|
345
|
+
function lapColorAt(hexes, position) {
|
|
346
|
+
const hex = hexes[position % hexes.length];
|
|
347
|
+
const lap = PALETTE_LAP_TONES[Math.floor(position / hexes.length) % PALETTE_LAP_TONES.length];
|
|
348
|
+
return lap ? relight(hex, lap.lightnessShift, lap.chromaScale) : hex;
|
|
349
|
+
}
|
|
319
350
|
/** The palette color for a chromosome at `position` in its assembly. */
|
|
320
351
|
export function refNamePaletteColorAt(position) {
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
352
|
+
return lapColorAt(refNameColorHexes, position);
|
|
353
|
+
}
|
|
354
|
+
const rowColorHexes = tableau10.filter(hex => !NEUTRALS.has(hex));
|
|
355
|
+
/**
|
|
356
|
+
* The colour the row palette deals the row or value at `position`: tableau10
|
|
357
|
+
* less its grey, then re-lit laps of it, as a karyotype's are.
|
|
358
|
+
*/
|
|
359
|
+
export function rowPaletteColorAt(position) {
|
|
360
|
+
return lapColorAt(rowColorHexes, position);
|
|
324
361
|
}
|
|
325
362
|
/**
|
|
326
363
|
* The color a refName paints, BY POSITION IN THE ASSEMBLY where the caller
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type PluginManager from '../PluginManager.ts';
|
|
2
|
-
import type {
|
|
2
|
+
import type { AnyTrackConfig } from '../configuration/index.ts';
|
|
3
3
|
import type { AbstractSessionModel } from '../util/types/index.ts';
|
|
4
4
|
import type { MenuItem } from './MenuTypes.ts';
|
|
5
5
|
import type { IStateTreeNode } from '@jbrowse/mobx-state-tree';
|
|
6
6
|
export interface MultiTrackSelection extends IStateTreeNode {
|
|
7
|
-
selection:
|
|
7
|
+
selection: AnyTrackConfig[];
|
|
8
8
|
}
|
|
9
9
|
export interface MultiTrackMenuItemsProps {
|
|
10
10
|
session: AbstractSessionModel;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export declare const DASH = 45;
|
|
2
|
+
export declare const SPACE = 32;
|
|
3
|
+
export declare const LOWER_BIT = 32;
|
|
4
|
+
export declare const N_UPPER = 78;
|
|
5
|
+
/** No base at this column of a row: a gap, or the padding of a short row. */
|
|
6
|
+
export declare function isGapByte(b: number): b is 32 | 45;
|
|
7
|
+
export declare function sameBase(a: number, b: number): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* An `N` reference base, which a row neither matches nor mismatches, so
|
|
10
|
+
* identity leaves it uncounted.
|
|
11
|
+
*/
|
|
12
|
+
export declare function isUnknownBase(b: number): boolean;
|
|
13
|
+
/**
|
|
14
|
+
* The first column carrying the row's own sequence. A gap run reaching either
|
|
15
|
+
* end of a row measures where the block was cut, not the alignment.
|
|
16
|
+
*/
|
|
17
|
+
export declare function firstDrawn(bytes: Uint8Array, at: number, len: number): number;
|
|
18
|
+
/** The last column carrying the row's own sequence, from `firstDrawn`'s answer. */
|
|
19
|
+
export declare function lastDrawn(bytes: Uint8Array, at: number, len: number, first: number): number;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const DASH = 45;
|
|
2
|
+
export const SPACE = 32;
|
|
3
|
+
export const LOWER_BIT = 0x20;
|
|
4
|
+
export const N_UPPER = 78;
|
|
5
|
+
/** No base at this column of a row: a gap, or the padding of a short row. */
|
|
6
|
+
export function isGapByte(b) {
|
|
7
|
+
return b === DASH || b === SPACE;
|
|
8
|
+
}
|
|
9
|
+
export function sameBase(a, b) {
|
|
10
|
+
return (a | LOWER_BIT) === (b | LOWER_BIT);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* An `N` reference base, which a row neither matches nor mismatches, so
|
|
14
|
+
* identity leaves it uncounted.
|
|
15
|
+
*/
|
|
16
|
+
export function isUnknownBase(b) {
|
|
17
|
+
return (b & ~LOWER_BIT) === N_UPPER;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The first column carrying the row's own sequence. A gap run reaching either
|
|
21
|
+
* end of a row measures where the block was cut, not the alignment.
|
|
22
|
+
*/
|
|
23
|
+
export function firstDrawn(bytes, at, len) {
|
|
24
|
+
let first = 0;
|
|
25
|
+
while (first < len && isGapByte(bytes[at + first])) {
|
|
26
|
+
first++;
|
|
27
|
+
}
|
|
28
|
+
return first;
|
|
29
|
+
}
|
|
30
|
+
/** The last column carrying the row's own sequence, from `firstDrawn`'s answer. */
|
|
31
|
+
export function lastDrawn(bytes, at, len, first) {
|
|
32
|
+
let last = len - 1;
|
|
33
|
+
while (last > first && isGapByte(bytes[at + last])) {
|
|
34
|
+
last--;
|
|
35
|
+
}
|
|
36
|
+
return last;
|
|
37
|
+
}
|
package/esm/util/analytics.d.ts
CHANGED
|
@@ -1,22 +1,21 @@
|
|
|
1
|
-
import type { AnyConfigurationModel } from '../configuration/index.ts';
|
|
1
|
+
import type { AnyConfigurationModel, TrackConfigEntry } from '../configuration/index.ts';
|
|
2
2
|
declare global {
|
|
3
3
|
interface Window {
|
|
4
4
|
ga?: (...args: unknown[]) => void;
|
|
5
5
|
}
|
|
6
6
|
}
|
|
7
|
-
type TrackConfig = AnyConfigurationModel & {
|
|
8
|
-
type: string;
|
|
9
|
-
};
|
|
10
7
|
interface AnalyticsRootModel {
|
|
11
8
|
jbrowse: {
|
|
12
|
-
tracks:
|
|
9
|
+
tracks: TrackConfigEntry[];
|
|
13
10
|
assemblies: unknown[];
|
|
14
11
|
plugins?: {
|
|
15
12
|
name?: string;
|
|
16
13
|
}[];
|
|
17
14
|
};
|
|
18
15
|
session?: {
|
|
19
|
-
sessionTracks:
|
|
16
|
+
sessionTracks: {
|
|
17
|
+
type: string;
|
|
18
|
+
}[];
|
|
20
19
|
views: unknown[];
|
|
21
20
|
};
|
|
22
21
|
version: string;
|