@genome-spy/core 0.24.2 → 0.25.1

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/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  },
8
8
  "contributors": [],
9
9
  "license": "BSD-2-Clause",
10
- "version": "0.24.2",
10
+ "version": "0.25.1",
11
11
  "main": "dist/index.js",
12
12
  "module": "src/index.js",
13
13
  "exports": {
@@ -53,5 +53,5 @@
53
53
  "vega-scale": "^7.1.1",
54
54
  "vega-util": "^1.16.0"
55
55
  },
56
- "gitHead": "587ef91161457b17a039f82111d15df339e1e9f5"
56
+ "gitHead": "e9c4acf0c725ce1ef46303a01a05d7ccf6dcc9c3"
57
57
  }
@@ -4,6 +4,8 @@
4
4
  * @typedef {import("./collector").default} Collector
5
5
  */
6
6
 
7
+ import NamedSource from "./sources/namedSource";
8
+
7
9
  /**
8
10
  * @template H A key (string, object, whatever) that is used to retrieve
9
11
  * data sources and collectors.
@@ -76,6 +78,43 @@ export default class DataFlow {
76
78
  return this._dataSourcesByHost.get(key);
77
79
  }
78
80
 
81
+ /**
82
+ *
83
+ * @param {string} name
84
+ */
85
+ findNamedDataSource(name) {
86
+ /** @type {NamedSource} */
87
+ let namedSource;
88
+ /** @type {H[]} */
89
+ let hosts = [];
90
+
91
+ // Note: If a named sources with the same name are present at multiple locations in the
92
+ // view hierarchy, the should actually be exactly the same instance. It's arranged that
93
+ // way in the data flow optimization phase.
94
+
95
+ for (const [host, dataSource] of this._dataSourcesByHost.entries()) {
96
+ if (dataSource instanceof NamedSource) {
97
+ if (name == dataSource.identifier) {
98
+ if (namedSource && namedSource !== dataSource) {
99
+ // TODO: Write a test case for this and remove the runtime check.
100
+ throw new Error(
101
+ `Found multiple instances of named data: ${name}. Data flow optimization is broken (it's a bug).`
102
+ );
103
+ }
104
+ namedSource = dataSource;
105
+ hosts.push(host);
106
+ }
107
+ }
108
+ }
109
+
110
+ if (namedSource) {
111
+ return {
112
+ dataSource: namedSource,
113
+ hosts,
114
+ };
115
+ }
116
+ }
117
+
79
118
  /**
80
119
  *
81
120
  * @param {Collector} collector
@@ -10,14 +10,20 @@ export function isNamedData(data) {
10
10
  }
11
11
 
12
12
  export default class NamedSource extends DataSource {
13
+ /**
14
+ * Data that has been provided explicitly using the updateDynamicData method
15
+ * @type {any[]}
16
+ */
17
+ #explicitData;
18
+
13
19
  /**
14
20
  * @param {import("../../spec/data").NamedData} params
15
- * @param {function(string):any[]} getNamedData
21
+ * @param {function(string):any[]} provider Function that retrieves a dataset using a name
16
22
  */
