@genome-spy/core 0.15.0 → 0.18.1

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 (70) hide show
  1. package/LICENSE +7 -7
  2. package/README.md +16 -0
  3. package/dist/index.js +42 -42
  4. package/dist/{genome-spy-schema.json → schema.json} +1824 -652
  5. package/dist/style.css +1 -1
  6. package/package.json +9 -7
  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/root.d.ts +3 -8
  41. package/src/spec/scale.d.ts +18 -1
  42. package/src/spec/view.d.ts +12 -6
  43. package/src/tooltip/refseqGeneTooltipHandler.js +1 -0
  44. package/src/types/filetypes.d.ts +10 -0
  45. package/src/types/internmap.d.ts +22 -0
  46. package/src/types/vega-loader.d.ts +1 -0
  47. package/src/utils/addBaseUrl.js +19 -0
  48. package/src/utils/addBaseUrl.test.js +21 -0
  49. package/src/utils/arrayUtils.js +12 -6
  50. package/src/utils/cloner.js +5 -3
  51. package/src/utils/concatIterables.js +2 -2
  52. package/src/utils/domainArray.js +0 -8
  53. package/src/utils/propertyCoalescer.js +9 -4
  54. package/src/view/axisResolution.js +11 -6
  55. package/src/view/axisView.js +8 -5
  56. package/src/view/decoratorView.js +6 -3
  57. package/src/view/facetView.js +3 -0
  58. package/src/view/flowBuilder.js +2 -1
  59. package/src/view/renderingContext/svgViewRenderingContext.js +7 -3
  60. package/src/view/scaleResolution.js +52 -32
  61. package/src/view/testUtils.js +7 -4
  62. package/src/view/unitView.js +15 -9
  63. package/src/view/view.js +10 -8
  64. package/src/view/viewFactory.js +2 -0
  65. package/src/view/viewUtils.js +4 -5
  66. package/src/options.d.ts +0 -15
  67. package/src/utils/fisheye.js +0 -60
  68. package/src/utils/html.js +0 -23
  69. package/src/utils/html.test.js +0 -13
  70. package/src/view/channel.js +0 -5
@@ -6,6 +6,8 @@ const minimumDomainSpan = 1;
6
6
  /**
7
7
  * Creates a "index" scale, which works similarly to d3's band scale but the domain
8
8
  * consists of integer indexes.
9
+ *
10
+ * @returns {import("./scaleIndex").ScaleIndex}
9
11
  */
