@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,107 @@
1
+ /**
2
+ * A class for handing paddings, borders, margins, etc.
3
+ *
4
+ * @typedef {import("../../spec/view").Paddings} Paddings
5
+ * @typedef {import("../../spec/view").PaddingConfig} PaddingConfig
6
+ */
7
+ export default class Padding {
8
+ /**
9
+ * @param {number} top
10
+ * @param {number} right
11
+ * @param {number} bottom
12
+ * @param {number} left
13
+ */
14
+ constructor(top, right, bottom, left) {
15
+ /** @readonly */ this.top = top || 0;
16
+ /** @readonly */ this.right = right || 0;
17
+ /** @readonly */ this.bottom = bottom || 0;
18
+ /** @readonly */ this.left = left || 0;
19
+ }
20
+
21
+ /**
22
+ * Returns the sum of left and right
23
+ */
24
+ get width() {
25
+ return this.left + this.right;
26
+ }
27
+
28
+ /**
29
+ * Returns the sum of top and bottom
30
+ */
31
+ get height() {
32
+ return this.top + this.bottom;
33
+ }
34
+
35
+ /**
36
+ * @param {number} amount In pixels
37
+ */
38
+ expand(amount) {
39
+ if (amount <= 0) {
40
+ return this;
41
+ }
42
+
43
+ return new Padding(
44
+ this.top + amount,
45
+ this.right + amount,
46
+ this.bottom + amount,
47
+ this.left + amount
48
+ );
49
+ }
50
+
51
+ /**
52
+ *
53
+ * @param {Padding} padding padding to add
54
+ */
55
+ add(padding) {
56
+ return new Padding(
57
+ this.top + padding.top,
58
+ this.right + padding.right,
59
+ this.bottom + padding.bottom,
60
+ this.left + padding.left
61
+ );
62
+ }
63
+
64
+ /**
65
+ *
66
+ * @param {PaddingConfig} config
67
+ */
68
+ static createFromConfig(config) {
69
+ if (typeof config == "number") {
70
+ return this.createUniformPadding(config);
71
+ } else if (config) {
72
+ return this.createFromRecord(config);
73
+ } else {
74
+ return zeroPadding;
75
+ }
76
+ }
77
+
78
+ /**
79
+ * @param {Paddings} paddings
80
+ */
81
+ static createFromRecord(paddings) {
82
+ return new Padding(
83
+ paddings.top,
84
+ paddings.right,
85
+ paddings.bottom,
86
+ paddings.left
87
+ );
88
+ }
89
+
90
+ /**
91
+ * Returns a zeroed padding.
92
+ */
93
+ static zero() {
94
+ return zeroPadding;
95
+ }
96
+
97
+ /**
98
+ *
99
+ * @param {number} value
100
+ */
101
+ static createUniformPadding(value) {
102
+ return new Padding(value, value, value, value);
103
+ }
104
+ }
105
+
106
+ const zeroPadding = Padding.createUniformPadding(0);
107
+ Object.freeze(zeroPadding);
@@ -0,0 +1,23 @@
1
+ export default class Point {
2
+ /**
3
+ *
4
+ * @param {number} x
5
+ * @param {number} y
6
+ */
7
+ constructor(x, y) {
8
+ /** @readonly */ this.x = x;
9
+ /** @readonly */ this.y = y;
10
+ }
11
+
12
+ /**
13
+ *
14
+ * @param {Point} point
15
+ */
16
+ equals(point) {
17
+ if (!point) {
18
+ return false;
19
+ }
20
+
21
+ return point === this || (point.x === this.x && point.y === this.y);
22
+ }
23
+ }
@@ -0,0 +1,282 @@
1
+ /**
2
+ * @param {number} value
3
+ * @returns {Accessor}
4
+ */
5
+ function constant(value) {
6
+ return () => value;
7
+ }
8
+
9
+ /**
10
+ * An immutable rectangle with a scene-graph -like hierarchy.
11
+ * Allows for implementing scrolling viewports etc.
12
+ *
13
+ * @typedef {("x" | "y" | "width" | "height")} Prop
14
+ * @typedef {import("./padding").default } Padding
15
+ * @typedef {() => number} Accessor
16
+ */
17
+ export default class Rectangle {
18
+ /**
19
+ *
20
+ * @param {number} x
21
+ * @param {number} y
22
+ * @param {number} width
23
+ * @param {number} height
24
+ */
25
+ static create(x, y, width, height) {
26
+ return new Rectangle(
27
+ constant(x),
28
+ constant(y),
29
+ constant(width),
30
+ constant(height)
31
+ );
32
+ }
33
+
34
+ /**
35
+ * @param {Prop} prop
36
+ * @param {number | function():number} value
37
+ * @returns {Accessor}
38
+ */
39
+ _offset(prop, value) {
40
+ // @ts-ignore
41
+ const accessor = this["_" + prop];
42
+ if (value === 0) {
43
+ return accessor;
44
+ }
45
+
46
+ switch (typeof value) {
47
+ case "number":
48
+ return () => accessor() + value;
49
+ case "function":
50
+ return () => accessor() + value();
51
+ default:
52
+ throw new Error("Not a number of function");
53
+ }
54
+ }
55
+
56
+ /**
57
+ * @param {Prop} prop
58
+ */
59
+ _passThrough(prop) {
60
+ return this._offset(prop, 0);
61
+ }
62
+
63
+ /**
64
+ *
65
+ * @param {Accessor} x
66
+ * @param {Accessor} y
67
+ * @param {Accessor} width
68
+ * @param {Accessor} height
69
+ */
70
+ constructor(x, y, width, height) {
71
+ /** @readonly */ this._x = x;
72
+ /** @readonly */ this._y = y;
73
+ /** @readonly */ this._width = width;
74
+ /** @readonly */ this._height = height;
75
+ }
76
+
77
+ /**
78
+ * @returns {number}
79
+ */
80
+ get x() {
81
+ return this._x();
82
+ }
83
+
84
+ /**
85
+ * @returns {number}
86
+ */
87
+ get y() {
88
+ return this._y();
89
+ }
90
+
91
+ /**
92
+ * @returns {number}
93
+ */
94
+ get width() {
95
+ return this._width();
96
+ }
97
+
98
+ /**
99
+ * @returns {number}
100
+ */
101
+ get height() {
102
+ return this._height();
103
+ }
104
+
105
+ get x2() {
106
+ return this._x() + this._width();
107
+ }
108
+
109
+ get y2() {
110
+ return this._y() + this._height();
111
+ }
112
+
113
+ /**
114
+ * Returns true if the given rectangle is the same or equal rectangle.
115
+ *
116
+ * @param {Rectangle} rectangle
117
+ */
118
+ equals(rectangle) {
119
+ if (!rectangle) {
120
+ return false;
121
+ }
122
+
123
+ return (
124
+ this === rectangle ||
125
+ (this.x === rectangle.x &&
126
+ this.y === rectangle.y &&
127
+ this.width === rectangle.width &&
128
+ this.height === rectangle.height)
129
+ );
130
+ }
131
+
132
+ /**
133
+ *
134
+ * @param {Partial<Record<Prop, number | function():number>>} props
135
+ */
136
+ modify(props) {
137
+ if (!Object.keys(props).length) {
138
+ return this;
139
+ }
140
+
141
+ /** @param {Prop} prop */
142
+ const map = (prop) => {
143
+ const v = props[prop];
144
+ return typeof v == "number"
145
+ ? constant(v)
146
+ : typeof v == "function"
147
+ ? v
148
+ : this._passThrough(prop);
149
+ };
150
+
151
+ return new Rectangle(map("x"), map("y"), map("width"), map("height"));
152
+ }
153
+
154
+ /**
155
+ *
156
+ * @param {number | function():number} x
157
+ * @param {number | function():number} y
158
+ */
159
+ translate(x, y) {
160
+ if (x === 0 && y === 0) {
161
+ return this;
162
+ }
163
+
164
+ return new Rectangle(
165
+ this._offset("x", x),
166
+ this._offset("y", y),
167
+ this._passThrough("width"),
168
+ this._passThrough("height")
169
+ );
170
+ }
171
+
172
+ /**
173
+ * Returns a copy of this rectangle translated by the given rectangle.
174
+ *
175
+ * @param {Rectangle} rectangle
176
+ */
177
+ translateBy(rectangle) {
178
+ // TODO: Make dynamic
179
+ return this.translate(rectangle.x, rectangle.y);
180
+ }
181
+
182
+ /**
183
+ *
184
+ * @param {Padding} padding
185
+ */
186
+ expand(padding, direction = 1) {
187
+ if (
188
+ padding.left == 0 &&
189
+ padding.top == 0 &&
190
+ padding.right == 0 &&
191
+ padding.bottom == 0
192
+ ) {
193
+ return this;
194
+ }
195
+
196
+ return new Rectangle(
197
+ padding.left
198
+ ? this._offset("x", -padding.left * direction)
199
+ : this._passThrough("x"),
200
+ padding.top
201
+ ? this._offset("y", -padding.top * direction)
202
+ : this._passThrough("y"),
203
+ padding.width
204
+ ? this._offset("width", padding.width * direction)
205
+ : this._passThrough("width"),
206
+ padding.height
207
+ ? this._offset("height", padding.height * direction)
208
+ : this._passThrough("height")
209
+ );
210
+ }
211
+
212
+ /**
213
+ *
214
+ * @param {Padding} padding
215
+ */
216
+ shrink(padding) {
217
+ return this.expand(padding, -1);
218
+ }
219
+
220
+ /**
221
+ * Returns an intersection of this and the other rectangle.
222
+ *
223
+ * @param {Rectangle} rectangle
224
+ */
225
+ intersect(rectangle) {
226
+ if (this === rectangle) {
227
+ return this;
228
+ }
229
+
230
+ return new Rectangle(
231
+ () => Math.max(this.x, rectangle.x),
232
+ () => Math.max(this.y, rectangle.y),
233
+ () =>
234
+ Math.min(this.x2, rectangle.x2) - Math.max(this.x, rectangle.x),
235
+ () =>
236
+ Math.min(this.y2, rectangle.y2) - Math.max(this.y, rectangle.y)
237
+ );
238
+ }
239
+
240
+ /**
241
+ * Returns true if width and height are greater than equal to zero.
242
+ */
243
+ isDefined() {
244
+ return this.width >= 0 && this.height >= 0;
245
+ }
246
+
247
+ /**
248
+ * Returns a flattened rectangle, i.e., drops the hierarchy and materializes
249
+ * the parameters.
250
+ */
251
+ flatten() {
252
+ return new Rectangle(
253
+ constant(this.x),
254
+ constant(this.y),
255
+ constant(this.width),
256
+ constant(this.height)
257
+ );
258
+ }
259
+
260
+ /**
261
+ * Tests whether the rectangle contains the given point. Uses half open intervals.
262
+ *
263
+ * @param {number} x
264
+ * @param {number} y
265
+ */
266
+ containsPoint(x, y) {
267
+ return x >= this.x && x < this.x2 && y >= this.y && y < this.y2;
268
+ }
269
+
270
+ /**
271
+ * Normalizes a point with respect to this rectangle
272
+ *
273
+ * @param {number} x
274
+ * @param {number} y
275
+ */
276
+ normalizePoint(x, y) {
277
+ return {
278
+ x: (x - this.x) / this.width,
279
+ y: (y - this.y) / this.height,
280
+ };
281
+ }
282
+ }
@@ -0,0 +1,171 @@
1
+ import Rectangle from "./rectangle";
2
+ import Padding from "./padding";
3
+
4
+ test("Rectangle creation", () => {
5
+ const r = Rectangle.create(1, 2, 3, 4);
6
+ expect(r.x).toEqual(1);
7
+ expect(r.y).toEqual(2);
8
+ expect(r.width).toEqual(3);
9
+ expect(r.height).toEqual(4);
10
+ });
11
+
12
+ test("equals", () => {
13
+ const r = Rectangle.create(1, 2, 3, 4);
14
+ expect(r.equals(r)).toBeTruthy();
15
+ expect(r.equals(Rectangle.create(1, 2, 3, 4))).toBeTruthy();
16
+ expect(r.equals(undefined)).toBeFalsy();
17
+ });
18
+
19
+ test("x2 and y2 calculation", () => {
20
+ const r = Rectangle.create(1, 2, 3, 4);
21
+ expect(r.x2).toEqual(4);
22
+ expect(r.y2).toEqual(6);
23
+ });
24
+
25
+ test("translate", () => {
26
+ const r = Rectangle.create(1, 2, 3, 4).translate(2, 3);
27
+ expect(r.x).toEqual(3);
28
+ expect(r.y).toEqual(5);
29
+ expect(r.width).toEqual(3);
30
+ expect(r.height).toEqual(4);
31
+ });
32
+
33
+ test("Dynamic translate", () => {
34
+ let tx = 0;
35
+ let ty = 0;
36
+
37
+ const r = Rectangle.create(1, 2, 3, 4)
38
+ .translate(
39
+ () => tx,
40
+ () => ty
41
+ )
42
+ .translate(2, 3);
43
+
44
+ expect(r.x).toEqual(3);
45
+ expect(r.y).toEqual(5);
46
+ expect(r.width).toEqual(3);
47
+ expect(r.height).toEqual(4);
48
+
49
+ tx = 1;
50
+ ty = 2;
51
+
52
+ expect(r.x).toEqual(4);
53
+ expect(r.y).toEqual(7);
54
+ expect(r.width).toEqual(3);
55
+ expect(r.height).toEqual(4);
56
+ });
57
+
58
+ test("expand", () => {
59
+ const r = Rectangle.create(1, 2, 3, 4).expand(
60
+ Padding.createFromRecord({ top: 2, right: 3, bottom: 4, left: 5 })
61
+ );
62
+
63
+ expect(r.x).toEqual(-4);
64
+ expect(r.width).toEqual(11);
65
+ expect(r.y).toEqual(0);
66
+ expect(r.height).toEqual(10);
67
+ });
68
+
69
+ test("shrink", () => {
70
+ const r = Rectangle.create(1, 2, 3, 4).shrink(
71
+ Padding.createUniformPadding(1)
72
+ );
73
+
74
+ expect(r.x).toEqual(2);
75
+ expect(r.width).toEqual(1);
76
+ expect(r.y).toEqual(3);
77
+ expect(r.height).toEqual(2);
78
+ });
79
+
80
+ test("modify", () => {
81
+ const r = Rectangle.create(1, 2, 3, 4);
82
+ const m = r.modify({ x: 5 });
83
+
84
+ expect(m.equals(r)).toBeFalsy();
85
+ expect(m.equals(Rectangle.create(5, 2, 3, 4))).toBeTruthy();
86
+ });
87
+
88
+ test("Dynamic modify", () => {
89
+ let x = 1;
90
+ const r = Rectangle.create(1, 2, 3, 4);
91
+ const m = r.modify({ x: () => x });
92
+
93
+ expect(m.equals(r)).toBeTruthy();
94
+ expect(m.equals(Rectangle.create(1, 2, 3, 4))).toBeTruthy();
95
+
96
+ x = 5;
97
+
98
+ expect(m.equals(r)).toBeFalsy();
99
+ expect(m.equals(Rectangle.create(5, 2, 3, 4))).toBeTruthy();
100
+ });
101
+
102
+ test("intersect", () => {
103
+ const a = Rectangle.create(1, 1, 6, 3);
104
+ const b = Rectangle.create(5, 2, 3, 4);
105
+ const c = Rectangle.create(5, 2, 2, 2);
106
+
107
+ expect(a.intersect(b).equals(c)).toBeTruthy();
108
+ expect(a.intersect(b).isDefined()).toBeTruthy();
109
+
110
+ const x = Rectangle.create(1, 1, 1, 1);
111
+ const y = Rectangle.create(3, 3, 1, 1);
112
+
113
+ expect(x.intersect(y).isDefined()).toBeFalsy();
114
+ });
115
+
116
+ test("isDefined", () => {
117
+ expect(Rectangle.create(0, 0, 1, 1).isDefined()).toBeTruthy();
118
+ expect(Rectangle.create(0, 0, 0, 0).isDefined()).toBeTruthy();
119
+
120
+ expect(Rectangle.create(0, 0, -1, 0).isDefined()).toBeFalsy();
121
+ expect(Rectangle.create(0, 0, 0, -1).isDefined()).toBeFalsy();
122
+ });
123
+
124
+ test("flatten", () => {
125
+ let tx = 0;
126
+ let ty = 0;
127
+
128
+ const r = Rectangle.create(1, 2, 3, 4).translate(
129
+ () => tx,
130
+ () => ty
131
+ );
132
+
133
+ const flattened = r.flatten();
134
+
135
+ tx = -1;
136
+ ty = -2;
137
+
138
+ expect(r.equals(Rectangle.create(0, 0, 3, 4)));
139
+ expect(flattened.equals(Rectangle.create(1, 2, 3, 4)));
140
+ });
141
+
142
+ test("containsPoint", () => {
143
+ const r = Rectangle.create(1, 2, 3, 4);
144
+
145
+ expect(r.containsPoint(0, 0)).toBeFalsy();
146
+ expect(r.containsPoint(0, 10)).toBeFalsy();
147
+ expect(r.containsPoint(10, 0)).toBeFalsy();
148
+ expect(r.containsPoint(10, 10)).toBeFalsy();
149
+
150
+ expect(r.containsPoint(2, 0)).toBeFalsy();
151
+ expect(r.containsPoint(2, 10)).toBeFalsy();
152
+ expect(r.containsPoint(0, 3)).toBeFalsy();
153
+ expect(r.containsPoint(10, 3)).toBeFalsy();
154
+
155
+ // Inclusive corner
156
+ expect(r.containsPoint(1, 2)).toBeTruthy();
157
+
158
+ // Inside
159
+ expect(r.containsPoint(2, 3)).toBeTruthy();
160
+
161
+ // Exclusive corner
162
+ expect(r.containsPoint(4, 6)).toBeFalsy();
163
+ });
164
+
165
+ test("normalizePoint", () => {
166
+ const r = Rectangle.create(1, 2, 6, 4);
167
+
168
+ expect(r.normalizePoint(1, 2)).toEqual({ x: 0, y: 0 });
169
+ expect(r.normalizePoint(7, 2)).toEqual({ x: 1, y: 0 });
170
+ expect(r.normalizePoint(4, 4)).toEqual({ x: 0.5, y: 0.5 });
171
+ });
@@ -0,0 +1,99 @@
1
+ /* eslint-disable max-depth */
2
+ import { isObject } from "vega-util";
3
+
4
+ /**
5
+ * Deep merge.
6
+ *
7
+ * A boolean true and an object are compatible. The object survives, the boolean is overwritten.
8
+ *
9
+ * TODO: Support arrays. Should accept identical arrays and complain about others.
10
+ *
11
+ * @param {T[]} objects Objects to merge
12
+ * @param {string} propertyOf What are we merging? Used in warning messages
13
+ * @param {string[]} [skip] Fields to skip. TODO: Support nested fields.
14
+ * @returns {T}
15
+ * @template T
16
+ */
17
+ export default function mergeObjects(objects, propertyOf, skip) {
18
+ skip = skip || [];
19
+
20
+ if (objects.some((d) => d === null)) {
21
+ if (objects.every((d) => d === null)) {
22
+ return null;
23
+ } else {
24
+ console.warn(objects);
25
+ throw new Error("Cannot merge objects with nulls!");
26
+ }
27
+ }
28
+
29
+ /** @type {any} */
30
+ const target = {};
31
+
32
+ /** @type {function(any, any):boolean} */
33
+ const compatible = (a, b) =>
34
+ a === b ||
35
+ (isPlainObject(a) && isPlainObject(b)) ||
36
+ (isPlainObject(a) && b === true) ||
37
+ (a === true && isObject(b));
38
+
39
+ /** @param {any} obj */
40
+ const merger = (obj) => {
41
+ // eslint-disable-next-line guard-for-in
42
+ for (let prop in obj) {
43
+ const sourceValue = obj[prop];
44
+ if (!skip.includes(prop) && sourceValue !== undefined) {
45
+ if (
46
+ target[prop] !== undefined &&
47
+ !compatible(target[prop], sourceValue)
48
+ ) {
49
+ console.warn(
50
+ `Conflicting property ${prop} of ${propertyOf}: (${JSON.stringify(
51
+ target[prop]
52
+ )} and ${JSON.stringify(
53
+ obj[prop]
54
+ )}). Using ${JSON.stringify(target[prop])}.`
55
+ );
56
+ } else {
57
+ const targetValue = target[prop];
58
+
59
+ if (isPlainObject(targetValue)) {
60
+ if (isPlainObject(sourceValue)) {
61
+ // Object merged to object
62
+ target[prop] = mergeObjects(
63
+ [targetValue, sourceValue],
64
+ prop
65
+ );
66
+ }
67
+ } else if (isPlainObject(sourceValue)) {
68
+ if (
69
+ !(targetValue === true || targetValue === undefined)
70
+ ) {
71
+ throw new Error(
72
+ "Bug in merge! Target is: " + targetValue
73
+ );
74
+ }
75
+ // Object replaces "true"
76
+ target[prop] = mergeObjects([{}, sourceValue], prop);
77
+ } else {
78
+ // Scalar
79
+ target[prop] = sourceValue;
80
+ }
81
+ }
82
+ }
83
+ }
84
+ };
85
+
86
+ for (const o of objects) {
87
+ merger(o);
88
+ }
89
+
90
+ return target;
91
+ }
92
+
93
+ /**
94
+ *
95
+ * @param {any} x
96
+ */
97
+ function isPlainObject(x) {
98
+ return isObject(x) && !Array.isArray(x);
99
+ }