@genome-spy/core 0.68.0 → 0.69.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 (163) hide show
  1. package/dist/bundle/index.es.js +12108 -10682
  2. package/dist/bundle/index.js +119 -119
  3. package/dist/schema.json +6224 -6319
  4. package/dist/src/data/dataFlow.d.ts.map +1 -1
  5. package/dist/src/data/dataFlow.js +10 -0
  6. package/dist/src/data/flowNode.d.ts +25 -10
  7. package/dist/src/data/flowNode.d.ts.map +1 -1
  8. package/dist/src/data/flowNode.js +66 -13
  9. package/dist/src/data/flowTestUtils.d.ts +2 -2
  10. package/dist/src/data/flowTestUtils.d.ts.map +1 -1
  11. package/dist/src/data/flowTestUtils.js +5 -4
  12. package/dist/src/data/sources/dataSource.js +2 -2
  13. package/dist/src/data/sources/lazy/bigBedSource.d.ts.map +1 -1
  14. package/dist/src/data/sources/lazy/bigBedSource.js +11 -10
  15. package/dist/src/data/sources/lazy/bigWigSource.d.ts.map +1 -1
  16. package/dist/src/data/sources/lazy/bigWigSource.js +11 -10
  17. package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +1 -1
  18. package/dist/src/data/sources/lazy/tabixSource.d.ts +0 -1
  19. package/dist/src/data/sources/lazy/tabixSource.d.ts.map +1 -1
  20. package/dist/src/data/sources/lazy/tabixSource.js +41 -11
  21. package/dist/src/data/sources/sequenceSource.d.ts.map +1 -1
  22. package/dist/src/data/sources/sequenceSource.js +5 -3
  23. package/dist/src/data/sources/urlSource.d.ts.map +1 -1
  24. package/dist/src/data/sources/urlSource.js +7 -3
  25. package/dist/src/data/transforms/filter.d.ts +4 -4
  26. package/dist/src/data/transforms/filter.d.ts.map +1 -1
  27. package/dist/src/data/transforms/filter.js +13 -7
  28. package/dist/src/data/transforms/filterScoredLabels.d.ts.map +1 -1
  29. package/dist/src/data/transforms/filterScoredLabels.js +11 -6
  30. package/dist/src/data/transforms/filterScoredLabels.test.d.ts +2 -0
  31. package/dist/src/data/transforms/filterScoredLabels.test.d.ts.map +1 -0
  32. package/dist/src/data/transforms/formula.d.ts +4 -4
  33. package/dist/src/data/transforms/formula.d.ts.map +1 -1
  34. package/dist/src/data/transforms/formula.js +12 -6
  35. package/dist/src/data/transforms/measureText.d.ts +2 -2
  36. package/dist/src/data/transforms/measureText.d.ts.map +1 -1
  37. package/dist/src/data/transforms/measureText.js +16 -12
  38. package/dist/src/data/transforms/transform.d.ts +2 -2
  39. package/dist/src/data/transforms/transform.d.ts.map +1 -1
  40. package/dist/src/data/transforms/transform.js +3 -3
  41. package/dist/src/encoder/accessor.d.ts +8 -4
  42. package/dist/src/encoder/accessor.d.ts.map +1 -1
  43. package/dist/src/encoder/accessor.js +10 -10
  44. package/dist/src/encoder/encoder.js +5 -5
  45. package/dist/src/genome/genome.d.ts +8 -0
  46. package/dist/src/genome/genome.d.ts.map +1 -1
  47. package/dist/src/genome/genome.js +16 -1
  48. package/dist/src/genomeSpy/inputBindingManager.js +1 -1
  49. package/dist/src/genomeSpy/interactionController.d.ts.map +1 -1
  50. package/dist/src/genomeSpy/interactionController.js +7 -1
  51. package/dist/src/genomeSpy.js +1 -1
  52. package/dist/src/gl/glslScaleGenerator.js +1 -1
  53. package/dist/src/marks/mark.d.ts.map +1 -1
  54. package/dist/src/marks/mark.js +22 -30
  55. package/dist/src/marks/point.d.ts.map +1 -1
  56. package/dist/src/marks/point.js +4 -6
  57. package/dist/src/paramRuntime/expressionCompiler.d.ts +7 -0
  58. package/dist/src/paramRuntime/expressionCompiler.d.ts.map +1 -0
  59. package/dist/src/paramRuntime/expressionCompiler.js +10 -0
  60. package/dist/src/paramRuntime/expressionRef.d.ts +20 -0
  61. package/dist/src/paramRuntime/expressionRef.d.ts.map +1 -0
  62. package/dist/src/paramRuntime/expressionRef.js +95 -0
  63. package/dist/src/paramRuntime/expressionRef.test.d.ts +2 -0
  64. package/dist/src/paramRuntime/expressionRef.test.d.ts.map +1 -0
  65. package/dist/src/paramRuntime/graphRuntime.d.ts +176 -0
  66. package/dist/src/paramRuntime/graphRuntime.d.ts.map +1 -0
  67. package/dist/src/paramRuntime/graphRuntime.js +628 -0
  68. package/dist/src/paramRuntime/graphRuntime.test.d.ts +2 -0
  69. package/dist/src/paramRuntime/graphRuntime.test.d.ts.map +1 -0
  70. package/dist/src/paramRuntime/index.d.ts +9 -0
  71. package/dist/src/paramRuntime/index.d.ts.map +1 -0
  72. package/dist/src/paramRuntime/index.js +8 -0
  73. package/dist/src/paramRuntime/lifecycleRegistry.d.ts +27 -0
  74. package/dist/src/paramRuntime/lifecycleRegistry.d.ts.map +1 -0
  75. package/dist/src/paramRuntime/lifecycleRegistry.js +54 -0
  76. package/dist/src/paramRuntime/paramRuntime.d.ts +165 -0
  77. package/dist/src/paramRuntime/paramRuntime.d.ts.map +1 -0
  78. package/dist/src/paramRuntime/paramRuntime.js +222 -0
  79. package/dist/src/paramRuntime/paramRuntime.test.d.ts +2 -0
  80. package/dist/src/paramRuntime/paramRuntime.test.d.ts.map +1 -0
  81. package/dist/src/paramRuntime/paramStore.d.ts +68 -0
  82. package/dist/src/paramRuntime/paramStore.d.ts.map +1 -0
  83. package/dist/src/paramRuntime/paramStore.js +148 -0
  84. package/dist/src/paramRuntime/paramStore.test.d.ts +2 -0
  85. package/dist/src/paramRuntime/paramStore.test.d.ts.map +1 -0
  86. package/dist/src/paramRuntime/paramUtils.d.ts +86 -0
  87. package/dist/src/paramRuntime/paramUtils.d.ts.map +1 -0
  88. package/dist/src/paramRuntime/paramUtils.js +272 -0
  89. package/dist/src/paramRuntime/selectionStore.d.ts +6 -0
  90. package/dist/src/paramRuntime/selectionStore.d.ts.map +1 -0
  91. package/dist/src/paramRuntime/selectionStore.js +13 -0
  92. package/dist/src/paramRuntime/types.d.ts +16 -0
  93. package/dist/src/paramRuntime/types.d.ts.map +1 -0
  94. package/dist/src/paramRuntime/types.js +25 -0
  95. package/dist/src/paramRuntime/viewParamRuntime.d.ts +164 -0
  96. package/dist/src/paramRuntime/viewParamRuntime.d.ts.map +1 -0
  97. package/dist/src/paramRuntime/viewParamRuntime.js +443 -0
  98. package/dist/src/scales/scaleInstanceManager.d.ts +6 -3
  99. package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -1
  100. package/dist/src/scales/scaleInstanceManager.js +17 -11
  101. package/dist/src/scales/scaleResolution.d.ts +1 -0
  102. package/dist/src/scales/scaleResolution.d.ts.map +1 -1
  103. package/dist/src/scales/scaleResolution.js +7 -1
  104. package/dist/src/selection/selection.js +1 -1
  105. package/dist/src/spec/coreSchemaRoot.d.ts +53 -0
  106. package/dist/src/spec/root.d.ts +1 -1
  107. package/dist/src/spec/view.d.ts +114 -33
  108. package/dist/src/tooltip/dataTooltipHandler.d.ts +1 -1
  109. package/dist/src/tooltip/dataTooltipHandler.js +23 -32
  110. package/dist/src/tooltip/dataTooltipHandler.test.d.ts +2 -0
  111. package/dist/src/tooltip/dataTooltipHandler.test.d.ts.map +1 -0
  112. package/dist/src/tooltip/flattenDatumRows.d.ts +13 -0
  113. package/dist/src/tooltip/flattenDatumRows.d.ts.map +1 -0
  114. package/dist/src/tooltip/flattenDatumRows.js +47 -0
  115. package/dist/src/tooltip/flattenDatumRows.test.d.ts +2 -0
  116. package/dist/src/tooltip/flattenDatumRows.test.d.ts.map +1 -0
  117. package/dist/src/tooltip/refseqGeneTooltipHandler.d.ts +1 -1
  118. package/dist/src/tooltip/refseqGeneTooltipHandler.js +7 -1
  119. package/dist/src/tooltip/tooltipContext.d.ts +13 -0
  120. package/dist/src/tooltip/tooltipContext.d.ts.map +1 -0
  121. package/dist/src/tooltip/tooltipContext.js +543 -0
  122. package/dist/src/tooltip/tooltipContext.test.d.ts +2 -0
  123. package/dist/src/tooltip/tooltipContext.test.d.ts.map +1 -0
  124. package/dist/src/tooltip/tooltipHandler.d.ts +40 -1
  125. package/dist/src/tooltip/tooltipHandler.d.ts.map +1 -1
  126. package/dist/src/tooltip/tooltipHandler.ts +62 -1
  127. package/dist/src/types/encoder.d.ts +1 -1
  128. package/dist/src/utils/inputBinding.d.ts +10 -2
  129. package/dist/src/utils/inputBinding.d.ts.map +1 -1
  130. package/dist/src/utils/inputBinding.js +12 -3
  131. package/dist/src/view/flowBuilder.d.ts.map +1 -1
  132. package/dist/src/view/flowBuilder.js +12 -3
  133. package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
  134. package/dist/src/view/gridView/gridChild.js +8 -3
  135. package/dist/src/view/gridView/selectionRect.d.ts +6 -10
  136. package/dist/src/view/gridView/selectionRect.d.ts.map +1 -1
  137. package/dist/src/view/gridView/selectionRect.js +3 -20
  138. package/dist/src/view/layerView.d.ts.map +1 -1
  139. package/dist/src/view/layerView.js +4 -2
  140. package/dist/src/view/multiscale.d.ts +35 -0
  141. package/dist/src/view/multiscale.d.ts.map +1 -0
  142. package/dist/src/view/multiscale.js +233 -0
  143. package/dist/src/view/multiscale.test.d.ts +2 -0
  144. package/dist/src/view/multiscale.test.d.ts.map +1 -0
  145. package/dist/src/view/unitView.d.ts.map +1 -1
  146. package/dist/src/view/unitView.js +10 -4
  147. package/dist/src/view/view.d.ts +5 -4
  148. package/dist/src/view/view.d.ts.map +1 -1
  149. package/dist/src/view/view.js +205 -28
  150. package/dist/src/view/viewFactory.d.ts +0 -12
  151. package/dist/src/view/viewFactory.d.ts.map +1 -1
  152. package/dist/src/view/viewFactory.js +35 -24
  153. package/dist/src/view/viewParamRuntime.test.d.ts +2 -0
  154. package/dist/src/view/viewParamRuntime.test.d.ts.map +1 -0
  155. package/dist/src/view/viewSelectors.d.ts.map +1 -1
  156. package/dist/src/view/viewSelectors.js +8 -5
  157. package/package.json +3 -3
  158. package/dist/src/spec/sampleView.d.ts +0 -197
  159. package/dist/src/view/paramMediator.d.ts +0 -168
  160. package/dist/src/view/paramMediator.d.ts.map +0 -1
  161. package/dist/src/view/paramMediator.js +0 -545
  162. package/dist/src/view/paramMediator.test.d.ts +0 -2
  163. package/dist/src/view/paramMediator.test.d.ts.map +0 -1
