@genome-spy/core 0.15.0 → 0.16.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 (67) hide show
  1. package/LICENSE +7 -7
  2. package/README.md +16 -0
  3. package/dist/genome-spy-schema.json +816 -624
  4. package/dist/index.js +42 -42
  5. package/dist/style.css +1 -1
  6. package/package.json +3 -2
  7. package/src/data/sources/dataUtils.js +50 -3
  8. package/src/data/sources/dynamicCallbackSource.js +2 -1
  9. package/src/data/sources/dynamicSource.js +2 -1
  10. package/src/data/sources/inlineSource.js +3 -5
  11. package/src/data/sources/namedSource.js +3 -7
  12. package/src/data/sources/urlSource.js +1 -1
  13. package/src/data/transforms/aggregate.js +1 -0
  14. package/src/data/transforms/flattenDelimited.js +6 -0
  15. package/src/data/transforms/regexFold.js +1 -1
  16. package/src/data/transforms/stack.js +3 -3
  17. package/src/embedApi.d.ts +59 -0
  18. package/src/encoder/accessor.js +6 -6
  19. package/src/encoder/encoder.js +47 -22
  20. package/src/genome/scaleIndex.d.ts +38 -0
  21. package/src/genome/scaleIndex.js +18 -52
  22. package/src/genome/scaleLocus.d.ts +11 -0
  23. package/src/genome/scaleLocus.js +12 -16
  24. package/src/genomeSpy.js +1 -1
  25. package/src/gl/dataToVertices.js +14 -6
  26. package/src/gl/includes/fp64-utils.js +10 -0
  27. package/src/gl/includes/scales.glsl +2 -0
  28. package/src/gl/webGLHelper.js +3 -1
  29. package/src/index.js +6 -28
  30. package/src/marks/link.js +1 -12
  31. package/src/marks/mark.js +27 -5
  32. package/src/marks/markUtils.js +41 -25
  33. package/src/marks/pointMark.js +5 -2
  34. package/src/marks/rule.js +11 -2
  35. package/src/scale/glslScaleGenerator.js +16 -29
  36. package/src/scale/scale.js +10 -0
  37. package/src/scale/ticks.js +11 -6
  38. package/src/spec/channel.d.ts +343 -43
  39. package/src/spec/data.d.ts +14 -3
  40. package/src/spec/scale.d.ts +18 -1
  41. package/src/spec/view.d.ts +12 -6
  42. package/src/tooltip/refseqGeneTooltipHandler.js +1 -0
  43. package/src/types/filetypes.d.ts +10 -0
  44. package/src/types/internmap.d.ts +22 -0
  45. package/src/types/vega-loader.d.ts +1 -0
  46. package/src/utils/arrayUtils.js +12 -6
  47. package/src/utils/cloner.js +5 -3
  48. package/src/utils/concatIterables.js +2 -2
  49. package/src/utils/domainArray.js +0 -8
  50. package/src/utils/propertyCoalescer.js +9 -4
  51. package/src/view/axisResolution.js +11 -6
  52. package/src/view/axisView.js +8 -5
  53. package/src/view/decoratorView.js +6 -3
  54. package/src/view/facetView.js +3 -0
  55. package/src/view/flowBuilder.js +2 -1
  56. package/src/view/renderingContext/svgViewRenderingContext.js +7 -3
  57. package/src/view/scaleResolution.js +52 -32
  58. package/src/view/testUtils.js +7 -4
  59. package/src/view/unitView.js +15 -9
  60. package/src/view/view.js +10 -8
  61. package/src/view/viewFactory.js +2 -0
  62. package/src/view/viewUtils.js +4 -4
  63. package/src/options.d.ts +0 -15
  64. package/src/utils/fisheye.js +0 -60
  65. package/src/utils/html.js +0 -23
  66. package/src/utils/html.test.js +0 -13
  67. package/src/view/channel.js +0 -5
