taxonium-component
Version:
React component for exploring large phylogenetic trees in the browser
561 lines (560 loc) • 18.1 kB
JavaScript
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