@@ -0,0 +1,543 @@
1
+ /**
2
+ * @typedef {import("./tooltipHandler.js").TooltipContext} TooltipContext
3
+ * @typedef {import("./tooltipHandler.js").TooltipGenomicDisplayMode} TooltipGenomicDisplayMode
4
+ * @typedef {import("./tooltipHandler.js").TooltipRow} TooltipRow
5
+ */
6
+
7
+ import { asArray } from "../utils/arrayUtils.js";
8
+ import { flattenDatumRows } from "./flattenDatumRows.js";
9
+
10
+ /** @type {Record<"x" | "y", "x2" | "y2">} */
11
+ const SECONDARY_AXIS = {
12
+ x: "x2",
13
+ y: "y2",
14
+ };
15
+
16
+ /** @type {Set<string>} */
17
+ const GENOMIC_MODES = new Set([
18
+ "auto",
19
+ "locus",
20
+ "interval",
21
+ "endpoints",
22
+ "disabled",
23
+ ]);
24
+
25
+ /** @type {WeakMap<object, Map<string, {groupId: string, chrom: string, pos: string, offset: number, channel: "x" | "y", ambiguous: boolean}>>} */
26
+ const linearizationMappingCache = new WeakMap();
27
+
28
+ /**
29
+ * Creates a stable context object for tooltip handlers.
30
+ *
31
+ * @param {Record<string, any>} datum
32
+ * @param {import("../marks/mark.js").default} mark
33
+ * @param {import("./tooltipHandler.js").TooltipHandlerParams} [params]
34
+ * @returns {TooltipContext}
35
+ */
36
+ export default function createTooltipContext(datum, mark, params) {
37
+ const mappingByLinearizedField = getLinearizationMappings(mark);
38
+
39
+ const xGenomic = buildGenomicRowsForAxis(
40
+ "x",
41
+ datum,
42
+ mark,
43
+ mappingByLinearizedField,
44
+ getConfiguredMode(params, "x")
45
+ );
46
+ const yGenomic = buildGenomicRowsForAxis(
47
+ "y",
48
+ datum,
49
+ mark,
50
+ mappingByLinearizedField,
51
+ getConfiguredMode(params, "y")
52
+ );
53
+
54
+ const useAxisPrefixes =
55
+ xGenomic.rows.length > 0 && yGenomic.rows.length > 0;
56
+
57
+ /** @type {TooltipRow[]} */
58
+ const genomicRows = [
59
+ ...(useAxisPrefixes
60
+ ? prefixGenomicRows("x", xGenomic.rows)
61
+ : xGenomic.rows),
62
+ ...(useAxisPrefixes
63
+ ? prefixGenomicRows("y", yGenomic.rows)
64
+ : yGenomic.rows),
65
+ ];
66
+
67
+ /** @type {Set<string>} */
68
+ const hiddenRowKeys = new Set();
69
+ for (const axisGenomic of [xGenomic, yGenomic]) {
70
+ for (const field of axisGenomic.usedLinearizedFields) {
71
+ const mapping = mappingByLinearizedField.get(field);
72
+ if (!mapping || mapping.ambiguous) {
73
+ continue;
74
+ }
75
+
76
+ const verified = verifyLinearizationMapping(
77
+ datum,
78
+ field,
79
+ mapping,
80
+ mark
81
+ );
82
+ if (verified) {
83
+ hiddenRowKeys.add(mapping.chrom);
84
+ hiddenRowKeys.add(mapping.pos);
85
+ }
86
+ }
87
+ }
88
+
89
+ return {
90
+ hiddenRowKeys: [...hiddenRowKeys],
91
+ genomicRows,
92
+ flattenDatumRows: () => flattenDatumRows(datum),
93
+ formatGenomicLocus: (axis, continuousPos) =>
94
+ formatGenomicLocus(mark, axis, continuousPos),
95
+ formatGenomicInterval: (axis, interval) =>
96
+ formatGenomicInterval(mark, axis, interval),
97
+ };
98
+ }
99
+
100
+ /**
101
+ * @param {import("../marks/mark.js").default} mark
102
+ * @returns {Map<string, {groupId: string, chrom: string, pos: string, offset: number, channel: "x" | "y", ambiguous: boolean}>}
103
+ */
104
+ function getLinearizationMappings(mark) {
105
+ const cached = linearizationMappingCache.get(mark);
106
+ if (cached) {
107
+ return cached;
108
+ }
109
+
110
+ const collected = collectLinearizationMappings(mark);
111
+ linearizationMappingCache.set(mark, collected);
112
+ return collected;
113
+ }
114
+
115
+ /**
116
+ * @param {"x" | "y"} axis
117
+ * @param {TooltipRow[]} rows
118
+ * @returns {TooltipRow[]}
119
+ */
120
+ function prefixGenomicRows(axis, rows) {
121
+ const axisPrefix = axis.toUpperCase() + " ";
122
+ return rows.map((row) => ({
123
+ key: axisPrefix + row.key,
124
+ value: row.value,
125
+ }));
126
+ }
127
+
128
+ /**
129
+ * @param {"x" | "y"} axis
130
+ * @param {Record<string, any>} datum
131
+ * @param {import("../marks/mark.js").default} mark
132
+ * @param {Map<string, {groupId: string, chrom: string, pos: string, offset: number, channel: "x" | "y", ambiguous: boolean}>} mappingByLinearizedField
133
+ * @param {TooltipGenomicDisplayMode} mode
134
+ * @returns {{ rows: TooltipRow[], usedLinearizedFields: Set<string> }}
135
+ */
136
+ function buildGenomicRowsForAxis(
137
+ axis,
138
+ datum,
139
+ mark,
140
+ mappingByLinearizedField,
141
+ mode
142
+ ) {
143
+ /** @type {Set<string>} */
144
+ const usedLinearizedFields = new Set();
145
+
146
+ if (mode === "disabled") {
147
+ return {
148
+ rows: [],
149
+ usedLinearizedFields,
150
+ };
151
+ }
152
+
153
+ const primary = readLocusCoordinate(mark, axis, datum);
154
+ if (!primary) {
155
+ return {
156
+ rows: [],
157
+ usedLinearizedFields,
158
+ };
159
+ }
160
+ if (primary.field) {
161
+ usedLinearizedFields.add(primary.field);
162
+ }
163
+
164
+ const secondary = readLocusCoordinate(mark, SECONDARY_AXIS[axis], datum);
165
+ if (secondary?.field) {
166
+ usedLinearizedFields.add(secondary.field);
167
+ }
168
+
169
+ /** @type {TooltipGenomicDisplayMode} */
170
+ const effectiveMode =
171
+ mode === "auto"
172
+ ? resolveAutoMode(primary, secondary, mappingByLinearizedField)
173
+ : mode;
174
+
175
+ if (effectiveMode === "endpoints" && secondary) {
176
+ const [endpoint1, endpoint2] = orderEndpointsByFieldHints(
177
+ primary,
178
+ secondary,
179
+ mappingByLinearizedField
180
+ );
181
+
182
+ return {
183
+ rows: [
184
+ {
185
+ key: "Endpoint 1",
186
+ value:
187
+ formatGenomicLocus(mark, axis, endpoint1.value) ??
188
+ String(endpoint1.value),
189
+ },
190
+ {
191
+ key: "Endpoint 2",
192
+ value:
193
+ formatGenomicLocus(mark, axis, endpoint2.value) ??
194
+ String(endpoint2.value),
195
+ },
196
+ ],
197
+ usedLinearizedFields,
198
+ };
199
+ }
200
+
201
+ if (effectiveMode === "interval" && secondary) {
202
+ return {
203
+ rows: [
204
+ {
205
+ key: "Interval",
206
+ value:
207
+ formatGenomicInterval(mark, axis, [
208
+ primary.value,
209
+ secondary.value,
210
+ ]) ?? primary.value + " - " + secondary.value,
211
+ },
212
+ ],
213
+ usedLinearizedFields,
214
+ };
215
+ }
216
+
217
+ return {
218
+ rows: [
219
+ {
220
+ key: "Coordinate",
221
+ value:
222
+ formatGenomicLocus(mark, axis, primary.value) ??
223
+ String(primary.value),
224
+ },
225
+ ],
226
+ usedLinearizedFields,
227
+ };
228
+ }
229
+
230
+ /**
231
+ * @param {{ field?: string }} primary
232
+ * @param {{ field?: string } | undefined} secondary
233
+ * @param {Map<string, {groupId: string, chrom: string, pos: string, offset: number, channel: "x" | "y", ambiguous: boolean}>} mappingByLinearizedField
234
+ * @returns {TooltipGenomicDisplayMode}
235
+ */
236
+ function resolveAutoMode(primary, secondary, mappingByLinearizedField) {
237
+ if (!secondary) {
238
+ return "locus";
239
+ }
240
+
241
+ const primaryGroup = getMappingGroup(
242
+ primary.field,
243
+ mappingByLinearizedField
244
+ );
245
+ const secondaryGroup = getMappingGroup(
246
+ secondary.field,
247
+ mappingByLinearizedField
248
+ );
249
+
250
+ if (primaryGroup && secondaryGroup && primaryGroup !== secondaryGroup) {
251
+ return "endpoints";
252
+ }
253
+
254
+ // Default to interval when grouping cannot be inferred.
255
+ return "interval";
256
+ }
257
+
258
+ /**
259
+ * @param {string | undefined} field
260
+ * @param {Map<string, {groupId: string, chrom: string, pos: string, offset: number, channel: "x" | "y", ambiguous: boolean}>} mappingByLinearizedField
261
+ */
262
+ function getMappingGroup(field, mappingByLinearizedField) {
263
+ const mapping = field ? mappingByLinearizedField.get(field) : undefined;
264
+ return mapping && !mapping.ambiguous ? mapping.groupId : undefined;
265
+ }
266
+
267
+ /**
268
+ * Heuristically preserves endpoint numbering from source fields. If one
269
+ * endpoint is recognized as "2" (or the other as "1"), swap display order.
270
+ *
271
+ * @param {{ value: number, field?: string }} primary
272
+ * @param {{ value: number, field?: string }} secondary
273
+ * @param {Map<string, {groupId: string, chrom: string, pos: string, offset: number, channel: "x" | "y", ambiguous: boolean}>} mappingByLinearizedField
274
+ */
275
+ function orderEndpointsByFieldHints(
276
+ primary,
277
+ secondary,
278
+ mappingByLinearizedField
279
+ ) {
280
+ const firstHint = inferEndpointOrdinal(
281
+ primary.field,
282
+ mappingByLinearizedField
283
+ );
284
+ const secondHint = inferEndpointOrdinal(
285
+ secondary.field,
286
+ mappingByLinearizedField
287
+ );
288
+
289
+ if (firstHint === 2 && secondHint !== 2) {
290
+ return [secondary, primary];
291
+ }
292
+
293
+ if (secondHint === 1 && firstHint !== 1) {
294
+ return [secondary, primary];
295
+ }
296
+
297
+ if (firstHint === 2 && secondHint === 1) {
298
+ return [secondary, primary];
299
+ }
300
+
301
+ return [primary, secondary];
302
+ }
303
+
304
+ /**
305
+ * @param {string | undefined} linearizedField
306
+ * @param {Map<string, {groupId: string, chrom: string, pos: string, offset: number, channel: "x" | "y", ambiguous: boolean}>} mappingByLinearizedField
307
+ * @returns {1 | 2 | undefined}
308
+ */
309
+ function inferEndpointOrdinal(linearizedField, mappingByLinearizedField) {
310
+ const mapping = linearizedField
311
+ ? mappingByLinearizedField.get(linearizedField)
312
+ : undefined;
313
+ /** @type {(string | undefined)[]} */
314
+ const candidates = [mapping?.pos, mapping?.chrom, linearizedField];
315
+
316
+ /** @type {1 | 2 | undefined} */
317
+ let ordinal;
318
+ for (const candidate of candidates) {
319
+ const detected = detectEndpointOrdinal(candidate);
320
+ if (detected === undefined) {
321
+ continue;
322
+ }
323
+ if (ordinal === undefined) {
324
+ ordinal = detected;
325
+ } else if (ordinal !== detected) {
326
+ return undefined;
327
+ }
328
+ }
329
+
330
+ return ordinal;
331
+ }
332
+
333
+ /**
334
+ * @param {string | undefined} fieldName
335
+ * @returns {1 | 2 | undefined}
336
+ */
337
+ function detectEndpointOrdinal(fieldName) {
338
+ if (!fieldName) {
339
+ return undefined;
340
+ }
341
+
342
+ const normalized = fieldName.toLowerCase();
343
+
344
+ const trailing = normalized.match(/(?:^|[^0-9])(1|2)$/);
345
+ if (trailing) {
346
+ return trailing[1] === "1" ? 1 : 2;
347
+ }
348
+
349
+ const word = normalized.match(/(?:^|[_-])(first|second)(?:[_-]|$)/);
350
+ if (word) {
351
+ return word[1] === "first" ? 1 : 2;
352
+ }
353
+
354
+ return undefined;
355
+ }
356
+
357
+ /**
358
+ * @param {import("../marks/mark.js").default} mark
359
+ * @param {"x" | "x2" | "y" | "y2"} channel
360
+ * @param {Record<string, any>} datum
361
+ * @returns {{ value: number, field?: string } | undefined}
362
+ */
363
+ function readLocusCoordinate(mark, channel, datum) {
364
+ const encoder = mark.encoders?.[channel];
365
+ if (encoder?.scale?.type !== "locus") {
366
+ return;
367
+ }
368
+
369
+ const accessor = encoder.dataAccessor;
370
+ if (!accessor) {
371
+ return;
372
+ }
373
+
374
+ const value = +accessor(datum);
375
+ if (!Number.isFinite(value)) {
376
+ return;
377
+ }
378
+
379
+ return {
380
+ value,
381
+ field: accessor.fields?.length === 1 ? accessor.fields[0] : undefined,
382
+ };
383
+ }
384
+
385
+ /**
386
+ * @param {import("../marks/mark.js").default} mark
387
+ * @returns {Map<string, {groupId: string, chrom: string, pos: string, offset: number, channel: "x" | "y", ambiguous: boolean}>}
388
+ */
389
+ function collectLinearizationMappings(mark) {
390
+ /** @type {Map<string, {groupId: string, chrom: string, pos: string, offset: number, channel: "x" | "y", ambiguous: boolean}>} */
391
+ const mappingByField = new Map();
392
+
393
+ let group = 0;
394
+ const collector = mark.unitView?.getCollector?.();
395
+ let current = collector?.parent;
396
+ while (current) {
397
+ const params = /** @type {any} */ (current).params;
398
+ if (params?.type === "linearizeGenomicCoordinate") {
399
+ const as = asArray(params.as);
400
+ const pos = asArray(params.pos);
401
+ const offsets = normalizeOffsets(params.offset, pos.length);
402
+ const groupId = "g" + group++;
403
+ const channel = params.channel === "y" ? "y" : "x";
404
+
405
+ for (let i = 0; i < as.length; i++) {
406
+ if (i >= pos.length) {
407
+ continue;
408
+ }
409
+
410
+ const existing = mappingByField.get(as[i]);
411
+ if (existing) {
412
+ existing.ambiguous = true;
413
+ } else {
414
+ mappingByField.set(as[i], {
415
+ groupId,
416
+ chrom: params.chrom,
417
+ pos: pos[i],
418
+ offset: offsets[i],
419
+ channel,
420
+ ambiguous: false,
421
+ });
422
+ }
423
+ }
424
+ }
425
+ current = current.parent;
426
+ }
427
+
428
+ return mappingByField;
429
+ }
430
+
431
+ /**
432
+ * @param {number | number[] | undefined} offset
433
+ * @param {number} count
434
+ * @returns {number[]}
435
+ */
436
+ function normalizeOffsets(offset, count) {
437
+ const offsets = asArray(offset);
438
+
439
+ if (offsets.length === 0) {
440
+ return new Array(count).fill(0);
441
+ }
442
+ if (offsets.length === 1) {
443
+ return new Array(count).fill(offsets[0]);
444
+ }
445
+ if (offsets.length === count) {
446
+ return offsets;
447
+ }
448
+
449
+ return new Array(count).fill(0);
450
+ }
451
+
452
+ /**
453
+ * @param {import("../marks/mark.js").default} mark
454
+ * @param {"x" | "y"} axis
455
+ * @param {number} continuousPos
456
+ * @returns {string | undefined}
457
+ */
458
+ function formatGenomicLocus(mark, axis, continuousPos) {
459
+ const genome = getGenome(mark, axis);
460
+ return genome?.formatLocus(continuousPos);
461
+ }
462
+
463
+ /**
464
+ * @param {import("../marks/mark.js").default} mark
465
+ * @param {"x" | "y"} axis
466
+ * @param {[number, number]} interval
467
+ * @returns {string | undefined}
468
+ */
469
+ function formatGenomicInterval(mark, axis, interval) {
470
+ const genome = getGenome(mark, axis);
471
+ return genome?.formatInterval(interval);
472
+ }
473
+
474
+ /**
475
+ * @param {import("../marks/mark.js").default} mark
476
+ * @param {"x" | "y"} axis
477
+ */
478
+ function getGenome(mark, axis) {
479
+ const scale = mark.encoders?.[axis]?.scale;
480
+ return scale?.type === "locus" && "genome" in scale
481
+ ? scale.genome()
482
+ : undefined;
483
+ }
484
+
485
+ /**
486
+ * @param {import("./tooltipHandler.js").TooltipHandlerParams | undefined} params
487
+ * @param {"x" | "y"} axis
488
+ * @returns {TooltipGenomicDisplayMode}
489
+ */
490
+ function getConfiguredMode(params, axis) {
491
+ const configured = params?.genomicCoordinates?.[axis];
492
+ const mode =
493
+ typeof configured === "string"
494
+ ? configured
495
+ : (configured?.mode ?? "auto");
496
+
497
+ if (!GENOMIC_MODES.has(mode)) {
498
+ throw new Error(
499
+ 'Unknown genomic coordinate display mode: "' + mode + '"'
500
+ );
501
+ }
502
+
503
+ return /** @type {TooltipGenomicDisplayMode} */ (mode);
504
+ }
505
+
506
+ /**
507
+ * @param {Record<string, any>} datum
508
+ * @param {string} linearizedField
509
+ * @param {{ chrom: string, pos: string, offset: number, channel: "x" | "y", ambiguous: boolean }} mapping
510
+ * @param {import("../marks/mark.js").default} mark
511
+ */
512
+ function verifyLinearizationMapping(datum, linearizedField, mapping, mark) {
513
+ // TODO: This picks a genome by transform channel. In mixed-axis or
514
+ // mixed-assembly cases, verify against both active genomic axes.
515
+ const genome = getGenome(mark, mapping.channel);
516
+ if (!genome) {
517
+ return false;
518
+ }
519
+
520
+ const chrom = datum[mapping.chrom];
521
+ const pos = datum[mapping.pos];
522
+ const linearized = datum[linearizedField];
523
+
524
+ if (chrom === undefined || pos === undefined || linearized === undefined) {
525
+ return false;
526
+ }
527
+
528
+ const numericPos = +pos;
529
+ const numericLinearized = +linearized;
530
+
531
+ if (!Number.isFinite(numericPos) || !Number.isFinite(numericLinearized)) {
532
+ return false;
533
+ }
534
+
535
+ let expected;
536
+ try {
537
+ expected = genome.toContinuous(chrom, numericPos - mapping.offset);
538
+ } catch (_error) {
539
+ return false;
540
+ }
541
+
542
+ return Math.abs(expected - numericLinearized) < 1e-6;
543
+ }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tooltipContext.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltipContext.test.d.ts","sourceRoot":"","sources":["../../../src/tooltip/tooltipContext.test.js"],"names":[],"mappings":""}
@@ -1,9 +1,48 @@
1
1
  import { TemplateResult } from "lit";
