@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,336 @@
1
+ import { isNumber } from "vega-util";
2
+ import { isStepSize } from "../../view/view";
3
+
4
+ /**
5
+ *
6
+ * Layout calculation inspired by flexbox. The elements may have an
7
+ * absolute size (in pixels) and a growth component for filling the
8
+ * remaining space. Spacing around zero-sized items are collapsed.
9
+ *
10
+ * Read more at https://css-tricks.com/flex-grow-is-weird/
11
+ *
12
+ * @typedef {object} SizeDef Size definition inspired by CSS flexbox
13
+ * @prop {number} [px] Size in pixels
14
+ * @prop {number} [grow] Share of remaining space
15
+ *
16
+ * @typedef {object} LocSize One-dimensional location and size
17
+ * @prop {number} location
18
+ * @prop {number} size
19
+ *
20
+ * @typedef {object} FlexOptions
21
+ * @prop {number} [spacing] gap between items in pixels
22
+ * @prop {number} [devicePixelRatio] allows for snapping to "retina" pixels.
23
+ * Default: `undefined`, which disables the snapping.
24
+ * @prop {number} [offset] add the offset to all locations. Default: `0`.
25
+ * @prop {boolean} [reverse] fill from "right to left".
26
+ *
27
+ * @param {SizeDef[]} items
28
+ * @param {number} containerSize in pixels
29
+ * @param {FlexOptions} [options]
30
+ * @returns {LocSize[]}
31
+ */
32
+ export function mapToPixelCoords(
33
+ items,
34
+ containerSize,
35
+ { spacing, devicePixelRatio, offset, reverse } = {}
36
+ ) {
37
+ spacing = spacing || 0;
38
+ offset = offset || 0;
39
+
40
+ let totalPx = 0;
41
+ let totalGrow = 0;
42
+
43
+ for (const size of items) {
44
+ totalPx += z(size.px) + (isZeroSizeDef(size) ? 0 : spacing);
45
+ totalGrow += z(size.grow);
46
+ }
47
+ totalPx -= spacing;
48
+
49
+ const remainingSpace = Math.max(0, containerSize - totalPx);
50
+
51
+ /** @type {function(number):number} x */
52
+ const round =
53
+ devicePixelRatio !== undefined
54
+ ? (x) => Math.round(x * devicePixelRatio) / devicePixelRatio
55
+ : (x) => x;
56
+
57
+ /**
58
+ * Buffer zero-sized items so that their locations can be spread evenly.
59
+ * They can then be interpolated nicely.
60
+ * @type {SizeDef[]}
61
+ */
62
+ const zeroBuffer = [];
63
+
64
+ /** @type {LocSize[]} */
65
+ const results = [];
66
+
67
+ /**
68
+ * Spread evenly
69
+ *
70
+ * @param {boolean} inMiddle
71
+ */
72
+ const flushZeroBuffer = (inMiddle) => {
73
+ const n = zeroBuffer.length;
74
+ if (!n) {
75
+ return;
76
+ }
77
+
78
+ const s = (inMiddle ? spacing : 0) * (reverse ? -1 : 1);
79
+
80
+ x -= s;
81
+ for (let i = 0; i < n; i++) {
82
+ results.push({
83
+ location: x + ((i + 1) / (n + 1)) * s,
84
+ size: 0,
85
+ });
86
+ }
87
+ x += s;
88
+
89
+ zeroBuffer.length = 0;
90
+ };
91
+
92
+ let x = reverse ? Math.max(containerSize, totalPx) : 0 + offset;
93
+
94
+ // Handle a special case
95
+ if (items.length == 1 && isZeroSizeDef(items[0])) {
96
+ return [{ location: x, size: 0 }];
97
+ }
98
+
99
+ for (let i = 0; i < items.length; i++) {
100
+ const size = items[i];
101
+
102
+ if (isZeroSizeDef(size)) {
103
+ zeroBuffer.push(size);
104
+ } else {
105
+ flushZeroBuffer(results.length > 0);
106
+
107
+ const advance =
108
+ z(size.px) +
109
+ (totalGrow ? (z(size.grow) / totalGrow) * remainingSpace : 0);
110
+
111
+ if (reverse) {
112
+ x -= advance;
113
+ }
114
+
115
+ results.push({ location: round(x), size: round(advance) });
116
+
117
+ if (!reverse) {
118
+ x += advance + spacing;
119
+ } else {
120
+ x -= spacing;
121
+ }
122
+ }
123
+ }
124
+
125
+ // Remove the last gap
126
+ x += reverse ? spacing : -spacing;
127
+
128
+ flushZeroBuffer(false);
129
+
130
+ return results;
131
+ }
132
+
133
+ /**
134
+ * Returns the minimum size (the sum of pixels sizes) for the flex items
135
+ *
136
+ * @param {SizeDef[]} items
137
+ * @param {FlexOptions} [options]
138
+ */
139
+ export function getMinimumSize(items, { spacing } = { spacing: 0 }) {
140
+ let minimumSize = 0;
141
+ for (const size of items) {
142
+ minimumSize += z(size.px) + (isZeroSizeDef(size) ? 0 : spacing);
143
+ }
144
+ return Math.max(0, minimumSize - spacing);
145
+ }
146
+
147
+ /**
148
+ * Returns true if relative (stretching) elements are present
149
+ * @param {SizeDef[]} items
150
+ */
151
+ export function isStretching(items) {
152
+ return items.some((size) => size.grow);
153
+ }
154
+
155
+ export class FlexDimensions {
156
+ /**
157
+ *
158
+ * @param {SizeDef} width
159
+ * @param {SizeDef} height
160
+ */
161
+ constructor(width, height) {
162
+ // TODO: Consider making immutable
163
+ /** @readonly */
164
+ this.width = width;
165
+ /** @readonly */
166
+ this.height = height;
167
+ }
168
+
169
+ /**
170
+ * Adds padding to absolute (px) dimensions
171
+ *
172
+ * @param {import("./padding").default} padding
173
+ */
174
+ addPadding(padding) {
175
+ return new FlexDimensions(
176
+ {
177
+ px: (this.width.px || 0) + padding.width,
178
+ grow: this.width.grow,
179
+ },
180
+ {
181
+ px: (this.height.px || 0) + padding.height,
182
+ grow: this.height.grow,
183
+ }
184
+ );
185
+ }
186
+ }
187
+
188
+ /**
189
+ * A sizedef that takes no space at all.
190
+ *
191
+ * @type {SizeDef}
192
+ */
193
+ export const ZERO_SIZEDEF = Object.freeze({
194
+ px: 0,
195
+ grow: 0,
196
+ });
197
+
198
+ export const ZERO_FLEXDIMENSIONS = new FlexDimensions(
199
+ ZERO_SIZEDEF,
200
+ ZERO_SIZEDEF
201
+ );
202
+
203
+ /**
204
+ * Is the sizeDef taking no space at all
205
+ *
206
+ * @param {SizeDef} sizeDef
207
+ */
208
+ export function isZeroSizeDef(sizeDef) {
209
+ return !sizeDef.px && !sizeDef.grow;
210
+ }
211
+
212
+ /**
213
+ * Converts undefined/null to zero
214
+ *
215
+ * @param {number} value
216
+ */
217
+ function z(value) {
218
+ return value || 0;
219
+ }
220
+
221
+ /**
222
+ *
223
+ * @param {*} spec
224
+ * @returns {spec is SizeDef}
225
+ */
226
+ export function isSizeDef(spec) {
227
+ return spec && (isNumber(spec.px) || isNumber(spec.grow));
228
+ }
229
+
230
+ /**
231
+ *
232
+ * @param {"container" | number | SizeDef | import("../../spec/view").Step} size
233
+ * @returns {SizeDef}
234
+ */
235
+ export function parseSizeDef(size) {
236
+ if (isStepSize(size)) {
237
+ throw new Error("parseSizeDef does not accept step-based sizes.");
238
+ } else if (isSizeDef(size)) {
239
+ return size;
240
+ } else if (isNumber(size)) {
241
+ return { px: size, grow: 0 };
242
+ } else if (size === "container") {
243
+ // https://vega.github.io/vega-lite/docs/size.html#specifying-responsive-width-and-height
244
+ return { px: 0, grow: 1 };
245
+ } else if (!size) {
246
+ return { px: 0, grow: 1 };
247
+ }
248
+
249
+ throw new Error(`Invalid sizeDef: ${size}`);
250
+ }
251
+
252
+ // TODO: Find a better place for the following utilities: ////////////////////////////////////
253
+
254
+ /**
255
+ * Interpolates between two LocSizes
256
+ *
257
+ * @param {LocSize} from
258
+ * @param {LocSize} to
259
+ * @param {function():number} ratio
260
+ * @returns {LocSize}
261
+ */
262
+ export function interpolateLocSizes(from, to, ratio) {
263
+ return {
264
+ get location() {
265
+ const r = ratio();
266
+ switch (r) {
267
+ case 0:
268
+ return from.location;
269
+ case 1:
270
+ return to.location;
271
+ default:
272
+ return r * to.location + (1 - r) * from.location;
273
+ }
274
+ },
275
+
276
+ get size() {
277
+ const r = ratio();
278
+ switch (r) {
279
+ case 0:
280
+ return from.size;
281
+ case 1:
282
+ return to.size;
283
+ default:
284
+ return r * to.size + (1 - r) * from.size;
285
+ }
286
+ },
287
+ };
288
+ }
289
+
290
+ /**
291
+ * Wraps a LocSize and allows scrolling.
292
+ *
293
+ * @param {LocSize} locSize
294
+ * @param {number | function():number} offset
295
+ * @returns {LocSize}
296
+ */
297
+ export function translateLocSize(locSize, offset) {
298
+ const fn = isNumber(offset) ? () => offset : offset;
299
+ return {
300
+ get location() {
301
+ return locSize.location + fn();
302
+ },
303
+
304
+ get size() {
305
+ return locSize.size;
306
+ },
307
+ };
308
+ }
309
+
310
+ /**
311
+ * Wraps a LocSize and allows scaling.
312
+ *
313
+ * @param {LocSize} locSize
314
+ * @param {number | function():number} factor
315
+ * @returns {LocSize}
316
+ */
317
+ export function scaleLocSize(locSize, factor) {
318
+ const fn = isNumber(factor) ? () => factor : factor;
319
+ return {
320
+ get location() {
321
+ return locSize.location * fn();
322
+ },
323
+
324
+ get size() {
325
+ return locSize.size * fn();
326
+ },
327
+ };
328
+ }
329
+
330
+ /**
331
+ * @param {LocSize} locSize
332
+ * @param {number} value
333
+ */
334
+ export function locSizeEncloses(locSize, value) {
335
+ return value >= locSize.location && value < locSize.location + locSize.size;
336
+ }
@@ -0,0 +1,296 @@
1
+ import {
2
+ mapToPixelCoords,
3
+ getMinimumSize,
4
+ isStretching,
5
+ parseSizeDef,
6
+ } from "./flexLayout";
7
+
8
+ test("parseSize", () => {
9
+ expect(parseSizeDef(10)).toEqual({ px: 10, grow: 0 });
10
+ expect(parseSizeDef({ px: 20, grow: 2 })).toEqual({ px: 20, grow: 2 });
11
+ expect(parseSizeDef(undefined)).toEqual({ px: 0, grow: 1 });
12
+ expect(parseSizeDef(null)).toEqual({ px: 0, grow: 1 });
13
+ expect(parseSizeDef("container")).toEqual({ px: 0, grow: 1 });
14
+ expect(() => parseSizeDef({})).toThrow();
15
+ });
16
+
17
+ describe("Basic flex functionality", () => {
18
+ test("Absolute sizes", () => {
19
+ const items = [10, 30, 20].map((x) => ({ px: x }));
20
+ const containerSize = 100;
21
+
22
+ const mapped = mapToPixelCoords(items, containerSize);
23
+
24
+ expect(mapped[0]).toEqual({ location: 0, size: 10 });
25
+ expect(mapped[1]).toEqual({ location: 10, size: 30 });
26
+ expect(mapped[2]).toEqual({ location: 40, size: 20 });
27
+ });
28
+
29
+ test("Absolute sizes with spacing", () => {
30
+ const items = [10, 30, 20].map((x) => ({ px: x }));
31
+ const containerSize = 100;
32
+
33
+ const mapped = mapToPixelCoords(items, containerSize, { spacing: 10 });
34
+
35
+ expect(mapped[0]).toEqual({ location: 0, size: 10 });
36
+ expect(mapped[1]).toEqual({ location: 20, size: 30 });
37
+ expect(mapped[2]).toEqual({ location: 60, size: 20 });
38
+ });
39
+
40
+ test("Absolute sizes with spacing, reversed", () => {
41
+ const items = [10, 30, 20].map((x) => ({ px: x }));
42
+ const containerSize = 100;
43
+
44
+ const mapped = mapToPixelCoords(items, containerSize, {
45
+ spacing: 10,
46
+ reverse: true,
47
+ });
48
+
49
+ expect(mapped[0]).toEqual({ location: 90, size: 10 });
50
+ expect(mapped[1]).toEqual({ location: 50, size: 30 });
51
+ expect(mapped[2]).toEqual({ location: 20, size: 20 });
52
+ });
53
+
54
+ test("Absolute sizes with spacing, reversed and insufficient containerSize", () => {
55
+ const items = [10, 30, 20].map((x) => ({ px: x }));
56
+ const containerSize = 0;
57
+
58
+ const mapped = mapToPixelCoords(items, containerSize, {
59
+ spacing: 10,
60
+ reverse: true,
61
+ });
62
+
63
+ expect(mapped[0]).toEqual({ location: 70, size: 10 });
64
+ expect(mapped[1]).toEqual({ location: 30, size: 30 });
65
+ expect(mapped[2]).toEqual({ location: 0, size: 20 });
66
+ });
67
+
68
+ test("Growing sizes", () => {
69
+ const items = [10, 20, 70].map((x) => ({ grow: x }));
70
+ const containerSize = 200;
71
+
72
+ const mapped = mapToPixelCoords(items, containerSize);
73
+
74
+ expect(mapped[0]).toEqual({ location: 0, size: 20 });
75
+ expect(mapped[1]).toEqual({ location: 20, size: 40 });
76
+ expect(mapped[2]).toEqual({ location: 60, size: 140 });
77
+ });
78
+
79
+ test("Growing sizes with spacing", () => {
80
+ const items = [10, 20, 70].map((x) => ({ grow: x }));
81
+ const containerSize = 220;
82
+ const mapped = mapToPixelCoords(items, containerSize, { spacing: 10 });
83
+
84
+ expect(mapped[0]).toEqual({ location: 0, size: 20 });
85
+ expect(mapped[1]).toEqual({ location: 30, size: 40 });
86
+ expect(mapped[2]).toEqual({ location: 80, size: 140 });
87
+ });
88
+
89
+ test("Mixed absolute and relative sizes", () => {
90
+ const items = [{ px: 100 }, { grow: 1 }, { grow: 9 }, { px: 200 }];
91
+ const containerSize = 1100;
92
+ const mapped = mapToPixelCoords(items, containerSize);
93
+
94
+ expect(mapped[0]).toEqual({ location: 0, size: 100 });
95
+ expect(mapped[1]).toEqual({ location: 100, size: 80 });
96
+ expect(mapped[2]).toEqual({ location: 180, size: 720 });
97
+ expect(mapped[3]).toEqual({ location: 900, size: 200 });
98
+ });
99
+
100
+ test("Sizes having both absolute and growing components", () => {
101
+ const items = [
102
+ { px: 1 },
103
+ { px: 2 },
104
+ { px: 3, grow: 2 },
105
+ { px: 4, grow: 1 },
106
+ ];
107
+ const containerSize = 16;
108
+ const mapped = mapToPixelCoords(items, containerSize);
109
+
110
+ expect(mapped[0]).toEqual({ location: 0, size: 1 });
111
+ expect(mapped[1]).toEqual({ location: 1, size: 2 });
112
+ expect(mapped[2]).toEqual({ location: 3, size: 7 });
113
+ expect(mapped[3]).toEqual({ location: 10, size: 6 });
114
+ });
115
+
116
+ test("Zero sizes return zero coords", () => {
117
+ const items = [{ grow: 0 }, { grow: 0 }];
118
+
119
+ const mapped = mapToPixelCoords(items, 0);
120
+ expect(mapped[0]).toEqual({ location: 0, size: 0 });
121
+ expect(mapped[1]).toEqual({ location: 0, size: 0 });
122
+
123
+ const mapped2 = mapToPixelCoords(items, 1);
124
+ expect(mapped2[0]).toEqual({ location: 0, size: 0 });
125
+ expect(mapped2[1]).toEqual({ location: 0, size: 0 });
126
+ });
127
+
128
+ test("Offset is added", () => {
129
+ const items = [10, 30, 20].map((x) => ({ px: x }));
130
+ const containerSize = 100;
131
+
132
+ const mapped = mapToPixelCoords(items, containerSize, { offset: 5 });
133
+
134
+ expect(mapped[0]).toEqual({ location: 5, size: 10 });
135
+ expect(mapped[1]).toEqual({ location: 15, size: 30 });
136
+ expect(mapped[2]).toEqual({ location: 45, size: 20 });
137
+ });
138
+ });
139
+
140
+ describe("Collapse gaps when items have zero px and grow", () => {
141
+ test("Zero as first", () => {
142
+ const items = [0, 30, 20].map((x) => ({ px: x }));
143
+ const containerSize = 100;
144
+
145
+ const mapped = mapToPixelCoords(items, containerSize, { spacing: 10 });
146
+
147
+ expect(mapped[0]).toEqual({ location: 0, size: 0 });
148
+ expect(mapped[1]).toEqual({ location: 0, size: 30 });
149
+ expect(mapped[2]).toEqual({ location: 40, size: 20 });
150
+ });
151
+
152
+ test("Zero in the middle", () => {
153
+ const items = [10, 0, 20].map((x) => ({ px: x }));
154
+ const containerSize = 100;
155
+
156
+ const mapped = mapToPixelCoords(items, containerSize, { spacing: 10 });
157
+
158
+ expect(mapped[0]).toEqual({ location: 0, size: 10 });
159
+ expect(mapped[1]).toEqual({ location: 15, size: 0 });
160
+ expect(mapped[2]).toEqual({ location: 20, size: 20 });
161
+ });
162
+
163
+ test("Multiple zeroes in the middle", () => {
164
+ const items = [10, 0, 0, 0, 20].map((x) => ({ px: x }));
165
+ const containerSize = 100;
166
+
167
+ const mapped = mapToPixelCoords(items, containerSize, { spacing: 10 });
168
+
169
+ expect(mapped[0]).toEqual({ location: 0, size: 10 });
170
+ expect(mapped[1]).toEqual({ location: 12.5, size: 0 });
171
+ expect(mapped[2]).toEqual({ location: 15, size: 0 });
172
+ expect(mapped[3]).toEqual({ location: 17.5, size: 0 });
173
+ expect(mapped[4]).toEqual({ location: 20, size: 20 });
174
+ });
175
+
176
+ test("Zero as last", () => {
177
+ const items = [10, 30, 0].map((x) => ({ px: x }));
178
+ const containerSize = 100;
179
+
180
+ const mapped = mapToPixelCoords(items, containerSize, { spacing: 10 });
181
+
182
+ expect(mapped[0]).toEqual({ location: 0, size: 10 });
183
+ expect(mapped[1]).toEqual({ location: 20, size: 30 });
184
+ expect(mapped[2]).toEqual({ location: 50, size: 0 });
185
+ });
186
+
187
+ test("Only a zero", () => {
188
+ const items = [0].map((x) => ({ px: x }));
189
+ const containerSize = 100;
190
+
191
+ const mapped = mapToPixelCoords(items, containerSize, { spacing: 10 });
192
+
193
+ expect(mapped[0]).toEqual({ location: 0, size: 0 });
194
+ });
195
+ });
196
+
197
+ describe("Collapse gaps when items have zero px and grow, reversed", () => {
198
+ test("Zero as first", () => {
199
+ const items = [0, 30, 20].map((x) => ({ px: x }));
200
+ const containerSize = 100;
201
+
202
+ const mapped = mapToPixelCoords(items, containerSize, {
203
+ spacing: 10,
204
+ reverse: true,
205
+ });
206
+
207
+ expect(mapped[0]).toEqual({ location: 100, size: 0 });
208
+ expect(mapped[1]).toEqual({ location: 70, size: 30 });
209
+ expect(mapped[2]).toEqual({ location: 40, size: 20 });
210
+ });
211
+
212
+ test("Zero in the middle", () => {
213
+ const items = [10, 0, 20].map((x) => ({ px: x }));
214
+ const containerSize = 100;
215
+
216
+ const mapped = mapToPixelCoords(items, containerSize, {
217
+ spacing: 10,
218
+ reverse: true,
219
+ });
220
+
221
+ expect(mapped[0]).toEqual({ location: 90, size: 10 });
222
+ expect(mapped[1]).toEqual({ location: 85, size: 0 });
223
+ expect(mapped[2]).toEqual({ location: 60, size: 20 });
224
+ });
225
+
226
+ test("Multiple zeroes in the middle", () => {
227
+ const items = [10, 0, 0, 0, 20].map((x) => ({ px: x }));
228
+ const containerSize = 100;
229
+
230
+ const mapped = mapToPixelCoords(items, containerSize, {
231
+ spacing: 10,
232
+ reverse: true,
233
+ });
234
+
235
+ expect(mapped[0]).toEqual({ location: 90, size: 10 });
236
+ expect(mapped[1]).toEqual({ location: 87.5, size: 0 });
237
+ expect(mapped[2]).toEqual({ location: 85, size: 0 });
238
+ expect(mapped[3]).toEqual({ location: 82.5, size: 0 });
239
+ expect(mapped[4]).toEqual({ location: 60, size: 20 });
240
+ });
241
+
242
+ test("Zero as last", () => {
243
+ const items = [10, 30, 0].map((x) => ({ px: x }));
244
+ const containerSize = 100;
245
+
246
+ const mapped = mapToPixelCoords(items, containerSize, {
247
+ spacing: 10,
248
+ reverse: true,
249
+ });
250
+
251
+ expect(mapped[0]).toEqual({ location: 90, size: 10 });
252
+ expect(mapped[1]).toEqual({ location: 50, size: 30 });
253
+ expect(mapped[2]).toEqual({ location: 50, size: 0 });
254
+ });
255
+
256
+ test("Only a zero", () => {
257
+ const items = [0].map((x) => ({ px: x }));
258
+ const containerSize = 100;
259
+
260
+ const mapped = mapToPixelCoords(items, containerSize, {
261
+ spacing: 10,
262
+ reverse: true,
263
+ });
264
+
265
+ expect(mapped[0]).toEqual({ location: 100, size: 0 });
266
+ });
267
+ });
268
+
269
+ describe("Utility fuctions", () => {
270
+ test("getMinimumSize", () => {
271
+ const items = [{ px: 100 }, { grow: 1 }, { grow: 9 }, { px: 200 }];
272
+
273
+ expect(getMinimumSize(items)).toEqual(300);
274
+
275
+ expect(getMinimumSize(items, { spacing: 10 })).toEqual(330);
276
+ });
277
+
278
+ test("getMinimumSize, items include zeroes", () => {
279
+ const items = [
280
+ { px: 100 },
281
+ { px: 0, grow: 0 },
282
+ { grow: 1 },
283
+ { grow: 9 },
284
+ { px: 200 },
285
+ ];
286
+
287
+ expect(getMinimumSize(items)).toEqual(300);
288
+
289
+ expect(getMinimumSize(items, { spacing: 10 })).toEqual(330);
290
+ });
291
+
292
+ test("isStretching", () => {
293
+ expect(isStretching([{ grow: 1 }])).toBeTruthy();
294
+ expect(isStretching([{ px: 1 }])).toBeFalsy();
295
+ });
296
+ });