@genome-spy/core 0.29.0 → 0.30.2
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/dist/index.es.js +16373 -0
- package/dist/index.js +43 -43
- package/package.json +10 -7
- package/src/data/collector.js +0 -183
- package/src/data/collector.test.js +0 -84
- package/src/data/dataFlow.js +0 -148
- package/src/data/dataFlow.test.js +0 -5
- package/src/data/facetNode.js +0 -17
- package/src/data/flow.test.js +0 -72
- package/src/data/flowBatch.d.ts +0 -40
- package/src/data/flowNode.js +0 -283
- package/src/data/flowNode.test.js +0 -50
- package/src/data/flowOptimizer.js +0 -123
- package/src/data/flowOptimizer.test.js +0 -193
- package/src/data/flowTestUtils.js +0 -63
- package/src/data/formats/fasta.js +0 -32
- package/src/data/formats/fasta.test.js +0 -27
- package/src/data/sources/dataSource.js +0 -22
- package/src/data/sources/dataSourceFactory.js +0 -24
- package/src/data/sources/dataUtils.js +0 -78
- package/src/data/sources/dynamicCallbackSource.js +0 -57
- package/src/data/sources/dynamicSource.js +0 -37
- package/src/data/sources/inlineSource.js +0 -67
- package/src/data/sources/inlineSource.test.js +0 -56
- package/src/data/sources/namedSource.js +0 -79
- package/src/data/sources/sequenceSource.js +0 -46
- package/src/data/sources/sequenceSource.test.js +0 -46
- package/src/data/sources/urlSource.js +0 -74
- package/src/data/transforms/aggregate.js +0 -70
- package/src/data/transforms/clone.js +0 -40
- package/src/data/transforms/clone.test.js +0 -11
- package/src/data/transforms/coverage.js +0 -187
- package/src/data/transforms/coverage.test.js +0 -123
- package/src/data/transforms/filter.js +0 -37
- package/src/data/transforms/filter.test.js +0 -18
- package/src/data/transforms/filterScoredLabels.js +0 -134
- package/src/data/transforms/flattenCompressedExons.js +0 -57
- package/src/data/transforms/flattenDelimited.js +0 -74
- package/src/data/transforms/flattenDelimited.test.js +0 -87
- package/src/data/transforms/flattenSequence.js +0 -39
- package/src/data/transforms/flattenSequence.test.js +0 -34
- package/src/data/transforms/formula.js +0 -39
- package/src/data/transforms/formula.test.js +0 -19
- package/src/data/transforms/identifier.js +0 -108
- package/src/data/transforms/identifier.test.js +0 -83
- package/src/data/transforms/linearizeGenomicCoordinate.js +0 -101
- package/src/data/transforms/measureText.js +0 -44
- package/src/data/transforms/pileup.js +0 -128
- package/src/data/transforms/pileup.test.js +0 -70
- package/src/data/transforms/project.js +0 -41
- package/src/data/transforms/project.test.js +0 -32
- package/src/data/transforms/regexExtract.js +0 -61
- package/src/data/transforms/regexExtract.test.js +0 -67
- package/src/data/transforms/regexFold.js +0 -141
- package/src/data/transforms/regexFold.test.js +0 -160
- package/src/data/transforms/sample.js +0 -101
- package/src/data/transforms/sample.test.js +0 -38
- package/src/data/transforms/stack.js +0 -137
- package/src/data/transforms/stack.test.js +0 -91
- package/src/data/transforms/transformFactory.js +0 -60
- package/src/embedApi.d.ts +0 -67
- package/src/encoder/accessor.js +0 -82
- package/src/encoder/accessor.test.js +0 -47
- package/src/encoder/encoder.js +0 -394
- package/src/encoder/encoder.test.js +0 -98
- package/src/fonts/Lato-Regular.json +0 -1267
- package/src/fonts/Lato-Regular.png +0 -0
- package/src/fonts/OFL.txt +0 -93
- package/src/fonts/README.md +0 -3
- package/src/fonts/bmFont.d.ts +0 -58
- package/src/fonts/bmFontManager.js +0 -357
- package/src/fonts/bmFontMetrics.js +0 -108
- package/src/genome/genome.js +0 -317
- package/src/genome/genome.test.js +0 -188
- package/src/genome/genomeStore.js +0 -54
- package/src/genome/locusFormat.js +0 -31
- package/src/genome/scaleIndex.d.ts +0 -38
- package/src/genome/scaleIndex.js +0 -166
- package/src/genome/scaleIndex.test.js +0 -78
- package/src/genome/scaleLocus.d.ts +0 -11
- package/src/genome/scaleLocus.js +0 -108
- package/src/genome/scaleLocus.test.js +0 -4
- package/src/genomeSpy.js +0 -784
- package/src/gl/arrayBuilder.js +0 -199
- package/src/gl/dataToVertices.js +0 -636
- package/src/gl/includes/common.glsl +0 -63
- package/src/gl/includes/picking.fragment.glsl +0 -1
- package/src/gl/includes/picking.vertex.glsl +0 -27
- package/src/gl/includes/sampleFacet.glsl +0 -107
- package/src/gl/includes/scales.glsl +0 -112
- package/src/gl/link.fragment.glsl +0 -18
- package/src/gl/link.vertex.glsl +0 -111
- package/src/gl/point.fragment.glsl +0 -123
- package/src/gl/point.vertex.glsl +0 -129
- package/src/gl/rect.fragment.glsl +0 -51
- package/src/gl/rect.vertex.glsl +0 -114
- package/src/gl/rule.fragment.glsl +0 -52
- package/src/gl/rule.vertex.glsl +0 -89
- package/src/gl/text.fragment.glsl +0 -31
- package/src/gl/text.vertex.glsl +0 -246
- package/src/gl/webGLHelper.js +0 -489
- package/src/img/bowtie.svg +0 -1
- package/src/img/genomespy-favicon.svg +0 -34
- package/src/index.html +0 -11
- package/src/index.js +0 -128
- package/src/marks/link.js +0 -175
- package/src/marks/mark.js +0 -975
- package/src/marks/markUtils.js +0 -125
- package/src/marks/pointMark.js +0 -251
- package/src/marks/rectMark.js +0 -241
- package/src/marks/rule.js +0 -250
- package/src/marks/text.js +0 -278
- package/src/node_modules/.vitest/results.json +0 -1
- package/src/scale/colorUtils.js +0 -184
- package/src/scale/glslScaleGenerator.js +0 -488
- package/src/scale/scale.js +0 -451
- package/src/scale/scale.test.js +0 -324
- package/src/scale/ticks.js +0 -203
- package/src/scale/ticks.test.js +0 -40
- package/src/singlePageApp.js +0 -13
- package/src/spec/axis.d.ts +0 -296
- package/src/spec/channel.d.ts +0 -430
- package/src/spec/data.d.ts +0 -196
- package/src/spec/font.d.ts +0 -15
- package/src/spec/genome.d.ts +0 -35
- package/src/spec/mark.d.ts +0 -429
- package/src/spec/root.d.ts +0 -17
- package/src/spec/sampleView.d.ts +0 -180
- package/src/spec/scale.d.ts +0 -273
- package/src/spec/title.d.ts +0 -102
- package/src/spec/tooltip.d.ts +0 -9
- package/src/spec/transform.d.ts +0 -479
- package/src/spec/view.d.ts +0 -201
- package/src/styles/genome-spy.scss +0 -153
- package/src/tooltip/dataTooltipHandler.js +0 -64
- package/src/tooltip/refseqGeneTooltipHandler.js +0 -78
- package/src/tooltip/tooltipHandler.ts +0 -12
- package/src/types/filetypes.d.ts +0 -14
- package/src/types/flatqueue.d.ts +0 -53
- package/src/types/glsl.d.ts +0 -4
- package/src/types/internmap.d.ts +0 -22
- package/src/types/object.d.ts +0 -21
- package/src/types/vega-loader.d.ts +0 -1
- package/src/types/vega-scale.d.ts +0 -60
- package/src/utils/addBaseUrl.js +0 -19
- package/src/utils/addBaseUrl.test.js +0 -22
- package/src/utils/animator.js +0 -83
- package/src/utils/arrayUtils.js +0 -61
- package/src/utils/binnedIndex.js +0 -167
- package/src/utils/binnedIndex.test.js +0 -155
- package/src/utils/clamp.js +0 -8
- package/src/utils/cloner.js +0 -34
- package/src/utils/cloner.test.js +0 -24
- package/src/utils/coalesce.js +0 -11
- package/src/utils/coalesce.test.js +0 -16
- package/src/utils/concatIterables.js +0 -26
- package/src/utils/concatIterables.test.js +0 -8
- package/src/utils/debounce.js +0 -37
- package/src/utils/domainArray.js +0 -216
- package/src/utils/domainArray.test.js +0 -130
- package/src/utils/eerp.js +0 -13
- package/src/utils/expression.js +0 -32
- package/src/utils/field.js +0 -28
- package/src/utils/formatObject.js +0 -31
- package/src/utils/indexer.js +0 -43
- package/src/utils/indexer.test.js +0 -47
- package/src/utils/inertia.js +0 -124
- package/src/utils/interactionEvent.js +0 -33
- package/src/utils/iterateNestedMaps.js +0 -21
- package/src/utils/iterateNestedMaps.test.js +0 -33
- package/src/utils/kWayMerge.js +0 -42
- package/src/utils/kWayMerge.test.js +0 -26
- package/src/utils/layout/flexLayout.js +0 -368
- package/src/utils/layout/flexLayout.test.js +0 -311
- package/src/utils/layout/grid.js +0 -95
- package/src/utils/layout/grid.test.js +0 -71
- package/src/utils/layout/padding.js +0 -120
- package/src/utils/layout/point.js +0 -23
- package/src/utils/layout/rectangle.js +0 -288
- package/src/utils/layout/rectangle.test.js +0 -172
- package/src/utils/mergeObjects.js +0 -99
- package/src/utils/mergeObjects.test.js +0 -42
- package/src/utils/numberExtractor.js +0 -24
- package/src/utils/numberExtractor.test.js +0 -6
- package/src/utils/point.js +0 -14
- package/src/utils/propertyCacher.js +0 -70
- package/src/utils/propertyCacher.test.js +0 -85
- package/src/utils/propertyCoalescer.js +0 -42
- package/src/utils/propertyCoalescer.test.js +0 -22
- package/src/utils/reservationMap.js +0 -103
- package/src/utils/reservationMap.test.js +0 -20
- package/src/utils/scaleNull.js +0 -19
- package/src/utils/setOperations.js +0 -75
- package/src/utils/smoothstep.js +0 -10
- package/src/utils/throttle.js +0 -34
- package/src/utils/topK.js +0 -76
- package/src/utils/topK.test.js +0 -64
- package/src/utils/transition.js +0 -74
- package/src/utils/ui/tooltip.js +0 -189
- package/src/utils/url.js +0 -22
- package/src/utils/variableTools.js +0 -24
- package/src/utils/variableTools.test.js +0 -13
- package/src/view/axisResolution.js +0 -140
- package/src/view/axisResolution.test.js +0 -201
- package/src/view/axisView.js +0 -747
- package/src/view/concatView.js +0 -45
- package/src/view/containerView.js +0 -159
- package/src/view/facetView.js +0 -491
- package/src/view/flowBuilder.js +0 -367
- package/src/view/flowBuilder.test.js +0 -125
- package/src/view/gridView.js +0 -786
- package/src/view/implicitRootView.js +0 -14
- package/src/view/importView.js +0 -19
- package/src/view/layerView.js +0 -74
- package/src/view/rendering.d.ts +0 -44
- package/src/view/renderingContext/compositeViewRenderingContext.js +0 -51
- package/src/view/renderingContext/deferredViewRenderingContext.js +0 -176
- package/src/view/renderingContext/layoutRecorderViewRenderingContext.js +0 -128
- package/src/view/renderingContext/simpleViewRenderingContext.js +0 -64
- package/src/view/renderingContext/svgViewRenderingContext.js +0 -125
- package/src/view/renderingContext/viewRenderingContext.js +0 -41
- package/src/view/scaleResolution.js +0 -791
- package/src/view/scaleResolution.test.js +0 -572
- package/src/view/scaleResolutionApi.d.ts +0 -40
- package/src/view/testUtils.js +0 -51
- package/src/view/title.js +0 -165
- package/src/view/unitView.js +0 -382
- package/src/view/view.js +0 -612
- package/src/view/view.test.js +0 -214
- package/src/view/viewContext.d.ts +0 -62
- package/src/view/viewFactory.js +0 -181
- package/src/view/viewFactory.test.js +0 -17
- package/src/view/viewUtils.js +0 -327
- package/src/view/zoom.js +0 -89
package/src/view/gridView.js
DELETED
|
@@ -1,786 +0,0 @@
|
|
|
1
|
-
/* eslint-disable max-depth */
|
|
2
|
-
import {
|
|
3
|
-
FlexDimensions,
|
|
4
|
-
getLargestSize,
|
|
5
|
-
mapToPixelCoords,
|
|
6
|
-
parseSizeDef,
|
|
7
|
-
ZERO_SIZEDEF,
|
|
8
|
-
} from "../utils/layout/flexLayout";
|
|
9
|
-
import Grid from "../utils/layout/grid";
|
|
10
|
-
import Padding from "../utils/layout/padding";
|
|
11
|
-
import Rectangle from "../utils/layout/rectangle";
|
|
12
|
-
import AxisView, { CHANNEL_ORIENTS } from "./axisView";
|
|
13
|
-
import ContainerView from "./containerView";
|
|
14
|
-
import LayerView from "./layerView";
|
|
15
|
-
import createTitle from "./title";
|
|
16
|
-
import UnitView from "./unitView";
|
|
17
|
-
import interactionToZoom from "./zoom";
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* @typedef {"row" | "column"} Direction
|
|
21
|
-
* @typedef {import("./view").default} View
|
|
22
|
-
*
|
|
23
|
-
* @typedef {object} GridChild
|
|
24
|
-
* @prop {View} view
|
|
25
|
-
* @prop {UnitView} [background]
|
|
26
|
-
* @prop {Partial<Record<import("../spec/axis").AxisOrient, AxisView>>} axes
|
|
27
|
-
* @prop {UnitView} [title]
|
|
28
|
-
* @prop {Rectangle} coords Coordinates of the view. Recorded for mouse tracking, etc.
|
|
29
|
-
*/
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Modeled after: https://vega.github.io/vega/docs/layout/
|
|
33
|
-
*
|
|
34
|
-
* This should take care of the following:
|
|
35
|
-
* - Composition: [hv]concat / facet / repeat
|
|
36
|
-
* - Views
|
|
37
|
-
* - Axes
|
|
38
|
-
* - Grid lines
|
|
39
|
-
* - View background
|
|
40
|
-
* - View titles
|
|
41
|
-
* - Facet (column / row) titles
|
|
42
|
-
* - Header / footer
|
|
43
|
-
* - Zoom / pan
|
|
44
|
-
* - And later on, brushing, legend(?)
|
|
45
|
-
*/
|
|
46
|
-
export default class GridView extends ContainerView {
|
|
47
|
-
#columns = Infinity;
|
|
48
|
-
|
|
49
|
-
#spacing = 10;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* @type { GridChild[] }
|
|
53
|
-
*/
|
|
54
|
-
#children = [];
|
|
55
|
-
|
|
56
|
-
#childSerial = 0;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
*
|
|
60
|
-
* @param {import("./viewUtils").AnyConcatSpec} spec
|
|
61
|
-
* @param {import("./viewUtils").ViewContext} context
|
|
62
|
-
* @param {ContainerView} parent
|
|
63
|
-
* @param {string} name
|
|
64
|
-
* @param {number} columns
|
|
65
|
-
*/
|
|
66
|
-
constructor(spec, context, parent, name, columns) {
|
|
67
|
-
super(spec, context, parent, name);
|
|
68
|
-
this.spec = spec;
|
|
69
|
-
|
|
70
|
-
this.#spacing = spec.spacing ?? 10;
|
|
71
|
-
this.#columns = columns;
|
|
72
|
-
|
|
73
|
-
this.#children = [];
|
|
74
|
-
|
|
75
|
-
this.wrappingFacet = false;
|
|
76
|
-
|
|
77
|
-
this._createChildren();
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
_createChildren() {
|
|
81
|
-
// Override
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* @param {View} view
|
|
86
|
-
*/
|
|
87
|
-
#makeGridChild(view) {
|
|
88
|
-
/** @type {GridChild} */
|
|
89
|
-
const gridChild = {
|
|
90
|
-
view,
|
|
91
|
-
background: undefined,
|
|
92
|
-
axes: {},
|
|
93
|
-
coords: Rectangle.ZERO,
|
|
94
|
-
};
|
|
95
|
-
|
|
96
|
-
if (view instanceof UnitView || view instanceof LayerView) {
|
|
97
|
-
/** @type {import("../spec/view").ViewBackground} */
|
|
98
|
-
const viewBackground = view.spec?.view;
|
|
99
|
-
if (viewBackground?.fill || viewBackground?.stroke) {
|
|
100
|
-
const unitView = new UnitView(
|
|
101
|
-
createBackground(viewBackground),
|
|
102
|
-
this.context,
|
|
103
|
-
view,
|
|
104
|
-
"background" + this.#childSerial
|
|
105
|
-
);
|
|
106
|
-
// TODO: Make configurable through spec:
|
|
107
|
-
unitView.blockEncodingInheritance = true;
|
|
108
|
-
gridChild.background = unitView;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const title = createTitle(view.spec.title);
|
|
112
|
-
if (title) {
|
|
113
|
-
const unitView = new UnitView(
|
|
114
|
-
title,
|
|
115
|
-
this.context,
|
|
116
|
-
view,
|
|
117
|
-
"title" + this.#childSerial
|
|
118
|
-
);
|
|
119
|
-
// TODO: Make configurable through spec:
|
|
120
|
-
unitView.blockEncodingInheritance = true;
|
|
121
|
-
gridChild.title = unitView;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
return gridChild;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* @param {View} view
|
|
130
|
-
*/
|
|
131
|
-
appendChild(view) {
|
|
132
|
-
view.parent ??= this;
|
|
133
|
-
this.#children.push(this.#makeGridChild(view));
|
|
134
|
-
this.#childSerial++;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
get #visibleChildren() {
|
|
138
|
-
return this.#children.filter((gridChild) => gridChild.view.isVisible());
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
get #grid() {
|
|
142
|
-
return new Grid(
|
|
143
|
-
this.#visibleChildren.length,
|
|
144
|
-
this.#columns ?? Infinity
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* @param {View[]} views
|
|
150
|
-
*/
|
|
151
|
-
setChildren(views) {
|
|
152
|
-
//this.#children = []; // TODO: Check why this breaks summary track
|
|
153
|
-
for (const view of views) {
|
|
154
|
-
this.appendChild(view);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* @param {import("./view").default} child
|
|
160
|
-
* @param {import("./view").default} replacement
|
|
161
|
-
*/
|
|
162
|
-
replaceChild(child, replacement) {
|
|
163
|
-
const i = this.#children.findIndex(
|
|
164
|
-
(gridChild) => gridChild.view == child
|
|
165
|
-
);
|
|
166
|
-
if (i >= 0) {
|
|
167
|
-
this.#children[i] = this.#makeGridChild(replacement);
|
|
168
|
-
} else {
|
|
169
|
-
throw new Error("Not my child view!");
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* Read-only view to children
|
|
175
|
-
*/
|
|
176
|
-
get children() {
|
|
177
|
-
return this.#children.map((gridChild) => gridChild.view);
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
get childCount() {
|
|
181
|
-
return this.#children.length;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
onScalesResolved() {
|
|
185
|
-
super.onScalesResolved();
|
|
186
|
-
|
|
187
|
-
this.#createAxes();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
#createAxes() {
|
|
191
|
-
if (Object.keys(this.resolutions.axis).length) {
|
|
192
|
-
throw new Error(
|
|
193
|
-
"ConcatView does not (currently) support shared axes!"
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Create axes
|
|
198
|
-
for (const gridChild of this.#children) {
|
|
199
|
-
const { view, axes } = gridChild;
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* @param {import("./view").AxisResolution} r
|
|
203
|
-
* @param {import("../spec/channel").PrimaryPositionalChannel} channel
|
|
204
|
-
* @param {UnitView | LayerView} axisParent
|
|
205
|
-
*/
|
|
206
|
-
const createAxis = (r, channel, axisParent) => {
|
|
207
|
-
const props = r.getAxisProps();
|
|
208
|
-
if (props === null) {
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Pick a default orient based on what is available
|
|
213
|
-
if (!props.orient) {
|
|
214
|
-
for (const orient of CHANNEL_ORIENTS[channel]) {
|
|
215
|
-
if (!axes[orient]) {
|
|
216
|
-
props.orient = orient;
|
|
217
|
-
break;
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
if (!props.orient) {
|
|
221
|
-
throw new Error(
|
|
222
|
-
"No slots available for an axis! Perhaps a LayerView has more than two children?"
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
props.title ??= r.getTitle();
|
|
228
|
-
|
|
229
|
-
if (!CHANNEL_ORIENTS[channel].includes(props.orient)) {
|
|
230
|
-
throw new Error(
|
|
231
|
-
`Invalid axis orientation "${props.orient}" on channel "${channel}"!`
|
|
232
|
-
);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (axes[props.orient]) {
|
|
236
|
-
throw new Error(
|
|
237
|
-
`An axis with the orient "${props.orient}" already exists!`
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
axes[props.orient] = new AxisView(
|
|
242
|
-
props,
|
|
243
|
-
r.scaleResolution.type,
|
|
244
|
-
this.context,
|
|
245
|
-
// Note: Axisview has a unit/layerView as parent so that scale/axis resolutions are inherited correctly
|
|
246
|
-
axisParent
|
|
247
|
-
);
|
|
248
|
-
};
|
|
249
|
-
|
|
250
|
-
// Handle shared axes
|
|
251
|
-
if (view instanceof UnitView || view instanceof LayerView) {
|
|
252
|
-
for (const channel of /** @type {import("../spec/channel").PrimaryPositionalChannel[]} */ ([
|
|
253
|
-
"x",
|
|
254
|
-
"y",
|
|
255
|
-
])) {
|
|
256
|
-
const r = view.resolutions.axis[channel];
|
|
257
|
-
if (!r) {
|
|
258
|
-
continue;
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
createAxis(r, channel, view);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Handle LayerView's possible independent axes
|
|
266
|
-
if (view instanceof LayerView) {
|
|
267
|
-
// First create axes that have an orient preference
|
|
268
|
-
for (const layerChild of view.children) {
|
|
269
|
-
for (const [channel, r] of Object.entries(
|
|
270
|
-
layerChild.resolutions.axis
|
|
271
|
-
)) {
|
|
272
|
-
const props = r.getAxisProps();
|
|
273
|
-
if (props && props.orient) {
|
|
274
|
-
createAxis(r, channel, layerChild);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
// Then create axes in a priority order
|
|
280
|
-
for (const layerChild of view.children) {
|
|
281
|
-
for (const [channel, r] of Object.entries(
|
|
282
|
-
layerChild.resolutions.axis
|
|
283
|
-
)) {
|
|
284
|
-
const props = r.getAxisProps();
|
|
285
|
-
if (props && !props.orient) {
|
|
286
|
-
createAxis(r, channel, layerChild);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* @returns {IterableIterator<View>}
|
|
296
|
-
*/
|
|
297
|
-
*[Symbol.iterator]() {
|
|
298
|
-
for (const gridChild of this.#children) {
|
|
299
|
-
if (gridChild.background) {
|
|
300
|
-
yield gridChild.background;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
for (const axisView of Object.values(gridChild.axes)) {
|
|
304
|
-
if (axisView) {
|
|
305
|
-
yield axisView;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
yield gridChild.view;
|
|
310
|
-
|
|
311
|
-
if (gridChild.title) {
|
|
312
|
-
yield gridChild.title;
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* @param {Direction} direction
|
|
319
|
-
*/
|
|
320
|
-
#getSizes(direction) {
|
|
321
|
-
/** @type {import("../spec/axis").AxisOrient[]} */
|
|
322
|
-
const orients =
|
|
323
|
-
direction == "column" ? ["left", "right"] : ["top", "bottom"];
|
|
324
|
-
|
|
325
|
-
const dim = direction == "column" ? "width" : "height";
|
|
326
|
-
|
|
327
|
-
/**
|
|
328
|
-
* @type {(indices: number[], side: 0 | 1) => number}
|
|
329
|
-
*/
|
|
330
|
-
const getMaxAxisSize = (indices, side) =>
|
|
331
|
-
indices
|
|
332
|
-
.map((index) => {
|
|
333
|
-
// Axis view is only present for unit and layer views
|
|
334
|
-
const axisView =
|
|
335
|
-
this.#visibleChildren[index].axes[orients[side]];
|
|
336
|
-
if (axisView) {
|
|
337
|
-
return Math.max(
|
|
338
|
-
axisView.getPerpendicularSize() +
|
|
339
|
-
axisView.axisProps.offset ?? 0,
|
|
340
|
-
0
|
|
341
|
-
);
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// For views other than unit or layer, use overhang instead
|
|
345
|
-
const overhang =
|
|
346
|
-
this.#visibleChildren[index].view.getOverhang();
|
|
347
|
-
if (direction == "column") {
|
|
348
|
-
return side ? overhang.right : overhang.left;
|
|
349
|
-
} else {
|
|
350
|
-
return side ? overhang.bottom : overhang.top;
|
|
351
|
-
}
|
|
352
|
-
})
|
|
353
|
-
.reduce((a, b) => Math.max(a, b), 0);
|
|
354
|
-
|
|
355
|
-
return this.#grid[
|
|
356
|
-
direction == "column" ? "colIndices" : "rowIndices"
|
|
357
|
-
].map((col) => ({
|
|
358
|
-
axisBefore: getMaxAxisSize(col, 0),
|
|
359
|
-
axisAfter: getMaxAxisSize(col, 1),
|
|
360
|
-
view: getLargestSize(
|
|
361
|
-
col.map(
|
|
362
|
-
(rowIndex) =>
|
|
363
|
-
this.#visibleChildren[rowIndex].view.getSize()[dim]
|
|
364
|
-
)
|
|
365
|
-
),
|
|
366
|
-
}));
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/**
|
|
370
|
-
* An example layout with two children, either column or row-based direction:
|
|
371
|
-
*
|
|
372
|
-
* 0. title
|
|
373
|
-
* 1. header
|
|
374
|
-
* 2. axis/padding
|
|
375
|
-
* 3. view
|
|
376
|
-
* 4. axis/padding
|
|
377
|
-
* 5. footer (if column and wrapping)
|
|
378
|
-
* 5. spacing
|
|
379
|
-
* 6. header (if column and wrapping)
|
|
380
|
-
* 7. axis/padding
|
|
381
|
-
* 8. view
|
|
382
|
-
* 9. axis/padding
|
|
383
|
-
* 10. footer
|
|
384
|
-
*
|
|
385
|
-
* @param {Direction} direction
|
|
386
|
-
*/
|
|
387
|
-
#makeFlexItems(direction) {
|
|
388
|
-
const sizes = this.#getSizes(direction);
|
|
389
|
-
|
|
390
|
-
/** @type {import("../utils/layout/flexLayout").SizeDef[]} */
|
|
391
|
-
const items = [];
|
|
392
|
-
|
|
393
|
-
// Title
|
|
394
|
-
items.push(ZERO_SIZEDEF);
|
|
395
|
-
|
|
396
|
-
for (const [i, size] of sizes.entries()) {
|
|
397
|
-
if (i > 0) {
|
|
398
|
-
// Spacing
|
|
399
|
-
items.push({ px: this.#spacing, grow: 0 });
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
if (i == 0 || this.wrappingFacet) {
|
|
403
|
-
// Header
|
|
404
|
-
items.push(ZERO_SIZEDEF);
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
// Axis/padding
|
|
408
|
-
items.push({ px: size.axisBefore, grow: 0 });
|
|
409
|
-
|
|
410
|
-
// View
|
|
411
|
-
items.push(size.view);
|
|
412
|
-
|
|
413
|
-
// Axis/padding
|
|
414
|
-
items.push({ px: size.axisAfter, grow: 0 });
|
|
415
|
-
|
|
416
|
-
if (i == sizes.length - 1 || this.wrappingFacet) {
|
|
417
|
-
//Footer
|
|
418
|
-
items.push(ZERO_SIZEDEF);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
return items;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
/**
|
|
426
|
-
* @param {Direction} direction
|
|
427
|
-
* @return {import("../utils/layout/flexLayout").SizeDef}
|
|
428
|
-
*/
|
|
429
|
-
#getFlexSize(direction) {
|
|
430
|
-
let grow = 0;
|
|
431
|
-
let px = 0;
|
|
432
|
-
|
|
433
|
-
const explicitSize =
|
|
434
|
-
(direction == "row" && this.spec.height) ??
|
|
435
|
-
(direction == "column" && this.spec.width);
|
|
436
|
-
if (explicitSize || explicitSize === 0) {
|
|
437
|
-
return parseSizeDef(explicitSize);
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
const sizes = this.#getSizes(direction);
|
|
441
|
-
|
|
442
|
-
for (const [i, size] of sizes.entries()) {
|
|
443
|
-
if (i > 0) {
|
|
444
|
-
// Spacing
|
|
445
|
-
px += this.#spacing;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if (i == 0 || this.wrappingFacet) {
|
|
449
|
-
// Header
|
|
450
|
-
px += 0;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
// Axis/padding
|
|
454
|
-
px += size.axisBefore;
|
|
455
|
-
|
|
456
|
-
// View
|
|
457
|
-
px += size.view.px ?? 0;
|
|
458
|
-
grow += size.view.grow ?? 0;
|
|
459
|
-
|
|
460
|
-
// Axis/padding
|
|
461
|
-
px += size.axisAfter;
|
|
462
|
-
|
|
463
|
-
if (i == sizes.length - 1 || this.wrappingFacet) {
|
|
464
|
-
//Footer
|
|
465
|
-
px += 0;
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
return { px, grow };
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
/**
|
|
473
|
-
* Locates a view slot in FlexLayout
|
|
474
|
-
*
|
|
475
|
-
* @param {Direction} direction
|
|
476
|
-
* @param {number} index column/row number
|
|
477
|
-
*/
|
|
478
|
-
#getViewSlot(direction, index) {
|
|
479
|
-
return direction == "row" && this.wrappingFacet
|
|
480
|
-
? // Views have header/footer on every row
|
|
481
|
-
1 + 6 * index + 2
|
|
482
|
-
: // Only first row has header, last row has footer.
|
|
483
|
-
2 + 4 * index + 1;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
/**
|
|
487
|
-
* @return {Padding}
|
|
488
|
-
*/
|
|
489
|
-
getOverhang() {
|
|
490
|
-
const cols = this.#getSizes("column");
|
|
491
|
-
const rows = this.#getSizes("row");
|
|
492
|
-
|
|
493
|
-
if (!cols.length || !rows.length) {
|
|
494
|
-
return Padding.zero();
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
const p = new Padding(
|
|
498
|
-
rows.at(0).axisBefore,
|
|
499
|
-
cols.at(-1).axisAfter,
|
|
500
|
-
rows.at(-1).axisAfter,
|
|
501
|
-
cols.at(0).axisBefore
|
|
502
|
-
);
|
|
503
|
-
return p;
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
/**
|
|
507
|
-
* @returns {FlexDimensions}
|
|
508
|
-
*/
|
|
509
|
-
getSize() {
|
|
510
|
-
return this._cache("size", () =>
|
|
511
|
-
new FlexDimensions(
|
|
512
|
-
this.#getFlexSize("column"),
|
|
513
|
-
this.#getFlexSize("row")
|
|
514
|
-
)
|
|
515
|
-
.subtractPadding(this.getOverhang())
|
|
516
|
-
.addPadding(this.getPadding())
|
|
517
|
-
);
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
/**
|
|
521
|
-
* @param {import("./renderingContext/viewRenderingContext").default} context
|
|
522
|
-
* @param {import("../utils/layout/rectangle").default} coords
|
|
523
|
-
* @param {import("./view").RenderingOptions} [options]
|
|
524
|
-
*/
|
|
525
|
-
render(context, coords, options = {}) {
|
|
526
|
-
if (!this.isVisible()) {
|
|
527
|
-
return;
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
if (!this.parent) {
|
|
531
|
-
// Usually padding is applied by the parent GridView, but if this is the root view, we need to apply it here
|
|
532
|
-
coords = coords.shrink(this.getPadding());
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
context.pushView(this, coords);
|
|
536
|
-
|
|
537
|
-
const flexOpts = {
|
|
538
|
-
devicePixelRatio: this.context.glHelper.dpr,
|
|
539
|
-
};
|
|
540
|
-
const columnFlexCoords = mapToPixelCoords(
|
|
541
|
-
this.#makeFlexItems("column"),
|
|
542
|
-
coords.width,
|
|
543
|
-
flexOpts
|
|
544
|
-
);
|
|
545
|
-
|
|
546
|
-
const rowFlexCoords = mapToPixelCoords(
|
|
547
|
-
this.#makeFlexItems("row"),
|
|
548
|
-
coords.height,
|
|
549
|
-
flexOpts
|
|
550
|
-
);
|
|
551
|
-
|
|
552
|
-
const grid = new Grid(
|
|
553
|
-
this.#visibleChildren.length,
|
|
554
|
-
this.#columns ?? Infinity
|
|
555
|
-
);
|
|
556
|
-
|
|
557
|
-
for (const [i, gridChild] of this.#visibleChildren.entries()) {
|
|
558
|
-
const { view, axes, background, title } = gridChild;
|
|
559
|
-
|
|
560
|
-
const [col, row] = grid.getCellCoords(i);
|
|
561
|
-
const colLocSize =
|
|
562
|
-
columnFlexCoords[this.#getViewSlot("column", col)];
|
|
563
|
-
const rowLocSize = rowFlexCoords[this.#getViewSlot("row", row)];
|
|
564
|
-
|
|
565
|
-
const viewSize = view.getSize();
|
|
566
|
-
const viewPadding = view.getPadding().subtract(view.getOverhang());
|
|
567
|
-
|
|
568
|
-
const x = colLocSize.location + viewPadding.left;
|
|
569
|
-
const y = rowLocSize.location + viewPadding.top;
|
|
570
|
-
|
|
571
|
-
const width =
|
|
572
|
-
(viewSize.width.grow ? colLocSize.size : viewSize.width.px) -
|
|
573
|
-
viewPadding.width;
|
|
574
|
-
const height =
|
|
575
|
-
(viewSize.height.grow ? rowLocSize.size : viewSize.height.px) -
|
|
576
|
-
viewPadding.height;
|
|
577
|
-
|
|
578
|
-
const childCoords = new Rectangle(
|
|
579
|
-
() => coords.x + x,
|
|
580
|
-
() => coords.y + y,
|
|
581
|
-
() => width,
|
|
582
|
-
() => height
|
|
583
|
-
);
|
|
584
|
-
|
|
585
|
-
gridChild.coords = childCoords;
|
|
586
|
-
|
|
587
|
-
background?.render(context, childCoords, options);
|
|
588
|
-
|
|
589
|
-
// If clipped, the axes should be drawn on top of the marks (because clipping may not be pixel-perfect)
|
|
590
|
-
const clipped = isClippedChildren(view);
|
|
591
|
-
if (clipped) {
|
|
592
|
-
view.render(context, childCoords, options);
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
for (const [orient, axisView] of Object.entries(axes)) {
|
|
596
|
-
const props = axisView.axisProps;
|
|
597
|
-
|
|
598
|
-
/** @type {import("../utils/layout/rectangle").default} */
|
|
599
|
-
let axisCoords;
|
|
600
|
-
|
|
601
|
-
const ps = axisView.getPerpendicularSize();
|
|
602
|
-
|
|
603
|
-
if (orient == "bottom") {
|
|
604
|
-
axisCoords = childCoords
|
|
605
|
-
.translate(0, childCoords.height + props.offset)
|
|
606
|
-
.modify({ height: ps });
|
|
607
|
-
} else if (orient == "top") {
|
|
608
|
-
axisCoords = childCoords
|
|
609
|
-
.translate(0, -ps - props.offset)
|
|
610
|
-
.modify({ height: ps });
|
|
611
|
-
} else if (orient == "left") {
|
|
612
|
-
axisCoords = childCoords
|
|
613
|
-
.translate(-ps - props.offset, 0)
|
|
614
|
-
.modify({ width: ps });
|
|
615
|
-
} else if (orient == "right") {
|
|
616
|
-
axisCoords = childCoords
|
|
617
|
-
.translate(childCoords.width + props.offset, 0)
|
|
618
|
-
.modify({ width: ps });
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
// Axes have no faceted data, thus, pass undefined facetId
|
|
622
|
-
axisView.render(context, axisCoords);
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
if (!clipped) {
|
|
626
|
-
view.render(context, childCoords, options);
|
|
627
|
-
}
|
|
628
|
-
|
|
629
|
-
title?.render(context, childCoords, {
|
|
630
|
-
...options,
|
|
631
|
-
clipRect: undefined, // Hack for SampleAttributePanel. TODO: Proper fix
|
|
632
|
-
});
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
context.popView(this);
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
/**
|
|
639
|
-
* @param {import("../utils/interactionEvent").default} event
|
|
640
|
-
*/
|
|
641
|
-
propagateInteractionEvent(event) {
|
|
642
|
-
this.handleInteractionEvent(undefined, event, true);
|
|
643
|
-
|
|
644
|
-
if (event.stopped) {
|
|
645
|
-
return;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
const pointedChild = this.#visibleChildren.find((gridChild) =>
|
|
649
|
-
gridChild.coords.containsPoint(event.point.x, event.point.y)
|
|
650
|
-
);
|
|
651
|
-
const pointedView = pointedChild?.view;
|
|
652
|
-
if (pointedView) {
|
|
653
|
-
pointedView.propagateInteractionEvent(event);
|
|
654
|
-
|
|
655
|
-
if (
|
|
656
|
-
pointedView instanceof UnitView ||
|
|
657
|
-
pointedView instanceof LayerView
|
|
658
|
-
) {
|
|
659
|
-
interactionToZoom(
|
|
660
|
-
event,
|
|
661
|
-
pointedChild.coords,
|
|
662
|
-
(zoomEvent) =>
|
|
663
|
-
this.#handleZoom(
|
|
664
|
-
pointedChild.coords,
|
|
665
|
-
pointedChild.view,
|
|
666
|
-
zoomEvent
|
|
667
|
-
),
|
|
668
|
-
this.context.getCurrentHover()
|
|
669
|
-
);
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
if (event.stopped) {
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
this.handleInteractionEvent(undefined, event, false);
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
/**
|
|
681
|
-
*
|
|
682
|
-
* @param {import("../utils/layout/rectangle").default} coords Coordinates
|
|
683
|
-
* @param {View} view
|
|
684
|
-
* @param {import("./zoom").ZoomEvent} zoomEvent
|
|
685
|
-
*/
|
|
686
|
-
#handleZoom(coords, view, zoomEvent) {
|
|
687
|
-
for (const [channel, resolutionSet] of Object.entries(
|
|
688
|
-
getZoomableResolutions(view)
|
|
689
|
-
)) {
|
|
690
|
-
if (resolutionSet.size <= 0) {
|
|
691
|
-
continue;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
const p = coords.normalizePoint(zoomEvent.x, zoomEvent.y);
|
|
695
|
-
const tp = coords.normalizePoint(
|
|
696
|
-
zoomEvent.x + zoomEvent.xDelta,
|
|
697
|
-
zoomEvent.y + zoomEvent.yDelta
|
|
698
|
-
);
|
|
699
|
-
|
|
700
|
-
const delta = {
|
|
701
|
-
x: tp.x - p.x,
|
|
702
|
-
y: tp.y - p.y,
|
|
703
|
-
};
|
|
704
|
-
|
|
705
|
-
for (const resolution of resolutionSet) {
|
|
706
|
-
resolution.zoom(
|
|
707
|
-
2 ** zoomEvent.zDelta,
|
|
708
|
-
channel == "y" ? 1 - p[channel] : p[channel],
|
|
709
|
-
channel == "x" ? delta.x : -delta.y
|
|
710
|
-
);
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
this.context.animator.requestRender();
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* @param {string} channel
|
|
719
|
-
* @param {import("./containerView").ResolutionTarget} resolutionType
|
|
720
|
-
* @returns {import("../spec/view").ResolutionBehavior}
|
|
721
|
-
*/
|
|
722
|
-
getDefaultResolution(channel, resolutionType) {
|
|
723
|
-
// TODO: Default to shared when working with genomic coordinates
|
|
724
|
-
return "independent";
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
/**
|
|
729
|
-
* @param {import("../spec/view").ViewBackground} viewBackground
|
|
730
|
-
* @returns {import("../spec/view").UnitSpec}
|
|
731
|
-
*/
|
|
732
|
-
export function createBackground(viewBackground) {
|
|
733
|
-
return {
|
|
734
|
-
configurableVisibility: false,
|
|
735
|
-
data: { values: [{}] },
|
|
736
|
-
mark: {
|
|
737
|
-
fill: null,
|
|
738
|
-
strokeWidth: 1.0,
|
|
739
|
-
fillOpacity: viewBackground.fill ? 1.0 : 0, // TODO: This should be handled at lower level
|
|
740
|
-
...viewBackground,
|
|
741
|
-
type: "rect",
|
|
742
|
-
clip: false, // Shouldn't be needed
|
|
743
|
-
tooltip: null,
|
|
744
|
-
},
|
|
745
|
-
};
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
/**
|
|
749
|
-
*
|
|
750
|
-
* @param {View} view
|
|
751
|
-
* @returns
|
|
752
|
-
*/
|
|
753
|
-
function getZoomableResolutions(view) {
|
|
754
|
-
/** @type {Record<import("../spec/channel").PrimaryPositionalChannel, Set<import("./scaleResolution").default>>} */
|
|
755
|
-
const resolutions = {
|
|
756
|
-
x: new Set(),
|
|
757
|
-
y: new Set(),
|
|
758
|
-
};
|
|
759
|
-
|
|
760
|
-
// Find all resolutions (scales) that are candidates for zooming
|
|
761
|
-
view.visit((v) => {
|
|
762
|
-
for (const [channel, resolutionSet] of Object.entries(resolutions)) {
|
|
763
|
-
const resolution = v.getScaleResolution(channel);
|
|
764
|
-
if (resolution && resolution.isZoomable()) {
|
|
765
|
-
resolutionSet.add(resolution);
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
return resolutions;
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
/**
|
|
774
|
-
* @param {View} view
|
|
775
|
-
*/
|
|
776
|
-
export function isClippedChildren(view) {
|
|
777
|
-
let clipped = true;
|
|
778
|
-
|
|
779
|
-
view.visit((v) => {
|
|
780
|
-
if (v instanceof UnitView) {
|
|
781
|
-
clipped &&= v.mark.properties.clip;
|
|
782
|
-
}
|
|
783
|
-
});
|
|
784
|
-
|
|
785
|
-
return clipped;
|
|
786
|
-
}
|