2
2
  import Mark from "../marks/mark.js";
3
+ export type TooltipGenomicDisplayMode = "auto" | "locus" | "interval" | "endpoints" | "disabled";
4
+ export interface TooltipGenomicAxisConfig {
5
+ mode?: TooltipGenomicDisplayMode;
6
+ }
7
+ export interface TooltipHandlerParams {
8
+ genomicCoordinates?: Partial<Record<"x" | "y", TooltipGenomicAxisConfig | TooltipGenomicDisplayMode>>;
9
+ [key: string]: any;
10
+ }
11
+ export interface TooltipRow {
12
+ key: string;
13
+ value: any;
14
+ }
15
+ export interface TooltipContext {
16
+ /**
17
+ * A list of row keys that should be hidden from the default tooltip table.
18
+ */
19
+ hiddenRowKeys?: string[];
20
+ /**
21
+ * Derived genomic rows to render before raw data rows.
22
+ */
23
+ genomicRows?: TooltipRow[];
24
+ /**
25
+ * Utility for flattening datum fields into tooltip rows.
26
+ */
27
+ flattenDatumRows?: () => TooltipRow[];
28
+ /**
29
+ * Utility for formatting a continuous genomic position.
30
+ */
31
+ formatGenomicLocus?: (axis: "x" | "y", continuousPos: number) => string | undefined;
32
+ /**
33
+ * Utility for formatting a continuous genomic interval.
34
+ */
35
+ formatGenomicInterval?: (axis: "x" | "y", interval: [number, number]) => string | undefined;
36
+ }
3
37
  /**
4
38
  * Converts a datum to tooltip (HTMLElement or lit's TemplateResult).
39
+ *
40
+ * TODO: `mark` leaks internals. Keep it for compatibility in this major
41
+ * version and replace with a stable tooltip context in the next major.
5
42
  */
