@genome-spy/core 0.14.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.
- package/LICENSE +24 -0
- package/README.md +16 -0
- package/dist/genome-spy-schema.json +4068 -0
- package/dist/index.js +42 -42
- package/dist/style.css +1 -1
- package/package.json +5 -4
- 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 +2 -5
- 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 +9 -35
- 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/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/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 -4
- package/src/options.d.ts +0 -9
- 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/src/spec/scale.d.ts
CHANGED
|
@@ -10,6 +10,23 @@
|
|
|
10
10
|
|
|
11
11
|
import { ChromosomalLocus } from "./genome";
|
|
12
12
|
|
|
13
|
+
export type ScaleType =
|
|
14
|
+
| "null"
|
|
15
|
+
| "linear"
|
|
16
|
+
| "log"
|
|
17
|
+
| "pow"
|
|
18
|
+
| "sqrt"
|
|
19
|
+
| "symlog"
|
|
20
|
+
| "identity"
|
|
21
|
+
| "sequential"
|
|
22
|
+
| "quantize"
|
|
23
|
+
| "threshold"
|
|
24
|
+
| "ordinal"
|
|
25
|
+
| "point"
|
|
26
|
+
| "band"
|
|
27
|
+
| "index"
|
|
28
|
+
| "locus";
|
|
29
|
+
|
|
13
30
|
export interface Scale {
|
|
14
31
|
/**
|
|
15
32
|
* The name of the scale. Names are optional but allow the scales to be referenced and found with the API.
|
|
@@ -27,7 +44,7 @@ export interface Scale {
|
|
|
27
44
|
*
|
|
28
45
|
* __Default value:__ please see the [scale type table](https://vega.github.io/vega-lite/docs/scale.html#type).
|
|
29
46
|
*/
|
|
30
|
-
type?:
|
|
47
|
+
type?: ScaleType;
|
|
31
48
|
|
|
32
49
|
/**
|
|
33
50
|
* Customized domain values.
|
package/src/spec/view.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { Data } from "./data";
|
|
2
2
|
import { Scale } from "./scale";
|
|
3
3
|
import { TransformParams } from "./transform";
|
|
4
|
-
import {
|
|
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?:
|
|
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:
|
|
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:
|
|
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
|
package/src/types/filetypes.d.ts
CHANGED
|
@@ -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";
|
package/src/utils/arrayUtils.js
CHANGED
|
@@ -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}
|
|
6
|
-
* @param {function(B):T}
|
|
13
|
+
* @param {function(A):T} aAccessor
|
|
14
|
+
* @param {function(B):T} bAccessor
|
|
7
15
|
* @template A, B, T
|
|
8
16
|
*/
|
|
9
|
-
export function
|
|
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]))
|
package/src/utils/cloner.js
CHANGED
|
@@ -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 =
|
|
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 { " +
|
package/src/utils/domainArray.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
-
|
|
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").
|
|
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
|
-
|
|
61
|
-
|
|
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 {
|
package/src/view/axisView.js
CHANGED
|
@@ -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 {
|
|
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").
|
|
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
|
-
|
|
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"
|
|
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"
|
|
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").
|
|
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 (
|
|
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").
|
|
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(),
|
package/src/view/facetView.js
CHANGED
package/src/view/flowBuilder.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
12
|
-
|
|
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
|
-
[
|
|
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
|
|
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 (!
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
437
|
+
powScale.exponent()
|
|
429
438
|
);
|
|
430
439
|
break;
|
|
440
|
+
}
|
|
431
441
|
default:
|
|
432
|
-
throw new Error(
|
|
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(
|
|
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
|
|
495
|
-
|
|
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 (
|
|
680
|
-
if (
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
744
|
-
props.clamp = true;
|
|
745
|
-
}
|
|
764
|
+
if (channel == "opacity" && isContinuous(props.type)) {
|
|
765
|
+
props.clamp = true;
|
|
746
766
|
}
|
|
747
767
|
}
|
|
748
768
|
|
package/src/view/testUtils.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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);
|