@@ -1,7 +1,13 @@
1
1
  import { Data } from "./data";
2
2
  import { Scale } from "./scale";
3
3
  import { TransformParams } from "./transform";
4
- import { Channel, Encoding, FacetFieldDef, PositionalChannel } from "./channel";
4
+ import {
5
+ Channel,
6
+ Encoding,
7
+ FacetFieldDef,
8
+ PrimaryPositionalChannel,
9
+ Type,
10
+ } from "./channel";
5
11
  import {
6
12
  FillAndStrokeProps,
7
13
  MarkConfigAndType,
@@ -30,7 +36,7 @@ export interface FacetMapping {
30
36
  * The opacity is interpolated between the specified stops.
31
37
  */
32
38
  export interface DynamicOpacity {
33
- channel?: PositionalChannel;
39
+ channel?: PrimaryPositionalChannel;
34
40
  /** Stops expressed as units (base pairs, for example) per pixel. */
35
41
  unitsPerPixel: number[];
36
42
  /** Opacity values that match the given stops. */
@@ -124,14 +130,14 @@ export interface LayerSpec extends ViewSpecBase, AggregateSamplesSpec {
124
130
  }
125
131
 
126
132
  export interface FacetSpec extends ViewSpecBase {
127
- facet: FacetFieldDef | FacetMapping;
133
+ facet: any; //FacetMapping | FacetFieldDef
128
134
  spec: LayerSpec | UnitSpec;
129
135
  columns?: number;
130
136
  spacing?: number;
131
137
  }
132
138
 
133
139
  export interface SampleAttributeDef {
134
- type: string;
140
+ type: Type; // TODO: Omit index/locus
135
141
  /** Color scale (primary) */
136
142
  scale?: Scale;
137
143
  barScale?: Scale;
@@ -170,7 +176,7 @@ export interface ResolveSpec {
170
176
 
171
177
  export type ContainerSpec = (
172
178
  | LayerSpec
173
- | FacetSpec
179
+ // | FacetSpec
174
180
  | SampleSpec
175
181
  | VConcatSpec
176
182
  | HConcatSpec
@@ -182,7 +188,7 @@ export type ContainerSpec = (
182
188
  export type ViewSpec =
183
189
  | UnitSpec
184
190
  | LayerSpec
185
- | FacetSpec
191
+ // | FacetSpec
186
192
  | SampleSpec
187
193
  | VConcatSpec
188
194
  | HConcatSpec
@@ -43,6 +43,7 @@ async function fetchGeneSummary(symbol) {
43
43
 
44
44
  console.log("Searching: " + symbol);
45
45
 
46
+ /** @type {RequestInit} */
46
47
  const opts = { mode: "cors" };
47
48
 
48
49
  const searchResult = await fetch(
@@ -2,3 +2,13 @@ declare module "*.svg" {
2
2
  const content: string;
3
3
  export default content;
4
4
  }
5
+
6
+ declare module "*.txt" {
7
+ const content: string;
8
+ export default content;
9
+ }
10
+
11
+ declare module "*.png" {
12
+ const value: any;
13
+ export = value;
14
+ }
@@ -0,0 +1,22 @@
1
+ // Source: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/d3-array/index.d.ts
2
+
3
+ declare module "internmap" {
4
+ /**
5
+ * The InternMap class extends the native JavaScript Map class, allowing Dates and other non-primitive keys by bypassing the SameValueZero algorithm when determining key equality.
6
+ */
7
+ export class InternMap<K = any, V = any> extends Map<K, V> {
8
+ constructor(
9
+ entries?: readonly (readonly [K, V])[] | null,
10
+ keyFunction?: KeyFunction
11
+ );
12
+ }
13
+
14
+ /**
15
+ * The InternSet class extends the native JavaScript Set class, allowing Dates and other non-primitive keys by bypassing the SameValueZero algorithm when determining key equality.
16
+ */
17
+ export class InternSet<T = any> extends Set<T> {
18
+ constructor(values?: readonly T[] | null, keyFunction?: KeyFunction);
19
+ }
20
+ }
21
+
22
+ type KeyFunction = (key: any) => string | number | bigint | boolean | symbol;
@@ -0,0 +1 @@
1
+ declare module "vega-loader";
@@ -1,14 +1,20 @@
1
1
  /**
2
- *
2
+ * @param {T[]} a
3
+ * @param {T[]} b
4
+ * @template T
5
+ */
6
+ export function shallowArrayEquals(a, b) {
7
+ return a.length == b.length && a.every((s, i) => a[i] === b[i]);
8
+ }
9
+
10
+ /**
3
11
  * @param {A[]} a
4
12
  * @param {B[]} b
5
- * @param {function(A):T} [aAccessor]
6
- * @param {function(B):T} [bAccessor]
13
+ * @param {function(A):T} aAccessor
14
+ * @param {function(B):T} bAccessor
7
15
  * @template A, B, T
8
16
  */
9
- export function shallowArrayEquals(a, b, aAccessor, bAccessor) {
10
- aAccessor = aAccessor || ((x) => x);
11
- bAccessor = bAccessor || ((x) => x);
17
+ export function shallowArrayEqualsWithAccessors(a, b, aAccessor, bAccessor) {
12
18
  return (
13
19
  a.length == b.length &&
14
20
  a.every((s, i) => aAccessor(a[i]) === bAccessor(b[i]))
@@ -7,14 +7,16 @@
7
7
  * https://mrale.ph/blog/2014/07/30/constructor-vs-objectcreate.html
8
8
  *
9
9
  * @param {T} template The template object that
10
- * @returns {function(T):T}
10
+ * @returns {(function(T):T) & { properties: string[] }}
11
11
  * @template T
12
12
  */
13
13
  export default function createCloner(template) {
14
14
  // TODO: Check that only properties, not methods get cloned
15
- const properties = Object.keys(template);
15
+ const properties = /** @type {string[]} */ (
16
+ Object.keys(template).filter((k) => typeof k == "string")
17
+ );
16
18
 
17
- const cloner = /** @type {function(T):T} */ (
19
+ const cloner = /** @type {(function(T):T) & { properties: string[] }} */ (
18
20
  new Function(
19
21
  "source",
20
22
  "return { " +
@@ -1,6 +1,6 @@
1
1
  /**
2
- *
3
- * @param {...Iterable} iterables
2
+ * @param {...Iterable<T>} iterables
3
+ * @template T
4
4
  */
5
5
  export default function concatIterables(...iterables) {
6
6
  if (iterables.length <= 0) {
@@ -194,14 +194,6 @@ export default function createDomain(type, initialDomain) {
194
194
  throw new Error("Unknown type: " + type);
195
195
  }
196
196
 
197
- /**
198
- *
199
- * @param {array} array
200
- */
201
- export function isDomainArray(array) {
202
- return array instanceof DomainArray;
203
- }
204
-
205
197
  /**
206
198
  * For unit tests
207
199
  *
@@ -2,16 +2,19 @@
2
2
  * Coalesces properties. Allows for creating chains of defaults without
3
3
  * using object destructuring, which may generate piles of garbage for GC.
4
4
  *
5
- * Still WIP. TODO: Efficient computed defaults.
5
+ * Still WIP.
6
+ * TODO: Efficient computed defaults.
7
+ * TODO: Make sense of the types
6
8
  *
7
9
  * @param {...function():T} sources
8
10
  * @returns {T}
9
11
  *
10
- * @template T
12
+ * @template {Record<string | symbol, any>} [T=object]
11
13
  */
12
14
  export default function coalesceProperties(...sources) {
15
+ /** @type {ProxyHandler<T>} */
13
16
  const handler = {
14
- get: function (target, prop, receiver) {
17
+ get(_target, prop, _receiver) {
15
18
  for (const source of sources) {
16
19
  const props = source();
17
20
  const value = props[prop];
@@ -22,7 +25,8 @@ export default function coalesceProperties(...sources) {
22
25
  return undefined;
23
26
  },
24
27
 
25
- has: function (target, prop, receiver) {
28
+ // @ts-ignore
29
+ has(target, prop, _receiver) {
26
30
  for (const source of sources) {
27
31
  const props = source();
28
32
  if (prop in props) {
@@ -33,5 +37,6 @@ export default function coalesceProperties(...sources) {
33
37
  },
34
38
  };
35
39
 
40
+ // @ts-ignore
36
41
  return new Proxy({}, handler);
37
42
  }
@@ -5,6 +5,7 @@ import {
5
5
  isExprDef,
6
6
  isFieldDef,
7
7
  isSecondaryChannel,
8
+ isValueDef,
8
9
  } from "../encoder/encoder";
9
10
  import { peek } from "../utils/arrayUtils";
10
11
  import coalesce from "../utils/coalesce";
@@ -22,7 +23,7 @@ export default class AxisResolution {
22
23
  */
23
24
  constructor(channel) {
24
25
  this.channel = channel;
25
- /** @type {import("./scaleResolution").ResolutionMember[]} The involved views */
26
+ /** @type {import("./scaleResolution").ResolutionMember<import("../spec/channel").PositionalChannel>[]} The involved views */
26
27
  this.members = [];
27
28
  }
28
29
 
@@ -35,7 +36,7 @@ export default class AxisResolution {
35
36
  * scales have been resolved.
36
37
  *
37
38
  * @param {UnitView} view
38
- * @param {import("../spec/channel").Channel} channel TODO: Do something for this
39
+ * @param {import("../spec/channel").PositionalChannel} channel TODO: Do something for this
39
40
  */
40
41
  pushUnitView(view, channel) {
41
42
  const newScaleResolution = view.getScaleResolution(this.channel);
@@ -56,10 +57,10 @@ export default class AxisResolution {
56
57
 
57
58
  getAxisProps() {
58
59
  return getCachedOrCall(this, "axisProps", () => {
59
- const propArray = this.members.map(
60
- (member) =>
61
- getChannelDefWithScale(member.view, member.channel).axis
62
- );
60
+ const propArray = this.members.map((member) => {
61
+ const channelDef = member.view.mark.encoding[member.channel];
62
+ return "axis" in channelDef && channelDef.axis;
63
+ });
63
64
 
64
65
  if (
65
66
  propArray.length > 0 &&
@@ -87,6 +88,10 @@ export default class AxisResolution {
87
88
  member.channel
88
89
  );
89
90
 
91
+ if (isValueDef(channelDef)) {
92
+ return undefined;
93
+ }
94
+
90
95
  // Retain nulls as they indicate that no title should be shown
91
96
 
92
97
  return {
@@ -2,14 +2,17 @@ import { validTicks, tickValues, tickFormat, tickCount } from "../scale/ticks";
2
2
  import LayerView from "./layerView";
3
3
  import { isNumber } from "vega-util";
4
4
  import smoothstep from "../utils/smoothstep";
5
- import { shallowArrayEquals } from "../utils/arrayUtils";
5
+ import {
6
+ shallowArrayEquals,
7
+ shallowArrayEqualsWithAccessors,
8
+ } from "../utils/arrayUtils";
6
9
  import { FlexDimensions } from "../utils/layout/flexLayout";
7
10
  import DynamicCallbackSource from "../data/sources/dynamicCallbackSource";
8
11
 
9
12
  const CHROM_LAYER_NAME = "chromosome_ticks_and_labels";
10
13
 
11
14
  /**
12
- * @typedef {import("../spec/channel").PositionalChannel} PositionalChannel
15
+ * @typedef {import("../spec/channel").PrimaryPositionalChannel} PositionalChannel
13
16
  * @typedef {import("../spec/view").GeometricDimension} GeometricDimension
14
17
  */
15
18
 
@@ -265,7 +268,7 @@ function generateTicks(axisProps, scale, axisLength, oldTicks = []) {
265
268
  : tickValues(scale, count);
266
269
 
267
270
  if (
268
- shallowArrayEquals(
271
+ shallowArrayEqualsWithAccessors(
269
272
  values,
270
273
  oldTicks,
271
274
  (v) => v,
@@ -428,7 +431,7 @@ function createAxis(axisProps) {
428
431
  },
429
432
  encoding: {
430
433
  [main]: { field: "value", type: "quantitative" },
431
- text: { field: "label", type: "quantitative" },
434
+ text: { field: "label" },
432
435
  },
433
436
  });
434
437
 
@@ -649,7 +652,7 @@ export function createGenomeAxis(axisProps) {
649
652
  },
650
653
  encoding: {
651
654
  [main + "2"]: { field: "continuousEnd", type: "locus" },
652
- text: { field: "name", type: "ordinal" },
655
+ text: { field: "name" },
653
656
  },
654
657
  };
655
658
  return labels;
@@ -6,7 +6,7 @@ import UnitView from "./unitView";
6
6
  import { ZERO_FLEXDIMENSIONS } from "../utils/layout/flexLayout";
7
7
 
8
8
  /**
9
- * @typedef {import("../spec/channel").PositionalChannel} PositionalChannel
9
+ * @typedef {import("../spec/channel").PrimaryPositionalChannel} PositionalChannel
10
10
  * @typedef {import("../spec/view").GeometricDimension} GeometricDimension
11
11
  */
12
12
 
@@ -384,7 +384,10 @@ export default class DecoratorView extends ContainerView {
384
384
  zDelta: 0,
385
385
  });
386
386
  }
387
- } else if (event.type == "mousedown" && event.uiEvent.button === 0) {
387
+ } else if (
388
+ event.type == "mousedown" &&
389
+ /** @type {MouseEvent} */ (event.uiEvent).button === 0
390
+ ) {
388
391
  const mouseEvent = /** @type {MouseEvent} */ (event.uiEvent);
389
392
  mouseEvent.preventDefault();
390
393
 
@@ -424,7 +427,7 @@ export default class DecoratorView extends ContainerView {
424
427
 
425
428
  _getZoomableResolutions() {
426
429
  return this._cache("zoomableResolutions", () => {
427
- /** @type {Record<import("../spec/channel").PositionalChannel, Set<import("./scaleResolution").default>>} */
430
+ /** @type {Record<import("../spec/channel").PrimaryPositionalChannel, Set<import("./scaleResolution").default>>} */
428
431
  const resolutions = {
429
432
  x: new Set(),
430
433
  y: new Set(),
@@ -1,3 +1,6 @@
1
+ // @ts-nocheck
2
+ // TODO: Enable when this is taken to use again
3
+
1
4
  import { isFacetFieldDef, isFacetMapping } from "./viewUtils";
2
5
  import ContainerView from "./containerView";
3
6
  import UnitView from "./unitView";
@@ -286,6 +286,7 @@ export function linearizeLocusAccess(view) {
286
286
  ...rewrittenEncoding,
287
287
  };
288
288
  // This is so ugly...
289
+ // @ts-ignore
289
290
  invalidate(view.mark, "encoding");
290
291
  },
291
292
  }
@@ -294,7 +295,7 @@ export function linearizeLocusAccess(view) {
294
295
 
295
296
  /**
296
297
  * @param {View} view
297
- * @param {Record<string, import("../spec/channel").ChannelDef>} [encoding]
298
+ * @param {Encoding} [encoding]
298
299
  * @returns {import("../spec/transform").CompareParams}
299
300
  */
300
301
  function getCompareParamsForView(view, encoding) {
@@ -8,8 +8,12 @@ import ViewRenderingContext from "./viewRenderingContext";
8
8
  * @typedef {import("../view").default} View
9
9
  */
10
10
  export default class SvgViewRenderingContext extends ViewRenderingContext {
11
- constructor() {
12
- super();
11
+ /**
12
+ *
13
+ * @param {import("../rendering").GlobalRenderingOptions} globalOptions
14
+ */
15
+ constructor(globalOptions) {
16
+ super(globalOptions);
13
17
 
14
18
  /** @type {import("../../utils/layout/rectangle").default} */
15
19
  this.coords = undefined;
@@ -39,7 +43,7 @@ export default class SvgViewRenderingContext extends ViewRenderingContext {
39
43
  this.svg.setAttributeNS(
40
44
  null,
41
45
  "viewBox",
42
- ["x", "y", "width", "height"].map((a) => viewBox[a]).join(" ")
46
+ [viewBox.x, viewBox.y, viewBox.width, viewBox.height].join(" ")
43
47
  );
44
48
  }
45
49
 
@@ -23,8 +23,8 @@ import {
23
23
  isColorChannel,
24
24
  isDiscreteChannel,
25
25
  isPositionalChannel,
26
+ isPrimaryPositionalChannel,
26
27
  isSecondaryChannel,
27
- primaryPositionalChannels,
28
28
  } from "../encoder/encoder";
29
29
  import {
30
30
  isChromosomalLocus,
@@ -34,6 +34,7 @@ import { NominalDomain } from "../utils/domainArray";
34
34
  import { easeQuadInOut } from "d3-ease";
35
35
  import { interpolateZoom } from "d3-interpolate";
36
36
  import { shallowArrayEquals } from "../utils/arrayUtils";
37
+ import { isScaleLocus } from "../genome/scaleLocus";
37
38
 
38
39
  export const QUANTITATIVE = "quantitative";
39
40
  export const ORDINAL = "ordinal";
@@ -41,6 +42,15 @@ export const NOMINAL = "nominal";
41
42
  export const LOCUS = "locus"; // Humdum, should this be "genomic"?
42
43
  export const INDEX = "index";
43
44
 
45
+ /**
46
+ * @template {Channel}[T=Channel]
47
+ * @typedef {{view: import("./unitView").default, channel: T}} ResolutionMember
48
+ * @typedef {import("./unitView").default} UnitView
49
+ * @typedef {import("../encoder/encoder").VegaScale} VegaScale
50
+ * @typedef {import("../utils/domainArray").DomainArray} DomainArray
51
+ * @typedef {import("../genome/genome").ChromosomalLocus} ChromosomalLocus
52
+ *
53
+ */
44
54
  /**
45
55
  * Resolution takes care of merging domains and scales from multiple views.
46
56
  * This class also provides some utility methods for zooming the scales etc..
@@ -50,12 +60,6 @@ export const INDEX = "index";
50
60
  * @typedef {import("./scaleResolutionApi").ScaleResolutionApi} ScaleResolutionApi
51
61
  * @implements {ScaleResolutionApi}
52
62
  *
53
- * @typedef {{view: import("./unitView").default, channel: Channel}} ResolutionMember
54
- * @typedef {import("./unitView").default} UnitView
55
- * @typedef {import("../encoder/encoder").VegaScale} VegaScale
56
- * @typedef {import("../utils/domainArray").DomainArray} DomainArray
57
- * @typedef {import("../genome/genome").ChromosomalLocus} ChromosomalLocus
58
- *
59
63
  * @typedef {import("../spec/channel").Channel} Channel
60
64
  * @typedef {import("../spec/scale").Scale} Scale
61
65
  * @typedef {import("../spec/scale").NumericDomain} NumericDomain
@@ -323,7 +327,7 @@ export default class ScaleResolution {
323
327
  const scale = createScale(props);
324
328
  this._scale = scale;
325
329
 
326
- if (scale.type == "locus") {
330
+ if (isScaleLocus(scale)) {
327
331
  scale.genome(this.getGenome());
328
332
  }
329
333
 
@@ -366,7 +370,7 @@ export default class ScaleResolution {
366
370
  }
367
371
 
368
372
  isZoomable() {
369
- if (!primaryPositionalChannels.includes(this.channel)) {
373
+ if (!isPrimaryPositionalChannel(this.channel)) {
370
374
  return false;
371
375
  }
372
376
 
@@ -400,13 +404,14 @@ export default class ScaleResolution {
400
404
  const oldDomain = scale.domain();
401
405
  let newDomain = [...oldDomain];
402
406
 
407
+ // @ts-expect-error
403
408
  const anchor = scale.invert(scaleAnchor);
404
409
 
405
410
  if (this.getScaleProps().reverse) {
406
411
  pan = -pan;
407
412
  }
408
413
 
409
- // TODO: log, pow, symlog, ...
414
+ // TODO: symlog
410
415
  switch (scale.type) {
411
416
  case "linear":
412
417
  case "index":
@@ -419,22 +424,33 @@ export default class ScaleResolution {
419
424
  newDomain = zoomLog(newDomain, anchor, scaleFactor);
420
425
  break;
421
426
  case "pow":
422
- case "sqrt":
423
- newDomain = panPow(newDomain, pan || 0, scale.exponent());
427
+ case "sqrt": {
428
+ const powScale =
429
+ /** @type {import("d3-scale").ScalePower<number, number>} */ (
430
+ scale
431
+ );
432
+ newDomain = panPow(newDomain, pan || 0, powScale.exponent());
424
433
  newDomain = zoomPow(
425
434
  newDomain,
426
435
  anchor,
427
436
  scaleFactor,
428
- scale.exponent()
437
+ powScale.exponent()
429
438
  );
430
439
  break;
440
+ }
431
441
  default:
432
- throw new Error("Unsupported scale type: " + scale.type);
442
+ throw new Error(
443
+ "Zooming is not implemented for: " + scale.type
444
+ );
433
445
  }
434
446
 
435
447
  // TODO: Use the zoomTo method. Move clamping etc there.
436
448
  if (this._zoomExtent) {
437
- newDomain = clampRange(newDomain, ...this._zoomExtent);
449
+ newDomain = clampRange(
450
+ newDomain,
451
+ this._zoomExtent[0],
452
+ this._zoomExtent[1]
453
+ );
438
454
  }
439
455
 
440
456
  if ([0, 1].some((i) => newDomain[i] != oldDomain[i])) {
@@ -491,9 +507,8 @@ export default class ScaleResolution {
491
507
  },
492
508
  });
493
509
  */
494
- const interpolator = interpolateZoom.rho(0.7)(
495
- [fc, 0, fw],
496
- [tc, 0, tw]
510
+ const interpolator = interpolateZoom([fc, 0, fw], [tc, 0, tw]).rho(
511
+ 0.7
497
512
  );
498
513
  await animator.transition({
499
514
  duration: (duration / 1000) * interpolator.duration,
@@ -672,12 +687,13 @@ export default class ScaleResolution {
672
687
  *
673
688
  * @param {Channel} channel
674
689
  * @param {string} dataType
690
+ * @returns {import("../spec/scale").ScaleType}
675
691
  */
676
692
  function getDefaultScaleType(channel, dataType) {
677
693
  // TODO: Band scale, Bin-Quantitative
678
694
 
679
- if ([INDEX, LOCUS].includes(dataType)) {
680
- if (primaryPositionalChannels.includes(channel)) {
695
+ if (dataType == INDEX || dataType == LOCUS) {
696
+ if (isPrimaryPositionalChannel(channel)) {
681
697
  return dataType;
682
698
  } else {
683
699
  // TODO: Also explicitly set scales should be validated
@@ -688,13 +704,11 @@ function getDefaultScaleType(channel, dataType) {
688
704
  }
689
705
 
690
706
  /**
691
- * @type {Partial<Record<Channel, string[]>>}
707
+ * @type {Partial<Record<Channel, (import("../spec/scale").ScaleType | undefined)[]>>}
692
708
  * Default types: nominal, ordinal, quantitative.
693
709
  * undefined = incompatible, "null" = disabled (pass-thru)
694
710
  */
695
711
  const defaults = {
696
- uniqueId: ["null", undefined, undefined],
697
- facetIndex: ["null", undefined, undefined],
698
712
  x: ["band", "band", "linear"],
699
713
  y: ["band", "band", "linear"],
700
714
  size: [undefined, "point", "linear"],
@@ -706,16 +720,24 @@ function getDefaultScaleType(channel, dataType) {
706
720
  stroke: ["ordinal", "ordinal", "linear"],
707
721
  strokeWidth: [undefined, undefined, "linear"],
708
722
  shape: ["ordinal", "ordinal", undefined],
709
- sample: ["null", "null", undefined],
710
- semanticScore: [undefined, undefined, "null"],
711
- search: ["null", undefined, undefined],
712
- text: ["null", "null", "null"],
713
723
  dx: [undefined, undefined, "null"],
714
724
  dy: [undefined, undefined, "null"],
715
725
  angle: [undefined, undefined, "linear"],
716
726
  };
717
727
 
718
- const type = defaults[channel]
728
+ /** @type {Channel[]} */
729
+ const typelessChannels = [
730
+ "uniqueId",
731
+ "facetIndex",
732
+ "semanticScore",
733
+ "search",
734
+ "text",
735
+ "sample",
736
+ ];
737
+
738
+ const type = typelessChannels.includes(channel)
739
+ ? "null"
740
+ : defaults[channel]
719
741
  ? defaults[channel][[NOMINAL, ORDINAL, QUANTITATIVE].indexOf(dataType)]
720
742
  : dataType == QUANTITATIVE
721
743
  ? "linear"
@@ -739,10 +761,8 @@ function applyLockedProperties(props, channel) {
739
761
  props.range = [0, 1];
740
762
  }
741
763
 
742
- if (channel == "opacity") {
743
- if (isContinuous(props.type)) {
744
- props.clamp = true;
745
- }
764
+ if (channel == "opacity" && isContinuous(props.type)) {
765
+ props.clamp = true;
746
766
  }
747
767
  }
748
768
 
@@ -15,15 +15,15 @@ import { ViewFactory } from "./viewFactory";
15
15
  /** @type {<V extends View>(spec: ViewSpec, viewClass: { new(...args: any[]): V }, context?: ViewContext) => V} */
16
16
  export function create(spec, viewClass, context = undefined) {
17
17
  const viewTypeRegistry = new ViewFactory();
18
- /** @type {ViewContext} */
19
- const c = {
18
+
19
+ const c = /** @type {ViewContext} */ ({
20
20
  ...(context || {}),
21
21
  accessorFactory: new AccessorFactory(),
22
22
 
23
23
  createView: function (spec, parent, defaultName) {
24
24
  return viewTypeRegistry.createView(spec, c, parent, defaultName);
25
25
  },
26
- };
26
+ });
27
27
 
28
28
  const view = c.createView(spec, null, "root");
29
29
 
@@ -40,7 +40,10 @@ export async function createAndInitialize(
40
40
  viewClass,
41
41
  context = undefined
42
42
  ) {
43
- context = { ...(context || {}), dataFlow: new DataFlow() };
43
+ context = /** @type {ViewContext} */ ({
44
+ ...(context || {}),
45
+ dataFlow: new DataFlow(),
46
+ });
44
47
  const view = create(spec, viewClass, context);
45
48
  resolveScalesAndAxes(view);
46
49
  await initializeData(view, context.dataFlow);