@genome-spy/core 0.43.3 → 0.45.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 (124) hide show
  1. package/dist/bundle/index.es.js +5231 -4324
  2. package/dist/bundle/index.js +197 -85
  3. package/dist/schema.json +723 -104
  4. package/dist/src/data/collector.d.ts.map +1 -1
  5. package/dist/src/data/collector.js +4 -2
  6. package/dist/src/data/flowOptimizer.test.js +12 -3
  7. package/dist/src/data/sources/dataUtils.d.ts.map +1 -1
  8. package/dist/src/data/sources/dataUtils.js +3 -1
  9. package/dist/src/data/sources/lazy/axisTickSource.d.ts +1 -1
  10. package/dist/src/data/sources/lazy/axisTickSource.d.ts.map +1 -1
  11. package/dist/src/data/sources/lazy/axisTickSource.js +2 -2
  12. package/dist/src/data/sources/lazy/bigBedSource.d.ts +1 -1
  13. package/dist/src/data/sources/lazy/bigBedSource.d.ts.map +1 -1
  14. package/dist/src/data/sources/lazy/bigBedSource.js +52 -20
  15. package/dist/src/data/sources/lazy/bigWigSource.d.ts +6 -1
  16. package/dist/src/data/sources/lazy/bigWigSource.d.ts.map +1 -1
  17. package/dist/src/data/sources/lazy/bigWigSource.js +33 -9
  18. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts +1 -1
  19. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
  20. package/dist/src/data/sources/lazy/singleAxisLazySource.js +1 -3
  21. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts +13 -14
  22. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
  23. package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +70 -48
  24. package/dist/src/data/sources/sequenceSource.d.ts.map +1 -1
  25. package/dist/src/data/sources/sequenceSource.js +14 -5
  26. package/dist/src/data/sources/sequenceSource.test.js +23 -5
  27. package/dist/src/data/sources/urlSource.d.ts.map +1 -1
  28. package/dist/src/data/sources/urlSource.js +15 -2
  29. package/dist/src/data/transforms/aggregate.d.ts.map +1 -1
  30. package/dist/src/data/transforms/aggregate.js +5 -2
  31. package/dist/src/data/transforms/filterScoredLabels.js +1 -1
  32. package/dist/src/encoder/encoder.d.ts +2 -4
  33. package/dist/src/encoder/encoder.d.ts.map +1 -1
  34. package/dist/src/encoder/encoder.js +20 -10
  35. package/dist/src/encoder/encoder.test.js +3 -0
  36. package/dist/src/genomeSpy.d.ts +8 -5
  37. package/dist/src/genomeSpy.d.ts.map +1 -1
  38. package/dist/src/genomeSpy.js +121 -42
  39. package/dist/src/gl/glslScaleGenerator.d.ts +23 -3
  40. package/dist/src/gl/glslScaleGenerator.d.ts.map +1 -1
  41. package/dist/src/gl/glslScaleGenerator.js +137 -42
  42. package/dist/src/gl/webGLHelper.d.ts.map +1 -1
  43. package/dist/src/gl/webGLHelper.js +5 -7
  44. package/dist/src/index.d.ts +1 -1
  45. package/dist/src/index.d.ts.map +1 -1
  46. package/dist/src/index.js +1 -1
  47. package/dist/src/marks/link.common.glsl.js +2 -0
  48. package/dist/src/marks/link.d.ts.map +1 -1
  49. package/dist/src/marks/link.js +19 -9
  50. package/dist/src/marks/link.vertex.glsl.js +1 -1
  51. package/dist/src/marks/mark.d.ts +25 -20
  52. package/dist/src/marks/mark.d.ts.map +1 -1
  53. package/dist/src/marks/mark.js +234 -129
  54. package/dist/src/marks/point.common.glsl.js +1 -1
  55. package/dist/src/marks/point.d.ts +1 -4
  56. package/dist/src/marks/point.d.ts.map +1 -1
  57. package/dist/src/marks/point.js +31 -23
  58. package/dist/src/marks/point.vertex.glsl.js +1 -1
  59. package/dist/src/marks/rect.common.glsl.js +2 -0
  60. package/dist/src/marks/rect.d.ts.map +1 -1
  61. package/dist/src/marks/rect.js +12 -12
  62. package/dist/src/marks/rect.vertex.glsl.js +1 -1
  63. package/dist/src/marks/rule.common.glsl.js +1 -1
  64. package/dist/src/marks/rule.js +2 -2
  65. package/dist/src/marks/text.common.glsl.js +1 -1
  66. package/dist/src/marks/text.d.ts.map +1 -1
  67. package/dist/src/marks/text.js +17 -9
  68. package/dist/src/spec/channel.d.ts +4 -3
  69. package/dist/src/spec/data.d.ts +11 -10
  70. package/dist/src/spec/mark.d.ts +28 -46
  71. package/dist/src/spec/parameter.d.ts +127 -0
  72. package/dist/src/spec/root.d.ts +1 -0
  73. package/dist/src/spec/scale.d.ts +2 -1
  74. package/dist/src/spec/title.d.ts +5 -4
  75. package/dist/src/spec/view.d.ts +20 -5
  76. package/dist/src/styles/genome-spy.css.d.ts +1 -1
  77. package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
  78. package/dist/src/styles/genome-spy.css.js +52 -5
  79. package/dist/src/styles/genome-spy.scss +63 -10
  80. package/dist/src/styles/update.sh +6 -0
  81. package/dist/src/tooltip/dataTooltipHandler.js +1 -1
  82. package/dist/src/tooltip/refseqGeneTooltipHandler.js +1 -1
  83. package/dist/src/tooltip/tooltipHandler.d.ts +1 -1
  84. package/dist/src/tooltip/tooltipHandler.d.ts.map +1 -1
  85. package/dist/src/tooltip/tooltipHandler.ts +1 -1
  86. package/dist/src/types/embedApi.d.ts +6 -0
  87. package/dist/src/types/scaleResolutionApi.d.ts +7 -3
  88. package/dist/src/types/viewContext.d.ts +2 -3
  89. package/dist/src/utils/debounce.d.ts +2 -2
  90. package/dist/src/utils/debounce.d.ts.map +1 -1
  91. package/dist/src/utils/debounce.js +5 -2
  92. package/dist/src/utils/expression.d.ts +2 -2
  93. package/dist/src/utils/expression.d.ts.map +1 -1
  94. package/dist/src/utils/expression.js +3 -3
  95. package/dist/src/utils/formatObject.d.ts +2 -2
  96. package/dist/src/utils/formatObject.d.ts.map +1 -1
  97. package/dist/src/utils/formatObject.js +2 -2
  98. package/dist/src/utils/inputBinding.d.ts +5 -0
  99. package/dist/src/utils/inputBinding.d.ts.map +1 -0
  100. package/dist/src/utils/inputBinding.js +115 -0
  101. package/dist/src/utils/ui/tooltip.js +1 -1
  102. package/dist/src/view/axisView.js +3 -3
  103. package/dist/src/view/paramMediator.d.ts +108 -0
  104. package/dist/src/view/paramMediator.d.ts.map +1 -0
  105. package/dist/src/view/paramMediator.js +337 -0
  106. package/dist/src/view/paramMediator.test.js +211 -0
  107. package/dist/src/view/scaleResolution.d.ts +8 -18
  108. package/dist/src/view/scaleResolution.d.ts.map +1 -1
  109. package/dist/src/view/scaleResolution.js +225 -126
  110. package/dist/src/view/scaleResolution.test.js +7 -7
  111. package/dist/src/view/unitView.d.ts.map +1 -1
  112. package/dist/src/view/unitView.js +10 -3
  113. package/dist/src/view/view.d.ts +4 -1
  114. package/dist/src/view/view.d.ts.map +1 -1
  115. package/dist/src/view/view.js +21 -7
  116. package/dist/src/view/viewFactory.d.ts.map +1 -1
  117. package/dist/src/view/viewFactory.js +45 -0
  118. package/dist/src/view/viewUtils.d.ts +5 -1
  119. package/dist/src/view/viewUtils.d.ts.map +1 -1
  120. package/dist/src/view/viewUtils.js +9 -4
  121. package/package.json +16 -17
  122. package/dist/src/paramBroker.d.ts +0 -30
  123. package/dist/src/paramBroker.d.ts.map +0 -1
  124. package/dist/src/paramBroker.js +0 -102
