@genome-spy/core 0.30.0 → 0.30.2

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 (234) hide show
  1. package/dist/index.es.js +16373 -0
  2. package/dist/index.js +43 -43
  3. package/package.json +10 -7
  4. package/src/data/collector.js +0 -183
  5. package/src/data/collector.test.js +0 -84
  6. package/src/data/dataFlow.js +0 -148
  7. package/src/data/dataFlow.test.js +0 -5
  8. package/src/data/facetNode.js +0 -17
  9. package/src/data/flow.test.js +0 -72
  10. package/src/data/flowBatch.d.ts +0 -40
  11. package/src/data/flowNode.js +0 -283
  12. package/src/data/flowNode.test.js +0 -50
  13. package/src/data/flowOptimizer.js +0 -123
  14. package/src/data/flowOptimizer.test.js +0 -193
  15. package/src/data/flowTestUtils.js +0 -63
  16. package/src/data/formats/fasta.js +0 -32
  17. package/src/data/formats/fasta.test.js +0 -27
  18. package/src/data/sources/dataSource.js +0 -22
  19. package/src/data/sources/dataSourceFactory.js +0 -24
  20. package/src/data/sources/dataUtils.js +0 -78
  21. package/src/data/sources/dynamicCallbackSource.js +0 -57
  22. package/src/data/sources/dynamicSource.js +0 -37
  23. package/src/data/sources/inlineSource.js +0 -67
  24. package/src/data/sources/inlineSource.test.js +0 -56
  25. package/src/data/sources/namedSource.js +0 -79
  26. package/src/data/sources/sequenceSource.js +0 -46
  27. package/src/data/sources/sequenceSource.test.js +0 -46
  28. package/src/data/sources/urlSource.js +0 -74
  29. package/src/data/transforms/aggregate.js +0 -70
  30. package/src/data/transforms/clone.js +0 -40
  31. package/src/data/transforms/clone.test.js +0 -11
  32. package/src/data/transforms/coverage.js +0 -187
  33. package/src/data/transforms/coverage.test.js +0 -123
  34. package/src/data/transforms/filter.js +0 -37
  35. package/src/data/transforms/filter.test.js +0 -18
  36. package/src/data/transforms/filterScoredLabels.js +0 -134
  37. package/src/data/transforms/flattenCompressedExons.js +0 -57
  38. package/src/data/transforms/flattenDelimited.js +0 -74
  39. package/src/data/transforms/flattenDelimited.test.js +0 -87
  40. package/src/data/transforms/flattenSequence.js +0 -39
  41. package/src/data/transforms/flattenSequence.test.js +0 -34
  42. package/src/data/transforms/formula.js +0 -39
  43. package/src/data/transforms/formula.test.js +0 -19
  44. package/src/data/transforms/identifier.js +0 -108
  45. package/src/data/transforms/identifier.test.js +0 -83
  46. package/src/data/transforms/linearizeGenomicCoordinate.js +0 -101
  47. package/src/data/transforms/measureText.js +0 -44
  48. package/src/data/transforms/pileup.js +0 -128
  49. package/src/data/transforms/pileup.test.js +0 -70
  50. package/src/data/transforms/project.js +0 -41
  51. package/src/data/transforms/project.test.js +0 -32
  52. package/src/data/transforms/regexExtract.js +0 -61
  53. package/src/data/transforms/regexExtract.test.js +0 -67
  54. package/src/data/transforms/regexFold.js +0 -141
  55. package/src/data/transforms/regexFold.test.js +0 -160
  56. package/src/data/transforms/sample.js +0 -101
  57. package/src/data/transforms/sample.test.js +0 -38
  58. package/src/data/transforms/stack.js +0 -137
  59. package/src/data/transforms/stack.test.js +0 -91
  60. package/src/data/transforms/transformFactory.js +0 -60
  61. package/src/embedApi.d.ts +0 -67
  62. package/src/encoder/accessor.js +0 -82
  63. package/src/encoder/accessor.test.js +0 -47
  64. package/src/encoder/encoder.js +0 -394
  65. package/src/encoder/encoder.test.js +0 -98
  66. package/src/fonts/Lato-Regular.json +0 -1267
  67. package/src/fonts/Lato-Regular.png +0 -0
  68. package/src/fonts/OFL.txt +0 -93
  69. package/src/fonts/README.md +0 -3
  70. package/src/fonts/bmFont.d.ts +0 -58
  71. package/src/fonts/bmFontManager.js +0 -357
  72. package/src/fonts/bmFontMetrics.js +0 -108
  73. package/src/genome/genome.js +0 -317
  74. package/src/genome/genome.test.js +0 -188
  75. package/src/genome/genomeStore.js +0 -54
  76. package/src/genome/locusFormat.js +0 -31
  77. package/src/genome/scaleIndex.d.ts +0 -38
  78. package/src/genome/scaleIndex.js +0 -166
  79. package/src/genome/scaleIndex.test.js +0 -78
  80. package/src/genome/scaleLocus.d.ts +0 -11
  81. package/src/genome/scaleLocus.js +0 -108
  82. package/src/genome/scaleLocus.test.js +0 -4
  83. package/src/genomeSpy.js +0 -785
  84. package/src/gl/arrayBuilder.js +0 -199
  85. package/src/gl/dataToVertices.js +0 -636
  86. package/src/gl/includes/common.glsl +0 -63
  87. package/src/gl/includes/picking.fragment.glsl +0 -1
  88. package/src/gl/includes/picking.vertex.glsl +0 -27
  89. package/src/gl/includes/sampleFacet.glsl +0 -107
  90. package/src/gl/includes/scales.glsl +0 -112
  91. package/src/gl/link.fragment.glsl +0 -18
  92. package/src/gl/link.vertex.glsl +0 -111
  93. package/src/gl/point.fragment.glsl +0 -123
  94. package/src/gl/point.vertex.glsl +0 -129
  95. package/src/gl/rect.fragment.glsl +0 -51
  96. package/src/gl/rect.vertex.glsl +0 -114
  97. package/src/gl/rule.fragment.glsl +0 -52
  98. package/src/gl/rule.vertex.glsl +0 -89
  99. package/src/gl/text.fragment.glsl +0 -31
  100. package/src/gl/text.vertex.glsl +0 -246
  101. package/src/gl/webGLHelper.js +0 -504
  102. package/src/img/bowtie.svg +0 -1
  103. package/src/img/genomespy-favicon.svg +0 -34
  104. package/src/index.html +0 -11
  105. package/src/index.js +0 -128
  106. package/src/marks/link.js +0 -175
  107. package/src/marks/mark.js +0 -975
  108. package/src/marks/markUtils.js +0 -125
  109. package/src/marks/pointMark.js +0 -251
  110. package/src/marks/rectMark.js +0 -241
  111. package/src/marks/rule.js +0 -250
  112. package/src/marks/text.js +0 -278
  113. package/src/node_modules/.vitest/results.json +0 -1
  114. package/src/scale/colorUtils.js +0 -184
  115. package/src/scale/glslScaleGenerator.js +0 -502
  116. package/src/scale/scale.js +0 -451
  117. package/src/scale/scale.test.js +0 -324
  118. package/src/scale/ticks.js +0 -203
  119. package/src/scale/ticks.test.js +0 -40
  120. package/src/singlePageApp.js +0 -13
  121. package/src/spec/axis.d.ts +0 -296
  122. package/src/spec/channel.d.ts +0 -430
  123. package/src/spec/data.d.ts +0 -196
  124. package/src/spec/font.d.ts +0 -15
  125. package/src/spec/genome.d.ts +0 -35
  126. package/src/spec/mark.d.ts +0 -429
  127. package/src/spec/root.d.ts +0 -17
  128. package/src/spec/sampleView.d.ts +0 -180
  129. package/src/spec/scale.d.ts +0 -273
  130. package/src/spec/title.d.ts +0 -102
  131. package/src/spec/tooltip.d.ts +0 -9
  132. package/src/spec/transform.d.ts +0 -479
  133. package/src/spec/view.d.ts +0 -201
  134. package/src/styles/genome-spy.scss +0 -153
  135. package/src/tooltip/dataTooltipHandler.js +0 -64
  136. package/src/tooltip/refseqGeneTooltipHandler.js +0 -78
  137. package/src/tooltip/tooltipHandler.ts +0 -12
  138. package/src/types/filetypes.d.ts +0 -14
  139. package/src/types/flatqueue.d.ts +0 -53
  140. package/src/types/glsl.d.ts +0 -4
  141. package/src/types/internmap.d.ts +0 -22
  142. package/src/types/object.d.ts +0 -21
  143. package/src/types/vega-loader.d.ts +0 -1
  144. package/src/types/vega-scale.d.ts +0 -60
  145. package/src/utils/addBaseUrl.js +0 -19
  146. package/src/utils/addBaseUrl.test.js +0 -22
  147. package/src/utils/animator.js +0 -83
  148. package/src/utils/arrayUtils.js +0 -61
  149. package/src/utils/binnedIndex.js +0 -167
  150. package/src/utils/binnedIndex.test.js +0 -155
  151. package/src/utils/clamp.js +0 -8
  152. package/src/utils/cloner.js +0 -34
  153. package/src/utils/cloner.test.js +0 -24
  154. package/src/utils/coalesce.js +0 -11
  155. package/src/utils/coalesce.test.js +0 -16
  156. package/src/utils/concatIterables.js +0 -26
  157. package/src/utils/concatIterables.test.js +0 -8
  158. package/src/utils/debounce.js +0 -37
  159. package/src/utils/domainArray.js +0 -216
  160. package/src/utils/domainArray.test.js +0 -130
  161. package/src/utils/eerp.js +0 -13
  162. package/src/utils/expression.js +0 -32
  163. package/src/utils/field.js +0 -28
  164. package/src/utils/formatObject.js +0 -31
  165. package/src/utils/indexer.js +0 -43
  166. package/src/utils/indexer.test.js +0 -47
  167. package/src/utils/inertia.js +0 -124
  168. package/src/utils/interactionEvent.js +0 -33
  169. package/src/utils/iterateNestedMaps.js +0 -21
  170. package/src/utils/iterateNestedMaps.test.js +0 -33
  171. package/src/utils/kWayMerge.js +0 -42
  172. package/src/utils/kWayMerge.test.js +0 -26
  173. package/src/utils/layout/flexLayout.js +0 -368
  174. package/src/utils/layout/flexLayout.test.js +0 -311
  175. package/src/utils/layout/grid.js +0 -95
  176. package/src/utils/layout/grid.test.js +0 -71
  177. package/src/utils/layout/padding.js +0 -120
  178. package/src/utils/layout/point.js +0 -23
  179. package/src/utils/layout/rectangle.js +0 -288
  180. package/src/utils/layout/rectangle.test.js +0 -172
  181. package/src/utils/mergeObjects.js +0 -99
  182. package/src/utils/mergeObjects.test.js +0 -42
  183. package/src/utils/numberExtractor.js +0 -24
  184. package/src/utils/numberExtractor.test.js +0 -6
  185. package/src/utils/point.js +0 -14
  186. package/src/utils/propertyCacher.js +0 -70
  187. package/src/utils/propertyCacher.test.js +0 -85
  188. package/src/utils/propertyCoalescer.js +0 -42
  189. package/src/utils/propertyCoalescer.test.js +0 -22
  190. package/src/utils/reservationMap.js +0 -103
  191. package/src/utils/reservationMap.test.js +0 -20
  192. package/src/utils/scaleNull.js +0 -19
  193. package/src/utils/setOperations.js +0 -75
  194. package/src/utils/smoothstep.js +0 -10
  195. package/src/utils/throttle.js +0 -34
  196. package/src/utils/topK.js +0 -76
  197. package/src/utils/topK.test.js +0 -64
  198. package/src/utils/transition.js +0 -74
  199. package/src/utils/ui/tooltip.js +0 -189
  200. package/src/utils/url.js +0 -22
  201. package/src/utils/variableTools.js +0 -24
  202. package/src/utils/variableTools.test.js +0 -13
  203. package/src/view/axisResolution.js +0 -140
  204. package/src/view/axisResolution.test.js +0 -201
  205. package/src/view/axisView.js +0 -747
  206. package/src/view/concatView.js +0 -45
  207. package/src/view/containerView.js +0 -159
  208. package/src/view/facetView.js +0 -491
  209. package/src/view/flowBuilder.js +0 -367
  210. package/src/view/flowBuilder.test.js +0 -125
  211. package/src/view/gridView.js +0 -786
  212. package/src/view/implicitRootView.js +0 -14
  213. package/src/view/importView.js +0 -19
  214. package/src/view/layerView.js +0 -74
  215. package/src/view/rendering.d.ts +0 -44
  216. package/src/view/renderingContext/compositeViewRenderingContext.js +0 -51
  217. package/src/view/renderingContext/deferredViewRenderingContext.js +0 -176
  218. package/src/view/renderingContext/layoutRecorderViewRenderingContext.js +0 -128
  219. package/src/view/renderingContext/simpleViewRenderingContext.js +0 -64
  220. package/src/view/renderingContext/svgViewRenderingContext.js +0 -125
  221. package/src/view/renderingContext/viewRenderingContext.js +0 -41
  222. package/src/view/scaleResolution.js +0 -797
  223. package/src/view/scaleResolution.test.js +0 -572
  224. package/src/view/scaleResolutionApi.d.ts +0 -40
  225. package/src/view/testUtils.js +0 -51
  226. package/src/view/title.js +0 -165
  227. package/src/view/unitView.js +0 -382
  228. package/src/view/view.js +0 -612
  229. package/src/view/view.test.js +0 -214
  230. package/src/view/viewContext.d.ts +0 -62
  231. package/src/view/viewFactory.js +0 -181
  232. package/src/view/viewFactory.test.js +0 -17
  233. package/src/view/viewUtils.js +0 -327
  234. package/src/view/zoom.js +0 -89
