@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,589 @@
1
+ import {
2
+ parseSizeDef,
3
+ FlexDimensions,
4
+ ZERO_FLEXDIMENSIONS,
5
+ } from "../utils/layout/flexLayout";
6
+ import Padding from "../utils/layout/padding";
7
+ import {
8
+ getCachedOrCall,
9
+ initPropertyCache,
10
+ invalidatePrefix,
11
+ } from "../utils/propertyCacher";
12
+ import { isNumber, span } from "vega-util";
13
+ import { scaleLog } from "d3-scale";
14
+ import { isFieldDef, getPrimaryChannel } from "../encoder/encoder";
15
+ import { appendToBaseUrl } from "../utils/url";
16
+ import { isDiscrete, bandSpace } from "vega-scale";
17
+ import { peek } from "../utils/arrayUtils";
18
+
19
+ // TODO: View classes have too many responsibilities. Come up with a way
20
+ // to separate the concerns. However, most concerns are tightly tied to
21
+ // the hierarchy, which makes the separation quite tricky.
22
+ // Separation of concerns would also make the code more easily testable.
23
+
24
+ /** Skip children */
25
+ export const VISIT_SKIP = "VISIT_SKIP";
26
+ /** Stop further visits */
27
+ export const VISIT_STOP = "VISIT_STOP";
28
+
29
+ /** @type {function(number):number} */
30
+ const defaultOpacityFunction = (parentOpacity) => parentOpacity;
31
+
32
+ /**
33
+ * @typedef {import("../spec/channel").Channel} Channel
34
+ * @typedef {import("../spec/channel").ChannelDef} ChannelDef
35
+ * @typedef {import("../spec/view").ViewSpec} ViewSpec
36
+ * @typedef {import("./viewUtils").ViewContext} ViewContext
37
+ * @typedef {import("../utils/layout/flexLayout").SizeDef} SizeDef
38
+ * @typedef {import("../utils/layout/flexLayout").LocSize} LocSize
39
+ *
40
+ * @typedef {import("../spec/view").ResolutionTarget} ResolutionTarget
41
+ * @typedef {import("./scaleResolution").default} ScaleResolution
42
+ * @typedef {import("./axisResolution").default} AxisResolution
43
+ *
44
+ * @typedef {VISIT_SKIP|VISIT_STOP|void} VisitResult
45
+ *
46
+ * @callback VisitorCallback
47
+ * @param {View} view
48
+ * @returns {VisitResult}
49
+ *
50
+ * @typedef {VisitorCallback & {
51
+ * postOrder?: function(View):void,
52
+ * beforeChildren?: function(View):void,
53
+ * afterChildren?: function(View):void}
54
+ * } Visitor
55
+ *
56
+ * @typedef {object} BroadcastMessage
57
+ * @prop {string} type Broadcast type
58
+ * @prop {any} [payload] Anything
59
+ *
60
+ * @typedef {import("./rendering").RenderingOptions} RenderingOptions
61
+ *
62
+ * @callback InteractionEventListener
63
+ * @param {import("../utils/layout/rectangle").default} coords
64
+ * Coordinates of the view
65
+ * @param {import("../utils/interactionEvent").default} event
66
+ */
67
+ export default class View {
68
+ /**
69
+ *
70
+ * @param {ViewSpec} spec
71
+ * @param {ViewContext} context
72
+ * @param {import("./containerView").default} parent
73
+ * @param {string} name
74
+ */
75
+ constructor(spec, context, parent, name) {
76
+ this.context = context;
77
+ this.parent = parent;
78
+ this.name = spec.name || name;
79
+ this.spec = spec;
80
+
81
+ this.resolutions = {
82
+ /**
83
+ * Channel-specific scale resolutions
84
+ * @type {Record<string, import("./scaleResolution").default>}
85
+ */
86
+ scale: {},
87
+ /**
88
+ * Channel-specific axis resolutions
89
+ * @type {Record<string, import("./axisResolution").default>}
90
+ */
91
+ axis: {},
92
+ };
93
+
94
+ /** @type {Record<string, (function(BroadcastMessage):void)[]>} */
95
+ this._broadcastHandlers = {};
96
+
97
+ /** @type {Record<string, InteractionEventListener[]>} */
98
+ this._capturingInteractionEventListeners = {};
99
+ /** @type {Record<string, InteractionEventListener[]>} */
100
+ this._nonCapturingInteractionEventListeners = {};
101
+
102
+ initPropertyCache(this);
103
+
104
+ /** @type {function(number):number} */
105
+ this.opacityFunction = defaultOpacityFunction;
106
+ }
107
+
108
+ getPadding() {
109
+ return this._cache("size/padding", () =>
110
+ Padding.createFromConfig(this.spec.padding)
111
+ );
112
+ }
113
+
114
+ /**
115
+ * Returns a computed, "effective" padding between the plot area and view's
116
+ * bounding box. The padding may include the configured padding, axes,
117
+ * peripheral views, etc.
118
+ *
119
+ * Effective padding allows for aligning views so that their content and
120
+ * axes line up properly.
121
+ */
122
+ getEffectivePadding() {
123
+ return this.getPadding();
124
+ }
125
+
126
+ /**
127
+ * Returns the configured size, if present. Otherwise a computed or default
128
+ * height is returned.
129
+ *
130
+ * @returns {FlexDimensions}
131
+ */
132
+ getSize() {
133
+ return this._cache("size/size", () =>
134
+ this.isVisible()
135
+ ? this.getSizeFromSpec().addPadding(this.getPadding())
136
+ : ZERO_FLEXDIMENSIONS
137
+ );
138
+ }
139
+
140
+ /**
141
+ * @return {FlexDimensions}
142
+ */
143
+ getSizeFromSpec() {
144
+ /**
145
+ *
146
+ * @param {"width" | "height"} dimension
147
+ * @return {SizeDef}
148
+ */
149
+ const handleSize = (dimension) => {
150
+ /** @type {SizeDef} */
151
+ let sizeDef;
152
+ let value = this.spec[dimension];
153
+
154
+ if (isStepSize(value)) {
155
+ const stepSize = value.step;
156
+
157
+ const scale = this.getScaleResolution(
158
+ dimension == "width" ? "x" : "y"
159
+ )?.getScale();
160
+
161
+ if (scale) {
162
+ // Note: this and all ancestral views need to be refreshed when the domain is changed.
163
+ let steps = 0;
164
+ if (isDiscrete(scale.type)) {
165
+ steps = scale.domain().length;
166
+ } else if (["locus", "index"].includes(scale.type)) {
167
+ const domain = scale.domain();
168
+ steps = peek(domain) - domain[0];
169
+ } else {
170
+ throw new Error(
171
+ `Cannot use step-based size with "${scale.type}" scale!`
172
+ );
173
+ }
174
+
175
+ steps = bandSpace(
176
+ steps,
177
+ scale.paddingInner(),
178
+ scale.paddingOuter()
179
+ );
180
+
181
+ sizeDef = { px: steps * stepSize, grow: 0 };
182
+ } else {
183
+ throw new Error(
184
+ "Cannot use 'step' size with missing scale!"
185
+ );
186
+ }
187
+ } else {
188
+ sizeDef = (value && parseSizeDef(value)) || { px: 0, grow: 1 };
189
+ }
190
+ return sizeDef;
191
+ };
192
+
193
+ return this._cache(
194
+ "size/sizeFromSpec",
195
+ () => new FlexDimensions(handleSize("width"), handleSize("height"))
196
+ );
197
+ }
198
+
199
+ isVisible() {
200
+ return this.context.isViewVisible(this);
201
+ }
202
+
203
+ isVisibleInSpec() {
204
+ return this.spec.visible ?? true;
205
+ }
206
+
207
+ /**
208
+ * Returns the effective opacity of this view, e.g., view's opacity multiplied
209
+ * by opacities of its ancestors.
210
+ *
211
+ * TODO: This methods makes sense only in Unit and Layer views.
212
+ *
213
+ * @returns {number}
214
+ */
215
+ getEffectiveOpacity() {
216
+ return this.opacityFunction(this.parent?.getEffectiveOpacity() ?? 1.0);
217
+ }
218
+
219
+ getPathString() {
220
+ return [...this.getAncestors()]
221
+ .map((v) => v.name)
222
+ .reverse()
223
+ .join("/");
224
+ }
225
+
226
+ *getAncestors() {
227
+ // eslint-disable-next-line consistent-this
228
+ let view = /** @type {View} */ (this);
229
+ do {
230
+ yield view;
231
+ view = view.parent;
232
+ } while (view);
233
+ }
234
+
235
+ /**
236
+ * Handles a broadcast message that is intended for the whole view hierarchy.
237
+ *
238
+ * @param {BroadcastMessage} message
239
+ */
240
+ handleBroadcast(message) {
241
+ // TODO: message types should be constants
242
+ for (const handler of this._broadcastHandlers[message.type] || []) {
243
+ handler(message);
244
+ }
245
+ }
246
+
247
+ /**
248
+ *
249
+ * @param {string} type
250
+ * @param {function(BroadcastMessage):void} handler
251
+ */
252
+ _addBroadcastHandler(type, handler) {
253
+ let handlers = this._broadcastHandlers[type];
254
+ if (!handlers) {
255
+ handlers = [];
256
+ this._broadcastHandlers[type] = handlers;
257
+ }
258
+ handlers.push(handler);
259
+ }
260
+
261
+ /**
262
+ * Handles an interactionEvent
263
+ *
264
+ * @param {import("../utils/layout/rectangle").default} coords
265
+ * Coordinates of the view
266
+ * @param {import("../utils/interactionEvent").default} event
267
+ * @param {boolean} capturing
268
+ */
269
+ handleInteractionEvent(coords, event, capturing) {
270
+ const listenersByType = capturing
271
+ ? this._capturingInteractionEventListeners
272
+ : this._nonCapturingInteractionEventListeners;
273
+ for (const listener of listenersByType[event.type] || []) {
274
+ listener(coords, event);
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Add an "interaction" event listener that mimics DOM's event model inside
280
+ * the view hierarchy.
281
+ *
282
+ * This is intended for GenomeSpy's internal use. It allows the views to handle
283
+ * low level interactions such as dragging, wheeling, etc.
284
+ *
285
+ * @param {string} type
286
+ * @param {InteractionEventListener} listener
287
+ * @param {boolean} [useCapture]
288
+ */
289
+ addInteractionEventListener(type, listener, useCapture) {
290
+ const listenersByType = useCapture
291
+ ? this._capturingInteractionEventListeners
292
+ : this._nonCapturingInteractionEventListeners;
293
+ let listeners = listenersByType[type];
294
+ if (!listeners) {
295
+ listeners = [];
296
+ listenersByType[type] = listeners;
297
+ }
298
+
299
+ listeners.push(listener);
300
+ }
301
+
302
+ /**
303
+ * Visits child views in depth-first order. Visitor's return value
304
+ * controls the traversal.
305
+ *
306
+ * @param {Visitor} visitor
307
+ * @returns {VisitResult}
308
+ *
309
+ */
310
+ visit(visitor) {
311
+ try {
312
+ const result = visitor(this);
313
+
314
+ if (visitor.postOrder) {
315
+ visitor.postOrder(this);
316
+ }
317
+
318
+ if (result !== VISIT_STOP) {
319
+ return result;
320
+ }
321
+ } catch (e) {
322
+ // Augment the exception with the view
323
+ e.view = this;
324
+ throw e;
325
+ }
326
+ }
327
+
328
+ /**
329
+ * Called after all scales in the view hierarchy have been resolved.
330
+ */
331
+ onScalesResolved() {
332
+ // Only set the opacity function once. The idea is to allow custom functions
333
+ // and prevent accidental overwrites.
334
+ if (
335
+ !this.opacityFunction ||
336
+ this.opacityFunction === defaultOpacityFunction
337
+ ) {
338
+ this.opacityFunction = createViewOpacityFunction(this);
339
+ }
340
+ }
341
+
342
+ /**
343
+ * ViewRenderingContext calls this method once for each view during each rendering
344
+ * pass. The order is depth first, pre order.
345
+ */
346
+ onBeforeRender() {
347
+ //
348
+ }
349
+
350
+ /**
351
+ * Recursively traverses the view hierarchy, computes the view coordinates,
352
+ * and coordinates the mark rendering.
353
+ *
354
+ * @param {import("./renderingContext/viewRenderingContext").default} context
355
+ * @param {import("../utils/layout/rectangle").default} coords The coordinate rectangle that the parent computed
356
+ * for the child that is being visited.
357
+ * @param {RenderingOptions} [options]
358
+ */
359
+ render(context, coords, options = {}) {
360
+ // override
361
+ }
362
+
363
+ /**
364
+ * Returns the encodings specified in this view combined with the inherited
365
+ * encodings. However, this does not contain any defaults or inferred/adjusted/fixed
366
+ * encodings. Those are available in Mark's encoding property.
367
+ *
368
+ * @param {View} [whoIsAsking] Passed to the immediate parent. Allows for
369
+ * selectively breaking the inheritance.
370
+ * @return {import("../spec/channel").Encoding}
371
+ */
372
+ getEncoding(whoIsAsking) {
373
+ const pe = this.parent ? this.parent.getEncoding(this) : {};
374
+ const te = this.spec.encoding || {};
375
+
376
+ /** @type {import("../spec/channel").Encoding} */
377
+ const combined = {
378
+ ...pe,
379
+ ...te,
380
+ };
381
+
382
+ for (const [channel, channelDef] of Object.entries(combined)) {
383
+ if (channelDef === null) {
384
+ // Prevent propagation
385
+ delete combined[channel];
386
+ }
387
+ }
388
+
389
+ return combined;
390
+ }
391
+
392
+ /**
393
+ * @param {View} [whoIsAsking] Passed to the immediate parent. Allows for
394
+ * selectively breaking the inheritance.
395
+ * @return {function(object):any}
396
+ */
397
+ getFacetAccessor(whoIsAsking) {
398
+ if (this.parent) {
399
+ return this.parent.getFacetAccessor(this);
400
+ }
401
+ }
402
+
403
+ /**
404
+ * Returns the fields that should be used for partitioning the data for facets.
405
+ *
406
+ * @param {View} [whoIsAsking]
407
+ * @returns {string[]}
408
+ */
409
+ getFacetFields(whoIsAsking) {
410
+ const sampleFieldDef = this.getEncoding().sample;
411
+ if (isFieldDef(sampleFieldDef)) {
412
+ return [sampleFieldDef.field];
413
+ } else {
414
+ return this.parent?.getFacetFields(this);
415
+ }
416
+ }
417
+
418
+ /**
419
+ * Returns a texture that has a mapping for the sample locations. This is implemented
420
+ * only in the SampleView of GenomeSpy App.
421
+ *
422
+ * @returns {WebGLTexture}
423
+ */
424
+ getSampleFacetTexture() {
425
+ return undefined;
426
+ }
427
+
428
+ /**
429
+ *
430
+ * @param {Channel} channel
431
+ * @param {ResolutionTarget} type
432
+ * @returns {ScaleResolution | AxisResolution}
433
+ */
434
+ _getResolution(channel, type) {
435
+ channel = getPrimaryChannel(channel);
436
+
437
+ /** @type {import("./view").default } */
438
+ // eslint-disable-next-line consistent-this
439
+ let view = this;
440
+ do {
441
+ const resolution = view.resolutions[type][channel];
442
+ if (resolution) {
443
+ return resolution;
444
+ }
445
+ view = view.parent;
446
+ } while (view);
447
+ }
448
+
449
+ /**
450
+ * @param {Channel} channel
451
+ */
452
+ getScaleResolution(channel) {
453
+ return /** @type {ScaleResolution} */ (
454
+ this._getResolution(channel, "scale")
455
+ );
456
+ }
457
+
458
+ /**
459
+ * @param {Channel} channel
460
+ */
461
+ getAxisResolution(channel) {
462
+ return /** @type {AxisResolution} */ (
463
+ this._getResolution(channel, "axis")
464
+ );
465
+ }
466
+
467
+ /**
468
+ * @returns {string}
469
+ */
470
+ getBaseUrl() {
471
+ return appendToBaseUrl(
472
+ () => this.parent?.getBaseUrl(),
473
+ this.spec.baseUrl
474
+ );
475
+ }
476
+
477
+ /**
478
+ * @returns {import("../data/sources/dataSource").default}
479
+ */
480
+ getDynamicDataSource() {
481
+ throw new Error("The view does not provide dynamic data!");
482
+ }
483
+
484
+ /**
485
+ * Returns `true` if this view and its children supports picking.
486
+ */
487
+ isPickingSupported() {
488
+ return true;
489
+ }
490
+
491
+ /**
492
+ * @param {any} key string
493
+ * @param {function(key?):T} callable A function that produces a value to be cached
494
+ * @returns {T}
495
+ * @template T
496
+ */
497
+ _cache(key, callable) {
498
+ return getCachedOrCall(this, key, callable);
499
+ }
500
+
501
+ /**
502
+ *
503
+ * @param {string} key
504
+ * @param {"self" | "progeny" | "ancestors"} [direction]
505
+ */
506
+ _invalidateCacheByPrefix(key, direction = "self") {
507
+ switch (direction) {
508
+ case "self":
509
+ invalidatePrefix(this, key);
510
+ break;
511
+ case "ancestors":
512
+ for (const view of this.getAncestors()) {
513
+ invalidatePrefix(view, key);
514
+ }
515
+ break;
516
+ case "progeny":
517
+ this.visit((view) => invalidatePrefix(view, key));
518
+ break;
519
+ default:
520
+ }
521
+ }
522
+
523
+ invalidateSizeCache() {
524
+ this._invalidateCacheByPrefix("size/", "ancestors");
525
+ }
526
+ }
527
+
528
+ /**
529
+ *
530
+ * @param {any} opacity
531
+ * @returns {opacity is import("../spec/view").DynamicOpacity}
532
+ */
533
+ function isDynamicOpacity(opacity) {
534
+ return "unitsPerPixel" in opacity;
535
+ }
536
+
537
+ /**
538
+ *
539
+ * @param {View} view
540
+ * @returns {function(number):number}
541
+ */
542
+ function createViewOpacityFunction(view) {
543
+ const opacityDef = view.spec.opacity;
544
+
545
+ if (opacityDef !== undefined) {
546
+ if (isNumber(opacityDef)) {
547
+ return (parentOpacity) => parentOpacity * opacityDef;
548
+ } else if (isDynamicOpacity(opacityDef)) {
549
+ /** @type {function(Channel):any} */
550
+ const getScale = (channel) => {
551
+ const scale = view.getScaleResolution(channel)?.getScale();
552
+ // Only works on linear scales
553
+ if (["linear", "index", "locus"].includes(scale?.type)) {
554
+ return scale;
555
+ }
556
+ };
557
+
558
+ const scale = opacityDef.channel
559
+ ? getScale(opacityDef.channel)
560
+ : getScale("x") || getScale("y");
561
+
562
+ if (!scale) {
563
+ throw new Error(
564
+ "Cannot find a resolved quantitative scale for dynamic opacity!"
565
+ );
566
+ }
567
+
568
+ const interpolate = scaleLog()
569
+ .domain(opacityDef.unitsPerPixel)
570
+ .range(opacityDef.values)
571
+ .clamp(true);
572
+
573
+ return (parentOpacity) => {
574
+ const rangeSpan = 1000; //TODO: span(scale.range());
575
+ const unitsPerPixel = span(scale.domain()) / rangeSpan;
576
+
577
+ return interpolate(unitsPerPixel) * parentOpacity;
578
+ };
579
+ }
580
+ }
581
+ return (parentOpacity) => parentOpacity;
582
+ }
583
+
584
+ /**
585
+ *
586
+ * @param {any} size
587
+ * @return {size is import("../spec/view").Step}
588
+ */
589
+ export const isStepSize = (size) => !!size?.step;