@genome-spy/core 0.43.2 → 0.44.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 (65) hide show
  1. package/dist/bundle/index.es.js +5621 -5346
  2. package/dist/bundle/index.js +156 -104
  3. package/dist/schema.json +412 -43
  4. package/dist/src/data/sources/lazy/axisTickSource.d.ts +1 -1
  5. package/dist/src/data/sources/lazy/axisTickSource.d.ts.map +1 -1
  6. package/dist/src/data/sources/lazy/axisTickSource.js +2 -2
  7. package/dist/src/data/sources/lazy/bigBedSource.js +70 -28
  8. package/dist/src/data/sources/lazy/bigWigSource.d.ts +6 -0
  9. package/dist/src/data/sources/lazy/bigWigSource.d.ts.map +1 -1
  10. package/dist/src/data/sources/lazy/bigWigSource.js +3 -5
  11. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts +1 -1
  12. package/dist/src/data/sources/lazy/singleAxisLazySource.d.ts.map +1 -1
  13. package/dist/src/data/sources/lazy/singleAxisLazySource.js +1 -1
  14. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts +7 -12
  15. package/dist/src/data/sources/lazy/singleAxisWindowedSource.d.ts.map +1 -1
  16. package/dist/src/data/sources/lazy/singleAxisWindowedSource.js +33 -29
  17. package/dist/src/data/transforms/filterScoredLabels.js +1 -1
  18. package/dist/src/encoder/encoder.d.ts.map +1 -1
  19. package/dist/src/encoder/encoder.js +16 -6
  20. package/dist/src/genomeSpy.d.ts +1 -0
  21. package/dist/src/genomeSpy.d.ts.map +1 -1
  22. package/dist/src/genomeSpy.js +108 -6
  23. package/dist/src/gl/glslScaleGenerator.d.ts +23 -3
  24. package/dist/src/gl/glslScaleGenerator.d.ts.map +1 -1
  25. package/dist/src/gl/glslScaleGenerator.js +137 -42
  26. package/dist/src/gl/webGLHelper.d.ts.map +1 -1
  27. package/dist/src/gl/webGLHelper.js +5 -7
  28. package/dist/src/marks/link.common.glsl.js +2 -0
  29. package/dist/src/marks/link.d.ts.map +1 -1
  30. package/dist/src/marks/link.js +19 -9
  31. package/dist/src/marks/link.vertex.glsl.js +1 -1
  32. package/dist/src/marks/mark.d.ts +19 -17
  33. package/dist/src/marks/mark.d.ts.map +1 -1
  34. package/dist/src/marks/mark.js +181 -120
  35. package/dist/src/marks/point.common.glsl.js +1 -1
  36. package/dist/src/marks/rect.common.glsl.js +2 -0
  37. package/dist/src/marks/rect.d.ts.map +1 -1
  38. package/dist/src/marks/rect.js +12 -12
  39. package/dist/src/marks/rect.vertex.glsl.js +1 -1
  40. package/dist/src/marks/rule.common.glsl.js +1 -1
  41. package/dist/src/marks/rule.js +2 -2
  42. package/dist/src/marks/text.common.glsl.js +1 -1
  43. package/dist/src/marks/text.js +2 -2
  44. package/dist/src/paramBroker.d.ts +19 -3
  45. package/dist/src/paramBroker.d.ts.map +1 -1
  46. package/dist/src/paramBroker.js +18 -2
  47. package/dist/src/spec/channel.d.ts +4 -3
  48. package/dist/src/spec/mark.d.ts +17 -25
  49. package/dist/src/spec/parameter.d.ts +123 -0
  50. package/dist/src/spec/root.d.ts +9 -0
  51. package/dist/src/spec/scale.d.ts +2 -1
  52. package/dist/src/spec/view.d.ts +1 -1
  53. package/dist/src/types/scaleResolutionApi.d.ts +7 -3
  54. package/dist/src/utils/expression.d.ts +2 -2
  55. package/dist/src/utils/expression.d.ts.map +1 -1
  56. package/dist/src/utils/expression.js +3 -3
  57. package/dist/src/view/axisView.js +3 -3
  58. package/dist/src/view/scaleResolution.d.ts +8 -18
  59. package/dist/src/view/scaleResolution.d.ts.map +1 -1
  60. package/dist/src/view/scaleResolution.js +220 -126
  61. package/dist/src/view/scaleResolution.test.js +7 -7
  62. package/dist/src/view/unitView.d.ts.map +1 -1
  63. package/dist/src/view/unitView.js +10 -3
  64. package/dist/src/view/view.js +2 -2
  65. package/package.json +2 -2
