UNPKG

taxonium-component

Version:

React component for exploring large phylogenetic trees in the browser

561 lines (560 loc) 18.1 kB
import { Q as U, R as z } from "./remoteFile-H_6BTCFF.js"; import { A as _ } from "./AbortablePromiseCache-CcuMrnn7.js"; import { g as q } from "./index-CoM8QAjP.js"; import { B as M } from "./index-CpJXUZUB.js"; import { r as O } from "./rxjs-L4bS73F7.js"; import { O as I } from "./JBrowsePanel-BNE3gNW1.js"; var S = { exports: {} }, J = S.exports, L; function P() { return L || (L = 1, (function(y, t) { (function(e, s) { y.exports = s(); })(J, (function() { const e = /^[\w+.-]+:\/\//, s = /^([\w+.-]+:)\/\/([^@/#?]*@)?([^:/#?]*)(:\d+)?(\/[^#?]*)?(\?[^#]*)?(#.*)?/, i = /^file:(?:\/\/((?![a-z]:)[^/#?]*)?)?(\/?[^#?]*)(\?[^#]*)?(#.*)?/i; function r(a) { return e.test(a); } function n(a) { return a.startsWith("//"); } function h(a) { return a.startsWith("/"); } function u(a) { return a.startsWith("file:"); } function d(a) { return /^[.?#]/.test(a); } function g(a) { const c = s.exec(a); return l(c[1], c[2] || "", c[3], c[4] || "", c[5] || "/", c[6] || "", c[7] || ""); } function p(a) { const c = i.exec(a), o = c[2]; return l("file:", "", c[1] || "", "", h(o) ? o : "/" + o, c[3] || "", c[4] || ""); } function l(a, c, o, m, w, f, k) { return { scheme: a, user: c, host: o, port: m, path: w, query: f, hash: k, type: 7 }; } function b(a) { if (n(a)) { const o = g("http:" + a); return o.scheme = "", o.type = 6, o; } if (h(a)) { const o = g("http://foo.com" + a); return o.scheme = "", o.host = "", o.type = 5, o; } if (u(a)) return p(a); if (r(a)) return g(a); const c = g("http://foo.com/" + a); return c.scheme = "", c.host = "", c.type = a ? a.startsWith("?") ? 3 : a.startsWith("#") ? 2 : 4 : 1, c; } function C(a) { if (a.endsWith("/..")) return a; const c = a.lastIndexOf("/"); return a.slice(0, c + 1); } function F(a, c) { A(c, c.type), a.path === "/" ? a.path = c.path : a.path = C(c.path) + a.path; } function A(a, c) { const o = c <= 4, m = a.path.split("/"); let w = 1, f = 0, k = !1; for (let T = 1; T < m.length; T++) { const R = m[T]; if (!R) { k = !0; continue; } if (k = !1, R !== ".") { if (R === "..") { f ? (k = !0, f--, w--) : o && (m[w++] = R); continue; } m[w++] = R, f++; } } let x = ""; for (let T = 1; T < w; T++) x += "/" + m[T]; (!x || k && !x.endsWith("/..")) && (x += "/"), a.path = x; } function D(a, c) { if (!a && !c) return ""; const o = b(a); let m = o.type; if (c && m !== 7) { const f = b(c), k = f.type; switch (m) { case 1: o.hash = f.hash; // fall through case 2: o.query = f.query; // fall through case 3: case 4: F(o, f); // fall through case 5: o.user = f.user, o.host = f.host, o.port = f.port; // fall through case 6: o.scheme = f.scheme; } k > m && (m = k); } A(o, m); const w = o.query + o.hash; switch (m) { // This is impossible, because of the empty checks at the start of the function. // case UrlType.Empty: case 2: case 3: return w; case 4: { const f = o.path.slice(1); return f ? d(c || a) && !d(f) ? "./" + f + w : f + w : w || "."; } case 5: return o.path + w; default: return o.scheme + "//" + o.user + o.host + o.port + o.path + w; } } return D; })); })(S)), S.exports; } var W = P(); const $ = /* @__PURE__ */ q(W); async function E(y, t, e = {}) { const { defaultContent: s = {} } = e; try { const i = await t(y, { encoding: "utf8" }), r = new TextDecoder("utf8"); return JSON.parse(r.decode(i)); } catch (i) { if (i.code === "ENOENT" || i.status === 404 || i.message.includes("404") || i.message.includes("ENOENT")) return s; throw i; } } function N(y, t = ".") { return $(y, t); } class G { constructor({ readFile: t, cacheSize: e = 100 }) { if (this.topList = [], this.chunkCache = new _({ cache: new U({ maxSize: e }), fill: this.readChunkItems.bind(this) }), this.readFile = t, !this.readFile) throw new Error('must provide a "readFile" function'); } importExisting(t, e, s, i, r) { this.topList = t, this.attrs = e, this.start = e.makeFastGetter("Start"), this.end = e.makeFastGetter("End"), this.lazyClass = r, this.baseURL = s, this.lazyUrlTemplate = i; } binarySearch(t, e, s) { let i = -1, r = t.length, n; for (; r - i > 1; ) n = i + r >>> 1, s(t[n]) >= e ? r = n : i = n; return s === this.end ? r : i; } readChunkItems(t) { const e = N(this.lazyUrlTemplate.replaceAll(/\{Chunk\}/gi, t), this.baseURL); return E(e, this.readFile, { defaultContent: [] }); } async *iterateSublist(t, e, s, i, r, n, h) { const u = this.attrs.makeGetter("Chunk"), d = this.attrs.makeGetter("Sublist"), g = []; for (let p = this.binarySearch(t, e, r); p < t.length && p >= 0 && i * n(t[p]) < i * s; p += i) { if (t[p][0] === this.lazyClass) { const b = u(t[p]), C = this.chunkCache.get(b, b).then((F) => [F, b]); g.push(C); } else yield [t[p], h.concat(p)]; const l = d(t[p]); l && (yield* this.iterateSublist(l, e, s, i, r, n, h.concat(p))); } for (const p of g) { const [l, b] = await p; l && (yield* this.iterateSublist(l, e, s, i, r, n, [ ...h, b ])); } } async *iterate(t, e) { const s = t > e ? -1 : 1, i = t > e ? this.start : this.end, r = t > e ? this.end : this.start; this.topList.length > 0 && (yield* this.iterateSublist(this.topList, t, e, s, i, r, [0])); } async histogram(t, e, s) { const i = new Array(s); i.fill(0); const r = (e - t) / s; for await (const n of this.iterate(t, e)) { const h = Math.max(0, (this.start(n) - t) / r | 0), u = Math.min(s, (this.end(n) - t) / r | 0); for (let d = h; d <= u; d += 1) i[d] += 1; } return i; } } class H { constructor(t) { this.classes = t, this.fields = []; for (let e = 0; e < t.length; e += 1) { this.fields[e] = {}; for (let s = 0; s < t[e].attributes.length; s += 1) this.fields[e][t[e].attributes[s]] = s + 1; t[e].proto === void 0 && (t[e].proto = {}), t[e].isArrayAttr === void 0 && (t[e].isArrayAttr = {}); } } /** * @private */ attrIndices(t) { return this.classes.map((e) => e.attributes.indexOf(t) + 1 || e.attributes.indexOf(t.toLowerCase()) + 1 || void 0); } get(t, e) { if (e in this.fields[t[0]]) return t[this.fields[t[0]][e]]; const s = e.toLowerCase(); if (s in this.fields[t[0]]) return t[this.fields[t[0]][s]]; const i = this.classes[t[0]].attributes.length + 1; return i >= t.length || !(e in t[i]) ? e in this.classes[t[0]].proto ? this.classes[t[0]].proto[e] : void 0 : t[i][e]; } makeSetter(t) { return (e, s) => { this.set(e, t, s); }; } makeGetter(t) { return (e) => this.get(e, t); } makeFastGetter(t) { const e = this.attrIndices(t); return function(i) { if (e[i[0]] !== void 0) return i[e[i[0]]]; }; } // construct(self, obj, klass) { // const result = new Array(self.classes[klass].length) // Object.keys(obj).forEach(attr => { // this.set(result, attr, obj[attr]) // }) // return result // } /** * Returns fast pre-compiled getter and setter functions for use with * Arrays that use this representation. * When the returned <code>get</code> and <code>set</code> functions are * added as methods to an Array that contains data in this * representation, they provide fast access by name to the data. * * @returns {Object} <code>{ get: function() {...}, set: function(val) {...} }</code> * * @example * var accessors = attrs.accessors(); * var feature = get_feature_from_someplace(); * feature.get = accessors.get; * // print out the feature start and end * console.log( feature.get('start') + ',' + feature.get('end') ); */ accessors() { return this._accessors || (this._accessors = this._makeAccessors()), this._accessors; } /** * @private */ _makeAccessors() { const t = {}, e = { get(i) { const r = this.get.field_accessors[i.toLowerCase()]; if (r) return r.call(this); }, set(i, r) { const n = this.set.field_accessors[i]; if (n) return n.call(this, r); }, tags() { return s[this[0]] || []; } }; e.get.field_accessors = {}, e.set.field_accessors = {}, this.classes.forEach((i, r) => { (i.attributes || []).forEach((n, h) => { t[n] = t[n] || [], t[n][r] = h + 1, n = n.toLowerCase(), t[n] = t[n] || [], t[n][r] = h + 1; }); }); const s = this.classes.map((i) => i.attributes); return Object.keys(t).forEach((i) => { const r = t[i]; e.get.field_accessors[i] = r ? function() { return this[r[this[0]]]; } : function() { }; }), e; } } class Q { constructor({ urlTemplate: t, chunkSize: e, length: s, cacheSize: i = 100, readFile: r }, n) { if (this.urlTemplate = t, this.chunkSize = e, this.length = s, this.baseUrl = n === void 0 ? "" : n, this.readFile = r, !r) throw new Error("must provide readFile callback"); this.chunkCache = new _({ cache: new U({ maxSize: i }), fill: this.getChunk.bind(this) }); } /** * call the callback on one element of the array * @param i index * @param callback callback, gets called with (i, value, param) * @param param (optional) callback will get this as its last parameter */ index(t, e, s) { this.range(t, t, e, void 0, s); } /** * async generator for the elements in the range [start,end] * * @param start index of first element to call the callback on * @param end index of last element to call the callback on */ async *range(t, e) { t = Math.max(0, t), e = Math.min(e, this.length - 1); const s = Math.floor(t / this.chunkSize), i = Math.floor(e / this.chunkSize), r = []; for (let n = s; n <= i; n += 1) r.push(this.chunkCache.get(n, n)); for (const n of r) { const [h, u] = await n; yield* this.filterChunkData(t, e, h, u); } } async getChunk(t) { let e = this.urlTemplate.replaceAll(/\{Chunk\}/gi, t); this.baseUrl && (e = N(e, this.baseUrl)); const s = await E(e, this.readFile); return [t, s]; } *filterChunkData(t, e, s, i) { const r = s * this.chunkSize, n = Math.max(0, t - r), h = Math.min(e - r, this.chunkSize - 1); for (let u = n; u <= h; u += 1) yield [u + r, i[u]]; } } function j() { return this._uniqueID; } function B() { return this._parent; } function K() { return this.get("subfeatures"); } class V { constructor({ baseUrl: t, urlTemplate: e, readFile: s, cacheSize: i = 10 }) { if (this.baseUrl = t, this.urlTemplates = { root: e }, this.readFile = s, !this.readFile) throw new Error('must provide a "readFile" function argument'); this.dataRootCache = new _({ cache: new U({ maxSize: i }), fill: this.fetchDataRoot.bind(this) }); } makeNCList() { return new G({ readFile: this.readFile }); } loadNCList(t, e, s) { t.nclist.importExisting(e.intervals.nclist, t.attrs, s, e.intervals.urlTemplate, e.intervals.lazyClass); } getDataRoot(t) { return this.dataRootCache.get(t, t); } fetchDataRoot(t) { const e = N(this.urlTemplates.root.replaceAll(/{\s*refseq\s*}/g, t), this.baseUrl); return E(e, this.readFile).then((s) => ( // trackInfo = JSON.parse( trackInfo ); this.parseTrackInfo(s, e) )); } parseTrackInfo(t, e) { const s = { nclist: this.makeNCList(), stats: { featureCount: t.featureCount || 0 } }; t.intervals && (s.attrs = new H(t.intervals.classes), this.loadNCList(s, t, e)); const { histograms: i } = t; if (i != null && i.meta) { for (let r = 0; r < i.meta.length; r += 1) i.meta[r].lazyArray = new Q({ ...i.meta[r].arrayParams, readFile: this.readFile }, e); s._histograms = i; } return s._histograms && Object.keys(s._histograms).forEach((r) => { s._histograms[r].forEach((h) => { Object.keys(h).forEach((u) => { typeof h[u] == "string" && String(Number(h[u])) === h[u] && (h[u] = Number(h[u])); }); }); }), s; } async getRegionStats(t) { return (await this.getDataRoot(t.ref)).stats; } /** * fetch binned counts of feature coverage in the given region. * * @param {object} query * @param {string} query.refName reference sequence name * @param {number} query.start region start * @param {number} query.end region end * @param {number} query.numBins number of bins desired in the feature counts * @param {number} query.basesPerBin number of bp desired in each feature counting bin * @returns {object} as: * `{ bins: hist, stats: statEntry }` */ async getRegionFeatureDensities({ refName: t, start: e, end: s, numBins: i, basesPerBin: r }) { const n = await this.getDataRoot(t); if (i) r = (s - e) / i; else if (r) i = Math.ceil((s - e) / r); else throw new TypeError("numBins or basesPerBin arg required for getRegionFeatureDensities"); const u = (n._histograms.stats || []).find((l) => l.basesPerBin >= r); let d = n._histograms.meta[0]; for (let l = 0; l < n._histograms.meta.length; l += 1) r >= n._histograms.meta[l].basesPerBin && (d = n._histograms.meta[l]); let g = r / d.basesPerBin; if (g > 0.9 && Math.abs(g - Math.round(g)) < 1e-4) { const l = Math.floor(e / d.basesPerBin); g = Math.round(g); const b = []; for (let C = 0; C < i; C += 1) b[C] = 0; for await (const [C, F] of d.lazyArray.range(l, l + g * i - 1)) b[Math.floor((C - l) / g)] += F; return { bins: b, stats: u }; } return { bins: await n.nclist.histogram(e, s, i), stats: u }; } /** * Fetch features in a given region. This method is an asynchronous generator * yielding feature objects. * * @param {object} args * @param {string} args.refName reference sequence name * @param {number} args.start start of region. 0-based half-open. * @param {number} args.end end of region. 0-based half-open. * @yields {object} */ async *getFeatures({ refName: t, start: e, end: s }) { var n; const i = await this.getDataRoot(t), r = (n = i.attrs) == null ? void 0 : n.accessors(); for await (const [h, u] of i.nclist.iterate(e, s)) { if (!h.decorated) { const d = u.join(","); this.decorateFeature(r, h, `${t},${d}`); } yield h; } } // helper method to recursively add .get and .tags methods to a feature and its // subfeatures decorateFeature(t, e, s, i) { e.get = t.get, e.tags = t.tags, e._uniqueID = s, e.id = j, e._parent = i, e.parent = B, e.children = K, (e.get("subfeatures") || []).forEach((r, n) => { this.decorateFeature(t, r, `${s}-${n}`, e); }), e.decorated = !0; } } const X = { refName: "seq_id" }, Y = { seq_id: "refName" }; class v { constructor(t, e, s) { this.ncFeature = t, this.uniqueId = s || t.id(), this.parentHandle = e; } set() { throw new Error("not implemented"); } jb2TagToJb1Tag(t) { return (X[t] || t).toLowerCase(); } jb1TagToJb2Tag(t) { const e = t.toLowerCase(); return Y[e] || e; } get(t) { const e = this.ncFeature.get(this.jb2TagToJb1Tag(t)); return e && t === "subfeatures" ? e.map((s) => new v(s, this)) : e; } tags() { return this.ncFeature.tags().map((t) => this.jb1TagToJb2Tag(t)); } id() { return this.uniqueId; } parent() { return this.parentHandle; } children() { return this.get("subfeatures"); } toJSON() { const t = { uniqueId: this.id() }; for (const e of this.ncFeature.tags()) { const s = this.jb1TagToJb2Tag(e), i = this.ncFeature.get(e); s === "subfeatures" ? t.subfeatures = (i || []).map((r) => new v(r, this).toJSON()) : t[s] = i; } return t; } } class nt extends M.BaseFeatureDataAdapter { constructor(t, e, s) { super(t, e, s); const i = this.getConf("refNames"), r = this.getConf("rootUrlTemplate"); this.configRefNames = i, this.nclist = new V({ baseUrl: "", urlTemplate: r.uri, readFile: (n) => new z(String(r.baseUri ? new URL(n, r.baseUri).toString() : n)).readFile() }); } getFeatures(t, e = {}) { return O.ObservableCreate(async (s) => { const { stopToken: i } = e; for await (const r of this.nclist.getFeatures(t, e)) I.checkStopToken(i), s.next(this.wrapFeature(r)); s.complete(); }); } wrapFeature(t) { return new v(t, void 0, `${this.id}-${t.id()}`); } async hasDataForRefName(t) { var e; const s = await this.nclist.getDataRoot(t); return !!(!((e = s == null ? void 0 : s.stats) === null || e === void 0) && e.featureCount); } async getRefNames() { return this.configRefNames || []; } } export { nt as default }; //# sourceMappingURL=NCListAdapter-CskSou40.js.map