@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.
- package/LICENSE +7 -7
- package/README.md +16 -0
- package/dist/index.js +42 -42
- package/dist/{genome-spy-schema.json → schema.json} +1824 -652
- package/dist/style.css +1 -1
- package/package.json +9 -7
- package/src/data/sources/dataUtils.js +50 -3
- package/src/data/sources/dynamicCallbackSource.js +2 -1
- package/src/data/sources/dynamicSource.js +2 -1
- package/src/data/sources/inlineSource.js +3 -5
- package/src/data/sources/namedSource.js +3 -7
- package/src/data/sources/urlSource.js +1 -1
- package/src/data/transforms/aggregate.js +1 -0
- package/src/data/transforms/flattenDelimited.js +6 -0
- package/src/data/transforms/regexFold.js +1 -1
- package/src/data/transforms/stack.js +3 -3
- package/src/embedApi.d.ts +59 -0
- package/src/encoder/accessor.js +6 -6
- package/src/encoder/encoder.js +47 -22
- package/src/genome/scaleIndex.d.ts +38 -0
- package/src/genome/scaleIndex.js +18 -52
- package/src/genome/scaleLocus.d.ts +11 -0
- package/src/genome/scaleLocus.js +12 -16
- package/src/genomeSpy.js +1 -1
- package/src/gl/dataToVertices.js +14 -6
- package/src/gl/includes/fp64-utils.js +10 -0
- package/src/gl/includes/scales.glsl +2 -0
- package/src/gl/webGLHelper.js +3 -1
- package/src/index.js +6 -28
- package/src/marks/link.js +1 -12
- package/src/marks/mark.js +27 -5
- package/src/marks/markUtils.js +41 -25
- package/src/marks/pointMark.js +5 -2
- package/src/marks/rule.js +11 -2
- package/src/scale/glslScaleGenerator.js +16 -29
- package/src/scale/scale.js +10 -0
- package/src/scale/ticks.js +11 -6
- package/src/spec/channel.d.ts +343 -43
- package/src/spec/data.d.ts +14 -3
- package/src/spec/root.d.ts +3 -8
- package/src/spec/scale.d.ts +18 -1
- package/src/spec/view.d.ts +12 -6
- package/src/tooltip/refseqGeneTooltipHandler.js +1 -0
- package/src/types/filetypes.d.ts +10 -0
- package/src/types/internmap.d.ts +22 -0
- package/src/types/vega-loader.d.ts +1 -0
- package/src/utils/addBaseUrl.js +19 -0
- package/src/utils/addBaseUrl.test.js +21 -0
- package/src/utils/arrayUtils.js +12 -6
- package/src/utils/cloner.js +5 -3
- package/src/utils/concatIterables.js +2 -2
- package/src/utils/domainArray.js +0 -8
- package/src/utils/propertyCoalescer.js +9 -4
- package/src/view/axisResolution.js +11 -6
- package/src/view/axisView.js +8 -5
- package/src/view/decoratorView.js +6 -3
- package/src/view/facetView.js +3 -0
- package/src/view/flowBuilder.js +2 -1
- package/src/view/renderingContext/svgViewRenderingContext.js +7 -3
- package/src/view/scaleResolution.js +52 -32
- package/src/view/testUtils.js +7 -4
- package/src/view/unitView.js +15 -9
- package/src/view/view.js +10 -8
- package/src/view/viewFactory.js +2 -0
- package/src/view/viewUtils.js +4 -5
- package/src/options.d.ts +0 -15
- package/src/utils/fisheye.js +0 -60
- package/src/utils/html.js +0 -23
- package/src/utils/html.test.js +0 -13
- package/src/view/channel.js +0 -5
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.genome-spy{font-family:system-ui,
|
|
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.
|
|
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:
|
|
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
|
|
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
|
|
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": "
|
|
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
|
|
12
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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) : (
|
|
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 = (
|
|
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 = (
|
|
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
|
+
}
|
package/src/encoder/accessor.js
CHANGED
|
@@ -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
|
-
|
|
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 {
|
|
44
|
-
const accessor =
|
|
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
|
}
|
package/src/encoder/encoder.js
CHANGED
|
@@ -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 {
|
|
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("../
|
|
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
|
-
|
|
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 =
|
|
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").
|
|
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 {
|
|
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,
|
|
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}
|
|
317
|
+
* @param {Channel} channel
|
|
286
318
|
*/
|
|
287
|
-
export function getPrimaryChannel(
|
|
288
|
-
return primaryChannels[
|
|
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
|
+
}
|