6
43
  export type TooltipHandler = (datum: Record<string, any>, mark: Mark,
7
44
  /** Optional parameters from the view specification */
8
- params?: Record<string, any>) => Promise<string | TemplateResult | HTMLElement>;
45
+ params?: TooltipHandlerParams,
46
+ /** Optional precomputed context from GenomeSpy internals */
47
+ context?: TooltipContext) => Promise<string | TemplateResult | HTMLElement>;
9
48
  //# sourceMappingURL=tooltipHandler.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tooltipHandler.d.ts","sourceRoot":"","sources":["../../../src/tooltip/tooltipHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AACrC,OAAO,IAAI,MAAM,kBAAkB,CAAC;AAEpC;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,CACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1B,IAAI,EAAE,IAAI;AACV,sDAAsD;AACtD,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,KAC3B,OAAO,CAAC,MAAM,GAAG,cAAc,GAAG,WAAW,CAAC,CAAC"}
1
+ {"version":3,"file":"tooltipHandler.d.ts","sourceRoot":"","sources":["../../../src/tooltip/tooltipHandler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,KAAK,CAAC;AACrC,OAAO,IAAI,MAAM,kBAAkB,CAAC;AAEpC,MAAM,MAAM,yBAAyB,GAC/B,MAAM,GACN,OAAO,GACP,UAAU,GACV,WAAW,GACX,UAAU,CAAC;AAEjB,MAAM,WAAW,wBAAwB;IACrC,IAAI,CAAC,EAAE,yBAAyB,CAAC;CACpC;AAED,MAAM,WAAW,oBAAoB;IACjC,kBAAkB,CAAC,EAAE,OAAO,CACxB,MAAM,CAAC,GAAG,GAAG,GAAG,EAAE,wBAAwB,GAAG,yBAAyB,CAAC,CAC1E,CAAC;IACF,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACvB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,GAAG,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC3B;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IAEzB;;OAEG;IACH,WAAW,CAAC,EAAE,UAAU,EAAE,CAAC;IAE3B;;OAEG;IACH,gBAAgB,CAAC,EAAE,MAAM,UAAU,EAAE,CAAC;IAEtC;;OAEG;IACH,kBAAkB,CAAC,EAAE,CACjB,IAAI,EAAE,GAAG,GAAG,GAAG,EACf,aAAa,EAAE,MAAM,KACpB,MAAM,GAAG,SAAS,CAAC;IAExB;;OAEG;IACH,qBAAqB,CAAC,EAAE,CACpB,IAAI,EAAE,GAAG,GAAG,GAAG,EACf,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,KACzB,MAAM,GAAG,SAAS,CAAC;CAC3B;AAED;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,CACzB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC1B,IAAI,EAAE,IAAI;AACV,sDAAsD;AACtD,MAAM,CAAC,EAAE,oBAAoB;AAC7B,4DAA4D;AAC5D,OAAO,CAAC,EAAE,cAAc,KACvB,OAAO,CAAC,MAAM,GAAG,cAAc,GAAG,WAAW,CAAC,CAAC"}
@@ -1,12 +1,73 @@
1
1
  import { TemplateResult } from "lit";
2
2
  import Mark from "../marks/mark.js";
3
3
 
4
+ export type TooltipGenomicDisplayMode =
5
+ | "auto"
6
+ | "locus"
7
+ | "interval"
8
+ | "endpoints"
9
+ | "disabled";
10
+
11
+ export interface TooltipGenomicAxisConfig {
12
+ mode?: TooltipGenomicDisplayMode;
13
+ }
14
+
15
+ export interface TooltipHandlerParams {
16
+ genomicCoordinates?: Partial<
17
+ Record<"x" | "y", TooltipGenomicAxisConfig | TooltipGenomicDisplayMode>
18
+ >;
19
+ [key: string]: any;
20
+ }
21
+
22
+ export interface TooltipRow {
23
+ key: string;
24
+ value: any;
25
+ }
26
+
27
+ export interface TooltipContext {
28
+ /**
29
+ * A list of row keys that should be hidden from the default tooltip table.
30
+ */
31
+ hiddenRowKeys?: string[];
32
+
33
+ /**
34
+ * Derived genomic rows to render before raw data rows.
35
+ */
36
+ genomicRows?: TooltipRow[];
37
+
38
+ /**
39
+ * Utility for flattening datum fields into tooltip rows.
40
+ */
41
+ flattenDatumRows?: () => TooltipRow[];
42
+
43
+ /**
44
+ * Utility for formatting a continuous genomic position.
45
+ */
46
+ formatGenomicLocus?: (
47
+ axis: "x" | "y",
48
+ continuousPos: number
49
+ ) => string | undefined;
50
+
51
+ /**
52
+ * Utility for formatting a continuous genomic interval.
53
+ */
54
+ formatGenomicInterval?: (
55
+ axis: "x" | "y",
56
+ interval: [number, number]
57
+ ) => string | undefined;
58
+ }
59
+
4
60
  /**
5
61
  * Converts a datum to tooltip (HTMLElement or lit's TemplateResult).
62
+ *
63
+ * TODO: `mark` leaks internals. Keep it for compatibility in this major
64
+ * version and replace with a stable tooltip context in the next major.
6
65
  */
7
66
  export type TooltipHandler = (
8
67
  datum: Record<string, any>,
9
68
  mark: Mark,
10
69
  /** Optional parameters from the view specification */
11
- params?: Record<string, any>
70
+ params?: TooltipHandlerParams,
71
+ /** Optional precomputed context from GenomeSpy internals */
72
+ context?: TooltipContext
12
73
  ) => Promise<string | TemplateResult | HTMLElement>;
@@ -25,7 +25,7 @@ import { ScaleLocus } from "../genome/scaleLocus.js";
25
25
  import { ScaleIndex } from "../genome/scaleIndex.js";
26
26
  import { Scalar } from "../spec/channel.js";
27
27
  import { Datum } from "../data/flowNode.js";
28
- import { ExprRefFunction } from "../view/paramMediator.js";
28
+ import { ExprRefFunction } from "../paramRuntime/types.js";
29
29
 
30
30
  export interface Accessor<T = Scalar> {
31
31
  (datum: Datum): T;