@genome-spy/core 0.43.3 → 0.45.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.
- package/dist/bundle/index.es.js +5231 -4324
- package/dist/bundle/index.js +197 -85
- package/dist/schema.json +723 -104
- package/dist/src/data/collector.d.ts.map +1 -1
- package/dist/src/data/collector.js +4 -2
- package/dist/src/data/flowOptimizer.test.js +12 -3
- package/dist/src/data/sources/dataUtils.d.ts.map +1 -1
- package/dist/src/data/sources/dataUtils.js +3 -1
- package/dist/src/data/sources/lazy/axisTickSource.d.ts +1 -1
- package/dist/src/data/sources/lazy/axisTickSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/axisTickSource.js +2 -2
- package/dist/src/data/sources/lazy/bigBedSource.d.ts +1 -1
- package/dist/src/data/sources/lazy/bigBedSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/bigBedSource.js +52 -20
- package/dist/src/data/sources/lazy/bigWigSource.d.ts +6 -1
- package/dist/src/data/sources/lazy/bigWigSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/bigWigSource.js +33 -9
- package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts +1 -1
- package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/singleAxisLazySource.js +1 -3
- package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts +13 -14
- package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
- package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +70 -48
- package/dist/src/data/sources/sequenceSource.d.ts.map +1 -1
- package/dist/src/data/sources/sequenceSource.js +14 -5
- package/dist/src/data/sources/sequenceSource.test.js +23 -5
- package/dist/src/data/sources/urlSource.d.ts.map +1 -1
- package/dist/src/data/sources/urlSource.js +15 -2
- package/dist/src/data/transforms/aggregate.d.ts.map +1 -1
- package/dist/src/data/transforms/aggregate.js +5 -2
- package/dist/src/data/transforms/filterScoredLabels.js +1 -1
- package/dist/src/encoder/encoder.d.ts +2 -4
- package/dist/src/encoder/encoder.d.ts.map +1 -1
- package/dist/src/encoder/encoder.js +20 -10
- package/dist/src/encoder/encoder.test.js +3 -0
- package/dist/src/genomeSpy.d.ts +8 -5
- package/dist/src/genomeSpy.d.ts.map +1 -1
- package/dist/src/genomeSpy.js +121 -42
- package/dist/src/gl/glslScaleGenerator.d.ts +23 -3
- package/dist/src/gl/glslScaleGenerator.d.ts.map +1 -1
- package/dist/src/gl/glslScaleGenerator.js +137 -42
- package/dist/src/gl/webGLHelper.d.ts.map +1 -1
- package/dist/src/gl/webGLHelper.js +5 -7
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/marks/link.common.glsl.js +2 -0
- package/dist/src/marks/link.d.ts.map +1 -1
- package/dist/src/marks/link.js +19 -9
- package/dist/src/marks/link.vertex.glsl.js +1 -1
- package/dist/src/marks/mark.d.ts +25 -20
- package/dist/src/marks/mark.d.ts.map +1 -1
- package/dist/src/marks/mark.js +234 -129
- package/dist/src/marks/point.common.glsl.js +1 -1
- package/dist/src/marks/point.d.ts +1 -4
- package/dist/src/marks/point.d.ts.map +1 -1
- package/dist/src/marks/point.js +31 -23
- package/dist/src/marks/point.vertex.glsl.js +1 -1
- package/dist/src/marks/rect.common.glsl.js +2 -0
- package/dist/src/marks/rect.d.ts.map +1 -1
- package/dist/src/marks/rect.js +12 -12
- package/dist/src/marks/rect.vertex.glsl.js +1 -1
- package/dist/src/marks/rule.common.glsl.js +1 -1
- package/dist/src/marks/rule.js +2 -2
- package/dist/src/marks/text.common.glsl.js +1 -1
- package/dist/src/marks/text.d.ts.map +1 -1
- package/dist/src/marks/text.js +17 -9
- package/dist/src/spec/channel.d.ts +4 -3
- package/dist/src/spec/data.d.ts +11 -10
- package/dist/src/spec/mark.d.ts +28 -46
- package/dist/src/spec/parameter.d.ts +127 -0
- package/dist/src/spec/root.d.ts +1 -0
- package/dist/src/spec/scale.d.ts +2 -1
- package/dist/src/spec/title.d.ts +5 -4
- package/dist/src/spec/view.d.ts +20 -5
- package/dist/src/styles/genome-spy.css.d.ts +1 -1
- package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
- package/dist/src/styles/genome-spy.css.js +52 -5
- package/dist/src/styles/genome-spy.scss +63 -10
- package/dist/src/styles/update.sh +6 -0
- package/dist/src/tooltip/dataTooltipHandler.js +1 -1
- package/dist/src/tooltip/refseqGeneTooltipHandler.js +1 -1
- package/dist/src/tooltip/tooltipHandler.d.ts +1 -1
- package/dist/src/tooltip/tooltipHandler.d.ts.map +1 -1
- package/dist/src/tooltip/tooltipHandler.ts +1 -1
- package/dist/src/types/embedApi.d.ts +6 -0
- package/dist/src/types/scaleResolutionApi.d.ts +7 -3
- package/dist/src/types/viewContext.d.ts +2 -3
- package/dist/src/utils/debounce.d.ts +2 -2
- package/dist/src/utils/debounce.d.ts.map +1 -1
- package/dist/src/utils/debounce.js +5 -2
- package/dist/src/utils/expression.d.ts +2 -2
- package/dist/src/utils/expression.d.ts.map +1 -1
- package/dist/src/utils/expression.js +3 -3
- package/dist/src/utils/formatObject.d.ts +2 -2
- package/dist/src/utils/formatObject.d.ts.map +1 -1
- package/dist/src/utils/formatObject.js +2 -2
- package/dist/src/utils/inputBinding.d.ts +5 -0
- package/dist/src/utils/inputBinding.d.ts.map +1 -0
- package/dist/src/utils/inputBinding.js +115 -0
- package/dist/src/utils/ui/tooltip.js +1 -1
- package/dist/src/view/axisView.js +3 -3
- package/dist/src/view/paramMediator.d.ts +108 -0
- package/dist/src/view/paramMediator.d.ts.map +1 -0
- package/dist/src/view/paramMediator.js +337 -0
- package/dist/src/view/paramMediator.test.js +211 -0
- package/dist/src/view/scaleResolution.d.ts +8 -18
- package/dist/src/view/scaleResolution.d.ts.map +1 -1
- package/dist/src/view/scaleResolution.js +225 -126
- package/dist/src/view/scaleResolution.test.js +7 -7
- package/dist/src/view/unitView.d.ts.map +1 -1
- package/dist/src/view/unitView.js +10 -3
- package/dist/src/view/view.d.ts +4 -1
- package/dist/src/view/view.d.ts.map +1 -1
- package/dist/src/view/view.js +21 -7
- package/dist/src/view/viewFactory.d.ts.map +1 -1
- package/dist/src/view/viewFactory.js +45 -0
- package/dist/src/view/viewUtils.d.ts +5 -1
- package/dist/src/view/viewUtils.d.ts.map +1 -1
- package/dist/src/view/viewUtils.js +9 -4
- package/package.json +16 -17
- package/dist/src/paramBroker.d.ts +0 -30
- package/dist/src/paramBroker.d.ts.map +0 -1
- package/dist/src/paramBroker.js +0 -102
|
@@ -45,15 +45,15 @@ export default class ScaleResolution implements ScaleResolutionApi {
|
|
|
45
45
|
* e.g., zoomed. The call is synchronous and happens before the views
|
|
46
46
|
* are rendered.
|
|
47
47
|
*
|
|
48
|
-
* @param {
|
|
48
|
+
* @param {ScaleResolutionEventType} type
|
|
49
49
|
* @param {ScaleResolutionListener} listener function
|
|
50
50
|
*/
|
|
51
|
-
addEventListener(type: "
|
|
51
|
+
addEventListener(type: import("../types/scaleResolutionApi.js").ScaleResolutionEventType, listener: import("../types/scaleResolutionApi.js").ScaleResolutionListener): void;
|
|
52
52
|
/**
|
|
53
|
-
* @param {
|
|
53
|
+
* @param {ScaleResolutionEventType} type
|
|
54
54
|
* @param {ScaleResolutionListener} listener function
|
|
55
55
|
*/
|
|
56
|
-
removeEventListener(type: "
|
|
56
|
+
removeEventListener(type: import("../types/scaleResolutionApi.js").ScaleResolutionEventType, listener: import("../types/scaleResolutionApi.js").ScaleResolutionListener): void;
|
|
57
57
|
/**
|
|
58
58
|
* Add a view to this resolution.
|
|
59
59
|
* N.B. This is expected to be called in depth-first order
|
|
@@ -62,18 +62,6 @@ export default class ScaleResolution implements ScaleResolutionApi {
|
|
|
62
62
|
* @param {ChannelWithScale} channel
|
|
63
63
|
*/
|
|
64
64
|
pushUnitView(view: import("./unitView.js").default, channel: import("../spec/channel.js").ChannelWithScale): void;
|
|
65
|
-
/**
|
|
66
|
-
* Returns true if the domain has been defined explicitly, i.e. not extracted from the data.
|
|
67
|
-
*/
|
|
68
|
-
isExplicitDomain(): boolean;
|
|
69
|
-
isDomainInitialized(): boolean;
|
|
70
|
-
/**
|
|
71
|
-
* Returns the merged scale properties supplemented with inferred properties
|
|
72
|
-
* and domain.
|
|
73
|
-
*
|
|
74
|
-
* @returns {import("../spec/scale.js").Scale}
|
|
75
|
-
*/
|
|
76
|
-
getScaleProps(): import("../spec/scale.js").Scale;
|
|
77
65
|
/**
|
|
78
66
|
* Unions the configured domains of all participating views.
|
|
79
67
|
*
|
|
@@ -91,9 +79,11 @@ export default class ScaleResolution implements ScaleResolutionApi {
|
|
|
91
79
|
*/
|
|
92
80
|
reconfigure(): void;
|
|
93
81
|
/**
|
|
94
|
-
* @returns {
|
|
82
|
+
* @returns {ScaleWithProps}
|
|
95
83
|
*/
|
|
96
|
-
|
|
84
|
+
get scale(): import("../types/encoder.js").VegaScale & {
|
|
85
|
+
props: import("../spec/scale.js").Scale;
|
|
86
|
+
};
|
|
97
87
|
getDomain(): any[];
|
|
98
88
|
/**
|
|
99
89
|
* @returns {NumericDomain | ComplexDomain}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaleResolution.d.ts","sourceRoot":"","sources":["../../../src/view/scaleResolution.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scaleResolution.d.ts","sourceRoot":"","sources":["../../../src/view/scaleResolution.js"],"names":[],"mappings":"AA86BA;;;;;;;;;;GAUG;AACH,6CAFW,OAAO,WAAW,EAAE,OAAO,GAAG,OAAO,WAAW,EAAE,OAAO,EAAE,QA4BrE;AAr6BD,0CAA2C;AAC3C,gCAAiC;AACjC,gCAAiC;AACjC,4BAA6B;AAC7B,4BAA6B;AAE7B;;;;GAIG;AACH;;;;;;;GAOG;AACH;IAyCI;;OAEG;IACH,2DASC;IARG,8CAAsB;IACtB,oDAAoD;IACpD,SADW,gBAAgB,EAAE,CACZ;IACjB,+DAA+D;IAC/D,MADW,MAAM,CACD;IAEhB,iEAAiE;IACjE,MADW,MAAM,CACI;IAWzB;;;;;;;OAOG;IACH,4KAEC;IAED;;;OAGG;IACH,+KAEC;IAcD;;;;;;OAMG;IACH,kHA0BC;IAmLD;;;;OAIG;IACH,qEAMC;IAED;;;;OAIG;IACH,+DAQC;IAED;;OAEG;IACH,oBAyCC;IAED;;OAEG;IACH;eAhXkC,OAAO,kBAAkB,EAAE,KAAK;MAuZjE;IAED,mBAEC;IAED;;OAEG;IACH,oBAFa,mFAA6B,CAOzC;IAED;;;;OAIG;IACH,oBAKC;IAED;;OAEG;IACH,sBAGC;IAUD;;;;;;;OAOG;IACH,kBALW,MAAM,eACN,MAAM,OACN,MAAM,GACJ,OAAO,CAwEnB;IAED;;;;;;OAMG;IACH,eAJW,mFAA6B,aAC7B,OAAO,GAAG,MAAM,iBAgD1B;IAED;;;;OAIG;IACH,qBAcC;IAED;;;;;OAKG;IACH,uBAOC;IA8DD;;;OAGG;IACH,aAFa,OAAO,qBAAqB,EAAE,OAAO,CAajD;IAID;;;;;OAKG;IACH,uBAFW,MAAM,yDAUhB;IAED;;OAEG;IACH,iBAFW,MAAM,yDAKhB;IAED;;;OAGG;IACH,6EAFa,MAAM,CAQlB;IAED;;;OAGG;IACH,mHAFa,MAAM,EAAE,CAOpB;;CA6BJ;wIAjyBY;IAAC,IAAI,EAAE,OAAO,eAAe,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,CAAC,CAAA;CAAC"}
|
|
@@ -20,7 +20,6 @@ import { scale as vegaScale, isDiscrete, isContinuous } from "vega-scale";
|
|
|
20
20
|
import mergeObjects from "../utils/mergeObjects.js";
|
|
21
21
|
import createScale, { configureScale } from "../scale/scale.js";
|
|
22
22
|
|
|
23
|
-
import { invalidate, getCachedOrCall } from "../utils/propertyCacher.js";
|
|
24
23
|
import {
|
|
25
24
|
getChannelDefWithScale,
|
|
26
25
|
isColorChannel,
|
|
@@ -37,6 +36,7 @@ import { NominalDomain } from "../utils/domainArray.js";
|
|
|
37
36
|
import { easeCubicInOut } from "d3-ease";
|
|
38
37
|
import { asArray, shallowArrayEquals } from "../utils/arrayUtils.js";
|
|
39
38
|
import eerp from "../utils/eerp.js";
|
|
39
|
+
import { isExprRef } from "./paramMediator.js";
|
|
40
40
|
|
|
41
41
|
// Register scaleLocus to Vega-Scale.
|
|
42
42
|
// Loci are discrete but the scale's domain can be adjusted in a continuous manner.
|
|
@@ -66,6 +66,7 @@ export const INDEX = "index";
|
|
|
66
66
|
export default class ScaleResolution {
|
|
67
67
|
/**
|
|
68
68
|
* @typedef {import("../types/scaleResolutionApi.js").ScaleResolutionApi} ScaleResolutionApi
|
|
69
|
+
* @typedef {import("../types/scaleResolutionApi.js").ScaleResolutionEventType} ScaleResolutionEventType
|
|
69
70
|
* @typedef {import("../spec/channel.js").Channel} Channel
|
|
70
71
|
* @typedef {import("../spec/channel.js").ChannelWithScale} ChannelWithScale
|
|
71
72
|
* @typedef {import("../spec/scale.js").NumericDomain} NumericDomain
|
|
@@ -77,16 +78,31 @@ export default class ScaleResolution {
|
|
|
77
78
|
* @typedef {import("../utils/domainArray.js").DomainArray} DomainArray
|
|
78
79
|
* @typedef {import("../genome/genome.js").ChromosomalLocus} ChromosomalLocus
|
|
79
80
|
* @typedef {import("../types/scaleResolutionApi.js").ScaleResolutionListener} ScaleResolutionListener
|
|
81
|
+
*
|
|
82
|
+
* @typedef {VegaScale & { props: import("../spec/scale.js").Scale }} ScaleWithProps
|
|
80
83
|
*/
|
|
81
84
|
|
|
82
85
|
/** @type {number[]} */
|
|
83
|
-
#zoomExtent
|
|
86
|
+
#zoomExtent;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* @type {Record<ScaleResolutionEventType, Set<ScaleResolutionListener>>}
|
|
90
|
+
*/
|
|
91
|
+
#listeners = {
|
|
92
|
+
domain: new Set(),
|
|
93
|
+
range: new Set(),
|
|
94
|
+
};
|
|
84
95
|
|
|
85
|
-
/** @type {
|
|
86
|
-
#
|
|
96
|
+
/** @type {ScaleWithProps} */
|
|
97
|
+
#scale;
|
|
87
98
|
|
|
88
|
-
/**
|
|
89
|
-
|
|
99
|
+
/**
|
|
100
|
+
* Keeps track of the expression references in the range. If range is modified,
|
|
101
|
+
* new expressions are created and the old ones must be invalidated.
|
|
102
|
+
*
|
|
103
|
+
* @type {Set<import("./paramMediator.js").ExprRefFunction>}
|
|
104
|
+
*/
|
|
105
|
+
#rangeExprRefListeners = new Set();
|
|
90
106
|
|
|
91
107
|
/**
|
|
92
108
|
* @param {Channel} channel
|
|
@@ -102,36 +118,41 @@ export default class ScaleResolution {
|
|
|
102
118
|
this.name = undefined;
|
|
103
119
|
}
|
|
104
120
|
|
|
121
|
+
get #firstMemberView() {
|
|
122
|
+
return this.members[0].view;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
get #viewContext() {
|
|
126
|
+
return this.#firstMemberView.context;
|
|
127
|
+
}
|
|
128
|
+
|
|
105
129
|
/**
|
|
106
130
|
* Adds a listener that is called when the scale domain is changed,
|
|
107
131
|
* e.g., zoomed. The call is synchronous and happens before the views
|
|
108
132
|
* are rendered.
|
|
109
133
|
*
|
|
110
|
-
* @param {
|
|
134
|
+
* @param {ScaleResolutionEventType} type
|
|
111
135
|
* @param {ScaleResolutionListener} listener function
|
|
112
136
|
*/
|
|
113
137
|
addEventListener(type, listener) {
|
|
114
|
-
|
|
115
|
-
throw new Error("Unsupported event type: " + type);
|
|
116
|
-
}
|
|
117
|
-
this.#domainListeners.add(listener);
|
|
138
|
+
this.#listeners[type].add(listener);
|
|
118
139
|
}
|
|
119
140
|
|
|
120
141
|
/**
|
|
121
|
-
* @param {
|
|
142
|
+
* @param {ScaleResolutionEventType} type
|
|
122
143
|
* @param {ScaleResolutionListener} listener function
|
|
123
144
|
*/
|
|
124
145
|
removeEventListener(type, listener) {
|
|
125
|
-
|
|
126
|
-
throw new Error("Unsupported event type: " + type);
|
|
127
|
-
}
|
|
128
|
-
this.#domainListeners.delete(listener);
|
|
146
|
+
this.#listeners[type].delete(listener);
|
|
129
147
|
}
|
|
130
148
|
|
|
131
|
-
|
|
132
|
-
|
|
149
|
+
/**
|
|
150
|
+
* @param {ScaleResolutionEventType} type
|
|
151
|
+
*/
|
|
152
|
+
#notifyListeners(type) {
|
|
153
|
+
for (const listener of this.#listeners[type].values()) {
|
|
133
154
|
listener({
|
|
134
|
-
type
|
|
155
|
+
type,
|
|
135
156
|
scaleResolution: this,
|
|
136
157
|
});
|
|
137
158
|
}
|
|
@@ -175,11 +196,11 @@ export default class ScaleResolution {
|
|
|
175
196
|
/**
|
|
176
197
|
* Returns true if the domain has been defined explicitly, i.e. not extracted from the data.
|
|
177
198
|
*/
|
|
178
|
-
isExplicitDomain() {
|
|
199
|
+
#isExplicitDomain() {
|
|
179
200
|
return !!this.getConfiguredDomain();
|
|
180
201
|
}
|
|
181
202
|
|
|
182
|
-
isDomainInitialized() {
|
|
203
|
+
#isDomainInitialized() {
|
|
183
204
|
const s = this.#scale;
|
|
184
205
|
if (!s) {
|
|
185
206
|
return false;
|
|
@@ -205,18 +226,15 @@ export default class ScaleResolution {
|
|
|
205
226
|
* @returns {import("../spec/scale.js").Scale}
|
|
206
227
|
*/
|
|
207
228
|
#getMergedScaleProps() {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
(member)
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
)
|
|
215
|
-
.filter((props) => props !== undefined);
|
|
229
|
+
const propArray = this.members
|
|
230
|
+
.map(
|
|
231
|
+
(member) =>
|
|
232
|
+
getChannelDefWithScale(member.view, member.channel).scale
|
|
233
|
+
)
|
|
234
|
+
.filter((props) => props !== undefined);
|
|
216
235
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
});
|
|
236
|
+
// TODO: Disabled scale: https://vega.github.io/vega-lite/docs/scale.html#disable
|
|
237
|
+
return mergeObjects(propArray, "scale", ["domain"]);
|
|
220
238
|
}
|
|
221
239
|
|
|
222
240
|
/**
|
|
@@ -225,67 +243,121 @@ export default class ScaleResolution {
|
|
|
225
243
|
*
|
|
226
244
|
* @returns {import("../spec/scale.js").Scale}
|
|
227
245
|
*/
|
|
228
|
-
getScaleProps() {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
return { type: "null" };
|
|
236
|
-
}
|
|
246
|
+
#getScaleProps() {
|
|
247
|
+
const mergedProps = this.#getMergedScaleProps();
|
|
248
|
+
if (mergedProps === null || mergedProps.type == "null") {
|
|
249
|
+
// No scale (pass-thru)
|
|
250
|
+
// TODO: Check that the channel is compatible
|
|
251
|
+
return { type: "null" };
|
|
252
|
+
}
|
|
237
253
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
254
|
+
const props = {
|
|
255
|
+
...this.#getDefaultScaleProperties(this.type),
|
|
256
|
+
...mergedProps,
|
|
257
|
+
};
|
|
242
258
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
259
|
+
if (!props.type) {
|
|
260
|
+
props.type = getDefaultScaleType(this.channel, this.type);
|
|
261
|
+
}
|
|
246
262
|
|
|
247
|
-
|
|
263
|
+
const domain = this.#getInitialDomain();
|
|
248
264
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
265
|
+
if (domain && domain.length > 0) {
|
|
266
|
+
props.domain = domain;
|
|
267
|
+
} else if (isDiscrete(props.type)) {
|
|
268
|
+
props.domain = new NominalDomain();
|
|
269
|
+
}
|
|
254
270
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
271
|
+
if (!props.domain && props.domainMid !== undefined) {
|
|
272
|
+
// Initialize with a bogus domain so that scale.js can inject the domainMid.
|
|
273
|
+
// The number of domain elements must be know before the glsl scale is generated.
|
|
274
|
+
props.domain = [props.domainMin ?? 0, props.domainMax ?? 1];
|
|
275
|
+
}
|
|
260
276
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
277
|
+
// Reverse discrete y axis
|
|
278
|
+
if (
|
|
279
|
+
this.channel == "y" &&
|
|
280
|
+
isDiscrete(props.type) &&
|
|
281
|
+
props.reverse == undefined
|
|
282
|
+
) {
|
|
283
|
+
props.reverse = true;
|
|
284
|
+
}
|
|
269
285
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
286
|
+
if (props.range && props.scheme) {
|
|
287
|
+
delete props.scheme;
|
|
288
|
+
// TODO: Props should be set more intelligently
|
|
289
|
+
/*
|
|
274
290
|
throw new Error(
|
|
275
291
|
`Scale has both "range" and "scheme" defined! Views: ${this._getViewPaths()}`
|
|
276
292
|
);
|
|
277
293
|
*/
|
|
278
|
-
|
|
294
|
+
}
|
|
279
295
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
296
|
+
// By default, index and locus scales are zoomable, others are not
|
|
297
|
+
if (!("zoom" in props) && ["index", "locus"].includes(props.type)) {
|
|
298
|
+
props.zoom = true;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
applyLockedProperties(props, this.channel);
|
|
302
|
+
|
|
303
|
+
return props;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Configures range. If range is an array of expressions, they are evaluated
|
|
308
|
+
* and the scale is updated when the expressions change.
|
|
309
|
+
*/
|
|
310
|
+
#configureRange() {
|
|
311
|
+
const props = this.#scale.props;
|
|
312
|
+
const range = props.range;
|
|
313
|
+
this.#rangeExprRefListeners.forEach((fn) => fn.invalidate());
|
|
314
|
+
|
|
315
|
+
if (!range || !isArray(range)) {
|
|
316
|
+
// Named ranges?
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* @param {T} array
|
|
322
|
+
* @param {boolean} reverse
|
|
323
|
+
* @returns {T}
|
|
324
|
+
* @template T
|
|
325
|
+
*/
|
|
326
|
+
const flip = (array, reverse) =>
|
|
327
|
+
// @ts-ignore TODO: Fix the type (should be a generic union array type)
|
|
328
|
+
reverse ? array.slice().reverse() : array;
|
|
329
|
+
|
|
330
|
+
if (range.some(isExprRef)) {
|
|
331
|
+
/** @type {(() => void)[]} */
|
|
332
|
+
let expressions;
|
|
333
|
+
|
|
334
|
+
const evaluateAndSet = () => {
|
|
335
|
+
this.#scale.range(
|
|
336
|
+
flip(
|
|
337
|
+
expressions.map((expr) => expr()),
|
|
338
|
+
props.reverse
|
|
339
|
+
)
|
|
340
|
+
);
|
|
341
|
+
};
|
|
284
342
|
|
|
285
|
-
|
|
343
|
+
expressions = range.map((elem) => {
|
|
344
|
+
if (isExprRef(elem)) {
|
|
345
|
+
const fn =
|
|
346
|
+
this.#firstMemberView.paramMediator.createExpression(
|
|
347
|
+
elem.expr
|
|
348
|
+
);
|
|
349
|
+
fn.addListener(evaluateAndSet);
|
|
350
|
+
this.#rangeExprRefListeners.add(fn);
|
|
351
|
+
return () => fn(null);
|
|
352
|
+
} else {
|
|
353
|
+
return () => elem;
|
|
354
|
+
}
|
|
355
|
+
});
|
|
286
356
|
|
|
287
|
-
|
|
288
|
-
}
|
|
357
|
+
evaluateAndSet();
|
|
358
|
+
} else {
|
|
359
|
+
this.#scale.range(flip(range, props.reverse));
|
|
360
|
+
}
|
|
289
361
|
}
|
|
290
362
|
|
|
291
363
|
#getInitialDomain() {
|
|
@@ -330,53 +402,64 @@ export default class ScaleResolution {
|
|
|
330
402
|
* Reconfigures the scale: updates domain and other settings
|
|
331
403
|
*/
|
|
332
404
|
reconfigure() {
|
|
333
|
-
|
|
334
|
-
const domainWasInitialized = this.isDomainInitialized();
|
|
405
|
+
const scale = this.#scale;
|
|
335
406
|
|
|
336
|
-
|
|
407
|
+
if (!scale || scale.type == "null") {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
337
410
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
configureScale(props, this.#scale);
|
|
341
|
-
if (isContinuous(this.#scale.type)) {
|
|
342
|
-
this.#zoomExtent = this.#getZoomExtent();
|
|
343
|
-
}
|
|
411
|
+
const domainWasInitialized = this.#isDomainInitialized();
|
|
412
|
+
const previousDomain = scale.domain();
|
|
344
413
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
414
|
+
const props = this.#getScaleProps();
|
|
415
|
+
configureScale({ ...props, range: undefined }, scale);
|
|
349
416
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
417
|
+
// Annotate the scale with the new props
|
|
418
|
+
scale.props = props;
|
|
419
|
+
this.#configureRange();
|
|
420
|
+
|
|
421
|
+
if (isContinuous(scale.type)) {
|
|
422
|
+
this.#zoomExtent = this.#getZoomExtent();
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (!domainWasInitialized) {
|
|
426
|
+
this.#notifyListeners("domain");
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const newDomain = scale.domain();
|
|
431
|
+
if (!shallowArrayEquals(newDomain, previousDomain)) {
|
|
432
|
+
if (this.isZoomable()) {
|
|
433
|
+
// Don't mess with zoomed views, restore the previous domain
|
|
434
|
+
scale.domain(previousDomain);
|
|
435
|
+
} else if (this.#isZoomingSupported()) {
|
|
436
|
+
// It can be zoomed, so lets make a smooth transition.
|
|
437
|
+
// Restore the previous domain and zoom smoothly to the new domain.
|
|
438
|
+
scale.domain(previousDomain);
|
|
439
|
+
this.zoomTo(newDomain, 500); // TODO: Configurable duration
|
|
440
|
+
} else {
|
|
441
|
+
// Update immediately if the previous domain was the initial domain [0, 0]
|
|
442
|
+
this.#notifyListeners("domain");
|
|
364
443
|
}
|
|
365
444
|
}
|
|
366
445
|
}
|
|
367
446
|
|
|
368
447
|
/**
|
|
369
|
-
* @returns {
|
|
448
|
+
* @returns {ScaleWithProps}
|
|
370
449
|
*/
|
|
371
|
-
|
|
450
|
+
get scale() {
|
|
372
451
|
if (this.#scale) {
|
|
373
452
|
return this.#scale;
|
|
374
453
|
}
|
|
375
454
|
|
|
376
|
-
const props = this
|
|
455
|
+
const props = this.#getScaleProps();
|
|
456
|
+
|
|
457
|
+
const scale = createScale({ ...props, range: undefined });
|
|
458
|
+
// Annotate the scale with props
|
|
459
|
+
scale.props = props;
|
|
377
460
|
|
|
378
|
-
const scale = createScale(props);
|
|
379
461
|
this.#scale = scale;
|
|
462
|
+
this.#configureRange();
|
|
380
463
|
|
|
381
464
|
if (isScaleLocus(scale)) {
|
|
382
465
|
scale.genome(this.getGenome());
|
|
@@ -386,11 +469,27 @@ export default class ScaleResolution {
|
|
|
386
469
|
this.#zoomExtent = this.#getZoomExtent();
|
|
387
470
|
}
|
|
388
471
|
|
|
472
|
+
// Hijack the range method
|
|
473
|
+
const range = scale.range;
|
|
474
|
+
if (range) {
|
|
475
|
+
const notify = () => this.#notifyListeners("range");
|
|
476
|
+
scale.range = function (/** @type {any} */ _) {
|
|
477
|
+
if (arguments.length) {
|
|
478
|
+
range(_);
|
|
479
|
+
notify();
|
|
480
|
+
} else {
|
|
481
|
+
return range();
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
// The initial setting
|
|
485
|
+
notify();
|
|
486
|
+
}
|
|
487
|
+
|
|
389
488
|
return scale;
|
|
390
489
|
}
|
|
391
490
|
|
|
392
491
|
getDomain() {
|
|
393
|
-
return this.
|
|
492
|
+
return this.scale.domain();
|
|
394
493
|
}
|
|
395
494
|
|
|
396
495
|
/**
|
|
@@ -420,14 +519,14 @@ export default class ScaleResolution {
|
|
|
420
519
|
*/
|
|
421
520
|
isZoomable() {
|
|
422
521
|
// Check explicit configuration
|
|
423
|
-
return this.#isZoomingSupported() && !!this.
|
|
522
|
+
return this.#isZoomingSupported() && !!this.scale.props.zoom;
|
|
424
523
|
}
|
|
425
524
|
|
|
426
525
|
/**
|
|
427
526
|
* Returns true if zooming is supported but not necessarily allowed in view spec.
|
|
428
527
|
*/
|
|
429
528
|
#isZoomingSupported() {
|
|
430
|
-
const type = this.
|
|
529
|
+
const type = this.scale.type;
|
|
431
530
|
return isContinuous(type);
|
|
432
531
|
}
|
|
433
532
|
|
|
@@ -444,7 +543,7 @@ export default class ScaleResolution {
|
|
|
444
543
|
return false;
|
|
445
544
|
}
|
|
446
545
|
|
|
447
|
-
const scale = this.
|
|
546
|
+
const scale = this.scale;
|
|
448
547
|
const oldDomain = scale.domain();
|
|
449
548
|
let newDomain = [...oldDomain];
|
|
450
549
|
|
|
@@ -452,7 +551,7 @@ export default class ScaleResolution {
|
|
|
452
551
|
// @ts-ignore
|
|
453
552
|
let anchor = scale.invert(scaleAnchor);
|
|
454
553
|
|
|
455
|
-
if (
|
|
554
|
+
if (scale.props.reverse) {
|
|
456
555
|
pan = -pan;
|
|
457
556
|
}
|
|
458
557
|
|
|
@@ -504,7 +603,7 @@ export default class ScaleResolution {
|
|
|
504
603
|
|
|
505
604
|
if ([0, 1].some((i) => newDomain[i] != oldDomain[i])) {
|
|
506
605
|
scale.domain(newDomain);
|
|
507
|
-
this.#
|
|
606
|
+
this.#notifyListeners("domain");
|
|
508
607
|
return true;
|
|
509
608
|
}
|
|
510
609
|
|
|
@@ -533,7 +632,7 @@ export default class ScaleResolution {
|
|
|
533
632
|
|
|
534
633
|
const animator = this.members[0]?.view.context.animator;
|
|
535
634
|
|
|
536
|
-
const scale = this.
|
|
635
|
+
const scale = this.scale;
|
|
537
636
|
const from = /** @type {number[]} */ (scale.domain());
|
|
538
637
|
|
|
539
638
|
if (duration > 0 && from.length == 2) {
|
|
@@ -552,16 +651,16 @@ export default class ScaleResolution {
|
|
|
552
651
|
const wt = (fw - w) / (fw - tw);
|
|
553
652
|
const c = wt * tc + (1 - wt) * fc;
|
|
554
653
|
scale.domain([c - w / 2, c + w / 2]);
|
|
555
|
-
this.#
|
|
654
|
+
this.#notifyListeners("domain");
|
|
556
655
|
},
|
|
557
656
|
});
|
|
558
657
|
|
|
559
658
|
scale.domain(to);
|
|
560
|
-
this.#
|
|
659
|
+
this.#notifyListeners("domain");
|
|
561
660
|
} else {
|
|
562
661
|
scale.domain(to);
|
|
563
662
|
animator?.requestRender();
|
|
564
|
-
this.#
|
|
663
|
+
this.#notifyListeners("domain");
|
|
565
664
|
}
|
|
566
665
|
}
|
|
567
666
|
|
|
@@ -580,7 +679,7 @@ export default class ScaleResolution {
|
|
|
580
679
|
|
|
581
680
|
if ([0, 1].some((i) => newDomain[i] != oldDomain[i])) {
|
|
582
681
|
this.#scale.domain(newDomain);
|
|
583
|
-
this.#
|
|
682
|
+
this.#notifyListeners("domain");
|
|
584
683
|
return true;
|
|
585
684
|
}
|
|
586
685
|
return false;
|
|
@@ -595,14 +694,14 @@ export default class ScaleResolution {
|
|
|
595
694
|
getZoomLevel() {
|
|
596
695
|
// Zoom level makes sense only for user-zoomable scales where zoom extent is defined
|
|
597
696
|
if (this.isZoomable()) {
|
|
598
|
-
return span(this.#zoomExtent) / span(this.
|
|
697
|
+
return span(this.#zoomExtent) / span(this.scale.domain());
|
|
599
698
|
}
|
|
600
699
|
|
|
601
700
|
return 1.0;
|
|
602
701
|
}
|
|
603
702
|
|
|
604
703
|
#getZoomExtent() {
|
|
605
|
-
const props = this.
|
|
704
|
+
const props = this.scale.props;
|
|
606
705
|
const zoom = props.zoom;
|
|
607
706
|
|
|
608
707
|
if (isZoomParams(zoom)) {
|
|
@@ -632,12 +731,12 @@ export default class ScaleResolution {
|
|
|
632
731
|
const channel = this.channel;
|
|
633
732
|
const props = {};
|
|
634
733
|
|
|
635
|
-
if (this
|
|
734
|
+
if (this.#isExplicitDomain()) {
|
|
636
735
|
props.zero = false;
|
|
637
736
|
}
|
|
638
737
|
|
|
639
738
|
if (isPositionalChannel(channel)) {
|
|
640
|
-
props.nice = !this
|
|
739
|
+
props.nice = !this.#isExplicitDomain();
|
|
641
740
|
} else if (isColorChannel(channel)) {
|
|
642
741
|
// TODO: Named ranges
|
|
643
742
|
props.scheme =
|
|
@@ -687,7 +786,7 @@ export default class ScaleResolution {
|
|
|
687
786
|
* @param {number} value
|
|
688
787
|
*/
|
|
689
788
|
invertToComplex(value) {
|
|
690
|
-
const scale = this.
|
|
789
|
+
const scale = this.scale;
|
|
691
790
|
if ("invert" in scale) {
|
|
692
791
|
const inverted = /** @type {number} */ (scale.invert(value));
|
|
693
792
|
return this.toComplex(inverted);
|