@genome-spy/core 0.63.0 → 0.65.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/dist/bundle/{index-CCJIjehY.js → AbortablePromiseCache-CcuMrnn7.js} +22 -91
- package/dist/bundle/browser-txUcLy2H.js +123 -0
- package/dist/bundle/index-BQpbYrv4.js +1712 -0
- package/dist/bundle/index-BhtHKLUo.js +73 -0
- package/dist/bundle/index-C0llXMqm.js +280 -0
- package/dist/bundle/index-CCe8rnZz.js +716 -0
- package/dist/bundle/index-CD7FLu9x.js +269 -0
- package/dist/bundle/{index-C08YCM2T.js → index-D-w7Mmt9.js} +246 -126
- package/dist/bundle/index-D74H8TTz.js +508 -0
- package/dist/bundle/index-DhcU-Gk-.js +1487 -0
- package/dist/bundle/index.es.js +5394 -4989
- package/dist/bundle/index.js +420 -362
- package/dist/bundle/inflate-DRgHi_KK.js +1050 -0
- package/dist/schema.json +93 -13
- package/dist/src/data/collector.d.ts +7 -2
- package/dist/src/data/collector.d.ts.map +1 -1
- package/dist/src/data/collector.js +13 -2
- package/dist/src/data/dataFlow.d.ts +20 -42
- package/dist/src/data/dataFlow.d.ts.map +1 -1
- package/dist/src/data/dataFlow.js +57 -80
- package/dist/src/data/dataFlow.test.js +35 -2
- package/dist/src/data/flowHandle.d.ts +15 -0
- package/dist/src/data/flowHandle.d.ts.map +1 -0
- package/dist/src/data/flowHandle.js +13 -0
- package/dist/src/data/flowInit.d.ts +85 -0
- package/dist/src/data/flowInit.d.ts.map +1 -0
- package/dist/src/data/flowInit.js +238 -0
- package/dist/src/data/flowInit.test.d.ts +2 -0
- package/dist/src/data/flowInit.test.d.ts.map +1 -0
- package/dist/src/data/flowInit.test.js +413 -0
- package/dist/src/data/flowOptimizer.d.ts +6 -4
- package/dist/src/data/flowOptimizer.d.ts.map +1 -1
- package/dist/src/data/flowOptimizer.js +29 -14
- package/dist/src/data/flowOptimizer.test.js +20 -15
- package/dist/src/data/sources/lazy/bamSource.js +1 -1
- package/dist/src/data/sources/lazy/bigBedSource.js +1 -1
- package/dist/src/data/sources/lazy/bigWigSource.js +1 -1
- package/dist/src/data/sources/lazy/gff3Source.d.ts +2 -6
- package/dist/src/data/sources/lazy/gff3Source.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/gff3Source.js +4 -8
- package/dist/src/data/sources/lazy/indexedFastaSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/indexedFastaSource.js +17 -17
- package/dist/src/data/sources/lazy/tabixSource.js +1 -1
- package/dist/src/genomeSpy.d.ts +1 -1
- package/dist/src/genomeSpy.d.ts.map +1 -1
- package/dist/src/genomeSpy.js +18 -61
- package/dist/src/gl/webGLHelper.d.ts.map +1 -1
- package/dist/src/gl/webGLHelper.js +8 -0
- package/dist/src/marks/link.fragment.glsl.js +1 -1
- package/dist/src/marks/link.vertex.glsl.js +1 -1
- package/dist/src/marks/mark.d.ts +1 -0
- package/dist/src/marks/mark.d.ts.map +1 -1
- package/dist/src/marks/mark.js +22 -1
- package/dist/src/marks/point.fragment.glsl.js +1 -1
- package/dist/src/marks/point.vertex.glsl.js +1 -1
- package/dist/src/marks/rect.fragment.glsl.js +1 -1
- package/dist/src/marks/rect.vertex.glsl.js +1 -1
- package/dist/src/marks/rule.fragment.glsl.js +1 -1
- package/dist/src/marks/rule.vertex.glsl.js +1 -1
- package/dist/src/marks/text.fragment.glsl.js +1 -1
- package/dist/src/marks/text.vertex.glsl.js +1 -1
- package/dist/src/selection/selection.d.ts +5 -0
- package/dist/src/selection/selection.d.ts.map +1 -1
- package/dist/src/selection/selection.js +43 -6
- package/dist/src/selection/selection.test.d.ts +2 -0
- package/dist/src/selection/selection.test.d.ts.map +1 -0
- package/dist/src/selection/selection.test.js +14 -0
- package/dist/src/spec/parameter.d.ts +28 -2
- package/dist/src/spec/sampleView.d.ts +3 -2
- package/dist/src/styles/{genome-spy.scss → genome-spy.css} +25 -21
- package/dist/src/styles/genome-spy.css.d.ts +1 -1
- package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
- package/dist/src/styles/genome-spy.css.js +264 -195
- package/dist/src/styles/update.sh +14 -4
- package/dist/src/types/viewContext.d.ts +1 -1
- package/dist/src/utils/expression.d.ts +5 -0
- package/dist/src/utils/expression.d.ts.map +1 -1
- package/dist/src/utils/expression.js +37 -0
- package/dist/src/utils/interactionEvent.d.ts +18 -1
- package/dist/src/utils/interactionEvent.d.ts.map +1 -1
- package/dist/src/utils/interactionEvent.js +101 -1
- package/dist/src/utils/interactionEvent.test.d.ts +2 -0
- package/dist/src/utils/interactionEvent.test.d.ts.map +1 -0
- package/dist/src/utils/interactionEvent.test.js +35 -0
- package/dist/src/view/axisResolution.d.ts +5 -0
- package/dist/src/view/axisResolution.d.ts.map +1 -1
- package/dist/src/view/axisResolution.js +16 -1
- package/dist/src/view/facetView.d.ts +1 -1
- package/dist/src/view/facetView.d.ts.map +1 -1
- package/dist/src/view/facetView.js +1 -0
- package/dist/src/view/flowBuilder.d.ts +2 -2
- package/dist/src/view/flowBuilder.d.ts.map +1 -1
- package/dist/src/view/flowBuilder.js +21 -4
- package/dist/src/view/gridView/gridView.d.ts.map +1 -1
- package/dist/src/view/gridView/gridView.js +13 -0
- package/dist/src/view/gridView/selectionRect.d.ts +8 -4
- package/dist/src/view/gridView/selectionRect.d.ts.map +1 -1
- package/dist/src/view/gridView/selectionRect.js +28 -3
- package/dist/src/view/gridView/selectionRect.test.d.ts +2 -0
- package/dist/src/view/gridView/selectionRect.test.d.ts.map +1 -0
- package/dist/src/view/gridView/selectionRect.test.js +87 -0
- package/dist/src/view/paramMediator.d.ts +2 -1
- package/dist/src/view/paramMediator.d.ts.map +1 -1
- package/dist/src/view/paramMediator.js +13 -1
- package/dist/src/view/paramMediator.test.js +22 -0
- package/dist/src/view/scaleResolution.d.ts +5 -0
- package/dist/src/view/scaleResolution.d.ts.map +1 -1
- package/dist/src/view/scaleResolution.js +10 -0
- package/dist/src/view/testUtils.d.ts.map +1 -1
- package/dist/src/view/testUtils.js +16 -4
- package/dist/src/view/unitView.d.ts.map +1 -1
- package/dist/src/view/unitView.js +103 -10
- package/dist/src/view/view.d.ts +17 -1
- package/dist/src/view/view.d.ts.map +1 -1
- package/dist/src/view/view.js +57 -1
- package/dist/src/view/viewDispose.test.d.ts +2 -0
- package/dist/src/view/viewDispose.test.d.ts.map +1 -0
- package/dist/src/view/viewDispose.test.js +110 -0
- package/dist/src/view/viewUtils.d.ts +4 -4
- package/dist/src/view/viewUtils.d.ts.map +1 -1
- package/dist/src/view/viewUtils.js +19 -15
- package/dist/src/view/viewUtils.test.d.ts +2 -0
- package/dist/src/view/viewUtils.test.d.ts.map +1 -0
- package/dist/src/view/viewUtils.test.js +87 -0
- package/package.json +16 -16
- package/dist/bundle/__vite-browser-external-C--ziKoh.js +0 -8
- package/dist/bundle/_commonjsHelpers-DjF3Plf2.js +0 -26
- package/dist/bundle/index-5ajWdKly.js +0 -1319
- package/dist/bundle/index-B03-Om4z.js +0 -274
- package/dist/bundle/index-BftNdA0O.js +0 -27
- package/dist/bundle/index-Bg7C4Xat.js +0 -2750
- package/dist/bundle/index-C3QR8Lv6.js +0 -2131
- package/dist/bundle/index-DTcHjAHp.js +0 -505
- package/dist/bundle/index-DnIkxb0L.js +0 -1025
- package/dist/bundle/index-Ww3TAo6_.js +0 -71
- package/dist/bundle/index-g8iXgW0W.js +0 -651
- package/dist/bundle/long-B-FASCSo.js +0 -2387
- package/dist/bundle/remoteFile-BuaqFGWk.js +0 -94
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { createTestViewContext } from "../view/testUtils.js";
|
|
4
|
+
import { buildDataFlow } from "../view/flowBuilder.js";
|
|
5
|
+
import { optimizeDataFlow } from "./flowOptimizer.js";
|
|
6
|
+
import {
|
|
7
|
+
collectNearestViewSubtreeDataSources,
|
|
8
|
+
collectViewSubtreeDataSources,
|
|
9
|
+
initializeViewSubtree,
|
|
10
|
+
loadViewSubtreeData,
|
|
11
|
+
syncFlowHandles,
|
|
12
|
+
} from "./flowInit.js";
|
|
13
|
+
|
|
14
|
+
describe("flowInit", () => {
|
|
15
|
+
test("syncs handles to canonical data sources after merge", async () => {
|
|
16
|
+
const context = createTestViewContext();
|
|
17
|
+
context.getNamedDataFromProvider = () => [];
|
|
18
|
+
context.addBroadcastListener = () => undefined;
|
|
19
|
+
context.removeBroadcastListener = () => undefined;
|
|
20
|
+
|
|
21
|
+
/** @type {import("../spec/view.js").HConcatSpec} */
|
|
22
|
+
const spec = {
|
|
23
|
+
hconcat: [
|
|
24
|
+
{
|
|
25
|
+
data: { name: "shared" },
|
|
26
|
+
mark: "point",
|
|
27
|
+
encoding: {
|
|
28
|
+
x: { field: "x", type: "quantitative" },
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
data: { name: "shared" },
|
|
33
|
+
mark: "point",
|
|
34
|
+
encoding: {
|
|
35
|
+
x: { field: "x", type: "quantitative" },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const root = await context.createOrImportView(spec, null, null, "root");
|
|
42
|
+
|
|
43
|
+
const flow = buildDataFlow(root, context.dataFlow);
|
|
44
|
+
const canonicalBySource = optimizeDataFlow(flow);
|
|
45
|
+
syncFlowHandles(root, canonicalBySource);
|
|
46
|
+
|
|
47
|
+
const concatRoot =
|
|
48
|
+
/** @type {import("../view/concatView.js").default} */ (root);
|
|
49
|
+
const left = concatRoot.children[0];
|
|
50
|
+
const right = concatRoot.children[1];
|
|
51
|
+
|
|
52
|
+
expect(left.flowHandle.dataSource).toBeDefined();
|
|
53
|
+
expect(right.flowHandle.dataSource).toBeDefined();
|
|
54
|
+
expect(left.flowHandle.dataSource).toBe(right.flowHandle.dataSource);
|
|
55
|
+
|
|
56
|
+
const sharedSources = flow.dataSources.filter(
|
|
57
|
+
(/** @type {import("./sources/dataSource.js").default} */ source) =>
|
|
58
|
+
source.identifier === "shared"
|
|
59
|
+
);
|
|
60
|
+
expect(sharedSources).toEqual([left.flowHandle.dataSource]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("initializeViewSubtree wires collector updates for subtree loads", async () => {
|
|
64
|
+
const context = createTestViewContext();
|
|
65
|
+
context.getNamedDataFromProvider = () => [];
|
|
66
|
+
context.addBroadcastListener = () => undefined;
|
|
67
|
+
context.removeBroadcastListener = () => undefined;
|
|
68
|
+
|
|
69
|
+
/** @type {import("../spec/view.js").UnitSpec} */
|
|
70
|
+
const spec = {
|
|
71
|
+
data: { values: [{ x: 1 }, { x: 2 }] },
|
|
72
|
+
mark: "point",
|
|
73
|
+
encoding: {
|
|
74
|
+
x: { field: "x", type: "quantitative" },
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const root = await context.createOrImportView(spec, null, null, "root");
|
|
79
|
+
const { dataSources } = initializeViewSubtree(root, context.dataFlow);
|
|
80
|
+
|
|
81
|
+
// This guards subtree-only initialization: dynamic view rebuilds should still
|
|
82
|
+
// trigger mark updates when their local collectors complete.
|
|
83
|
+
const unitView = /** @type {import("../view/unitView.js").default} */ (
|
|
84
|
+
root
|
|
85
|
+
);
|
|
86
|
+
const initializeSpy = vi.spyOn(unitView.mark, "initializeData");
|
|
87
|
+
|
|
88
|
+
await Promise.all(
|
|
89
|
+
Array.from(dataSources).map((dataSource) => dataSource.load())
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
expect(initializeSpy).toHaveBeenCalledTimes(1);
|
|
93
|
+
initializeSpy.mockRestore();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("disposeSubtree removes observers before rebuilding subtree", async () => {
|
|
97
|
+
const context = createTestViewContext();
|
|
98
|
+
context.getNamedDataFromProvider = () => [{ x: 1 }];
|
|
99
|
+
context.addBroadcastListener = () => undefined;
|
|
100
|
+
context.removeBroadcastListener = () => undefined;
|
|
101
|
+
|
|
102
|
+
/** @type {import("../spec/view.js").UnitSpec} */
|
|
103
|
+
const spec = {
|
|
104
|
+
data: { name: "shared" },
|
|
105
|
+
mark: "point",
|
|
106
|
+
encoding: {
|
|
107
|
+
x: { field: "x", type: "quantitative" },
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const firstRoot = await context.createOrImportView(
|
|
112
|
+
spec,
|
|
113
|
+
null,
|
|
114
|
+
null,
|
|
115
|
+
"first"
|
|
116
|
+
);
|
|
117
|
+
const { dataSources: firstSources } = initializeViewSubtree(
|
|
118
|
+
firstRoot,
|
|
119
|
+
context.dataFlow
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const firstUnit = /** @type {import("../view/unitView.js").default} */ (
|
|
123
|
+
firstRoot
|
|
124
|
+
);
|
|
125
|
+
const firstCollector = firstUnit.flowHandle.collector;
|
|
126
|
+
const firstInitializeSpy = vi.spyOn(firstUnit.mark, "initializeData");
|
|
127
|
+
|
|
128
|
+
await Promise.all(
|
|
129
|
+
Array.from(firstSources).map((dataSource) => dataSource.load())
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
expect(firstInitializeSpy).toHaveBeenCalledTimes(1);
|
|
133
|
+
firstInitializeSpy.mockRestore();
|
|
134
|
+
|
|
135
|
+
firstRoot.disposeSubtree();
|
|
136
|
+
|
|
137
|
+
// This prevents stale observers from firing after a subtree is rebuilt.
|
|
138
|
+
expect(firstCollector.observers.size).toBe(0);
|
|
139
|
+
|
|
140
|
+
const secondRoot = await context.createOrImportView(
|
|
141
|
+
spec,
|
|
142
|
+
null,
|
|
143
|
+
null,
|
|
144
|
+
"second"
|
|
145
|
+
);
|
|
146
|
+
const { dataSources: secondSources } = initializeViewSubtree(
|
|
147
|
+
secondRoot,
|
|
148
|
+
context.dataFlow
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const secondUnit =
|
|
152
|
+
/** @type {import("../view/unitView.js").default} */ (secondRoot);
|
|
153
|
+
const secondInitializeSpy = vi.spyOn(secondUnit.mark, "initializeData");
|
|
154
|
+
|
|
155
|
+
await Promise.all(
|
|
156
|
+
Array.from(secondSources).map((dataSource) => dataSource.load())
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
expect(secondInitializeSpy).toHaveBeenCalledTimes(1);
|
|
160
|
+
secondInitializeSpy.mockRestore();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
test("disposeSubtree prunes named source branches", async () => {
|
|
164
|
+
const context = createTestViewContext();
|
|
165
|
+
context.getNamedDataFromProvider = () => [{ x: 1 }];
|
|
166
|
+
context.addBroadcastListener = () => undefined;
|
|
167
|
+
context.removeBroadcastListener = () => undefined;
|
|
168
|
+
|
|
169
|
+
/** @type {import("../spec/view.js").UnitSpec} */
|
|
170
|
+
const spec = {
|
|
171
|
+
data: { name: "shared" },
|
|
172
|
+
mark: "point",
|
|
173
|
+
encoding: {
|
|
174
|
+
x: { field: "x", type: "quantitative" },
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const root = await context.createOrImportView(spec, null, null, "root");
|
|
179
|
+
initializeViewSubtree(root, context.dataFlow);
|
|
180
|
+
|
|
181
|
+
const unitView = /** @type {import("../view/unitView.js").default} */ (
|
|
182
|
+
root
|
|
183
|
+
);
|
|
184
|
+
const dataSource = unitView.flowHandle.dataSource;
|
|
185
|
+
|
|
186
|
+
// This guards against stale flow branches when a subtree is disposed.
|
|
187
|
+
expect(context.dataFlow.dataSources).toContain(dataSource);
|
|
188
|
+
expect(dataSource.children.length).toBeGreaterThan(0);
|
|
189
|
+
|
|
190
|
+
root.disposeSubtree();
|
|
191
|
+
|
|
192
|
+
expect(dataSource.children.length).toBe(0);
|
|
193
|
+
expect(context.dataFlow.dataSources).not.toContain(dataSource);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
test("collectNearestViewSubtreeDataSources stops at nested sources", async () => {
|
|
197
|
+
const context = createTestViewContext();
|
|
198
|
+
context.addBroadcastListener = () => undefined;
|
|
199
|
+
context.removeBroadcastListener = () => undefined;
|
|
200
|
+
|
|
201
|
+
/** @type {import("../spec/view.js").LayerSpec} */
|
|
202
|
+
const spec = {
|
|
203
|
+
data: { values: [{ x: 0 }] },
|
|
204
|
+
layer: [
|
|
205
|
+
{
|
|
206
|
+
data: { values: [{ x: 1 }] },
|
|
207
|
+
mark: "point",
|
|
208
|
+
encoding: {
|
|
209
|
+
x: { field: "x", type: "quantitative" },
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
mark: "point",
|
|
214
|
+
encoding: {
|
|
215
|
+
x: { field: "x", type: "quantitative" },
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const root = await context.createOrImportView(spec, null, null, "root");
|
|
222
|
+
initializeViewSubtree(root, context.dataFlow);
|
|
223
|
+
|
|
224
|
+
// Nearest-source semantics: a top-level source hides deeper sources.
|
|
225
|
+
const sources = collectNearestViewSubtreeDataSources(root);
|
|
226
|
+
expect(sources.size).toBe(1);
|
|
227
|
+
|
|
228
|
+
const [rootSource] = Array.from(sources);
|
|
229
|
+
const layerRoot =
|
|
230
|
+
/** @type {import("../view/layerView.js").default} */ (root);
|
|
231
|
+
const childWithSource = layerRoot.children[0];
|
|
232
|
+
|
|
233
|
+
expect(rootSource).toBe(layerRoot.flowHandle.dataSource);
|
|
234
|
+
expect(childWithSource.flowHandle.dataSource).not.toBe(rootSource);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test("loadViewSubtreeData only loads nearest sources", async () => {
|
|
238
|
+
const context = createTestViewContext();
|
|
239
|
+
context.addBroadcastListener = () => undefined;
|
|
240
|
+
context.removeBroadcastListener = () => undefined;
|
|
241
|
+
|
|
242
|
+
/** @type {import("../spec/view.js").LayerSpec} */
|
|
243
|
+
const spec = {
|
|
244
|
+
data: { values: [{ x: 0 }] },
|
|
245
|
+
layer: [
|
|
246
|
+
{
|
|
247
|
+
data: { values: [{ x: 1 }] },
|
|
248
|
+
mark: "point",
|
|
249
|
+
encoding: {
|
|
250
|
+
x: { field: "x", type: "quantitative" },
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
],
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
const root = await context.createOrImportView(spec, null, null, "root");
|
|
257
|
+
initializeViewSubtree(root, context.dataFlow);
|
|
258
|
+
|
|
259
|
+
const layerRoot =
|
|
260
|
+
/** @type {import("../view/layerView.js").default} */ (root);
|
|
261
|
+
const rootSource = layerRoot.flowHandle.dataSource;
|
|
262
|
+
const childSource = layerRoot.children[0].flowHandle.dataSource;
|
|
263
|
+
|
|
264
|
+
const rootLoadSpy = vi.spyOn(rootSource, "load");
|
|
265
|
+
const childLoadSpy = vi.spyOn(childSource, "load");
|
|
266
|
+
|
|
267
|
+
// Data-ready should ignore nested sources.
|
|
268
|
+
await loadViewSubtreeData(root);
|
|
269
|
+
|
|
270
|
+
expect(rootLoadSpy).toHaveBeenCalledTimes(1);
|
|
271
|
+
expect(childLoadSpy).toHaveBeenCalledTimes(0);
|
|
272
|
+
|
|
273
|
+
rootLoadSpy.mockRestore();
|
|
274
|
+
childLoadSpy.mockRestore();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test("collectViewSubtreeDataSources includes nested sources", async () => {
|
|
278
|
+
const context = createTestViewContext();
|
|
279
|
+
context.addBroadcastListener = () => undefined;
|
|
280
|
+
context.removeBroadcastListener = () => undefined;
|
|
281
|
+
|
|
282
|
+
/** @type {import("../spec/view.js").LayerSpec} */
|
|
283
|
+
const spec = {
|
|
284
|
+
data: { values: [{ x: 0 }] },
|
|
285
|
+
layer: [
|
|
286
|
+
{
|
|
287
|
+
data: { values: [{ x: 1 }] },
|
|
288
|
+
mark: "point",
|
|
289
|
+
encoding: {
|
|
290
|
+
x: { field: "x", type: "quantitative" },
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const root = await context.createOrImportView(spec, null, null, "root");
|
|
297
|
+
initializeViewSubtree(root, context.dataFlow);
|
|
298
|
+
|
|
299
|
+
// Initialization needs the full set of sources, including nested ones.
|
|
300
|
+
const sources = collectViewSubtreeDataSources(root);
|
|
301
|
+
expect(sources.size).toBe(2);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("collectNearestViewSubtreeDataSources returns child sources when root has none", async () => {
|
|
305
|
+
const context = createTestViewContext();
|
|
306
|
+
context.addBroadcastListener = () => undefined;
|
|
307
|
+
context.removeBroadcastListener = () => undefined;
|
|
308
|
+
|
|
309
|
+
/** @type {import("../spec/view.js").HConcatSpec} */
|
|
310
|
+
const spec = {
|
|
311
|
+
hconcat: [
|
|
312
|
+
{
|
|
313
|
+
data: { values: [{ x: 1 }] },
|
|
314
|
+
mark: "point",
|
|
315
|
+
encoding: {
|
|
316
|
+
x: { field: "x", type: "quantitative" },
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
data: { values: [{ x: 2 }] },
|
|
321
|
+
mark: "point",
|
|
322
|
+
encoding: {
|
|
323
|
+
x: { field: "x", type: "quantitative" },
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
],
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
const root = await context.createOrImportView(spec, null, null, "root");
|
|
330
|
+
initializeViewSubtree(root, context.dataFlow);
|
|
331
|
+
|
|
332
|
+
// Without a root source, the nearest sources include the child sources.
|
|
333
|
+
// Layout decorations may add additional sources.
|
|
334
|
+
const sources = collectNearestViewSubtreeDataSources(root);
|
|
335
|
+
const concatRoot =
|
|
336
|
+
/** @type {import("../view/concatView.js").default} */ (root);
|
|
337
|
+
expect(sources.has(concatRoot.children[0].flowHandle.dataSource)).toBe(
|
|
338
|
+
true
|
|
339
|
+
);
|
|
340
|
+
expect(sources.has(concatRoot.children[1].flowHandle.dataSource)).toBe(
|
|
341
|
+
true
|
|
342
|
+
);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("loadViewSubtreeData emits subtree data ready broadcast", async () => {
|
|
346
|
+
const context = createTestViewContext();
|
|
347
|
+
context.addBroadcastListener = () => undefined;
|
|
348
|
+
context.removeBroadcastListener = () => undefined;
|
|
349
|
+
|
|
350
|
+
/** @type {import("../spec/view.js").UnitSpec} */
|
|
351
|
+
const spec = {
|
|
352
|
+
data: { values: [{ x: 1 }] },
|
|
353
|
+
mark: "point",
|
|
354
|
+
encoding: {
|
|
355
|
+
x: { field: "x", type: "quantitative" },
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const root = await context.createOrImportView(spec, null, null, "root");
|
|
360
|
+
initializeViewSubtree(root, context.dataFlow);
|
|
361
|
+
|
|
362
|
+
let calls = 0;
|
|
363
|
+
root._addBroadcastHandler("subtreeDataReady", (message) => {
|
|
364
|
+
calls += 1;
|
|
365
|
+
expect(message.payload.subtreeRoot).toBe(root);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
await loadViewSubtreeData(root);
|
|
369
|
+
|
|
370
|
+
expect(calls).toBe(1);
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
test("loadViewSubtreeData deduplicates concurrent loads", async () => {
|
|
374
|
+
const context = createTestViewContext();
|
|
375
|
+
context.getNamedDataFromProvider = () => [{ x: 1 }];
|
|
376
|
+
context.addBroadcastListener = () => undefined;
|
|
377
|
+
context.removeBroadcastListener = () => undefined;
|
|
378
|
+
|
|
379
|
+
/** @type {import("../spec/view.js").HConcatSpec} */
|
|
380
|
+
const spec = {
|
|
381
|
+
data: { name: "shared" },
|
|
382
|
+
hconcat: [
|
|
383
|
+
{
|
|
384
|
+
mark: "point",
|
|
385
|
+
encoding: {
|
|
386
|
+
x: { field: "x", type: "quantitative" },
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
mark: "point",
|
|
391
|
+
encoding: {
|
|
392
|
+
x: { field: "x", type: "quantitative" },
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
const root = await context.createOrImportView(spec, null, null, "root");
|
|
399
|
+
initializeViewSubtree(root, context.dataFlow);
|
|
400
|
+
|
|
401
|
+
const dataSource = root.flowHandle.dataSource;
|
|
402
|
+
const loadSpy = vi.spyOn(dataSource, "load");
|
|
403
|
+
|
|
404
|
+
// Prevent duplicate fetches when concurrent subtrees share a source.
|
|
405
|
+
await Promise.all([
|
|
406
|
+
loadViewSubtreeData(root, new Set([dataSource])),
|
|
407
|
+
loadViewSubtreeData(root, new Set([dataSource])),
|
|
408
|
+
]);
|
|
409
|
+
|
|
410
|
+
expect(loadSpy).toHaveBeenCalledTimes(1);
|
|
411
|
+
loadSpy.mockRestore();
|
|
412
|
+
});
|
|
413
|
+
});
|
|
@@ -12,16 +12,18 @@ export function removeRedundantCloneTransforms(node: import("./flowNode.js").def
|
|
|
12
12
|
export function removeRedundantCollectors(): void;
|
|
13
13
|
export function combineAndPullCollectorsUp(): void;
|
|
14
14
|
/**
|
|
15
|
-
* @param {import("./dataFlow.js").default
|
|
15
|
+
* @param {import("./dataFlow.js").default} dataFlow
|
|
16
|
+
* @returns {Map<import("./sources/dataSource.js").default, import("./sources/dataSource.js").default>}
|
|
16
17
|
*/
|
|
17
|
-
export function combineIdenticalDataSources(dataFlow: import("./dataFlow.js").default
|
|
18
|
+
export function combineIdenticalDataSources(dataFlow: import("./dataFlow.js").default): Map<import("./sources/dataSource.js").default, import("./sources/dataSource.js").default>;
|
|
18
19
|
/**
|
|
19
20
|
*
|
|
20
21
|
* @param {import("./flowNode.js").default} root
|
|
21
22
|
*/
|
|
22
23
|
export function optimizeFlowGraph(root: import("./flowNode.js").default): void;
|
|
23
24
|
/**
|
|
24
|
-
* @param {import("./dataFlow.js").default
|
|
25
|
+
* @param {import("./dataFlow.js").default} dataFlow
|
|
26
|
+
* @returns {Map<import("./sources/dataSource.js").default, import("./sources/dataSource.js").default>}
|
|
25
27
|
*/
|
|
26
|
-
export function optimizeDataFlow(dataFlow: import("./dataFlow.js").default
|
|
28
|
+
export function optimizeDataFlow(dataFlow: import("./dataFlow.js").default): Map<import("./sources/dataSource.js").default, import("./sources/dataSource.js").default>;
|
|
27
29
|
//# sourceMappingURL=flowOptimizer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"flowOptimizer.d.ts","sourceRoot":"","sources":["../../../src/data/flowOptimizer.js"],"names":[],"mappings":"AAGA;;;GAGG;AACH,oCAHW,OAAO,eAAe,EAAE,OAAO,WAC/B,OAAO,eAAe,EAAE,OAAO,WAczC;AAED;;;;GAIG;AACH,qDAFW,OAAO,eAAe,EAAE,OAAO,iCAgCzC;AAED,kDAEC;AAED,mDAEC;AAmBD
|
|
1
|
+
{"version":3,"file":"flowOptimizer.d.ts","sourceRoot":"","sources":["../../../src/data/flowOptimizer.js"],"names":[],"mappings":"AAGA;;;GAGG;AACH,oCAHW,OAAO,eAAe,EAAE,OAAO,WAC/B,OAAO,eAAe,EAAE,OAAO,WAczC;AAED;;;;GAIG;AACH,qDAFW,OAAO,eAAe,EAAE,OAAO,iCAgCzC;AAED,kDAEC;AAED,mDAEC;AAmBD;;;GAGG;AACH,sDAHW,OAAO,eAAe,EAAE,OAAO,GAC7B,GAAG,CAAC,OAAO,yBAAyB,EAAE,OAAO,EAAE,OAAO,yBAAyB,EAAE,OAAO,CAAC,CAoCrG;AAED;;;GAGG;AACH,wCAFW,OAAO,eAAe,EAAE,OAAO,QASzC;AAED;;;GAGG;AACH,2CAHW,OAAO,eAAe,EAAE,OAAO,GAC7B,GAAG,CAAC,OAAO,yBAAyB,EAAE,OAAO,EAAE,OAAO,yBAAyB,EAAE,OAAO,CAAC,CAQrG"}
|
|
@@ -82,30 +82,43 @@ export function combineAndPullCollectorsUp() {
|
|
|
82
82
|
// --F--G
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
|
-
* @param {import("./dataFlow.js").default
|
|
85
|
+
* @param {import("./dataFlow.js").default} dataFlow
|
|
86
|
+
* @returns {Map<import("./sources/dataSource.js").default, import("./sources/dataSource.js").default>}
|
|
86
87
|
*/
|
|
87
88
|
export function combineIdenticalDataSources(dataFlow) {
|
|
88
|
-
const
|
|
89
|
+
const dataSources = dataFlow.dataSources;
|
|
89
90
|
|
|
90
91
|
/** @type {Map<string, import("./sources/dataSource.js").default>} */
|
|
91
92
|
const sourcesByIdentifiers = new Map();
|
|
92
|
-
for (const
|
|
93
|
-
const ds = e[1];
|
|
93
|
+
for (const ds of dataSources) {
|
|
94
94
|
if (ds.identifier && !sourcesByIdentifiers.has(ds.identifier)) {
|
|
95
95
|
sourcesByIdentifiers.set(ds.identifier, ds);
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
/** @type {Set<import("./sources/dataSource.js").default>} */
|
|
100
|
+
const mergedSources = new Set();
|
|
101
|
+
/** @type {Map<import("./sources/dataSource.js").default, import("./sources/dataSource.js").default>} */
|
|
102
|
+
const canonicalBySource = new Map();
|
|
103
|
+
|
|
104
|
+
for (const dataSource of dataSources) {
|
|
105
|
+
if (dataSource.identifier) {
|
|
106
|
+
const target = sourcesByIdentifiers.get(dataSource.identifier);
|
|
107
|
+
if (target) {
|
|
108
|
+
if (target !== dataSource) {
|
|
109
|
+
target.adoptChildrenOf(dataSource);
|
|
110
|
+
}
|
|
111
|
+
mergedSources.add(target);
|
|
112
|
+
canonicalBySource.set(dataSource, target);
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
mergedSources.add(dataSource);
|
|
116
|
+
canonicalBySource.set(dataSource, dataSource);
|
|
106
117
|
}
|
|
107
|
-
dataFlow.addDataSource(dataSource, key);
|
|
108
118
|
}
|
|
119
|
+
|
|
120
|
+
dataFlow.replaceDataSources(mergedSources);
|
|
121
|
+
return canonicalBySource;
|
|
109
122
|
}
|
|
110
123
|
|
|
111
124
|
/**
|
|
@@ -122,11 +135,13 @@ export function optimizeFlowGraph(root) {
|
|
|
122
135
|
}
|
|
123
136
|
|
|
124
137
|
/**
|
|
125
|
-
* @param {import("./dataFlow.js").default
|
|
138
|
+
* @param {import("./dataFlow.js").default} dataFlow
|
|
139
|
+
* @returns {Map<import("./sources/dataSource.js").default, import("./sources/dataSource.js").default>}
|
|
126
140
|
*/
|
|
127
141
|
export function optimizeDataFlow(dataFlow) {
|
|
128
|
-
combineIdenticalDataSources(dataFlow);
|
|
142
|
+
const canonicalBySource = combineIdenticalDataSources(dataFlow);
|
|
129
143
|
for (const dataSource of dataFlow.dataSources) {
|
|
130
144
|
optimizeFlowGraph(dataSource);
|
|
131
145
|
}
|
|
146
|
+
return canonicalBySource;
|
|
132
147
|
}
|
|
@@ -142,7 +142,7 @@ const viewStub = /** @type {any} */ (
|
|
|
142
142
|
|
|
143
143
|
describe("Merge indentical data sources", () => {
|
|
144
144
|
test("Merges correctly", () => {
|
|
145
|
-
/** @type {DataFlow
|
|
145
|
+
/** @type {DataFlow} */
|
|
146
146
|
const dataFlow = new DataFlow();
|
|
147
147
|
|
|
148
148
|
const a = new UrlSource({ url: "http://genomespy.app/" }, viewStub);
|
|
@@ -157,21 +157,25 @@ describe("Merge indentical data sources", () => {
|
|
|
157
157
|
const cc = new Collector();
|
|
158
158
|
c.addChild(cc);
|
|
159
159
|
|
|
160
|
-
dataFlow.addDataSource(a
|
|
161
|
-
dataFlow.addDataSource(b
|
|
162
|
-
dataFlow.addDataSource(c
|
|
160
|
+
dataFlow.addDataSource(a);
|
|
161
|
+
dataFlow.addDataSource(b);
|
|
162
|
+
dataFlow.addDataSource(c);
|
|
163
163
|
|
|
164
|
-
dataFlow.addCollector(ac
|
|
165
|
-
dataFlow.addCollector(bc
|
|
166
|
-
dataFlow.addCollector(cc
|
|
164
|
+
dataFlow.addCollector(ac);
|
|
165
|
+
dataFlow.addCollector(bc);
|
|
166
|
+
dataFlow.addCollector(cc);
|
|
167
167
|
|
|
168
168
|
combineIdenticalDataSources(dataFlow);
|
|
169
169
|
|
|
170
170
|
expect(dataFlow.dataSources.length).toEqual(2);
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
expect(
|
|
172
|
+
const entries = dataFlow.dataSources;
|
|
173
|
+
const sharedIdentifier = a.identifier;
|
|
174
|
+
expect(entries).toContain(a);
|
|
175
|
+
expect(entries).toContain(c);
|
|
176
|
+
expect(
|
|
177
|
+
entries.filter((source) => source.identifier == sharedIdentifier)
|
|
178
|
+
).toEqual([a]);
|
|
175
179
|
|
|
176
180
|
expect(new Set(a.children)).toEqual(new Set([ac, bc]));
|
|
177
181
|
expect(c.children[0]).toBe(cc);
|
|
@@ -187,18 +191,19 @@ describe("Merge indentical data sources", () => {
|
|
|
187
191
|
});
|
|
188
192
|
|
|
189
193
|
test("Does not merge those with undefined identifier", () => {
|
|
190
|
-
/** @type {DataFlow
|
|
194
|
+
/** @type {DataFlow} */
|
|
191
195
|
const dataFlow = new DataFlow();
|
|
192
196
|
|
|
193
197
|
const a = new InlineSource({ values: [1, 2, 3] }, viewStub);
|
|
194
198
|
const b = new InlineSource({ values: [1, 2, 3] }, viewStub);
|
|
195
199
|
|
|
196
|
-
dataFlow.addDataSource(a
|
|
197
|
-
dataFlow.addDataSource(b
|
|
200
|
+
dataFlow.addDataSource(a);
|
|
201
|
+
dataFlow.addDataSource(b);
|
|
198
202
|
|
|
199
203
|
combineIdenticalDataSources(dataFlow);
|
|
200
204
|
|
|
201
|
-
|
|
202
|
-
expect(
|
|
205
|
+
const entries = dataFlow.dataSources;
|
|
206
|
+
expect(entries).toContain(a);
|
|
207
|
+
expect(entries).toContain(b);
|
|
203
208
|
});
|
|
204
209
|
});
|
|
@@ -43,7 +43,7 @@ export default class BamSource extends SingleAxisWindowedSource {
|
|
|
43
43
|
this.initializedPromise = new Promise((resolve) => {
|
|
44
44
|
Promise.all([
|
|
45
45
|
import("@gmod/bam"),
|
|
46
|
-
import("generic-
|
|
46
|
+
import("generic-filehandle2"),
|
|
47
47
|
]).then(([{ BamFile }, { RemoteFile }]) => {
|
|
48
48
|
const withBase = (/** @type {string} */ uri) =>
|
|
49
49
|
new RemoteFile(addBaseUrl(uri, this.view.getBaseUrl()));
|
|
@@ -63,7 +63,7 @@ export default class BigBedSource extends SingleAxisWindowedSource {
|
|
|
63
63
|
Promise.all([
|
|
64
64
|
import("@gmod/bed"),
|
|
65
65
|
import("@gmod/bbi"),
|
|
66
|
-
import("generic-
|
|
66
|
+
import("generic-filehandle2"),
|
|
67
67
|
]).then(([bed, { BigBed }, { RemoteFile }]) => {
|
|
68
68
|
const BED = bed.default;
|
|
69
69
|
|
|
@@ -62,7 +62,7 @@ export default class BigWigSource extends SingleAxisWindowedSource {
|
|
|
62
62
|
this.initializedPromise = new Promise((resolve, reject) => {
|
|
63
63
|
Promise.all([
|
|
64
64
|
import("@gmod/bbi"),
|
|
65
|
-
import("generic-
|
|
65
|
+
import("generic-filehandle2"),
|
|
66
66
|
]).then(([{ BigWig }, { RemoteFile }]) => {
|
|
67
67
|
this.#bbi = new BigWig({
|
|
68
68
|
filehandle: new RemoteFile(
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @extends {TabixSource<import("
|
|
2
|
+
* @extends {TabixSource<import("gff-nostream").GFF3Feature>}
|
|
3
3
|
*/
|
|
4
|
-
export default class Gff3Source extends TabixSource<import("
|
|
4
|
+
export default class Gff3Source extends TabixSource<import("gff-nostream").GFF3Feature> {
|
|
5
5
|
constructor(params: import("../../../spec/data.js").TabixData, view: import("../../../view/view.js").default);
|
|
6
|
-
/**
|
|
7
|
-
* @param {string[]} lines
|
|
8
|
-
*/
|
|
9
|
-
_parseFeatures(lines: string[]): any;
|
|
10
6
|
#private;
|
|
11
7
|
}
|
|
12
8
|
import TabixSource from "./tabixSource.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gff3Source.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/lazy/gff3Source.js"],"names":[],"mappings":"AAEA;;GAEG;AACH
|
|
1
|
+
{"version":3,"file":"gff3Source.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/lazy/gff3Source.js"],"names":[],"mappings":"AAEA;;GAEG;AACH;;;CAuBC;wBA5BuB,kBAAkB"}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import TabixSource from "./tabixSource.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* @extends {TabixSource<import("
|
|
4
|
+
* @extends {TabixSource<import("gff-nostream").GFF3Feature>}
|
|
5
5
|
*/
|
|
6
6
|
export default class Gff3Source extends TabixSource {
|
|
7
|
-
/** @type {import("
|
|
7
|
+
/** @type {import("gff-nostream")} */
|
|
8
8
|
#gff;
|
|
9
9
|
|
|
10
10
|
get label() {
|
|
@@ -15,18 +15,14 @@ export default class Gff3Source extends TabixSource {
|
|
|
15
15
|
* @param {string} header
|
|
16
16
|
*/
|
|
17
17
|
async _handleHeader(header) {
|
|
18
|
-
this.#gff =
|
|
18
|
+
this.#gff = await import("gff-nostream");
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* @param {string[]} lines
|
|
23
23
|
*/
|
|
24
24
|
_parseFeatures(lines) {
|
|
25
|
-
|
|
26
|
-
// eslint-disable-next-line no-sync
|
|
27
|
-
const features = this.#gff?.parseStringSync(lines.join("\n"), {
|
|
28
|
-
parseSequences: false,
|
|
29
|
-
});
|
|
25
|
+
const features = this.#gff?.parseArraySync(lines);
|
|
30
26
|
|
|
31
27
|
return features;
|
|
32
28
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexedFastaSource.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/lazy/indexedFastaSource.js"],"names":[],"mappings":"AAGA;IACI;;;OAGG;IACH,oBAHW,OAAO,uBAAuB,EAAE,gBAAgB,QAChD,OAAO,uBAAuB,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"indexedFastaSource.d.ts","sourceRoot":"","sources":["../../../../../src/data/sources/lazy/indexedFastaSource.js"],"names":[],"mappings":"AAGA;IACI;;;OAGG;IACH,oBAHW,OAAO,uBAAuB,EAAE,gBAAgB,QAChD,OAAO,uBAAuB,EAAE,OAAO,EAwCjD;IA1BG,yDAAgC;IAQhC,iCAiBE;IATM,iDAKE;CAwCjB;qCAhFoC,+BAA+B"}
|