@genome-spy/core 0.14.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 (226) hide show
  1. package/dist/index.js +224 -0
  2. package/dist/style.css +1 -0
  3. package/package.json +54 -0
  4. package/src/data/collector.js +178 -0
  5. package/src/data/collector.test.js +82 -0
  6. package/src/data/dataFlow.js +109 -0
  7. package/src/data/dataFlow.test.js +3 -0
  8. package/src/data/facetNode.js +17 -0
  9. package/src/data/flow.test.js +71 -0
  10. package/src/data/flowBatch.d.ts +40 -0
  11. package/src/data/flowNode.js +283 -0
  12. package/src/data/flowNode.test.js +49 -0
  13. package/src/data/flowOptimizer.js +117 -0
  14. package/src/data/flowOptimizer.test.js +192 -0
  15. package/src/data/flowTestUtils.js +63 -0
  16. package/src/data/formats/fasta.js +32 -0
  17. package/src/data/formats/fasta.test.js +26 -0
  18. package/src/data/sources/dataSource.js +22 -0
  19. package/src/data/sources/dataSourceFactory.js +24 -0
  20. package/src/data/sources/dataUtils.js +31 -0
  21. package/src/data/sources/dynamicCallbackSource.js +56 -0
  22. package/src/data/sources/dynamicSource.js +36 -0
  23. package/src/data/sources/inlineSource.js +69 -0
  24. package/src/data/sources/inlineSource.test.js +55 -0
  25. package/src/data/sources/namedSource.js +74 -0
  26. package/src/data/sources/sequenceSource.js +46 -0
  27. package/src/data/sources/sequenceSource.test.js +45 -0
  28. package/src/data/sources/urlSource.js +74 -0
  29. package/src/data/transforms/aggregate.js +69 -0
  30. package/src/data/transforms/clone.js +40 -0
  31. package/src/data/transforms/clone.test.js +10 -0
  32. package/src/data/transforms/coverage.js +187 -0
  33. package/src/data/transforms/coverage.test.js +122 -0
  34. package/src/data/transforms/filter.js +37 -0
  35. package/src/data/transforms/filter.test.js +17 -0
  36. package/src/data/transforms/filterScoredLabels.js +134 -0
  37. package/src/data/transforms/flattenCompressedExons.js +57 -0
  38. package/src/data/transforms/flattenDelimited.js +68 -0
  39. package/src/data/transforms/flattenDelimited.test.js +86 -0
  40. package/src/data/transforms/flattenSequence.js +39 -0
  41. package/src/data/transforms/flattenSequence.test.js +33 -0
  42. package/src/data/transforms/formula.js +39 -0
  43. package/src/data/transforms/formula.test.js +18 -0
  44. package/src/data/transforms/identifier.js +108 -0
  45. package/src/data/transforms/identifier.test.js +82 -0
  46. package/src/data/transforms/linearizeGenomicCoordinate.js +101 -0
  47. package/src/data/transforms/measureText.js +44 -0
  48. package/src/data/transforms/pileup.js +128 -0
  49. package/src/data/transforms/pileup.test.js +69 -0
  50. package/src/data/transforms/project.js +41 -0
  51. package/src/data/transforms/project.test.js +31 -0
  52. package/src/data/transforms/regexExtract.js +61 -0
  53. package/src/data/transforms/regexExtract.test.js +66 -0
  54. package/src/data/transforms/regexFold.js +141 -0
  55. package/src/data/transforms/regexFold.test.js +159 -0
  56. package/src/data/transforms/sample.js +101 -0
  57. package/src/data/transforms/sample.test.js +37 -0
  58. package/src/data/transforms/stack.js +137 -0
  59. package/src/data/transforms/stack.test.js +90 -0
  60. package/src/data/transforms/transformFactory.js +60 -0
  61. package/src/encoder/accessor.js +82 -0
  62. package/src/encoder/accessor.test.js +46 -0
  63. package/src/encoder/encoder.js +369 -0
  64. package/src/encoder/encoder.test.js +97 -0
  65. package/src/fonts/Lato-Regular.json +1267 -0
  66. package/src/fonts/Lato-Regular.png +0 -0
  67. package/src/fonts/OFL.txt +93 -0
  68. package/src/fonts/README.md +3 -0
  69. package/src/fonts/bmFont.d.ts +58 -0
  70. package/src/fonts/bmFontManager.js +357 -0
  71. package/src/fonts/bmFontMetrics.js +108 -0
  72. package/src/genome/genome.js +305 -0
  73. package/src/genome/genome.test.js +152 -0
  74. package/src/genome/genomeStore.js +54 -0
  75. package/src/genome/locusFormat.js +31 -0
  76. package/src/genome/scaleIndex.js +199 -0
  77. package/src/genome/scaleIndex.test.js +61 -0
  78. package/src/genome/scaleLocus.js +112 -0
  79. package/src/genome/scaleLocus.test.js +3 -0
  80. package/src/genomeSpy.js +753 -0
  81. package/src/gl/arrayBuilder.js +199 -0
  82. package/src/gl/dataToVertices.js +621 -0
  83. package/src/gl/includes/common.glsl +63 -0
  84. package/src/gl/includes/fp64-arithmetic.glsl +187 -0
  85. package/src/gl/includes/fp64-utils.js +132 -0
  86. package/src/gl/includes/picking.fragment.glsl +3 -0
  87. package/src/gl/includes/picking.vertex.glsl +29 -0
  88. package/src/gl/includes/sampleFacet.glsl +107 -0
  89. package/src/gl/includes/scales.glsl +79 -0
  90. package/src/gl/includes/scales_fp64.glsl +30 -0
  91. package/src/gl/link.fragment.glsl +18 -0
  92. package/src/gl/link.vertex.glsl +111 -0
  93. package/src/gl/point.fragment.glsl +123 -0
  94. package/src/gl/point.vertex.glsl +128 -0
  95. package/src/gl/rect.fragment.glsl +51 -0
  96. package/src/gl/rect.vertex.glsl +114 -0
  97. package/src/gl/rule.fragment.glsl +52 -0
  98. package/src/gl/rule.vertex.glsl +89 -0
  99. package/src/gl/text.fragment.glsl +31 -0
  100. package/src/gl/text.vertex.glsl +246 -0
  101. package/src/gl/webGLHelper.js +490 -0
  102. package/src/img/bowtie.svg +1 -0
  103. package/src/img/genomespy-favicon.svg +34 -0
  104. package/src/index.html +11 -0
  105. package/src/index.js +151 -0
  106. package/src/marks/link.js +189 -0
  107. package/src/marks/mark.js +867 -0
  108. package/src/marks/markUtils.js +109 -0
  109. package/src/marks/pointMark.js +279 -0
  110. package/src/marks/rectMark.js +236 -0
  111. package/src/marks/rule.js +231 -0
  112. package/src/marks/text.js +274 -0
  113. package/src/options.d.ts +9 -0
  114. package/src/scale/colorUtils.js +184 -0
  115. package/src/scale/glslScaleGenerator.js +462 -0
  116. package/src/scale/scale.js +441 -0
  117. package/src/scale/scale.test.js +323 -0
  118. package/src/scale/ticks.js +198 -0
  119. package/src/scale/ticks.test.js +39 -0
  120. package/src/singlePageApp.js +13 -0
  121. package/src/spec/axis.d.ts +296 -0
  122. package/src/spec/channel.d.ts +127 -0
  123. package/src/spec/data.d.ts +185 -0
  124. package/src/spec/font.d.ts +15 -0
  125. package/src/spec/genome.d.ts +35 -0
  126. package/src/spec/mark.d.ts +432 -0
  127. package/src/spec/root.d.ts +22 -0
  128. package/src/spec/scale.d.ts +265 -0
  129. package/src/spec/tooltip.d.ts +9 -0
  130. package/src/spec/transform.d.ts +479 -0
  131. package/src/spec/view.d.ts +215 -0
  132. package/src/styles/genome-spy.scss +153 -0
  133. package/src/tooltip/dataTooltipHandler.js +59 -0
  134. package/src/tooltip/refseqGeneTooltipHandler.js +77 -0
  135. package/src/tooltip/tooltipHandler.ts +12 -0
  136. package/src/types/filetypes.d.ts +4 -0
  137. package/src/types/flatqueue.d.ts +53 -0
  138. package/src/types/glsl.d.ts +4 -0
  139. package/src/types/object.d.ts +21 -0
  140. package/src/types/vega-scale.d.ts +60 -0
  141. package/src/utils/animator.js +83 -0
  142. package/src/utils/arrayUtils.js +55 -0
  143. package/src/utils/binnedRangeIndex.js +83 -0
  144. package/src/utils/clamp.js +8 -0
  145. package/src/utils/cloner.js +32 -0
  146. package/src/utils/cloner.test.js +23 -0
  147. package/src/utils/coalesce.js +11 -0
  148. package/src/utils/coalesce.test.js +15 -0
  149. package/src/utils/concatIterables.js +26 -0
  150. package/src/utils/concatIterables.test.js +7 -0
  151. package/src/utils/debounce.js +37 -0
  152. package/src/utils/domainArray.js +224 -0
  153. package/src/utils/domainArray.test.js +129 -0
  154. package/src/utils/eerp.js +13 -0
  155. package/src/utils/expression.js +32 -0
  156. package/src/utils/field.js +28 -0
  157. package/src/utils/fisheye.js +60 -0
  158. package/src/utils/formatObject.js +31 -0
  159. package/src/utils/html.js +23 -0
  160. package/src/utils/html.test.js +13 -0
  161. package/src/utils/indexer.js +43 -0
  162. package/src/utils/indexer.test.js +46 -0
  163. package/src/utils/inertia.js +124 -0
  164. package/src/utils/interactionEvent.js +33 -0
  165. package/src/utils/iterateNestedMaps.js +21 -0
  166. package/src/utils/iterateNestedMaps.test.js +32 -0
  167. package/src/utils/kWayMerge.js +42 -0
  168. package/src/utils/kWayMerge.test.js +25 -0
  169. package/src/utils/layout/flexLayout.js +336 -0
  170. package/src/utils/layout/flexLayout.test.js +296 -0
  171. package/src/utils/layout/padding.js +107 -0
  172. package/src/utils/layout/point.js +23 -0
  173. package/src/utils/layout/rectangle.js +282 -0
  174. package/src/utils/layout/rectangle.test.js +171 -0
  175. package/src/utils/mergeObjects.js +99 -0
  176. package/src/utils/mergeObjects.test.js +41 -0
  177. package/src/utils/numberExtractor.js +24 -0
  178. package/src/utils/numberExtractor.test.js +5 -0
  179. package/src/utils/point.js +14 -0
  180. package/src/utils/propertyCacher.js +70 -0
  181. package/src/utils/propertyCacher.test.js +84 -0
  182. package/src/utils/propertyCoalescer.js +37 -0
  183. package/src/utils/propertyCoalescer.test.js +21 -0
  184. package/src/utils/reservationMap.js +103 -0
  185. package/src/utils/reservationMap.test.js +19 -0
  186. package/src/utils/scaleNull.js +19 -0
  187. package/src/utils/setOperations.js +75 -0
  188. package/src/utils/smoothstep.js +10 -0
  189. package/src/utils/throttle.js +34 -0
  190. package/src/utils/topK.js +76 -0
  191. package/src/utils/topK.test.js +63 -0
  192. package/src/utils/transition.js +74 -0
  193. package/src/utils/ui/tooltip.js +189 -0
  194. package/src/utils/url.js +22 -0
  195. package/src/utils/variableTools.js +24 -0
  196. package/src/utils/variableTools.test.js +12 -0
  197. package/src/view/axisResolution.js +135 -0
  198. package/src/view/axisResolution.test.js +200 -0
  199. package/src/view/axisView.js +746 -0
  200. package/src/view/channel.js +5 -0
  201. package/src/view/concatView.js +296 -0
  202. package/src/view/containerView.js +141 -0
  203. package/src/view/decoratorView.js +510 -0
  204. package/src/view/facetView.js +488 -0
  205. package/src/view/flowBuilder.js +362 -0
  206. package/src/view/flowBuilder.test.js +124 -0
  207. package/src/view/importView.js +19 -0
  208. package/src/view/layerView.js +60 -0
  209. package/src/view/rendering.d.ts +44 -0
  210. package/src/view/renderingContext/compositeViewRenderingContext.js +51 -0
  211. package/src/view/renderingContext/deferredViewRenderingContext.js +174 -0
  212. package/src/view/renderingContext/layoutRecorderViewRenderingContext.js +128 -0
  213. package/src/view/renderingContext/simpleViewRenderingContext.js +62 -0
  214. package/src/view/renderingContext/svgViewRenderingContext.js +121 -0
  215. package/src/view/renderingContext/viewRenderingContext.js +41 -0
  216. package/src/view/scaleResolution.js +756 -0
  217. package/src/view/scaleResolution.test.js +571 -0
  218. package/src/view/scaleResolutionApi.d.ts +40 -0
  219. package/src/view/testUtils.js +48 -0
  220. package/src/view/unitView.js +368 -0
  221. package/src/view/view.js +589 -0
  222. package/src/view/view.test.js +213 -0
  223. package/src/view/viewContext.d.ts +57 -0
  224. package/src/view/viewFactory.js +179 -0
  225. package/src/view/viewFactory.test.js +16 -0
  226. package/src/view/viewUtils.js +420 -0
