@genome-spy/core 0.17.0 → 0.19.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.
@@ -2851,6 +2851,9 @@
2851
2851
  {
2852
2852
  "additionalProperties": false,
2853
2853
  "properties": {
2854
+ "$schema": {
2855
+ "type": "string"
2856
+ },
2854
2857
  "aggregateSamples": {
2855
2858
  "items": {
2856
2859
  "anyOf": [
@@ -3010,6 +3013,9 @@
3010
3013
  {
3011
3014
  "additionalProperties": false,
3012
3015
  "properties": {
3016
+ "$schema": {
3017
+ "type": "string"
3018
+ },
3013
3019
  "aggregateSamples": {
3014
3020
  "items": {
3015
3021
  "anyOf": [
@@ -3172,6 +3178,9 @@
3172
3178
  {
3173
3179
  "additionalProperties": false,
3174
3180
  "properties": {
3181
+ "$schema": {
3182
+ "type": "string"
3183
+ },
3175
3184
  "baseUrl": {
3176
3185
  "type": "string"
3177
3186
  },
@@ -3322,6 +3331,9 @@
3322
3331
  {
3323
3332
  "additionalProperties": false,
3324
3333
  "properties": {
3334
+ "$schema": {
3335
+ "type": "string"
3336
+ },
3325
3337
  "baseUrl": {
3326
3338
  "type": "string"
3327
3339
  },
@@ -3471,6 +3483,9 @@
3471
3483
  {
3472
3484
  "additionalProperties": false,
3473
3485
  "properties": {
3486
+ "$schema": {
3487
+ "type": "string"
3488
+ },
3474
3489
  "baseUrl": {
3475
3490
  "type": "string"
3476
3491
  },
@@ -3620,6 +3635,9 @@
3620
3635
  {
3621
3636
  "additionalProperties": false,
3622
3637
  "properties": {
3638
+ "$schema": {
3639
+ "type": "string"
3640
+ },
3623
3641
  "baseUrl": {
3624
3642
  "type": "string"
3625
3643
  },
package/package.json CHANGED
@@ -7,12 +7,13 @@
7
7
  },
8
8
  "contributors": [],
9
9
  "license": "BSD-2-Clause",
10
- "version": "0.17.0",
10
+ "version": "0.19.0",
11
11
  "main": "dist/index.js",
12
12
  "module": "src/index.js",
13
13
  "exports": {
14
14
  ".": "./src/index.js",
15
- "./*": "./src/*"
15
+ "./*": "./src/*",
16
+ "./schema.json": "./dist/schema.json"
16
17
  },
17
18
  "files": [
18
19
  "dist/",
@@ -20,16 +21,16 @@
20
21
  ],
21
22
  "repository": {
22
23
  "type": "git",
23
- "url": "github:tuner/genome-spy",
24
+ "url": "github:genome-spy/genome-spy",
24
25
  "directory": "packages/core"
25
26
  },
26
27
  "scripts": {
27
28
  "dev": "node dev-server.js",
28
- "build": "vite build",
29
- "prepublishOnly": "npm run build && npm run build:schema",
29
+ "build": "vite build && npm run build:schema",
30
+ "prepublishOnly": "npm run build",
30
31
  "test:tsc": "tsc -p tsconfig.json",
31
32
  "checkSpec": "tsc --allowJs --checkJs --strict --noEmit --moduleResolution node --target es6 src/spec/root.d.ts",
32
- "build:schema": "mkdir -p dist && ts-json-schema-generator --path 'src/spec/*.ts' --type RootSpec > dist/genome-spy-schema.json"
33
+ "build:schema": "mkdir -p dist && ts-json-schema-generator --path 'src/spec/*.ts' --type RootSpec > dist/schema.json"
33
34
  },
34
35
  "dependencies": {
35
36
  "@types/d3-array": "^3.0.2",
@@ -51,5 +52,5 @@
51
52
  "vega-scale": "^7.1.1",
52
53
  "vega-util": "^1.16.0"
53
54
  },
54
- "gitHead": "32db7f46f77206140bd624344e9465d32a94bd07"
55
+ "gitHead": "282eb640bf4c29be3755ef5de3992b8d76ad5f4c"
55
56
  }
@@ -27,7 +27,7 @@ export default class Collector extends FlowNode {
27
27
  /** @type {(function(Collector):void)[]} */
28
28
  this.observers = [];
29
29
 
30
- /** @type {Map<any | any[], Data>} */
30
+ /** @type {Map<any | any[], Data>} TODO: proper type for key */
31
31
  this.facetBatches = undefined;
32
32
 
33
33
  this._init();
@@ -59,8 +59,6 @@ export default class Collector extends FlowNode {
59
59
  * @param {import("./flowBatch").FlowBatch} flowBatch
60
60
  */
61
61
  beginBatch(flowBatch) {
62
- // TODO: Propagate batches to children(?)
63
-
64
62
  if (isFacetBatch(flowBatch)) {
65
63
  this._data = [];
66
64
  this.facetBatches.set(asArray(flowBatch.facetId), this._data);
@@ -103,7 +101,14 @@ export default class Collector extends FlowNode {
103
101
  }
104
102
 
105
103
  if (this.children.length) {
106
- for (const data of this.facetBatches.values()) {
104
+ for (const [key, data] of this.facetBatches.entries()) {
105
+ if (key) {
106
+ /** @type {import("./flowBatch").FacetBatch} */
107
+ const facetBatch = { type: "facet", facetId: key };
108
+ for (const child of this.children) {
109
+ child.beginBatch(facetBatch);
110
+ }
111
+ }
107
112
  for (const datum of data) {
108
113
  this._propagate(datum);
109
114
  }
@@ -1,3 +1,4 @@
1
+ import Collector from "./collector";
1
2
  import { BEHAVIOR_CLONES } from "./flowNode";
2
3
  import CloneTransform from "./transforms/clone";
3
4
 
@@ -31,6 +32,11 @@ export function validateLinks(node, parent = undefined) {
31
32
  * @param {FlowNode} node
32
33
  */
33
34
  export function removeRedundantCloneTransforms(node, cloneRequired = false) {
35
+ if (node instanceof Collector) {
36
+ // If an object is modified downstream of Collector, it must be cloned
37
+ cloneRequired = true;
38
+ }
39
+
34
40
  if (node instanceof CloneTransform) {
35
41
  if (cloneRequired) {
36
42
  cloneRequired = false;
@@ -257,15 +257,27 @@ export default class Genome {
257
257
  parseInterval(str) {
258
258
  // TODO: consider changing [0-9XY] to support other species besides humans
259
259
  const matches = str.match(
260
- /^(chr[0-9A-Z]+):([0-9,]+)-(?:(chr[0-9A-Z]+):)?([0-9,]+)$/
260
+ /^(chr[0-9A-Z]+)(?::([0-9,]+)(?:-(?:(chr[0-9A-Z]+):)?([0-9,]+))?)?$/
261
261
  );
262
262
 
263
263
  if (matches) {
264
264
  const startChr = matches[1];
265
+
266
+ if (matches.slice(2).every((x) => x === undefined)) {
267
+ const chrom = this.getChromosome(startChr);
268
+ if (chrom) {
269
+ return [chrom.continuousStart, chrom.continuousEnd];
270
+ }
271
+ return;
272
+ }
273
+
265
274
  const endChr = matches[3] || startChr;
266
275
 
267
276
  const startIndex = parseInt(matches[2].replace(/,/g, ""));
268
- const endIndex = parseInt(matches[4].replace(/,/g, ""));
277
+ const endIndex =
278
+ matches[4] !== undefined
279
+ ? parseInt(matches[4].replace(/,/g, ""))
280
+ : startIndex;
269
281
 
270
282
  return [
271
283
  this.toContinuous(startChr, startIndex - 1),
@@ -150,3 +150,38 @@ describe("C. elegans genome, chromosome names prefixed with 'chr'", () => {
150
150
  expect(g.toContinuous("III", 10)).toEqual(30351865);
151
151
  });
152
152
  });
153
+
154
+ describe("Parse interval strings", () => {
155
+ const chromosomes = [
156
+ { name: "chr1", size: 1000 },
157
+ { name: "chr2", size: 2000 },
158
+ { name: "chr3", size: 3000 },
159
+ { name: "chrX", size: 4000 },
160
+ ];
161
+
162
+ const g = new Genome({ name: "random", contigs: chromosomes });
163
+
164
+ test("Parses a single chromosome, returns an interval spanning the chromosome", () => {
165
+ expect(g.parseInterval("chr2")).toEqual([1000, 3000]);
166
+ });
167
+
168
+ test("Returns undefined on unknown chromosome", () => {
169
+ expect(g.parseInterval("chrZ")).toBeUndefined();
170
+ });
171
+
172
+ test("Parses a single coordinate without a thousand separator", () => {
173
+ expect(g.parseInterval("chr2:1500")).toEqual([2499, 2500]);
174
+ });
175
+
176
+ test("Parses a single coordinate with a thousand separator", () => {
177
+ expect(g.parseInterval("chr2:1,500")).toEqual([2499, 2500]);
178
+ });
179
+
180
+ test("Parses an interval within a single chromosome", () => {
181
+ expect(g.parseInterval("chr2:1,500-1,700")).toEqual([2499, 2700]);
182
+ });
183
+
184
+ test("Parses an interval spanning multiple chromosomes", () => {
185
+ expect(g.parseInterval("chr2:1,500-chr3:1,500")).toEqual([2499, 4500]);
186
+ });
187
+ });
@@ -2,6 +2,8 @@ import { GenomeConfig } from "./genome";
2
2
  import { ViewSpec } from "./view";
3
3
 
4
4
  interface RootConfig {
5
+ $schema?: string;
6
+
5
7
  genome?: GenomeConfig;
6
8
 
7
9
  baseUrl?: string;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * @param {string} url
3
+ * @param {string} baseUrl
4
+ */
5
+ export default function addBaseUrl(url, baseUrl) {
6
+ // Regex copied from vega-loader
7
+ if (!baseUrl || /^(data:|([A-Za-z]+:)?\/\/)/.test(url)) {
8
+ return url;
9
+ }
10
+
11
+ if (!url.startsWith("/")) {
12
+ if (!baseUrl.endsWith("/")) {
13
+ baseUrl += "/";
14
+ }
15
+ return baseUrl + url;
16
+ }
17
+
18
+ return url;
19
+ }
@@ -0,0 +1,21 @@
1
+ import addBaseUrl from "./addBaseUrl";
2
+
3
+ test("addBaseUrl adds baseUrl when needed", () => {
4
+ expect(addBaseUrl("foo.html", "https://site.com/")).toEqual(
5
+ "https://site.com/foo.html"
6
+ );
7
+ expect(addBaseUrl("foo.html", "https://site.com")).toEqual(
8
+ "https://site.com/foo.html"
9
+ );
10
+ expect(addBaseUrl("bar/foo.html", "https://site.com/")).toEqual(
11
+ "https://site.com/bar/foo.html"
12
+ );
13
+ expect(addBaseUrl("../foo.html", "https://site.com/bar/")).toEqual(
14
+ "https://site.com/bar/../foo.html"
15
+ );
16
+ });
17
+
18
+ test("addBaseUrl doesn't add baseUrl when not needed", () => {
19
+ expect(addBaseUrl("/foo.html", "https://site.com/")).toEqual("/foo.html");
20
+ expect(addBaseUrl("foo.html", undefined)).toEqual("foo.html");
21
+ });
@@ -17,6 +17,7 @@ import {
17
17
  import createDomain from "../utils/domainArray";
18
18
  import AxisResolution from "./axisResolution";
19
19
  import { isAggregateSamplesSpec } from "./viewFactory";
20
+ import { peek } from "../utils/arrayUtils";
20
21
 
21
22
  /**
22
23
  *
@@ -337,10 +338,13 @@ export default class UnitView extends ContainerView {
337
338
  if (isAggregateSamplesSpec(this.spec)) {
338
339
  // TODO: Support multiple
339
340
  for (const sumSpec of this.spec.aggregateSamples) {
340
- sumSpec.transform = [
341
- ...(sumSpec.transform ?? []),
342
- { type: "mergeFacets" },
343
- ];
341
+ const transform = sumSpec.transform ?? [];
342
+ if (transform.length && peek(transform).type != "collect") {
343
+ // MergeFacets must be a direct child of Collector
344
+ transform.push({ type: "collect" });
345
+ }
346
+ transform.push({ type: "mergeFacets" });
347
+ sumSpec.transform = transform;
344
348
 
345
349
  sumSpec.encoding = {
346
350
  ...(sumSpec.encoding ?? {}),