@genome-spy/core 0.77.0 → 0.78.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/{esm-CscjKVDc.js → esm-DAnOffpD.js} +1 -1
- package/dist/bundle/{esm-0dYHNV_D.js → esm-DNtC3H80.js} +1 -1
- package/dist/bundle/{esm-CRMf_I9V.js → esm-DVOHLB1e.js} +1 -1
- package/dist/bundle/esm-NIYEaYkc.js +1221 -0
- package/dist/bundle/index.es.js +2816 -2703
- package/dist/bundle/index.js +57 -54
- package/dist/schema.json +825 -112
- package/dist/src/genomeSpy/headlessBootstrap.d.ts.map +1 -1
- package/dist/src/genomeSpy/headlessBootstrap.js +2 -0
- package/dist/src/genomeSpy/interactionController.d.ts +4 -1
- package/dist/src/genomeSpy/interactionController.d.ts.map +1 -1
- package/dist/src/genomeSpy/interactionController.js +57 -13
- package/dist/src/genomeSpyBase.d.ts.map +1 -1
- package/dist/src/genomeSpyBase.js +5 -1
- package/dist/src/scales/domainExpressions.d.ts +21 -0
- package/dist/src/scales/domainExpressions.d.ts.map +1 -0
- package/dist/src/scales/domainExpressions.js +43 -0
- package/dist/src/scales/domainPlanner.d.ts +12 -1
- package/dist/src/scales/domainPlanner.d.ts.map +1 -1
- package/dist/src/scales/domainPlanner.js +55 -36
- package/dist/src/scales/scaleInstanceManager.d.ts +1 -0
- package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -1
- package/dist/src/scales/scaleInstanceManager.js +5 -0
- package/dist/src/scales/scalePropsResolver.d.ts +6 -1
- package/dist/src/scales/scalePropsResolver.d.ts.map +1 -1
- package/dist/src/scales/scalePropsResolver.js +35 -10
- package/dist/src/scales/scaleResolution.d.ts +16 -0
- package/dist/src/scales/scaleResolution.d.ts.map +1 -1
- package/dist/src/scales/scaleResolution.js +136 -16
- package/dist/src/scales/scaleRules.d.ts +10 -0
- package/dist/src/scales/scaleRules.d.ts.map +1 -1
- package/dist/src/scales/scaleRules.js +38 -1
- package/dist/src/scales/viewLevelScaleConfig.d.ts +45 -0
- package/dist/src/scales/viewLevelScaleConfig.d.ts.map +1 -0
- package/dist/src/scales/viewLevelScaleConfig.js +138 -0
- package/dist/src/spec/scale.d.ts +19 -6
- package/dist/src/spec/view.d.ts +11 -0
- package/dist/src/styles/genome-spy.css +4 -1
- 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 +4 -1
- package/dist/src/utils/ui/tooltip.d.ts +4 -0
- package/dist/src/utils/ui/tooltip.d.ts.map +1 -1
- package/dist/src/utils/ui/tooltip.js +35 -10
- package/dist/src/view/containerMutationHelper.d.ts.map +1 -1
- package/dist/src/view/containerMutationHelper.js +11 -3
- package/package.json +2 -2
- package/dist/bundle/esm-C49STiCR.js +0 -1248
|
@@ -9,7 +9,12 @@ import {
|
|
|
9
9
|
getConfiguredNamedRange,
|
|
10
10
|
isConfigRangeName,
|
|
11
11
|
} from "../config/scaleConfig.js";
|
|
12
|
-
import {
|
|
12
|
+
import { collectConfiguredDomainExprRefs } from "./domainExpressions.js";
|
|
13
|
+
import {
|
|
14
|
+
applyLockedProperties,
|
|
15
|
+
getDefaultScaleType,
|
|
16
|
+
validateScaleTypeCompatibility,
|
|
17
|
+
} from "./scaleRules.js";
|
|
13
18
|
import { INDEX, LOCUS } from "./scaleResolutionConstants.js";
|
|
14
19
|
|
|
15
20
|
/**
|
|
@@ -23,6 +28,7 @@ import { INDEX, LOCUS } from "./scaleResolutionConstants.js";
|
|
|
23
28
|
* @param {Channel} options.channel
|
|
24
29
|
* @param {import("../spec/channel.js").Type} options.dataType
|
|
25
30
|
* @param {ScaleResolutionMember[]} options.orderedMembers
|
|
31
|
+
* @param {{ view: import("../view/view.js").default, config: Scale } | undefined} [options.viewLevelScaleConfig]
|
|
26
32
|
* @param {boolean} options.isExplicitDomain
|
|
27
33
|
* @param {import("../spec/config.js").GenomeSpyConfig[]} options.configScopes
|
|
28
34
|
* @returns {Scale}
|
|
@@ -31,6 +37,7 @@ export function resolveScalePropsBase({
|
|
|
31
37
|
channel,
|
|
32
38
|
dataType,
|
|
33
39
|
orderedMembers,
|
|
40
|
+
viewLevelScaleConfig,
|
|
34
41
|
isExplicitDomain,
|
|
35
42
|
configScopes,
|
|
36
43
|
}) {
|
|
@@ -44,9 +51,11 @@ export function resolveScalePropsBase({
|
|
|
44
51
|
)
|
|
45
52
|
.filter((markType) => !!markType);
|
|
46
53
|
|
|
47
|
-
const propArray =
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
const propArray = viewLevelScaleConfig
|
|
55
|
+
? [viewLevelScaleConfig.config]
|
|
56
|
+
: memberList
|
|
57
|
+
.map((member) => member.channelDef.scale)
|
|
58
|
+
.filter((props) => props !== undefined);
|
|
50
59
|
|
|
51
60
|
// TODO: Disabled scale: https://vega.github.io/vega-lite/docs/scale.html#disable
|
|
52
61
|
const mergedProps = mergeObjects(propArray, "scale", ["domain"]);
|
|
@@ -76,6 +85,13 @@ export function resolveScalePropsBase({
|
|
|
76
85
|
props.type = getDefaultScaleType(channel, dataType);
|
|
77
86
|
}
|
|
78
87
|
|
|
88
|
+
validateScaleTypeCompatibility(
|
|
89
|
+
channel,
|
|
90
|
+
dataType,
|
|
91
|
+
viewLevelScaleConfig ? props.type : undefined,
|
|
92
|
+
`View-level scales.${channel}.type`
|
|
93
|
+
);
|
|
94
|
+
|
|
79
95
|
if (typeof props.range == "string") {
|
|
80
96
|
if (!isConfigRangeName(props.range)) {
|
|
81
97
|
throw new Error(
|
|
@@ -132,9 +148,15 @@ export function resolveScalePropsBase({
|
|
|
132
148
|
}
|
|
133
149
|
|
|
134
150
|
if (props.domainTransition === undefined) {
|
|
135
|
-
const hasExprDrivenDomain =
|
|
136
|
-
|
|
137
|
-
|
|
151
|
+
const hasExprDrivenDomain =
|
|
152
|
+
memberList.some(
|
|
153
|
+
(member) =>
|
|
154
|
+
collectConfiguredDomainExprRefs(
|
|
155
|
+
member.channelDef.scale?.domain
|
|
156
|
+
).length > 0
|
|
157
|
+
) ||
|
|
158
|
+
collectConfiguredDomainExprRefs(viewLevelScaleConfig?.config.domain)
|
|
159
|
+
.length > 0;
|
|
138
160
|
props.domainTransition = !hasExprDrivenDomain;
|
|
139
161
|
}
|
|
140
162
|
|
|
@@ -143,9 +165,12 @@ export function resolveScalePropsBase({
|
|
|
143
165
|
props.range.some(isExprRef) &&
|
|
144
166
|
memberList.length > 0
|
|
145
167
|
) {
|
|
146
|
-
const rangeOwner =
|
|
147
|
-
|
|
148
|
-
|
|
168
|
+
const rangeOwner =
|
|
169
|
+
viewLevelScaleConfig?.config.range !== undefined
|
|
170
|
+
? viewLevelScaleConfig.view
|
|
171
|
+
: memberList.find(
|
|
172
|
+
(member) => member.channelDef.scale?.range !== undefined
|
|
173
|
+
)?.view;
|
|
149
174
|
if (rangeOwner) {
|
|
150
175
|
/** @type {any} */
|
|
151
176
|
(props).__rangeExprScope = rangeOwner;
|
|
@@ -68,6 +68,22 @@ export default class ScaleResolution implements ScaleResolutionApi {
|
|
|
68
68
|
* @returns {() => boolean}
|
|
69
69
|
*/
|
|
70
70
|
registerMember(member: ScaleResolutionMember): () => boolean;
|
|
71
|
+
/**
|
|
72
|
+
* @param {import("../view/view.js").default} view
|
|
73
|
+
* @param {import("../spec/scale.js").Scale} config
|
|
74
|
+
*/
|
|
75
|
+
attachViewLevelScaleConfig(view: import("../view/view.js").default, config: import("../spec/scale.js").Scale): void;
|
|
76
|
+
/**
|
|
77
|
+
* @param {import("../view/view.js").default} view
|
|
78
|
+
*/
|
|
79
|
+
clearViewLevelScaleConfig(view: import("../view/view.js").default): void;
|
|
80
|
+
/**
|
|
81
|
+
* @returns {{ view: import("../view/view.js").default, config: import("../spec/scale.js").Scale } | undefined}
|
|
82
|
+
*/
|
|
83
|
+
getViewLevelScaleConfig(): {
|
|
84
|
+
view: import("../view/view.js").default;
|
|
85
|
+
config: import("../spec/scale.js").Scale;
|
|
86
|
+
} | undefined;
|
|
71
87
|
dispose(): void;
|
|
72
88
|
/**
|
|
73
89
|
* @param {import("../data/collector.js").default} collector
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaleResolution.d.ts","sourceRoot":"","sources":["../../../src/scales/scaleResolution.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scaleResolution.d.ts","sourceRoot":"","sources":["../../../src/scales/scaleResolution.js"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,7 @@ import ScaleInstanceManager from "./scaleInstanceManager.js";
|
|
|
15
15
|
import { resolveScalePropsBase } from "./scalePropsResolver.js";
|
|
16
16
|
import DomainPlanner from "./domainPlanner.js";
|
|
17
17
|
import ScaleInteractionController from "./scaleInteractionController.js";
|
|
18
|
+
import { validateScaleTypeCompatibility } from "./scaleRules.js";
|
|
18
19
|
import {
|
|
19
20
|
INDEX,
|
|
20
21
|
LOCUS,
|
|
@@ -24,11 +25,8 @@ import {
|
|
|
24
25
|
} from "./scaleResolutionConstants.js";
|
|
25
26
|
|
|
26
27
|
import { getAccessorDomainKey } from "../encoder/accessor.js";
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
isSecondaryChannel,
|
|
30
|
-
} from "../encoder/encoder.js";
|
|
31
|
-
import { isExprRef } from "../paramRuntime/paramUtils.js";
|
|
28
|
+
import { isSecondaryChannel } from "../encoder/encoder.js";
|
|
29
|
+
import { collectConfiguredDomainExprRefs } from "./domainExpressions.js";
|
|
32
30
|
import { NominalDomain } from "../utils/domainArray.js";
|
|
33
31
|
import { shallowArrayEquals } from "../utils/arrayUtils.js";
|
|
34
32
|
import createIndexer from "../utils/indexer.js";
|
|
@@ -143,6 +141,11 @@ export default class ScaleResolution {
|
|
|
143
141
|
/** @type {import("../view/view.js").default | undefined} */
|
|
144
142
|
#hostView;
|
|
145
143
|
|
|
144
|
+
/**
|
|
145
|
+
* @type {{ view: import("../view/view.js").default, config: import("../spec/scale.js").Scale } | undefined}
|
|
146
|
+
*/
|
|
147
|
+
#viewLevelScaleConfig;
|
|
148
|
+
|
|
146
149
|
#resolvingScaleProps = 0;
|
|
147
150
|
|
|
148
151
|
#memberRegistrationBatchDepth = 0;
|
|
@@ -168,6 +171,8 @@ export default class ScaleResolution {
|
|
|
168
171
|
getAllMembers: () => this.#members,
|
|
169
172
|
getDataMembers: () =>
|
|
170
173
|
this.#getActiveMembers(this.#dataDomainMembers),
|
|
174
|
+
getViewLevelConfiguredDomain: () =>
|
|
175
|
+
this.#getViewLevelConfiguredDomain(),
|
|
171
176
|
getType: () => this.type,
|
|
172
177
|
getLocusExtent: (assembly) => this.#getLocusExtent(assembly),
|
|
173
178
|
fromComplexInterval: this.fromComplexInterval.bind(this),
|
|
@@ -400,6 +405,10 @@ export default class ScaleResolution {
|
|
|
400
405
|
* @returns {boolean}
|
|
401
406
|
*/
|
|
402
407
|
#hasConfiguredDomain() {
|
|
408
|
+
if (this.#viewLevelScaleConfig?.config.domain !== undefined) {
|
|
409
|
+
return true;
|
|
410
|
+
}
|
|
411
|
+
|
|
403
412
|
for (const member of this.#members) {
|
|
404
413
|
if (
|
|
405
414
|
member.contributesToDomain &&
|
|
@@ -452,6 +461,8 @@ export default class ScaleResolution {
|
|
|
452
461
|
const member = normalizeMember(newMember);
|
|
453
462
|
const { channel, channelDef } = member;
|
|
454
463
|
|
|
464
|
+
this.#assertCanRegisterMember(member);
|
|
465
|
+
|
|
455
466
|
// A convenience hack for cases where the new member should adapt
|
|
456
467
|
// the scale type to the existing one. For example: SelectionRect
|
|
457
468
|
// TODO: Add test
|
|
@@ -481,15 +492,12 @@ export default class ScaleResolution {
|
|
|
481
492
|
explicitScaleType ??
|
|
482
493
|
(type === INDEX || type === LOCUS ? type : undefined);
|
|
483
494
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
`Index and locus scales are only supported on positional channels (x/y). Channel "${this.channel}" resolves to scale type "${effectiveScaleType}".`
|
|
491
|
-
);
|
|
492
|
-
}
|
|
495
|
+
validateScaleTypeCompatibility(
|
|
496
|
+
this.channel,
|
|
497
|
+
type,
|
|
498
|
+
effectiveScaleType,
|
|
499
|
+
`encoding.${channel}.scale.type`
|
|
500
|
+
);
|
|
493
501
|
|
|
494
502
|
if (name) {
|
|
495
503
|
if (this.name !== undefined && name != this.name) {
|
|
@@ -587,6 +595,100 @@ export default class ScaleResolution {
|
|
|
587
595
|
};
|
|
588
596
|
}
|
|
589
597
|
|
|
598
|
+
/**
|
|
599
|
+
* @param {import("../view/view.js").default} view
|
|
600
|
+
* @param {import("../spec/scale.js").Scale} config
|
|
601
|
+
*/
|
|
602
|
+
attachViewLevelScaleConfig(view, config) {
|
|
603
|
+
if (
|
|
604
|
+
this.#viewLevelScaleConfig &&
|
|
605
|
+
this.#viewLevelScaleConfig.view !== view
|
|
606
|
+
) {
|
|
607
|
+
throw new Error(
|
|
608
|
+
`Multiple view-level scale configs target the same ${this.channel} scale resolution.`
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
for (const member of this.#members) {
|
|
613
|
+
this.#assertMemberHasNoScaleConfig(member);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
this.#viewLevelScaleConfig = { view, config };
|
|
617
|
+
this.#invalidateMergedScaleProps();
|
|
618
|
+
this.#invalidateConfiguredDomain();
|
|
619
|
+
this.#refreshSelectionDomainParamSubscriptions();
|
|
620
|
+
this.#refreshConfiguredDomainExprSubscriptions();
|
|
621
|
+
if (this.#scaleManager.scale) {
|
|
622
|
+
this.#scaleManager.resetScale();
|
|
623
|
+
this.initializeScale();
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/**
|
|
628
|
+
* @param {import("../view/view.js").default} view
|
|
629
|
+
*/
|
|
630
|
+
clearViewLevelScaleConfig(view) {
|
|
631
|
+
if (this.#viewLevelScaleConfig?.view === view) {
|
|
632
|
+
this.#viewLevelScaleConfig = undefined;
|
|
633
|
+
this.#invalidateMergedScaleProps();
|
|
634
|
+
this.#invalidateConfiguredDomain();
|
|
635
|
+
this.#refreshSelectionDomainParamSubscriptions();
|
|
636
|
+
this.#refreshConfiguredDomainExprSubscriptions();
|
|
637
|
+
if (this.#scaleManager.scale) {
|
|
638
|
+
this.#scaleManager.resetScale();
|
|
639
|
+
this.initializeScale();
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* @returns {{ view: import("../view/view.js").default, config: import("../spec/scale.js").Scale } | undefined}
|
|
646
|
+
*/
|
|
647
|
+
getViewLevelScaleConfig() {
|
|
648
|
+
return this.#viewLevelScaleConfig;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
#getViewLevelConfiguredDomain() {
|
|
652
|
+
const viewLevelScaleConfig = this.#viewLevelScaleConfig;
|
|
653
|
+
if (!viewLevelScaleConfig) {
|
|
654
|
+
return undefined;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return {
|
|
658
|
+
view: viewLevelScaleConfig.view,
|
|
659
|
+
channel:
|
|
660
|
+
/** @type {import("../spec/channel.js").ChannelWithScale} */ (
|
|
661
|
+
this.channel
|
|
662
|
+
),
|
|
663
|
+
type: this.type,
|
|
664
|
+
domain: viewLevelScaleConfig.config.domain,
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* @param {ScaleResolutionMember} member
|
|
670
|
+
*/
|
|
671
|
+
#assertCanRegisterMember(member) {
|
|
672
|
+
if (!this.#viewLevelScaleConfig) {
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
this.#assertMemberHasNoScaleConfig(member);
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* @param {ScaleResolutionMember} member
|
|
681
|
+
*/
|
|
682
|
+
#assertMemberHasNoScaleConfig(member) {
|
|
683
|
+
if (member.channelDef.scale === undefined) {
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
throw new Error(
|
|
688
|
+
`Cannot mix view-level scales.${this.channel} with encoding.${member.channel}.scale in the same scale resolution.`
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
|
|
590
692
|
dispose() {
|
|
591
693
|
this.#clearSelectionDomainParamSubscriptions();
|
|
592
694
|
this.#clearConfiguredDomainExprSubscriptions();
|
|
@@ -658,11 +760,28 @@ export default class ScaleResolution {
|
|
|
658
760
|
continue;
|
|
659
761
|
}
|
|
660
762
|
const domain = member.channelDef.scale?.domain;
|
|
661
|
-
|
|
763
|
+
const exprRefs = collectConfiguredDomainExprRefs(domain);
|
|
764
|
+
if (exprRefs.length === 0) {
|
|
662
765
|
continue;
|
|
663
766
|
}
|
|
664
767
|
|
|
665
|
-
const
|
|
768
|
+
for (const exprRef of exprRefs) {
|
|
769
|
+
const expr = member.view.paramRuntime.createExpression(
|
|
770
|
+
exprRef.expr
|
|
771
|
+
);
|
|
772
|
+
const unsubscribe = expr.subscribe(listener);
|
|
773
|
+
this.#configuredDomainExprUnsubscribers.push(unsubscribe);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const viewLevelDomain = this.#viewLevelScaleConfig?.config.domain;
|
|
778
|
+
const viewLevelExprRefs =
|
|
779
|
+
collectConfiguredDomainExprRefs(viewLevelDomain);
|
|
780
|
+
for (const exprRef of viewLevelExprRefs) {
|
|
781
|
+
const expr =
|
|
782
|
+
this.#viewLevelScaleConfig.view.paramRuntime.createExpression(
|
|
783
|
+
exprRef.expr
|
|
784
|
+
);
|
|
666
785
|
const unsubscribe = expr.subscribe(listener);
|
|
667
786
|
this.#configuredDomainExprUnsubscribers.push(unsubscribe);
|
|
668
787
|
}
|
|
@@ -754,6 +873,7 @@ export default class ScaleResolution {
|
|
|
754
873
|
channel: this.channel,
|
|
755
874
|
dataType: this.type,
|
|
756
875
|
orderedMembers: this.#getOrderedMembers(),
|
|
876
|
+
viewLevelScaleConfig: this.#viewLevelScaleConfig,
|
|
757
877
|
isExplicitDomain: this.isDomainDefinedExplicitly(),
|
|
758
878
|
configScopes: this.#resolutionView.getConfigScopes(),
|
|
759
879
|
});
|
|
@@ -7,6 +7,16 @@
|
|
|
7
7
|
* @returns {import("../spec/scale.js").ScaleType}
|
|
8
8
|
*/
|
|
9
9
|
export function getDefaultScaleType(channel: Channel, dataType: import("../spec/channel.js").Type): import("../spec/scale.js").ScaleType;
|
|
10
|
+
/**
|
|
11
|
+
* Validates explicit scale type choices against the resolved data type and
|
|
12
|
+
* channel constraints.
|
|
13
|
+
*
|
|
14
|
+
* @param {Channel} channel
|
|
15
|
+
* @param {import("../spec/channel.js").Type} dataType
|
|
16
|
+
* @param {import("../spec/scale.js").ScaleType | undefined} scaleType
|
|
17
|
+
* @param {string} source
|
|
18
|
+
*/
|
|
19
|
+
export function validateScaleTypeCompatibility(channel: Channel, dataType: import("../spec/channel.js").Type, scaleType: import("../spec/scale.js").ScaleType | undefined, source: string): void;
|
|
10
20
|
/**
|
|
11
21
|
* @param {import("../spec/scale.js").Scale} props
|
|
12
22
|
* @param {Channel} channel
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaleRules.d.ts","sourceRoot":"","sources":["../../../src/scales/scaleRules.js"],"names":[],"mappings":"AAcA;;GAEG;AAEH;;;;GAIG;AACH,6CAJW,OAAO,YACP,OAAO,oBAAoB,EAAE,IAAI,GAC/B,OAAO,kBAAkB,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"scaleRules.d.ts","sourceRoot":"","sources":["../../../src/scales/scaleRules.js"],"names":[],"mappings":"AAcA;;GAEG;AAEH;;;;GAIG;AACH,6CAJW,OAAO,YACP,OAAO,oBAAoB,EAAE,IAAI,GAC/B,OAAO,kBAAkB,EAAE,SAAS,CA0EhD;AAED;;;;;;;;GAQG;AACH,wDALW,OAAO,YACP,OAAO,oBAAoB,EAAE,IAAI,aACjC,OAAO,kBAAkB,EAAE,SAAS,GAAG,SAAS,UAChD,MAAM,QA6BhB;AAED;;;GAGG;AACH,6CAHW,OAAO,kBAAkB,EAAE,KAAK,WAChC,OAAO,QAgBjB;sBA1IY,OAAO,oBAAoB,EAAE,OAAO"}
|
|
@@ -37,7 +37,6 @@ export function getDefaultScaleType(channel, dataType) {
|
|
|
37
37
|
if (isPrimaryPositionalChannel(channel)) {
|
|
38
38
|
return dataType;
|
|
39
39
|
} else {
|
|
40
|
-
// TODO: Also explicitly set scales should be validated
|
|
41
40
|
throw new Error(
|
|
42
41
|
channel +
|
|
43
42
|
" does not support " +
|
|
@@ -96,6 +95,44 @@ export function getDefaultScaleType(channel, dataType) {
|
|
|
96
95
|
return type;
|
|
97
96
|
}
|
|
98
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Validates explicit scale type choices against the resolved data type and
|
|
100
|
+
* channel constraints.
|
|
101
|
+
*
|
|
102
|
+
* @param {Channel} channel
|
|
103
|
+
* @param {import("../spec/channel.js").Type} dataType
|
|
104
|
+
* @param {import("../spec/scale.js").ScaleType | undefined} scaleType
|
|
105
|
+
* @param {string} source
|
|
106
|
+
*/
|
|
107
|
+
export function validateScaleTypeCompatibility(
|
|
108
|
+
channel,
|
|
109
|
+
dataType,
|
|
110
|
+
scaleType,
|
|
111
|
+
source
|
|
112
|
+
) {
|
|
113
|
+
if (!scaleType) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (
|
|
118
|
+
[INDEX, LOCUS].includes(scaleType) &&
|
|
119
|
+
!isPrimaryPositionalChannel(channel)
|
|
120
|
+
) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
`Index and locus scales are only supported on positional channels (x/y). Channel "${channel}" resolves to scale type "${scaleType}".`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (
|
|
127
|
+
([INDEX, LOCUS].includes(dataType) && scaleType !== dataType) ||
|
|
128
|
+
(scaleType === LOCUS && scaleType !== dataType)
|
|
129
|
+
) {
|
|
130
|
+
throw new Error(
|
|
131
|
+
`${source} "${scaleType}" is incompatible with "${dataType}" data.`
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
99
136
|
/**
|
|
100
137
|
* @param {import("../spec/scale.js").Scale} props
|
|
101
138
|
* @param {Channel} channel
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../spec/channel.js").ChannelWithScale} ChannelWithScale
|
|
3
|
+
* @typedef {import("../spec/scale.js").Scale} Scale
|
|
4
|
+
* @typedef {import("../view/view.js").default} View
|
|
5
|
+
* @typedef {import("./scaleResolution.js").default} ScaleResolution
|
|
6
|
+
*
|
|
7
|
+
* @typedef {object} ViewLevelScaleConfigMapping
|
|
8
|
+
* @prop {View} view
|
|
9
|
+
* @prop {ChannelWithScale} channel
|
|
10
|
+
* @prop {Scale} config
|
|
11
|
+
* @prop {ScaleResolution | undefined} resolution
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Maps view-level scale configs to the unique scale resolution visible from
|
|
15
|
+
* each configured subtree. Configs with no matching resolution stay pending.
|
|
16
|
+
*
|
|
17
|
+
* @param {View} root
|
|
18
|
+
* @returns {ViewLevelScaleConfigMapping[]}
|
|
19
|
+
*/
|
|
20
|
+
export function mapViewLevelScaleConfigs(root: View): ViewLevelScaleConfigMapping[];
|
|
21
|
+
/**
|
|
22
|
+
* Maps view-level scale configs and attaches non-pending configs to their
|
|
23
|
+
* target resolutions.
|
|
24
|
+
*
|
|
25
|
+
* @param {View} root
|
|
26
|
+
* @returns {ViewLevelScaleConfigMapping[]}
|
|
27
|
+
*/
|
|
28
|
+
export function attachViewLevelScaleConfigs(root: View): ViewLevelScaleConfigMapping[];
|
|
29
|
+
/**
|
|
30
|
+
* Clears view-level scale configs owned by views in the subtree.
|
|
31
|
+
*
|
|
32
|
+
* @param {View} root
|
|
33
|
+
*/
|
|
34
|
+
export function clearViewLevelScaleConfigs(root: View): void;
|
|
35
|
+
export type ChannelWithScale = import("../spec/channel.js").ChannelWithScale;
|
|
36
|
+
export type Scale = import("../spec/scale.js").Scale;
|
|
37
|
+
export type View = import("../view/view.js").default;
|
|
38
|
+
export type ScaleResolution = import("./scaleResolution.js").default;
|
|
39
|
+
export type ViewLevelScaleConfigMapping = {
|
|
40
|
+
view: View;
|
|
41
|
+
channel: ChannelWithScale;
|
|
42
|
+
config: Scale;
|
|
43
|
+
resolution: ScaleResolution | undefined;
|
|
44
|
+
};
|
|
45
|
+
//# sourceMappingURL=viewLevelScaleConfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viewLevelScaleConfig.d.ts","sourceRoot":"","sources":["../../../src/scales/viewLevelScaleConfig.js"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;;;GAMG;AACH,+CAHW,IAAI,GACF,2BAA2B,EAAE,CAwBzC;AAED;;;;;;GAMG;AACH,kDAHW,IAAI,GACF,2BAA2B,EAAE,CAczC;AAED;;;;GAIG;AACH,iDAFW,IAAI,QAYd;+BA/EY,OAAO,oBAAoB,EAAE,gBAAgB;oBAC7C,OAAO,kBAAkB,EAAE,KAAK;mBAChC,OAAO,iBAAiB,EAAE,OAAO;8BACjC,OAAO,sBAAsB,EAAE,OAAO;;UAGzC,IAAI;aACJ,gBAAgB;YAChB,KAAK;gBACL,eAAe,GAAG,SAAS"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import("../spec/channel.js").ChannelWithScale} ChannelWithScale
|
|
3
|
+
* @typedef {import("../spec/scale.js").Scale} Scale
|
|
4
|
+
* @typedef {import("../view/view.js").default} View
|
|
5
|
+
* @typedef {import("./scaleResolution.js").default} ScaleResolution
|
|
6
|
+
*
|
|
7
|
+
* @typedef {object} ViewLevelScaleConfigMapping
|
|
8
|
+
* @prop {View} view
|
|
9
|
+
* @prop {ChannelWithScale} channel
|
|
10
|
+
* @prop {Scale} config
|
|
11
|
+
* @prop {ScaleResolution | undefined} resolution
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Maps view-level scale configs to the unique scale resolution visible from
|
|
16
|
+
* each configured subtree. Configs with no matching resolution stay pending.
|
|
17
|
+
*
|
|
18
|
+
* @param {View} root
|
|
19
|
+
* @returns {ViewLevelScaleConfigMapping[]}
|
|
20
|
+
*/
|
|
21
|
+
export function mapViewLevelScaleConfigs(root) {
|
|
22
|
+
/** @type {ViewLevelScaleConfigMapping[]} */
|
|
23
|
+
const mappings = [];
|
|
24
|
+
|
|
25
|
+
for (const view of root.getDescendants()) {
|
|
26
|
+
const scales = view.spec.scales;
|
|
27
|
+
if (!scales) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
for (const [channel, config] of Object.entries(scales)) {
|
|
32
|
+
mappings.push(
|
|
33
|
+
mapViewLevelScaleConfig(
|
|
34
|
+
view,
|
|
35
|
+
/** @type {ChannelWithScale} */ (channel),
|
|
36
|
+
config
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return mappings;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Maps view-level scale configs and attaches non-pending configs to their
|
|
47
|
+
* target resolutions.
|
|
48
|
+
*
|
|
49
|
+
* @param {View} root
|
|
50
|
+
* @returns {ViewLevelScaleConfigMapping[]}
|
|
51
|
+
*/
|
|
52
|
+
export function attachViewLevelScaleConfigs(root) {
|
|
53
|
+
clearViewLevelScaleConfigs(root);
|
|
54
|
+
const mappings = mapViewLevelScaleConfigs(root);
|
|
55
|
+
for (const mapping of mappings) {
|
|
56
|
+
if (mapping.resolution) {
|
|
57
|
+
mapping.resolution.attachViewLevelScaleConfig(
|
|
58
|
+
mapping.view,
|
|
59
|
+
mapping.config
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return mappings;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Clears view-level scale configs owned by views in the subtree.
|
|
68
|
+
*
|
|
69
|
+
* @param {View} root
|
|
70
|
+
*/
|
|
71
|
+
export function clearViewLevelScaleConfigs(root) {
|
|
72
|
+
const views = new Set(root.getDescendants());
|
|
73
|
+
const resolutions = collectAllScaleResolutions(root);
|
|
74
|
+
|
|
75
|
+
for (const resolution of resolutions) {
|
|
76
|
+
const config = resolution.getViewLevelScaleConfig();
|
|
77
|
+
if (config && views.has(config.view)) {
|
|
78
|
+
resolution.clearViewLevelScaleConfig(config.view);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* @param {View} view
|
|
85
|
+
* @param {ChannelWithScale} channel
|
|
86
|
+
* @param {Scale} config
|
|
87
|
+
* @returns {ViewLevelScaleConfigMapping}
|
|
88
|
+
*/
|
|
89
|
+
function mapViewLevelScaleConfig(view, channel, config) {
|
|
90
|
+
const resolutions = collectVisibleScaleResolutions(view, channel);
|
|
91
|
+
|
|
92
|
+
if (resolutions.size > 1) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
`View-level scales.${channel} maps to multiple scale resolutions. ` +
|
|
95
|
+
`Move scales.${channel} closer to the intended subtree or configure scale resolution explicitly.`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const resolution = resolutions.values().next().value;
|
|
100
|
+
return {
|
|
101
|
+
view,
|
|
102
|
+
channel,
|
|
103
|
+
config,
|
|
104
|
+
resolution,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* @param {View} view
|
|
110
|
+
* @param {ChannelWithScale} channel
|
|
111
|
+
* @returns {Set<ScaleResolution>}
|
|
112
|
+
*/
|
|
113
|
+
function collectVisibleScaleResolutions(view, channel) {
|
|
114
|
+
/** @type {Set<ScaleResolution>} */
|
|
115
|
+
const resolutions = new Set();
|
|
116
|
+
for (const descendant of view.getDescendants()) {
|
|
117
|
+
const resolution = descendant.getScaleResolution(channel);
|
|
118
|
+
if (resolution) {
|
|
119
|
+
resolutions.add(resolution);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return resolutions;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @param {View} view
|
|
127
|
+
* @returns {Set<ScaleResolution>}
|
|
128
|
+
*/
|
|
129
|
+
function collectAllScaleResolutions(view) {
|
|
130
|
+
/** @type {Set<ScaleResolution>} */
|
|
131
|
+
const resolutions = new Set();
|
|
132
|
+
for (const descendant of view.getDescendants()) {
|
|
133
|
+
for (const resolution of Object.values(descendant.resolutions.scale)) {
|
|
134
|
+
resolutions.add(resolution);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return resolutions;
|
|
138
|
+
}
|
package/dist/src/spec/scale.d.ts
CHANGED
|
@@ -67,8 +67,17 @@ export interface Scale {
|
|
|
67
67
|
* For _temporal_ fields, `domain` can be a two-element array minimum and maximum values, in the form of either timestamps or the [DateTime definition objects](https://vega.github.io/vega-lite/docs/types.html#datetime).
|
|
68
68
|
*
|
|
69
69
|
* For _ordinal_ and _nominal_ fields, `domain` can be an array that lists valid input values.
|
|
70
|
+
|
|
71
|
+
* The domain can also be defined by an expression reference that evaluates to the domain array.
|
|
72
|
+
* Array elements may also be expression references.
|
|
73
|
+
*
|
|
70
74
|
*/
|
|
71
|
-
domain?:
|
|
75
|
+
domain?:
|
|
76
|
+
| ScalarDomain
|
|
77
|
+
| ComplexDomain
|
|
78
|
+
| SelectionDomainRef
|
|
79
|
+
| ExprRef
|
|
80
|
+
| DomainValueArray;
|
|
72
81
|
|
|
73
82
|
/**
|
|
74
83
|
* Inserts a single mid-point value into a two-element domain. The mid-point value must lie between the domain minimum and maximum values. This property can be useful for setting a midpoint for [diverging color scales](https://vega.github.io/vega-lite/docs/scale.html#piecewise). The domainMid property is only intended for use with scales supporting continuous, piecewise domains.
|
|
@@ -97,10 +106,10 @@ export interface Scale {
|
|
|
97
106
|
* transition.
|
|
98
107
|
*
|
|
99
108
|
* Set this to `false` to apply domain updates immediately. The default is
|
|
100
|
-
* `true`, except for domains
|
|
109
|
+
* `true`, except for domains that include `ExprRef`s, which default to
|
|
101
110
|
* `false` unless overridden.
|
|
102
111
|
*
|
|
103
|
-
* __Default value:__ `true`, except `false` for ExprRef
|
|
112
|
+
* __Default value:__ `true`, except `false` for `ExprRef`-driven domains.
|
|
104
113
|
*/
|
|
105
114
|
domainTransition?: boolean | Record<string, unknown>;
|
|
106
115
|
|
|
@@ -109,9 +118,9 @@ export interface Scale {
|
|
|
109
118
|
*
|
|
110
119
|
* - A string indicating a [pre-defined named scale range](https://vega.github.io/vega-lite/docs/scale.html#range-config) (e.g., example, `"symbol"`, or `"diverging"`).
|
|
111
120
|
*
|
|
112
|
-
* - For [continuous scales](https://vega.github.io/vega-lite/docs/scale.html#continuous), two-element array indicating
|
|
121
|
+
* - For [continuous scales](https://vega.github.io/vega-lite/docs/scale.html#continuous), two-element array indicating minimum and maximum values, or an array with more than two entries for specifying a [piecewise scale](https://vega.github.io/vega-lite/docs/scale.html#piecewise). Array elements may also be expression references.
|
|
113
122
|
*
|
|
114
|
-
* - For [discrete](https://vega.github.io/vega-lite/docs/scale.html#discrete) and [discretizing](https://vega.github.io/vega-lite/docs/scale.html#discretizing) scales, an array of desired output values.
|
|
123
|
+
* - For [discrete](https://vega.github.io/vega-lite/docs/scale.html#discrete) and [discretizing](https://vega.github.io/vega-lite/docs/scale.html#discretizing) scales, an array of desired output values. Array elements may also be expression references.
|
|
115
124
|
*
|
|
116
125
|
* __Notes:__
|
|
117
126
|
*
|
|
@@ -119,7 +128,7 @@ export interface Scale {
|
|
|
119
128
|
*
|
|
120
129
|
* 2) Any directly specified `range` for `x` and `y` channels will be ignored. Range can be customized via the view's corresponding [size](https://vega.github.io/vega-lite/docs/size.html) (`width` and `height`).
|
|
121
130
|
*/
|
|
122
|
-
range?: number
|
|
131
|
+
range?: (number | string | ExprRef)[] | string;
|
|
123
132
|
|
|
124
133
|
// ordinal
|
|
125
134
|
|
|
@@ -257,6 +266,10 @@ export interface Scale {
|
|
|
257
266
|
|
|
258
267
|
export type InlineLocusAssembly = GenomeDefinition;
|
|
259
268
|
|
|
269
|
+
export type DomainValue = number | string | boolean | ExprRef;
|
|
270
|
+
|
|
271
|
+
export type DomainValueArray = DomainValue[];
|
|
272
|
+
|
|
260
273
|
export interface SelectionDomainRef {
|
|
261
274
|
/**
|
|
262
275
|
* Name of an interval selection parameter that provides the domain.
|