17
- constructor(params, getNamedData) {
23
+ constructor(params, provider) {
18
24
  super();
19
25
 
20
- this.getNamedData = getNamedData;
26
+ this.provider = provider;
21
27
  this.params = params;
22
28
  }
23
29
 
@@ -28,17 +34,20 @@ export default class NamedSource extends DataSource {
28
34
  return this.params.name;
29
35
  }
30
36
 
31
- _getValues() {
32
- const data = this.getNamedData(this.params.name);
33
- if (data) {
34
- return data;
35
- } else {
36
- throw new Error("Cannot find named data: " + this.params.name);
37
- }
37
+ /**
38
+ * Update the named data. If data is omitted, a data provider is used instead.
39
+ *
40
+ * @param {import("../flowNode").Datum[]} [data]
41
+ */
42
+ updateDynamicData(data) {
43
+ // TODO: Throw is data is undefined and the provider is unable to provide any data
44
+ this.#explicitData = data;
45
+ this.loadSynchronously();
38
46
  }
39
47
 
40
48
  loadSynchronously() {
41
- const data = this._getValues();
49
+ const data =
50
+ this.#explicitData ?? this.provider(this.params.name) ?? [];
42
51
 
43
52
  /** @type {(x: any) => import("../flowNode").Datum} */
44
53
  let wrap = (x) => x;
package/src/embedApi.d.ts CHANGED
@@ -17,9 +17,9 @@ export type EmbedFunction = (
17
17
 
18
18
  export interface EmbedOptions {
19
19
  /**
20
- * A function that allows retrieval of named data sources.
21
- *
22
- * TODO: Support dynamic updates, i.e., pushing new data.
20
+ * A function that allows retrieval of named data. There are two ways to provide named data:
21
+ * 1. A data provider (this)
22
+ * 2. Explicit updates using the `updateNamedData` method (the other).
23
23
  */
24
24
  namedDataProvider?: (name: string) => any[];
25
25
 
@@ -52,8 +52,16 @@ export interface EmbedResult {
52
52
  removeEventListener: (type: string, listener: (event: any) => void) => void;
53
53
 
54
54
  /**
55
- * Returns a named _ScaleResolution_ object that allows for attaching event
55
+ * Returns a named `ScaleResolution` object that allows for attaching event
56
56
  * listeners and controlling the scale domain.
57
57
  */
58
58
  getScaleResolutionByName: (name: string) => ScaleResolutionApi;
59
+
60
+ /**
61
+ * Updates a named dataset
62
+ *
63
+ * @param name data source to update
64
+ * @param data new data. If left undefined, the data is retrieved from a provider.
65
+ */
66
+ updateNamedData: (name: string, data?: any[]) => void;
59
67
  }
package/src/genomeSpy.js CHANGED
@@ -137,10 +137,9 @@ export default class GenomeSpy {
137
137
  }
138
138
 
139
139
  /**
140
- *
141
140
  * @param {string} name
142
141
  */
143
- getNamedData(name) {
142
+ getNamedDataFromProvider(name) {
144
143
  for (const provider of this.namedDataProviders) {
145
144
  const data = provider(name);
146
145
  if (data) {
@@ -149,6 +148,36 @@ export default class GenomeSpy {
149
148
  }
150
149
  }
151
150
 
151
+ /**
152
+ *
153
+ * @param {string} name
154
+ * @param {any[]} data
155
+ */
156
+ updateNamedData(name, data) {
157
+ const namedSource =
158
+ this.viewRoot.context.dataFlow.findNamedDataSource(name);
159
+ if (!namedSource) {
160
+ throw new Error("No such named data source: " + name);
161
+ }
162
+
163
+ namedSource.dataSource.updateDynamicData(data);
164
+
165
+ // Scale domains may need adjustment.
166
+ // TODO: Refactor so that Collectors handle scale extents etc.
167
+ for (const host of namedSource.hosts) {
168
+ host.visit((view) => {
169
+ for (const resolution of Object.values(
170
+ view.resolutions.scale
171
+ )) {
172
+ // TODO: Only update domain
173
+ resolution.reconfigure();
174
+ }
175
+ });
176
+ }
177
+
178
+ this.animator.requestRender();
179
+ }
180
+
152
181
  /**
153
182
  * Broadcast a message to all views
154
183
  *
@@ -243,7 +272,7 @@ export default class GenomeSpy {
243
272
  // placeholder
244
273
  },
245
274
  updateTooltip: this.updateTooltip.bind(this),
246
- getNamedData: this.getNamedData.bind(this),
275
+ getNamedDataFromProvider: this.getNamedDataFromProvider.bind(this),
247
276
  getCurrentHover: () => this._currentHover,
248
277
 
249
278
  addKeyboardListener: (type, listener) => {
@@ -1 +1 @@
1
- flat in highp vec4 vPickingColor;
1
+ in highp vec4 vPickingColor;
@@ -4,7 +4,7 @@
4
4
  * https://deck.gl/docs/developer-guide/custom-layers/picking
5
5
  */
6
6
 
7
- flat out highp vec4 vPickingColor;
7
+ out highp vec4 vPickingColor;
8
8
 
9
9
  /**
10
10
  * Passes the unique id to the fragment shader as a color if picking is enabled.
@@ -4,15 +4,15 @@ const lowp vec4 black = vec4(0.0, 0.0, 0.0, 1.0);
4
4
  uniform bool uInwardStroke;
5
5
  uniform float uGradientStrength;
6
6
 
7
- flat in float vRadius;
8
- flat in float vRadiusWithPadding;
7
+ in float vRadius;
8
+ in float vRadiusWithPadding;
9
9
 
10
- flat in lowp vec4 vFillColor;
11
- flat in lowp vec4 vStrokeColor;
12
- flat in lowp float vShape;
13
- flat in lowp float vHalfStrokeWidth;
10
+ in lowp vec4 vFillColor;
11
+ in lowp vec4 vStrokeColor;
12
+ in lowp float vShape;
13
+ in lowp float vHalfStrokeWidth;
14
14
 
15
- flat in mat2 vRotationMatrix;
15
+ in mat2 vRotationMatrix;
16
16
 
17
17
  out lowp vec4 fragColor;
18
18
 
@@ -17,13 +17,13 @@ uniform float uMaxPointSize;
17
17
  uniform float uZoomLevel;
18
18
  uniform float uSemanticThreshold;
19
19
 
20
- flat out float vRadius;
21
- flat out float vRadiusWithPadding;
22
- flat out lowp vec4 vFillColor;
23
- flat out lowp vec4 vStrokeColor;
24
- flat out lowp float vShape;
25
- flat out lowp float vHalfStrokeWidth;
26
- flat out mat2 vRotationMatrix;
20
+ out float vRadius;
21
+ out float vRadiusWithPadding;
22
+ out lowp vec4 vFillColor;
23
+ out lowp vec4 vStrokeColor;
24
+ out lowp float vShape;
25
+ out lowp float vHalfStrokeWidth;
26
+ out mat2 vRotationMatrix;
27
27
 
28
28
 
29
29
  float computeSemanticThresholdFactor() {
@@ -2,12 +2,12 @@
2
2
  in vec2 vPosInPixels;
3
3
  #endif
4
4
 
5
- flat in vec2 vHalfSizeInPixels;
5
+ in vec2 vHalfSizeInPixels;
6
6
 
7
- flat in lowp vec4 vFillColor;
8
- flat in lowp vec4 vStrokeColor;
9
- flat in float vHalfStrokeWidth;
10
- flat in vec4 vCornerRadii;
7
+ in lowp vec4 vFillColor;
8
+ in lowp vec4 vStrokeColor;
9
+ in float vHalfStrokeWidth;
10
+ in vec4 vCornerRadii;
11
11
 
12
12
  out lowp vec4 fragColor;
13
13
 
@@ -15,10 +15,10 @@ uniform float uMinOpacity;
15
15
  /** top-right, bottom-right, top-left, bottom-left */
16
16
  uniform vec4 uCornerRadii;
17
17
 
18
- flat out lowp vec4 vFillColor;
19
- flat out lowp vec4 vStrokeColor;
20
- flat out float vHalfStrokeWidth;
21
- flat out vec4 vCornerRadii;
18
+ out lowp vec4 vFillColor;
19
+ out lowp vec4 vStrokeColor;
20
+ out float vHalfStrokeWidth;
21
+ out vec4 vCornerRadii;
22
22
 
23
23
 
24
24
  #if defined(ROUNDED_CORNERS) || defined(STROKED)
@@ -27,7 +27,7 @@ out vec2 vPosInPixels;
27
27
  #endif
28
28
 
29
29
  /** Size of the rect in pixels */
30
- flat out vec2 vHalfSizeInPixels;
30
+ out vec2 vHalfSizeInPixels;
31
31
 
32
32
  /**
33
33
  * Clamps the minimumSize and returns an opacity that reflects the amount of clamping.
@@ -8,8 +8,8 @@ uniform float uDashTextureSize;
8
8
  uniform float uStrokeDashOffset;
9
9
  uniform lowp int uStrokeCap;
10
10
 
11
- flat in vec4 vColor;
12
- flat in float vSize;
11
+ in vec4 vColor;
12
+ in float vSize;
13
13
 
14
14
  /** Position on the rule along its length in pixels */
15
15
  in vec2 vPosInPixels;
@@ -15,10 +15,10 @@ uniform float uMinLength;
15
15
  uniform mediump float uDashTextureSize;
16
16
  uniform lowp int uStrokeCap;
17
17
 
18
- flat out vec4 vColor;
18
+ out vec4 vColor;
19
19
 
20
20
  /** Stroke width */
21
- flat out float vSize;
21
+ out float vSize;
22
22
 
23
23
  /** The distance from the line center to the direction of normal in pixels */
24
24
  out float vNormalLengthInPixels;
@@ -2,8 +2,8 @@ uniform sampler2D uTexture;
2
2
 
3
3
  in vec2 vTexCoord;
4
4
  in float vEdgeFadeOpacity;
5
- flat in vec4 vColor;
6
- flat in float vSlope;
5
+ in vec4 vColor;
6
+ in float vSlope;
7
7
 
8
8
  out lowp vec4 fragColor;
9
9
 
@@ -29,8 +29,8 @@ uniform bool uFlushY;
29
29
  #endif
30
30
 
31
31
  out vec2 vTexCoord;
32
- flat out vec4 vColor;
33
- flat out float vSlope;
32
+ out vec4 vColor;
33
+ out float vSlope;
34
34
  out float vEdgeFadeOpacity;
35
35
 
36
36
  struct RangeResult {
package/src/index.js CHANGED
@@ -11,6 +11,7 @@ export { GenomeSpy, html, icon };
11
11
  * Embeds GenomeSpy into the DOM
12
12
  *
13
13
  * @type {import("./embedApi.js").EmbedFunction}
14
+ * @returns {Promise<import("./embedApi").EmbedResult>}
14
15
  */
15
16
  export async function embed(el, spec, options = {}) {
16
17
  /** @type {HTMLElement} */
@@ -85,6 +86,8 @@ export async function embed(el, spec, options = {}) {
85
86
  getScaleResolutionByName(name) {
86
87
  return genomeSpy.getNamedScaleResolutions().get(name);
87
88
  },
89
+
90
+ updateNamedData: genomeSpy.updateNamedData.bind(genomeSpy),
88
91
  };
89
92
  }
90
93
 
@@ -116,7 +116,11 @@ export function buildDataFlow(root, existingFlow) {
116
116
  const dataSource = isDynamicCallbackData(view.spec.data)
117
117
  ? view.getDynamicDataSource()
118
118
  : isNamedData(view.spec.data)
119
- ? new NamedSource(view.spec.data, view.context.getNamedData)
119
+ ? // TODO: Only one NamedSource instance per unique name should exists
120
+ new NamedSource(
121
+ view.spec.data,
122
+ view.context.getNamedDataFromProvider
123
+ )
120
124
  : createDataSource(view.spec.data, view.getBaseUrl());
121
125
 
122
126
  currentNode = dataSource;
@@ -48,7 +48,7 @@ export default interface ViewContext {
48
48
  listener: (event: KeyboardEvent) => void
49
49
  ) => void;
50
50
 
51
- getNamedData: (name: string) => any[];
51
+ getNamedDataFromProvider: (name: string) => any[];
52
52
 
53
53
  isViewVisible: (view: View) => boolean;
54
54