10
12
  export default function scaleIndex() {
11
13
  let domain = [0, 1];
@@ -21,29 +23,20 @@ export default function scaleIndex() {
21
23
  /** The number of the first element. This affects the generated ticks and their labels. */
22
24
  let numberingOffset = 0;
23
25
 
24
- /**
25
- *
26
- * @param {number} x
27
- */
28
- function scale(x) {
29
- // In principle, the domain consists of integer indices. However,
30
- // we accept real numbers so that items can be centered inside a band.
31
- // TODO: paddingInner/paddingOuter/align. Now they are implemented in GLSL.
32
- return ((x - domain[0]) / domainSpan) * rangeSpan + range[0];
33
- }
26
+ const scaleFunction = (/** @type {number} */ x) =>
27
+ ((x - domain[0]) / domainSpan) * rangeSpan + range[0];
34
28
 
35
29
  /**
30
+ * In principle, the domain consists of integer indices. However,
31
+ * we accept real numbers so that items can be centered inside a band.
36
32
  *
37
- * @param {number} y
33
+ * @type {import("./scaleIndex").ScaleIndex}
38
34
  */
39
- scale.invert = function (y) {
40
- return ((y - range[0]) / rangeSpan) * domainSpan + domain[0];
41
- };
35
+ const scale = /** @type {any} */ (scaleFunction);
42
36
 
43
- /**
44
- *
45
- * @param {Iterable<number>} [_]
46
- */
37
+ scale.invert = (y) => ((y - range[0]) / rangeSpan) * domainSpan + domain[0];
38
+
39
+ // @ts-expect-error
47
40
  scale.domain = function (_) {
48
41
  if (arguments.length) {
49
42
  domain = extent(_);
@@ -62,10 +55,7 @@ export default function scaleIndex() {
62
55
  }
63
56
  };
64
57
 
65
- /**
66
- *
67
- * @param {Iterable<number>} [_]
68
- */
58
+ // @ts-expect-error
69
59
  scale.range = function (_) {
70
60
  if (arguments.length) {
71
61
  range = [..._];
@@ -76,10 +66,7 @@ export default function scaleIndex() {
76
66
  }
77
67
  };
78
68
 
79
- /**
80
- *
81
- * @param {number} [_]
82
- */
69
+ // @ts-expect-error
83
70
  scale.numberingOffset = function (_) {
84
71
  if (arguments.length) {
85
72
  numberingOffset = _;
@@ -89,10 +76,7 @@ export default function scaleIndex() {
89
76
  }
90
77
  };
91
78
 
92
- /**
93
- *
94
- * @param {number} _
95
- */
79
+ // @ts-expect-error
96
80
  scale.padding = function (_) {
97
81
  if (arguments.length) {
98
82
  paddingOuter = _;
@@ -103,10 +87,7 @@ export default function scaleIndex() {
103
87
  }
104
88
  };
105
89
 
106
- /**
107
- *
108
- * @param {number} _
109
- */
90
+ // @ts-expect-error
110
91
  scale.paddingInner = function (_) {
111
92
  if (arguments.length) {
112
93
  paddingInner = Math.min(1, _);
@@ -116,10 +97,7 @@ export default function scaleIndex() {
116
97
  }
117
98
  };
118
99
 
119
- /**
120
- *
121
- * @param {number} _
122
- */
100
+ // @ts-expect-error
123
101
  scale.paddingOuter = function (_) {
124
102
  if (arguments.length) {
125
103
  paddingOuter = _;
@@ -129,10 +107,7 @@ export default function scaleIndex() {
129
107
  }
130
108
  };
131
109
 
132
- /**
133
- *
134
- * @param {number} _
135
- */
110
+ // @ts-expect-error
136
111
  scale.align = function (_) {
137
112
  if (arguments.length) {
138
113
  align = Math.max(0, Math.min(1, _));
@@ -146,10 +121,6 @@ export default function scaleIndex() {
146
121
 
147
122
  scale.bandwidth = () => scale.step();
148
123
 
149
- /**
150
- * @param {number} count
151
- * @returns {number[]}
152
- */
153
124
  scale.ticks = (count) => {
154
125
  const align = /** @type {number} */ (scale.align());
155
126
  const offset = /** @type {number} */ (scale.numberingOffset());
@@ -162,12 +133,7 @@ export default function scaleIndex() {
162
133
  .map((x) => x - numberingOffset);
163
134
  };
164
135
 
165
- /**
166
- *
167
- * @param {number} [count]
168
- * @param {string} [specifier]
169
- */
170
- scale.tickFormat = function (count, specifier) {
136
+ scale.tickFormat = (count, specifier) => {
171
137
  if (specifier) {
172
138
  throw new Error(
173
139
  "Index scale's tickFormat does not support a specifier!"
@@ -0,0 +1,11 @@
1
+ import Genome from "./genome";
2
+ import { ScaleIndex } from "./scaleIndex";
3
+
4
+ export default function scaleLocus(): ScaleLocus;
5
+
6
+ export interface ScaleLocus extends ScaleIndex {
7
+ genome(): Genome;
8
+ genome(genome: Genome): this;
9
+ }
10
+
11
+ export function isScaleLocus(scale: any): scale is ScaleLocus;
@@ -1,24 +1,22 @@
1
1
  import { tickStep } from "d3-array";
2
2
  import { format as d3format } from "d3-format";
3
- import scaleIndex from "./scaleIndex";
3
+ import scaleIndex from "./scaleIndex.js";
4
4
 
5
5
  /**
6
6
  * Creates a "locus" scale, which works similarly to band scale but the domain
7
7
  * consists of integer indexes.
8
8
  *
9
9
  * @typedef {import("./genome").default} Genome
10
+ * @returns {import("./scaleLocus").ScaleLocus}
10
11
  */
11
12
  export default function scaleLocus() {
12
- const scale = scaleIndex().numberingOffset(1);
13
+ /** @type {import("./scaleLocus").ScaleLocus} */
14
+ const scale = /** @type {any} */ (scaleIndex().numberingOffset(1));
13
15
 
14
16
  /** @type {Genome} */
15
17
  let genome;
16
18
 
17
- /**
18
- *
19
- * @param {Genome} [_]
20
- * @deprecated
21
- */
19
+ // @ts-expect-error
22
20
  scale.genome = function (_) {
23
21
  if (arguments.length) {
24
22
  genome = _;
@@ -28,10 +26,6 @@ export default function scaleLocus() {
28
26
  }
29
27
  };
30
28
 
31
- /**
32
- * @param {number} count
33
- * @returns {number[]}
34
- */
35
29
  scale.ticks = (count) => {
36
30
  if (!genome) {
37
31
  return [];
@@ -68,11 +62,6 @@ export default function scaleLocus() {
68
62
  return ticks;
69
63
  };
70
64
 
71
- /**
72
- *
73
- * @param {number} [count]
74
- * @param {string} [specifier]
75
- */
76
65
  scale.tickFormat = (count, specifier) => {
77
66
  if (!genome) {
78
67
  return;
@@ -110,3 +99,10 @@ export default function scaleLocus() {
110
99
 
111
100
  return scale;
112
101
  }
102
+
103
+ /**
104
+ * @type {import("./scaleLocus").isScaleLocus}
105
+ */
106
+ export function isScaleLocus(scale) {
107
+ return scale.type == "locus";
108
+ }
package/src/genomeSpy.js CHANGED
@@ -59,7 +59,7 @@ export default class GenomeSpy {
59
59
  *
60
60
  * @param {HTMLElement} container
61
61
  * @param {RootSpec} spec
62
- * @param {import("./options").EmbedOptions} [options]
62
+ * @param {import("./embedApi").EmbedOptions} [options]
63
63
  */
64
64
  constructor(container, spec, options = {}) {
65
65
  this.container = container;
@@ -6,6 +6,7 @@ import ArrayBuilder from "./arrayBuilder";
6
6
  import { SDF_PADDING } from "../fonts/bmFontMetrics";
7
7
  import { peek } from "../utils/arrayUtils";
8
8
  import createBinningRangeIndexer from "../utils/binnedRangeIndex";
9
+ import { isValueDef } from "../encoder/encoder";
9
10
 
10
11
  /**
11
12
  * @typedef {object} RangeEntry Represents a location of a vertex subset
@@ -56,6 +57,8 @@ export class GeometryBuilder {
56
57
  const doubleArray = [0, 0];
57
58
  const fp64 = ce.scale.fp64;
58
59
 
60
+ const indexer = ce.indexer;
61
+
59
62
  /**
60
63
  * Discrete variables both numeric and strings must be "indexed",
61
64
  * 64 bit floats must be converted to vec2.
@@ -63,8 +66,8 @@ export class GeometryBuilder {
63
66
  *
64
67
  * @type {function(any):(number | number[])}
65
68
  */
66
- const f = ce.indexer
67
- ? ce.indexer
69
+ const f = indexer
70
+ ? (d) => indexer(accessor(d))
68
71
  : fp64
69
72
  ? (d) => fp64ify(accessor(d), doubleArray)
70
73
  : accessor;
@@ -462,10 +465,15 @@ export class TextVertexBuilder extends GeometryBuilder {
462
465
 
463
466
  const e = encoders;
464
467
 
465
- /** @type {function(any):any} */
466
- this.numberFormat = e.text.channelDef.format
467
- ? format(e.text.channelDef.format)
468
- : (d) => d;
468
+ const channelDef =
469
+ /** @type {import("../spec/channel").TextDef<string>} */ (
470
+ e.text.channelDef
471
+ );
472
+ /** @type {(value: any) => string} */
473
+ this.numberFormat =
474
+ !isValueDef(channelDef) && channelDef.format
475
+ ? format(channelDef.format)
476
+ : (d) => d;
469
477
 
470
478
  this.updateVertexCoord = this.variableBuilder.createUpdater(
471
479
  "vertexCoord",
@@ -45,6 +45,9 @@ export function fp64LowPart(a) {
45
45
  return a - Math.fround(a);
46
46
  }
47
47
 
48
+ /**
49
+ * @param {WebGLRenderingContext | WebGL2RenderingContext} gl
50
+ */
48
51
  export function getPlatformShaderDefines(gl) {
49
52
  const debugInfo = getContextInfo(gl);
50
53
 
@@ -94,6 +97,9 @@ const GL_RENDERER = 0x1f01;
94
97
  const GL_VERSION = 0x1f02;
95
98
  const GL_SHADING_LANGUAGE_VERSION = 0x8b8c;
96
99
 
100
+ /**
101
+ * @param {WebGLRenderingContext | WebGL2RenderingContext} gl
102
+ */
97
103
  export function getContextInfo(gl) {
98
104
  const info = gl.getExtension("WEBGL_debug_renderer_info");
99
105
  const vendor = gl.getParameter(
@@ -113,6 +119,10 @@ export function getContextInfo(gl) {
113
119
  return gpuInfo;
114
120
  }
115
121
 
122
+ /**
123
+ * @param {string} vendor
124
+ * @param {string} renderer
125
+ */
116
126
  function identifyGPUVendor(vendor, renderer) {
117
127
  if (vendor.match(/NVIDIA/i) || renderer.match(/NVIDIA/i)) {
118
128
  return "NVIDIA";
@@ -70,6 +70,8 @@ float scaleBand(float value, vec2 domainExtent, vec2 range,
70
70
 
71
71
  float n = domainExtent[1] - domainExtent[0];
72
72
 
73
+ paddingInner = int(n) > 1 ? paddingInner : 0.0;
74
+
73
75
  // Adapted from: https://github.com/d3/d3-scale/blob/master/src/band.js
74
76
  float step = (stop - start) / max(1.0, n - paddingInner + paddingOuter * 2.0);
75
77
  start += (stop - start - step * (n - paddingInner)) * align;
@@ -395,6 +395,8 @@ export default class WebGLHelper {
395
395
  * Copy-pasted from twgl.js:
396
396
  * https://github.com/greggman/twgl.js/blob/master/src/programs.js
397
397
  * Copyright 2019 Gregg Tavares, MIT license
398
+ *
399
+ * @param {string} src
398
400
  */
399
401
  function addLineNumbersWithError(src, log = "", lineOffset = 0) {
400
402
  const errorRE = /ERROR:\s*\d+:(\d+)/gi;
@@ -426,7 +428,7 @@ function addLineNumbersWithError(src, log = "", lineOffset = 0) {
426
428
  * @param {WebGLShader} fragmentShader
427
429
  */
428
430
  export function createProgram(gl, vertexShader, fragmentShader) {
429
- var program = gl.createProgram();
431
+ const program = gl.createProgram();
430
432
  gl.attachShader(program, vertexShader);
431
433
  gl.attachShader(program, fragmentShader);
432
434
  gl.linkProgram(program);
package/src/index.js CHANGED
@@ -10,9 +10,7 @@ export { GenomeSpy, html, icon };
10
10
  /**
11
11
  * Embeds GenomeSpy into the DOM
12
12
  *
13
- * @param {HTMLElement | string} el HTMLElement or a query selector
14
- * @param {object | string} spec a spec object or an url to a json spec
15
- * @param {import("./options.js").EmbedOptions} [options] options
13
+ * @type {import("./embedApi.js").EmbedFunction}
16
14
  */
17
15
  export async function embed(el, spec, options = {}) {
18
16
  /** @type {HTMLElement} */
@@ -33,19 +31,11 @@ export async function embed(el, spec, options = {}) {
33
31
  let genomeSpy;
34
32
 
35
33
  try {
36
- const specObject = /** @type {import("./spec/root").RootSpec} */ (
37
- isObject(spec) ? spec : await loadSpec(spec)
38
- );
39
-
40
- specObject.baseUrl = specObject.baseUrl || "";
34
+ const specObject = isObject(spec) ? spec : await loadSpec(spec);
41
35
 
42
- if (!("width" in specObject)) {
43
- specObject.width = "container";
44
- }
45
-
46
- if (!("padding" in specObject)) {
47
- specObject.padding = 10;
48
- }
36
+ specObject.baseUrl ??= "";
37
+ specObject.width ??= "container";
38
+ specObject.padding ??= 10;
49
39
 
50
40
  if (element == document.body) {
51
41
  // Need to add a wrapper to make sizing behavior more stable
@@ -74,10 +64,6 @@ export async function embed(el, spec, options = {}) {
74
64
  }
75
65
  },
76
66
 
77
- /**
78
- * @param {string} type
79
- * @param {(event: any) => void} listener
80
- */
81
67
  addEventListener(type, listener) {
82
68
  const listenersByType = genomeSpy._eventListeners;
83
69
 
@@ -90,20 +76,12 @@ export async function embed(el, spec, options = {}) {
90
76
  listeners.add(listener);
91
77
  },
92
78
 
93
- /**
94
- * @param {string} type
95
- * @param {(event: any) => void} listener
96
- */
97
79
  removeEventListener(type, listener) {
98
80
  const listenersByType = genomeSpy._eventListeners;
99
81
 
100
82
  listenersByType.get(type)?.delete(listener);
101
83
  },
102
84
 
103
- /**
104
- * @param {string} name
105
- * @returns {import("./view/scaleResolutionApi").ScaleResolutionApi}
106
- */
107
85
  getScaleResolutionByName(name) {
108
86
  return genomeSpy.getNamedScaleResolutions().get(name);
109
87
  },
@@ -113,7 +91,7 @@ export async function embed(el, spec, options = {}) {
113
91
  /**
114
92
  *
115
93
  * @param {import("./genomeSpy").default} genomeSpy
116
- * @param {import("./options.js").EmbedOptions} options options
94
+ * @param {import("./embedApi.js").EmbedOptions} options options
117
95
  */
118
96
  function applyOptions(genomeSpy, options) {
119
97
  if (options.namedDataProvider) {
package/src/marks/link.js CHANGED
@@ -20,9 +20,7 @@ export default class LinkMark extends Mark {
20
20
  y: 0.0,
21
21
  y2: undefined,
22
22
  size: 1.0,
23
- size2: undefined,
24
23
  color: "black",
25
- color2: undefined,
26
24
  opacity: 1.0,
27
25
 
28
26
  segments: 101, // Performance is affected more by the fill rate, i.e. number of pixels
@@ -41,24 +39,15 @@ export default class LinkMark extends Mark {
41
39
  "y",
42
40
  "y2",
43
41
  "size",
44
- "size2",
45
42
  "height",
46
43
  "color",
47
- "color2",
48
44
  "opacity",
49
45
  ];
50
46
  }
51
47
 
52
48
  /** @return {import("../spec/channel").Channel[]} */
53
49
  getSupportedChannels() {
54
- return [
55
- ...super.getSupportedChannels(),
56
- "x2",
57
- "y2",
58
- "size",
59
- "size2",
60
- "color2",
61
- ];
50
+ return [...super.getSupportedChannels(), "x2", "y2", "size"];
62
51
  }
63
52
 
64
53
  /**
package/src/marks/mark.js CHANGED
@@ -11,6 +11,7 @@ import { isDiscrete } from "vega-scale";
11
11
  import { fp64ify } from "../gl/includes/fp64-utils";
12
12
  import createEncoders, {
13
13
  isChannelDefWithScale,
14
+ isDatumDef,
14
15
  isValueDef,
15
16
  } from "../encoder/encoder";
16
17
  import {
@@ -18,6 +19,7 @@ import {
18
19
  generateValueGlsl,
19
20
  generateScaleGlsl,
20
21
  RANGE_TEXTURE_PREFIX,
22
+ ATTRIBUTE_PREFIX,
21
23
  } from "../scale/glslScaleGenerator";
22
24
  import FP64 from "../gl/includes/fp64-arithmetic.glsl";
23
25
  import GLSL_COMMON from "../gl/includes/common.glsl";
@@ -29,6 +31,7 @@ import GLSL_PICKING_FRAGMENT from "../gl/includes/picking.fragment.glsl";
29
31
  import { getCachedOrCall } from "../utils/propertyCacher";
30
32
  import { createProgram } from "../gl/webGLHelper";
31
33
  import coalesceProperties from "../utils/propertyCoalescer";
34
+ import { isScalar } from "../utils/variableTools";
32
35
 
33
36
  export const SAMPLE_FACET_UNIFORM = "SAMPLE_FACET_UNIFORM";
34
37
  export const SAMPLE_FACET_TEXTURE = "SAMPLE_FACET_TEXTURE";
@@ -159,8 +162,6 @@ export default class Mark {
159
162
  if (this.isPickingParticipant()) {
160
163
  encoding.uniqueId = {
161
164
  field: "_uniqueId", // TODO: Use constant
162
- type: "nominal",
163
- scale: null,
164
165
  };
165
166
  }
166
167
 
@@ -191,9 +192,7 @@ export default class Mark {
191
192
  const propToValueDef = (property) => {
192
193
  const value =
193
194
  this.properties[/** @type {keyof MarkConfig} */ (property)];
194
- // TODO: Use isScalar ... but... tooltip breaks if type guard is used
195
- return { value };
196
- //}
195
+ return isScalar(value) && { value };
197
196
  };
198
197
 
199
198
  const propertyValues = Object.fromEntries(
@@ -401,6 +400,28 @@ export default class Mark {
401
400
  "Domains"
402
401
  );
403
402
  }
403
+
404
+ this.gl.useProgram(this.programInfo.program);
405
+
406
+ this._setDatums();
407
+ }
408
+
409
+ _setDatums() {
410
+ for (const [channel, channelDef] of Object.entries(this.encoding)) {
411
+ if (isDatumDef(channelDef)) {
412
+ const encoder = this.encoders[channel];
413
+
414
+ const datum = encoder.indexer
415
+ ? encoder.indexer(channelDef.datum)
416
+ : encoder.scale.fp64
417
+ ? fp64ify(+channelDef.datum)
418
+ : +channelDef.datum;
419
+
420
+ setUniforms(this.programInfo, {
421
+ [ATTRIBUTE_PREFIX + channel]: datum,
422
+ });
423
+ }
424
+ }
404
425
  }
405
426
 
406
427
  /**
@@ -656,6 +677,7 @@ export default class Mark {
656
677
  // inferior performance. Based on profiling, this optimization gives
657
678
  // a significant performance boost.
658
679
  this.gl.uniform4f(
680
+ // @ts-expect-error
659
681
  locationSetter.location, // TODO: Make a twgl pull request to fix typing
660
682
  pos,
661
683
  height,
@@ -1,4 +1,8 @@
1
- import { isValueDef, getSecondaryChannel } from "../encoder/encoder";
1
+ import {
2
+ isValueDef,
3
+ getSecondaryChannel,
4
+ isChannelDefWithScale,
5
+ } from "../encoder/encoder";
2
6
 
3
7
  /**
4
8
  *
@@ -8,46 +12,58 @@ import { isValueDef, getSecondaryChannel } from "../encoder/encoder";
8
12
 
9
13
  /**
10
14
  * @param {Encoding} encoding
11
- * @param {Channel} channel
15
+ * @param {import("../spec/channel").PrimaryPositionalChannel} channel
12
16
  */
13
17
  export function fixPositional(encoding, channel) {
14
- const secondary = getSecondaryChannel(channel);
15
- if (encoding[channel]) {
16
- if (!encoding[secondary]) {
17
- if (encoding[channel].type == "quantitative") {
18
+ const secondaryChannel = getSecondaryChannel(channel);
19
+
20
+ // Must make copies because the definition may be shared with other views/marks
21
+ let primary = encoding[channel] && { ...encoding[channel] };
22
+ let secondary = encoding[secondaryChannel] && {
23
+ ...encoding[secondaryChannel],
24
+ };
25
+
26
+ if (isValueDef(primary) || isValueDef(secondary)) {
27
+ return;
28
+ }
29
+
30
+ if (primary) {
31
+ // TODO: fix. May not be a proper type guard.
32
+ if (!isChannelDefWithScale(encoding[channel])) {
33
+ // nop
34
+ return;
35
+ }
36
+
37
+ if (!secondary) {
38
+ if (primary.type == "quantitative") {
18
39
  // Bar plot, anchor the other end to zero
19
- encoding[secondary] = {
20
- datum: 0,
21
- };
40
+ secondary = { datum: 0 };
22
41
  } else {
23
- // Must make copies because the definition may be shared with other views/marks
24
- encoding[channel] = { ...encoding[channel] };
25
- encoding[secondary] = { ...encoding[channel] };
42
+ secondary = { ...primary };
26
43
 
27
44
  // Fill the bands (bar plot / heatmap)
28
45
  // We are following the Vega-Lite convention:
29
46
  // the band property works differently on rectangular marks, i.e., it adjusts the band coverage.
30
- const adjustment = (1 - (encoding[channel].band || 1)) / 2;
31
- encoding[channel].band = 0 + adjustment;
32
- encoding[secondary].band = 1 - adjustment;
47
+ const adjustment = (1 - (primary.band ?? 1)) / 2;
48
+ primary.band = 0 + adjustment;
49
+ secondary.band = 1 - adjustment;
33
50
 
34
51
  // TODO: If the secondary channel duplicates the primary channel
35
52
  // the data should be uploaded to the GPU only once.
36
53
  }
37
- } else if (encoding[channel].type != "quantitative") {
38
- const adjustment = (1 - (encoding[channel].band || 1)) / 2;
39
- encoding[channel].band = adjustment;
40
- encoding[secondary].band = -adjustment;
54
+ } else if (primary.type != "quantitative") {
55
+ const adjustment = (1 - (primary.band || 1)) / 2;
56
+ primary.band = adjustment;
57
+ secondary.band = -adjustment;
41
58
  }
42
- } else if (encoding[secondary]) {
43
- throw new Error(
44
- `Only secondary channel ${secondary} has been specified!`
45
- );
46
59
  } else {
47
60
  // Nothing specified, fill the whole viewport
48
- encoding[channel] = { value: 0 };
49
- encoding[secondary] = { value: 1 };
61
+ primary = { value: 0 };
62
+ secondary = { value: 1 };
50
63
  }
64
+
65
+ encoding[channel] = primary;
66
+ encoding[secondaryChannel] = secondary;
51
67
  }
52
68
 
53
69
  /**
@@ -178,7 +178,9 @@ export default class PointMark extends Mark {
178
178
  if (e.constant) {
179
179
  return e(null);
180
180
  } else {
181
- return e.scale.range().reduce((a, b) => Math.max(a, b));
181
+ return /** @type {number[]} */ (e.scale.range()).reduce((a, b) =>
182
+ Math.max(a, b)
183
+ );
182
184
  }
183
185
  }
184
186
 
@@ -196,7 +198,8 @@ export default class PointMark extends Mark {
196
198
  } else if (p >= 1) {
197
199
  return Infinity;
198
200
  } else {
199
- return quantileSorted(this.sampledSemanticScores, p);
201
+ const scores = /** @type {any} */ (this.sampledSemanticScores);
202
+ return quantileSorted(/** @type {number[]} */ (scores), p);
200
203
  }
201
204
  } else {
202
205
  return -1;
package/src/marks/rule.js CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  import VERTEX_SHADER from "../gl/rule.vertex.glsl";
9
9
  import FRAGMENT_SHADER from "../gl/rule.fragment.glsl";
10
10
  import { RuleVertexBuilder } from "../gl/dataToVertices";
11
+ import { isChannelDefWithScale } from "../encoder/encoder.js";
11
12
 
12
13
  export default class RuleMark extends Mark {
13
14
  /**
@@ -89,10 +90,18 @@ export default class RuleMark extends Mark {
89
90
  // Limited horizontal rule
90
91
  encoding.y2 = encoding.y;
91
92
  } else if (encoding.y && encoding.x) {
92
- if (!encoding.x2 && encoding.y.type == "quantitative") {
93
+ if (
94
+ !encoding.x2 &&
95
+ isChannelDefWithScale(encoding.y) &&
96
+ encoding.y.type == "quantitative"
97
+ ) {
93
98
  encoding.x2 = encoding.x;
94
99
  encoding.y2 = { datum: 0 };
95
- } else if (!encoding.y2 && encoding.x.type == "quantitative") {
100
+ } else if (
101
+ !encoding.y2 &&
102
+ isChannelDefWithScale(encoding.x) &&
103
+ encoding.x.type == "quantitative"
104
+ ) {
96
105
  encoding.y2 = encoding.y;
97
106
  encoding.x2 = { datum: 0 };
98
107
  } else {