@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,488 @@
1
+ import { isFacetFieldDef, isFacetMapping } from "./viewUtils";
2
+ import ContainerView from "./containerView";
3
+ import UnitView from "./unitView";
4
+ import { cross } from "d3-array";
5
+ import { mapToPixelCoords } from "../utils/layout/flexLayout";
6
+ import { OrdinalDomain } from "../utils/domainArray";
7
+ import Rectangle from "../utils/layout/rectangle";
8
+ import coalesce from "../utils/coalesce";
9
+ import { field as vegaField } from "vega-util";
10
+ import DecoratorView from "./decoratorView";
11
+ import Padding from "../utils/layout/padding";
12
+
13
+ const DEFAULT_SPACING = 20;
14
+
15
+ /**
16
+ * @typedef {"column" | "row"} FacetChannel
17
+ * @type {FacetChannel[]}
18
+ */
19
+ const FACET_CHANNELS = ["column", "row"];
20
+
21
+ /**
22
+ * @type {Record<FacetChannel, FacetChannel>}
23
+ */
24
+ // eslint-disable-next-line no-unused-vars
25
+ const PERPENDICULAR_FACET_CHANNELS = {
26
+ column: "row",
27
+ row: "column",
28
+ };
29
+
30
+ // https://vega.github.io/vega-lite/docs/header.html#labels
31
+ // TODO: Configurable
32
+ const headerConfig = {
33
+ labelFontSize: 12,
34
+ labelColor: "black",
35
+ };
36
+
37
+ /** @type {Record<FacetChannel, any>} */
38
+ const headerConfigs = {
39
+ column: {
40
+ ...headerConfig,
41
+ labelAngle: 0,
42
+ },
43
+ row: {
44
+ ...headerConfig,
45
+ labelAngle: -90,
46
+ },
47
+ };
48
+
49
+ /**
50
+ * Implements (a subset of) the Vega-Lite's Facet-operator:
51
+ * https://vega.github.io/vega-lite/docs/facet.html
52
+ *
53
+ * TODO:
54
+ * - Facet channel titles
55
+ * - Suppress redundant axes
56
+ * - Make this thing configurable
57
+ *
58
+ * @typedef {import("./view").default} View
59
+ * @typedef {import("./layerView").default} LayerView
60
+ * @typedef {import("./viewUtils").FacetFieldDef} FacetFieldDef
61
+ * @typedef {import("./viewUtils").FacetMapping} FacetMapping
62
+ * @typedef {import("../utils/layout/flexLayout").LocSize} LocSize
63
+ * @typedef {import("../utils/layout/flexLayout").SizeDef} SizeDef
64
+ *
65
+ * @typedef {object} FacetDimension Stuff for working with facet dimensions
66
+ * @prop {function} accessor
67
+ * @prop {boolean[] | string[] | number[]} factors
68
+ * @prop {FacetFieldDef} facetFieldDef
69
+ *
70
+ */
71
+ export default class FacetView extends ContainerView {
72
+ /**
73
+ *
74
+ * @param {import("./viewUtils").FacetSpec} spec
75
+ * @param {import("./viewUtils").ViewContext} context
76
+ * @param {ContainerView} parent
77
+ * @param {string} name
78
+ */
79
+ constructor(spec, context, parent, name) {
80
+ super(spec, context, parent, name);
81
+
82
+ this.spec = spec;
83
+
84
+ this.child = /** @type { UnitView | LayerView | DecoratorView } */ (
85
+ context.createView(spec.spec, this, `facet`)
86
+ );
87
+
88
+ /**
89
+ * Faceted views for displaying the facet labels
90
+ *
91
+ * @type {Record<FacetChannel, UnitView>}
92
+ */
93
+ this._labelViews = Object.fromEntries(
94
+ FACET_CHANNELS.map((channel) => [
95
+ channel,
96
+ new UnitView(
97
+ createLabelViewSpec(headerConfigs[channel]),
98
+ this.context,
99
+ this,
100
+ `facetLabel-${channel}`
101
+ ),
102
+ ])
103
+ );
104
+
105
+ /** @type {Record<FacetChannel, FacetDimension>} */
106
+ this._facetDimensions = { column: undefined, row: undefined };
107
+ }
108
+
109
+ /**
110
+ * @returns {IterableIterator<View>}
111
+ */
112
+ *[Symbol.iterator]() {
113
+ yield this.child;
114
+ for (const view of Object.values(this._labelViews)) {
115
+ yield view;
116
+ }
117
+ }
118
+
119
+ /**
120
+ * @param {import("./view").default} child
121
+ * @param {import("./view").default} replacement
122
+ */
123
+ replaceChild(child, replacement) {
124
+ if (child !== this.child) {
125
+ throw new Error("Not my child!");
126
+ }
127
+
128
+ this.child = /** @type {UnitView | LayerView | DecoratorView} */ (
129
+ replacement
130
+ );
131
+ }
132
+
133
+ transformData() {
134
+ super.transformData();
135
+ // A hacky solution for updating facets. TODO: Something more robust.
136
+ this.updateFacets();
137
+ }
138
+
139
+ /**
140
+ *
141
+ * @param {"row" | "column"} channel
142
+ */
143
+ getAccessor(channel) {
144
+ /** @type {import("./viewUtils").FacetMapping} */
145
+ let facetMapping;
146
+ if (isFacetMapping(this.spec.facet)) {
147
+ facetMapping = this.spec.facet; // Mark provides encodings with defaults and possible modifications
148
+ } else if (isFacetFieldDef(this.spec.facet)) {
149
+ // TODO: Check "columns"
150
+ facetMapping = {
151
+ column: this.spec.facet,
152
+ };
153
+ } else {
154
+ throw new Error(
155
+ "Invalid facet specification: " +
156
+ JSON.stringify(this.spec.facet)
157
+ );
158
+ }
159
+
160
+ if (facetMapping[channel]) {
161
+ return this.context.accessorFactory.createAccessor(
162
+ facetMapping[channel]
163
+ );
164
+ }
165
+ }
166
+
167
+ updateFacets() {
168
+ /** @type {import("./viewUtils").FacetMapping} */
169
+ let facetMapping;
170
+ if (isFacetMapping(this.spec.facet)) {
171
+ facetMapping = this.spec.facet; // Mark provides encodings with defaults and possible modifications
172
+ } else if (isFacetFieldDef(this.spec.facet)) {
173
+ // TODO: Check "columns"
174
+ facetMapping = {
175
+ column: this.spec.facet,
176
+ };
177
+ } else {
178
+ throw new Error(
179
+ "Invalid facet specification: " +
180
+ JSON.stringify(this.spec.facet)
181
+ );
182
+ }
183
+
184
+ for (const channel of FACET_CHANNELS) {
185
+ const facetFieldDef = facetMapping[channel];
186
+ if (!facetFieldDef) {
187
+ continue;
188
+ }
189
+
190
+ const accessor = this.context.accessorFactory.createAccessor(
191
+ facetMapping[channel]
192
+ );
193
+
194
+ const factors = new OrdinalDomain().extendAllWithAccessor(
195
+ this.getData(),
196
+ accessor
197
+ );
198
+
199
+ // TODO: Configurable sorting
200
+ factors.sort();
201
+
202
+ this._facetDimensions[channel] = {
203
+ accessor,
204
+ factors,
205
+ facetFieldDef,
206
+ };
207
+ }
208
+ }
209
+
210
+ updateLabels() {
211
+ for (const channel of FACET_CHANNELS) {
212
+ const facetDimension = this._facetDimensions[channel];
213
+ this._labelViews[channel].updateData(
214
+ facetDimension
215
+ ? facetDimension.factors.map((d) => ({ data: d }))
216
+ : []
217
+ );
218
+ }
219
+ }
220
+
221
+ /**
222
+ * Returns an accessor that returns a (composite) key for partitioning the data
223
+ *
224
+ * @param {View} [whoIsAsking] Passed to the immediate parent. Allows for
225
+ * selectively breaking the inheritance.
226
+ * @return {function(object):any}
227
+ */
228
+ getFacetAccessor(whoIsAsking) {
229
+ const { column, row } = this._facetDimensions;
230
+
231
+ if (Object.values(this._labelViews).includes(whoIsAsking)) {
232
+ // Label views are faceted by the facet labels
233
+ return vegaField("data");
234
+ } else if (column && row) {
235
+ const columnField = vegaField(column.facetFieldDef.field);
236
+ const rowField = vegaField(row.facetFieldDef.field);
237
+ return /** @param {object} d */ (d) =>
238
+ columnField(d) + "," + rowField(d);
239
+ } else if (column) {
240
+ return vegaField(column.facetFieldDef.field);
241
+ } else if (row) {
242
+ return vegaField(row.facetFieldDef.field);
243
+ } else {
244
+ throw new Error("updateFacets() must be called first!");
245
+ }
246
+ }
247
+
248
+ getFacetGroups() {
249
+ const { column, row } = this._facetDimensions;
250
+
251
+ if (column && row) {
252
+ return cross(
253
+ column.factors,
254
+ row.factors,
255
+ (col, row) => col + "," + row
256
+ );
257
+ } else if (column) {
258
+ return column.factors;
259
+ } else if (row) {
260
+ return row.factors;
261
+ } else {
262
+ throw new Error("updateFacets() must be called first!");
263
+ }
264
+ }
265
+
266
+ getSize() {
267
+ // TODO: IMPLEMENT!
268
+ return super.getSize();
269
+ }
270
+
271
+ /**
272
+ * @param {import("./renderingContext/viewRenderingContext").default} context
273
+ * @param {import("../utils/layout/rectangle").default} coords
274
+ * @param {import("./view").RenderingOptions} [options]
275
+ */
276
+ render(context, coords, options = {}) {
277
+ if (!this.isVisible()) {
278
+ return;
279
+ }
280
+
281
+ coords = coords.shrink(this.getPadding());
282
+ context.pushView(this, coords);
283
+
284
+ // Size of the view that is being repeated for all the facets
285
+ const childSize = this.child.getSize();
286
+
287
+ // Fugly hack. TODO: Figure out a systematic phase for doing this
288
+ if (!this._labelsUpdated) {
289
+ this.updateLabels();
290
+ this._labelsUpdated = true;
291
+ }
292
+
293
+ // TODO: Validate. Columns is not compatible with row channel
294
+ const wrap = this.spec.columns && this._facetDimensions.column;
295
+
296
+ // We use two flexLayouts to create a grid for the facets.
297
+ // Stride and offset control how the cells in the grid are allocated
298
+ // for the facets and the intervening facet labels.
299
+ const xStride = 1;
300
+ const xOffset = wrap ? 0 : this._facetDimensions.row ? 1 : 0;
301
+ const yStride = wrap ? 2 : 1;
302
+ const yOffset = this._facetDimensions.column ? 1 : 0;
303
+
304
+ /**
305
+ * @param {SizeDef} childSize
306
+ * @param {number} count
307
+ * @param {number} stride
308
+ * @param {number} offset
309
+ */
310
+ const calculateCellSizes = (childSize, count, stride, offset) => {
311
+ // TODO: take the channel into account
312
+ const labelSize = { px: headerConfig.labelFontSize };
313
+
314
+ /** @type {SizeDef[]} */
315
+ const cellSizes = [];
316
+
317
+ for (let i = 0; i < offset; i++) {
318
+ cellSizes.push(labelSize);
319
+ }
320
+
321
+ for (let i = 0; i < count; i++) {
322
+ for (let j = 1; j < stride; j++) {
323
+ cellSizes.push(labelSize);
324
+ }
325
+ cellSizes.push(childSize);
326
+ }
327
+
328
+ return cellSizes;
329
+ };
330
+
331
+ /**
332
+ *
333
+ * @param {FacetChannel} channel
334
+ * @param {number} count Number of factors
335
+ */
336
+ const computeFlexCoords = (channel, count) => {
337
+ const dimension = this._facetDimensions[channel];
338
+
339
+ const spacing = coalesce(
340
+ dimension ? dimension.facetFieldDef.spacing : undefined,
341
+ this.spec.spacing,
342
+ DEFAULT_SPACING
343
+ );
344
+
345
+ const cellSizes =
346
+ channel == "column"
347
+ ? calculateCellSizes(
348
+ childSize.width,
349
+ count,
350
+ xStride,
351
+ xOffset
352
+ )
353
+ : calculateCellSizes(
354
+ childSize.height,
355
+ count,
356
+ yStride,
357
+ wrap ? 0 : yOffset
358
+ );
359
+
360
+ return mapToPixelCoords(
361
+ cellSizes,
362
+ coords[channel == "column" ? "width" : "height"],
363
+ {
364
+ spacing,
365
+ devicePixelRatio: window.devicePixelRatio,
366
+ }
367
+ );
368
+ };
369
+
370
+ let nCols = 0;
371
+ let nRows = 0;
372
+ let n = 0;
373
+
374
+ if (wrap) {
375
+ // Wrapping layout
376
+ n = this._facetDimensions.column.factors.length;
377
+ nCols = this.spec.columns;
378
+ nRows = Math.ceil(n / nCols);
379
+ } else {
380
+ /** @param {FacetDimension} facetDimension */
381
+ const getCount = (facetDimension) =>
382
+ facetDimension ? facetDimension.factors.length : 1;
383
+
384
+ nCols = getCount(this._facetDimensions.column);
385
+ nRows = getCount(this._facetDimensions.row);
386
+ n = nCols * nRows;
387
+ }
388
+
389
+ const columnFlexCoords = computeFlexCoords("column", nCols);
390
+ const rowFlexCoords = computeFlexCoords("row", nRows);
391
+
392
+ const axisSizes =
393
+ this.child instanceof DecoratorView
394
+ ? this.child.getAxisSizes()
395
+ : Padding.createUniformPadding(0);
396
+
397
+ const facetIds = this.getFacetGroups();
398
+
399
+ // Render column labels
400
+ if (this._facetDimensions.column) {
401
+ const factors = this._facetDimensions.column.factors;
402
+ for (let x = 0; x < factors.length; x++) {
403
+ // Take wrapping labels into account
404
+ const xCell = columnFlexCoords[(x % nCols) * xStride + xOffset];
405
+ const yCell = rowFlexCoords[Math.floor(x / nCols) * yStride];
406
+ this._labelViews.column.render(
407
+ context,
408
+ Rectangle.create(
409
+ xCell.location + axisSizes.left,
410
+ yCell.location,
411
+ xCell.size - axisSizes.width,
412
+ yCell.size
413
+ ).translateBy(coords),
414
+ { ...options, facetId: factors[x] }
415
+ );
416
+ }
417
+ }
418
+
419
+ // Render row labels
420
+ if (this._facetDimensions.row) {
421
+ const factors = this._facetDimensions.row.factors;
422
+ for (let y = 0; y < factors.length; y++) {
423
+ const xCell = columnFlexCoords[0];
424
+ const yCell = rowFlexCoords[y * yStride + yOffset];
425
+ this._labelViews.row.render(
426
+ context,
427
+ Rectangle.create(
428
+ xCell.location,
429
+ yCell.location + axisSizes.top,
430
+ xCell.size,
431
+ yCell.size - axisSizes.height
432
+ ).translateBy(coords),
433
+ { ...options, facetId: factors[y] }
434
+ );
435
+ }
436
+ }
437
+
438
+ // Render facets
439
+ let i = 0;
440
+ for (let x = 0; x < nCols; x++) {
441
+ for (let y = 0; y < nRows; y++) {
442
+ if (i >= n) break;
443
+
444
+ const xCell = columnFlexCoords[x * xStride + xOffset];
445
+ const yCell = rowFlexCoords[y * yStride + yOffset];
446
+ this.child.render(
447
+ context,
448
+ new Rectangle(
449
+ xCell.location,
450
+ yCell.location,
451
+ xCell.size,
452
+ yCell.size
453
+ ).translateBy(coords),
454
+ { ...options, facetId: facetIds[i] }
455
+ );
456
+ i++;
457
+ }
458
+ }
459
+
460
+ context.popView(this);
461
+ }
462
+ }
463
+
464
+ /**
465
+ *
466
+ */
467
+ function createLabelViewSpec(headerConfig) {
468
+ // TODO: Support styling: https://vega.github.io/vega-lite/docs/header.html#labels
469
+
470
+ /** @type {import("./viewUtils").UnitSpec} */
471
+ const titleView = {
472
+ data: {
473
+ values: [],
474
+ },
475
+ mark: {
476
+ type: "text",
477
+ clip: false,
478
+ angle: headerConfig.labelAngle,
479
+ },
480
+ encoding: {
481
+ text: { field: "data", type: "nominal" },
482
+ size: { value: headerConfig.labelFontSize },
483
+ color: { value: headerConfig.labelColor },
484
+ },
485
+ };
486
+
487
+ return titleView;
488
+ }