loom-browser 0.0.13 → 0.0.15
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/dist/loom-react.esm.js +235 -46
- package/dist/loom-react.esm.min.js +1 -1
- package/dist/loom-react.esm.min.js.map +1 -1
- package/dist/loom-worker.js +317 -10
- package/dist/loom-worker.min.js +1 -1
- package/dist/loom-worker.min.js.map +1 -1
- package/dist/loom.esm.js +1898 -1674
- package/dist/loom.esm.min.js +1 -1
- package/dist/loom.esm.min.js.map +1 -1
- package/dist/loom.js +1898 -1673
- package/dist/loom.min.js +1 -1
- package/dist/loom.min.js.map +1 -1
- package/dist/tsconfig.src.tsbuildinfo +1 -1
- package/dist/types/browser/headless/headlessGenomeBrowser.d.ts +3 -1
- package/dist/types/browser/headless/trackFactories.d.ts +15 -0
- package/dist/types/dataSources/bigBedDataSource.d.ts +33 -0
- package/dist/types/dataSources/config.d.ts +5 -1
- package/dist/types/formats/formatDetection.d.ts +2 -0
- package/dist/types/genome/genome.d.ts +3 -2
- package/dist/types/index.d.ts +2 -1
- package/dist/types/types.d.ts +1 -1
- package/package.json +1 -1
package/dist/loom-react.esm.js
CHANGED
|
@@ -5759,15 +5759,15 @@ class TwoBitFile {
|
|
|
5759
5759
|
*/
|
|
5760
5760
|
// ─── Internal helpers ─────────────────────────────────────────────────────────
|
|
5761
5761
|
// Cache TwoBitFile instances by URL to reuse parsed headers and indices.
|
|
5762
|
-
const readerCache$
|
|
5762
|
+
const readerCache$5 = new Map();
|
|
5763
5763
|
// Parallel filehandle cache for direct batched reads (bypasses @gmod/twobit).
|
|
5764
5764
|
const filehandleCache = new Map();
|
|
5765
|
-
function getReader$
|
|
5766
|
-
let reader = readerCache$
|
|
5765
|
+
function getReader$4(url, fetchImpl) {
|
|
5766
|
+
let reader = readerCache$5.get(url);
|
|
5767
5767
|
if (!reader) {
|
|
5768
5768
|
const filehandle = getFilehandle(url, fetchImpl);
|
|
5769
5769
|
reader = new TwoBitFile({ filehandle });
|
|
5770
|
-
readerCache$
|
|
5770
|
+
readerCache$5.set(url, reader);
|
|
5771
5771
|
}
|
|
5772
5772
|
return reader;
|
|
5773
5773
|
}
|
|
@@ -5796,14 +5796,14 @@ function getFilehandle(url, fetchImpl) {
|
|
|
5796
5796
|
function createTwoBitSequenceProvider(url, fetchImpl) {
|
|
5797
5797
|
return async (locus) => {
|
|
5798
5798
|
var _a;
|
|
5799
|
-
const reader = getReader$
|
|
5799
|
+
const reader = getReader$4(url, fetchImpl);
|
|
5800
5800
|
try {
|
|
5801
5801
|
const sequence = await reader.getSequence(locus.chr, locus.start, locus.end);
|
|
5802
5802
|
return sequence !== null && sequence !== void 0 ? sequence : '';
|
|
5803
5803
|
}
|
|
5804
5804
|
catch (err) {
|
|
5805
5805
|
if (err instanceof Error && (err.name === 'AbortError' || ((_a = err.message) === null || _a === void 0 ? void 0 : _a.includes('aborted')))) {
|
|
5806
|
-
readerCache$
|
|
5806
|
+
readerCache$5.delete(url);
|
|
5807
5807
|
filehandleCache.delete(url);
|
|
5808
5808
|
}
|
|
5809
5809
|
throw err;
|
|
@@ -9703,10 +9703,10 @@ class BgzipIndexedFasta extends IndexedFasta {
|
|
|
9703
9703
|
*/
|
|
9704
9704
|
// ─── Internal helpers ─────────────────────────────────────────────────────────
|
|
9705
9705
|
// Cache reader instances by URL to reuse parsed indices.
|
|
9706
|
-
const readerCache$
|
|
9707
|
-
function getReader$
|
|
9706
|
+
const readerCache$4 = new Map();
|
|
9707
|
+
function getReader$3(fastaURL, indexURL, compressedIndexURL, fetchImpl) {
|
|
9708
9708
|
const cacheKey = fastaURL;
|
|
9709
|
-
let reader = readerCache$
|
|
9709
|
+
let reader = readerCache$4.get(cacheKey);
|
|
9710
9710
|
if (!reader) {
|
|
9711
9711
|
const fileOpts = fetchImpl ? { fetch: fetchImpl } : undefined;
|
|
9712
9712
|
if (compressedIndexURL) {
|
|
@@ -9722,7 +9722,7 @@ function getReader$2(fastaURL, indexURL, compressedIndexURL, fetchImpl) {
|
|
|
9722
9722
|
fai: new RemoteFile(indexURL, fileOpts),
|
|
9723
9723
|
});
|
|
9724
9724
|
}
|
|
9725
|
-
readerCache$
|
|
9725
|
+
readerCache$4.set(cacheKey, reader);
|
|
9726
9726
|
}
|
|
9727
9727
|
return reader;
|
|
9728
9728
|
}
|
|
@@ -9742,14 +9742,14 @@ function createFastaSequenceProvider(fastaURL, indexURL, compressedIndexURL, fet
|
|
|
9742
9742
|
const faiURL = indexURL !== null && indexURL !== void 0 ? indexURL : fastaURL + '.fai';
|
|
9743
9743
|
return async (locus, signal) => {
|
|
9744
9744
|
var _a;
|
|
9745
|
-
const reader = getReader$
|
|
9745
|
+
const reader = getReader$3(fastaURL, faiURL, compressedIndexURL, fetchImpl);
|
|
9746
9746
|
try {
|
|
9747
9747
|
const sequence = await reader.getSequence(locus.chr, locus.start, locus.end, { signal });
|
|
9748
9748
|
return sequence !== null && sequence !== void 0 ? sequence : '';
|
|
9749
9749
|
}
|
|
9750
9750
|
catch (err) {
|
|
9751
9751
|
if (err instanceof Error && (err.name === 'AbortError' || ((_a = err.message) === null || _a === void 0 ? void 0 : _a.includes('aborted')))) {
|
|
9752
|
-
readerCache$
|
|
9752
|
+
readerCache$4.delete(fastaURL);
|
|
9753
9753
|
}
|
|
9754
9754
|
throw err;
|
|
9755
9755
|
}
|
|
@@ -9877,15 +9877,49 @@ function createGenomeSync(config) {
|
|
|
9877
9877
|
return new GenomeImpl(id, config.chromSizes, { name, sequence, cytobands: config.cytobands, nameSet: config.nameSet });
|
|
9878
9878
|
}
|
|
9879
9879
|
// ─── Sequence resolution ────��─────────────────────────────────���──────────────
|
|
9880
|
+
/**
|
|
9881
|
+
* Wrap a primary SequenceProvider with a fallback.
|
|
9882
|
+
*
|
|
9883
|
+
* On each request, tries `primary` first. If it throws (and the signal was
|
|
9884
|
+
* not aborted), tries `fallback`. If both fail, the primary error is thrown
|
|
9885
|
+
* so the caller sees the error from their configured source.
|
|
9886
|
+
*
|
|
9887
|
+
* The caching layer (createCachedSequence) wraps the combined provider,
|
|
9888
|
+
* so successful fallback results are cached normally — subsequent requests
|
|
9889
|
+
* for the same region won't re-attempt the primary.
|
|
9890
|
+
*/
|
|
9891
|
+
function createFallbackProvider(primary, fallback) {
|
|
9892
|
+
return async (locus, signal) => {
|
|
9893
|
+
try {
|
|
9894
|
+
return await primary(locus, signal);
|
|
9895
|
+
}
|
|
9896
|
+
catch (primaryErr) {
|
|
9897
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted)
|
|
9898
|
+
throw primaryErr;
|
|
9899
|
+
console.warn('[loom] Primary sequence source failed, trying fallback:', primaryErr.message);
|
|
9900
|
+
try {
|
|
9901
|
+
return await fallback(locus, signal);
|
|
9902
|
+
}
|
|
9903
|
+
catch (_a) {
|
|
9904
|
+
// Both failed — throw the primary error (more relevant to the user's config)
|
|
9905
|
+
throw primaryErr;
|
|
9906
|
+
}
|
|
9907
|
+
}
|
|
9908
|
+
};
|
|
9909
|
+
}
|
|
9880
9910
|
/**
|
|
9881
9911
|
* Resolve a sequence provider from config.
|
|
9882
9912
|
*
|
|
9883
9913
|
* Exactly one sequence source must be set. Throws if multiple are provided.
|
|
9884
9914
|
* Sources: sequenceProvider, twoBitURL, fastaURL, ucscGenome.
|
|
9885
9915
|
* When none is set, falls back to UCSC API via `id` if available.
|
|
9916
|
+
*
|
|
9917
|
+
* When twoBitURL is used and a genome ID is available, the UCSC REST API is
|
|
9918
|
+
* automatically added as a fallback — if the 2-bit server is unreachable,
|
|
9919
|
+
* the API is tried instead.
|
|
9886
9920
|
*/
|
|
9887
9921
|
function resolveSequenceProvider(config) {
|
|
9888
|
-
var _a;
|
|
9922
|
+
var _a, _b;
|
|
9889
9923
|
const sources = [
|
|
9890
9924
|
config.sequenceProvider && 'sequenceProvider',
|
|
9891
9925
|
config.twoBitURL && 'twoBitURL',
|
|
@@ -9900,13 +9934,20 @@ function resolveSequenceProvider(config) {
|
|
|
9900
9934
|
return config.sequenceProvider;
|
|
9901
9935
|
}
|
|
9902
9936
|
if (config.twoBitURL) {
|
|
9903
|
-
|
|
9937
|
+
const primary = createTwoBitSequenceProvider(config.twoBitURL, config.fetchImpl);
|
|
9938
|
+
// Add UCSC REST API fallback when a genome ID is known
|
|
9939
|
+
const genomeId = (_a = config.ucscGenome) !== null && _a !== void 0 ? _a : config.id;
|
|
9940
|
+
if (genomeId) {
|
|
9941
|
+
const fallback = (locus, signal) => fetchSequence(locus, { genome: genomeId }, signal);
|
|
9942
|
+
return createCachedSequence(createFallbackProvider(primary, fallback));
|
|
9943
|
+
}
|
|
9944
|
+
return createCachedSequence(primary);
|
|
9904
9945
|
}
|
|
9905
9946
|
if (config.fastaURL) {
|
|
9906
9947
|
return createCachedSequence(createFastaSequenceProvider(config.fastaURL, config.indexURL, config.compressedIndexURL, config.fetchImpl));
|
|
9907
9948
|
}
|
|
9908
9949
|
// UCSC API — explicit ucscGenome, or implicit via id
|
|
9909
|
-
const ucscGenome = (
|
|
9950
|
+
const ucscGenome = (_b = config.ucscGenome) !== null && _b !== void 0 ? _b : config.id;
|
|
9910
9951
|
if (ucscGenome) {
|
|
9911
9952
|
return createCachedSequence((locus, signal) => fetchSequence(locus, { genome: ucscGenome }, signal));
|
|
9912
9953
|
}
|
|
@@ -9914,13 +9955,15 @@ function resolveSequenceProvider(config) {
|
|
|
9914
9955
|
}
|
|
9915
9956
|
// ─── Pre-built singletons ��─────────��─────────────────────────────────────────
|
|
9916
9957
|
/**
|
|
9917
|
-
* Pre-built hg38 genome with
|
|
9958
|
+
* Pre-built hg38 genome with 2-bit sequence (UCSC API fallback).
|
|
9918
9959
|
*
|
|
9919
9960
|
* This is the default genome used by HeadlessGenomeBrowser when no genome
|
|
9920
|
-
* is specified.
|
|
9961
|
+
* is specified. Matches igv.js's default behavior: 2-bit file as primary
|
|
9962
|
+
* sequence source, with UCSC REST API as automatic fallback for resilience.
|
|
9921
9963
|
*/
|
|
9922
9964
|
const hg38Genome = createGenomeSync({
|
|
9923
9965
|
id: 'hg38',
|
|
9966
|
+
twoBitURL: 'https://hgdownload.soe.ucsc.edu/goldenPath/hg38/bigZips/hg38.2bit',
|
|
9924
9967
|
chromSizes: hg38ChromSizes,
|
|
9925
9968
|
});
|
|
9926
9969
|
|
|
@@ -10011,6 +10054,12 @@ const binaryFormats = new Set(['bigwig', 'bw', 'bigbed', 'bb', 'biginteract', 'b
|
|
|
10011
10054
|
function isBinaryFormat(format) {
|
|
10012
10055
|
return binaryFormats.has(format.toLowerCase());
|
|
10013
10056
|
}
|
|
10057
|
+
/** BigBed-specific formats (annotation data, not quantitative). */
|
|
10058
|
+
const bigBedFormats = new Set(['bigbed', 'bb', 'biginteract', 'biggenepred', 'bignarrowpeak']);
|
|
10059
|
+
/** Check if a format string represents a BigBed (annotation) format vs BigWig (quantitative). */
|
|
10060
|
+
function isBigBedFormat(format) {
|
|
10061
|
+
return bigBedFormats.has(format.toLowerCase());
|
|
10062
|
+
}
|
|
10014
10063
|
/**
|
|
10015
10064
|
* Check if a URL is likely tabix-indexed.
|
|
10016
10065
|
*
|
|
@@ -10050,7 +10099,7 @@ function inferIndexURL(url) {
|
|
|
10050
10099
|
* Layer 1 (Data): no DOM, no canvas.
|
|
10051
10100
|
*/
|
|
10052
10101
|
/** Known data source config types. */
|
|
10053
|
-
const KNOWN_DS_TYPES = new Set(['bigwig', 'gtx', 'ucsc', 'text', 'memory']);
|
|
10102
|
+
const KNOWN_DS_TYPES = new Set(['bigwig', 'bigbed', 'gtx', 'ucsc', 'text', 'memory']);
|
|
10054
10103
|
/** Check whether a data source config type is one of the known concrete types. */
|
|
10055
10104
|
function isKnownDataSourceType(type) {
|
|
10056
10105
|
return KNOWN_DS_TYPES.has(type);
|
|
@@ -10084,7 +10133,16 @@ function resolveDataSourceConfig(config) {
|
|
|
10084
10133
|
auth.headers = config.headers;
|
|
10085
10134
|
if (config.withCredentials)
|
|
10086
10135
|
auth.withCredentials = config.withCredentials;
|
|
10087
|
-
//
|
|
10136
|
+
// BigBed binary formats (annotation data)
|
|
10137
|
+
if (format && isBigBedFormat(format)) {
|
|
10138
|
+
log.warn(`Unknown data source type "${type}", inferred "bigbed" from URL: ${url}`);
|
|
10139
|
+
return {
|
|
10140
|
+
type: 'bigbed',
|
|
10141
|
+
url,
|
|
10142
|
+
...auth,
|
|
10143
|
+
};
|
|
10144
|
+
}
|
|
10145
|
+
// BigWig / other binary formats (quantitative data)
|
|
10088
10146
|
if (format && (format === 'bigwig' || format === 'bw' || isBinaryFormat(format))) {
|
|
10089
10147
|
log.warn(`Unknown data source type "${type}", inferred "bigwig" from URL: ${url}`);
|
|
10090
10148
|
return {
|
|
@@ -13384,14 +13442,14 @@ class BinaryParser {
|
|
|
13384
13442
|
// ─── Internal helpers ─────────────────────────────────────────────────────────
|
|
13385
13443
|
// Cache BigWig instances by URL to reuse parsed headers and trees.
|
|
13386
13444
|
// Auth-aware readers share the cache key (URL) — the fetchImpl is bound at creation.
|
|
13387
|
-
const readerCache$
|
|
13445
|
+
const readerCache$3 = new Map();
|
|
13388
13446
|
function getGmodReader(url, fetchImpl) {
|
|
13389
13447
|
const resolvedUrl = normalizeGoogleURL(url);
|
|
13390
|
-
let reader = readerCache$
|
|
13448
|
+
let reader = readerCache$3.get(resolvedUrl);
|
|
13391
13449
|
if (!reader) {
|
|
13392
13450
|
const fileOpts = fetchImpl ? { fetch: fetchImpl } : undefined;
|
|
13393
13451
|
reader = new BigWig({ filehandle: new RemoteFile(resolvedUrl, fileOpts) });
|
|
13394
|
-
readerCache$
|
|
13452
|
+
readerCache$3.set(resolvedUrl, reader);
|
|
13395
13453
|
}
|
|
13396
13454
|
return reader;
|
|
13397
13455
|
}
|
|
@@ -13512,7 +13570,7 @@ async function fetchBigWigFeatures(url, locus, options = {}) {
|
|
|
13512
13570
|
// Evict the cached reader so the next fetch gets a fresh one.
|
|
13513
13571
|
// @gmod/bbi caches the header promise; an aborted parse poisons it permanently.
|
|
13514
13572
|
if (err instanceof Error && (err.name === 'AbortError' || ((_b = err.message) === null || _b === void 0 ? void 0 : _b.includes('aborted')))) {
|
|
13515
|
-
readerCache$
|
|
13573
|
+
readerCache$3.delete(url);
|
|
13516
13574
|
}
|
|
13517
13575
|
throw err;
|
|
13518
13576
|
}
|
|
@@ -13640,6 +13698,102 @@ class BigWigDataSource {
|
|
|
13640
13698
|
}
|
|
13641
13699
|
}
|
|
13642
13700
|
|
|
13701
|
+
/**
|
|
13702
|
+
* BigBed data source — fetches BED features from BigBed binary files.
|
|
13703
|
+
*
|
|
13704
|
+
* Uses @gmod/bbi's BigBed class for binary parsing and range queries.
|
|
13705
|
+
* Returns BedFeature[] suitable for annotation track rendering.
|
|
13706
|
+
*
|
|
13707
|
+
* Mirrors igv.js BWSource behavior for bigbed format:
|
|
13708
|
+
* - Features are decoded from BED rest fields via decodeBed()
|
|
13709
|
+
* - No zoom-level summarization (unlike BigWig)
|
|
13710
|
+
* - Feature density estimation from header dataCount
|
|
13711
|
+
*
|
|
13712
|
+
* Layer 1 (Data + Layout): no DOM.
|
|
13713
|
+
*/
|
|
13714
|
+
// Cache BigBed instances by URL to reuse parsed headers and index trees.
|
|
13715
|
+
const readerCache$2 = new Map();
|
|
13716
|
+
function getReader$2(url, fetchImpl) {
|
|
13717
|
+
const resolvedUrl = normalizeGoogleURL(url);
|
|
13718
|
+
let reader = readerCache$2.get(resolvedUrl);
|
|
13719
|
+
if (!reader) {
|
|
13720
|
+
const fileOpts = fetchImpl ? { fetch: fetchImpl } : undefined;
|
|
13721
|
+
reader = new BigBed({ filehandle: new RemoteFile(resolvedUrl, fileOpts) });
|
|
13722
|
+
readerCache$2.set(resolvedUrl, reader);
|
|
13723
|
+
}
|
|
13724
|
+
return reader;
|
|
13725
|
+
}
|
|
13726
|
+
class BigBedDataSource {
|
|
13727
|
+
constructor(url, fetchImpl) {
|
|
13728
|
+
this.url = url;
|
|
13729
|
+
this.fetchImpl = fetchImpl;
|
|
13730
|
+
this.bb = getReader$2(url, fetchImpl);
|
|
13731
|
+
}
|
|
13732
|
+
/** Set a chromosome name resolver for alias resolution (e.g., "1" → "chr1"). */
|
|
13733
|
+
setChromNameResolver(resolver) {
|
|
13734
|
+
this._resolveChromName = resolver;
|
|
13735
|
+
}
|
|
13736
|
+
async fetch(locus, _bpPerPixel, signal) {
|
|
13737
|
+
var _a;
|
|
13738
|
+
const chr = this._resolveChromName
|
|
13739
|
+
? this._resolveChromName(locus.chr)
|
|
13740
|
+
: locus.chr;
|
|
13741
|
+
try {
|
|
13742
|
+
const rawFeatures = await this.bb.getFeatures(chr, locus.start, locus.end, { signal });
|
|
13743
|
+
const result = [];
|
|
13744
|
+
for (const f of rawFeatures) {
|
|
13745
|
+
const parsed = parseBigBedFeature(chr, f.start, f.end, f.rest);
|
|
13746
|
+
if (parsed)
|
|
13747
|
+
result.push(parsed);
|
|
13748
|
+
}
|
|
13749
|
+
return result;
|
|
13750
|
+
}
|
|
13751
|
+
catch (err) {
|
|
13752
|
+
// Evict poisoned reader on abort (same pattern as BigWig)
|
|
13753
|
+
if (err instanceof Error && (err.name === 'AbortError' || ((_a = err.message) === null || _a === void 0 ? void 0 : _a.includes('aborted')))) {
|
|
13754
|
+
readerCache$2.delete(normalizeGoogleURL(this.url));
|
|
13755
|
+
}
|
|
13756
|
+
throw err;
|
|
13757
|
+
}
|
|
13758
|
+
}
|
|
13759
|
+
/**
|
|
13760
|
+
* Search for a feature by name via BigBed extra index.
|
|
13761
|
+
* Returns matching features, or empty array if not found or not indexed.
|
|
13762
|
+
*/
|
|
13763
|
+
async search(name) {
|
|
13764
|
+
const header = await this.bb.getHeader();
|
|
13765
|
+
const results = await this.bb.searchExtraIndex(name);
|
|
13766
|
+
if (results.length === 0)
|
|
13767
|
+
return [];
|
|
13768
|
+
const refsByNumber = header.refsByNumber;
|
|
13769
|
+
const features = [];
|
|
13770
|
+
for (const r of results) {
|
|
13771
|
+
const chromId = r.chromId;
|
|
13772
|
+
if (chromId == null || !(refsByNumber === null || refsByNumber === void 0 ? void 0 : refsByNumber[chromId]))
|
|
13773
|
+
continue;
|
|
13774
|
+
const chr = refsByNumber[chromId].name;
|
|
13775
|
+
const parsed = parseBigBedFeature(chr, r.start, r.end, r.rest);
|
|
13776
|
+
if (parsed)
|
|
13777
|
+
features.push(parsed);
|
|
13778
|
+
}
|
|
13779
|
+
return features;
|
|
13780
|
+
}
|
|
13781
|
+
dispose() {
|
|
13782
|
+
readerCache$2.delete(normalizeGoogleURL(this.url));
|
|
13783
|
+
}
|
|
13784
|
+
}
|
|
13785
|
+
/**
|
|
13786
|
+
* Parse a BigBed feature's rest field into a BedFeature.
|
|
13787
|
+
* Reconstructs BED tokens from chr/start/end + tab-delimited rest, then uses decodeBed().
|
|
13788
|
+
*/
|
|
13789
|
+
function parseBigBedFeature(chr, start, end, rest) {
|
|
13790
|
+
const tokens = [chr, String(start), String(end)];
|
|
13791
|
+
if (rest) {
|
|
13792
|
+
tokens.push(...rest.split('\t'));
|
|
13793
|
+
}
|
|
13794
|
+
return decodeBed(tokens);
|
|
13795
|
+
}
|
|
13796
|
+
|
|
13643
13797
|
/**
|
|
13644
13798
|
* HTTP range request reader using native fetch.
|
|
13645
13799
|
* Replaces igvxhr + BufferedReader for BigWig file reading.
|
|
@@ -20551,6 +20705,8 @@ function createDataSource(config) {
|
|
|
20551
20705
|
switch (config.type) {
|
|
20552
20706
|
case 'bigwig':
|
|
20553
20707
|
return new BigWigDataSource(config.url, config.windowFunction);
|
|
20708
|
+
case 'bigbed':
|
|
20709
|
+
return new BigBedDataSource(config.url);
|
|
20554
20710
|
case 'gtx':
|
|
20555
20711
|
return new GtxDataSource(config.url, config.experimentId, config.windowFunction);
|
|
20556
20712
|
case 'ucsc':
|
|
@@ -20832,6 +20988,11 @@ registerTypeAlias('broadpeak', 'annotation');
|
|
|
20832
20988
|
registerTypeAlias('genepred', 'annotation');
|
|
20833
20989
|
registerTypeAlias('genepredext', 'annotation');
|
|
20834
20990
|
registerTypeAlias('refflat', 'annotation');
|
|
20991
|
+
// BigBed format → annotation track type aliases
|
|
20992
|
+
registerTypeAlias('bigbed', 'annotation');
|
|
20993
|
+
registerTypeAlias('bedtype', 'annotation');
|
|
20994
|
+
registerTypeAlias('biggenepred', 'annotation');
|
|
20995
|
+
registerTypeAlias('bignarrowpeak', 'annotation');
|
|
20835
20996
|
// Merged/overlay aliases
|
|
20836
20997
|
registerTypeAlias('overlay', 'merged');
|
|
20837
20998
|
// Interaction format aliases
|
|
@@ -21984,10 +22145,12 @@ function isZoomAware(track) {
|
|
|
21984
22145
|
* tracks point to the same URL/region.
|
|
21985
22146
|
*/
|
|
21986
22147
|
function dataSourceCacheKey(config) {
|
|
21987
|
-
var _a, _b, _c, _d, _e;
|
|
22148
|
+
var _a, _b, _c, _d, _e, _f;
|
|
21988
22149
|
switch (config.type) {
|
|
21989
22150
|
case 'bigwig':
|
|
21990
22151
|
return `bigwig:${config.url}:${(_a = config.windowFunction) !== null && _a !== void 0 ? _a : 'mean'}`;
|
|
22152
|
+
case 'bigbed':
|
|
22153
|
+
return `bigbed:${config.url}`;
|
|
21991
22154
|
case 'gtx':
|
|
21992
22155
|
return `gtx:${config.url}:${config.experimentId}`;
|
|
21993
22156
|
case 'ucsc':
|
|
@@ -21995,13 +22158,9 @@ function dataSourceCacheKey(config) {
|
|
|
21995
22158
|
case 'text':
|
|
21996
22159
|
return `text:${config.url}:${(_d = config.format) !== null && _d !== void 0 ? _d : ''}:${(_e = config.indexURL) !== null && _e !== void 0 ? _e : ''}`;
|
|
21997
22160
|
case 'memory':
|
|
21998
|
-
// Each memory data source is unique — no deduplication.
|
|
21999
22161
|
return `memory:${Math.random()}`;
|
|
22000
|
-
default:
|
|
22001
|
-
|
|
22002
|
-
const resolved = resolveDataSourceConfig(config);
|
|
22003
|
-
return dataSourceCacheKey(resolved);
|
|
22004
|
-
}
|
|
22162
|
+
default:
|
|
22163
|
+
return `unknown:${config.type}:${(_f = config.url) !== null && _f !== void 0 ? _f : Math.random()}`;
|
|
22005
22164
|
}
|
|
22006
22165
|
}
|
|
22007
22166
|
|
|
@@ -22670,6 +22829,9 @@ function wireDataSource(ds, ctx) {
|
|
|
22670
22829
|
if (ds instanceof BigWigDataSource || ds instanceof GtxDataSource) {
|
|
22671
22830
|
ds.setChromNameResolver(alias => ctx.genome.getChromosomeName(alias));
|
|
22672
22831
|
}
|
|
22832
|
+
else if (ds instanceof BigBedDataSource) {
|
|
22833
|
+
ds.setChromNameResolver(alias => ctx.genome.getChromosomeName(alias));
|
|
22834
|
+
}
|
|
22673
22835
|
else if (ds instanceof TextFeatureSource) {
|
|
22674
22836
|
ds.setChromNameResolver(alias => ctx.genome.getChromosomeName(alias));
|
|
22675
22837
|
if (ctx.cumulativeOffsets) {
|
|
@@ -22857,6 +23019,30 @@ function createBedTrack(ctx, url, options) {
|
|
|
22857
23019
|
searchableFields: options === null || options === void 0 ? void 0 : options.searchableFields,
|
|
22858
23020
|
};
|
|
22859
23021
|
}
|
|
23022
|
+
function createBigBedTrack(ctx, url, options) {
|
|
23023
|
+
var _a;
|
|
23024
|
+
const { canvas } = ctx.canvasProvider.createCanvas(0, 0);
|
|
23025
|
+
const track = new AnnotationTrackCanvas(canvas, {
|
|
23026
|
+
locus: ctx.locus,
|
|
23027
|
+
features: [],
|
|
23028
|
+
config: options === null || options === void 0 ? void 0 : options.config,
|
|
23029
|
+
theme: ctx.theme,
|
|
23030
|
+
canvasProvider: ctx.canvasProvider,
|
|
23031
|
+
name: options === null || options === void 0 ? void 0 : options.name,
|
|
23032
|
+
});
|
|
23033
|
+
const dataSourceConfig = { type: 'bigbed', url, ...options === null || options === void 0 ? void 0 : options.auth };
|
|
23034
|
+
const fetchImpl = resolveTrackFetchImpl(ctx, options);
|
|
23035
|
+
const { dataSource } = getOrCreateDataSource(dataSourceConfig, () => new BigBedDataSource(url, fetchImpl), ctx);
|
|
23036
|
+
return {
|
|
23037
|
+
track,
|
|
23038
|
+
dataSource,
|
|
23039
|
+
dataSourceConfig,
|
|
23040
|
+
maxTrackHeight: options === null || options === void 0 ? void 0 : options.maxTrackHeight,
|
|
23041
|
+
metadata: options === null || options === void 0 ? void 0 : options.metadata,
|
|
23042
|
+
searchable: (_a = options === null || options === void 0 ? void 0 : options.searchable) !== null && _a !== void 0 ? _a : true,
|
|
23043
|
+
searchableFields: options === null || options === void 0 ? void 0 : options.searchableFields,
|
|
23044
|
+
};
|
|
23045
|
+
}
|
|
22860
23046
|
function createInteractionTrack(ctx, url, options) {
|
|
22861
23047
|
var _a;
|
|
22862
23048
|
const { canvas } = ctx.canvasProvider.createCanvas(0, 0);
|
|
@@ -23206,10 +23392,10 @@ class HeadlessGenomeBrowser {
|
|
|
23206
23392
|
}
|
|
23207
23393
|
// ─── Track lifecycle ─────────────────────────────────────────────────────
|
|
23208
23394
|
/** Add a track with an optional data source for automatic data management. */
|
|
23209
|
-
addTrack(track, dataSource, dataSourceConfig, maxTrackHeight, order) {
|
|
23395
|
+
addTrack(track, dataSource, dataSourceConfig, maxTrackHeight, order, id) {
|
|
23210
23396
|
var _a;
|
|
23211
23397
|
this._snapshotForUndo('track-add');
|
|
23212
|
-
|
|
23398
|
+
id !== null && id !== void 0 ? id : (id = generateTrackId(track.type));
|
|
23213
23399
|
const mt = {
|
|
23214
23400
|
id,
|
|
23215
23401
|
track,
|
|
@@ -24086,11 +24272,9 @@ class HeadlessGenomeBrowser {
|
|
|
24086
24272
|
if (!this.dataSourceWorkerProvider && dataSource) {
|
|
24087
24273
|
wireDataSource(dataSource, ctx);
|
|
24088
24274
|
}
|
|
24089
|
-
this.addTrack(created.track, dataSource !== null && dataSource !== void 0 ? dataSource : undefined, (_a = created.dataSourceConfig) !== null && _a !== void 0 ? _a : undefined, undefined, (_b = created.order) !== null && _b !== void 0 ? _b : undefined);
|
|
24275
|
+
this.addTrack(created.track, dataSource !== null && dataSource !== void 0 ? dataSource : undefined, (_a = created.dataSourceConfig) !== null && _a !== void 0 ? _a : undefined, undefined, (_b = created.order) !== null && _b !== void 0 ? _b : undefined, trackConfig.id);
|
|
24090
24276
|
// Restore bookkeeping fields for round-trip serialization
|
|
24091
24277
|
const mt = this.findMT(created.track);
|
|
24092
|
-
if (trackConfig.id)
|
|
24093
|
-
mt.id = trackConfig.id;
|
|
24094
24278
|
if (created.name)
|
|
24095
24279
|
mt.name = created.name;
|
|
24096
24280
|
if (trackConfig.metadata)
|
|
@@ -24176,10 +24360,8 @@ class HeadlessGenomeBrowser {
|
|
|
24176
24360
|
wireDataSource(dataSource, ctx);
|
|
24177
24361
|
}
|
|
24178
24362
|
}
|
|
24179
|
-
this.addTrack(created.track, dataSource !== null && dataSource !== void 0 ? dataSource : undefined, dataSourceConfig !== null && dataSourceConfig !== void 0 ? dataSourceConfig : undefined, undefined, (_a = created.order) !== null && _a !== void 0 ? _a : undefined);
|
|
24363
|
+
this.addTrack(created.track, dataSource !== null && dataSource !== void 0 ? dataSource : undefined, dataSourceConfig !== null && dataSourceConfig !== void 0 ? dataSourceConfig : undefined, undefined, (_a = created.order) !== null && _a !== void 0 ? _a : undefined, trackConfig.id);
|
|
24180
24364
|
const mt = this.findMT(created.track);
|
|
24181
|
-
if (trackConfig.id)
|
|
24182
|
-
mt.id = trackConfig.id;
|
|
24183
24365
|
if (created.name)
|
|
24184
24366
|
mt.name = created.name;
|
|
24185
24367
|
if (trackConfig.metadata)
|
|
@@ -24238,6 +24420,10 @@ class HeadlessGenomeBrowser {
|
|
|
24238
24420
|
addBedTrack(url, options) {
|
|
24239
24421
|
return this.registerFactory(createBedTrack(this.factoryContext(), url, options));
|
|
24240
24422
|
}
|
|
24423
|
+
/** Add a BigBed annotation track from a URL. */
|
|
24424
|
+
addBigBedTrack(url, options) {
|
|
24425
|
+
return this.registerFactory(createBigBedTrack(this.factoryContext(), url, options));
|
|
24426
|
+
}
|
|
24241
24427
|
/** Add an interaction (arc/BEDPE) track from a URL. */
|
|
24242
24428
|
addInteractionTrack(url, options) {
|
|
24243
24429
|
return this.registerFactory(createInteractionTrack(this.factoryContext(), url, options));
|
|
@@ -28436,23 +28622,26 @@ const LoomBrowser = forwardRef(function LoomBrowser(props, ref) {
|
|
|
28436
28622
|
options.genome = genome;
|
|
28437
28623
|
const b = new GenomeBrowser(container, options);
|
|
28438
28624
|
browserRef.current = b;
|
|
28439
|
-
// Load session tracks if provided
|
|
28625
|
+
// Load session tracks if provided — a session is a complete state
|
|
28626
|
+
// snapshot, so skip default tracks to avoid duplicates.
|
|
28440
28627
|
if (session) {
|
|
28441
28628
|
b.loadSession(session);
|
|
28442
28629
|
}
|
|
28630
|
+
else {
|
|
28631
|
+
// Convenience default tracks (default: true)
|
|
28632
|
+
if (ruler !== false)
|
|
28633
|
+
b.addRuler();
|
|
28634
|
+
if (genes !== false)
|
|
28635
|
+
b.addGeneTrack();
|
|
28636
|
+
if (sequence !== false)
|
|
28637
|
+
b.addSequenceTrack();
|
|
28638
|
+
}
|
|
28443
28639
|
// Add config-driven tracks
|
|
28444
28640
|
if (trackConfigs) {
|
|
28445
28641
|
for (const tc of trackConfigs) {
|
|
28446
28642
|
b.addTrackFromConfig(tc);
|
|
28447
28643
|
}
|
|
28448
28644
|
}
|
|
28449
|
-
// Convenience default tracks (default: true)
|
|
28450
|
-
if (ruler !== false)
|
|
28451
|
-
b.addRuler();
|
|
28452
|
-
if (genes !== false)
|
|
28453
|
-
b.addGeneTrack();
|
|
28454
|
-
if (sequence !== false)
|
|
28455
|
-
b.addSequenceTrack();
|
|
28456
28645
|
// Forward locus changes to onLocusChange / onFrameChange (for controlled mode)
|
|
28457
28646
|
b.on(BrowserEvent.LocusChange, (event) => {
|
|
28458
28647
|
var _a, _b;
|