@genome-spy/core 0.76.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.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/dist/bundle/browser-KWU9rWZT.js +123 -0
  3. package/dist/bundle/esm-CT3ygiMq.js +1084 -0
  4. package/dist/bundle/{esm-D_euN86T.js → esm-DAnOffpD.js} +2 -2
  5. package/dist/bundle/{esm-BimDEpBb.js → esm-DNtC3H80.js} +2 -2
  6. package/dist/bundle/{esm-CngqBe45.js → esm-DVOHLB1e.js} +2 -2
  7. package/dist/bundle/esm-NIYEaYkc.js +1221 -0
  8. package/dist/bundle/index.es.js +3007 -2787
  9. package/dist/bundle/index.js +110 -99
  10. package/dist/schema.json +825 -112
  11. package/dist/src/data/sources/lazy/bigWigSource.d.ts.map +1 -1
  12. package/dist/src/data/sources/lazy/bigWigSource.js +31 -11
  13. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts +19 -3
  14. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
  15. package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +34 -13
  16. package/dist/src/data/transforms/filterScoredLabels.d.ts.map +1 -1
  17. package/dist/src/data/transforms/filterScoredLabels.js +3 -2
  18. package/dist/src/embedFactory.d.ts.map +1 -1
  19. package/dist/src/embedFactory.js +2 -0
  20. package/dist/src/genomeSpy/headlessBootstrap.d.ts.map +1 -1
  21. package/dist/src/genomeSpy/headlessBootstrap.js +2 -0
  22. package/dist/src/genomeSpy/interactionController.d.ts +4 -1
  23. package/dist/src/genomeSpy/interactionController.d.ts.map +1 -1
  24. package/dist/src/genomeSpy/interactionController.js +57 -13
  25. package/dist/src/genomeSpy/viewContextFactory.d.ts.map +1 -1
  26. package/dist/src/genomeSpy/viewContextFactory.js +1 -0
  27. package/dist/src/genomeSpyBase.d.ts +6 -0
  28. package/dist/src/genomeSpyBase.d.ts.map +1 -1
  29. package/dist/src/genomeSpyBase.js +16 -1
  30. package/dist/src/index.d.ts +1 -0
  31. package/dist/src/index.d.ts.map +1 -1
  32. package/dist/src/index.js +1 -0
  33. package/dist/src/marks/__snapshots__/shaderSnapshot.test.js.snap +2 -2
  34. package/dist/src/marks/mark.d.ts.map +1 -1
  35. package/dist/src/marks/mark.js +0 -6
  36. package/dist/src/marks/rect.vertex.glsl.js +1 -1
  37. package/dist/src/minimal.d.ts +1 -0
  38. package/dist/src/minimal.d.ts.map +1 -1
  39. package/dist/src/minimal.js +1 -0
  40. package/dist/src/paramRuntime/embedParamApi.d.ts +26 -0
  41. package/dist/src/paramRuntime/embedParamApi.d.ts.map +1 -0
  42. package/dist/src/paramRuntime/embedParamApi.js +133 -0
  43. package/dist/src/scales/domainExpressions.d.ts +21 -0
  44. package/dist/src/scales/domainExpressions.d.ts.map +1 -0
  45. package/dist/src/scales/domainExpressions.js +43 -0
  46. package/dist/src/scales/domainPlanner.d.ts +12 -1
  47. package/dist/src/scales/domainPlanner.d.ts.map +1 -1
  48. package/dist/src/scales/domainPlanner.js +55 -36
  49. package/dist/src/scales/scaleInstanceManager.d.ts +1 -0
  50. package/dist/src/scales/scaleInstanceManager.d.ts.map +1 -1
  51. package/dist/src/scales/scaleInstanceManager.js +5 -0
  52. package/dist/src/scales/scaleInteractionController.d.ts +8 -4
  53. package/dist/src/scales/scaleInteractionController.d.ts.map +1 -1
  54. package/dist/src/scales/scaleInteractionController.js +55 -7
  55. package/dist/src/scales/scalePropsResolver.d.ts +6 -1
  56. package/dist/src/scales/scalePropsResolver.d.ts.map +1 -1
  57. package/dist/src/scales/scalePropsResolver.js +35 -10
  58. package/dist/src/scales/scaleResolution.d.ts +19 -3
  59. package/dist/src/scales/scaleResolution.d.ts.map +1 -1
  60. package/dist/src/scales/scaleResolution.js +141 -20
  61. package/dist/src/scales/scaleRules.d.ts +10 -0
  62. package/dist/src/scales/scaleRules.d.ts.map +1 -1
  63. package/dist/src/scales/scaleRules.js +38 -1
  64. package/dist/src/scales/viewLevelScaleConfig.d.ts +45 -0
  65. package/dist/src/scales/viewLevelScaleConfig.d.ts.map +1 -0
  66. package/dist/src/scales/viewLevelScaleConfig.js +138 -0
  67. package/dist/src/selection/index.d.ts +8 -0
  68. package/dist/src/selection/index.d.ts.map +1 -0
  69. package/dist/src/selection/index.js +12 -0
  70. package/dist/src/spec/scale.d.ts +19 -6
  71. package/dist/src/spec/view.d.ts +11 -0
  72. package/dist/src/styles/genome-spy.css +12 -1
  73. package/dist/src/styles/genome-spy.css.d.ts +1 -1
  74. package/dist/src/styles/genome-spy.css.d.ts.map +1 -1
  75. package/dist/src/styles/genome-spy.css.js +12 -1
  76. package/dist/src/types/embedApi.d.ts +54 -0
  77. package/dist/src/types/scaleResolutionApi.d.ts +28 -1
  78. package/dist/src/types/viewContext.d.ts +11 -0
  79. package/dist/src/utils/ui/tooltip.d.ts +4 -0
  80. package/dist/src/utils/ui/tooltip.d.ts.map +1 -1
  81. package/dist/src/utils/ui/tooltip.js +57 -10
  82. package/dist/src/view/containerMutationHelper.d.ts.map +1 -1
  83. package/dist/src/view/containerMutationHelper.js +11 -3
  84. package/package.json +3 -3
  85. package/dist/bundle/browser-BTgw5ieH.js +0 -126
  86. package/dist/bundle/esm-Bvlm1uVk.js +0 -1015
  87. package/dist/bundle/esm-C49STiCR.js +0 -1248