@@ -0,0 +1,213 @@
1
+ import UnitView from "./unitView";
2
+ import LayerView from "./layerView";
3
+
4
+ import { create, createAndInitialize } from "./testUtils";
5
+ import { toRegularArray as r } from "../utils/domainArray";
6
+ import ConcatView from "./concatView";
7
+ import PointMark from "../marks/pointMark";
8
+ import View from "./view";
9
+
10
+ describe("Trivial creations and initializations", () => {
11
+ test("Fails on empty spec", () => {
12
+ // @ts-ignore
13
+ expect(() => create({}, View)).toThrow();
14
+ });
15
+
16
+ test("Parses a trivial spec", () => {
17
+ expect(create({ mark: "point" }, View)).toBeInstanceOf(UnitView);
18
+ expect(create({ layer: [] }, View)).toBeInstanceOf(LayerView);
19
+ });
20
+
21
+ test("Parses a more comples spec", () => {
22
+ const view = create(
23
+ {
24
+ concat: [
25
+ {
26
+ layer: [{ mark: "point" }, { mark: "rect" }],
27
+ },
28
+ { mark: "rect" },
29
+ ],
30
+ },
31
+ ConcatView
32
+ );
33
+
34
+ expect(view).toBeInstanceOf(ConcatView);
35
+ expect(view.children[0]).toBeInstanceOf(LayerView);
36
+ expect(view.children[0].children[0]).toBeInstanceOf(UnitView);
37
+ expect(view.children[0].children[0].mark).toBeInstanceOf(PointMark);
38
+ expect(view.children[1]).toBeInstanceOf(UnitView);
39
+ expect(view.children[2]).toBeUndefined();
40
+ });
41
+
42
+ test("Parses and initializes a trivial spec", () => {
43
+ const spec = {
44
+ data: { values: [1] },
45
+ mark: "point",
46
+ encoding: {
47
+ x: { field: "data", type: "quantitative" },
48
+ y: { field: "data", type: "quantitative" },
49
+ },
50
+ };
51
+
52
+ expect(createAndInitialize(spec, View)).resolves.toBeInstanceOf(
53
+ UnitView
54
+ );
55
+ });
56
+ });
57
+
58
+ describe("Test domain handling", () => {
59
+ const dataSpec = {
60
+ values: [
61
+ { a: 1, b: 3 },
62
+ { a: 2, b: 4 },
63
+ { a: 3, b: 5 },
64
+ ],
65
+ };
66
+
67
+ test("Uses domain from the scale properties", () => {
68
+ const spec = {
69
+ data: dataSpec,
70
+ mark: "point",
71
+ encoding: {
72
+ x: { field: "a", type: "quantitative" },
73
+ y: {
74
+ field: "a",
75
+ type: "quantitative",
76
+ scale: { domain: [0, 1000] },
77
+ },
78
+ },
79
+ };
80
+
81
+ return createAndInitialize(spec, UnitView).then((view) =>
82
+ expect(r(view.getConfiguredDomain("y"))).toEqual([0, 1000])
83
+ );
84
+ });
85
+
86
+ test("Includes a constant in the data domain", () => {
87
+ const spec = {
88
+ data: dataSpec,
89
+ mark: "point",
90
+ encoding: {
91
+ x: { datum: 123, type: "quantitative" },
92
+ y: { field: "a", type: "quantitative" },
93
+ },
94
+ };
95
+
96
+ return createAndInitialize(spec, UnitView).then((view) =>
97
+ expect(r(view.extractDataDomain("x"))).toEqual([123, 123])
98
+ );
99
+ });
100
+
101
+ test("Extracts domain from the data", () => {
102
+ const spec = {
103
+ data: dataSpec,
104
+ mark: "point",
105
+ encoding: {
106
+ x: { field: "a", type: "quantitative" },
107
+ y: { field: "a", type: "quantitative" },
108
+ },
109
+ };
110
+
111
+ return createAndInitialize(spec, UnitView).then((view) =>
112
+ expect(r(view.extractDataDomain("y"))).toEqual([1, 3])
113
+ );
114
+ });
115
+
116
+ test("Extracts domain from the data when a secondary channel is being used", () => {
117
+ const spec = {
118
+ data: dataSpec,
119
+ mark: "rect",
120
+ encoding: {
121
+ x: { field: "a", type: "quantitative" },
122
+ y: { field: "a", type: "quantitative" },
123
+ y2: { field: "b", type: "quantitative" },
124
+ },
125
+ };
126
+
127
+ return createAndInitialize(spec, UnitView).then((view) =>
128
+ expect(r(view.extractDataDomain("y"))).toEqual([1, 5])
129
+ );
130
+ });
131
+ });
132
+
133
+ describe("Utility methods", () => {
134
+ test("BaseUrl is handled correctly", async () => {
135
+ createAndInitialize(
136
+ {
137
+ layer: [],
138
+ },
139
+ LayerView
140
+ ).then((view) => expect(view.getBaseUrl()).toBeUndefined());
141
+
142
+ await createAndInitialize(
143
+ {
144
+ baseUrl: "blaa",
145
+ layer: [],
146
+ },
147
+ LayerView
148
+ ).then((view) => expect(view.getBaseUrl()).toEqual("blaa"));
149
+
150
+ await createAndInitialize(
151
+ {
152
+ baseUrl: "https://site.com",
153
+ layer: [],
154
+ },
155
+ LayerView
156
+ ).then((view) => expect(view.getBaseUrl()).toEqual("https://site.com"));
157
+
158
+ await createAndInitialize(
159
+ {
160
+ baseUrl: "https://site.com",
161
+ layer: [
162
+ {
163
+ baseUrl: "blaa",
164
+ layer: [],
165
+ },
166
+ ],
167
+ },
168
+ LayerView
169
+ ).then((view) =>
170
+ expect(view.children[0].getBaseUrl()).toEqual(
171
+ "https://site.com/blaa"
172
+ )
173
+ );
174
+
175
+ await createAndInitialize(
176
+ {
177
+ baseUrl: "https://site.com",
178
+ layer: [
179
+ {
180
+ baseUrl: "https://another-site.com",
181
+ layer: [],
182
+ },
183
+ ],
184
+ },
185
+ LayerView
186
+ ).then((view) =>
187
+ expect(view.children[0].getBaseUrl()).toEqual(
188
+ "https://another-site.com"
189
+ )
190
+ );
191
+
192
+ await createAndInitialize(
193
+ {
194
+ baseUrl: "https://site.com",
195
+ layer: [
196
+ {
197
+ layer: [
198
+ {
199
+ baseUrl: "blaa",
200
+ layer: [],
201
+ },
202
+ ],
203
+ },
204
+ ],
205
+ },
206
+ LayerView
207
+ ).then((view) =>
208
+ expect(view.children[0].children[0].getBaseUrl()).toEqual(
209
+ "https://site.com/blaa"
210
+ )
211
+ );
212
+ });
213
+ });
@@ -0,0 +1,57 @@
1
+ import { TemplateResult } from "lit-html";
2
+ import View from "./view";
3
+ import DataFlow from "../data/dataFlow";
4
+ import AccessorFactory from "../encoder/accessor";
5
+ import WebGLHelper from "../gl/webGLHelper";
6
+ import Animator from "../utils/animator";
7
+ import GenomeStore from "../genome/genomeStore";
8
+ import BmFontManager from "../fonts/bmFontManager";
9
+ import Mark from "../marks/mark";
10
+ import { Datum } from "../data/flowNode";
11
+ import { ViewSpec } from "../spec/view";
12
+ import ContainerView from "./containerView";
13
+
14
+ /**
15
+ * ViewContext provides essential data and interfaces to View classes.
16
+ */
17
+ export default interface ViewContext {
18
+ dataFlow: DataFlow<View>;
19
+ accessorFactory: AccessorFactory;
20
+ glHelper: WebGLHelper;
21
+ animator: Animator;
22
+ genomeStore?: GenomeStore;
23
+ fontManager: BmFontManager;
24
+
25
+ requestLayoutReflow: () => void;
26
+
27
+ updateTooltip: <T>(
28
+ datum: T,
29
+ converter?: (datum: T) => Promise<TemplateResult>
30
+ ) => void;
31
+
32
+ getCurrentHover: () => { mark: Mark; datum: Datum };
33
+
34
+ /**
35
+ * Adds a keyboard event listener to the document. Cleanup is performed automatically
36
+ * when GenomeSpy is finalized.
37
+ *
38
+ * TODO: Listeners should be called only when the mouse pointer is inside the
39
+ * container or the app covers the full document.
40
+ */
41
+ addKeyboardListener: (
42
+ type: "keydown" | "keyup",
43
+ listener: (event: KeyboardEvent) => void
44
+ ) => void;
45
+
46
+ getNamedData: (name: string) => any[];
47
+
48
+ isViewVisible: (view: View) => boolean;
49
+
50
+ isViewSpec: (spec: any) => boolean;
51
+
52
+ createView: (
53
+ spec: ViewSpec,
54
+ parent?: ContainerView,
55
+ defaultName?: string
56
+ ) => View;
57
+ }
@@ -0,0 +1,179 @@
1
+ // eslint-disable-next-line no-unused-vars
2
+ import View from "./view";
3
+
4
+ import UnitView from "./unitView";
5
+ import ImportView from "./importView";
6
+ import LayerView from "./layerView";
7
+ import ConcatView from "./concatView";
8
+ import { isArray, isObject, isString } from "vega-util";
9
+
10
+ /**
11
+ * @typedef {import("./viewContext").default} ViewContext
12
+ * @typedef {import("../spec/view").UnitSpec} UnitSpec
13
+ * @typedef {import("../spec/view").ViewSpec} ViewSpec
14
+ * @typedef {import("../spec/view").LayerSpec} LayerSpec
15
+ * @typedef {import("../spec/view").ImportSpec} ImportSpec
16
+ * @typedef {import("../spec/view").SampleSpec} SampleSpec
17
+ * @typedef {import("../spec/view").VConcatSpec} VConcatSpec
18
+ * @typedef {import("../spec/view").HConcatSpec} HConcatSpec
19
+ * @typedef {import("../spec/view").ConcatSpec} ConcatSpec
20
+ * @typedef {VConcatSpec | HConcatSpec | ConcatSpec} AnyConcatSpec
21
+ *
22
+ * @typedef {(spec: ViewSpec) => boolean} specGuard
23
+ * @typedef {(spec: ViewSpec, context: ViewContext, parent?: import("./containerView").default, defaultName?: string) => View} factory
24
+ */
25
+
26
+ export class ViewFactory {
27
+ constructor() {
28
+ /** @type {{specGuard: specGuard, factory: factory}[]} */
29
+ this.types = [];
30
+
31
+ const makeDefaultFactory =
32
+ (/** @type {typeof View} */ ViewClass) =>
33
+ /** @type {factory} */
34
+ (spec, context, parent, defaultName) =>
35
+ /** @type {View} */ (
36
+ new ViewClass(
37
+ spec,
38
+ context,
39
+ parent,
40
+ spec.name ?? defaultName
41
+ )
42
+ );
43
+
44
+ this.addViewType(isImportSpec, makeDefaultFactory(ImportView));
45
+ this.addViewType(isLayerSpec, makeDefaultFactory(LayerView));
46
+ this.addViewType(isUnitSpec, makeDefaultFactory(UnitView));
47
+ this.addViewType(isVConcatSpec, makeDefaultFactory(ConcatView));
48
+ this.addViewType(isHConcatSpec, makeDefaultFactory(ConcatView));
49
+ this.addViewType(isConcatSpec, makeDefaultFactory(ConcatView));
50
+ //this.addViewType(isFacetSpec, makeDefaultFactory(FacetView));
51
+ }
52
+
53
+ /**
54
+ * @param {specGuard} specGuard
55
+ * @param {factory} factory
56
+ */
57
+ addViewType(specGuard, factory) {
58
+ this.types.push({ specGuard, factory });
59
+ }
60
+
61
+ /**
62
+ * @param {ViewSpec} spec
63
+ * @param {ViewContext} context
64
+ * @param {import("./containerView").default} [parent]
65
+ * @param {string} [defaultName]
66
+ */
67
+ createView(spec, context, parent, defaultName) {
68
+ const type = this.types.find((type) => type.specGuard(spec));
69
+ if (type) {
70
+ return type.factory(
71
+ spec,
72
+ context,
73
+ parent,
74
+ defaultName ?? "unnamed"
75
+ );
76
+ } else {
77
+ throw new Error(
78
+ "Invalid spec, cannot figure out the view type from the properties: " +
79
+ JSON.stringify([...Object.keys(spec)])
80
+ );
81
+ }
82
+ }
83
+
84
+ /**
85
+ *
86
+ * @param {ViewSpec} spec
87
+ * @returns {spec is ViewSpec}
88
+ */
89
+ isViewSpec(spec) {
90
+ const matches = this.types.filter((type) => type.specGuard(spec));
91
+
92
+ if (matches.length > 1) {
93
+ throw new Error("Ambiguous spec. Cannot create a view!");
94
+ }
95
+
96
+ return matches.length == 1;
97
+ }
98
+ }
99
+
100
+ /**
101
+ *
102
+ * @param {ViewSpec} spec
103
+ * @returns {spec is UnitSpec}
104
+ */
105
+ export function isUnitSpec(spec) {
106
+ return "mark" in spec && (isString(spec.mark) || isObject(spec.mark));
107
+ }
108
+
109
+ /**
110
+ *
111
+ * @param {ViewSpec} spec
112
+ * @returns {spec is LayerSpec}
113
+ */
114
+ export function isLayerSpec(spec) {
115
+ return "layer" in spec && isObject(spec.layer);
116
+ }
117
+
118
+ /**
119
+ *
120
+ * @param {ViewSpec} spec
121
+ * @returns {spec is LayerSpec}
122
+ */
123
+ export function isFacetSpec(spec) {
124
+ return (
125
+ "facet" in spec &&
126
+ isObject(spec.facet) &&
127
+ "spec" in spec &&
128
+ isObject(spec.spec)
129
+ );
130
+ }
131
+
132
+ /**
133
+ *
134
+ * @param {ViewSpec} spec
135
+ * @returns {spec is import("../spec/view").AggregateSamplesSpec}
136
+ */
137
+ export function isAggregateSamplesSpec(spec) {
138
+ return (
139
+ spec &&
140
+ (isUnitSpec(spec) || isLayerSpec(spec)) &&
141
+ "aggregateSamples" in spec
142
+ );
143
+ }
144
+
145
+ /**
146
+ *
147
+ * @param {object} spec
148
+ * @returns {spec is ImportSpec}
149
+ */
150
+ export function isImportSpec(spec) {
151
+ return "import" in spec;
152
+ }
153
+
154
+ /**
155
+ *
156
+ * @param {ViewSpec} spec
157
+ * @returns {spec is VConcatSpec}
158
+ */
159
+ export function isVConcatSpec(spec) {
160
+ return "vconcat" in spec && isArray(spec.vconcat);
161
+ }
162
+
163
+ /**
164
+ *
165
+ * @param {ViewSpec} spec
166
+ * @returns {spec is HConcatSpec}
167
+ */
168
+ export function isHConcatSpec(spec) {
169
+ return "hconcat" in spec && isArray(spec.hconcat);
170
+ }
171
+
172
+ /**
173
+ *
174
+ * @param {ViewSpec} spec
175
+ * @returns {spec is ConcatSpec}
176
+ */
177
+ export function isConcatSpec(spec) {
178
+ return "concat" in spec && isArray(spec.concat);
179
+ }
@@ -0,0 +1,16 @@
1
+ import { ViewFactory } from "./viewFactory";
2
+
3
+ test("isViewSpec", () => {
4
+ const factory = new ViewFactory();
5
+
6
+ // @ts-ignore
7
+ expect(factory.isViewSpec({})).toBeFalsy();
8
+
9
+ expect(factory.isViewSpec({ mark: "rect" })).toBeTruthy();
10
+ expect(factory.isViewSpec({ layer: [] })).toBeTruthy();
11
+ expect(factory.isViewSpec({ hconcat: [] })).toBeTruthy();
12
+ expect(factory.isViewSpec({ vconcat: [] })).toBeTruthy();
13
+ expect(factory.isViewSpec({ concat: [] })).toBeTruthy();
14
+
15
+ expect(() => factory.isViewSpec({ mark: "rect", layer: [] })).toThrow();
16
+ });