@@ -1,3 +1,7 @@
1
+ import {
2
+ activateExprRefProps,
3
+ withoutExprRef,
4
+ } from "../../view/paramMediator.js";
1
5
  import DataSource from "./dataSource.js";
2
6
 
3
7
  /**
@@ -16,7 +20,11 @@ export default class SequenceSource extends DataSource {
16
20
  */
17
21
  constructor(params, view) {
18
22
  super();
19
- this.sequence = params.sequence;
23
+ this.sequence = activateExprRefProps(
24
+ view.paramMediator,
25
+ params.sequence,
26
+ () => this.loadSynchronously()
27
+ );
20
28
 
21
29
  if (!("start" in this.sequence)) {
22
30
  throw new Error("'start' is missing from sequence parameters!");
@@ -27,14 +35,15 @@ export default class SequenceSource extends DataSource {
27
35
  }
28
36
 
29
37
  loadSynchronously() {
30
- const as = this.sequence.as || "data";
31
- const step = this.sequence.step || 1;
32
- const stop = this.sequence.stop;
38
+ const as = withoutExprRef(this.sequence.as) ?? "data";
39
+ const start = withoutExprRef(this.sequence.start) ?? 0;
40
+ const step = withoutExprRef(this.sequence.step) ?? 1;
41
+ const stop = withoutExprRef(this.sequence.stop);
33
42
 
34
43
  this.reset();
35
44
  this.beginBatch({ type: "file" });
36
45
 
37
- for (let x = this.sequence.start; x < stop; x += step) {
46
+ for (let x = start; x < stop; x += step) {
38
47
  this._propagate({ [as]: x });
39
48
  }
40
49
 
@@ -14,10 +14,18 @@ async function collectSource(source) {
14
14
  return [...collector.getData()];
15
15
  }
16
16
 
17
+ const viewStub = {
18
+ paramMediator: {
19
+ registerParam: () => {},
20
+ allocateSetter: () => {},
21
+ createExpression: () => {},
22
+ },
23
+ };
24
+
17
25
  test("SequenceSource generates a sequence", async () => {
18
26
  expect(
19
27
  await collectSource(
20
- new SequenceSource({ sequence: { start: 0, stop: 3 } })
28
+ new SequenceSource({ sequence: { start: 0, stop: 3 } }, viewStub)
21
29
  )
22
30
  ).toEqual([{ data: 0 }, { data: 1 }, { data: 2 }]);
23
31
  });
@@ -25,7 +33,10 @@ test("SequenceSource generates a sequence", async () => {
25
33
  test("SequenceSource generates a sequence with a custom step", async () => {
26
34
  expect(
27
35
  await collectSource(
28
- new SequenceSource({ sequence: { start: 0, stop: 5, step: 2 } })
36
+ new SequenceSource(
37
+ { sequence: { start: 0, stop: 5, step: 2 } },
38
+ viewStub
39
+ )
29
40
  )
30
41
  ).toEqual([{ data: 0 }, { data: 2 }, { data: 4 }]);
31
42
  });
@@ -33,14 +44,21 @@ test("SequenceSource generates a sequence with a custom step", async () => {
33
44
  test("SequenceSource generates a sequence with a custom field name", async () => {
34
45
  expect(
35
46
  await collectSource(
36
- new SequenceSource({ sequence: { start: 0, stop: 3, as: "x" } })
47
+ new SequenceSource(
48
+ { sequence: { start: 0, stop: 3, as: "x" } },
49
+ viewStub
50
+ )
37
51
  )
38
52
  ).toEqual([{ x: 0 }, { x: 1 }, { x: 2 }]);
39
53
  });
40
54
 
41
55
  test("SequenceSource throws on missing 'start' parameter", () => {
42
- expect(() => new SequenceSource({ sequence: { stop: 3 } })).toThrow();
56
+ expect(
57
+ () => new SequenceSource({ sequence: { stop: 3 } }, viewStub)
58
+ ).toThrow();
43
59
  });
44
60
  test("SequenceSource throws on missing 'stop' parameter", () => {
45
- expect(() => new SequenceSource({ sequence: { start: 0 } })).toThrow();
61
+ expect(
62
+ () => new SequenceSource({ sequence: { start: 0 } }, viewStub)
63
+ ).toThrow();
46
64
  });
@@ -1 +1 @@
1
- {"version":3,"file":"urlSource.d.ts","sourceRoot":"","sources":["../../../../src/data/sources/urlSource.js"],"names":[],"mappings":"AAIA;;;GAGG;AACH,gCAHW,QAAQ,OAAO,oBAAoB,EAAE,IAAI,CAAC,gDAKpD;AAED;IACI;;;OAGG;IACH,oBAHW,OAAO,oBAAoB,EAAE,OAAO,QACpC,OAAO,oBAAoB,EAAE,OAAO,EAO9C;IAFG,6CAAoB;IACpB,gBAAiC;CAoDxC;uBAvEsB,iBAAiB"}
1
+ {"version":3,"file":"urlSource.d.ts","sourceRoot":"","sources":["../../../../src/data/sources/urlSource.js"],"names":[],"mappings":"AAQA;;;GAGG;AACH,gCAHW,QAAQ,OAAO,oBAAoB,EAAE,IAAI,CAAC,gDAKpD;AAED;IACI;;;OAGG;IACH,oBAHW,OAAO,oBAAoB,EAAE,OAAO,QACpC,OAAO,oBAAoB,EAAE,OAAO,EAU9C;IALG,6CAEC;IAED,gBAAiC;CA0DxC;uBApFsB,iBAAiB"}
@@ -1,6 +1,10 @@
1
1
  import { loader as vegaLoader, read } from "vega-loader";
2
2
  import { getFormat } from "./dataUtils.js";
3
3
  import DataSource from "./dataSource.js";
4
+ import {
5
+ activateExprRefProps,
6
+ withoutExprRef,
7
+ } from "../../view/paramMediator.js";
4
8
 
5
9
  /**
6
10
  * @param {Partial<import("../../spec/data.js").Data>} data
@@ -18,7 +22,10 @@ export default class UrlSource extends DataSource {
18
22
  constructor(params, view) {
19
23
  super();
20
24
 
21
- this.params = params;
25
+ this.params = activateExprRefProps(view.paramMediator, params, () =>
26
+ this.load()
27
+ );
28
+
22
29
  this.baseUrl = view?.getBaseUrl();
23
30
  }
24
31
 
@@ -27,11 +34,17 @@ export default class UrlSource extends DataSource {
27
34
  }
28
35
 
29
36
  async load() {
30
- const url = this.params.url;
37
+ const url = withoutExprRef(this.params.url);
31
38
 
32
39
  /** @type {string[]} */
33
40
  const urls = Array.isArray(url) ? url : [url];
34
41
 
42
+ if (urls.length === 0 || !urls[0]) {
43
+ this.reset();
44
+ this.complete();
45
+ return;
46
+ }
47
+
35
48
  /** @param {string} url */
36
49
  const load = async (url) =>
37
50
  // TODO: Support chunked loading
@@ -1 +1 @@
1
- {"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../../../../src/data/transforms/aggregate.js"],"names":[],"mappings":"AAKA;;;;;;GAMG;AACH;IAKI;;OAEG;IACH,oBAFW,OAAO,yBAAyB,EAAE,eAAe,EAQ3D;IAJG,0DAAoB;IAEpB,oBAAoB;IACpB,QADW,GAAG,EAAE,CACA;CAyCvB;qBAjEyC,gBAAgB"}
1
+ {"version":3,"file":"aggregate.d.ts","sourceRoot":"","sources":["../../../../src/data/transforms/aggregate.js"],"names":[],"mappings":"AAKA;;;;;;GAMG;AACH;IAKI;;OAEG;IACH,oBAFW,OAAO,yBAAyB,EAAE,eAAe,EAQ3D;IAJG,0DAAoB;IAEpB,oBAAoB;IACpB,QADW,GAAG,EAAE,CACA;CA4CvB;qBApEyC,gBAAgB"}
@@ -46,8 +46,11 @@ export default class AggregateTransform extends FlowNode {
46
46
  const groupFieldAccessors = groupby.map((f) => field(f));
47
47
 
48
48
  // TODO: Fix case where no group fields are specified
49
- // @ts-expect-error
50
- const groups = d3group(this.buffer, ...groupFieldAccessors);
49
+
50
+ // There's something strange in d3-array's typings
51
+ const groups = /** @type {Map<any, any>} */ /** @type {any} */ (
52
+ d3group(this.buffer, ...groupFieldAccessors)
53
+ );
51
54
 
52
55
  for (const [group, data] of iterateNestedMaps(groups)) {
53
56
  /** @type {any} */
@@ -75,7 +75,7 @@ export default class FilterScoredLabelsTransform extends FlowNode {
75
75
  _filterAndPropagate() {
76
76
  super.reset();
77
77
 
78
- const scale = this.resolution.getScale();
78
+ const scale = this.resolution.scale;
79
79
  const rangeSpan =
80
80
  this.resolution.members[0].view.coords?.[
81
81
  this.channel == "x" ? "width" : "height"
@@ -1,8 +1,6 @@
1
1
  /**
2
2
  * Creates an object that contains encoders for every channel of a mark
3
3
  *
4
- * TODO: This should actually receive the mark as parameter
5
- *
6
4
  * TODO: This method should have a test. But how to mock Mark...
7
5
  *
8
6
  * @param {import("../marks/mark.js").default} mark
@@ -11,14 +9,14 @@
11
9
  */
12
10
  export default function createEncoders(mark: import("../marks/mark.js").default, encoding?: import("../spec/channel.js").Encoding): Partial<Record<import("../spec/channel.js").Channel, import("../types/encoder.js").Encoder>>;
13
11
  /**
14
- *
12
+ * @param {import("../marks/mark.js").default} mark
15
13
  * @param {import("../spec/channel.js").ChannelDef} channelDef
16
14
  * @param {any} scale
17
15
  * @param {Accessor} accessor
18
16
  * @param {Channel} channel
19
17
  * @returns {Encoder}
20
18
  */
21
- export function createEncoder(channelDef: import("../spec/channel.js").ChannelDef, scale: any, accessor: import("../types/encoder.js").Accessor, channel: import("../spec/channel.js").Channel): import("../types/encoder.js").Encoder;
19
+ export function createEncoder(mark: import("../marks/mark.js").default, channelDef: import("../spec/channel.js").ChannelDef, scale: any, accessor: import("../types/encoder.js").Accessor, channel: import("../spec/channel.js").Channel): import("../types/encoder.js").Encoder;
22
20
  /**
23
21
  * TODO: Move to a more generic place
24
22
  *
@@ -1 +1 @@
1
- {"version":3,"file":"encoder.d.ts","sourceRoot":"","sources":["../../../src/encoder/encoder.js"],"names":[],"mappings":"AAIA;;;;;;;;;;GAUG;AACH,6CAJW,OAAO,kBAAkB,EAAE,OAAO,aAClC,OAAO,oBAAoB,EAAE,QAAQ,gGAsC/C;AAED;;;;;;;GAOG;AACH,0CANW,OAAO,oBAAoB,EAAE,UAAU,SACvC,GAAG,0IA2Fb;AAED;;;;;GAKG;AACH,uCAHW,OAAO,oBAAoB,EAAE,UAAU,4FAKjD;AAED;;;GAGG;AACH,uCAHW,OAAO,oBAAoB,EAAE,UAAU,mEAKjD;AAED;;;GAGG;AACH,uCAHW,OAAO,oBAAoB,EAAE,UAAU,uDAKjD;AAED;;;GAGG;AACH,kDAHW,OAAO,oBAAoB,EAAE,UAAU,kEAWjD;AAED;;;GAGG;AACH,6CAHW,OAAO,qBAAqB,EAAE,OAAO,WACrC,OAAO,oBAAoB,EAAE,OAAO,27CAS9C;AAED;;;GAGG;AACH,iDAHW,OAAO,oBAAoB,EAAE,UAAU,4FAKjD;AAED;;;GAGG;AACH,0CAHW,OAAO,oBAAoB,EAAE,UAAU,0DAKjD;AAED;;;GAGG;AACH,sCAHW,OAAO,oBAAoB,EAAE,UAAU,sDAKjD;AAoBD;;;GAGG;AACH,oDAHW,OAAO,oBAAoB,EAAE,OAAO,oEAM9C;AAED;;;GAGG;AACH,6CAHW,OAAO,oBAAoB,EAAE,OAAO,6DAM9C;AAqBD;;;GAGG;AACH,4CAFW,MAAM,WAIhB;AAED;;;;GAIG;AACH,oDAFW,OAAO,oBAAoB,EAAE,OAAO,2DAS9C;AAED;;;;;GAKG;AACH,2CAFW,OAAO,oBAAoB,EAAE,OAAO,wCAI9C;AAED;;;;GAIG;AACH,kDAFW,OAAO,oBAAoB,EAAE,OAAO,0CAM9C;AAED;;GAEG;AACH,wCAFW,OAAO,oBAAoB,EAAE,OAAO,WAI9C;AAED;;;;GAIG;AACH,2CAFW,OAAO,oBAAoB,EAAE,OAAO,WAI9C;AAED;;;GAGG;AACH,4CAHW,OAAO,oBAAoB,EAAE,OAAO,4DAuB9C;AAED;;;;;GAKG;AACH,0CAHW,OAAO,oBAAoB,EAAE,OAAO,GAClC,GAAG,EAAE,CAsBjB;AAED;;;GAGG;AACH,gDAHW,OAAO,oBAAoB,EAAE,OAAO,UACzB,GAAG,KAAE,MAAM,CAmBhC;AA7LD;;GAEG;AACH,wCAFU,OAAO,oBAAoB,EAAE,wBAAwB,EAAE,CAEb;AAEpD;;GAEG;AACH,0CAFU,OAAO,oBAAoB,EAAE,0BAA0B,EAAE,CAEX;AAExD;;GAEG;AACH,iCAFU,OAAO,oBAAoB,EAAE,iBAAiB,EAAE,CAKxD;AAoBF;;;;GAIG;AACH,gCAFU,QAAQ,OAAO,OAAO,oBAAoB,EAAE,OAAO,EAAE,OAAO,oBAAoB,EAAE,0BAA0B,CAAC,CAAC,CAKtH;AAEF;;;;GAIG;AACH,8BAFU,QAAQ,OAAO,OAAO,oBAAoB,EAAE,OAAO,EAAE,OAAO,oBAAoB,EAAE,OAAO,CAAC,CAAC,CAInG"}
1
+ {"version":3,"file":"encoder.d.ts","sourceRoot":"","sources":["../../../src/encoder/encoder.js"],"names":[],"mappings":"AAKA;;;;;;;;GAQG;AACH,6CAJW,OAAO,kBAAkB,EAAE,OAAO,aAClC,OAAO,oBAAoB,EAAE,QAAQ,gGAuC/C;AAED;;;;;;;GAOG;AACH,oCAPW,OAAO,kBAAkB,EAAE,OAAO,cAClC,OAAO,oBAAoB,EAAE,UAAU,SACvC,GAAG,0IAqGb;AAED;;;;;GAKG;AACH,uCAHW,OAAO,oBAAoB,EAAE,UAAU,4FAKjD;AAED;;;GAGG;AACH,uCAHW,OAAO,oBAAoB,EAAE,UAAU,mEAKjD;AAED;;;GAGG;AACH,uCAHW,OAAO,oBAAoB,EAAE,UAAU,uDAKjD;AAED;;;GAGG;AACH,kDAHW,OAAO,oBAAoB,EAAE,UAAU,kEAWjD;AAED;;;GAGG;AACH,6CAHW,OAAO,qBAAqB,EAAE,OAAO,WACrC,OAAO,oBAAoB,EAAE,OAAO,27CAS9C;AAED;;;GAGG;AACH,iDAHW,OAAO,oBAAoB,EAAE,UAAU,4FAKjD;AAED;;;GAGG;AACH,0CAHW,OAAO,oBAAoB,EAAE,UAAU,0DAKjD;AAED;;;GAGG;AACH,sCAHW,OAAO,oBAAoB,EAAE,UAAU,sDAKjD;AAoBD;;;GAGG;AACH,oDAHW,OAAO,oBAAoB,EAAE,OAAO,oEAM9C;AAED;;;GAGG;AACH,6CAHW,OAAO,oBAAoB,EAAE,OAAO,6DAM9C;AAqBD;;;GAGG;AACH,4CAFW,MAAM,WAIhB;AAED;;;;GAIG;AACH,oDAFW,OAAO,oBAAoB,EAAE,OAAO,2DAS9C;AAED;;;;;GAKG;AACH,2CAFW,OAAO,oBAAoB,EAAE,OAAO,wCAI9C;AAED;;;;GAIG;AACH,kDAFW,OAAO,oBAAoB,EAAE,OAAO,0CAM9C;AAED;;GAEG;AACH,wCAFW,OAAO,oBAAoB,EAAE,OAAO,WAI9C;AAED;;;;GAIG;AACH,2CAFW,OAAO,oBAAoB,EAAE,OAAO,WAI9C;AAED;;;GAGG;AACH,4CAHW,OAAO,oBAAoB,EAAE,OAAO,4DAuB9C;AAED;;;;;GAKG;AACH,0CAHW,OAAO,oBAAoB,EAAE,OAAO,GAClC,GAAG,EAAE,CAsBjB;AAED;;;GAGG;AACH,gDAHW,OAAO,oBAAoB,EAAE,OAAO,UACzB,GAAG,KAAE,MAAM,CAmBhC;AA7LD;;GAEG;AACH,wCAFU,OAAO,oBAAoB,EAAE,wBAAwB,EAAE,CAEb;AAEpD;;GAEG;AACH,0CAFU,OAAO,oBAAoB,EAAE,0BAA0B,EAAE,CAEX;AAExD;;GAEG;AACH,iCAFU,OAAO,oBAAoB,EAAE,iBAAiB,EAAE,CAKxD;AAoBF;;;;GAIG;AACH,gCAFU,QAAQ,OAAO,OAAO,oBAAoB,EAAE,OAAO,EAAE,OAAO,oBAAoB,EAAE,0BAA0B,CAAC,CAAC,CAKtH;AAEF;;;;GAIG;AACH,8BAFU,QAAQ,OAAO,OAAO,oBAAoB,EAAE,OAAO,EAAE,OAAO,oBAAoB,EAAE,OAAO,CAAC,CAAC,CAInG"}
@@ -1,12 +1,11 @@
1
1
  import { isDiscrete } from "vega-scale";
2
2
  import createIndexer from "../utils/indexer.js";
3
3
  import scaleNull from "../utils/scaleNull.js";
4
+ import { isExprRef } from "../view/paramMediator.js";
4
5
 
5
6
  /**
6
7
  * Creates an object that contains encoders for every channel of a mark
7
8
  *
8
- * TODO: This should actually receive the mark as parameter
9
- *
10
9
  * TODO: This method should have a test. But how to mock Mark...
11
10
  *
12
11
  * @param {import("../marks/mark.js").default} mark
@@ -40,8 +39,9 @@ export default function createEncoders(mark, encoding) {
40
39
  const resolution = mark.unitView.getScaleResolution(channelWithScale);
41
40
 
42
41
  encoders[channel] = createEncoder(
42
+ mark,
43
43
  encoding[channel],
44
- resolution?.getScale(),
44
+ resolution?.scale,
45
45
  mark.unitView.getAccessor(channel),
46
46
  channel
47
47
  );
@@ -51,14 +51,14 @@ export default function createEncoders(mark, encoding) {
51
51
  }
52
52
 
53
53
  /**
54
- *
54
+ * @param {import("../marks/mark.js").default} mark
55
55
  * @param {import("../spec/channel.js").ChannelDef} channelDef
56
56
  * @param {any} scale
57
57
  * @param {Accessor} accessor
58
58
  * @param {Channel} channel
59
59
  * @returns {Encoder}
60
60
  */
61
- export function createEncoder(channelDef, scale, accessor, channel) {
61
+ export function createEncoder(mark, channelDef, scale, accessor, channel) {
62
62
  /**
63
63
  * @typedef {import("../spec/channel.js").Channel} Channel
64
64
  * @typedef {import("../types/encoder.js").Encoder} Encoder
@@ -69,11 +69,21 @@ export function createEncoder(channelDef, scale, accessor, channel) {
69
69
  let encoder;
70
70
 
71
71
  if (isValueDef(channelDef)) {
72
- const value = channelDef.value;
73
- encoder = /** @type {Encoder} */ ((datum) => value);
74
- encoder.constant = true;
75
- encoder.constantValue = true;
76
- encoder.accessor = undefined;
72
+ if (isExprRef(channelDef.value)) {
73
+ const fn = mark.unitView.paramMediator.createExpression(
74
+ channelDef.value.expr
75
+ );
76
+ encoder = /** @type {Encoder} */ ((datum) => fn(null));
77
+ encoder.constant = true;
78
+ encoder.constantValue = false;
79
+ encoder.accessor = accessor;
80
+ } else {
81
+ const value = channelDef.value;
82
+ encoder = /** @type {Encoder} */ ((datum) => value);
83
+ encoder.constant = true;
84
+ encoder.constantValue = true;
85
+ encoder.accessor = undefined;
86
+ }
77
87
  } else if (accessor) {
78
88
  if (channel == "text") {
79
89
  // TODO: Define somewhere channels that don't use a scale
@@ -29,6 +29,7 @@ describe("Encoder", () => {
29
29
  const encoders = {};
30
30
  for (const [channel, channelDef] of Object.entries(encoding)) {
31
31
  encoders[channel] = createEncoder(
32
+ null, // TODO: stub the mark
32
33
  channelDef,
33
34
  scales[channel],
34
35
  accessorFactory.createAccessor(encodingSpecs[channel]),
@@ -95,4 +96,6 @@ describe("Encoder", () => {
95
96
  });
96
97
 
97
98
  // TODO: Test indexer
99
+
100
+ // TODO: Text ExprRef
98
101
  });
@@ -13,6 +13,7 @@ export default class GenomeSpy {
13
13
  */
14
14
  constructor(container: HTMLElement, spec: import("./spec/root.js").RootSpec, options?: import("./types/embedApi.js").EmbedOptions);
15
15
  container: HTMLElement;
16
+ options: import("./types/embedApi.js").EmbedOptions;
16
17
  /** @type {(() => void)[]} */
17
18
  _destructionCallbacks: (() => void)[];
18
19
  /** Root level configuration object */
@@ -69,13 +70,16 @@ export default class GenomeSpy {
69
70
  tooltipHandlers: Record<string, import("./tooltip/tooltipHandler.js").TooltipHandler>;
70
71
  /** @type {View} */
71
72
  viewRoot: import("./view/view.js").default;
72
- _paramBroker: ParamBroker;
73
73
  /**
74
74
  * Views that are currently loading data using lazy sources.
75
75
  *
76
76
  * @type {Map<View, boolean>}
77
77
  */
78
78
  _loadingViews: Map<import("./view/view.js").default, boolean>;
79
+ /**
80
+ * @type {HTMLElement}
81
+ */
82
+ _inputBindingContainer: HTMLElement;
79
83
  /**
80
84
  *
81
85
  * @param {(name: string) => any[]} provider
@@ -103,10 +107,9 @@ export default class GenomeSpy {
103
107
  * animations with html elements than with WebGL.
104
108
  */
105
109
  _updateLoadingIndicators(): void;
106
- _prepareContainer(): void;
107
110
  _glHelper: WebGLHelper;
108
- loadingMessageElement: HTMLDivElement;
109
- loadingIndicatorsElement: HTMLDivElement;
111
+ loadingMessageElement: HTMLElement;
112
+ loadingIndicatorsElement: HTMLElement;
110
113
  tooltip: Tooltip;
111
114
  /**
112
115
  * Unregisters all listeners, removes all created dom elements, removes all css classes from the container
@@ -139,6 +142,7 @@ export default class GenomeSpy {
139
142
  renderPickingFramebuffer(): void;
140
143
  getSearchableViews(): UnitView[];
141
144
  getNamedScaleResolutions(): Map<string, import("./view/scaleResolution.js").default>;
145
+ #private;
142
146
  }
143
147
  /**
144
148
  * Events that are broadcasted to all views.
@@ -150,7 +154,6 @@ import Animator from "./utils/animator.js";
150
154
  import GenomeStore from "./genome/genomeStore.js";
151
155
  import BufferedViewRenderingContext from "./view/renderingContext/bufferedViewRenderingContext.js";
152
156
  import Inertia from "./utils/inertia.js";
153
- import ParamBroker from "./paramBroker.js";
154
157
  import WebGLHelper from "./gl/webGLHelper.js";
155
158
  import Tooltip from "./utils/ui/tooltip.js";
156
159
  import UnitView from "./view/unitView.js";
@@ -1 +1 @@
1
- {"version":3,"file":"genomeSpy.d.ts","sourceRoot":"","sources":["../../src/genomeSpy.js"],"names":[],"mappings":"AA6CA;IACI;;;;;OAKG;IAEH;;;;;OAKG;IACH,uBAJW,WAAW,qDAEX,OAAO,qBAAqB,EAAE,YAAY,EAyFpD;IAtFG,uBAA0B;IAE1B,6BAA6B;IAC7B,uBADW,CAAC,MAAM,IAAI,CAAC,EAAE,CACM;IAM/B,sCAAsC;IACtC,wCAAgB;IAEhB,iCAA4C;IAC5C,yBAAoC;IAEpC,4CAA4C;IAC5C,oBADW,QAAU,MAAM,KAAE,MAAM,EAAE,CAAC,EAAE,CACZ;IAE5B,mBAAoD;IAEpD,0BAA0B;IAC1B,aADW,WAAW,CACM;IAE5B;;;;;OAKG;IACH,qEAF0B,OAAO,CAE8B;IAE/D,2CAA2C;IAC3C,mBADW,4BAA4B,CACL;IAClC,2CAA2C;IAC3C,iBADW,4BAA4B,CACP;IAEhC,oDAAoD;IACpD,6BAAgC;IAEhC;;;OAGG;IACH,eAFU;QAAE,IAAI,EAAE,OAAO,iBAAiB,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,oBAAoB,EAAE,KAAK,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAEpF;IAE9B,uBAA+C;IAE/C;;;OAGG;IACH,oBAFU,IAAI,MAAM,EAAE,QAAU,aAAa,KAAE,IAAI,CAAC,EAAE,CAAC,CAEpB;IAEnC;;;;;;OAMG;IACH,yCAFkC,GAAG,KAAK,IAAI,GAEd;IAEhC;;;OAGG;IACH,kDAFkC,GAAG,KAAK,IAAI,GAEL;IAEzC,oFAAoF;IACpF,iBADW,OAAO,MAAM,EAAE,OAAO,6BAA6B,EAAE,cAAc,CAAC,CAK9E;IAED,mBAAmB;IACnB,2CAAyB;IAEzB,0BAAqC;IAErC;;;;OAIG;IACH,8DAA8B;IAGlC;;;OAGG;IACH,2CAFkB,MAAM,KAAK,GAAG,EAAE,QAIjC;IAED;;OAEG;IACH,+BAFW,MAAM,YAShB;IAED;;;;OAIG;IACH,sBAHW,MAAM,QACN,GAAG,EAAE,QAaf;IAED;;;;;OAKG;IACH,gBAHW,kBAAkB,YAClB,GAAG,QAQb;IAED;;;OAGG;IACH,iCAyCC;IAED,0BA0EC;IAtEG,uBAOC;IAyCD,sCAA0D;IAS1D,yCAA6D;IAI7D,iBAA0C;IAW9C;;OAEG;IACH,gBAmBC;IAED,sCA8MC;IAED;;;OAGG;IACH,UAFa,QAAQ,OAAO,CAAC,CA6B5B;IAED,4BA6IC;IAjIe,iCAAoC;IAmIpD;;;OAGG;IACH,kBAHW,MAAM,KACN,MAAM,QAiEhB;IAED;;;;;;;OAOG;IACH,oDAHuB,QAAQ,MAAM,GAAG,WAAW,GAAG,OAAO,KAAK,EAAE,cAAc,CAAC,QAYlF;IAED,sBAyCC;IAED,kBAIC;IAED,iCAOC;IAED,iCASC;IAED,qFAWC;CACJ;;;;iCA51BY,eAAe,GAAG,YAAY,GAAG,QAAQ,GAAG,gBAAgB;4BAhC7C,uBAAuB;4BA0BP,uBAAuB;qBAZ9C,qBAAqB;wBAIlB,yBAAyB;yCARR,yDAAyD;oBAYvD,oBAAoB;wBAMvC,kBAAkB;wBApBlB,qBAAqB;oBAVzB,uBAAuB;qBAQtB,oBAAoB"}
1
+ {"version":3,"file":"genomeSpy.d.ts","sourceRoot":"","sources":["../../src/genomeSpy.js"],"names":[],"mappings":"AA8CA;IACI;;;;;OAKG;IAEH;;;;;OAKG;IACH,uBAJW,WAAW,qDAEX,OAAO,qBAAqB,EAAE,YAAY,EA2FpD;IAxFG,uBAA0B;IAC1B,oDAAsB;IAItB,6BAA6B;IAC7B,uBADW,CAAC,MAAM,IAAI,CAAC,EAAE,CACM;IAE/B,sCAAsC;IACtC,wCAAgB;IAEhB,iCAA4C;IAC5C,yBAAoC;IAEpC,4CAA4C;IAC5C,oBADW,QAAU,MAAM,KAAE,MAAM,EAAE,CAAC,EAAE,CACZ;IAE5B,mBAAoD;IAEpD,0BAA0B;IAC1B,aADW,WAAW,CACM;IAE5B;;;;;OAKG;IACH,qEAF0B,OAAO,CAE8B;IAE/D,2CAA2C;IAC3C,mBADW,4BAA4B,CACL;IAClC,2CAA2C;IAC3C,iBADW,4BAA4B,CACP;IAEhC,oDAAoD;IACpD,6BAAgC;IAEhC;;;OAGG;IACH,eAFU;QAAE,IAAI,EAAE,OAAO,iBAAiB,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,OAAO,oBAAoB,EAAE,KAAK,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAEpF;IAE9B,uBAA+C;IAE/C;;;OAGG;IACH,oBAFU,IAAI,MAAM,EAAE,QAAU,aAAa,KAAE,IAAI,CAAC,EAAE,CAAC,CAEpB;IAEnC;;;;;;OAMG;IACH,yCAFkC,GAAG,KAAK,IAAI,GAEd;IAEhC;;;OAGG;IACH,kDAFkC,GAAG,KAAK,IAAI,GAEL;IAEzC,oFAAoF;IACpF,iBADW,OAAO,MAAM,EAAE,OAAO,6BAA6B,EAAE,cAAc,CAAC,CAK9E;IAED,mBAAmB;IACnB,2CAAyB;IAEzB;;;;OAIG;IACH,8DAA8B;IAE9B;;OAEG;IACH,wBAFU,WAAW,CAEkB;IA2C3C;;;OAGG;IACH,2CAFkB,MAAM,KAAK,GAAG,EAAE,QAIjC;IAED;;OAEG;IACH,+BAFW,MAAM,YAShB;IAED;;;;OAIG;IACH,sBAHW,MAAM,QACN,GAAG,EAAE,QAaf;IAED;;;;;OAKG;IACH,gBAHW,kBAAkB,YAClB,GAAG,QAQb;IAED;;;OAGG;IACH,iCAyCC;IA0DG,uBAOC;IAGD,mCAGE;IAOF,sCAEE;IAGF,iBAAyC;IAW7C;;OAEG;IACH,gBAuBC;IAED,sCA8MC;IAED;;;OAGG;IACH,UAFa,QAAQ,OAAO,CAAC,CA6B5B;IAED,4BA6IC;IAjIe,iCAAoC;IAmIpD;;;OAGG;IACH,kBAHW,MAAM,KACN,MAAM,QAiEhB;IAED;;;;;;;OAOG;IACH,oDAHuB,QAAQ,MAAM,GAAG,WAAW,GAAG,OAAO,KAAK,EAAE,cAAc,CAAC,QAYlF;IAED,sBAyCC;IAED,kBAIC;IAED,iCAOC;IAED,iCASC;IAED,qFAWC;;CACJ;;;;iCA15BY,eAAe,GAAG,YAAY,GAAG,QAAQ,GAAG,gBAAgB;4BAjC7C,uBAAuB;4BA2BP,uBAAuB;qBAZ9C,qBAAqB;wBAIlB,yBAAyB;yCARR,yDAAyD;oBAYvD,oBAAoB;wBAdvC,qBAAqB;oBAXzB,uBAAuB;qBAStB,oBAAoB"}
@@ -1,5 +1,5 @@
1
1
  import { formats as vegaFormats } from "vega-loader";
2
- import { html, render } from "lit-html";
2
+ import { html, render } from "lit";
3
3
  import { styleMap } from "lit/directives/style-map.js";
4
4
  import SPINNER from "./img/90-ring-with-bg.svg";
5
5
 
@@ -11,6 +11,7 @@ import {
11
11
  checkForDuplicateScaleNames,
12
12
  setImplicitScaleNames,
13
13
  calculateCanvasSize,
14
+ calculateViewRootSize,
14
15
  } from "./view/viewUtils.js";
15
16
  import UnitView from "./view/unitView.js";
16
17
 
@@ -34,7 +35,7 @@ import dataTooltipHandler from "./tooltip/dataTooltipHandler.js";
34
35
  import { invalidatePrefix } from "./utils/propertyCacher.js";
35
36
  import { VIEW_ROOT_NAME, ViewFactory } from "./view/viewFactory.js";
36
37
  import { reconfigureScales } from "./view/scaleResolution.js";
37
- import ParamBroker from "./paramBroker.js";
38
+ import createBindingInputs from "./utils/inputBinding.js";
38
39
 
39
40
  /**
40
41
  * Events that are broadcasted to all views.
@@ -59,14 +60,13 @@ export default class GenomeSpy {
59
60
  */
60
61
  constructor(container, spec, options = {}) {
61
62
  this.container = container;
63
+ this.options = options;
64
+
65
+ options.inputBindingContainer ??= "default";
62
66
 
63
67
  /** @type {(() => void)[]} */
64
68
  this._destructionCallbacks = [];
65
69
 
66
- const styleElement = document.createElement("style");
67
- styleElement.innerHTML = css;
68
- container.appendChild(styleElement);
69
-
70
70
  /** Root level configuration object */
71
71
  this.spec = spec;
72
72
 
@@ -136,14 +136,57 @@ export default class GenomeSpy {
136
136
  /** @type {View} */
137
137
  this.viewRoot = undefined;
138
138
 
139
- this._paramBroker = new ParamBroker();
140
-
141
139
  /**
142
140
  * Views that are currently loading data using lazy sources.
143
141
  *
144
142
  * @type {Map<View, boolean>}
145
143
  */
146
144
  this._loadingViews = new Map();
145
+
146
+ /**
147
+ * @type {HTMLElement}
148
+ */
149
+ this._inputBindingContainer = undefined;
150
+ }
151
+
152
+ get #canvasWrapper() {
153
+ return /** @type {HTMLElement} */ (
154
+ this.container.querySelector(".canvas-wrapper")
155
+ );
156
+ }
157
+
158
+ #initializeParameterBindings() {
159
+ /** @type {import("lit").TemplateResult[]} */
160
+ const inputs = [];
161
+
162
+ this.viewRoot.visit((view) => {
163
+ const mediator = view.paramMediator;
164
+ inputs.push(...createBindingInputs(mediator));
165
+ });
166
+ const ibc = this.options.inputBindingContainer;
167
+
168
+ if (!ibc || ibc == "none" || !inputs.length) {
169
+ return;
170
+ }
171
+
172
+ this._inputBindingContainer = element("div", {
173
+ className: "gs-input-bindings",
174
+ });
175
+
176
+ if (ibc == "default") {
177
+ this.container.appendChild(this._inputBindingContainer);
178
+ } else if (ibc instanceof HTMLElement) {
179
+ ibc.appendChild(this._inputBindingContainer);
180
+ } else {
181
+ throw new Error("Invalid inputBindingContainer");
182
+ }
183
+
184
+ if (inputs.length) {
185
+ render(
186
+ html`<div class="gs-input-binding">${inputs}</div>`,
187
+ this._inputBindingContainer
188
+ );
189
+ }
147
190
  }
148
191
 
149
192
  /**
@@ -203,7 +246,7 @@ export default class GenomeSpy {
203
246
  * animations with html elements than with WebGL.
204
247
  */
205
248
  _updateLoadingIndicators() {
206
- /** @type {import("lit-html").TemplateResult[]} */
249
+ /** @type {import("lit").TemplateResult[]} */
207
250
  const indicators = [];
208
251
 
209
252
  const isSomethingVisible = () =>
@@ -245,17 +288,10 @@ export default class GenomeSpy {
245
288
  render(indicators, this.loadingIndicatorsElement);
246
289
  }
247
290
 
248
- _prepareContainer() {
249
- this.container.classList.add("genome-spy");
250
- this.container.classList.add("loading");
251
-
252
- this._glHelper = new WebGLHelper(
253
- this.container,
254
- () =>
255
- this.viewRoot
256
- ? calculateCanvasSize(this.viewRoot)
257
- : { width: undefined, height: undefined },
258
- this.spec.background
291
+ #setupDpr() {
292
+ const dprSetter = this.viewRoot.paramMediator.allocateSetter(
293
+ "devicePixelRatio",
294
+ window.devicePixelRatio
259
295
  );
260
296
 
261
297
  const resizeCallback = () => {
@@ -271,9 +307,6 @@ export default class GenomeSpy {
271
307
  resizeObserver.observe(this.container);
272
308
  this._destructionCallbacks.push(() => resizeObserver.disconnect());
273
309
 
274
- const dprSetter = this._paramBroker.allocateSetter("devicePixelRatio");
275
- dprSetter(window.devicePixelRatio);
276
-
277
310
  /** @type {() => void} */
278
311
  let remove = null;
279
312
 
@@ -295,22 +328,48 @@ export default class GenomeSpy {
295
328
  if (remove) {
296
329
  this._destructionCallbacks.push(remove);
297
330
  }
331
+ }
332
+
333
+ #prepareContainer() {
334
+ this.container.classList.add("genome-spy");
335
+
336
+ const styleElement = document.createElement("style");
337
+ styleElement.innerHTML = css;
338
+ this.container.appendChild(styleElement);
339
+
340
+ const canvasWrapper = element("div", {
341
+ class: "canvas-wrapper",
342
+ });
343
+ this.container.appendChild(canvasWrapper);
344
+
345
+ canvasWrapper.classList.add("loading");
346
+
347
+ this._glHelper = new WebGLHelper(
348
+ canvasWrapper,
349
+ () =>
350
+ this.viewRoot
351
+ ? calculateCanvasSize(calculateViewRootSize(this.viewRoot))
352
+ : { width: undefined, height: undefined },
353
+ this.spec.background
354
+ );
298
355
 
299
356
  // The initial loading message that is shown until the first frame is rendered
300
- this.loadingMessageElement = document.createElement("div");
301
- this.loadingMessageElement.className = "loading-message";
302
- this.loadingMessageElement.innerHTML = `<div class="message">Loading<span class="ellipsis">...</span></div>`;
303
- this.container.appendChild(this.loadingMessageElement);
357
+ this.loadingMessageElement = element("div", {
358
+ class: "loading-message",
359
+ innerHTML: `<div class="message">Loading<span class="ellipsis">...</span></div>`,
360
+ });
361
+ canvasWrapper.appendChild(this.loadingMessageElement);
304
362
 
305
363
  // A container for loading indicators (for lazy data sources.)
306
364
  // These could alternatively be included in the view hierarchy,
307
365
  // but it's easier this way – particularly if we want to show
308
366
  // some fancy animated spinners.
309
- this.loadingIndicatorsElement = document.createElement("div");
310
- this.loadingIndicatorsElement.className = "loading-indicators";
311
- this.container.appendChild(this.loadingIndicatorsElement);
367
+ this.loadingIndicatorsElement = element("div", {
368
+ class: "loading-indicators",
369
+ });
370
+ canvasWrapper.appendChild(this.loadingIndicatorsElement);
312
371
 
313
- this.tooltip = new Tooltip(this.container);
372
+ this.tooltip = new Tooltip(canvasWrapper);
314
373
 
315
374
  this.loadingMessageElement
316
375
  .querySelector(".message")
@@ -327,8 +386,10 @@ export default class GenomeSpy {
327
386
  destroy() {
328
387
  // TODO: There's a memory leak somewhere
329
388
 
389
+ const canvasWrapper = this.#canvasWrapper;
390
+
330
391
  this.container.classList.remove("genome-spy");
331
- this.container.classList.remove("loading");
392
+ canvasWrapper.classList.remove("loading");
332
393
 
333
394
  for (const [type, listeners] of this._keyboardListeners) {
334
395
  for (const listener of listeners) {
@@ -340,6 +401,8 @@ export default class GenomeSpy {
340
401
 
341
402
  this._glHelper.finalize();
342
403
 
404
+ this._inputBindingContainer?.remove();
405
+
343
406
  while (this.container.firstChild) {
344
407
  this.container.firstChild.remove();
345
408
  }
@@ -367,8 +430,6 @@ export default class GenomeSpy {
367
430
  return self._glHelper.dpr;
368
431
  },
369
432
 
370
- paramBroker: this._paramBroker,
371
-
372
433
  requestLayoutReflow: () => {
373
434
  // placeholder
374
435
  },
@@ -463,6 +524,11 @@ export default class GenomeSpy {
463
524
  VIEW_ROOT_NAME
464
525
  );
465
526
 
527
+ this.#canvasWrapper.style.flexGrow =
528
+ calculateViewRootSize(this.viewRoot).height.grow > 0 ? "1" : "0";
529
+
530
+ this.#initializeParameterBindings();
531
+
466
532
  checkForDuplicateScaleNames(this.viewRoot);
467
533
 
468
534
  setImplicitScaleNames(this.viewRoot);
@@ -477,6 +543,7 @@ export default class GenomeSpy {
477
543
  // We should now have a complete view hierarchy. Let's update the canvas size
478
544
  // and ensure that the loading message is visible.
479
545
  this._glHelper.invalidateSize();
546
+ this.#setupDpr();
480
547
 
481
548
  // Collect all unit views to a list because they need plenty of initialization
482
549
  const unitViews = /** @type {UnitView[]} */ (
@@ -506,12 +573,14 @@ export default class GenomeSpy {
506
573
  view.mark.initializeData();
507
574
  // Update WebGL buffers
508
575
  view.mark.updateGraphicsData();
576
+ context.animator.requestRender();
509
577
  }, view);
510
578
  }
511
579
 
512
580
  // Have to wait until asynchronous font loading is complete.
513
581
  // Text mark's geometry builder needs font metrics before data can be
514
582
  // converted into geometries.
583
+ // TODO: Make updateGraphicsData async and await font loading there.
515
584
  await context.fontManager.waitUntilReady();
516
585
 
517
586
  // Find all data sources and initiate loading
@@ -533,12 +602,6 @@ export default class GenomeSpy {
533
602
 
534
603
  await graphicsInitialized;
535
604
 
536
- this.viewRoot.visit((view) => {
537
- for (const resolution of Object.values(view.resolutions.scale)) {
538
- this._glHelper.createRangeTexture(resolution);
539
- }
540
- });
541
-
542
605
  for (const view of unitViews) {
543
606
  view.mark.finalizeGraphicsInitialization();
544
607
  }
@@ -559,7 +622,7 @@ export default class GenomeSpy {
559
622
  */
560
623
  async launch() {
561
624
  try {
562
- this._prepareContainer();
625
+ this.#prepareContainer();
563
626
 
564
627
  await this._prepareViewsAndData();
565
628
 
@@ -578,7 +641,7 @@ export default class GenomeSpy {
578
641
 
579
642
  return false;
580
643
  } finally {
581
- this.container.classList.remove("loading");
644
+ this.#canvasWrapper.classList.remove("loading");
582
645
  // Transition listener doesn't appear to work on observablehq
583
646
  window.setTimeout(() => {
584
647
  this.loadingMessageElement.style.display = "none";
@@ -914,3 +977,19 @@ function createMessageBox(container, message) {
914
977
  messageBox.appendChild(messageText);
915
978
  container.appendChild(messageBox);
916
979
  }
980
+
981
+ /**
982
+ * @param {string} tag
983
+ * @param {Record<string, any>} attrs
984
+ */
985
+ function element(tag, attrs) {
986
+ const el = document.createElement(tag);
987
+ for (const [key, value] of Object.entries(attrs)) {
988
+ if (["innerHTML", "innerText", "className"].includes(key)) {
989
+ // @ts-ignore
990
+ el[key] = value;
991
+ }
992
+ el.setAttribute(key, value);
993
+ }
994
+ return el;
995
+ }