@genome-spy/core 0.30.0 → 0.30.3
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/index.es.js +16379 -0
- package/dist/index.js +43 -43
- package/package.json +10 -7
- package/src/data/collector.js +0 -183
- package/src/data/collector.test.js +0 -84
- package/src/data/dataFlow.js +0 -148
- package/src/data/dataFlow.test.js +0 -5
- package/src/data/facetNode.js +0 -17
- package/src/data/flow.test.js +0 -72
- package/src/data/flowBatch.d.ts +0 -40
- package/src/data/flowNode.js +0 -283
- package/src/data/flowNode.test.js +0 -50
- package/src/data/flowOptimizer.js +0 -123
- package/src/data/flowOptimizer.test.js +0 -193
- package/src/data/flowTestUtils.js +0 -63
- package/src/data/formats/fasta.js +0 -32
- package/src/data/formats/fasta.test.js +0 -27
- package/src/data/sources/dataSource.js +0 -22
- package/src/data/sources/dataSourceFactory.js +0 -24
- package/src/data/sources/dataUtils.js +0 -78
- package/src/data/sources/dynamicCallbackSource.js +0 -57
- package/src/data/sources/dynamicSource.js +0 -37
- package/src/data/sources/inlineSource.js +0 -67
- package/src/data/sources/inlineSource.test.js +0 -56
- package/src/data/sources/namedSource.js +0 -79
- package/src/data/sources/sequenceSource.js +0 -46
- package/src/data/sources/sequenceSource.test.js +0 -46
- package/src/data/sources/urlSource.js +0 -74
- package/src/data/transforms/aggregate.js +0 -70
- package/src/data/transforms/clone.js +0 -40
- package/src/data/transforms/clone.test.js +0 -11
- package/src/data/transforms/coverage.js +0 -187
- package/src/data/transforms/coverage.test.js +0 -123
- package/src/data/transforms/filter.js +0 -37
- package/src/data/transforms/filter.test.js +0 -18
- package/src/data/transforms/filterScoredLabels.js +0 -134
- package/src/data/transforms/flattenCompressedExons.js +0 -57
- package/src/data/transforms/flattenDelimited.js +0 -74
- package/src/data/transforms/flattenDelimited.test.js +0 -87
- package/src/data/transforms/flattenSequence.js +0 -39
- package/src/data/transforms/flattenSequence.test.js +0 -34
- package/src/data/transforms/formula.js +0 -39
- package/src/data/transforms/formula.test.js +0 -19
- package/src/data/transforms/identifier.js +0 -108
- package/src/data/transforms/identifier.test.js +0 -83
- package/src/data/transforms/linearizeGenomicCoordinate.js +0 -101
- package/src/data/transforms/measureText.js +0 -44
- package/src/data/transforms/pileup.js +0 -128
- package/src/data/transforms/pileup.test.js +0 -70
- package/src/data/transforms/project.js +0 -41
- package/src/data/transforms/project.test.js +0 -32
- package/src/data/transforms/regexExtract.js +0 -61
- package/src/data/transforms/regexExtract.test.js +0 -67
- package/src/data/transforms/regexFold.js +0 -141
- package/src/data/transforms/regexFold.test.js +0 -160
- package/src/data/transforms/sample.js +0 -101
- package/src/data/transforms/sample.test.js +0 -38
- package/src/data/transforms/stack.js +0 -137
- package/src/data/transforms/stack.test.js +0 -91
- package/src/data/transforms/transformFactory.js +0 -60
- package/src/embedApi.d.ts +0 -67
- package/src/encoder/accessor.js +0 -82
- package/src/encoder/accessor.test.js +0 -47
- package/src/encoder/encoder.js +0 -394
- package/src/encoder/encoder.test.js +0 -98
- package/src/fonts/Lato-Regular.json +0 -1267
- package/src/fonts/Lato-Regular.png +0 -0
- package/src/fonts/OFL.txt +0 -93
- package/src/fonts/README.md +0 -3
- package/src/fonts/bmFont.d.ts +0 -58
- package/src/fonts/bmFontManager.js +0 -357
- package/src/fonts/bmFontMetrics.js +0 -108
- package/src/genome/genome.js +0 -317
- package/src/genome/genome.test.js +0 -188
- package/src/genome/genomeStore.js +0 -54
- package/src/genome/locusFormat.js +0 -31
- package/src/genome/scaleIndex.d.ts +0 -38
- package/src/genome/scaleIndex.js +0 -166
- package/src/genome/scaleIndex.test.js +0 -78
- package/src/genome/scaleLocus.d.ts +0 -11
- package/src/genome/scaleLocus.js +0 -108
- package/src/genome/scaleLocus.test.js +0 -4
- package/src/genomeSpy.js +0 -785
- package/src/gl/arrayBuilder.js +0 -199
- package/src/gl/dataToVertices.js +0 -636
- package/src/gl/includes/common.glsl +0 -63
- package/src/gl/includes/picking.fragment.glsl +0 -1
- package/src/gl/includes/picking.vertex.glsl +0 -27
- package/src/gl/includes/sampleFacet.glsl +0 -107
- package/src/gl/includes/scales.glsl +0 -112
- package/src/gl/link.fragment.glsl +0 -18
- package/src/gl/link.vertex.glsl +0 -111
- package/src/gl/point.fragment.glsl +0 -123
- package/src/gl/point.vertex.glsl +0 -129
- package/src/gl/rect.fragment.glsl +0 -51
- package/src/gl/rect.vertex.glsl +0 -114
- package/src/gl/rule.fragment.glsl +0 -52
- package/src/gl/rule.vertex.glsl +0 -89
- package/src/gl/text.fragment.glsl +0 -31
- package/src/gl/text.vertex.glsl +0 -246
- package/src/gl/webGLHelper.js +0 -504
- package/src/img/bowtie.svg +0 -1
- package/src/img/genomespy-favicon.svg +0 -34
- package/src/index.html +0 -11
- package/src/index.js +0 -128
- package/src/marks/link.js +0 -175
- package/src/marks/mark.js +0 -975
- package/src/marks/markUtils.js +0 -125
- package/src/marks/pointMark.js +0 -251
- package/src/marks/rectMark.js +0 -241
- package/src/marks/rule.js +0 -250
- package/src/marks/text.js +0 -278
- package/src/node_modules/.vitest/results.json +0 -1
- package/src/scale/colorUtils.js +0 -184
- package/src/scale/glslScaleGenerator.js +0 -502
- package/src/scale/scale.js +0 -451
- package/src/scale/scale.test.js +0 -324
- package/src/scale/ticks.js +0 -203
- package/src/scale/ticks.test.js +0 -40
- package/src/singlePageApp.js +0 -13
- package/src/spec/axis.d.ts +0 -296
- package/src/spec/channel.d.ts +0 -430
- package/src/spec/data.d.ts +0 -196
- package/src/spec/font.d.ts +0 -15
- package/src/spec/genome.d.ts +0 -35
- package/src/spec/mark.d.ts +0 -429
- package/src/spec/root.d.ts +0 -17
- package/src/spec/sampleView.d.ts +0 -180
- package/src/spec/scale.d.ts +0 -273
- package/src/spec/title.d.ts +0 -102
- package/src/spec/tooltip.d.ts +0 -9
- package/src/spec/transform.d.ts +0 -479
- package/src/spec/view.d.ts +0 -201
- package/src/styles/genome-spy.scss +0 -153
- package/src/tooltip/dataTooltipHandler.js +0 -64
- package/src/tooltip/refseqGeneTooltipHandler.js +0 -78
- package/src/tooltip/tooltipHandler.ts +0 -12
- package/src/types/filetypes.d.ts +0 -14
- package/src/types/flatqueue.d.ts +0 -53
- package/src/types/glsl.d.ts +0 -4
- package/src/types/internmap.d.ts +0 -22
- package/src/types/object.d.ts +0 -21
- package/src/types/vega-loader.d.ts +0 -1
- package/src/types/vega-scale.d.ts +0 -60
- package/src/utils/addBaseUrl.js +0 -19
- package/src/utils/addBaseUrl.test.js +0 -22
- package/src/utils/animator.js +0 -83
- package/src/utils/arrayUtils.js +0 -61
- package/src/utils/binnedIndex.js +0 -167
- package/src/utils/binnedIndex.test.js +0 -155
- package/src/utils/clamp.js +0 -8
- package/src/utils/cloner.js +0 -34
- package/src/utils/cloner.test.js +0 -24
- package/src/utils/coalesce.js +0 -11
- package/src/utils/coalesce.test.js +0 -16
- package/src/utils/concatIterables.js +0 -26
- package/src/utils/concatIterables.test.js +0 -8
- package/src/utils/debounce.js +0 -37
- package/src/utils/domainArray.js +0 -216
- package/src/utils/domainArray.test.js +0 -130
- package/src/utils/eerp.js +0 -13
- package/src/utils/expression.js +0 -32
- package/src/utils/field.js +0 -28
- package/src/utils/formatObject.js +0 -31
- package/src/utils/indexer.js +0 -43
- package/src/utils/indexer.test.js +0 -47
- package/src/utils/inertia.js +0 -124
- package/src/utils/interactionEvent.js +0 -33
- package/src/utils/iterateNestedMaps.js +0 -21
- package/src/utils/iterateNestedMaps.test.js +0 -33
- package/src/utils/kWayMerge.js +0 -42
- package/src/utils/kWayMerge.test.js +0 -26
- package/src/utils/layout/flexLayout.js +0 -368
- package/src/utils/layout/flexLayout.test.js +0 -311
- package/src/utils/layout/grid.js +0 -95
- package/src/utils/layout/grid.test.js +0 -71
- package/src/utils/layout/padding.js +0 -120
- package/src/utils/layout/point.js +0 -23
- package/src/utils/layout/rectangle.js +0 -288
- package/src/utils/layout/rectangle.test.js +0 -172
- package/src/utils/mergeObjects.js +0 -99
- package/src/utils/mergeObjects.test.js +0 -42
- package/src/utils/numberExtractor.js +0 -24
- package/src/utils/numberExtractor.test.js +0 -6
- package/src/utils/point.js +0 -14
- package/src/utils/propertyCacher.js +0 -70
- package/src/utils/propertyCacher.test.js +0 -85
- package/src/utils/propertyCoalescer.js +0 -42
- package/src/utils/propertyCoalescer.test.js +0 -22
- package/src/utils/reservationMap.js +0 -103
- package/src/utils/reservationMap.test.js +0 -20
- package/src/utils/scaleNull.js +0 -19
- package/src/utils/setOperations.js +0 -75
- package/src/utils/smoothstep.js +0 -10
- package/src/utils/throttle.js +0 -34
- package/src/utils/topK.js +0 -76
- package/src/utils/topK.test.js +0 -64
- package/src/utils/transition.js +0 -74
- package/src/utils/ui/tooltip.js +0 -189
- package/src/utils/url.js +0 -22
- package/src/utils/variableTools.js +0 -24
- package/src/utils/variableTools.test.js +0 -13
- package/src/view/axisResolution.js +0 -140
- package/src/view/axisResolution.test.js +0 -201
- package/src/view/axisView.js +0 -747
- package/src/view/concatView.js +0 -45
- package/src/view/containerView.js +0 -159
- package/src/view/facetView.js +0 -491
- package/src/view/flowBuilder.js +0 -367
- package/src/view/flowBuilder.test.js +0 -125
- package/src/view/gridView.js +0 -786
- package/src/view/implicitRootView.js +0 -14
- package/src/view/importView.js +0 -19
- package/src/view/layerView.js +0 -74
- package/src/view/rendering.d.ts +0 -44
- package/src/view/renderingContext/compositeViewRenderingContext.js +0 -51
- package/src/view/renderingContext/deferredViewRenderingContext.js +0 -176
- package/src/view/renderingContext/layoutRecorderViewRenderingContext.js +0 -128
- package/src/view/renderingContext/simpleViewRenderingContext.js +0 -64
- package/src/view/renderingContext/svgViewRenderingContext.js +0 -125
- package/src/view/renderingContext/viewRenderingContext.js +0 -41
- package/src/view/scaleResolution.js +0 -797
- package/src/view/scaleResolution.test.js +0 -572
- package/src/view/scaleResolutionApi.d.ts +0 -40
- package/src/view/testUtils.js +0 -51
- package/src/view/title.js +0 -165
- package/src/view/unitView.js +0 -382
- package/src/view/view.js +0 -612
- package/src/view/view.test.js +0 -214
- package/src/view/viewContext.d.ts +0 -62
- package/src/view/viewFactory.js +0 -181
- package/src/view/viewFactory.test.js +0 -17
- package/src/view/viewUtils.js +0 -327
- package/src/view/zoom.js +0 -89
|
@@ -1,797 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
panLinear,
|
|
3
|
-
zoomLinear,
|
|
4
|
-
clampRange,
|
|
5
|
-
span,
|
|
6
|
-
panLog,
|
|
7
|
-
zoomLog,
|
|
8
|
-
panPow,
|
|
9
|
-
zoomPow,
|
|
10
|
-
isArray,
|
|
11
|
-
isObject,
|
|
12
|
-
isBoolean,
|
|
13
|
-
} from "vega-util";
|
|
14
|
-
import { isDiscrete, isContinuous } from "vega-scale";
|
|
15
|
-
|
|
16
|
-
import mergeObjects from "../utils/mergeObjects";
|
|
17
|
-
import createScale, { configureScale } from "../scale/scale";
|
|
18
|
-
|
|
19
|
-
import { invalidate, getCachedOrCall } from "../utils/propertyCacher";
|
|
20
|
-
import {
|
|
21
|
-
getChannelDefWithScale,
|
|
22
|
-
getDiscreteRange,
|
|
23
|
-
isColorChannel,
|
|
24
|
-
isDiscreteChannel,
|
|
25
|
-
isPositionalChannel,
|
|
26
|
-
isPrimaryPositionalChannel,
|
|
27
|
-
isSecondaryChannel,
|
|
28
|
-
} from "../encoder/encoder";
|
|
29
|
-
import {
|
|
30
|
-
isChromosomalLocus,
|
|
31
|
-
isChromosomalLocusInterval,
|
|
32
|
-
} from "../genome/genome";
|
|
33
|
-
import { NominalDomain } from "../utils/domainArray";
|
|
34
|
-
import { easeQuadInOut } from "d3-ease";
|
|
35
|
-
import { interpolateZoom } from "d3-interpolate";
|
|
36
|
-
import { shallowArrayEquals } from "../utils/arrayUtils";
|
|
37
|
-
import { isScaleLocus } from "../genome/scaleLocus";
|
|
38
|
-
|
|
39
|
-
export const QUANTITATIVE = "quantitative";
|
|
40
|
-
export const ORDINAL = "ordinal";
|
|
41
|
-
export const NOMINAL = "nominal";
|
|
42
|
-
export const LOCUS = "locus"; // Humdum, should this be "genomic"?
|
|
43
|
-
export const INDEX = "index";
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* @template {Channel}[T=Channel]
|
|
47
|
-
* @typedef {{view: import("./unitView").default, channel: T}} ResolutionMember
|
|
48
|
-
* @typedef {import("./unitView").default} UnitView
|
|
49
|
-
* @typedef {import("../encoder/encoder").VegaScale} VegaScale
|
|
50
|
-
* @typedef {import("../utils/domainArray").DomainArray} DomainArray
|
|
51
|
-
* @typedef {import("../genome/genome").ChromosomalLocus} ChromosomalLocus
|
|
52
|
-
*
|
|
53
|
-
*/
|
|
54
|
-
/**
|
|
55
|
-
* Resolution takes care of merging domains and scales from multiple views.
|
|
56
|
-
* This class also provides some utility methods for zooming the scales etc..
|
|
57
|
-
*
|
|
58
|
-
* TODO: This has grown a bit too fat. Consider splitting.
|
|
59
|
-
*
|
|
60
|
-
* @typedef {import("./scaleResolutionApi").ScaleResolutionApi} ScaleResolutionApi
|
|
61
|
-
* @implements {ScaleResolutionApi}
|
|
62
|
-
*
|
|
63
|
-
* @typedef {import("../spec/channel").Channel} Channel
|
|
64
|
-
* @typedef {import("../spec/scale").Scale} Scale
|
|
65
|
-
* @typedef {import("../spec/scale").NumericDomain} NumericDomain
|
|
66
|
-
* @typedef {import("../spec/scale").ScalarDomain} ScalarDomain
|
|
67
|
-
* @typedef {import("../spec/scale").ComplexDomain} ComplexDomain
|
|
68
|
-
* @typedef {import("../spec/scale").ZoomParams} ZoomParams
|
|
69
|
-
*/
|
|
70
|
-
export default class ScaleResolution {
|
|
71
|
-
/**
|
|
72
|
-
* @param {Channel} channel
|
|
73
|
-
*/
|
|
74
|
-
constructor(channel) {
|
|
75
|
-
this.channel = channel;
|
|
76
|
-
/** @type {ResolutionMember[]} The involved views */
|
|
77
|
-
this.members = [];
|
|
78
|
-
/** @type {string} Data type (quantitative, nominal, etc...) */
|
|
79
|
-
this.type = null;
|
|
80
|
-
|
|
81
|
-
/** @type {number[]} */
|
|
82
|
-
this._zoomExtent = undefined;
|
|
83
|
-
|
|
84
|
-
/** @type {Set<import("./scaleResolutionApi").ScaleResolutionListener>} Observers that are called when the scale domain is changed */
|
|
85
|
-
this._domainListeners = new Set();
|
|
86
|
-
|
|
87
|
-
/** @type {string} An optional unique identifier for the scale */
|
|
88
|
-
this.name = undefined;
|
|
89
|
-
|
|
90
|
-
/** @type {VegaScale} */
|
|
91
|
-
this._scale = undefined;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Adds a listener that is called when the scale domain is changed,
|
|
96
|
-
* e.g., zoomed. The call is synchronous and happens before the views
|
|
97
|
-
* are rendered.
|
|
98
|
-
*
|
|
99
|
-
* @param {"domain"} type
|
|
100
|
-
* @param {import("./scaleResolutionApi").ScaleResolutionListener} listener function
|
|
101
|
-
*/
|
|
102
|
-
addEventListener(type, listener) {
|
|
103
|
-
if (type != "domain") {
|
|
104
|
-
throw new Error("Unsupported event type: " + type);
|
|
105
|
-
}
|
|
106
|
-
this._domainListeners.add(listener);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* @param {"domain"} type
|
|
111
|
-
* @param {import("./scaleResolutionApi").ScaleResolutionListener} listener function
|
|
112
|
-
*/
|
|
113
|
-
removeEventListener(type, listener) {
|
|
114
|
-
if (type != "domain") {
|
|
115
|
-
throw new Error("Unsupported event type: " + type);
|
|
116
|
-
}
|
|
117
|
-
this._domainListeners.delete(listener);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
_notifyDomainListeners() {
|
|
121
|
-
for (const listener of this._domainListeners.values()) {
|
|
122
|
-
listener({
|
|
123
|
-
type: "domain",
|
|
124
|
-
scaleResolution: this,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* Add a view to this resolution.
|
|
131
|
-
* N.B. This is expected to be called in depth-first order
|
|
132
|
-
*
|
|
133
|
-
* @param {UnitView} view
|
|
134
|
-
* @param {import("./view").Channel} channel
|
|
135
|
-
*/
|
|
136
|
-
pushUnitView(view, channel) {
|
|
137
|
-
const channelDef = getChannelDefWithScale(view, channel);
|
|
138
|
-
const type = channelDef.type;
|
|
139
|
-
const name = channelDef?.scale?.name;
|
|
140
|
-
|
|
141
|
-
if (name) {
|
|
142
|
-
if (this.name !== undefined && name != this.name) {
|
|
143
|
-
throw new Error(
|
|
144
|
-
`Shared scales have conflicting names: "${name}" vs. "${this.name}"!`
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
this.name = name;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
if (!this.type) {
|
|
151
|
-
this.type = type;
|
|
152
|
-
} else if (type !== this.type && !isSecondaryChannel(channel)) {
|
|
153
|
-
// TODO: Include a reference to the layer
|
|
154
|
-
throw new Error(
|
|
155
|
-
`Can not use shared scale for different data types: ${this.type} vs. ${type}. Use "resolve: independent" for channel ${this.channel}`
|
|
156
|
-
);
|
|
157
|
-
// Actually, point scale could be changed into band scale
|
|
158
|
-
// TODO: Use the same merging logic as in: https://github.com/vega/vega-lite/blob/master/src/scale.ts
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
this.members.push({ view, channel });
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Returns true if the domain has been defined explicitly, i.e. not extracted from the data.
|
|
166
|
-
*/
|
|
167
|
-
isExplicitDomain() {
|
|
168
|
-
return !!this.getConfiguredDomain();
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Collects and merges scale properties from the participating views.
|
|
173
|
-
* Does not include inferred default values such as schemes etc.
|
|
174
|
-
*
|
|
175
|
-
* @returns {import("../spec/scale").Scale}
|
|
176
|
-
*/
|
|
177
|
-
_getMergedScaleProps() {
|
|
178
|
-
return getCachedOrCall(this, "mergedScaleProps", () => {
|
|
179
|
-
const propArray = this.members
|
|
180
|
-
.map(
|
|
181
|
-
(member) =>
|
|
182
|
-
getChannelDefWithScale(member.view, member.channel)
|
|
183
|
-
.scale
|
|
184
|
-
)
|
|
185
|
-
.filter((props) => props !== undefined);
|
|
186
|
-
|
|
187
|
-
// TODO: Disabled scale: https://vega.github.io/vega-lite/docs/scale.html#disable
|
|
188
|
-
return mergeObjects(propArray, "scale", ["domain"]);
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Returns the merged scale properties supplemented with inferred properties
|
|
194
|
-
* and domain.
|
|
195
|
-
*
|
|
196
|
-
* @returns {import("../spec/scale").Scale}
|
|
197
|
-
*/
|
|
198
|
-
getScaleProps() {
|
|
199
|
-
// eslint-disable-next-line complexity
|
|
200
|
-
return getCachedOrCall(this, "scaleProps", () => {
|
|
201
|
-
const mergedProps = this._getMergedScaleProps();
|
|
202
|
-
if (mergedProps === null || mergedProps.type == "null") {
|
|
203
|
-
// No scale (pass-thru)
|
|
204
|
-
// TODO: Check that the channel is compatible
|
|
205
|
-
return { type: "null" };
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const props = {
|
|
209
|
-
...this._getDefaultScaleProperties(this.type),
|
|
210
|
-
...mergedProps,
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
if (!props.type) {
|
|
214
|
-
props.type = getDefaultScaleType(this.channel, this.type);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
const domain = this.getInitialDomain();
|
|
218
|
-
|
|
219
|
-
if (domain && domain.length > 0) {
|
|
220
|
-
props.domain = domain;
|
|
221
|
-
} else if (isDiscrete(props.type)) {
|
|
222
|
-
props.domain = new NominalDomain();
|
|
223
|
-
} else if (props.scheme) {
|
|
224
|
-
// An initial domain is required when using a scheme.
|
|
225
|
-
// Otherwise configureScale() does something weird when the domain is set later,
|
|
226
|
-
// resulting in an interpolator between just two colors.
|
|
227
|
-
// TODO: Fix configureScale().
|
|
228
|
-
props.domain = [0, 1];
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (!props.domain && props.domainMid !== undefined) {
|
|
232
|
-
// Initialize with a bogus domain so that scale.js can inject the domainMid.
|
|
233
|
-
// The number of domain elements must be know before the glsl scale is generated.
|
|
234
|
-
props.domain = [props.domainMin ?? 0, props.domainMax ?? 1];
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// Reverse discrete y axis
|
|
238
|
-
if (
|
|
239
|
-
this.channel == "y" &&
|
|
240
|
-
isDiscrete(props.type) &&
|
|
241
|
-
props.reverse == undefined
|
|
242
|
-
) {
|
|
243
|
-
props.reverse = true;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
if (props.range && props.scheme) {
|
|
247
|
-
delete props.scheme;
|
|
248
|
-
// TODO: Props should be set more intelligently
|
|
249
|
-
/*
|
|
250
|
-
throw new Error(
|
|
251
|
-
`Scale has both "range" and "scheme" defined! Views: ${this._getViewPaths()}`
|
|
252
|
-
);
|
|
253
|
-
*/
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// By default, index and locus scales are zoomable, others are not
|
|
257
|
-
if (!("zoom" in props) && ["index", "locus"].includes(props.type)) {
|
|
258
|
-
props.zoom = true;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
applyLockedProperties(props, this.channel);
|
|
262
|
-
|
|
263
|
-
return props;
|
|
264
|
-
});
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
getInitialDomain() {
|
|
268
|
-
// TODO: intersect the domain with zoom extent (if it's defined)
|
|
269
|
-
return (
|
|
270
|
-
this.getConfiguredDomain() ??
|
|
271
|
-
(this.type == LOCUS
|
|
272
|
-
? this.getGenome().getExtent()
|
|
273
|
-
: this.getDataDomain())
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Unions the configured domains of all participating views.
|
|
279
|
-
*
|
|
280
|
-
* @return { DomainArray }
|
|
281
|
-
*/
|
|
282
|
-
getConfiguredDomain() {
|
|
283
|
-
return this._reduceDomains((member) =>
|
|
284
|
-
isSecondaryChannel(member.channel)
|
|
285
|
-
? undefined
|
|
286
|
-
: member.view.getConfiguredDomain(member.channel)
|
|
287
|
-
);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
/**
|
|
291
|
-
* Extracts and unions the data domains of all participating views.
|
|
292
|
-
*
|
|
293
|
-
* @return { DomainArray }
|
|
294
|
-
*/
|
|
295
|
-
getDataDomain() {
|
|
296
|
-
// TODO: Optimize: extract domain only once if the views share the data
|
|
297
|
-
return this._reduceDomains((member) =>
|
|
298
|
-
isSecondaryChannel(member.channel)
|
|
299
|
-
? undefined
|
|
300
|
-
: member.view.extractDataDomain(member.channel)
|
|
301
|
-
);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
/**
|
|
305
|
-
* Reconfigures the scale: updates domain and other settings
|
|
306
|
-
*/
|
|
307
|
-
reconfigure() {
|
|
308
|
-
if (this._scale && this._scale.type != "null") {
|
|
309
|
-
invalidate(this, "scaleProps");
|
|
310
|
-
const props = this.getScaleProps();
|
|
311
|
-
configureScale(props, this._scale);
|
|
312
|
-
if (isContinuous(this._scale.type)) {
|
|
313
|
-
this._zoomExtent = this._getZoomExtent();
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/**
|
|
319
|
-
* @returns {import("../encoder/encoder").VegaScale}
|
|
320
|
-
*/
|
|
321
|
-
getScale() {
|
|
322
|
-
if (this._scale) {
|
|
323
|
-
return this._scale;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const props = this.getScaleProps();
|
|
327
|
-
|
|
328
|
-
const scale = createScale(props);
|
|
329
|
-
this._scale = scale;
|
|
330
|
-
|
|
331
|
-
if (isScaleLocus(scale)) {
|
|
332
|
-
scale.genome(this.getGenome());
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
if (isContinuous(scale.type)) {
|
|
336
|
-
this._zoomExtent = this._getZoomExtent();
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return scale;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
getDomain() {
|
|
343
|
-
return this.getScale().domain();
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* @returns {NumericDomain | ComplexDomain}
|
|
348
|
-
*/
|
|
349
|
-
getComplexDomain() {
|
|
350
|
-
return (
|
|
351
|
-
this.getGenome()?.toChromosomalInterval(this.getDomain()) ??
|
|
352
|
-
this.getDomain()
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
/**
|
|
357
|
-
* Return true if the scale is zoomable and the current domain differs from the initial domain.
|
|
358
|
-
*
|
|
359
|
-
* @returns true if zoomed
|
|
360
|
-
*/
|
|
361
|
-
isZoomed() {
|
|
362
|
-
return (
|
|
363
|
-
this.isZoomable() &&
|
|
364
|
-
shallowArrayEquals(this.getInitialDomain(), this.getDomain())
|
|
365
|
-
);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
isZoomable() {
|
|
369
|
-
if (!isPrimaryPositionalChannel(this.channel)) {
|
|
370
|
-
return false;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const scaleType = this.getScale().type;
|
|
374
|
-
if (
|
|
375
|
-
!["linear", "locus", "index", "log", "pow", "sqrt"].includes(
|
|
376
|
-
scaleType
|
|
377
|
-
)
|
|
378
|
-
) {
|
|
379
|
-
return false;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
// Check explicit configuration
|
|
383
|
-
return !!this.getScaleProps().zoom;
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Pans (translates) and zooms using a specified scale factor.
|
|
388
|
-
*
|
|
389
|
-
* @param {number} scaleFactor
|
|
390
|
-
* @param {number} scaleAnchor
|
|
391
|
-
* @param {number} pan
|
|
392
|
-
* @returns {boolean} true if the scale was zoomed
|
|
393
|
-
*/
|
|
394
|
-
zoom(scaleFactor, scaleAnchor, pan) {
|
|
395
|
-
if (!this.isZoomable()) {
|
|
396
|
-
return false;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const scale = this.getScale();
|
|
400
|
-
const oldDomain = scale.domain();
|
|
401
|
-
let newDomain = [...oldDomain];
|
|
402
|
-
|
|
403
|
-
// @ts-expect-error
|
|
404
|
-
let anchor = scale.invert(scaleAnchor);
|
|
405
|
-
|
|
406
|
-
if (this.getScaleProps().reverse) {
|
|
407
|
-
pan = -pan;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
if ("align" in scale) {
|
|
411
|
-
anchor += scale.align();
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
// TODO: symlog
|
|
415
|
-
switch (scale.type) {
|
|
416
|
-
case "linear":
|
|
417
|
-
case "index":
|
|
418
|
-
case "locus":
|
|
419
|
-
newDomain = panLinear(newDomain, pan || 0);
|
|
420
|
-
newDomain = zoomLinear(newDomain, anchor, scaleFactor);
|
|
421
|
-
break;
|
|
422
|
-
case "log":
|
|
423
|
-
newDomain = panLog(newDomain, pan || 0);
|
|
424
|
-
newDomain = zoomLog(newDomain, anchor, scaleFactor);
|
|
425
|
-
break;
|
|
426
|
-
case "pow":
|
|
427
|
-
case "sqrt": {
|
|
428
|
-
const powScale =
|
|
429
|
-
/** @type {import("d3-scale").ScalePower<number, number>} */ (
|
|
430
|
-
scale
|
|
431
|
-
);
|
|
432
|
-
newDomain = panPow(newDomain, pan || 0, powScale.exponent());
|
|
433
|
-
newDomain = zoomPow(
|
|
434
|
-
newDomain,
|
|
435
|
-
anchor,
|
|
436
|
-
scaleFactor,
|
|
437
|
-
powScale.exponent()
|
|
438
|
-
);
|
|
439
|
-
break;
|
|
440
|
-
}
|
|
441
|
-
default:
|
|
442
|
-
throw new Error(
|
|
443
|
-
"Zooming is not implemented for: " + scale.type
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// TODO: Use the zoomTo method. Move clamping etc there.
|
|
448
|
-
if (this._zoomExtent) {
|
|
449
|
-
newDomain = clampRange(
|
|
450
|
-
newDomain,
|
|
451
|
-
this._zoomExtent[0],
|
|
452
|
-
this._zoomExtent[1]
|
|
453
|
-
);
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if ([0, 1].some((i) => newDomain[i] != oldDomain[i])) {
|
|
457
|
-
scale.domain(newDomain);
|
|
458
|
-
this._notifyDomainListeners();
|
|
459
|
-
return true;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
return false;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Immediately zooms to the given interval.
|
|
467
|
-
*
|
|
468
|
-
* @param {NumericDomain | ComplexDomain} domain
|
|
469
|
-
* @param {boolean | number} [duration] an approximate duration for transition.
|
|
470
|
-
* Zero duration zooms immediately. Boolean `true` indicates a default duration.
|
|
471
|
-
*/
|
|
472
|
-
async zoomTo(domain, duration = false) {
|
|
473
|
-
if (isBoolean(duration)) {
|
|
474
|
-
duration = duration ? 700 : 0;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if (!this.isZoomable()) {
|
|
478
|
-
throw new Error("Not a zoomable scale!");
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
const to = this.fromComplexInterval(domain);
|
|
482
|
-
|
|
483
|
-
// TODO: Intersect the domain with zoom extent
|
|
484
|
-
|
|
485
|
-
const animator = this.members[0]?.view.context.animator;
|
|
486
|
-
|
|
487
|
-
const scale = this.getScale();
|
|
488
|
-
const from = /** @type {number[]} */ (scale.domain());
|
|
489
|
-
|
|
490
|
-
if (duration > 0 && from.length == 2) {
|
|
491
|
-
const fw = from[1] - from[0];
|
|
492
|
-
const fc = from[0] + fw / 2;
|
|
493
|
-
|
|
494
|
-
const tw = to[1] - to[0];
|
|
495
|
-
const tc = to[0] + tw / 2;
|
|
496
|
-
|
|
497
|
-
/*
|
|
498
|
-
await animator.transition({
|
|
499
|
-
duration,
|
|
500
|
-
easingFunction: easeExpInOut,
|
|
501
|
-
onUpdate: (t) => {
|
|
502
|
-
const w = eerp(fw, tw, t);
|
|
503
|
-
const wt = (fw - w) / (fw - tw);
|
|
504
|
-
const c = wt * tc + (1 - wt) * fc;
|
|
505
|
-
scale.domain([c - w / 2, c + w / 2]);
|
|
506
|
-
this._notifyDomainListeners();
|
|
507
|
-
},
|
|
508
|
-
});
|
|
509
|
-
*/
|
|
510
|
-
const interpolator = interpolateZoom([fc, 0, fw], [tc, 0, tw]).rho(
|
|
511
|
-
0.7
|
|
512
|
-
);
|
|
513
|
-
await animator.transition({
|
|
514
|
-
duration: (duration / 1000) * interpolator.duration,
|
|
515
|
-
easingFunction: easeQuadInOut,
|
|
516
|
-
onUpdate: (t) => {
|
|
517
|
-
const [c, , w] = interpolator(t);
|
|
518
|
-
scale.domain([c - w / 2, c + w / 2]);
|
|
519
|
-
this._notifyDomainListeners();
|
|
520
|
-
},
|
|
521
|
-
});
|
|
522
|
-
scale.domain(to);
|
|
523
|
-
this._notifyDomainListeners();
|
|
524
|
-
} else {
|
|
525
|
-
scale.domain(to);
|
|
526
|
-
animator?.requestRender();
|
|
527
|
-
this._notifyDomainListeners();
|
|
528
|
-
}
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
/**
|
|
532
|
-
* Resets the current domain to the initial one
|
|
533
|
-
*
|
|
534
|
-
* @returns true if the domain was changed
|
|
535
|
-
*/
|
|
536
|
-
resetZoom() {
|
|
537
|
-
if (!this.isZoomable()) {
|
|
538
|
-
throw new Error("Not a zoomable scale!");
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
const oldDomain = this.getDomain();
|
|
542
|
-
const newDomain = this.getInitialDomain();
|
|
543
|
-
|
|
544
|
-
if ([0, 1].some((i) => newDomain[i] != oldDomain[i])) {
|
|
545
|
-
this._scale.domain(newDomain);
|
|
546
|
-
this._notifyDomainListeners();
|
|
547
|
-
return true;
|
|
548
|
-
}
|
|
549
|
-
return false;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Returns the zoom level with respect to the reference domain span (the original domain).
|
|
554
|
-
*
|
|
555
|
-
* In principle, this is highly specific to positional channels. However, zooming can
|
|
556
|
-
* be generalized to other quantitative channels such as color, opacity, size, etc.
|
|
557
|
-
*/
|
|
558
|
-
getZoomLevel() {
|
|
559
|
-
if (this.isZoomable()) {
|
|
560
|
-
return span(this._zoomExtent) / span(this.getScale().domain());
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
return 1.0;
|
|
564
|
-
}
|
|
565
|
-
|
|
566
|
-
_getZoomExtent() {
|
|
567
|
-
const props = this.getScaleProps();
|
|
568
|
-
const zoom = props.zoom;
|
|
569
|
-
|
|
570
|
-
if (isZoomParams(zoom)) {
|
|
571
|
-
if (isArray(zoom.extent)) {
|
|
572
|
-
return this.fromComplexInterval(zoom.extent);
|
|
573
|
-
}
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
if (zoom) {
|
|
577
|
-
if (props.type == "locus") {
|
|
578
|
-
return this.getGenome().getExtent();
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// TODO: Perhaps this should be "domain" for index scale and nothing for quantitative.
|
|
582
|
-
// Would behave similarly to Vega-Lite, which doesn't have constraints.
|
|
583
|
-
return this._scale.domain();
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
/**
|
|
588
|
-
* TODO: These actually depend on the mark, so this is clearly a wrong place.
|
|
589
|
-
* And besides, these should be configurable (themeable)
|
|
590
|
-
*
|
|
591
|
-
* @param {string} dataType
|
|
592
|
-
*/
|
|
593
|
-
_getDefaultScaleProperties(dataType) {
|
|
594
|
-
const channel = this.channel;
|
|
595
|
-
const props = {};
|
|
596
|
-
|
|
597
|
-
if (this.isExplicitDomain()) {
|
|
598
|
-
props.zero = false;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
if (isPositionalChannel(channel)) {
|
|
602
|
-
props.nice = !this.isExplicitDomain();
|
|
603
|
-
} else if (isColorChannel(channel)) {
|
|
604
|
-
// TODO: Named ranges
|
|
605
|
-
props.scheme =
|
|
606
|
-
dataType == NOMINAL
|
|
607
|
-
? "tableau10"
|
|
608
|
-
: dataType == ORDINAL
|
|
609
|
-
? "blues"
|
|
610
|
-
: "viridis";
|
|
611
|
-
} else if (isDiscreteChannel(channel)) {
|
|
612
|
-
// Shapes of point mark, for example
|
|
613
|
-
props.range = getDiscreteRange(channel);
|
|
614
|
-
} else if (channel == "size") {
|
|
615
|
-
props.range = [0, 400]; // TODO: Configurable default. This is currently optimized for points.
|
|
616
|
-
} else if (channel == "angle") {
|
|
617
|
-
props.range = [0, 360];
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
return props;
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
getGenome() {
|
|
624
|
-
if (this.type !== "locus") {
|
|
625
|
-
return undefined;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
// TODO: Support multiple assemblies
|
|
629
|
-
const genome = this.members[0].view.context.genomeStore?.getGenome();
|
|
630
|
-
if (!genome) {
|
|
631
|
-
throw new Error("No genome has been defined!");
|
|
632
|
-
}
|
|
633
|
-
return genome;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
// TODO: Move the "complex" stuff into scaleLocus.
|
|
637
|
-
|
|
638
|
-
/**
|
|
639
|
-
* Inverts a value in range to a value on domain. Returns an object in
|
|
640
|
-
* case of locus scale.
|
|
641
|
-
*
|
|
642
|
-
* @param {number} value
|
|
643
|
-
*/
|
|
644
|
-
invertToComplex(value) {
|
|
645
|
-
const scale = this.getScale();
|
|
646
|
-
if ("invert" in scale) {
|
|
647
|
-
const inverted = /** @type {number} */ (scale.invert(value));
|
|
648
|
-
return this.toComplex(inverted);
|
|
649
|
-
} else {
|
|
650
|
-
throw new Error("The scale does not support inverting!");
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
/**
|
|
655
|
-
* @param {number} value
|
|
656
|
-
*/
|
|
657
|
-
toComplex(value) {
|
|
658
|
-
const genome = this.getGenome();
|
|
659
|
-
return genome ? genome.toChromosomal(value) : value;
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
/**
|
|
663
|
-
* @param {number | ChromosomalLocus} complex
|
|
664
|
-
* @returns {number}
|
|
665
|
-
*/
|
|
666
|
-
fromComplex(complex) {
|
|
667
|
-
if (isChromosomalLocus(complex)) {
|
|
668
|
-
const genome = this.getGenome();
|
|
669
|
-
return genome.toContinuous(complex.chrom, complex.pos);
|
|
670
|
-
}
|
|
671
|
-
return complex;
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
/**
|
|
675
|
-
* @param {ScalarDomain | ComplexDomain} interval
|
|
676
|
-
* @returns {number[]}
|
|
677
|
-
*/
|
|
678
|
-
fromComplexInterval(interval) {
|
|
679
|
-
if (this.type === "locus" && isChromosomalLocusInterval(interval)) {
|
|
680
|
-
return this.getGenome().toContinuousInterval(interval);
|
|
681
|
-
}
|
|
682
|
-
return /** @type {number[]} */ (interval);
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
_getViewPaths() {
|
|
686
|
-
return this.members.map((v) => v.view.getPathString()).join(", ");
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
/**
|
|
690
|
-
* Iterate all participanting views and reduce (union) their domains using an accessor.
|
|
691
|
-
* Accessor may return the an explicitly configured domain or a domain extracted from the data.
|
|
692
|
-
*
|
|
693
|
-
* @param {function(ResolutionMember):DomainArray} domainAccessor
|
|
694
|
-
* @returns {DomainArray}
|
|
695
|
-
*/
|
|
696
|
-
_reduceDomains(domainAccessor) {
|
|
697
|
-
const domains = this.members
|
|
698
|
-
.map(domainAccessor)
|
|
699
|
-
.filter((domain) => !!domain);
|
|
700
|
-
|
|
701
|
-
if (domains.length) {
|
|
702
|
-
return domains.reduce((acc, curr) => acc.extendAll(curr));
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
/**
|
|
708
|
-
*
|
|
709
|
-
* @param {Channel} channel
|
|
710
|
-
* @param {string} dataType
|
|
711
|
-
* @returns {import("../spec/scale").ScaleType}
|
|
712
|
-
*/
|
|
713
|
-
function getDefaultScaleType(channel, dataType) {
|
|
714
|
-
// TODO: Band scale, Bin-Quantitative
|
|
715
|
-
|
|
716
|
-
if (dataType == INDEX || dataType == LOCUS) {
|
|
717
|
-
if (isPrimaryPositionalChannel(channel)) {
|
|
718
|
-
return dataType;
|
|
719
|
-
} else {
|
|
720
|
-
// TODO: Also explicitly set scales should be validated
|
|
721
|
-
throw new Error(
|
|
722
|
-
`${channel} does not support ${dataType} data type. Only positional channels do.`
|
|
723
|
-
);
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
/**
|
|
728
|
-
* @type {Partial<Record<Channel, (import("../spec/scale").ScaleType | undefined)[]>>}
|
|
729
|
-
* Default types: nominal, ordinal, quantitative.
|
|
730
|
-
* undefined = incompatible, "null" = disabled (pass-thru)
|
|
731
|
-
*/
|
|
732
|
-
const defaults = {
|
|
733
|
-
x: ["band", "band", "linear"],
|
|
734
|
-
y: ["band", "band", "linear"],
|
|
735
|
-
size: [undefined, "point", "linear"],
|
|
736
|
-
opacity: [undefined, "point", "linear"],
|
|
737
|
-
fillOpacity: [undefined, "point", "linear"],
|
|
738
|
-
strokeOpacity: [undefined, "point", "linear"],
|
|
739
|
-
color: ["ordinal", "ordinal", "linear"],
|
|
740
|
-
fill: ["ordinal", "ordinal", "linear"],
|
|
741
|
-
stroke: ["ordinal", "ordinal", "linear"],
|
|
742
|
-
strokeWidth: [undefined, undefined, "linear"],
|
|
743
|
-
shape: ["ordinal", "ordinal", undefined],
|
|
744
|
-
dx: [undefined, undefined, "null"],
|
|
745
|
-
dy: [undefined, undefined, "null"],
|
|
746
|
-
angle: [undefined, undefined, "linear"],
|
|
747
|
-
};
|
|
748
|
-
|
|
749
|
-
/** @type {Channel[]} */
|
|
750
|
-
const typelessChannels = [
|
|
751
|
-
"uniqueId",
|
|
752
|
-
"facetIndex",
|
|
753
|
-
"semanticScore",
|
|
754
|
-
"search",
|
|
755
|
-
"text",
|
|
756
|
-
"sample",
|
|
757
|
-
];
|
|
758
|
-
|
|
759
|
-
const type = typelessChannels.includes(channel)
|
|
760
|
-
? "null"
|
|
761
|
-
: defaults[channel]
|
|
762
|
-
? defaults[channel][[NOMINAL, ORDINAL, QUANTITATIVE].indexOf(dataType)]
|
|
763
|
-
: dataType == QUANTITATIVE
|
|
764
|
-
? "linear"
|
|
765
|
-
: "ordinal";
|
|
766
|
-
|
|
767
|
-
if (type === undefined) {
|
|
768
|
-
throw new Error(
|
|
769
|
-
`Channel "${channel}" is not compatible with "${dataType}" data type. Use of a proper scale may be needed.`
|
|
770
|
-
);
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
return type;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
/**
|
|
777
|
-
* @param {import("../spec/scale").Scale} props
|
|
778
|
-
* @param {import("../spec/channel").Channel} channel
|
|
779
|
-
*/
|
|
780
|
-
function applyLockedProperties(props, channel) {
|
|
781
|
-
if (isPositionalChannel(channel) && props.type !== "ordinal") {
|
|
782
|
-
props.range = [0, 1];
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
if (channel == "opacity" && isContinuous(props.type)) {
|
|
786
|
-
props.clamp = true;
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
/**
|
|
791
|
-
*
|
|
792
|
-
* @param {boolean | ZoomParams} zoom
|
|
793
|
-
* @returns {zoom is ZoomParams}
|
|
794
|
-
*/
|
|
795
|
-
function isZoomParams(zoom) {
|
|
796
|
-
return isObject(zoom);
|
|
797
|
-
}
|