@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.
Files changed (103) hide show
  1. package/LICENSE +1 -1
  2. package/dist/bundle/index.es.js +6779 -5393
  3. package/dist/bundle/index.js +133 -121
  4. package/dist/schema.json +281 -17
  5. package/dist/src/data/formats/bed.d.ts +8 -0
  6. package/dist/src/data/formats/bed.d.ts.map +1 -0
  7. package/dist/src/data/formats/bed.js +53 -0
  8. package/dist/src/data/formats/bedpe.d.ts +8 -0
  9. package/dist/src/data/formats/bedpe.d.ts.map +1 -0
  10. package/dist/src/data/formats/bedpe.js +160 -0
  11. package/dist/src/data/sources/dataUtils.d.ts +16 -0
  12. package/dist/src/data/sources/dataUtils.d.ts.map +1 -1
  13. package/dist/src/data/sources/dataUtils.js +53 -3
  14. package/dist/src/data/sources/urlSource.d.ts +4 -0
  15. package/dist/src/data/sources/urlSource.d.ts.map +1 -1
  16. package/dist/src/data/sources/urlSource.js +133 -14
  17. package/dist/src/genome/assemblyPreflight.d.ts +31 -0
  18. package/dist/src/genome/assemblyPreflight.d.ts.map +1 -0
  19. package/dist/src/genome/assemblyPreflight.js +99 -0
  20. package/dist/src/genome/genome.d.ts +2 -2
  21. package/dist/src/genome/genome.d.ts.map +1 -1
  22. package/dist/src/genome/genome.js +4 -0
  23. package/dist/src/genome/genomeStore.d.ts +34 -3
  24. package/dist/src/genome/genomeStore.d.ts.map +1 -1
  25. package/dist/src/genome/genomeStore.js +409 -18
  26. package/dist/src/genome/rootGenomeConfig.d.ts +26 -0
  27. package/dist/src/genome/rootGenomeConfig.d.ts.map +1 -0
  28. package/dist/src/genome/rootGenomeConfig.js +94 -0
  29. package/dist/src/genomeSpy/interactionController.d.ts +5 -1
  30. package/dist/src/genomeSpy/interactionController.d.ts.map +1 -1
  31. package/dist/src/genomeSpy/interactionController.js +244 -29
  32. package/dist/src/genomeSpy/renderCoordinator.js +1 -1
  33. package/dist/src/genomeSpy.d.ts +13 -3
  34. package/dist/src/genomeSpy.d.ts.map +1 -1
  35. package/dist/src/genomeSpy.js +81 -7
  36. package/dist/src/gl/canvasSizeHelper.d.ts +74 -0
  37. package/dist/src/gl/canvasSizeHelper.d.ts.map +1 -0
  38. package/dist/src/gl/canvasSizeHelper.js +203 -0
  39. package/dist/src/gl/webGLHelper.d.ts +25 -11
  40. package/dist/src/gl/webGLHelper.d.ts.map +1 -1
  41. package/dist/src/gl/webGLHelper.js +59 -33
  42. package/dist/src/index.d.ts.map +1 -1
  43. package/dist/src/index.js +5 -2
  44. package/dist/src/marks/link.d.ts.map +1 -1
  45. package/dist/src/marks/link.js +5 -3
  46. package/dist/src/marks/mark.d.ts.map +1 -1
  47. package/dist/src/marks/mark.js +6 -1
  48. package/dist/src/scales/domainPlanner.d.ts +34 -3
  49. package/dist/src/scales/domainPlanner.d.ts.map +1 -1
  50. package/dist/src/scales/domainPlanner.js +247 -26
  51. package/dist/src/scales/scaleInstanceManager.d.ts +2 -1
  52. package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -1
  53. package/dist/src/scales/scaleInstanceManager.js +10 -11
  54. package/dist/src/scales/scaleInteractionController.d.ts.map +1 -1
  55. package/dist/src/scales/scaleInteractionController.js +16 -14
  56. package/dist/src/scales/scaleResolution.d.ts +16 -0
  57. package/dist/src/scales/scaleResolution.d.ts.map +1 -1
  58. package/dist/src/scales/scaleResolution.js +314 -54
  59. package/dist/src/scales/scaleResolutionTestUtils.d.ts +21 -0
  60. package/dist/src/scales/scaleResolutionTestUtils.d.ts.map +1 -0
  61. package/dist/src/scales/scaleResolutionTestUtils.js +33 -0
  62. package/dist/src/scales/selectionDomainUtils.d.ts +22 -0
  63. package/dist/src/scales/selectionDomainUtils.d.ts.map +1 -0
  64. package/dist/src/scales/selectionDomainUtils.js +79 -0
  65. package/dist/src/scales/zoomDomainUtils.d.ts +18 -0
  66. package/dist/src/scales/zoomDomainUtils.d.ts.map +1 -0
  67. package/dist/src/scales/zoomDomainUtils.js +69 -0
  68. package/dist/src/screenshotHarness.d.ts +16 -0
  69. package/dist/src/screenshotHarness.d.ts.map +1 -0
  70. package/dist/src/screenshotHarness.js +242 -0
  71. package/dist/src/singlePageApp.js +1 -1
  72. package/dist/src/spec/data.d.ts +23 -3
  73. package/dist/src/spec/genome.d.ts +22 -2
  74. package/dist/src/spec/parameter.d.ts +39 -2
  75. package/dist/src/spec/root.d.ts +20 -1
  76. package/dist/src/spec/scale.d.ts +41 -5
  77. package/dist/src/styles/genome-spy.css +8 -0
  78. package/dist/src/styles/genome-spy.css.d.ts +1 -1
  79. package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
  80. package/dist/src/styles/genome-spy.css.js +8 -0
  81. package/dist/src/tooltip/dataTooltipHandler.js +59 -10
  82. package/dist/src/types/embedApi.d.ts +19 -0
  83. package/dist/src/utils/inferSpecBaseUrl.d.ts +14 -0
  84. package/dist/src/utils/inferSpecBaseUrl.d.ts.map +1 -0
  85. package/dist/src/utils/inferSpecBaseUrl.js +73 -0
  86. package/dist/src/utils/interactionEvent.d.ts +53 -3
  87. package/dist/src/utils/interactionEvent.d.ts.map +1 -1
  88. package/dist/src/utils/interactionEvent.js +62 -1
  89. package/dist/src/view/containerMutationHelper.d.ts.map +1 -1
  90. package/dist/src/view/containerMutationHelper.js +8 -0
  91. package/dist/src/view/dataReadiness.d.ts +2 -2
  92. package/dist/src/view/dataReadiness.d.ts.map +1 -1
  93. package/dist/src/view/dataReadiness.js +63 -58
  94. package/dist/src/view/facetView.js +1 -1
  95. package/dist/src/view/gridView/gridChild.d.ts +7 -0
  96. package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
  97. package/dist/src/view/gridView/gridChild.js +180 -11
  98. package/dist/src/view/gridView/gridView.d.ts.map +1 -1
  99. package/dist/src/view/gridView/gridView.js +60 -17
  100. package/dist/src/view/zoom.d.ts +14 -2
  101. package/dist/src/view/zoom.d.ts.map +1 -1
  102. package/dist/src/view/zoom.js +373 -76
  103. 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":"AAGA;;;;;;;GAOG;AACH,kCAJW,OAAO,oBAAoB,EAAE,UAAU,SAEvC,MAAM,GAAG,MAAM,EAAE;;;;;;;;;;;;;EAoB3B;AAED;;;GAGG;AACH,mCAHW,MAAM,GACJ,MAAM,CAIlB;AAED;;GAEG;AACH,wCAFW,MAAM,GAAG,MAAM,EAAE,UAU3B;AAWD;;;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;AAxCM,+BAAgC,GAAG,QAI3B,OAAO,uBAAuB,EAAE,MAAM;;UAGtB,OAAO,gBAAgB,EAAE,KAAK,qCANR"}
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
- // @ts-ignore TODO: Fix typing
20
- format.parse ??= "auto";
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
- return url.match(/\.(csv|tsv|json)/)?.[1];
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":"AASA;;;GAGG;AACH,gCAHW,OAAO,CAAC,OAAO,oBAAoB,EAAE,IAAI,CAAC,GACxC,IAAI,IAAI,OAAO,oBAAoB,EAAE,OAAO,CAIxD;AAED;IACI;;;OAGG;IACH,oBAHW,OAAO,oBAAoB,EAAE,OAAO,QACpC,OAAO,oBAAoB,EAAE,OAAO,EAc9C;IATG,6CAMC;IAED,gBAAiC;;CA4GxC;uBA3IsB,iBAAiB"}
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
- * @param {Partial<import("../../spec/data.js").Data>} data
12
- * @returns {data is import("../../spec/data.js").UrlData}
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 text = await result.text();
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(text, { type: props.type ?? "tsv" })
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
- // @ts-ignore
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":"AAuXA;;;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;AAxaD;;;;;;;;;;;;;;;;;;;;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,QA2CtB;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;2BA7WY,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"}
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