@genome-spy/core 0.68.0 → 0.69.1
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.
- package/dist/bundle/index.es.js +12119 -10681
- package/dist/bundle/index.js +119 -119
- package/dist/schema.json +6224 -6319
- package/dist/src/data/dataFlow.d.ts.map +1 -1
- package/dist/src/data/dataFlow.js +10 -0
- package/dist/src/data/flowNode.d.ts +25 -10
- package/dist/src/data/flowNode.d.ts.map +1 -1
- package/dist/src/data/flowNode.js +66 -13
- package/dist/src/data/flowTestUtils.d.ts +2 -2
- package/dist/src/data/flowTestUtils.d.ts.map +1 -1
- package/dist/src/data/flowTestUtils.js +5 -4
- package/dist/src/data/sources/dataSource.js +2 -2
- package/dist/src/data/sources/lazy/bigBedSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/bigBedSource.js +11 -10
- package/dist/src/data/sources/lazy/bigWigSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/bigWigSource.js +11 -10
- package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +1 -1
- package/dist/src/data/sources/lazy/tabixSource.d.ts +0 -1
- package/dist/src/data/sources/lazy/tabixSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/tabixSource.js +41 -11
- package/dist/src/data/sources/sequenceSource.d.ts.map +1 -1
- package/dist/src/data/sources/sequenceSource.js +5 -3
- package/dist/src/data/sources/urlSource.d.ts.map +1 -1
- package/dist/src/data/sources/urlSource.js +7 -3
- package/dist/src/data/transforms/filter.d.ts +4 -4
- package/dist/src/data/transforms/filter.d.ts.map +1 -1
- package/dist/src/data/transforms/filter.js +13 -7
- package/dist/src/data/transforms/filterScoredLabels.d.ts.map +1 -1
- package/dist/src/data/transforms/filterScoredLabels.js +11 -6
- package/dist/src/data/transforms/filterScoredLabels.test.d.ts +2 -0
- package/dist/src/data/transforms/filterScoredLabels.test.d.ts.map +1 -0
- package/dist/src/data/transforms/formula.d.ts +4 -4
- package/dist/src/data/transforms/formula.d.ts.map +1 -1
- package/dist/src/data/transforms/formula.js +12 -6
- package/dist/src/data/transforms/measureText.d.ts +2 -2
- package/dist/src/data/transforms/measureText.d.ts.map +1 -1
- package/dist/src/data/transforms/measureText.js +16 -12
- package/dist/src/data/transforms/transform.d.ts +2 -2
- package/dist/src/data/transforms/transform.d.ts.map +1 -1
- package/dist/src/data/transforms/transform.js +3 -3
- package/dist/src/encoder/accessor.d.ts +8 -4
- package/dist/src/encoder/accessor.d.ts.map +1 -1
- package/dist/src/encoder/accessor.js +10 -10
- package/dist/src/encoder/encoder.js +5 -5
- package/dist/src/genome/genome.d.ts +8 -0
- package/dist/src/genome/genome.d.ts.map +1 -1
- package/dist/src/genome/genome.js +16 -1
- package/dist/src/genomeSpy/inputBindingManager.js +1 -1
- package/dist/src/genomeSpy/interactionController.d.ts.map +1 -1
- package/dist/src/genomeSpy/interactionController.js +7 -1
- package/dist/src/genomeSpy.js +1 -1
- package/dist/src/gl/glslScaleGenerator.js +1 -1
- package/dist/src/marks/mark.d.ts.map +1 -1
- package/dist/src/marks/mark.js +22 -30
- package/dist/src/marks/point.d.ts.map +1 -1
- package/dist/src/marks/point.js +4 -6
- package/dist/src/paramRuntime/expressionCompiler.d.ts +7 -0
- package/dist/src/paramRuntime/expressionCompiler.d.ts.map +1 -0
- package/dist/src/paramRuntime/expressionCompiler.js +10 -0
- package/dist/src/paramRuntime/expressionRef.d.ts +20 -0
- package/dist/src/paramRuntime/expressionRef.d.ts.map +1 -0
- package/dist/src/paramRuntime/expressionRef.js +95 -0
- package/dist/src/paramRuntime/expressionRef.test.d.ts +2 -0
- package/dist/src/paramRuntime/expressionRef.test.d.ts.map +1 -0
- package/dist/src/paramRuntime/graphRuntime.d.ts +176 -0
- package/dist/src/paramRuntime/graphRuntime.d.ts.map +1 -0
- package/dist/src/paramRuntime/graphRuntime.js +628 -0
- package/dist/src/paramRuntime/graphRuntime.test.d.ts +2 -0
- package/dist/src/paramRuntime/graphRuntime.test.d.ts.map +1 -0
- package/dist/src/paramRuntime/index.d.ts +9 -0
- package/dist/src/paramRuntime/index.d.ts.map +1 -0
- package/dist/src/paramRuntime/index.js +8 -0
- package/dist/src/paramRuntime/lifecycleRegistry.d.ts +27 -0
- package/dist/src/paramRuntime/lifecycleRegistry.d.ts.map +1 -0
- package/dist/src/paramRuntime/lifecycleRegistry.js +54 -0
- package/dist/src/paramRuntime/paramRuntime.d.ts +165 -0
- package/dist/src/paramRuntime/paramRuntime.d.ts.map +1 -0
- package/dist/src/paramRuntime/paramRuntime.js +222 -0
- package/dist/src/paramRuntime/paramRuntime.test.d.ts +2 -0
- package/dist/src/paramRuntime/paramRuntime.test.d.ts.map +1 -0
- package/dist/src/paramRuntime/paramStore.d.ts +68 -0
- package/dist/src/paramRuntime/paramStore.d.ts.map +1 -0
- package/dist/src/paramRuntime/paramStore.js +148 -0
- package/dist/src/paramRuntime/paramStore.test.d.ts +2 -0
- package/dist/src/paramRuntime/paramStore.test.d.ts.map +1 -0
- package/dist/src/paramRuntime/paramUtils.d.ts +86 -0
- package/dist/src/paramRuntime/paramUtils.d.ts.map +1 -0
- package/dist/src/paramRuntime/paramUtils.js +272 -0
- package/dist/src/paramRuntime/selectionStore.d.ts +6 -0
- package/dist/src/paramRuntime/selectionStore.d.ts.map +1 -0
- package/dist/src/paramRuntime/selectionStore.js +13 -0
- package/dist/src/paramRuntime/types.d.ts +16 -0
- package/dist/src/paramRuntime/types.d.ts.map +1 -0
- package/dist/src/paramRuntime/types.js +25 -0
- package/dist/src/paramRuntime/viewParamRuntime.d.ts +164 -0
- package/dist/src/paramRuntime/viewParamRuntime.d.ts.map +1 -0
- package/dist/src/paramRuntime/viewParamRuntime.js +443 -0
- package/dist/src/scales/scaleInstanceManager.d.ts +6 -3
- package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -1
- package/dist/src/scales/scaleInstanceManager.js +17 -11
- package/dist/src/scales/scaleResolution.d.ts +1 -0
- package/dist/src/scales/scaleResolution.d.ts.map +1 -1
- package/dist/src/scales/scaleResolution.js +7 -1
- package/dist/src/selection/selection.js +1 -1
- package/dist/src/spec/coreSchemaRoot.d.ts +53 -0
- package/dist/src/spec/root.d.ts +1 -1
- package/dist/src/spec/view.d.ts +114 -33
- package/dist/src/tooltip/dataTooltipHandler.d.ts +1 -1
- package/dist/src/tooltip/dataTooltipHandler.js +23 -32
- package/dist/src/tooltip/dataTooltipHandler.test.d.ts +2 -0
- package/dist/src/tooltip/dataTooltipHandler.test.d.ts.map +1 -0
- package/dist/src/tooltip/flattenDatumRows.d.ts +13 -0
- package/dist/src/tooltip/flattenDatumRows.d.ts.map +1 -0
- package/dist/src/tooltip/flattenDatumRows.js +47 -0
- package/dist/src/tooltip/flattenDatumRows.test.d.ts +2 -0
- package/dist/src/tooltip/flattenDatumRows.test.d.ts.map +1 -0
- package/dist/src/tooltip/refseqGeneTooltipHandler.d.ts +1 -1
- package/dist/src/tooltip/refseqGeneTooltipHandler.js +7 -1
- package/dist/src/tooltip/tooltipContext.d.ts +13 -0
- package/dist/src/tooltip/tooltipContext.d.ts.map +1 -0
- package/dist/src/tooltip/tooltipContext.js +543 -0
- package/dist/src/tooltip/tooltipContext.test.d.ts +2 -0
- package/dist/src/tooltip/tooltipContext.test.d.ts.map +1 -0
- package/dist/src/tooltip/tooltipHandler.d.ts +40 -1
- package/dist/src/tooltip/tooltipHandler.d.ts.map +1 -1
- package/dist/src/tooltip/tooltipHandler.ts +62 -1
- package/dist/src/types/encoder.d.ts +1 -1
- package/dist/src/utils/inputBinding.d.ts +10 -2
- package/dist/src/utils/inputBinding.d.ts.map +1 -1
- package/dist/src/utils/inputBinding.js +12 -3
- package/dist/src/view/flowBuilder.d.ts.map +1 -1
- package/dist/src/view/flowBuilder.js +12 -3
- package/dist/src/view/gridView/gridChild.d.ts.map +1 -1
- package/dist/src/view/gridView/gridChild.js +8 -3
- package/dist/src/view/gridView/selectionRect.d.ts +6 -10
- package/dist/src/view/gridView/selectionRect.d.ts.map +1 -1
- package/dist/src/view/gridView/selectionRect.js +3 -20
- package/dist/src/view/layerView.d.ts.map +1 -1
- package/dist/src/view/layerView.js +4 -2
- package/dist/src/view/multiscale.d.ts +35 -0
- package/dist/src/view/multiscale.d.ts.map +1 -0
- package/dist/src/view/multiscale.js +233 -0
- package/dist/src/view/multiscale.test.d.ts +2 -0
- package/dist/src/view/multiscale.test.d.ts.map +1 -0
- package/dist/src/view/unitView.d.ts.map +1 -1
- package/dist/src/view/unitView.js +10 -4
- package/dist/src/view/view.d.ts +5 -4
- package/dist/src/view/view.d.ts.map +1 -1
- package/dist/src/view/view.js +223 -28
- package/dist/src/view/viewFactory.d.ts +0 -12
- package/dist/src/view/viewFactory.d.ts.map +1 -1
- package/dist/src/view/viewFactory.js +35 -24
- package/dist/src/view/viewParamRuntime.test.d.ts +2 -0
- package/dist/src/view/viewParamRuntime.test.d.ts.map +1 -0
- package/dist/src/view/viewSelectors.d.ts.map +1 -1
- package/dist/src/view/viewSelectors.js +8 -5
- package/package.json +3 -3
- package/dist/src/spec/sampleView.d.ts +0 -197
- package/dist/src/view/paramMediator.d.ts +0 -168
- package/dist/src/view/paramMediator.d.ts.map +0 -1
- package/dist/src/view/paramMediator.js +0 -545
- package/dist/src/view/paramMediator.test.d.ts +0 -2
- 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 @@
|
|
|
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?:
|
|
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;;
|
|
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?:
|
|
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 "../
|
|
28
|
+
import { ExprRefFunction } from "../paramRuntime/types.js";
|
|
29
29
|
|
|
30
30
|
export interface Accessor<T = Scalar> {
|
|
31
31
|
(datum: Datum): T;
|