@@ -1,786 +0,0 @@
1
- /* eslint-disable max-depth */
2
- import {
3
- FlexDimensions,
4
- getLargestSize,
5
- mapToPixelCoords,
6
- parseSizeDef,
7
- ZERO_SIZEDEF,
8
- } from "../utils/layout/flexLayout";
9
- import Grid from "../utils/layout/grid";
10
- import Padding from "../utils/layout/padding";
11
- import Rectangle from "../utils/layout/rectangle";
12
- import AxisView, { CHANNEL_ORIENTS } from "./axisView";
13
- import ContainerView from "./containerView";
14
- import LayerView from "./layerView";
15
- import createTitle from "./title";
16
- import UnitView from "./unitView";
17
- import interactionToZoom from "./zoom";
18
-
19
- /**
20
- * @typedef {"row" | "column"} Direction
21
- * @typedef {import("./view").default} View
22
- *
23
- * @typedef {object} GridChild
24
- * @prop {View} view
25
- * @prop {UnitView} [background]
26
- * @prop {Partial<Record<import("../spec/axis").AxisOrient, AxisView>>} axes
27
- * @prop {UnitView} [title]
28
- * @prop {Rectangle} coords Coordinates of the view. Recorded for mouse tracking, etc.
29
- */
30
-
31
- /**
32
- * Modeled after: https://vega.github.io/vega/docs/layout/
33
- *
34
- * This should take care of the following:
35
- * - Composition: [hv]concat / facet / repeat
36
- * - Views
37
- * - Axes
38
- * - Grid lines
39
- * - View background
40
- * - View titles
41
- * - Facet (column / row) titles
42
- * - Header / footer
43
- * - Zoom / pan
44
- * - And later on, brushing, legend(?)
45
- */
46
- export default class GridView extends ContainerView {
47
- #columns = Infinity;
48
-
49
- #spacing = 10;
50
-
51
- /**
52
- * @type { GridChild[] }
53
- */
54
- #children = [];
55
-
56
- #childSerial = 0;
57
-
58
- /**
59
- *
60
- * @param {import("./viewUtils").AnyConcatSpec} spec
61
- * @param {import("./viewUtils").ViewContext} context
62
- * @param {ContainerView} parent
63
- * @param {string} name
64
- * @param {number} columns
65
- */
66
- constructor(spec, context, parent, name, columns) {
67
- super(spec, context, parent, name);
68
- this.spec = spec;
69
-
70
- this.#spacing = spec.spacing ?? 10;
71
- this.#columns = columns;
72
-
73
- this.#children = [];
74
-
75
- this.wrappingFacet = false;
76
-
77
- this._createChildren();
78
- }
79
-
80
- _createChildren() {
81
- // Override
82
- }
83
-
84
- /**
85
- * @param {View} view
86
- */
87
- #makeGridChild(view) {
88
- /** @type {GridChild} */
89
- const gridChild = {
90
- view,
91
- background: undefined,
92
- axes: {},
93
- coords: Rectangle.ZERO,
94
- };
95
-
96
- if (view instanceof UnitView || view instanceof LayerView) {
97
- /** @type {import("../spec/view").ViewBackground} */
98
- const viewBackground = view.spec?.view;
99
- if (viewBackground?.fill || viewBackground?.stroke) {
100
- const unitView = new UnitView(
101
- createBackground(viewBackground),
102
- this.context,
103
- view,
104
- "background" + this.#childSerial
105
- );
106
- // TODO: Make configurable through spec:
107
- unitView.blockEncodingInheritance = true;
108
- gridChild.background = unitView;
109
- }
110
-
111
- const title = createTitle(view.spec.title);
112
- if (title) {
113
- const unitView = new UnitView(
114
- title,
115
- this.context,
116
- view,
117
- "title" + this.#childSerial
118
- );
119
- // TODO: Make configurable through spec:
120
- unitView.blockEncodingInheritance = true;
121
- gridChild.title = unitView;
122
- }
123
- }
124
-
125
- return gridChild;
126
- }
127
-
128
- /**
129
- * @param {View} view
130
- */
131
- appendChild(view) {
132
- view.parent ??= this;
133
- this.#children.push(this.#makeGridChild(view));
134
- this.#childSerial++;
135
- }
136
-
137
- get #visibleChildren() {
138
- return this.#children.filter((gridChild) => gridChild.view.isVisible());
139
- }
140
-
141
- get #grid() {
142
- return new Grid(
143
- this.#visibleChildren.length,
144
- this.#columns ?? Infinity
145
- );
146
- }
147
-
148
- /**
149
- * @param {View[]} views
150
- */
151
- setChildren(views) {
152
- //this.#children = []; // TODO: Check why this breaks summary track
153
- for (const view of views) {
154
- this.appendChild(view);
155
- }
156
- }
157
-
158
- /**
159
- * @param {import("./view").default} child
160
- * @param {import("./view").default} replacement
161
- */
162
- replaceChild(child, replacement) {
163
- const i = this.#children.findIndex(
164
- (gridChild) => gridChild.view == child
165
- );
166
- if (i >= 0) {
167
- this.#children[i] = this.#makeGridChild(replacement);
168
- } else {
169
- throw new Error("Not my child view!");
170
- }
171
- }
172
-
173
- /**
174
- * Read-only view to children
175
- */
176
- get children() {
177
- return this.#children.map((gridChild) => gridChild.view);
178
- }
179
-
180
- get childCount() {
181
- return this.#children.length;
182
- }
183
-
184
- onScalesResolved() {
185
- super.onScalesResolved();
186
-
187
- this.#createAxes();
188
- }
189
-
190
- #createAxes() {
191
- if (Object.keys(this.resolutions.axis).length) {
192
- throw new Error(
193
- "ConcatView does not (currently) support shared axes!"
194
- );
195
- }
196
-
197
- // Create axes
198
- for (const gridChild of this.#children) {
199
- const { view, axes } = gridChild;
200
-
201
- /**
202
- * @param {import("./view").AxisResolution} r
203
- * @param {import("../spec/channel").PrimaryPositionalChannel} channel
204
- * @param {UnitView | LayerView} axisParent
205
- */
206
- const createAxis = (r, channel, axisParent) => {
207
- const props = r.getAxisProps();
208
- if (props === null) {
209
- return;
210
- }
211
-
212
- // Pick a default orient based on what is available
213
- if (!props.orient) {
214
- for (const orient of CHANNEL_ORIENTS[channel]) {
215
- if (!axes[orient]) {
216
- props.orient = orient;
217
- break;
218
- }
219
- }
220
- if (!props.orient) {
221
- throw new Error(
222
- "No slots available for an axis! Perhaps a LayerView has more than two children?"
223
- );
224
- }
225
- }
226
-
227
- props.title ??= r.getTitle();
228
-
229
- if (!CHANNEL_ORIENTS[channel].includes(props.orient)) {
230
- throw new Error(
231
- `Invalid axis orientation "${props.orient}" on channel "${channel}"!`
232
- );
233
- }
234
-
235
- if (axes[props.orient]) {
236
- throw new Error(
237
- `An axis with the orient "${props.orient}" already exists!`
238
- );
239
- }
240
-
241
- axes[props.orient] = new AxisView(
242
- props,
243
- r.scaleResolution.type,
244
- this.context,
245
- // Note: Axisview has a unit/layerView as parent so that scale/axis resolutions are inherited correctly
246
- axisParent
247
- );
248
- };
249
-
250
- // Handle shared axes
251
- if (view instanceof UnitView || view instanceof LayerView) {
252
- for (const channel of /** @type {import("../spec/channel").PrimaryPositionalChannel[]} */ ([
253
- "x",
254
- "y",
255
- ])) {
256
- const r = view.resolutions.axis[channel];
257
- if (!r) {
258
- continue;
259
- }
260
-
261
- createAxis(r, channel, view);
262
- }
263
- }
264
-
265
- // Handle LayerView's possible independent axes
266
- if (view instanceof LayerView) {
267
- // First create axes that have an orient preference
268
- for (const layerChild of view.children) {
269
- for (const [channel, r] of Object.entries(
270
- layerChild.resolutions.axis
271
- )) {
272
- const props = r.getAxisProps();
273
- if (props && props.orient) {
274
- createAxis(r, channel, layerChild);
275
- }
276
- }
277
- }
278
-
279
- // Then create axes in a priority order
280
- for (const layerChild of view.children) {
281
- for (const [channel, r] of Object.entries(
282
- layerChild.resolutions.axis
283
- )) {
284
- const props = r.getAxisProps();
285
- if (props && !props.orient) {
286
- createAxis(r, channel, layerChild);
287
- }
288
- }
289
- }
290
- }
291
- }
292
- }
293
-
294
- /**
295
- * @returns {IterableIterator<View>}
296
- */
297
- *[Symbol.iterator]() {
298
- for (const gridChild of this.#children) {
299
- if (gridChild.background) {
300
- yield gridChild.background;
301
- }
302
-
303
- for (const axisView of Object.values(gridChild.axes)) {
304
- if (axisView) {
305
- yield axisView;
306
- }
307
- }
308
-
309
- yield gridChild.view;
310
-
311
- if (gridChild.title) {
312
- yield gridChild.title;
313
- }
314
- }
315
- }
316
-
317
- /**
318
- * @param {Direction} direction
319
- */
320
- #getSizes(direction) {
321
- /** @type {import("../spec/axis").AxisOrient[]} */
322
- const orients =
323
- direction == "column" ? ["left", "right"] : ["top", "bottom"];
324
-
325
- const dim = direction == "column" ? "width" : "height";
326
-
327
- /**
328
- * @type {(indices: number[], side: 0 | 1) => number}
329
- */
330
- const getMaxAxisSize = (indices, side) =>
331
- indices
332
- .map((index) => {
333
- // Axis view is only present for unit and layer views
334
- const axisView =
335
- this.#visibleChildren[index].axes[orients[side]];
336
- if (axisView) {
337
- return Math.max(
338
- axisView.getPerpendicularSize() +
339
- axisView.axisProps.offset ?? 0,
340
- 0
341
- );
342
- }
343
-
344
- // For views other than unit or layer, use overhang instead
345
- const overhang =
346
- this.#visibleChildren[index].view.getOverhang();
347
- if (direction == "column") {
348
- return side ? overhang.right : overhang.left;
349
- } else {
350
- return side ? overhang.bottom : overhang.top;
351
- }
352
- })
353
- .reduce((a, b) => Math.max(a, b), 0);
354
-
355
- return this.#grid[
356
- direction == "column" ? "colIndices" : "rowIndices"
357
- ].map((col) => ({
358
- axisBefore: getMaxAxisSize(col, 0),
359
- axisAfter: getMaxAxisSize(col, 1),
360
- view: getLargestSize(
361
- col.map(
362
- (rowIndex) =>
363
- this.#visibleChildren[rowIndex].view.getSize()[dim]
364
- )
365
- ),
366
- }));
367
- }
368
-
369
- /**
370
- * An example layout with two children, either column or row-based direction:
371
- *
372
- * 0. title
373
- * 1. header
374
- * 2. axis/padding
375
- * 3. view
376
- * 4. axis/padding
377
- * 5. footer (if column and wrapping)
378
- * 5. spacing
379
- * 6. header (if column and wrapping)
380
- * 7. axis/padding
381
- * 8. view
382
- * 9. axis/padding
383
- * 10. footer
384
- *
385
- * @param {Direction} direction
386
- */
387
- #makeFlexItems(direction) {
388
- const sizes = this.#getSizes(direction);
389
-
390
- /** @type {import("../utils/layout/flexLayout").SizeDef[]} */
391
- const items = [];
392
-
393
- // Title
394
- items.push(ZERO_SIZEDEF);
395
-
396
- for (const [i, size] of sizes.entries()) {
397
- if (i > 0) {
398
- // Spacing
399
- items.push({ px: this.#spacing, grow: 0 });
400
- }
401
-
402
- if (i == 0 || this.wrappingFacet) {
403
- // Header
404
- items.push(ZERO_SIZEDEF);
405
- }
406
-
407
- // Axis/padding
408
- items.push({ px: size.axisBefore, grow: 0 });
409
-
410
- // View
411
- items.push(size.view);
412
-
413
- // Axis/padding
414
- items.push({ px: size.axisAfter, grow: 0 });
415
-
416
- if (i == sizes.length - 1 || this.wrappingFacet) {
417
- //Footer
418
- items.push(ZERO_SIZEDEF);
419
- }
420
- }
421
-
422
- return items;
423
- }
424
-
425
- /**
426
- * @param {Direction} direction
427
- * @return {import("../utils/layout/flexLayout").SizeDef}
428
- */
429
- #getFlexSize(direction) {
430
- let grow = 0;
431
- let px = 0;
432
-
433
- const explicitSize =
434
- (direction == "row" && this.spec.height) ??
435
- (direction == "column" && this.spec.width);
436
- if (explicitSize || explicitSize === 0) {
437
- return parseSizeDef(explicitSize);
438
- }
439
-
440
- const sizes = this.#getSizes(direction);
441
-
442
- for (const [i, size] of sizes.entries()) {
443
- if (i > 0) {
444
- // Spacing
445
- px += this.#spacing;
446
- }
447
-
448
- if (i == 0 || this.wrappingFacet) {
449
- // Header
450
- px += 0;
451
- }
452
-
453
- // Axis/padding
454
- px += size.axisBefore;
455
-
456
- // View
457
- px += size.view.px ?? 0;
458
- grow += size.view.grow ?? 0;
459
-
460
- // Axis/padding
461
- px += size.axisAfter;
462
-
463
- if (i == sizes.length - 1 || this.wrappingFacet) {
464
- //Footer
465
- px += 0;
466
- }
467
- }
468
-
469
- return { px, grow };
470
- }
471
-
472
- /**
473
- * Locates a view slot in FlexLayout
474
- *
475
- * @param {Direction} direction
476
- * @param {number} index column/row number
477
- */
478
- #getViewSlot(direction, index) {
479
- return direction == "row" && this.wrappingFacet
480
- ? // Views have header/footer on every row
481
- 1 + 6 * index + 2
482
- : // Only first row has header, last row has footer.
483
- 2 + 4 * index + 1;
484
- }
485
-
486
- /**
487
- * @return {Padding}
488
- */
489
- getOverhang() {
490
- const cols = this.#getSizes("column");
491
- const rows = this.#getSizes("row");
492
-
493
- if (!cols.length || !rows.length) {
494
- return Padding.zero();
495
- }
496
-
497
- const p = new Padding(
498
- rows.at(0).axisBefore,
499
- cols.at(-1).axisAfter,
500
- rows.at(-1).axisAfter,
501
- cols.at(0).axisBefore
502
- );
503
- return p;
504
- }
505
-
506
- /**
507
- * @returns {FlexDimensions}
508
- */
509
- getSize() {
510
- return this._cache("size", () =>
511
- new FlexDimensions(
512
- this.#getFlexSize("column"),
513
- this.#getFlexSize("row")
514
- )
515
- .subtractPadding(this.getOverhang())
516
- .addPadding(this.getPadding())
517
- );
518
- }
519
-
520
- /**
521
- * @param {import("./renderingContext/viewRenderingContext").default} context
522
- * @param {import("../utils/layout/rectangle").default} coords
523
- * @param {import("./view").RenderingOptions} [options]
524
- */
525
- render(context, coords, options = {}) {
526
- if (!this.isVisible()) {
527
- return;
528
- }
529
-
530
- if (!this.parent) {
531
- // Usually padding is applied by the parent GridView, but if this is the root view, we need to apply it here
532
- coords = coords.shrink(this.getPadding());
533
- }
534
-
535
- context.pushView(this, coords);
536
-
537
- const flexOpts = {
538
- devicePixelRatio: this.context.glHelper.dpr,
539
- };
540
- const columnFlexCoords = mapToPixelCoords(
541
- this.#makeFlexItems("column"),
542
- coords.width,
543
- flexOpts
544
- );
545
-
546
- const rowFlexCoords = mapToPixelCoords(
547
- this.#makeFlexItems("row"),
548
- coords.height,
549
- flexOpts
550
- );
551
-
552
- const grid = new Grid(
553
- this.#visibleChildren.length,
554
- this.#columns ?? Infinity
555
- );
556
-
557
- for (const [i, gridChild] of this.#visibleChildren.entries()) {
558
- const { view, axes, background, title } = gridChild;
559
-
560
- const [col, row] = grid.getCellCoords(i);
561
- const colLocSize =
562
- columnFlexCoords[this.#getViewSlot("column", col)];
563
- const rowLocSize = rowFlexCoords[this.#getViewSlot("row", row)];
564
-
565
- const viewSize = view.getSize();
566
- const viewPadding = view.getPadding().subtract(view.getOverhang());
567
-
568
- const x = colLocSize.location + viewPadding.left;
569
- const y = rowLocSize.location + viewPadding.top;
570
-
571
- const width =
572
- (viewSize.width.grow ? colLocSize.size : viewSize.width.px) -
573
- viewPadding.width;
574
- const height =
575
- (viewSize.height.grow ? rowLocSize.size : viewSize.height.px) -
576
- viewPadding.height;
577
-
578
- const childCoords = new Rectangle(
579
- () => coords.x + x,
580
- () => coords.y + y,
581
- () => width,
582
- () => height
583
- );
584
-
585
- gridChild.coords = childCoords;
586
-
587
- background?.render(context, childCoords, options);
588
-
589
- // If clipped, the axes should be drawn on top of the marks (because clipping may not be pixel-perfect)
590
- const clipped = isClippedChildren(view);
591
- if (clipped) {
592
- view.render(context, childCoords, options);
593
- }
594
-
595
- for (const [orient, axisView] of Object.entries(axes)) {
596
- const props = axisView.axisProps;
597
-
598
- /** @type {import("../utils/layout/rectangle").default} */
599
- let axisCoords;
600
-
601
- const ps = axisView.getPerpendicularSize();
602
-
603
- if (orient == "bottom") {
604
- axisCoords = childCoords
605
- .translate(0, childCoords.height + props.offset)
606
- .modify({ height: ps });
607
- } else if (orient == "top") {
608
- axisCoords = childCoords
609
- .translate(0, -ps - props.offset)
610
- .modify({ height: ps });
611
- } else if (orient == "left") {
612
- axisCoords = childCoords
613
- .translate(-ps - props.offset, 0)
614
- .modify({ width: ps });
615
- } else if (orient == "right") {
616
- axisCoords = childCoords
617
- .translate(childCoords.width + props.offset, 0)
618
- .modify({ width: ps });
619
- }
620
-
621
- // Axes have no faceted data, thus, pass undefined facetId
622
- axisView.render(context, axisCoords);
623
- }
624
-
625
- if (!clipped) {
626
- view.render(context, childCoords, options);
627
- }
628
-
629
- title?.render(context, childCoords, {
630
- ...options,
631
- clipRect: undefined, // Hack for SampleAttributePanel. TODO: Proper fix
632
- });
633
- }
634
-
635
- context.popView(this);
636
- }
637
-
638
- /**
639
- * @param {import("../utils/interactionEvent").default} event
640
- */
641
- propagateInteractionEvent(event) {
642
- this.handleInteractionEvent(undefined, event, true);
643
-
644
- if (event.stopped) {
645
- return;
646
- }
647
-
648
- const pointedChild = this.#visibleChildren.find((gridChild) =>
649
- gridChild.coords.containsPoint(event.point.x, event.point.y)
650
- );
651
- const pointedView = pointedChild?.view;
652
- if (pointedView) {
653
- pointedView.propagateInteractionEvent(event);
654
-
655
- if (
656
- pointedView instanceof UnitView ||
657
- pointedView instanceof LayerView
658
- ) {
659
- interactionToZoom(
660
- event,
661
- pointedChild.coords,
662
- (zoomEvent) =>
663
- this.#handleZoom(
664
- pointedChild.coords,
665
- pointedChild.view,
666
- zoomEvent
667
- ),
668
- this.context.getCurrentHover()
669
- );
670
- }
671
- }
672
-
673
- if (event.stopped) {
674
- return;
675
- }
676
-
677
- this.handleInteractionEvent(undefined, event, false);
678
- }
679
-
680
- /**
681
- *
682
- * @param {import("../utils/layout/rectangle").default} coords Coordinates
683
- * @param {View} view
684
- * @param {import("./zoom").ZoomEvent} zoomEvent
685
- */
686
- #handleZoom(coords, view, zoomEvent) {
687
- for (const [channel, resolutionSet] of Object.entries(
688
- getZoomableResolutions(view)
689
- )) {
690
- if (resolutionSet.size <= 0) {
691
- continue;
692
- }
693
-
694
- const p = coords.normalizePoint(zoomEvent.x, zoomEvent.y);
695
- const tp = coords.normalizePoint(
696
- zoomEvent.x + zoomEvent.xDelta,
697
- zoomEvent.y + zoomEvent.yDelta
698
- );
699
-
700
- const delta = {
701
- x: tp.x - p.x,
702
- y: tp.y - p.y,
703
- };
704
-
705
- for (const resolution of resolutionSet) {
706
- resolution.zoom(
707
- 2 ** zoomEvent.zDelta,
708
- channel == "y" ? 1 - p[channel] : p[channel],
709
- channel == "x" ? delta.x : -delta.y
710
- );
711
- }
712
- }
713
-
714
- this.context.animator.requestRender();
715
- }
716
-
717
- /**
718
- * @param {string} channel
719
- * @param {import("./containerView").ResolutionTarget} resolutionType
720
- * @returns {import("../spec/view").ResolutionBehavior}
721
- */
722
- getDefaultResolution(channel, resolutionType) {
723
- // TODO: Default to shared when working with genomic coordinates
724
- return "independent";
725
- }
726
- }
727
-
728
- /**
729
- * @param {import("../spec/view").ViewBackground} viewBackground
730
- * @returns {import("../spec/view").UnitSpec}
731
- */
732
- export function createBackground(viewBackground) {
733
- return {
734
- configurableVisibility: false,
735
- data: { values: [{}] },
736
- mark: {
737
- fill: null,
738
- strokeWidth: 1.0,
739
- fillOpacity: viewBackground.fill ? 1.0 : 0, // TODO: This should be handled at lower level
740
- ...viewBackground,
741
- type: "rect",
742
- clip: false, // Shouldn't be needed
743
- tooltip: null,
744
- },
745
- };
746
- }
747
-
748
- /**
749
- *
750
- * @param {View} view
751
- * @returns
752
- */
753
- function getZoomableResolutions(view) {
754
- /** @type {Record<import("../spec/channel").PrimaryPositionalChannel, Set<import("./scaleResolution").default>>} */
755
- const resolutions = {
756
- x: new Set(),
757
- y: new Set(),
758
- };
759
-
760
- // Find all resolutions (scales) that are candidates for zooming
761
- view.visit((v) => {
762
- for (const [channel, resolutionSet] of Object.entries(resolutions)) {
763
- const resolution = v.getScaleResolution(channel);
764
- if (resolution && resolution.isZoomable()) {
765
- resolutionSet.add(resolution);
766
- }
767
- }
768
- });
769
-
770
- return resolutions;
771
- }
772
-
773
- /**
774
- * @param {View} view
775
- */
776
- export function isClippedChildren(view) {
777
- let clipped = true;
778
-
779
- view.visit((v) => {
780
- if (v instanceof UnitView) {
781
- clipped &&= v.mark.properties.clip;
782
- }
783
- });
784
-
785
- return clipped;
786
- }