@@ -20,7 +20,6 @@ import { scale as vegaScale, isDiscrete, isContinuous } from "vega-scale";
20
20
  import mergeObjects from "../utils/mergeObjects.js";
21
21
  import createScale, { configureScale } from "../scale/scale.js";
22
22
 
23
- import { invalidate, getCachedOrCall } from "../utils/propertyCacher.js";
24
23
  import {
25
24
  getChannelDefWithScale,
26
25
  isColorChannel,
@@ -37,6 +36,7 @@ import { NominalDomain } from "../utils/domainArray.js";
37
36
  import { easeCubicInOut } from "d3-ease";
38
37
  import { asArray, shallowArrayEquals } from "../utils/arrayUtils.js";
39
38
  import eerp from "../utils/eerp.js";
39
+ import { isExprRef } from "../marks/mark.js";
40
40
 
41
41
  // Register scaleLocus to Vega-Scale.
42
42
  // Loci are discrete but the scale's domain can be adjusted in a continuous manner.
@@ -66,6 +66,7 @@ export const INDEX = "index";
66
66
  export default class ScaleResolution {
67
67
  /**
68
68
  * @typedef {import("../types/scaleResolutionApi.js").ScaleResolutionApi} ScaleResolutionApi
69
+ * @typedef {import("../types/scaleResolutionApi.js").ScaleResolutionEventType} ScaleResolutionEventType
69
70
  * @typedef {import("../spec/channel.js").Channel} Channel
70
71
  * @typedef {import("../spec/channel.js").ChannelWithScale} ChannelWithScale
71
72
  * @typedef {import("../spec/scale.js").NumericDomain} NumericDomain
@@ -77,16 +78,31 @@ export default class ScaleResolution {
77
78
  * @typedef {import("../utils/domainArray.js").DomainArray} DomainArray
78
79
  * @typedef {import("../genome/genome.js").ChromosomalLocus} ChromosomalLocus
79
80
  * @typedef {import("../types/scaleResolutionApi.js").ScaleResolutionListener} ScaleResolutionListener
81
+ *
82
+ * @typedef {VegaScale & { props: import("../spec/scale.js").Scale }} ScaleWithProps
80
83
  */
81
84
 
82
85
  /** @type {number[]} */
83
- #zoomExtent = undefined;
86
+ #zoomExtent;
87
+
88
+ /**
89
+ * @type {Record<ScaleResolutionEventType, Set<ScaleResolutionListener>>}
90
+ */
91
+ #listeners = {
92
+ domain: new Set(),
93
+ range: new Set(),
94
+ };
84
95
 
85
- /** @type {Set<ScaleResolutionListener>} Observers that are called when the scale domain is changed */
86
- #domainListeners = new Set();
96
+ /** @type {ScaleWithProps} */
97
+ #scale;
87
98
 
88
- /** @type {VegaScale} */
89
- #scale = undefined;
99
+ /**
100
+ * Keeps track of the expression references in the range. If range is modified,
101
+ * new expressions are created and the old ones must be invalidated.
102
+ *
103
+ * @type {Set<import("../paramBroker.js").ExprRefFunction>}
104
+ */
105
+ #rangeExprRefListeners = new Set();
90
106
 
91
107
  /**
92
108
  * @param {Channel} channel
@@ -102,36 +118,37 @@ export default class ScaleResolution {
102
118
  this.name = undefined;
103
119
  }
104
120
 
121
+ get #viewContext() {
122
+ return this.members[0].view.context;
123
+ }
124
+
105
125
  /**
106
126
  * Adds a listener that is called when the scale domain is changed,
107
127
  * e.g., zoomed. The call is synchronous and happens before the views
108
128
  * are rendered.
109
129
  *
110
- * @param {"domain"} type
130
+ * @param {ScaleResolutionEventType} type
111
131
  * @param {ScaleResolutionListener} listener function
112
132
  */
113
133
  addEventListener(type, listener) {
114
- if (type != "domain") {
115
- throw new Error("Unsupported event type: " + type);
116
- }
117
- this.#domainListeners.add(listener);
134
+ this.#listeners[type].add(listener);
118
135
  }
119
136
 
120
137
  /**
121
- * @param {"domain"} type
138
+ * @param {ScaleResolutionEventType} type
122
139
  * @param {ScaleResolutionListener} listener function
123
140
  */
124
141
  removeEventListener(type, listener) {
125
- if (type != "domain") {
126
- throw new Error("Unsupported event type: " + type);
127
- }
128
- this.#domainListeners.delete(listener);
142
+ this.#listeners[type].delete(listener);
129
143
  }
130
144
 
131
- #notifyDomainListeners() {
132
- for (const listener of this.#domainListeners.values()) {
145
+ /**
146
+ * @param {ScaleResolutionEventType} type
147
+ */
148
+ #notifyListeners(type) {
149
+ for (const listener of this.#listeners[type].values()) {
133
150
  listener({
134
- type: "domain",
151
+ type,
135
152
  scaleResolution: this,
136
153
  });
137
154
  }
@@ -175,11 +192,11 @@ export default class ScaleResolution {
175
192
  /**
176
193
  * Returns true if the domain has been defined explicitly, i.e. not extracted from the data.
177
194
  */
178
- isExplicitDomain() {
195
+ #isExplicitDomain() {
179
196
  return !!this.getConfiguredDomain();
180
197
  }
181
198
 
182
- isDomainInitialized() {
199
+ #isDomainInitialized() {
183
200
  const s = this.#scale;
184
201
  if (!s) {
185
202
  return false;
@@ -205,18 +222,15 @@ export default class ScaleResolution {
205
222
  * @returns {import("../spec/scale.js").Scale}
206
223
  */
207
224
  #getMergedScaleProps() {
208
- return getCachedOrCall(this, "mergedScaleProps", () => {
209
- const propArray = this.members
210
- .map(
211
- (member) =>
212
- getChannelDefWithScale(member.view, member.channel)
213
- .scale
214
- )
215
- .filter((props) => props !== undefined);
225
+ const propArray = this.members
226
+ .map(
227
+ (member) =>
228
+ getChannelDefWithScale(member.view, member.channel).scale
229
+ )
230
+ .filter((props) => props !== undefined);
216
231
 
217
- // TODO: Disabled scale: https://vega.github.io/vega-lite/docs/scale.html#disable
218
- return mergeObjects(propArray, "scale", ["domain"]);
219
- });
232
+ // TODO: Disabled scale: https://vega.github.io/vega-lite/docs/scale.html#disable
233
+ return mergeObjects(propArray, "scale", ["domain"]);
220
234
  }
221
235
 
222
236
  /**
@@ -225,67 +239,120 @@ export default class ScaleResolution {
225
239
  *
226
240
  * @returns {import("../spec/scale.js").Scale}
227
241
  */
228
- getScaleProps() {
229
- // eslint-disable-next-line complexity
230
- return getCachedOrCall(this, "scaleProps", () => {
231
- const mergedProps = this.#getMergedScaleProps();
232
- if (mergedProps === null || mergedProps.type == "null") {
233
- // No scale (pass-thru)
234
- // TODO: Check that the channel is compatible
235
- return { type: "null" };
236
- }
242
+ #getScaleProps() {
243
+ const mergedProps = this.#getMergedScaleProps();
244
+ if (mergedProps === null || mergedProps.type == "null") {
245
+ // No scale (pass-thru)
246
+ // TODO: Check that the channel is compatible
247
+ return { type: "null" };
248
+ }
237
249
 
238
- const props = {
239
- ...this.#getDefaultScaleProperties(this.type),
240
- ...mergedProps,
241
- };
250
+ const props = {
251
+ ...this.#getDefaultScaleProperties(this.type),
252
+ ...mergedProps,
253
+ };
242
254
 
243
- if (!props.type) {
244
- props.type = getDefaultScaleType(this.channel, this.type);
245
- }
255
+ if (!props.type) {
256
+ props.type = getDefaultScaleType(this.channel, this.type);
257
+ }
246
258
 
247
- const domain = this.#getInitialDomain();
259
+ const domain = this.#getInitialDomain();
248
260
 
249
- if (domain && domain.length > 0) {
250
- props.domain = domain;
251
- } else if (isDiscrete(props.type)) {
252
- props.domain = new NominalDomain();
253
- }
261
+ if (domain && domain.length > 0) {
262
+ props.domain = domain;
263
+ } else if (isDiscrete(props.type)) {
264
+ props.domain = new NominalDomain();
265
+ }
254
266
 
255
- if (!props.domain && props.domainMid !== undefined) {
256
- // Initialize with a bogus domain so that scale.js can inject the domainMid.
257
- // The number of domain elements must be know before the glsl scale is generated.
258
- props.domain = [props.domainMin ?? 0, props.domainMax ?? 1];
259
- }
267
+ if (!props.domain && props.domainMid !== undefined) {
268
+ // Initialize with a bogus domain so that scale.js can inject the domainMid.
269
+ // The number of domain elements must be know before the glsl scale is generated.
270
+ props.domain = [props.domainMin ?? 0, props.domainMax ?? 1];
271
+ }
260
272
 
261
- // Reverse discrete y axis
262
- if (
263
- this.channel == "y" &&
264
- isDiscrete(props.type) &&
265
- props.reverse == undefined
266
- ) {
267
- props.reverse = true;
268
- }
273
+ // Reverse discrete y axis
274
+ if (
275
+ this.channel == "y" &&
276
+ isDiscrete(props.type) &&
277
+ props.reverse == undefined
278
+ ) {
279
+ props.reverse = true;
280
+ }
269
281
 
270
- if (props.range && props.scheme) {
271
- delete props.scheme;
272
- // TODO: Props should be set more intelligently
273
- /*
282
+ if (props.range && props.scheme) {
283
+ delete props.scheme;
284
+ // TODO: Props should be set more intelligently
285
+ /*
274
286
  throw new Error(
275
287
  `Scale has both "range" and "scheme" defined! Views: ${this._getViewPaths()}`
276
288
  );
277
289
  */
278
- }
290
+ }
279
291
 
280
- // By default, index and locus scales are zoomable, others are not
281
- if (!("zoom" in props) && ["index", "locus"].includes(props.type)) {
282
- props.zoom = true;
283
- }
292
+ // By default, index and locus scales are zoomable, others are not
293
+ if (!("zoom" in props) && ["index", "locus"].includes(props.type)) {
294
+ props.zoom = true;
295
+ }
284
296
 
285
- applyLockedProperties(props, this.channel);
297
+ applyLockedProperties(props, this.channel);
286
298
 
287
- return props;
288
- });
299
+ return props;
300
+ }
301
+
302
+ /**
303
+ * Configures range. If range is an array of expressions, they are evaluated
304
+ * and the scale is updated when the expressions change.
305
+ */
306
+ #configureRange() {
307
+ const props = this.#scale.props;
308
+ const range = props.range;
309
+ this.#rangeExprRefListeners.forEach((fn) => fn.invalidate());
310
+
311
+ if (!range || !isArray(range)) {
312
+ // Named ranges?
313
+ return;
314
+ }
315
+
316
+ /**
317
+ * @param {T} array
318
+ * @param {boolean} reverse
319
+ * @returns {T}
320
+ * @template T
321
+ */
322
+ const flip = (array, reverse) =>
323
+ // @ts-ignore TODO: Fix the type (should be a generic union array type)
324
+ reverse ? array.slice().reverse() : array;
325
+
326
+ if (range.some(isExprRef)) {
327
+ /** @type {(() => void)[]} */
328
+ let expressions;
329
+
330
+ const evaluateAndSet = () => {
331
+ this.#scale.range(
332
+ flip(
333
+ expressions.map((expr) => expr()),
334
+ props.reverse
335
+ )
336
+ );
337
+ };
338
+
339
+ expressions = range.map((elem) => {
340
+ if (isExprRef(elem)) {
341
+ const fn = this.#viewContext.paramBroker.createExpression(
342
+ elem.expr
343
+ );
344
+ fn.addListener(evaluateAndSet);
345
+ this.#rangeExprRefListeners.add(fn);
346
+ return () => fn(null);
347
+ } else {
348
+ return () => elem;
349
+ }
350
+ });
351
+
352
+ evaluateAndSet();
353
+ } else {
354
+ this.#scale.range(flip(range, props.reverse));
355
+ }
289
356
  }
290
357
 
291
358
  #getInitialDomain() {
@@ -330,53 +397,64 @@ export default class ScaleResolution {
330
397
  * Reconfigures the scale: updates domain and other settings
331
398
  */
332
399
  reconfigure() {
333
- if (this.#scale && this.#scale.type != "null") {
334
- const domainWasInitialized = this.isDomainInitialized();
400
+ const scale = this.#scale;
335
401
 
336
- const previousDomain = this.#scale.domain();
402
+ if (!scale || scale.type == "null") {
403
+ return;
404
+ }
337
405
 
338
- invalidate(this, "scaleProps");
339
- const props = this.getScaleProps();
340
- configureScale(props, this.#scale);
341
- if (isContinuous(this.#scale.type)) {
342
- this.#zoomExtent = this.#getZoomExtent();
343
- }
406
+ const domainWasInitialized = this.#isDomainInitialized();
407
+ const previousDomain = scale.domain();
344
408
 
345
- if (!domainWasInitialized) {
346
- this.#notifyDomainListeners();
347
- return;
348
- }
409
+ const props = this.#getScaleProps();
410
+ configureScale({ ...props, range: undefined }, scale);
349
411
 
350
- const newDomain = this.#scale.domain();
351
- if (!shallowArrayEquals(newDomain, previousDomain)) {
352
- if (this.isZoomable()) {
353
- // Don't mess with zoomed views, restore the previous domain
354
- this.#scale.domain(previousDomain);
355
- } else if (this.#isZoomingSupported()) {
356
- // It can be zoomed, so lets make a smooth transition.
357
- // Restore the previous domain and zoom smoothly to the new domain.
358
- this.#scale.domain(previousDomain);
359
- this.zoomTo(newDomain, 500); // TODO: Configurable duration
360
- } else {
361
- // Update immediately if the previous domain was the initial domain [0, 0]
362
- this.#notifyDomainListeners();
363
- }
412
+ // Annotate the scale with the new props
413
+ scale.props = props;
414
+ this.#configureRange();
415
+
416
+ if (isContinuous(scale.type)) {
417
+ this.#zoomExtent = this.#getZoomExtent();
418
+ }
419
+
420
+ if (!domainWasInitialized) {
421
+ this.#notifyListeners("domain");
422
+ return;
423
+ }
424
+
425
+ const newDomain = scale.domain();
426
+ if (!shallowArrayEquals(newDomain, previousDomain)) {
427
+ if (this.isZoomable()) {
428
+ // Don't mess with zoomed views, restore the previous domain
429
+ scale.domain(previousDomain);
430
+ } else if (this.#isZoomingSupported()) {
431
+ // It can be zoomed, so lets make a smooth transition.
432
+ // Restore the previous domain and zoom smoothly to the new domain.
433
+ scale.domain(previousDomain);
434
+ this.zoomTo(newDomain, 500); // TODO: Configurable duration
435
+ } else {
436
+ // Update immediately if the previous domain was the initial domain [0, 0]
437
+ this.#notifyListeners("domain");
364
438
  }
365
439
  }
366
440
  }
367
441
 
368
442
  /**
369
- * @returns {VegaScale}
443
+ * @returns {ScaleWithProps}
370
444
  */
371
- getScale() {
445
+ get scale() {
372
446
  if (this.#scale) {
373
447
  return this.#scale;
374
448
  }
375
449
 
376
- const props = this.getScaleProps();
450
+ const props = this.#getScaleProps();
451
+
452
+ const scale = createScale({ ...props, range: undefined });
453
+ // Annotate the scale with props
454
+ scale.props = props;
377
455
 
378
- const scale = createScale(props);
379
456
  this.#scale = scale;
457
+ this.#configureRange();
380
458
 
381
459
  if (isScaleLocus(scale)) {
382
460
  scale.genome(this.getGenome());
@@ -386,11 +464,27 @@ export default class ScaleResolution {
386
464
  this.#zoomExtent = this.#getZoomExtent();
387
465
  }
388
466
 
467
+ // Hijack the range method
468
+ const range = scale.range;
469
+ if (range) {
470
+ const notify = () => this.#notifyListeners("range");
471
+ scale.range = function (/** @type {any} */ _) {
472
+ if (arguments.length) {
473
+ range(_);
474
+ notify();
475
+ } else {
476
+ return range();
477
+ }
478
+ };
479
+ // The initial setting
480
+ notify();
481
+ }
482
+
389
483
  return scale;
390
484
  }
391
485
 
392
486
  getDomain() {
393
- return this.getScale().domain();
487
+ return this.scale.domain();
394
488
  }
395
489
 
396
490
  /**
@@ -420,14 +514,14 @@ export default class ScaleResolution {
420
514
  */
421
515
  isZoomable() {
422
516
  // Check explicit configuration
423
- return this.#isZoomingSupported() && !!this.getScaleProps().zoom;
517
+ return this.#isZoomingSupported() && !!this.scale.props.zoom;
424
518
  }
425
519
 
426
520
  /**
427
521
  * Returns true if zooming is supported but not necessarily allowed in view spec.
428
522
  */
429
523
  #isZoomingSupported() {
430
- const type = this.getScale().type;
524
+ const type = this.scale.type;
431
525
  return isContinuous(type);
432
526
  }
433
527
 
@@ -444,7 +538,7 @@ export default class ScaleResolution {
444
538
  return false;
445
539
  }
446
540
 
447
- const scale = this.getScale();
541
+ const scale = this.scale;
448
542
  const oldDomain = scale.domain();
449
543
  let newDomain = [...oldDomain];
450
544
 
@@ -452,7 +546,7 @@ export default class ScaleResolution {
452
546
  // @ts-ignore
453
547
  let anchor = scale.invert(scaleAnchor);
454
548
 
455
- if (this.getScaleProps().reverse) {
549
+ if (scale.props.reverse) {
456
550
  pan = -pan;
457
551
  }
458
552
 
@@ -504,7 +598,7 @@ export default class ScaleResolution {
504
598
 
505
599
  if ([0, 1].some((i) => newDomain[i] != oldDomain[i])) {
506
600
  scale.domain(newDomain);
507
- this.#notifyDomainListeners();
601
+ this.#notifyListeners("domain");
508
602
  return true;
509
603
  }
510
604
 
@@ -533,7 +627,7 @@ export default class ScaleResolution {
533
627
 
534
628
  const animator = this.members[0]?.view.context.animator;
535
629
 
536
- const scale = this.getScale();
630
+ const scale = this.scale;
537
631
  const from = /** @type {number[]} */ (scale.domain());
538
632
 
539
633
  if (duration > 0 && from.length == 2) {
@@ -552,16 +646,16 @@ export default class ScaleResolution {
552
646
  const wt = (fw - w) / (fw - tw);
553
647
  const c = wt * tc + (1 - wt) * fc;
554
648
  scale.domain([c - w / 2, c + w / 2]);
555
- this.#notifyDomainListeners();
649
+ this.#notifyListeners("domain");
556
650
  },
557
651
  });
558
652
 
559
653
  scale.domain(to);
560
- this.#notifyDomainListeners();
654
+ this.#notifyListeners("domain");
561
655
  } else {
562
656
  scale.domain(to);
563
657
  animator?.requestRender();
564
- this.#notifyDomainListeners();
658
+ this.#notifyListeners("domain");
565
659
  }
566
660
  }
567
661
 
@@ -580,7 +674,7 @@ export default class ScaleResolution {
580
674
 
581
675
  if ([0, 1].some((i) => newDomain[i] != oldDomain[i])) {
582
676
  this.#scale.domain(newDomain);
583
- this.#notifyDomainListeners();
677
+ this.#notifyListeners("domain");
584
678
  return true;
585
679
  }
586
680
  return false;
@@ -595,14 +689,14 @@ export default class ScaleResolution {
595
689
  getZoomLevel() {
596
690
  // Zoom level makes sense only for user-zoomable scales where zoom extent is defined
597
691
  if (this.isZoomable()) {
598
- return span(this.#zoomExtent) / span(this.getScale().domain());
692
+ return span(this.#zoomExtent) / span(this.scale.domain());
599
693
  }
600
694
 
601
695
  return 1.0;
602
696
  }
603
697
 
604
698
  #getZoomExtent() {
605
- const props = this.getScaleProps();
699
+ const props = this.scale.props;
606
700
  const zoom = props.zoom;
607
701
 
608
702
  if (isZoomParams(zoom)) {
@@ -632,12 +726,12 @@ export default class ScaleResolution {
632
726
  const channel = this.channel;
633
727
  const props = {};
634
728
 
635
- if (this.isExplicitDomain()) {
729
+ if (this.#isExplicitDomain()) {
636
730
  props.zero = false;
637
731
  }
638
732
 
639
733
  if (isPositionalChannel(channel)) {
640
- props.nice = !this.isExplicitDomain();
734
+ props.nice = !this.#isExplicitDomain();
641
735
  } else if (isColorChannel(channel)) {
642
736
  // TODO: Named ranges
643
737
  props.scheme =
@@ -687,7 +781,7 @@ export default class ScaleResolution {
687
781
  * @param {number} value
688
782
  */
689
783
  invertToComplex(value) {
690
- const scale = this.getScale();
784
+ const scale = this.scale;
691
785
  if ("invert" in scale) {
692
786
  const inverted = /** @type {number} */ (scale.invert(value));
693
787
  return this.toComplex(inverted);
@@ -335,7 +335,7 @@ describe("Domain handling", () => {
335
335
  );
336
336
 
337
337
  /** @param {import("./view.js").default} view */
338
- const d = (view) => view.getScaleResolution("y").getScale().domain();
338
+ const d = (view) => view.getScaleResolution("y").scale.domain();
339
339
 
340
340
  expect(r(d(view))).toEqual([1, 5]);
341
341
  expect(r(d(view.children[0]))).toEqual([1, 5]);
@@ -371,7 +371,7 @@ describe("Domain handling", () => {
371
371
  );
372
372
 
373
373
  /** @param {import("./view.js").default} view */
374
- const d = (view) => view.getScaleResolution("y").getScale().domain();
374
+ const d = (view) => view.getScaleResolution("y").scale.domain();
375
375
 
376
376
  expect(r(d(view))).toEqual([1, 5]);
377
377
  expect(r(d(view.children[0]))).toEqual([1, 5]);
@@ -403,7 +403,7 @@ describe("Domain handling", () => {
403
403
  );
404
404
 
405
405
  /** @param {import("./view.js").default} view */
406
- const d = (view) => view.getScaleResolution("y").getScale().domain();
406
+ const d = (view) => view.getScaleResolution("y").scale.domain();
407
407
 
408
408
  // FAILS!!!!!!! TODO: FIX!!
409
409
  // expect(r(d(view))).toEqual([1, 5]);
@@ -442,7 +442,7 @@ describe("Domain handling", () => {
442
442
  );
443
443
 
444
444
  /** @param {import("./view.js").default} view */
445
- const d = (view) => view.getScaleResolution("y").getScale().domain();
445
+ const d = (view) => view.getScaleResolution("y").scale.domain();
446
446
 
447
447
  expect(r(d(view))).toEqual([1, 5]);
448
448
  expect(r(d(view.children[0]))).toEqual([1, 5]);
@@ -469,7 +469,7 @@ describe("Domain handling", () => {
469
469
 
470
470
  const d = /** @param {import("../spec/channel.js").Channel} channel*/ (
471
471
  channel
472
- ) => view.getScaleResolution(channel).getScale().domain();
472
+ ) => view.getScaleResolution(channel).scale.domain();
473
473
 
474
474
  expect(d("x")).toEqual([0, 3]);
475
475
  expect(d("y")).toEqual([0, 3]);
@@ -497,7 +497,7 @@ describe("Domain handling", () => {
497
497
  );
498
498
 
499
499
  const d = /** @param {Channel} channel*/ (channel) =>
500
- view.getScaleResolution(channel).getScale().domain();
500
+ view.getScaleResolution(channel).scale.domain();
501
501
 
502
502
  expect(d("x")).toEqual([1, 4]);
503
503
  expect(d("x")).toEqual([1, 4]);
@@ -530,7 +530,7 @@ describe("Domain handling", () => {
530
530
  }
531
531
 
532
532
  const d = /** @param {Channel} channel*/ (channel) =>
533
- view.getScaleResolution(channel).getScale().domain();
533
+ view.getScaleResolution(channel).scale.domain();
534
534
 
535
535
  expect(d("x")).toEqual([2, 3]);
536
536
  expect(d("y")).toEqual([2, 3]);
@@ -1 +1 @@
1
- {"version":3,"file":"unitView.d.ts","sourceRoot":"","sources":["../../../src/view/unitView.js"],"names":[],"mappings":"AAqBA;;;;GAIG;AACH;QAHkB,MAAM,GAAE,cAAc,kBAAkB,EAAE,OAAO;EASjE;AAEF;IACI;;;;;;;OAOG;IACH;;;;;;;;OAQG;IACH,kBAPW,OAAO,iBAAiB,EAAE,QAAQ,WAClC,OAAO,yBAAyB,EAAE,OAAO,gBACzC,OAAO,oBAAoB,EAAE,OAAO,cACpC,OAAO,WAAW,EAAE,OAAO,QAC3B,MAAM,YACN,OAAO,WAAW,EAAE,WAAW,EAkBzC;IAbG,yCAAgB;IAIZ,iDAAiD;IACjD,MADW,OAAO,kBAAkB,EAAE,OAAO,CACnB;IAUlC;;;;OAIG;IACH,gBAJW,OAAO,4CAA4C,EAAE,OAAO,UAC5D,OAAO,uBAAuB,EAAE,OAAO,YACvC,OAAO,uBAAuB,EAAE,gBAAgB,QAY1D;IAED,kDAIC;IAED;;;;;OAKG;IACH,iEAyEC;IAED;;;OAGG;IACH,mGASC;IAkBD;;OAEG;IACH,uDAEC;IAED;;OAEG;IACH,6/CAcC;IAED;;;;;OAKG;IACH,6BAHW,OAAO,oBAAoB,EAAE,gBAAgB,iDAkBvD;IAED;;;;;;;;;;;;OAYG;IACH,gHA2CC;IAED,uBAQC;IAgBD;;;;OAIG;IACH,8BAJW,MAAM,+DAEJ,OAAO,iBAAiB,EAAE,kBAAkB,CAKxD;CACJ;0BAnVyB,oBAAoB"}
1
+ {"version":3,"file":"unitView.d.ts","sourceRoot":"","sources":["../../../src/view/unitView.js"],"names":[],"mappings":"AAqBA;;;;GAIG;AACH;QAHkB,MAAM,GAAE,cAAc,kBAAkB,EAAE,OAAO;EASjE;AAEF;IACI;;;;;;;OAOG;IACH;;;;;;;;OAQG;IACH,kBAPW,OAAO,iBAAiB,EAAE,QAAQ,WAClC,OAAO,yBAAyB,EAAE,OAAO,gBACzC,OAAO,oBAAoB,EAAE,OAAO,cACpC,OAAO,WAAW,EAAE,OAAO,QAC3B,MAAM,YACN,OAAO,WAAW,EAAE,WAAW,EAkBzC;IAbG,yCAAgB;IAIZ,iDAAiD;IACjD,MADW,OAAO,kBAAkB,EAAE,OAAO,CACnB;IAUlC;;;;OAIG;IACH,gBAJW,OAAO,4CAA4C,EAAE,OAAO,UAC5D,OAAO,uBAAuB,EAAE,OAAO,YACvC,OAAO,uBAAuB,EAAE,gBAAgB,QAY1D;IAED,kDAIC;IAED;;;;;OAKG;IACH,iEAgFC;IAED;;;OAGG;IACH,mGASC;IAkBD;;OAEG;IACH,uDAEC;IAED;;OAEG;IACH,6/CAcC;IAED;;;;;OAKG;IACH,6BAHW,OAAO,oBAAoB,EAAE,gBAAgB,iDAkBvD;IAED;;;;;;;;;;;;OAYG;IACH,gHA2CC;IAED,uBAQC;IAgBD;;;;OAIG;IACH,8BAJW,MAAM,+DAEJ,OAAO,iBAAiB,EAAE,kBAAkB,CAKxD;CACJ;0BA1VyB,oBAAoB"}
@@ -160,9 +160,16 @@ export default class UnitView extends ContainerView {
160
160
  );
161
161
  } else if (type == "scale" && isChannelWithScale(channel)) {
162
162
  if (!view.resolutions[type][targetChannel]) {
163
- view.resolutions[type][targetChannel] = new ScaleResolution(
164
- targetChannel
165
- );
163
+ const resolution = new ScaleResolution(targetChannel);
164
+ view.resolutions[type][targetChannel] = resolution;
165
+
166
+ resolution.addEventListener("range", (event) => {
167
+ // Create if WebGLHelper is available, i.e., if not running in headless mode
168
+ this.context.glHelper?.createRangeTexture(
169
+ event.scaleResolution,
170
+ true
171
+ );
172
+ });
166
173
  }
167
174
  view.resolutions[type][targetChannel].pushUnitView(
168
175
  this,
@@ -217,7 +217,7 @@ export default class View {
217
217
 
218
218
  const scale = this.getScaleResolution(
219
219
  dimension == "width" ? "x" : "y"
220
- )?.getScale();
220
+ )?.scale;
221
221
 
222
222
  if (scale) {
223
223
  // Note: this and all ancestral views need to be refreshed when the domain is changed.
@@ -706,7 +706,7 @@ function createViewOpacityFunction(view) {
706
706
  } else if (isDynamicOpacity(opacityDef)) {
707
707
  /** @type {(channel: import("../spec/channel.js").ChannelWithScale) => any} */
708
708
  const getScale = (channel) => {
709
- const scale = view.getScaleResolution(channel)?.getScale();
709
+ const scale = view.getScaleResolution(channel)?.scale;
710
710
  // Only works on linear scales
711
711
  if (["linear", "index", "locus"].includes(scale?.type)) {
712
712
  return scale;