@genome-spy/core 0.72.0 → 0.73.0
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/LICENSE +1 -1
- package/dist/bundle/index.es.js +6779 -5393
- package/dist/bundle/index.js +133 -121
- package/dist/schema.json +281 -17
- package/dist/src/data/formats/bed.d.ts +8 -0
- package/dist/src/data/formats/bed.d.ts.map +1 -0
- package/dist/src/data/formats/bed.js +53 -0
- package/dist/src/data/formats/bedpe.d.ts +8 -0
- package/dist/src/data/formats/bedpe.d.ts.map +1 -0
- package/dist/src/data/formats/bedpe.js +160 -0
- package/dist/src/data/sources/dataUtils.d.ts +16 -0
- package/dist/src/data/sources/dataUtils.d.ts.map +1 -1
- package/dist/src/data/sources/dataUtils.js +53 -3
- package/dist/src/data/sources/urlSource.d.ts +4 -0
- package/dist/src/data/sources/urlSource.d.ts.map +1 -1
- package/dist/src/data/sources/urlSource.js +133 -14
- package/dist/src/genome/assemblyPreflight.d.ts +31 -0
- package/dist/src/genome/assemblyPreflight.d.ts.map +1 -0
- package/dist/src/genome/assemblyPreflight.js +99 -0
- package/dist/src/genome/genome.d.ts +2 -2
- package/dist/src/genome/genome.d.ts.map +1 -1
- package/dist/src/genome/genome.js +4 -0
- package/dist/src/genome/genomeStore.d.ts +34 -3
- package/dist/src/genome/genomeStore.d.ts.map +1 -1
- package/dist/src/genome/genomeStore.js +409 -18
- package/dist/src/genome/rootGenomeConfig.d.ts +26 -0
- package/dist/src/genome/rootGenomeConfig.d.ts.map +1 -0
- package/dist/src/genome/rootGenomeConfig.js +94 -0
- package/dist/src/genomeSpy/interactionController.d.ts +5 -1
- package/dist/src/genomeSpy/interactionController.d.ts.map +1 -1
- package/dist/src/genomeSpy/interactionController.js +244 -29
- package/dist/src/genomeSpy/renderCoordinator.js +1 -1
- package/dist/src/genomeSpy.d.ts +13 -3
- package/dist/src/genomeSpy.d.ts.map +1 -1
- package/dist/src/genomeSpy.js +81 -7
- package/dist/src/gl/canvasSizeHelper.d.ts +74 -0
- package/dist/src/gl/canvasSizeHelper.d.ts.map +1 -0
- package/dist/src/gl/canvasSizeHelper.js +203 -0
- package/dist/src/gl/webGLHelper.d.ts +25 -11
- package/dist/src/gl/webGLHelper.d.ts.map +1 -1
- package/dist/src/gl/webGLHelper.js +59 -33
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +5 -2
- package/dist/src/marks/link.d.ts.map +1 -1
- package/dist/src/marks/link.js +5 -3
- package/dist/src/marks/mark.d.ts.map +1 -1
- package/dist/src/marks/mark.js +6 -1
- package/dist/src/scales/domainPlanner.d.ts +34 -3
- package/dist/src/scales/domainPlanner.d.ts.map +1 -1
- package/dist/src/scales/domainPlanner.js +247 -26
- package/dist/src/scales/scaleInstanceManager.d.ts +2 -1
- package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -1
- package/dist/src/scales/scaleInstanceManager.js +10 -11
- package/dist/src/scales/scaleInteractionController.d.ts.map +1 -1
- package/dist/src/scales/scaleInteractionController.js +16 -14
- package/dist/src/scales/scaleResolution.d.ts +16 -0
- package/dist/src/scales/scaleResolution.d.ts.map +1 -1
- package/dist/src/scales/scaleResolution.js +314 -54
- package/dist/src/scales/scaleResolutionTestUtils.d.ts +21 -0
- package/dist/src/scales/scaleResolutionTestUtils.d.ts.map +1 -0
- package/dist/src/scales/scaleResolutionTestUtils.js +33 -0
- package/dist/src/scales/selectionDomainUtils.d.ts +22 -0
- package/dist/src/scales/selectionDomainUtils.d.ts.map +1 -0
- package/dist/src/scales/selectionDomainUtils.js +79 -0
- package/dist/src/scales/zoomDomainUtils.d.ts +18 -0
- package/dist/src/scales/zoomDomainUtils.d.ts.map +1 -0
- package/dist/src/scales/zoomDomainUtils.js +69 -0
- package/dist/src/screenshotHarness.d.ts +16 -0
- package/dist/src/screenshotHarness.d.ts.map +1 -0
- package/dist/src/screenshotHarness.js +242 -0
- package/dist/src/singlePageApp.js +1 -1
- package/dist/src/spec/data.d.ts +23 -3
- package/dist/src/spec/genome.d.ts +22 -2
- package/dist/src/spec/parameter.d.ts +39 -2
- package/dist/src/spec/root.d.ts +20 -1
- package/dist/src/spec/scale.d.ts +41 -5
- package/dist/src/styles/genome-spy.css +8 -0
- package/dist/src/styles/genome-spy.css.d.ts +1 -1
- package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
- package/dist/src/styles/genome-spy.css.js +8 -0
- package/dist/src/tooltip/dataTooltipHandler.js +59 -10
- package/dist/src/types/embedApi.d.ts +19 -0
- package/dist/src/utils/inferSpecBaseUrl.d.ts +14 -0
- package/dist/src/utils/inferSpecBaseUrl.d.ts.map +1 -0
- package/dist/src/utils/inferSpecBaseUrl.js +73 -0
- package/dist/src/utils/interactionEvent.d.ts +53 -3
- package/dist/src/utils/interactionEvent.d.ts.map +1 -1
- package/dist/src/utils/interactionEvent.js +62 -1
- package/dist/src/view/containerMutationHelper.d.ts.map +1 -1
- package/dist/src/view/containerMutationHelper.js +8 -0
- package/dist/src/view/dataReadiness.d.ts +2 -2
- package/dist/src/view/dataReadiness.d.ts.map +1 -1
- package/dist/src/view/dataReadiness.js +63 -58
- package/dist/src/view/facetView.js +1 -1
- package/dist/src/view/gridView/gridChild.d.ts +7 -0
- package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
- package/dist/src/view/gridView/gridChild.js +180 -11
- package/dist/src/view/gridView/gridView.d.ts.map +1 -1
- package/dist/src/view/gridView/gridView.js +60 -17
- package/dist/src/view/zoom.d.ts +14 -2
- package/dist/src/view/zoom.d.ts.map +1 -1
- package/dist/src/view/zoom.js +373 -76
- package/package.json +4 -2
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const blankLinePattern = /^\s*$/;
|
|
2
|
+
const controlLinePattern = /^\s*(?:browser\b|track\b|#)/;
|
|
3
|
+
|
|
4
|
+
const defaultColumns = [
|
|
5
|
+
"chrom1",
|
|
6
|
+
"start1",
|
|
7
|
+
"end1",
|
|
8
|
+
"chrom2",
|
|
9
|
+
"start2",
|
|
10
|
+
"end2",
|
|
11
|
+
"name",
|
|
12
|
+
"score",
|
|
13
|
+
"strand1",
|
|
14
|
+
"strand2",
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const requiredColumns = defaultColumns.slice(0, 6);
|
|
18
|
+
|
|
19
|
+
const normalizeNone = (/** @type {string} */ value) => value;
|
|
20
|
+
const normalizeStringSentinel = (/** @type {string} */ value) =>
|
|
21
|
+
value == "." ? null : value;
|
|
22
|
+
const normalizeStrand = (/** @type {string} */ value) => {
|
|
23
|
+
if (value == "+") {
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
if (value == "-") {
|
|
27
|
+
return -1;
|
|
28
|
+
}
|
|
29
|
+
return 0;
|
|
30
|
+
};
|
|
31
|
+
const normalizeCoordinate = (/** @type {string} */ value) => {
|
|
32
|
+
if (value == "." || value == "-1" || value == "") {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const parsed = Number(value);
|
|
36
|
+
return Number.isInteger(parsed) ? parsed : null;
|
|
37
|
+
};
|
|
38
|
+
const normalizeScore = (/** @type {string} */ value) => {
|
|
39
|
+
if (value == "." || value == "") {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const parsed = Number(value);
|
|
43
|
+
return Number.isNaN(parsed) ? value : parsed;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** @type {Record<string, (value: string) => any>} */
|
|
47
|
+
const columnNormalizers = {
|
|
48
|
+
chrom1: normalizeStringSentinel,
|
|
49
|
+
chrom2: normalizeStringSentinel,
|
|
50
|
+
name: normalizeStringSentinel,
|
|
51
|
+
strand1: normalizeStrand,
|
|
52
|
+
strand2: normalizeStrand,
|
|
53
|
+
start1: normalizeCoordinate,
|
|
54
|
+
end1: normalizeCoordinate,
|
|
55
|
+
start2: normalizeCoordinate,
|
|
56
|
+
end2: normalizeCoordinate,
|
|
57
|
+
score: normalizeScore,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* BEDPE has fixed required leading columns; optional tail columns vary by producer.
|
|
62
|
+
* Detect a header row by matching only the required prefix.
|
|
63
|
+
*
|
|
64
|
+
* @param {string[]} row
|
|
65
|
+
*/
|
|
66
|
+
function looksLikeHeaderRow(row) {
|
|
67
|
+
if (row.length < requiredColumns.length) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < requiredColumns.length; i++) {
|
|
72
|
+
if (row[i] != requiredColumns[i]) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {string} data
|
|
82
|
+
* @param {{ columns?: string[] }} [format]
|
|
83
|
+
*/
|
|
84
|
+
export default function bedpe(data, format = {}) {
|
|
85
|
+
const lines = data.split(/\r?\n/);
|
|
86
|
+
const explicitColumns = format.columns;
|
|
87
|
+
let dataStarted = false;
|
|
88
|
+
let columnsInitialized = false;
|
|
89
|
+
let lineNumber = 0;
|
|
90
|
+
|
|
91
|
+
/** @type {string[]} */
|
|
92
|
+
const columns = [];
|
|
93
|
+
/** @type {((value: string) => any)[]} */
|
|
94
|
+
const normalizers = [];
|
|
95
|
+
/** @type {Record<string, any>[]} */
|
|
96
|
+
const rows = [];
|
|
97
|
+
|
|
98
|
+
for (const line of lines) {
|
|
99
|
+
lineNumber++;
|
|
100
|
+
|
|
101
|
+
if (line.length == 0) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!dataStarted) {
|
|
106
|
+
if (blankLinePattern.test(line) || controlLinePattern.test(line)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
dataStarted = true;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (blankLinePattern.test(line)) {
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const row = line.split("\t");
|
|
117
|
+
|
|
118
|
+
if (!columnsInitialized) {
|
|
119
|
+
const baseColumns = explicitColumns
|
|
120
|
+
? explicitColumns
|
|
121
|
+
: looksLikeHeaderRow(row)
|
|
122
|
+
? row
|
|
123
|
+
: defaultColumns;
|
|
124
|
+
|
|
125
|
+
for (const column of baseColumns) {
|
|
126
|
+
columns.push(column);
|
|
127
|
+
normalizers.push(columnNormalizers[column] ?? normalizeNone);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
columnsInitialized = true;
|
|
131
|
+
|
|
132
|
+
if (!explicitColumns && baseColumns == row) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
while (columns.length < row.length) {
|
|
138
|
+
columns.push("field" + (columns.length + 1));
|
|
139
|
+
normalizers.push(normalizeNone);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (row.length < requiredColumns.length) {
|
|
143
|
+
throw new Error(
|
|
144
|
+
`BEDPE line ${lineNumber} has ${row.length} columns, expected at least ${requiredColumns.length}.`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** @type {Record<string, any>} */
|
|
149
|
+
const datum = {};
|
|
150
|
+
|
|
151
|
+
for (let i = 0; i < row.length; i++) {
|
|
152
|
+
const columnName = columns[i];
|
|
153
|
+
datum[columnName] = normalizers[i](row[i]);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
rows.push(datum);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return rows;
|
|
160
|
+
}
|
|
@@ -17,8 +17,16 @@ export function getFormat(params: import("../../spec/data.js").DataSource, urls?
|
|
|
17
17
|
type?: "json";
|
|
18
18
|
property?: string;
|
|
19
19
|
parse?: import("../../spec/data.js").Parse | null;
|
|
20
|
+
} | {
|
|
21
|
+
type: "bed";
|
|
22
|
+
parse?: import("../../spec/data.js").Parse | null;
|
|
23
|
+
} | {
|
|
24
|
+
type: "bedpe";
|
|
25
|
+
columns?: string[];
|
|
26
|
+
parse?: import("../../spec/data.js").Parse | null;
|
|
20
27
|
} | {
|
|
21
28
|
type: string;
|
|
29
|
+
parse?: import("../../spec/data.js").Parse | null;
|
|
22
30
|
};
|
|
23
31
|
/**
|
|
24
32
|
* @param {string} type
|
|
@@ -29,6 +37,10 @@ export function responseType(type: string): string;
|
|
|
29
37
|
* @param {string | string[]} url
|
|
30
38
|
*/
|
|
31
39
|
export function extractTypeFromUrl(url: string | string[]): string;
|
|
40
|
+
/**
|
|
41
|
+
* @param {string} url
|
|
42
|
+
*/
|
|
43
|
+
export function hasGzipExtension(url: string): boolean;
|
|
32
44
|
/**
|
|
33
45
|
* @param {import("../../spec/data.js").DataFormat} dataFormat
|
|
34
46
|
* @return {dataFormat is import("../../spec/data.js").CsvDataFormat}
|
|
@@ -50,6 +62,10 @@ export function isJsonDataFormat(dataFormat: import("../../spec/data.js").DataFo
|
|
|
50
62
|
* @return {dataSource is import("../../spec/data.js").UrlData}
|
|
51
63
|
*/
|
|
52
64
|
export function isUrlData(dataSource: import("../../spec/data.js").DataSource): dataSource is import("../../spec/data.js").UrlData;
|
|
65
|
+
/**
|
|
66
|
+
* @param {string | undefined} type
|
|
67
|
+
*/
|
|
68
|
+
export function isAutoParseFormat(type: string | undefined): boolean;
|
|
53
69
|
export function makeWrapper(d: any): ((x: import("../../spec/channel.js").Scalar) => {
|
|
54
70
|
data: import("../../spec/channel.js").Scalar;
|
|
55
71
|
}) | ((x: import("../flowNode.js").Datum) => import("../flowNode.js").Datum);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataUtils.d.ts","sourceRoot":"","sources":["../../../../src/data/sources/dataUtils.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dataUtils.d.ts","sourceRoot":"","sources":["../../../../src/data/sources/dataUtils.js"],"names":[],"mappings":"AAMA;;;;;;;GAOG;AACH,kCAJW,OAAO,oBAAoB,EAAE,UAAU,SAEvC,MAAM,GAAG,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;EAsB3B;AAED;;;GAGG;AACH,mCAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;GAEG;AACH,wCAFW,MAAM,GAAG,MAAM,EAAE,UA2B3B;AAED;;GAEG;AACH,sCAFW,MAAM,WAWhB;AAkBD;;;GAGG;AACH,4CAHW,OAAO,oBAAoB,EAAE,UAAU,GACtC,UAAU,IAAI,OAAO,oBAAoB,EAAE,aAAa,CAInE;AAED;;;GAGG;AACH,4CAHW,OAAO,oBAAoB,EAAE,UAAU,GACtC,UAAU,IAAI,OAAO,oBAAoB,EAAE,aAAa,CAInE;AAED;;;GAGG;AACH,6CAHW,OAAO,oBAAoB,EAAE,UAAU,GACtC,UAAU,IAAI,OAAO,oBAAoB,EAAE,cAAc,CAIpE;AAED;;;;GAIG;AACH,sCAHW,OAAO,oBAAoB,EAAE,UAAU,GACtC,UAAU,IAAI,OAAO,oBAAoB,EAAE,OAAO,CAI7D;AAED;;GAEG;AACH,wCAFW,MAAM,GAAG,SAAS,WAI5B;AA/CM,+BAAgC,GAAG,QAI3B,OAAO,uBAAuB,EAAE,MAAM;;UAGtB,OAAO,gBAAgB,EAAE,KAAK,qCANR"}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { formats } from "vega-loader";
|
|
2
2
|
import { isInlineData } from "./inlineSource.js";
|
|
3
3
|
|
|
4
|
+
const autoParseFormats = new Set(["csv", "tsv", "dsv"]);
|
|
5
|
+
const compressionExtensions = new Set(["gz", "bgz", "bgzf"]);
|
|
6
|
+
|
|
4
7
|
/**
|
|
5
8
|
* Validates data source params, infers format if not specified explicitly,
|
|
6
9
|
* returns a complete DataSource params object.
|
|
@@ -16,8 +19,10 @@ export function getFormat(params, urls = []) {
|
|
|
16
19
|
const format = { ...params.format };
|
|
17
20
|
|
|
18
21
|
format.type ??= isUrlData(params) && extractTypeFromUrl(urls);
|
|
19
|
-
|
|
20
|
-
|
|
22
|
+
if (format.parse === undefined && isAutoParseFormat(format.type)) {
|
|
23
|
+
// @ts-ignore TODO: Fix typing
|
|
24
|
+
format.parse = "auto";
|
|
25
|
+
}
|
|
21
26
|
|
|
22
27
|
if (!format.type) {
|
|
23
28
|
throw new Error(
|
|
@@ -46,8 +51,46 @@ export function extractTypeFromUrl(url) {
|
|
|
46
51
|
}
|
|
47
52
|
|
|
48
53
|
if (url) {
|
|
49
|
-
|
|
54
|
+
const path = stripUrlQueryAndHash(url).split("/").pop()?.toLowerCase();
|
|
55
|
+
|
|
56
|
+
if (!path) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const extensions = path.split(".");
|
|
61
|
+
while (
|
|
62
|
+
extensions.length > 1 &&
|
|
63
|
+
compressionExtensions.has(extensions.at(-1))
|
|
64
|
+
) {
|
|
65
|
+
extensions.pop();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const extension = extensions.at(-1);
|
|
69
|
+
if (extension && formats(extension)) {
|
|
70
|
+
return extension;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {string} url
|
|
77
|
+
*/
|
|
78
|
+
export function hasGzipExtension(url) {
|
|
79
|
+
const path = stripUrlQueryAndHash(url).split("/").pop()?.toLowerCase();
|
|
80
|
+
|
|
81
|
+
if (!path) {
|
|
82
|
+
return false;
|
|
50
83
|
}
|
|
84
|
+
|
|
85
|
+
const extension = path.split(".").at(-1);
|
|
86
|
+
return !!extension && compressionExtensions.has(extension);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {string} url
|
|
91
|
+
*/
|
|
92
|
+
function stripUrlQueryAndHash(url) {
|
|
93
|
+
return url.replace(/[?#].*$/, "");
|
|
51
94
|
}
|
|
52
95
|
|
|
53
96
|
export const makeWrapper = (/** @type {any} */ d) =>
|
|
@@ -91,3 +134,10 @@ export function isJsonDataFormat(dataFormat) {
|
|
|
91
134
|
export function isUrlData(dataSource) {
|
|
92
135
|
return "url" in dataSource;
|
|
93
136
|
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* @param {string | undefined} type
|
|
140
|
+
*/
|
|
141
|
+
export function isAutoParseFormat(type) {
|
|
142
|
+
return autoParseFormats.has(type);
|
|
143
|
+
}
|
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
* @returns {data is import("../../spec/data.js").UrlData}
|
|
4
4
|
*/
|
|
5
5
|
export function isUrlData(data: Partial<import("../../spec/data.js").Data>): data is import("../../spec/data.js").UrlData;
|
|
6
|
+
/**
|
|
7
|
+
* Loads eager data from URLs and transparently decompresses gzip-compatible
|
|
8
|
+
* payloads before handing them to the registered format reader.
|
|
9
|
+
*/
|
|
6
10
|
export default class UrlSource extends DataSource {
|
|
7
11
|
/**
|
|
8
12
|
* @param {import("../../spec/data.js").UrlData} params
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"urlSource.d.ts","sourceRoot":"","sources":["../../../../src/data/sources/urlSource.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"urlSource.d.ts","sourceRoot":"","sources":["../../../../src/data/sources/urlSource.js"],"names":[],"mappings":"AA+IA;;;GAGG;AACH,gCAHW,OAAO,CAAC,OAAO,oBAAoB,EAAE,IAAI,CAAC,GACxC,IAAI,IAAI,OAAO,oBAAoB,EAAE,OAAO,CAIxD;AAzID;;;GAGG;AACH;IACI;;;OAGG;IACH,oBAHW,OAAO,oBAAoB,EAAE,OAAO,QACpC,OAAO,oBAAoB,EAAE,OAAO,EAc9C;IATG,6CAMC;IAED,gBAAiC;;CA6GxC;uBA3IsB,iBAAiB"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { read } from "vega-loader";
|
|
2
|
-
import { getFormat, responseType } from "./dataUtils.js";
|
|
2
|
+
import { getFormat, hasGzipExtension, responseType } from "./dataUtils.js";
|
|
3
3
|
import DataSource from "./dataSource.js";
|
|
4
4
|
import {
|
|
5
5
|
activateExprRefProps,
|
|
@@ -7,14 +7,13 @@ import {
|
|
|
7
7
|
} from "../../paramRuntime/paramUtils.js";
|
|
8
8
|
import { concatUrl } from "../../utils/url.js";
|
|
9
9
|
|
|
10
|
+
const gzipMimeTypes = new Set(["application/gzip", "application/x-gzip"]);
|
|
11
|
+
const textDecoder = new TextDecoder();
|
|
12
|
+
|
|
10
13
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
14
|
+
* Loads eager data from URLs and transparently decompresses gzip-compatible
|
|
15
|
+
* payloads before handing them to the registered format reader.
|
|
13
16
|
*/
|
|
14
|
-
export function isUrlData(data) {
|
|
15
|
-
return "url" in data;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
17
|
export default class UrlSource extends DataSource {
|
|
19
18
|
/**
|
|
20
19
|
* @param {import("../../spec/data.js").UrlData} params
|
|
@@ -48,6 +47,7 @@ export default class UrlSource extends DataSource {
|
|
|
48
47
|
*/
|
|
49
48
|
async #loadUrlsFromFile(props) {
|
|
50
49
|
const listUrl = concatUrl(this.baseUrl, props.urlsFromFile);
|
|
50
|
+
const format = { type: props.type ?? "tsv" };
|
|
51
51
|
|
|
52
52
|
const result = await fetch(listUrl);
|
|
53
53
|
|
|
@@ -56,10 +56,14 @@ export default class UrlSource extends DataSource {
|
|
|
56
56
|
`Cannot load "${listUrl}": ${result.status} ${result.statusText}`
|
|
57
57
|
);
|
|
58
58
|
}
|
|
59
|
-
const
|
|
59
|
+
const content = await readResponseBody(
|
|
60
|
+
result,
|
|
61
|
+
listUrl,
|
|
62
|
+
responseType(format.type)
|
|
63
|
+
);
|
|
60
64
|
|
|
61
65
|
const files = /** @type {string[] | {url: string}[]} */ (
|
|
62
|
-
read(
|
|
66
|
+
read(content, format)
|
|
63
67
|
)
|
|
64
68
|
.map((u) => (typeof u === "string" ? u : u.url))
|
|
65
69
|
.map((u) => concatUrl(listUrl, u));
|
|
@@ -94,11 +98,7 @@ export default class UrlSource extends DataSource {
|
|
|
94
98
|
if (!result.ok) {
|
|
95
99
|
throw new Error(`${result.status} ${result.statusText}`);
|
|
96
100
|
}
|
|
97
|
-
|
|
98
|
-
return typeof result[type] == "function"
|
|
99
|
-
? // @ts-ignore
|
|
100
|
-
result[type]()
|
|
101
|
-
: result.text();
|
|
101
|
+
return await readResponseBody(result, url, type);
|
|
102
102
|
} catch (e) {
|
|
103
103
|
throw new Error(
|
|
104
104
|
`Could not load data: ${url}. Reason: ${e.message}`
|
|
@@ -140,3 +140,122 @@ export default class UrlSource extends DataSource {
|
|
|
140
140
|
this.complete();
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @param {Partial<import("../../spec/data.js").Data>} data
|
|
146
|
+
* @returns {data is import("../../spec/data.js").UrlData}
|
|
147
|
+
*/
|
|
148
|
+
export function isUrlData(data) {
|
|
149
|
+
return "url" in data;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param {Uint8Array} bytes
|
|
154
|
+
*/
|
|
155
|
+
function hasGzipMagic(bytes) {
|
|
156
|
+
return (
|
|
157
|
+
bytes.length >= 10 &&
|
|
158
|
+
bytes[0] == 0x1f &&
|
|
159
|
+
bytes[1] == 0x8b &&
|
|
160
|
+
bytes[2] == 0x08 &&
|
|
161
|
+
(bytes[3] & 0xe0) == 0
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* @param {string | null} contentType
|
|
167
|
+
*/
|
|
168
|
+
function isGzipMimeType(contentType) {
|
|
169
|
+
if (!contentType) {
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return gzipMimeTypes.has(contentType.split(";")[0].trim().toLowerCase());
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* @param {string | null} contentEncoding
|
|
178
|
+
*/
|
|
179
|
+
function hasGzipContentEncoding(contentEncoding) {
|
|
180
|
+
if (!contentEncoding) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return contentEncoding
|
|
185
|
+
.toLowerCase()
|
|
186
|
+
.split(",")
|
|
187
|
+
.some((encoding) => encoding.trim() == "gzip");
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* @param {Uint8Array} bytes
|
|
192
|
+
* @returns {ArrayBuffer}
|
|
193
|
+
*/
|
|
194
|
+
function toArrayBuffer(bytes) {
|
|
195
|
+
return new Uint8Array(bytes).buffer;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* @param {Uint8Array} bytes
|
|
200
|
+
*/
|
|
201
|
+
async function decompressGzip(bytes) {
|
|
202
|
+
if (typeof DecompressionStream != "function") {
|
|
203
|
+
throw new Error(
|
|
204
|
+
"Gzip-compressed URL data requires DecompressionStream support."
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const body = new Response(toArrayBuffer(bytes)).body;
|
|
209
|
+
if (!body) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
"Cannot create a readable stream for gzip decompression."
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const stream = body.pipeThrough(new DecompressionStream("gzip"));
|
|
216
|
+
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* @param {Response} response
|
|
221
|
+
* @param {string} url
|
|
222
|
+
* @param {string} type
|
|
223
|
+
*/
|
|
224
|
+
async function readResponseBody(response, url, type) {
|
|
225
|
+
const gzipHint =
|
|
226
|
+
hasGzipExtension(url) ||
|
|
227
|
+
isGzipMimeType(response.headers.get("content-type")) ||
|
|
228
|
+
hasGzipContentEncoding(response.headers.get("content-encoding"));
|
|
229
|
+
|
|
230
|
+
if (!gzipHint) {
|
|
231
|
+
return readResponseUsingType(response, type);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
235
|
+
const bodyBytes = hasGzipMagic(bytes) ? await decompressGzip(bytes) : bytes;
|
|
236
|
+
return deserializeBytes(bodyBytes, type);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* @param {Response} response
|
|
241
|
+
* @param {string} type
|
|
242
|
+
*/
|
|
243
|
+
function readResponseUsingType(response, type) {
|
|
244
|
+
// @ts-ignore
|
|
245
|
+
return typeof response[type] == "function"
|
|
246
|
+
? // @ts-ignore
|
|
247
|
+
response[type]()
|
|
248
|
+
: response.text();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* @param {Uint8Array} bytes
|
|
253
|
+
* @param {string} type
|
|
254
|
+
*/
|
|
255
|
+
function deserializeBytes(bytes, type) {
|
|
256
|
+
if (type == "arrayBuffer") {
|
|
257
|
+
return toArrayBuffer(bytes);
|
|
258
|
+
} else {
|
|
259
|
+
return textDecoder.decode(bytes);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../spec/scale.js").Scale["assembly"]} AssemblyReference
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {object} AssemblyPreflightResult
|
|
6
|
+
* @prop {AssemblyReference[]} assemblies
|
|
7
|
+
* @prop {boolean} needsDefaultAssembly
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* @param {import("../view/view.js").default} viewRoot
|
|
11
|
+
* @returns {AssemblyPreflightResult}
|
|
12
|
+
*/
|
|
13
|
+
export function collectAssembliesFromViewHierarchy(viewRoot: import("../view/view.js").default): AssemblyPreflightResult;
|
|
14
|
+
/**
|
|
15
|
+
* Ensures that all assemblies required by the given view hierarchy are loaded
|
|
16
|
+
* before any scale initialization path can run.
|
|
17
|
+
*
|
|
18
|
+
* Reminder: call this immediately after view hierarchy creation and before
|
|
19
|
+
* operations that can implicitly initialize scales (for example, step-based
|
|
20
|
+
* size resolution, dynamic opacity setup, or encoder initialization).
|
|
21
|
+
*
|
|
22
|
+
* @param {import("../view/view.js").default} viewRoot
|
|
23
|
+
* @param {import("./genomeStore.js").default} genomeStore
|
|
24
|
+
*/
|
|
25
|
+
export function ensureAssembliesForView(viewRoot: import("../view/view.js").default, genomeStore: import("./genomeStore.js").default): Promise<void>;
|
|
26
|
+
export type AssemblyReference = import("../spec/scale.js").Scale["assembly"];
|
|
27
|
+
export type AssemblyPreflightResult = {
|
|
28
|
+
assemblies: AssemblyReference[];
|
|
29
|
+
needsDefaultAssembly: boolean;
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=assemblyPreflight.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemblyPreflight.d.ts","sourceRoot":"","sources":["../../../src/genome/assemblyPreflight.js"],"names":[],"mappings":"AAEA;;GAEG;AAEH;;;;GAIG;AAEH;;;GAGG;AACH,6DAHW,OAAO,iBAAiB,EAAE,OAAO,GAC/B,uBAAuB,CAsBnC;AAED;;;;;;;;;;GAUG;AACH,kDAHW,OAAO,iBAAiB,EAAE,OAAO,eACjC,OAAO,kBAAkB,EAAE,OAAO,iBAgB5C;gCA5DY,OAAO,kBAAkB,EAAE,KAAK,CAAC,UAAU,CAAC;;gBAK/C,iBAAiB,EAAE;0BACnB,OAAO"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { visitAddressableViews } from "../view/viewSelectors.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import("../spec/scale.js").Scale["assembly"]} AssemblyReference
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @typedef {object} AssemblyPreflightResult
|
|
9
|
+
* @prop {AssemblyReference[]} assemblies
|
|
10
|
+
* @prop {boolean} needsDefaultAssembly
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {import("../view/view.js").default} viewRoot
|
|
15
|
+
* @returns {AssemblyPreflightResult}
|
|
16
|
+
*/
|
|
17
|
+
export function collectAssembliesFromViewHierarchy(viewRoot) {
|
|
18
|
+
/** @type {AssemblyReference[]} */
|
|
19
|
+
const assemblies = [];
|
|
20
|
+
let needsDefaultAssembly = false;
|
|
21
|
+
|
|
22
|
+
const resolutions = collectRelevantScaleResolutions(viewRoot);
|
|
23
|
+
for (const resolution of resolutions) {
|
|
24
|
+
const requirement = resolution.getAssemblyRequirement();
|
|
25
|
+
if (requirement.assembly) {
|
|
26
|
+
assemblies.push(requirement.assembly);
|
|
27
|
+
}
|
|
28
|
+
if (requirement.needsDefaultAssembly) {
|
|
29
|
+
needsDefaultAssembly = true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
assemblies,
|
|
35
|
+
needsDefaultAssembly,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Ensures that all assemblies required by the given view hierarchy are loaded
|
|
41
|
+
* before any scale initialization path can run.
|
|
42
|
+
*
|
|
43
|
+
* Reminder: call this immediately after view hierarchy creation and before
|
|
44
|
+
* operations that can implicitly initialize scales (for example, step-based
|
|
45
|
+
* size resolution, dynamic opacity setup, or encoder initialization).
|
|
46
|
+
*
|
|
47
|
+
* @param {import("../view/view.js").default} viewRoot
|
|
48
|
+
* @param {import("./genomeStore.js").default} genomeStore
|
|
49
|
+
*/
|
|
50
|
+
export async function ensureAssembliesForView(viewRoot, genomeStore) {
|
|
51
|
+
const { assemblies, needsDefaultAssembly } =
|
|
52
|
+
collectAssembliesFromViewHierarchy(viewRoot);
|
|
53
|
+
if (needsDefaultAssembly) {
|
|
54
|
+
const defaultAssembly = genomeStore.getDefaultAssemblyName();
|
|
55
|
+
if (!defaultAssembly) {
|
|
56
|
+
throw new Error(
|
|
57
|
+
"No default assembly has been configured. Set root `assembly`, define exactly one entry in root `genomes`, or set `scale.assembly` on each locus scale."
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
assemblies.push(defaultAssembly);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
await genomeStore.ensureAssemblies(assemblies);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Collects scale resolutions that can influence user-authored views while
|
|
68
|
+
* excluding internal helper subtrees (axis/grid/etc.).
|
|
69
|
+
*
|
|
70
|
+
* Reminder: implicit root wrappers are marked non-addressable, so include the
|
|
71
|
+
* root ancestry explicitly before traversing addressable views.
|
|
72
|
+
*
|
|
73
|
+
* @param {import("../view/view.js").default} viewRoot
|
|
74
|
+
* @returns {Set<import("../scales/scaleResolution.js").default>}
|
|
75
|
+
*/
|
|
76
|
+
function collectRelevantScaleResolutions(viewRoot) {
|
|
77
|
+
/** @type {Set<import("../scales/scaleResolution.js").default>} */
|
|
78
|
+
const resolutions = new Set();
|
|
79
|
+
|
|
80
|
+
/** @type {import("../spec/channel.js").PrimaryPositionalChannel[]} */
|
|
81
|
+
const locusChannels = ["x", "y"];
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {import("../view/view.js").default} view
|
|
85
|
+
*/
|
|
86
|
+
const collectFromView = (view) => {
|
|
87
|
+
for (const channel of locusChannels) {
|
|
88
|
+
const resolution = view.getScaleResolution(channel);
|
|
89
|
+
if (resolution) {
|
|
90
|
+
resolutions.add(resolution);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
collectFromView(viewRoot);
|
|
96
|
+
visitAddressableViews(viewRoot, collectFromView);
|
|
97
|
+
|
|
98
|
+
return resolutions;
|
|
99
|
+
}
|
|
@@ -60,13 +60,13 @@ export default class Genome {
|
|
|
60
60
|
*/
|
|
61
61
|
constructor(config: GenomeConfig);
|
|
62
62
|
config: {
|
|
63
|
-
url: string;
|
|
64
63
|
name: string;
|
|
65
64
|
} | {
|
|
66
65
|
name: string;
|
|
66
|
+
url: string;
|
|
67
67
|
} | {
|
|
68
|
-
contigs: import("../spec/genome.js").Contig[];
|
|
69
68
|
name: string;
|
|
69
|
+
contigs: import("../spec/genome.js").Contig[];
|
|
70
70
|
};
|
|
71
71
|
/** @type {(Chromosome & ChromosomeAnnotation)[]} */
|
|
72
72
|
chromosomes: (Chromosome & ChromosomeAnnotation)[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"genome.d.ts","sourceRoot":"","sources":["../../../src/genome/genome.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"genome.d.ts","sourceRoot":"","sources":["../../../src/genome/genome.js"],"names":[],"mappings":"AA2XA;;;GAGG;AACH,gDAFW,MAAM;;;IAOhB;AAED;;;;GAIG;AACH,0CAHW,GAAG,GACF,KAAK,IAAI,gBAAgB,CAIpC;AAED;;;;GAIG;AACH,kDAHW,GAAG,EAAE,GACJ,KAAK,IAAI,gBAAgB,EAAE,CAItC;AAED;;;GAGG;AACH,sCAHW,GAAG,GACD,KAAK,IAAI,YAAY,CASjC;AAED;;;GAGG;AACH,yCAHW,GAAG,GACD,KAAK,IAAI,OAAO,mBAAmB,EAAE,eAAe,CAIhE;AAED;;;GAGG;AACH,4CAHW,GAAG,GACD,KAAK,IAAI,OAAO,mBAAmB,EAAE,kBAAkB,CAInE;AA5aD;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH;IACI;;OAEG;IACH,oBAFW,YAAY,EA6CtB;IA1CG;;;;;;;;MAA2C;IAc3C,oDAAoD;IACpD,aADW,CAAC,UAAU,GAAG,oBAAoB,CAAC,EAAE,CAC3B;IAErB,2CAA2C;IAC3C,0BADW,GAAG,CAAC,MAAM,GAAG,MAAM,EAAE,MAAM,CAAC,CACE;IAEzC,sEAAsE;IACtE,mBADW,GAAG,CAAC,MAAM,GAAG,MAAM,EAAE,UAAU,GAAG,oBAAoB,CAAC,CAChC;IAElC,uBAAuB;IACvB,cADW,MAAM,EAAE,CACG;IAEtB,kBAAkB;IAkBtB,mBAEC;IAED;;OAEG;IACH,cAFW,MAAM,iBAmBhB;IAED,wBAEC;IAED;;;OAGG;IACH,0BAFW,UAAU,EAAE,QA+CtB;IAED,sBAEC;IAED;;;;;OAKG;IACH,oBAHW,MAAM,GAAG,MAAM,OACf,MAAM,UAShB;IAED;;;OAGG;IACH,4BAFW,MAAM,qCAgBhB;IAED;;;;OAIG;IACH,6BAHW,MAAM,GACJ,gBAAgB,CAY5B;IAED;;;OAGG;IACH,oBAFW,MAAM,qCAIhB;IAED;;;;;;;;;;OAUG;IACH,yBAHW,MAAM,EAAE,GACN,MAAM,CAIlB;IAED;;;;;;OAMG;IACH,2BAHW,MAAM,GACJ,MAAM,CAQlB;IAED;;;OAGG;IACH,gCAHW,MAAM,EAAE,GACN,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAUhD;IAED;;;;;;OAMG;IACH,kCAFW,gBAAgB,EAAE,YAgB5B;IAED;;;;;OAKG;IACH,wCAHW,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,GAClC,0BAA0B,EAAE,CAiCxC;IAED;;;;OAIG;IACH,4DAFW,MAAM,EAAE,gCAOlB;IAED;;;;OAIG;IACH,mBAHW,MAAM,GACJ,CAAC,MAAM,EAAE,MAAM,CAAC,CAgC5B;CACJ;2BAjXY,OAAO,mBAAmB,EAAE,YAAY;+BACxC,OAAO,mBAAmB,EAAE,gBAAgB;;UAG/C,MAAM;UACN,MAAM;;;;;;WAGN,MAAM;;;;YACN,MAAM;;;;qBACN,MAAM;;;;mBACN,MAAM;wBACN,MAAM,EAAE;;;;SACR,OAAO;;;WAGP,MAAM;cACN,MAAM;YACN,MAAM"}
|
|
@@ -111,6 +111,10 @@ export default class Genome {
|
|
|
111
111
|
* @param {Chromosome[]} chromSizes
|
|
112
112
|
*/
|
|
113
113
|
setChromSizes(chromSizes) {
|
|
114
|
+
this.chromosomes = [];
|
|
115
|
+
this.cumulativeChromPositions = new Map();
|
|
116
|
+
this.chromosomesByName = new Map();
|
|
117
|
+
|
|
114
118
|
let pos = 0;
|
|
115
119
|
this.startByIndex = [0];
|
|
116
120
|
|