@genome-spy/core 0.24.2 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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.0",
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": "1b8f0acaf7aaf15b1c6d348b7a1d891e41dbc977"
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) => {
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