@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,621 @@
1
+ import { InternMap } from "internmap";
2
+ import { format } from "d3-format";
3
+ import { isString } from "vega-util";
4
+ import { fp64ify } from "./includes/fp64-utils";
5
+ import ArrayBuilder from "./arrayBuilder";
6
+ import { SDF_PADDING } from "../fonts/bmFontMetrics";
7
+ import { peek } from "../utils/arrayUtils";
8
+ import createBinningRangeIndexer from "../utils/binnedRangeIndex";
9
+
10
+ /**
11
+ * @typedef {object} RangeEntry Represents a location of a vertex subset
12
+ * @prop {number} offset in vertices
13
+ * @prop {number} count in vertices
14
+ * @prop {import("../utils/binnedRangeIndex").Lookup} xIndex
15
+ *
16
+ * @typedef {import("./arraybuilder").ConverterMetadata} Converter
17
+ * @typedef {import("../encoder/encoder").Encoder} Encoder
18
+ */
19
+ export class GeometryBuilder {
20
+ /**
21
+ *
22
+ * @param {object} object
23
+ * @param {Record<string, Encoder>} object.encoders
24
+ * @param {string[]} [object.attributes]
25
+ * @param {number} [object.numVertices] If the number of data items is known, a
26
+ * preallocated TypedArray is used
27
+ * @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
28
+ */
29
+ constructor({
30
+ encoders,
31
+ numVertices = undefined,
32
+ attributes = [],
33
+ buildXIndex = false,
34
+ }) {
35
+ this.encoders = encoders;
36
+ this._buildXIndex = buildXIndex;
37
+
38
+ // Encoders for variable channels
39
+ this.variableEncoders = Object.fromEntries(
40
+ Object.entries(encoders).filter(
41
+ ([channel, e]) =>
42
+ attributes.includes(channel) && e && e.scale && !e.constant
43
+ )
44
+ );
45
+
46
+ this.allocatedVertices = numVertices;
47
+
48
+ this.variableBuilder = new ArrayBuilder(numVertices);
49
+
50
+ // Create converters and updaters for all variable channels.
51
+ // TODO: If more than one channels use the same field with the same data type, convert the field only once.
52
+
53
+ for (const [channel, ce] of Object.entries(this.variableEncoders)) {
54
+ const accessor = ce.accessor;
55
+
56
+ const doubleArray = [0, 0];
57
+ const fp64 = ce.scale.fp64;
58
+
59
+ /**
60
+ * Discrete variables both numeric and strings must be "indexed",
61
+ * 64 bit floats must be converted to vec2.
62
+ * 32 bit continuous variables go to GPU as is.
63
+ *
64
+ * @type {function(any):(number | number[])}
65
+ */
66
+ const f = ce.indexer
67
+ ? ce.indexer
68
+ : fp64
69
+ ? (d) => fp64ify(accessor(d), doubleArray)
70
+ : accessor;
71
+
72
+ this.variableBuilder.addConverter(channel, {
73
+ f,
74
+ numComponents: fp64 ? 2 : 1,
75
+ arrayReference: fp64 ? doubleArray : undefined,
76
+ });
77
+ }
78
+
79
+ this.lastOffset = 0;
80
+
81
+ /** @type {Map<any, RangeEntry>} keep track of sample locations within the vertex array */
82
+ this.rangeMap = new InternMap([], JSON.stringify);
83
+ }
84
+
85
+ /**
86
+ * Must be called at the end of `addBatch`
87
+ *
88
+ * @param {any} key
89
+ */
90
+ registerBatch(key) {
91
+ const offset = this.lastOffset;
92
+ const index = this.variableBuilder.vertexCount;
93
+ const size = index - offset;
94
+ if (size) {
95
+ this.rangeMap.set(key, {
96
+ offset,
97
+ count: size,
98
+ xIndex: this.xIndexer?.getIndex(),
99
+ });
100
+ }
101
+ this.lastOffset = index;
102
+ }
103
+
104
+ /**
105
+ * @param {Map<any, object[]>} batches
106
+ */
107
+ addBatches(batches) {
108
+ for (const [key, data] of batches) {
109
+ this.addBatch(key, data);
110
+ }
111
+ }
112
+
113
+ /**
114
+ * @param {any} key The facet id, for example
115
+ * @param {object[]} data
116
+ */
117
+ addBatch(key, data, lo = 0, hi = data.length) {
118
+ for (let i = lo; i < hi; i++) {
119
+ this.variableBuilder.pushFromDatum(data[i]);
120
+ }
121
+
122
+ this.registerBatch(key);
123
+ }
124
+
125
+ /**
126
+ * @param {any[]} data
127
+ */
128
+ prepareXIndexer(data) {
129
+ if (!this._buildXIndex) {
130
+ return;
131
+ }
132
+
133
+ const xe = this.variableEncoders.x;
134
+ const x2e = this.variableEncoders.x2;
135
+
136
+ if (xe && x2e) {
137
+ const xa = xe.accessor;
138
+ const x2a = x2e.accessor;
139
+
140
+ this.xIndexer = createBinningRangeIndexer(50, [
141
+ xa(data[0]),
142
+ x2a(peek(data)),
143
+ ]);
144
+
145
+ let lastVertexCount = this.variableBuilder.vertexCount;
146
+
147
+ /**
148
+ * @param {any} datum
149
+ */
150
+ this.addToXIndex = (datum) => {
151
+ let currentVertexCount = this.variableBuilder.vertexCount;
152
+ this.xIndexer(
153
+ xa(datum),
154
+ x2a(datum),
155
+ lastVertexCount,
156
+ currentVertexCount
157
+ );
158
+ lastVertexCount = currentVertexCount;
159
+ };
160
+ } else {
161
+ this.xIndexer = undefined;
162
+ /**
163
+ * @param {any} datum
164
+ */
165
+ this.addToXIndex = (datum) => {
166
+ //
167
+ };
168
+ }
169
+ }
170
+
171
+ /**
172
+ * Add the datum to an index, which allows for efficient rendering of ranges
173
+ * on the x axis. Must be called after a datum has been pushed to the ArrayBuilder.
174
+ *
175
+ * @param {any} datum
176
+ */
177
+ addToXIndex(datum) {
178
+ //
179
+ }
180
+
181
+ toArrays() {
182
+ return {
183
+ /** @type {Record<string, {data: number[] | Float32Array, numComponents: number, divisor?: number}>} */
184
+ arrays: this.variableBuilder.arrays,
185
+ /** Number of vertices used */
186
+ vertexCount: this.variableBuilder.vertexCount,
187
+ /** Number of vertices allocated in buffers */
188
+ allocatedVertices: this.allocatedVertices,
189
+ rangeMap: this.rangeMap,
190
+ };
191
+ }
192
+ }
193
+
194
+ export class RectVertexBuilder extends GeometryBuilder {
195
+ /**
196
+ *
197
+ * @param {Object} object
198
+ * @param {Record<string, Encoder>} object.encoders
199
+ * @param {string[]} object.attributes
200
+ * @param {number} [object.tessellationThreshold]
201
+ * If the rect is wider than the threshold, tessellate it into pieces
202
+ * @param {number[]} [object.visibleRange]
203
+ * @param {number} [object.numItems] Number of data items
204
+ * @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
205
+ */
206
+ constructor({
207
+ encoders,
208
+ attributes,
209
+ tessellationThreshold = Infinity,
210
+ visibleRange = [-Infinity, Infinity],
211
+ numItems,
212
+ buildXIndex = false,
213
+ }) {
214
+ super({
215
+ encoders,
216
+ attributes,
217
+ numVertices:
218
+ tessellationThreshold == Infinity ? numItems * 6 : undefined,
219
+ buildXIndex,
220
+ });
221
+
222
+ this.visibleRange = visibleRange;
223
+
224
+ this.tessellationThreshold = tessellationThreshold || Infinity;
225
+
226
+ this.updateFrac = this.variableBuilder.createUpdater("frac", 2);
227
+ }
228
+
229
+ /**
230
+ *
231
+ * @param {any} key
232
+ * @param {object[]} data
233
+ */
234
+ addBatch(key, data, lo = 0, hi = data.length) {
235
+ if (hi <= lo) {
236
+ return;
237
+ }
238
+
239
+ const e =
240
+ /** @type {Object.<string, import("../encoder/encoder").NumberEncoder>} */ (
241
+ this.encoders
242
+ );
243
+ const [lower, upper] = this.visibleRange;
244
+
245
+ /**
246
+ * @param {import("../encoder/encoder").Encoder} encoder
247
+ */
248
+ const a = (encoder) => encoder.accessor || ((x) => 0);
249
+
250
+ const xAccessor = a(e.x);
251
+ const x2Accessor = a(e.x2);
252
+
253
+ this.prepareXIndexer(data);
254
+
255
+ const frac = [0, 0];
256
+ this.updateFrac(frac);
257
+
258
+ for (let i = lo; i < hi; i++) {
259
+ const d = data[i];
260
+
261
+ let x = xAccessor(d),
262
+ x2 = x2Accessor(d);
263
+
264
+ if (x > x2) {
265
+ [x, x2] = [x2, x];
266
+ }
267
+
268
+ // Skip rects that fall outside the visible range. TODO: Optimize by using binary search / interval tree
269
+ if (x2 < lower || x > upper) {
270
+ continue;
271
+ }
272
+
273
+ // Truncate to prevent tessellation of parts that are outside the viewport
274
+ if (x < lower) x = lower;
275
+ if (x2 > upper) x2 = upper;
276
+
277
+ // Start a new segment.
278
+ this.variableBuilder.updateFromDatum(d);
279
+
280
+ frac[0] = 0;
281
+ frac[1] = 0;
282
+
283
+ // Tessellate segments
284
+ const tileCount = 1;
285
+ // width < Infinity
286
+ // ? Math.ceil(width / this.tessellationThreshold)
287
+ // : 1;
288
+
289
+ // Duplicate the first vertex to produce degenerate triangles
290
+ this.variableBuilder.pushAll();
291
+
292
+ for (let i = 0; i <= tileCount; i++) {
293
+ frac[0] = i / tileCount;
294
+ frac[1] = 0;
295
+ this.variableBuilder.pushAll();
296
+ frac[1] = 1;
297
+ this.variableBuilder.pushAll();
298
+ }
299
+
300
+ // Duplicate the last vertex to produce a degenerate triangle between the segments
301
+ this.variableBuilder.pushAll();
302
+ this.addToXIndex(d);
303
+ }
304
+
305
+ this.registerBatch(key);
306
+ }
307
+ }
308
+
309
+ export class RuleVertexBuilder extends GeometryBuilder {
310
+ /**
311
+ *
312
+ * @param {Object} object
313
+ * @param {Record<string, Encoder>} object.encoders
314
+ * @param {string[]} object.attributes
315
+ * @param {number} [object.tessellationThreshold]
316
+ * If the rule is wider than the threshold, tessellate it into pieces
317
+ * @param {number[]} [object.visibleRange]
318
+ * @param {number} [object.numItems] Number of data items
319
+ * @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
320
+ */
321
+ constructor({
322
+ encoders,
323
+ attributes,
324
+ tessellationThreshold = Infinity,
325
+ visibleRange = [-Infinity, Infinity],
326
+ numItems,
327
+ buildXIndex,
328
+ }) {
329
+ super({
330
+ encoders,
331
+ attributes,
332
+ numVertices:
333
+ tessellationThreshold == Infinity ? numItems * 6 : undefined,
334
+ buildXIndex,
335
+ });
336
+
337
+ this.visibleRange = visibleRange;
338
+
339
+ this.tessellationThreshold = tessellationThreshold || Infinity;
340
+
341
+ this.updateSide = this.variableBuilder.createUpdater("side", 1);
342
+ this.updatePos = this.variableBuilder.createUpdater("pos", 1);
343
+ }
344
+
345
+ /* eslint-disable complexity */
346
+ /**
347
+ *
348
+ * @param {any} key
349
+ * @param {object[]} data
350
+ */
351
+ addBatch(key, data, lo = 0, hi = data.length) {
352
+ //const [lower, upper] = this.visibleRange; // TODO
353
+
354
+ this.prepareXIndexer(data);
355
+
356
+ for (let i = lo; i < hi; i++) {
357
+ const d = data[i];
358
+
359
+ // Start a new rule. Duplicate the first vertex to produce degenerate triangles
360
+ this.variableBuilder.updateFromDatum(d);
361
+ this.updateSide(-0.5);
362
+ this.updatePos(0);
363
+ this.variableBuilder.pushAll();
364
+
365
+ // Tesselate segments
366
+ const tileCount = 1;
367
+ // width < Infinity
368
+ // ? Math.ceil(width / this.tessellationThreshold)
369
+ // : 1;
370
+ for (let i = 0; i <= tileCount; i++) {
371
+ this.updatePos(i / tileCount);
372
+ this.updateSide(-0.5);
373
+ this.variableBuilder.pushAll();
374
+ this.updateSide(0.5);
375
+ this.variableBuilder.pushAll();
376
+ }
377
+
378
+ // Duplicate the last vertex to produce a degenerate triangle between the rules
379
+ this.variableBuilder.pushAll();
380
+ this.addToXIndex(d);
381
+ }
382
+
383
+ this.registerBatch(key);
384
+ }
385
+ }
386
+
387
+ export class PointVertexBuilder extends GeometryBuilder {
388
+ /**
389
+ *
390
+ * @param {object} object
391
+ * @param {Record<string, Encoder>} object.encoders
392
+ * @param {string[]} object.attributes
393
+ * @param {number} [object.numItems] Number of points if known, uses TypedArray
394
+ */
395
+ constructor({ encoders, attributes, numItems = undefined }) {
396
+ super({
397
+ encoders,
398
+ attributes,
399
+ numVertices: numItems,
400
+ });
401
+ }
402
+ }
403
+
404
+ export class ConnectionVertexBuilder extends GeometryBuilder {
405
+ /**
406
+ * @param {object} object
407
+ * @param {Record<string, Encoder>} object.encoders
408
+ * @param {string[]} object.attributes
409
+ * @param {number} [object.numItems ] Number of points if known, uses TypedArray
410
+ */
411
+ constructor({ encoders, attributes, numItems = undefined }) {
412
+ super({
413
+ encoders,
414
+ attributes,
415
+ numVertices: numItems,
416
+ });
417
+ }
418
+
419
+ toArrays() {
420
+ const arrays = this.variableBuilder.arrays;
421
+
422
+ // Prepare for instanced rendering
423
+ for (let a of Object.values(arrays)) {
424
+ a.divisor = 1;
425
+ }
426
+
427
+ return super.toArrays();
428
+ }
429
+ }
430
+
431
+ export class TextVertexBuilder extends GeometryBuilder {
432
+ /**
433
+ *
434
+ * @param {object} object
435
+ * @param {Record<string, Encoder>} object.encoders
436
+ * @param {string[]} object.attributes
437
+ * @param {import("../fonts/bmFontMetrics").BMFontMetrics} object.fontMetrics
438
+ * @param {Record<string, any>} object.properties
439
+ * @param {number} [object.numCharacters] number of characters
440
+ * @param {boolean} [object.buildXIndex] True if data are sorted by the field mapped to x channel and should be indexed
441
+ * @param {boolean} [object.logoLetters]
442
+ */
443
+ constructor({
444
+ encoders,
445
+ attributes,
446
+ fontMetrics,
447
+ properties,
448
+ numCharacters = undefined,
449
+ buildXIndex = false,
450
+ }) {
451
+ super({
452
+ encoders,
453
+ attributes,
454
+ numVertices: numCharacters * 6, // six vertices per quad (character)
455
+ buildXIndex,
456
+ });
457
+
458
+ this.metadata = fontMetrics;
459
+ this.metrics = fontMetrics;
460
+
461
+ this.properties = properties;
462
+
463
+ const e = encoders;
464
+
465
+ /** @type {function(any):any} */
466
+ this.numberFormat = e.text.channelDef.format
467
+ ? format(e.text.channelDef.format)
468
+ : (d) => d;
469
+
470
+ this.updateVertexCoord = this.variableBuilder.createUpdater(
471
+ "vertexCoord",
472
+ 2
473
+ );
474
+ this.updateTextureCoord = this.variableBuilder.createUpdater(
475
+ "textureCoord",
476
+ 2
477
+ );
478
+
479
+ this.updateWidth = this.variableBuilder.createUpdater("width", 1);
480
+ }
481
+
482
+ /**
483
+ *
484
+ * @param {any} key
485
+ * @param {object[]} data
486
+ */
487
+ addBatch(key, data, lo = 0, hi = data.length) {
488
+ const align = this.properties.align || "left";
489
+ const logoLetters = this.properties.logoLetters ?? false;
490
+
491
+ const base = this.metadata.common.base;
492
+ const scale = this.metadata.common.scaleH; // Assume square textures
493
+
494
+ let baseline = -SDF_PADDING;
495
+ switch (this.properties.baseline) {
496
+ case "top":
497
+ baseline += this.metrics.capHeight;
498
+ break;
499
+ case "middle":
500
+ baseline += this.metrics.capHeight / 2;
501
+ break;
502
+ case "bottom":
503
+ baseline -= this.metrics.descent;
504
+ break;
505
+ default:
506
+ // alphabetic
507
+ }
508
+
509
+ const accessor = this.encoders.text.accessor || this.encoders.text; // accessor or constant value
510
+
511
+ const vertexCoord = [0, 0];
512
+ this.updateVertexCoord(vertexCoord);
513
+ const textureCoord = [0, 0];
514
+ this.updateTextureCoord(textureCoord);
515
+
516
+ this.prepareXIndexer(data);
517
+
518
+ for (let i = lo; i < hi; i++) {
519
+ const d = data[i];
520
+
521
+ const value = this.numberFormat(accessor(d));
522
+ const str = isString(value)
523
+ ? value
524
+ : value === null
525
+ ? ""
526
+ : "" + value;
527
+ if (str.length == 0) continue;
528
+
529
+ this.variableBuilder.updateFromDatum(d);
530
+
531
+ const textWidth = logoLetters
532
+ ? str.length
533
+ : this.metrics.measureWidth(str);
534
+
535
+ this.updateWidth(textWidth); // TODO: Check if one letter space should be reduced
536
+
537
+ let x =
538
+ align == "right"
539
+ ? -textWidth
540
+ : align == "center"
541
+ ? -textWidth / 2
542
+ : 0;
543
+
544
+ if (!logoLetters) {
545
+ const firstChar = this.metrics.getCharByCode(str.charCodeAt(0));
546
+ x -= (firstChar.width - firstChar.xadvance) / base / 2; // TODO: Fix, this is a bit off..
547
+ }
548
+
549
+ let bottom = -0.5,
550
+ height = 1,
551
+ normalWidth = 1;
552
+
553
+ for (let i = 0; i < str.length; i++) {
554
+ const c = this.metrics.getCharByCode(str.charCodeAt(i));
555
+
556
+ const advance = logoLetters ? 1 : c.xadvance / base;
557
+
558
+ if (c.id == 32) {
559
+ x += advance;
560
+ continue;
561
+ }
562
+
563
+ if (!logoLetters) {
564
+ height = c.height / base;
565
+ bottom = -(c.height + c.yoffset + baseline) / base;
566
+ normalWidth = c.width / base;
567
+ } else {
568
+ normalWidth = (c.width + SDF_PADDING * 2) / c.width;
569
+ x = -normalWidth / 2;
570
+ height = (c.height + SDF_PADDING * 2) / c.height;
571
+ bottom = -0.5 - SDF_PADDING / c.height;
572
+ }
573
+
574
+ const tx = c.x;
575
+ const ty = c.y;
576
+
577
+ vertexCoord[0] = x;
578
+ vertexCoord[1] = bottom + height;
579
+ textureCoord[0] = tx / scale;
580
+ textureCoord[1] = ty / scale;
581
+ this.variableBuilder.pushAll();
582
+
583
+ vertexCoord[0] = x + normalWidth;
584
+ vertexCoord[1] = bottom + height;
585
+ textureCoord[0] = (tx + c.width) / scale;
586
+ textureCoord[1] = ty / scale;
587
+ this.variableBuilder.pushAll();
588
+
589
+ vertexCoord[0] = x;
590
+ vertexCoord[1] = bottom;
591
+ textureCoord[0] = tx / scale;
592
+ textureCoord[1] = (ty + c.height) / scale;
593
+ this.variableBuilder.pushAll();
594
+
595
+ vertexCoord[0] = x + normalWidth;
596
+ vertexCoord[1] = bottom + height;
597
+ textureCoord[0] = (tx + c.width) / scale;
598
+ textureCoord[1] = ty / scale;
599
+ this.variableBuilder.pushAll();
600
+
601
+ vertexCoord[0] = x;
602
+ vertexCoord[1] = bottom;
603
+ textureCoord[0] = tx / scale;
604
+ textureCoord[1] = (ty + c.height) / scale;
605
+ this.variableBuilder.pushAll();
606
+
607
+ vertexCoord[0] = x + normalWidth;
608
+ vertexCoord[1] = bottom;
609
+ textureCoord[0] = (tx + c.width) / scale;
610
+ textureCoord[1] = (ty + c.height) / scale;
611
+ this.variableBuilder.pushAll();
612
+
613
+ x += advance;
614
+ }
615
+
616
+ this.addToXIndex(data);
617
+ }
618
+
619
+ this.registerBatch(key);
620
+ }
621
+ }
@@ -0,0 +1,63 @@
1
+ #define PI 3.141593
2
+
3
+ /** Offset in "unit" units */
4
+ uniform vec2 uViewOffset;
5
+
6
+ uniform vec2 uViewScale;
7
+
8
+ /** Size of the logical viewport in pixels, i.e., the view */
9
+ uniform vec2 uViewportSize;
10
+
11
+ uniform lowp float uDevicePixelRatio;
12
+
13
+ // TODO: Views with opacity less than 1.0 should be rendered into a texture
14
+ // that is rendered with the specified opacity.
15
+ uniform lowp float uViewOpacity;
16
+
17
+ /**
18
+ * Maps a coordinate on the unit scale to a normalized device coordinate.
19
+ * (0, 0) is at the bottom left corner.
20
+ */
21
+ vec4 unitToNdc(vec2 coord) {
22
+ return vec4((coord * uViewScale + uViewOffset) * 2.0 - 1.0, 0.0, 1.0);
23
+ }
24
+
25
+ vec4 unitToNdc(float x, float y) {
26
+ return unitToNdc(vec2(x, y));
27
+ }
28
+
29
+ vec4 pixelsToNdc(vec2 coord) {
30
+ return unitToNdc(coord / uViewportSize);
31
+ }
32
+
33
+ vec4 pixelsToNdc(float x, float y) {
34
+ return pixelsToNdc(vec2(x, y));
35
+ }
36
+
37
+ float linearstep(float edge0, float edge1, float x) {
38
+ return clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
39
+ }
40
+
41
+ // Fragment shader stuff ////////////////////////////////////////////////////////
42
+
43
+ // TODO: include the following only in fragment shaders
44
+
45
+ /**
46
+ * Specialized linearstep for doing antialiasing
47
+ */
48
+ float distanceToRatio(float d) {
49
+ return clamp(d * uDevicePixelRatio + 0.5, 0.0, 1.0);
50
+ }
51
+
52
+ vec4 distanceToColor(float d, vec4 fill, vec4 stroke, float halfStrokeWidth) {
53
+ if (halfStrokeWidth > 0.0) {
54
+ // Distance to stroke's edge. Negative inside the stroke.
55
+ float sd = abs(d) - halfStrokeWidth;
56
+ return mix(
57
+ stroke,
58
+ d <= 0.0 ? fill : vec4(0.0),
59
+ distanceToRatio(sd));
60
+ } else {
61
+ return fill * distanceToRatio(-d);
62
+ }
63
+ }