@@ -27,6 +27,7 @@ import {
27
27
  * @typedef {import("../spec/scale.js").ComplexDomain} ComplexDomain
28
28
  * @typedef {import("../spec/scale.js").ZoomParams} ZoomParams
29
29
  * @typedef {import("../types/encoder.js").VegaScale} VegaScale
30
+ * @typedef {import("../types/scaleResolutionApi.js").ZoomToOptions} ZoomToOptions
30
31
  * @typedef {VegaScale & { props: import("../spec/scale.js").Scale }} ScaleWithProps
31
32
  */
32
33
 
@@ -37,6 +38,9 @@ export default class ScaleInteractionController {
37
38
  /** @type {() => import("../utils/animator.js").default} */
38
39
  #getAnimator;
39
40
 
41
+ /** @type {() => void} */
42
+ #renderImmediately;
43
+
40
44
  /** @type {() => number[]} */
41
45
  #getInitialDomainSnapshot;
42
46
 
@@ -56,6 +60,7 @@ export default class ScaleInteractionController {
56
60
  * @param {object} options
57
61
  * @param {() => ScaleWithProps} options.getScale
58
62
  * @param {() => import("../utils/animator.js").default} options.getAnimator
63
+ * @param {() => void} options.renderImmediately
59
64
  * @param {() => number[]} options.getInitialDomainSnapshot
60
65
  * @param {() => number[]} options.getResetDomain
61
66
  * @param {(domain: ScalarDomain | ComplexDomain) => number[]} options.fromComplexInterval
@@ -64,6 +69,7 @@ export default class ScaleInteractionController {
64
69
  constructor({
65
70
  getScale,
66
71
  getAnimator,
72
+ renderImmediately,
67
73
  getInitialDomainSnapshot,
68
74
  getResetDomain,
69
75
  fromComplexInterval,
@@ -71,6 +77,7 @@ export default class ScaleInteractionController {
71
77
  }) {
72
78
  this.#getScale = getScale;
73
79
  this.#getAnimator = getAnimator;
80
+ this.#renderImmediately = renderImmediately;
74
81
  this.#getInitialDomainSnapshot = getInitialDomainSnapshot;
75
82
  this.#getResetDomain = getResetDomain;
76
83
  this.#fromComplexInterval = fromComplexInterval;
@@ -170,13 +177,11 @@ export default class ScaleInteractionController {
170
177
  * Immediately zooms to the given interval.
171
178
  *
172
179
  * @param {NumericDomain | ComplexDomain} domain
173
- * @param {boolean | number} [duration] an approximate duration for transition.
174
- * Zero duration zooms immediately. Boolean `true` indicates a default duration.
180
+ * @param {ZoomToOptions | boolean | number} [options] Zoom options.
181
+ * Passing the duration directly as a boolean or number is deprecated.
175
182
  */
176
- async zoomTo(domain, duration = false) {
177
- if (isBoolean(duration)) {
178
- duration = duration ? 700 : 0;
179
- }
183
+ async zoomTo(domain, options = false) {
184
+ const { duration, renderImmediately } = normalizeZoomToOptions(options);
180
185
 
181
186
  if (!this.isZoomingSupported()) {
182
187
  throw new Error("Not a zoomable scale!");
@@ -195,6 +200,12 @@ export default class ScaleInteractionController {
195
200
  const from = /** @type {number[]} */ (scale.domain());
196
201
 
197
202
  if (duration > 0 && from.length == 2) {
203
+ if (renderImmediately) {
204
+ throw new Error(
205
+ "renderImmediately is not supported for animated zooms."
206
+ );
207
+ }
208
+
198
209
  // Spans
199
210
  const fw = from[1] - from[0];
200
211
  const tw = to[1] - to[0];
@@ -234,7 +245,11 @@ export default class ScaleInteractionController {
234
245
  } else {
235
246
  this.#cancelZoomTransition();
236
247
  scale.domain(to);
237
- animator?.requestRender();
248
+ if (renderImmediately) {
249
+ this.#renderImmediately();
250
+ } else {
251
+ animator?.requestRender();
252
+ }
238
253
  }
239
254
  }
240
255
 
@@ -279,6 +294,39 @@ export default class ScaleInteractionController {
279
294
  }
280
295
  }
281
296
 
297
+ /**
298
+ * @param {ZoomToOptions | boolean | number | undefined} options
299
+ * @returns {{ duration: number, renderImmediately: boolean }}
300
+ */
301
+ function normalizeZoomToOptions(options) {
302
+ if (options === undefined) {
303
+ return {
304
+ duration: 0,
305
+ renderImmediately: false,
306
+ };
307
+ }
308
+
309
+ if (isBoolean(options)) {
310
+ return {
311
+ duration: options ? 700 : 0,
312
+ renderImmediately: false,
313
+ };
314
+ }
315
+
316
+ if (typeof options === "number") {
317
+ return {
318
+ duration: options,
319
+ renderImmediately: false,
320
+ };
321
+ }
322
+
323
+ const duration = options.duration ?? 0;
324
+ return {
325
+ duration: isBoolean(duration) ? (duration ? 700 : 0) : duration,
326
+ renderImmediately: options.renderImmediately === true,
327
+ };
328
+ }
329
+
282
330
  /**
283
331
  * @param {ScaleWithProps} scale
284
332
  * @param {ZoomParams | boolean | undefined} zoom
@@ -8,14 +8,19 @@
8
8
  * @param {Channel} options.channel
9
9
  * @param {import("../spec/channel.js").Type} options.dataType
10
10
  * @param {ScaleResolutionMember[]} options.orderedMembers
11
+ * @param {{ view: import("../view/view.js").default, config: Scale } | undefined} [options.viewLevelScaleConfig]
11
12
  * @param {boolean} options.isExplicitDomain
12
13
  * @param {import("../spec/config.js").GenomeSpyConfig[]} options.configScopes
13
14
  * @returns {Scale}
14
15
  */
15
- export function resolveScalePropsBase({ channel, dataType, orderedMembers, isExplicitDomain, configScopes, }: {
16
+ export function resolveScalePropsBase({ channel, dataType, orderedMembers, viewLevelScaleConfig, isExplicitDomain, configScopes, }: {
16
17
  channel: Channel;
17
18
  dataType: import("../spec/channel.js").Type;
18
19
  orderedMembers: ScaleResolutionMember[];
20
+ viewLevelScaleConfig?: {
21
+ view: import("../view/view.js").default;
22
+ config: Scale;
23
+ } | undefined;
19
24
  isExplicitDomain: boolean;
20
25
  configScopes: import("../spec/config.js").GenomeSpyConfig[];
21
26
  }): Scale;
@@ -1 +1 @@
1
- {"version":3,"file":"scalePropsResolver.d.ts","sourceRoot":"","sources":["../../../src/scales/scalePropsResolver.js"],"names":[],"mappings":"AAcA;;;;GAIG;AAEH;;;;;;;;GAQG;AACH,8GAPG;IAAyB,OAAO,EAAxB,OAAO;IACoC,QAAQ,EAAnD,OAAO,oBAAoB,EAAE,IAAI;IACA,cAAc,EAA/C,qBAAqB,EAAE;IACN,gBAAgB,EAAjC,OAAO;IACgD,YAAY,EAAnE,OAAO,mBAAmB,EAAE,eAAe,EAAE;CACrD,GAAU,KAAK,CA6IjB;sBAzJY,OAAO,oBAAoB,EAAE,OAAO;oBACpC,OAAO,kBAAkB,EAAE,KAAK;oCAChC,OAAO,sBAAsB,EAAE,qBAAqB"}
1
+ {"version":3,"file":"scalePropsResolver.d.ts","sourceRoot":"","sources":["../../../src/scales/scalePropsResolver.js"],"names":[],"mappings":"AAmBA;;;;GAIG;AAEH;;;;;;;;;GASG;AACH,oIARG;IAAyB,OAAO,EAAxB,OAAO;IACoC,QAAQ,EAAnD,OAAO,oBAAoB,EAAE,IAAI;IACA,cAAc,EAA/C,qBAAqB,EAAE;IAC0D,oBAAoB,GAArG;QAAE,IAAI,EAAE,OAAO,iBAAiB,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,KAAK,CAAA;KAAE,GAAG,SAAS;IACrD,gBAAgB,EAAjC,OAAO;IACgD,YAAY,EAAnE,OAAO,mBAAmB,EAAE,eAAe,EAAE;CACrD,GAAU,KAAK,CAgKjB;sBA7KY,OAAO,oBAAoB,EAAE,OAAO;oBACpC,OAAO,kBAAkB,EAAE,KAAK;oCAChC,OAAO,sBAAsB,EAAE,qBAAqB"}
@@ -9,7 +9,12 @@ import {
9
9
  getConfiguredNamedRange,
10
10
  isConfigRangeName,
11
11
  } from "../config/scaleConfig.js";
12
- import { applyLockedProperties, getDefaultScaleType } from "./scaleRules.js";
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 = memberList
48
- .map((member) => member.channelDef.scale)
49
- .filter((props) => props !== undefined);
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 = memberList.some((member) =>
136
- isExprRef(member.channelDef.scale?.domain)
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 = memberList.find(
147
- (member) => member.channelDef.scale?.range !== undefined
148
- )?.view;
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
@@ -185,10 +201,10 @@ export default class ScaleResolution implements ScaleResolutionApi {
185
201
  * Immediately zooms to the given interval.
186
202
  *
187
203
  * @param {NumericDomain | ComplexDomain} domain
188
- * @param {boolean | number} [duration] an approximate duration for transition.
189
- * Zero duration zooms immediately. Boolean `true` indicates a default duration.
204
+ * @param {import("../types/scaleResolutionApi.js").ZoomToOptions | boolean | number} [options]
205
+ * Zoom options. Passing the duration directly as a boolean or number is deprecated.
190
206
  */
191
- zoomTo(domain: import("../spec/scale.js").NumericDomain | import("../spec/scale.js").ComplexDomain, duration?: boolean | number): Promise<void>;
207
+ zoomTo(domain: import("../spec/scale.js").NumericDomain | import("../spec/scale.js").ComplexDomain, options?: import("../types/scaleResolutionApi.js").ZoomToOptions | boolean | number): Promise<void>;
192
208
  /**
193
209
  * Resets the current domain to the initial one
194
210
  *
@@ -1 +1 @@
1
- {"version":3,"file":"scaleResolution.d.ts","sourceRoot":"","sources":["../../../src/scales/scaleResolution.js"],"names":[],"mappings":"AAoDA;;;;;;;;GAQG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AACH;IA6cI;;;;;;;;OAQG;IACH,uBALa,CAAC,eACH,QAAQ,CAAC,eAAe,CAAC,YACzB,MAAM,CAAC,GACL,CAAC,CAqBb;IAlaD;;;OAGG;IACH,sEAFW,OAAO,iBAAiB,EAAE,OAAO,EAsC3C;IAnCG,8CAAsB;IACtB,0FAA0F;IAC1F,MADW,OAAO,oBAAoB,EAAE,IAAI,CAC5B;IAEhB,iEAAiE;IACjE,MADW,MAAM,CACI;IA0EzB,2BASC;IAwBD;;;;;;;OAOG;IACH,4KAEC;IAED;;;OAGG;IACH,+KAEC;IAiCD,sCA6CC;IA+MD;;;OAGG;IACH,uBAHW,qBAAqB,GACnB,MAAM,OAAO,CAazB;IAED,gBAMC;IAoFD;;;;OAIG;IACH,0CAJW,OAAO,sBAAsB,EAAE,OAAO,aACtC,QAAQ,CAAC,OAAO,qBAAqB,EAAE,aAAa,CAAC,GACnD,MAAM,IAAI,CAkCtB;IAED;;OAEG;IACH,qCAEC;IAED,+BAiBC;IAuED;;;;;;;;;;;OAWG;IACH,0BALa;QACR,QAAQ,EAAE,OAAO,kBAAkB,EAAE,KAAK,CAAC,UAAU,CAAC,GAAG,SAAS,CAAC;QACnE,oBAAoB,EAAE,OAAO,CAAA;KAC9B,CAeH;IAED;;;;;;;OAOG;IACH,wBAFa,OAAO,kBAAkB,EAAE,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,CAShE;IAgGD;;;;;OAKG;IACH,oBAYC;IAED;;;;;OAKG;IACH,0BA0BC;IAoID;;OAEG;IACH;eAlhCkC,OAAO,kBAAkB,EAAE,KAAK;MAyhCjE;IAED;;;;;;OAMG;IACH;eAliCkC,OAAO,kBAAkB,EAAE,KAAK;MAyiCjE;IAED;;;;OAIG;IACH;eAhjCkC,OAAO,kBAAkB,EAAE,KAAK;MAyjCjE;IAED,mBASC;IAED;;;;OAIG;IACH,+DAEC;IAED;;OAEG;IACH,oBAFa,mFAA6B,CASzC;IAED;;OAEG;IACH;;;;MAqBC;IAED;;;;OAIG;IACH,oBAEC;IAED;;OAEG;IACH,sBASC;IAED;;;;;;;OAOG;IACH,kBALW,MAAM,eACN,MAAM,OACN,MAAM,GACJ,OAAO,CAInB;IAED;;;;;;OAMG;IACH,eAJW,mFAA6B,aAC7B,OAAO,GAAG,MAAM,iBAK1B;IAED;;;;OAIG;IACH,qBAEC;IAED;;;;;OAKG;IACH,uBAEC;IAED;;;;;;;OAOG;IACH,wBAoBC;IAED;;;;;OAKG;IACH,uBAFW,MAAM,yDAUhB;IAED;;OAEG;IACH,iBAFW,MAAM,yDAIhB;IAED;;;OAGG;IACH,qBAHW,MAAM,+CAAmB,GACvB,MAAM,CAIlB;IAED;;;OAGG;IACH,8BAHW,kFAA4B,GAC1B,MAAM,EAAE,CAOpB;;CACJ;kCAvyC+B,CAAC,SAApB,6CAAkB;;;;UAGrB,OAAO,qBAAqB,EAAE,OAAO;aACrC,CAAC;gBACD,OAAO,oBAAoB,EAAE,mBAAmB;yBAChD,OAAO;;sBApCV,+BAA+B;sBAA/B,+BAA+B;wBAA/B,+BAA+B;wBAA/B,+BAA+B;6BAA/B,+BAA+B"}
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
- isPrimaryPositionalChannel,
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),
@@ -183,6 +188,7 @@ export default class ScaleResolution {
183
188
  this.#interactionController = new ScaleInteractionController({
184
189
  getScale: () => this.getScale(),
185
190
  getAnimator: () => this.#viewContext.animator,
191
+ renderImmediately: () => this.#viewContext.renderImmediately(),
186
192
  getInitialDomainSnapshot: () =>
187
193
  this.#domainAggregator.initialDomainSnapshot,
188
194
  getResetDomain: () => this.#getConfiguredOrDefaultDomain(),
@@ -399,6 +405,10 @@ export default class ScaleResolution {
399
405
  * @returns {boolean}
400
406
  */
401
407
  #hasConfiguredDomain() {
408
+ if (this.#viewLevelScaleConfig?.config.domain !== undefined) {
409
+ return true;
410
+ }
411
+
402
412
  for (const member of this.#members) {
403
413
  if (
404
414
  member.contributesToDomain &&
@@ -451,6 +461,8 @@ export default class ScaleResolution {
451
461
  const member = normalizeMember(newMember);
452
462
  const { channel, channelDef } = member;
453
463
 
464
+ this.#assertCanRegisterMember(member);
465
+
454
466
  // A convenience hack for cases where the new member should adapt
455
467
  // the scale type to the existing one. For example: SelectionRect
456
468
  // TODO: Add test
@@ -480,15 +492,12 @@ export default class ScaleResolution {
480
492
  explicitScaleType ??
481
493
  (type === INDEX || type === LOCUS ? type : undefined);
482
494
 
483
- if (
484
- effectiveScaleType &&
485
- [INDEX, LOCUS].includes(effectiveScaleType) &&
486
- !isPrimaryPositionalChannel(this.channel)
487
- ) {
488
- throw new Error(
489
- `Index and locus scales are only supported on positional channels (x/y). Channel "${this.channel}" resolves to scale type "${effectiveScaleType}".`
490
- );
491
- }
495
+ validateScaleTypeCompatibility(
496
+ this.channel,
497
+ type,
498
+ effectiveScaleType,
499
+ `encoding.${channel}.scale.type`
500
+ );
492
501
 
493
502
  if (name) {
494
503
  if (this.name !== undefined && name != this.name) {
@@ -586,6 +595,100 @@ export default class ScaleResolution {
586
595
  };
587
596
  }
588
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
+
589
692
  dispose() {
590
693
  this.#clearSelectionDomainParamSubscriptions();
591
694
  this.#clearConfiguredDomainExprSubscriptions();
@@ -657,11 +760,28 @@ export default class ScaleResolution {
657
760
  continue;
658
761
  }
659
762
  const domain = member.channelDef.scale?.domain;
660
- if (!isExprRef(domain)) {
763
+ const exprRefs = collectConfiguredDomainExprRefs(domain);
764
+ if (exprRefs.length === 0) {
661
765
  continue;
662
766
  }
663
767
 
664
- const expr = member.view.paramRuntime.createExpression(domain.expr);
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
+ );
665
785
  const unsubscribe = expr.subscribe(listener);
666
786
  this.#configuredDomainExprUnsubscribers.push(unsubscribe);
667
787
  }
@@ -753,6 +873,7 @@ export default class ScaleResolution {
753
873
  channel: this.channel,
754
874
  dataType: this.type,
755
875
  orderedMembers: this.#getOrderedMembers(),
876
+ viewLevelScaleConfig: this.#viewLevelScaleConfig,
756
877
  isExplicitDomain: this.isDomainDefinedExplicitly(),
757
878
  configScopes: this.#resolutionView.getConfigScopes(),
758
879
  });
@@ -1273,11 +1394,11 @@ export default class ScaleResolution {
1273
1394
  * Immediately zooms to the given interval.
1274
1395
  *
1275
1396
  * @param {NumericDomain | ComplexDomain} domain
1276
- * @param {boolean | number} [duration] an approximate duration for transition.
1277
- * Zero duration zooms immediately. Boolean `true` indicates a default duration.
1397
+ * @param {import("../types/scaleResolutionApi.js").ZoomToOptions | boolean | number} [options]
1398
+ * Zoom options. Passing the duration directly as a boolean or number is deprecated.
1278
1399
  */
1279
- async zoomTo(domain, duration = false) {
1280
- return this.#interactionController.zoomTo(domain, duration);
1400
+ async zoomTo(domain, options = false) {
1401
+ return this.#interactionController.zoomTo(domain, options);
1281
1402
  }
1282
1403
 
1283
1404
  /**
@@ -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,CA2EhD;AAED;;;GAGG;AACH,6CAHW,OAAO,kBAAkB,EAAE,KAAK,WAChC,OAAO,QAgBjB;sBArGY,OAAO,oBAAoB,EAAE,OAAO"}
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"}