@genome-spy/core 0.14.2 → 0.18.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 (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
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- .genome-spy{font-family:system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol";position:relative}.genome-spy canvas{transform:scale(1);opacity:1;transition:transform .6s,opacity .6s}.genome-spy .loading-message{position:absolute;top:0;bottom:0;left:0;right:0;display:flex;align-items:center;justify-content:center}.genome-spy .loading-message .message{color:#666;opacity:0;transition:opacity .7s}.genome-spy.loading canvas{transform:scale(.95);opacity:0}.genome-spy.loading .loading-message .message{opacity:1}.genome-spy.loading .ellipsis{animation:blinker 1s linear infinite}@keyframes blinker{50%{opacity:0}}.genome-spy .tooltip{position:absolute;max-width:450px;overflow:hidden;background:#f6f6f6;padding:10px;font-size:13px;box-shadow:0 3px 15px #00000036;pointer-events:none;z-index:100}.genome-spy .tooltip>:last-child{margin-bottom:0}.genome-spy .tooltip>.title{padding-bottom:5px;margin-bottom:5px;border-bottom:1px dashed #b6b6b6}.genome-spy .tooltip .summary{font-size:12px}.genome-spy .tooltip table{border-collapse:collapse}.genome-spy .tooltip table:first-child{margin-top:0}.genome-spy .tooltip table th,.genome-spy .tooltip table td{padding:2px .4em;vertical-align:top}.genome-spy .tooltip table th:first-child,.genome-spy .tooltip table td:first-child{padding-left:0}.genome-spy .tooltip table th{text-align:left;font-weight:bold}.genome-spy .tooltip .color-legend{display:inline-block;width:.8em;height:.8em;margin-left:.4em;box-shadow:0 0 3px 1px #fff}.genome-spy .tooltip .attributes .hovered{background-color:#e0e0e0}.genome-spy .tooltip .na{color:#aaa;font-style:italic;font-size:80%}.genome-spy .gene-track-tooltip .summary{font-size:90%}.genome-spy .message-box{display:flex;align-items:center;justify-content:center;position:absolute;top:0;height:100%;width:100%}.genome-spy .message-box>div{border:1px solid red;padding:10px;background:#fff0f0}
1
+ .genome-spy{font-family:system-ui,Segoe UI,Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol;position:relative}.genome-spy canvas{transform:scale(1);opacity:1;transition:transform .6s,opacity .6s}.genome-spy .loading-message{position:absolute;top:0;bottom:0;left:0;right:0;display:flex;align-items:center;justify-content:center}.genome-spy .loading-message .message{color:#666;opacity:0;transition:opacity .7s}.genome-spy.loading canvas{transform:scale(.95);opacity:0}.genome-spy.loading .loading-message .message{opacity:1}.genome-spy.loading .ellipsis{animation:blinker 1s linear infinite}@keyframes blinker{50%{opacity:0}}.genome-spy .tooltip{position:absolute;max-width:450px;overflow:hidden;background:#f6f6f6;padding:10px;font-size:13px;box-shadow:0 3px 15px #00000036;pointer-events:none;z-index:100}.genome-spy .tooltip>:last-child{margin-bottom:0}.genome-spy .tooltip>.title{padding-bottom:5px;margin-bottom:5px;border-bottom:1px dashed #b6b6b6}.genome-spy .tooltip .summary{font-size:12px}.genome-spy .tooltip table{border-collapse:collapse}.genome-spy .tooltip table:first-child{margin-top:0}.genome-spy .tooltip table th,.genome-spy .tooltip table td{padding:2px .4em;vertical-align:top}.genome-spy .tooltip table th:first-child,.genome-spy .tooltip table td:first-child{padding-left:0}.genome-spy .tooltip table th{text-align:left;font-weight:700}.genome-spy .tooltip .color-legend{display:inline-block;width:.8em;height:.8em;margin-left:.4em;box-shadow:0 0 3px 1px #fff}.genome-spy .tooltip .attributes .hovered{background-color:#e0e0e0}.genome-spy .tooltip .na{color:#aaa;font-style:italic;font-size:80%}.genome-spy .gene-track-tooltip .summary{font-size:90%}.genome-spy .message-box{display:flex;align-items:center;justify-content:center;position:absolute;top:0;height:100%;width:100%}.genome-spy .message-box>div{border:1px solid red;padding:10px;background:#fff0f0}
package/package.json CHANGED
@@ -7,12 +7,13 @@
7
7
  },
8
8
  "contributors": [],
9
9
  "license": "BSD-2-Clause",
10
- "version": "0.14.2",
10
+ "version": "0.18.0",
11
11
  "main": "dist/index.js",
12
12
  "module": "src/index.js",
13
13
  "exports": {
14
14
  ".": "./src/index.js",
15
- "./*": "./src/*"
15
+ "./*": "./src/*",
16
+ "./schema.json": "./dist/schema.json"
16
17
  },
17
18
  "files": [
18
19
  "dist/",
@@ -20,15 +21,16 @@
20
21
  ],
21
22
  "repository": {
22
23
  "type": "git",
23
- "url": "github:tuner/genome-spy",
24
+ "url": "github:genome-spy/genome-spy",
24
25
  "directory": "packages/core"
25
26
  },
26
27
  "scripts": {
27
28
  "dev": "node dev-server.js",
28
- "build": "vite build",
29
- "prepublishOnly": "npm run build && npm run build:schema",
29
+ "build": "vite build && npm run build:schema",
30
+ "prepublishOnly": "npm run build",
31
+ "test:tsc": "tsc -p tsconfig.json",
30
32
  "checkSpec": "tsc --allowJs --checkJs --strict --noEmit --moduleResolution node --target es6 src/spec/root.d.ts",
31
- "build:schema": "mkdir -p dist && ts-json-schema-generator --path 'src/spec/*.ts' --type ViewSpec > dist/genome-spy-schema.json"
33
+ "build:schema": "mkdir -p dist && ts-json-schema-generator --path 'src/spec/*.ts' --type RootSpec > dist/schema.json"
32
34
  },
33
35
  "dependencies": {
34
36
  "@types/d3-array": "^3.0.2",
@@ -50,5 +52,5 @@
50
52
  "vega-scale": "^7.1.1",
51
53
  "vega-util": "^1.16.0"
52
54
  },
53
- "gitHead": "bd13241dba46e380539bc40ac7bef65eced23fc0"
55
+ "gitHead": "ae445fc90ed7640ad9c3b39e81560d81126ebbeb"
54
56
  }
@@ -8,8 +8,9 @@
8
8
  export function getFormat(params) {
9
9
  const format = { ...params.format };
10
10
 
11
- format.type = format.type || extractTypeFromUrl(params.url);
12
- format.parse = format.parse || "auto";
11
+ format.type ??= isUrlData(params) && extractTypeFromUrl(params.url);
12
+ // @ts-expect-error
13
+ format.parse ??= "auto";
13
14
 
14
15
  if (!format.type) {
15
16
  throw new Error(
@@ -22,10 +23,56 @@ export function getFormat(params) {
22
23
  }
23
24
 
24
25
  /**
25
- * @param {string} url
26
+ * @param {string | string[]} url
26
27
  */
27
28
  export function extractTypeFromUrl(url) {
29
+ if (Array.isArray(url)) {
30
+ url = url[0];
31
+ }
32
+
28
33
  if (url) {
29
34
  return url.match(/\.(csv|tsv|json)/)?.[1];
30
35
  }
31
36
  }
37
+
38
+ export const makeWrapper = (/** @type {any} */ d) =>
39
+ typeof d != "object" ? scalarWrapper : nopWrapper;
40
+
41
+ const scalarWrapper = (
42
+ /** @type {import("@genome-spy/core/spec/channel").Scalar} */ x
43
+ ) => ({ data: x });
44
+
45
+ const nopWrapper = (/** @type {import("../flowNode").Datum} */ x) => x;
46
+
47
+ /**
48
+ * @param {import("../../spec/data").DataFormat} dataFormat
49
+ * @return {dataFormat is import("../../spec/data").CsvDataFormat}
50
+ */
51
+ export function isCsvDataFormat(dataFormat) {
52
+ return dataFormat.type == "csv" || dataFormat.type == "tsv";
53
+ }
54
+
55
+ /**
56
+ * @param {import("../../spec/data").DataFormat} dataFormat
57
+ * @return {dataFormat is import("../../spec/data").DsvDataFormat}
58
+ */
59
+ export function isDsvDataFormat(dataFormat) {
60
+ return dataFormat.type == "dsv";
61
+ }
62
+
63
+ /**
64
+ * @param {import("../../spec/data").DataFormat} dataFormat
65
+ * @return {dataFormat is import("../../spec/data").JsonDataFormat}
66
+ */
67
+ export function isJsonDataFormat(dataFormat) {
68
+ return dataFormat.type == "json";
69
+ }
70
+
71
+ /**
72
+ *
73
+ * @param {import("../../spec/data").DataSource} dataSource
74
+ * @return {dataSource is import("../../spec/data").UrlData}
75
+ */
76
+ export function isUrlData(dataSource) {
77
+ return "url" in dataSource;
78
+ }
@@ -1,4 +1,5 @@
1
1
  import DataSource from "./dataSource";
2
+ import { makeWrapper } from "./dataUtils";
2
3
 
3
4
  /**
4
5
  * @param {Partial<import("../../spec/data").Data>} data
@@ -41,7 +42,7 @@ export default class DynamicCallbackSource extends DataSource {
41
42
 
42
43
  for (const d of iterable) {
43
44
  if (!wrap) {
44
- wrap = typeof d != "object" ? (x) => ({ data: x }) : (x) => x;
45
+ wrap = makeWrapper(d);
45
46
  }
46
47
 
47
48
  this._propagate(wrap(d));
@@ -1,4 +1,5 @@
1
1
  import DataSource from "./dataSource";
2
+ import { makeWrapper } from "./dataUtils";
2
3
 
3
4
  /**
4
5
  * @param {Partial<import("../../spec/data").Data>} data
@@ -21,7 +22,7 @@ export default class DynamicSource extends DataSource {
21
22
 
22
23
  for (const d of iterable) {
23
24
  if (!wrap) {
24
- wrap = typeof d != "object" ? (x) => ({ data: x }) : (x) => x;
25
+ wrap = makeWrapper(d);
25
26
  }
26
27
 
27
28
  this._propagate(wrap(d));
@@ -1,5 +1,5 @@
1
1
  import { read } from "vega-loader";
2
- import { getFormat } from "./dataUtils";
2
+ import { getFormat, makeWrapper } from "./dataUtils";
3
3
  import DataSource from "./dataSource";
4
4
 
5
5
  /**
@@ -31,16 +31,14 @@ export default class InlineSource extends DataSource {
31
31
 
32
32
  let data = [];
33
33
 
34
+ /** @type {(x: any) => import("../flowNode").Datum} */
34
35
  let wrap = (x) => x;
35
36
 
36
37
  if (Array.isArray(values)) {
37
38
  if (values.length > 0) {
38
39
  data = values;
39
40
  // TODO: Should check the whole array and abort if types are heterogeneous
40
- if (typeof values[0] != "object") {
41
- // Wrap scalars to objects
42
- wrap = (d) => ({ data: d });
43
- }
41
+ wrap = makeWrapper(values[0]);
44
42
  }
45
43
  } else if (typeof values == "object") {
46
44
  data = [values];
@@ -1,4 +1,5 @@
1
1
  import DataSource from "./dataSource";
2
+ import { makeWrapper } from "./dataUtils";
2
3
 
3
4
  /**
4
5
  * @param {Partial<import("../../spec/data").Data>} data
@@ -39,18 +40,13 @@ export default class NamedSource extends DataSource {
39
40
  loadSynchronously() {
40
41
  const data = this._getValues();
41
42
 
42
- /**
43
- * @param {any} x
44
- */
43
+ /** @type {(x: any) => import("../flowNode").Datum} */
45
44
  let wrap = (x) => x;
46
45
 
47
46
  if (Array.isArray(data)) {
48
47
  if (data.length > 0) {
49
48
  // TODO: Should check the whole array and abort if types are heterogeneous
50
- if (typeof data[0] != "object") {
51
- // Wrap scalars to objects
52
- wrap = (d) => ({ data: d });
53
- }
49
+ wrap = makeWrapper(data[0]);
54
50
  }
55
51
  } else {
56
52
  throw new Error(
@@ -40,7 +40,7 @@ export default class UrlSource extends DataSource {
40
40
  baseURL: this.baseUrl,
41
41
  })
42
42
  .load(url)
43
- .catch((e) => {
43
+ .catch((/** @type {Error} */ e) => {
44
44
  // TODO: Include baseurl in the error message. Should be normalized, however.
45
45
  throw new Error(
46
46
  `Cannot fetch: ${this.baseUrl}${url}: ${e.message}`
@@ -49,6 +49,7 @@ export default class AggregateTransform extends FlowNode {
49
49
  const groupFieldAccessors = groupby.map((f) => field(f));
50
50
 
51
51
  // TODO: Fix case where no group fields are specified
52
+ // @ts-expect-error
52
53
  const groups = d3group(this.buffer, ...groupFieldAccessors);
53
54
 
54
55
  for (const [group, data] of iterateNestedMaps(groups)) {
@@ -47,6 +47,7 @@ export default class FlattenDelimitedTransform extends FlowNode {
47
47
  const flatLen = splitFields[0].length;
48
48
 
49
49
  for (let ri = 0; ri < flatLen; ri++) {
50
+ /** @type {import("../flowNode").Datum} */
50
51
  const newRow = Object.assign({}, datum);
51
52
  for (let fi = 0; fi < accessors.length; fi++) {
52
53
  newRow[as[fi]] = splitFields[fi][ri];
@@ -57,6 +58,11 @@ export default class FlattenDelimitedTransform extends FlowNode {
57
58
  }
58
59
  }
59
60
 
61
+ /**
62
+ *
63
+ * @param {any[]} splitFields
64
+ * @param {unknown} row
65
+ */
60
66
  function validateSplit(splitFields, row) {
61
67
  const splitLengths = splitFields.map((f) => f.length);
62
68
  if (!splitLengths.every((x) => x == splitLengths[0])) {
@@ -48,7 +48,7 @@ export default class RegexFoldTransform extends FlowNode {
48
48
  * @param {any} datum
49
49
  */
50
50
  const detectColumns = (datum) => {
51
- const colNames = Object.keys(datum);
51
+ const colNames = /** @type {string[]} */ (Object.keys(datum));
52
52
 
53
53
  /** @type {Map<string, string[]>} */
54
54
  const sampleColMap = new Map();
@@ -43,7 +43,7 @@ export default class StackTransform extends FlowNode {
43
43
  ? compare(params.sort.field, params.sort.order)
44
44
  : undefined;
45
45
 
46
- const valueAccessor = params.field ? field(params.field) : (d) => 1;
46
+ const valueAccessor = params.field ? field(params.field) : () => 1;
47
47
 
48
48
  const groupFields = params.groupby.map((f) => field(f));
49
49
 
@@ -52,7 +52,7 @@ export default class StackTransform extends FlowNode {
52
52
  ).map((a) => a[1]);
53
53
 
54
54
  /** @type {(datum: any) => boolean} */
55
- let inclusionPredicate = (datum) => true;
55
+ let inclusionPredicate = (_datum) => true;
56
56
 
57
57
  if (params.baseField) {
58
58
  const baseAccessor = field(params.baseField);
@@ -108,7 +108,7 @@ export default class StackTransform extends FlowNode {
108
108
  break;
109
109
  default:
110
110
  offsetF = (value, sum) => value;
111
- sumF = (values, accessor) => 1;
111
+ sumF = (_values, _accessor) => 1;
112
112
  }
113
113
 
114
114
  for (const group of groups) {
@@ -0,0 +1,59 @@
1
+ import { ScaleResolutionApi } from "./view/scaleResolutionApi";
2
+ import { TooltipHandler } from "./tooltip/tooltipHandler";
3
+ import { RootSpec } from "./spec/root";
4
+
5
+ /**
6
+ * Embeds GenomeSpy into the DOM
7
+ *
8
+ * @param el HTMLElement or a query selector
9
+ * @param spec A spec object or an URL to a JSON spec
10
+ * @param options Options
11
+ */
12
+ export type EmbedFunction = (
13
+ el: HTMLElement | string,
14
+ spec: RootSpec | string,
15
+ options?: EmbedOptions
16
+ ) => EmbedResult;
17
+
18
+ export interface EmbedOptions {
19
+ /**
20
+ * A function that allows retrieval of named data sources.
21
+ *
22
+ * TODO: Support dynamic updates, i.e., pushing new data.
23
+ */
24
+ namedDataProvider?: (name: string) => any[];
25
+
26
+ /**
27
+ * Custom tooltip handlers. Use `"default"` to override the default handler
28
+ */
29
+ tooltipHandlers?: Record<string, TooltipHandler>;
30
+ }
31
+
32
+ /**
33
+ * An API for controlling the embedded GenomeSpy instance.
34
+ */
35
+ export interface EmbedResult {
36
+ /**
37
+ * Releases all resources and unregisters event listeners, etc.
38
+ */
39
+ finalize: () => void;
40
+
41
+ /**
42
+ * Adds an event listener, which is called when the user interacts with a mark
43
+ * instance. Currently, only `"click"` events are supported. The callback receives
44
+ * an event object as its first (and only) parameter. Its `datum` property
45
+ * contains the datum that the user interacted with.
46
+ */
47
+ addEventListener: (type: string, listener: (event: any) => void) => void;
48
+
49
+ /**
50
+ * Removes a registered event listener.
51
+ */
52
+ removeEventListener: (type: string, listener: (event: any) => void) => void;
53
+
54
+ /**
55
+ * Returns a named _ScaleResolution_ object that allows for attaching event
56
+ * listeners and controlling the scale domain.
57
+ */
58
+ getScaleResolutionByName: (name: string) => ScaleResolutionApi;
59
+ }
@@ -21,8 +21,9 @@ export default class AccessorFactory {
21
21
  this.register((channelDef) => {
22
22
  if (isFieldDef(channelDef)) {
23
23
  try {
24
- /** @type {Accessor} */
25
- const accessor = field(channelDef.field);
24
+ const accessor = /** @type {Accessor} */ (
25
+ field(channelDef.field)
26
+ );
26
27
  accessor.constant = false;
27
28
  accessor.fields = accessorFields(accessor);
28
29
  return accessor;
@@ -40,8 +41,8 @@ export default class AccessorFactory {
40
41
 
41
42
  this.register((channelDef) => {
42
43
  if (isDatumDef(channelDef)) {
43
- /** @type {Accessor} */
44
- const accessor = constant(channelDef.datum);
44
+ const c = /** @type {any} */ (constant(channelDef.datum));
45
+ const accessor = /** @type {Accessor} */ (c);
45
46
  accessor.constant = true; // Can be optimized downstream
46
47
  accessor.fields = [];
47
48
  return accessor;
@@ -75,8 +76,7 @@ export default class AccessorFactory {
75
76
  * @param {string} expr
76
77
  */
77
78
  function createExpressionAccessor(expr) {
78
- /** @type {Accessor} */
79
- const accessor = createFunction(expr);
79
+ const accessor = /** @type {Accessor} */ (createFunction(expr));
80
80
  accessor.constant = accessor.fields.length == 0; // Not bulletproof, eh
81
81
  return accessor;
82
82
  }
@@ -5,7 +5,7 @@ import createIndexer from "../utils/indexer";
5
5
  * @typedef {Object} EncoderMetadata
6
6
  * @prop {boolean} constant True if the accessor returns the same value for all objects
7
7
  * @prop {boolean} constantValue True the encoder returns a "value" without a scale
8
- * @prop {function} invert
8
+ * @prop {(value: import("../spec/channel").Scalar) => import("../spec/channel").Scalar} invert
9
9
  * @prop {VegaScale} [scale]
10
10
  * @prop {import("./accessor").Accessor} accessor
11
11
  * @prop {function(any):number} [indexer] Converts ordinal values to index numbers
@@ -17,7 +17,7 @@ import createIndexer from "../utils/indexer";
17
17
  *
18
18
  * @typedef {object} ScaleMetadata
19
19
  * @prop {string} type Scale type
20
- * @prop {boolean} fp64 Whether to use emulated 64 bit floating point in WebGL
20
+ * @prop {boolean} [fp64] Whether to use emulated 64 bit floating point in WebGL
21
21
  *
22
22
  * @typedef {(
23
23
  import("d3-scale").ScaleContinuousNumeric<any, any> |
@@ -37,7 +37,7 @@ import createIndexer from "../utils/indexer";
37
37
  import("d3-scale").ScalePoint<any>
38
38
  )} D3Scale
39
39
  *
40
- * @typedef {D3Scale & ScaleMetadata} VegaScale
40
+ * @typedef {(D3Scale | import("../genome/scaleIndex").ScaleIndex | import("../genome/scaleLocus").ScaleLocus) & ScaleMetadata} VegaScale
41
41
  *
42
42
  * @typedef {import("../spec/channel").Channel} Channel
43
43
  */
@@ -85,7 +85,7 @@ export default function createEncoders(mark, encoding) {
85
85
 
86
86
  /**
87
87
  *
88
- * @param {import("../view/viewUtils").ChannelDef} channelDef
88
+ * @param {import("../spec/channel").ChannelDef} channelDef
89
89
  * @param {any} scale
90
90
  * @param {import("./accessor").Accessor} accessor
91
91
  * @param {Channel} channel
@@ -96,7 +96,8 @@ export function createEncoder(channelDef, scale, accessor, channel) {
96
96
  let encoder;
97
97
 
98
98
  if (isValueDef(channelDef)) {
99
- encoder = /** @type {Encoder} */ ((datum) => channelDef.value);
99
+ const value = channelDef.value;
100
+ encoder = /** @type {Encoder} */ ((datum) => value);
100
101
  encoder.constant = true;
101
102
  encoder.constantValue = true;
102
103
  encoder.accessor = undefined;
@@ -121,7 +122,7 @@ export function createEncoder(channelDef, scale, accessor, channel) {
121
122
  // TODO: pass the found values back to the scale/resolution
122
123
  const indexer = createIndexer();
123
124
  indexer.addAll(scale.domain());
124
- encoder.indexer = (d) => indexer(accessor(d));
125
+ encoder.indexer = indexer;
125
126
  }
126
127
 
127
128
  encoder.constant = accessor.constant;
@@ -153,6 +154,7 @@ export function createEncoder(channelDef, scale, accessor, channel) {
153
154
  encoder.applyMetadata = (target) => {
154
155
  for (const prop in encoder) {
155
156
  if (prop in encoder) {
157
+ // @ts-ignore
156
158
  target[prop] = encoder[prop];
157
159
  }
158
160
  }
@@ -174,7 +176,7 @@ export function isValueDef(channelDef) {
174
176
 
175
177
  /**
176
178
  * @param {import("../spec/channel").ChannelDef} channelDef
177
- * @returns {channelDef is import("../spec/channel").FieldDef}
179
+ * @returns {channelDef is import("../spec/channel").FieldDefBase<string>}
178
180
  */
179
181
  export function isFieldDef(channelDef) {
180
182
  return channelDef && "field" in channelDef;
@@ -193,6 +195,7 @@ export function isDatumDef(channelDef) {
193
195
  * @returns {channelDef is import("../spec/channel").ChannelDefWithScale}
194
196
  */
195
197
  export function isChannelDefWithScale(channelDef) {
198
+ // TODO: Not accurate, fix
196
199
  return (
197
200
  isFieldDef(channelDef) ||
198
201
  isDatumDef(channelDef) ||
@@ -231,26 +234,55 @@ export function isExprDef(channelDef) {
231
234
  }
232
235
 
233
236
  /**
234
- * @type {Channel[]}
237
+ * @type {import("../spec/channel").PrimaryPositionalChannel[]}
235
238
  */
236
239
  export const primaryPositionalChannels = ["x", "y"];
237
240
 
241
+ /**
242
+ * @type {import("../spec/channel").SecondaryPositionalChannel[]}
243
+ */
244
+ export const secondaryPositionalChannels = ["x2", "y2"];
245
+
246
+ /**
247
+ * @type {import("../spec/channel").PositionalChannel[]}
248
+ */
249
+ export const positionalChannels = [
250
+ ...primaryPositionalChannels,
251
+ ...secondaryPositionalChannels,
252
+ ];
253
+
254
+ /**
255
+ * @param {Channel} channel
256
+ * @returns {channel is import("../spec/channel").PrimaryPositionalChannel}
257
+ */
258
+ export function isPrimaryPositionalChannel(channel) {
259
+ // @ts-expect-error
260
+ return primaryPositionalChannels.includes(channel);
261
+ }
262
+
263
+ /**
264
+ * @param {Channel} channel
265
+ * @returns {channel is import("../spec/channel").PositionalChannel}
266
+ */
267
+ export function isPositionalChannel(channel) {
268
+ // @ts-expect-error
269
+ return positionalChannels.includes(channel);
270
+ }
271
+
238
272
  /**
239
273
  * Map primary channels to secondarys
240
274
  *
241
- * @type {Partial<Record<Channel, Channel>>}
275
+ * @type {Partial<Record<Channel, import("../spec/channel").SecondaryPositionalChannel>>}
242
276
  */
243
277
  export const secondaryChannels = {
244
278
  x: "x2",
245
279
  y: "y2",
246
- size: "size2",
247
- color: "color2",
248
280
  };
249
281
 
250
282
  /**
251
283
  * Map secondary channels to primaries
252
284
  *
253
- * @type {Record<Channel, Channel>}
285
+ * @type {Partial<Record<Channel, Channel>>}
254
286
  */
255
287
  export const primaryChannels = Object.fromEntries(
256
288
  Object.entries(secondaryChannels).map((entry) => [entry[1], entry[0]])
@@ -282,10 +314,10 @@ export function getSecondaryChannel(primaryChannel) {
282
314
  * Finds the primary channel for the provided channel, which may be
283
315
  * the primary or secondary.
284
316
  *
285
- * @param {Channel} maybeSecondary
317
+ * @param {Channel} channel
286
318
  */
287
- export function getPrimaryChannel(maybeSecondary) {
288
- return primaryChannels[maybeSecondary] || maybeSecondary;
319
+ export function getPrimaryChannel(channel) {
320
+ return primaryChannels[channel] ?? channel;
289
321
  }
290
322
 
291
323
  /**
@@ -299,13 +331,6 @@ export function getChannelWithSecondarys(channel) {
299
331
  : [channel];
300
332
  }
301
333
 
302
- /**
303
- * @param {Channel} channel
304
- */
305
- export function isPositionalChannel(channel) {
306
- return primaryPositionalChannels.includes(getPrimaryChannel(channel));
307
- }
308
-
309
334
  /**
310
335
  * @param {Channel} channel
311
336
  */
@@ -0,0 +1,38 @@
1
+ export default function scaleIndex(): ScaleIndex;
2
+
3
+ export interface ScaleIndex {
4
+ (value: number): number;
5
+
6
+ invert(x: number): number;
7
+
8
+ domain(): number[];
9
+ domain(_: Iterable<number>): this;
10
+
11
+ range(): number[];
12
+ range(_: Iterable<number>): this;
13
+
14
+ numberingOffset(): number;
15
+ numberingOffset(_: number): this;
16
+
17
+ padding(): number;
18
+ padding(_: number): this;
19
+
20
+ paddingInner(): number;
21
+ paddingInner(_: number): this;
22
+
23
+ paddingOuter(): number;
24
+ paddingOuter(_: number): this;
25
+
26
+ align(): number;
27
+ align(_: number): this;
28
+
29
+ step(): number;
30
+
31
+ bandwidth(): number;
32
+
33
+ ticks(count: number): number[];
34
+
35
+ tickFormat(count?: number, specifier?: string): (x: number) => string;
36
+
37
+ copy(): this;
38
+ }