@genome-spy/core 0.14.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/dist/index.js +224 -0
- package/dist/style.css +1 -0
- package/package.json +54 -0
- package/src/data/collector.js +178 -0
- package/src/data/collector.test.js +82 -0
- package/src/data/dataFlow.js +109 -0
- package/src/data/dataFlow.test.js +3 -0
- package/src/data/facetNode.js +17 -0
- package/src/data/flow.test.js +71 -0
- package/src/data/flowBatch.d.ts +40 -0
- package/src/data/flowNode.js +283 -0
- package/src/data/flowNode.test.js +49 -0
- package/src/data/flowOptimizer.js +117 -0
- package/src/data/flowOptimizer.test.js +192 -0
- package/src/data/flowTestUtils.js +63 -0
- package/src/data/formats/fasta.js +32 -0
- package/src/data/formats/fasta.test.js +26 -0
- package/src/data/sources/dataSource.js +22 -0
- package/src/data/sources/dataSourceFactory.js +24 -0
- package/src/data/sources/dataUtils.js +31 -0
- package/src/data/sources/dynamicCallbackSource.js +56 -0
- package/src/data/sources/dynamicSource.js +36 -0
- package/src/data/sources/inlineSource.js +69 -0
- package/src/data/sources/inlineSource.test.js +55 -0
- package/src/data/sources/namedSource.js +74 -0
- package/src/data/sources/sequenceSource.js +46 -0
- package/src/data/sources/sequenceSource.test.js +45 -0
- package/src/data/sources/urlSource.js +74 -0
- package/src/data/transforms/aggregate.js +69 -0
- package/src/data/transforms/clone.js +40 -0
- package/src/data/transforms/clone.test.js +10 -0
- package/src/data/transforms/coverage.js +187 -0
- package/src/data/transforms/coverage.test.js +122 -0
- package/src/data/transforms/filter.js +37 -0
- package/src/data/transforms/filter.test.js +17 -0
- package/src/data/transforms/filterScoredLabels.js +134 -0
- package/src/data/transforms/flattenCompressedExons.js +57 -0
- package/src/data/transforms/flattenDelimited.js +68 -0
- package/src/data/transforms/flattenDelimited.test.js +86 -0
- package/src/data/transforms/flattenSequence.js +39 -0
- package/src/data/transforms/flattenSequence.test.js +33 -0
- package/src/data/transforms/formula.js +39 -0
- package/src/data/transforms/formula.test.js +18 -0
- package/src/data/transforms/identifier.js +108 -0
- package/src/data/transforms/identifier.test.js +82 -0
- package/src/data/transforms/linearizeGenomicCoordinate.js +101 -0
- package/src/data/transforms/measureText.js +44 -0
- package/src/data/transforms/pileup.js +128 -0
- package/src/data/transforms/pileup.test.js +69 -0
- package/src/data/transforms/project.js +41 -0
- package/src/data/transforms/project.test.js +31 -0
- package/src/data/transforms/regexExtract.js +61 -0
- package/src/data/transforms/regexExtract.test.js +66 -0
- package/src/data/transforms/regexFold.js +141 -0
- package/src/data/transforms/regexFold.test.js +159 -0
- package/src/data/transforms/sample.js +101 -0
- package/src/data/transforms/sample.test.js +37 -0
- package/src/data/transforms/stack.js +137 -0
- package/src/data/transforms/stack.test.js +90 -0
- package/src/data/transforms/transformFactory.js +60 -0
- package/src/encoder/accessor.js +82 -0
- package/src/encoder/accessor.test.js +46 -0
- package/src/encoder/encoder.js +369 -0
- package/src/encoder/encoder.test.js +97 -0
- package/src/fonts/Lato-Regular.json +1267 -0
- package/src/fonts/Lato-Regular.png +0 -0
- package/src/fonts/OFL.txt +93 -0
- package/src/fonts/README.md +3 -0
- package/src/fonts/bmFont.d.ts +58 -0
- package/src/fonts/bmFontManager.js +357 -0
- package/src/fonts/bmFontMetrics.js +108 -0
- package/src/genome/genome.js +305 -0
- package/src/genome/genome.test.js +152 -0
- package/src/genome/genomeStore.js +54 -0
- package/src/genome/locusFormat.js +31 -0
- package/src/genome/scaleIndex.js +199 -0
- package/src/genome/scaleIndex.test.js +61 -0
- package/src/genome/scaleLocus.js +112 -0
- package/src/genome/scaleLocus.test.js +3 -0
- package/src/genomeSpy.js +753 -0
- package/src/gl/arrayBuilder.js +199 -0
- package/src/gl/dataToVertices.js +621 -0
- package/src/gl/includes/common.glsl +63 -0
- package/src/gl/includes/fp64-arithmetic.glsl +187 -0
- package/src/gl/includes/fp64-utils.js +132 -0
- package/src/gl/includes/picking.fragment.glsl +3 -0
- package/src/gl/includes/picking.vertex.glsl +29 -0
- package/src/gl/includes/sampleFacet.glsl +107 -0
- package/src/gl/includes/scales.glsl +79 -0
- package/src/gl/includes/scales_fp64.glsl +30 -0
- package/src/gl/link.fragment.glsl +18 -0
- package/src/gl/link.vertex.glsl +111 -0
- package/src/gl/point.fragment.glsl +123 -0
- package/src/gl/point.vertex.glsl +128 -0
- package/src/gl/rect.fragment.glsl +51 -0
- package/src/gl/rect.vertex.glsl +114 -0
- package/src/gl/rule.fragment.glsl +52 -0
- package/src/gl/rule.vertex.glsl +89 -0
- package/src/gl/text.fragment.glsl +31 -0
- package/src/gl/text.vertex.glsl +246 -0
- package/src/gl/webGLHelper.js +490 -0
- package/src/img/bowtie.svg +1 -0
- package/src/img/genomespy-favicon.svg +34 -0
- package/src/index.html +11 -0
- package/src/index.js +151 -0
- package/src/marks/link.js +189 -0
- package/src/marks/mark.js +867 -0
- package/src/marks/markUtils.js +109 -0
- package/src/marks/pointMark.js +279 -0
- package/src/marks/rectMark.js +236 -0
- package/src/marks/rule.js +231 -0
- package/src/marks/text.js +274 -0
- package/src/options.d.ts +9 -0
- package/src/scale/colorUtils.js +184 -0
- package/src/scale/glslScaleGenerator.js +462 -0
- package/src/scale/scale.js +441 -0
- package/src/scale/scale.test.js +323 -0
- package/src/scale/ticks.js +198 -0
- package/src/scale/ticks.test.js +39 -0
- package/src/singlePageApp.js +13 -0
- package/src/spec/axis.d.ts +296 -0
- package/src/spec/channel.d.ts +127 -0
- package/src/spec/data.d.ts +185 -0
- package/src/spec/font.d.ts +15 -0
- package/src/spec/genome.d.ts +35 -0
- package/src/spec/mark.d.ts +432 -0
- package/src/spec/root.d.ts +22 -0
- package/src/spec/scale.d.ts +265 -0
- package/src/spec/tooltip.d.ts +9 -0
- package/src/spec/transform.d.ts +479 -0
- package/src/spec/view.d.ts +215 -0
- package/src/styles/genome-spy.scss +153 -0
- package/src/tooltip/dataTooltipHandler.js +59 -0
- package/src/tooltip/refseqGeneTooltipHandler.js +77 -0
- package/src/tooltip/tooltipHandler.ts +12 -0
- package/src/types/filetypes.d.ts +4 -0
- package/src/types/flatqueue.d.ts +53 -0
- package/src/types/glsl.d.ts +4 -0
- package/src/types/object.d.ts +21 -0
- package/src/types/vega-scale.d.ts +60 -0
- package/src/utils/animator.js +83 -0
- package/src/utils/arrayUtils.js +55 -0
- package/src/utils/binnedRangeIndex.js +83 -0
- package/src/utils/clamp.js +8 -0
- package/src/utils/cloner.js +32 -0
- package/src/utils/cloner.test.js +23 -0
- package/src/utils/coalesce.js +11 -0
- package/src/utils/coalesce.test.js +15 -0
- package/src/utils/concatIterables.js +26 -0
- package/src/utils/concatIterables.test.js +7 -0
- package/src/utils/debounce.js +37 -0
- package/src/utils/domainArray.js +224 -0
- package/src/utils/domainArray.test.js +129 -0
- package/src/utils/eerp.js +13 -0
- package/src/utils/expression.js +32 -0
- package/src/utils/field.js +28 -0
- package/src/utils/fisheye.js +60 -0
- package/src/utils/formatObject.js +31 -0
- package/src/utils/html.js +23 -0
- package/src/utils/html.test.js +13 -0
- package/src/utils/indexer.js +43 -0
- package/src/utils/indexer.test.js +46 -0
- package/src/utils/inertia.js +124 -0
- package/src/utils/interactionEvent.js +33 -0
- package/src/utils/iterateNestedMaps.js +21 -0
- package/src/utils/iterateNestedMaps.test.js +32 -0
- package/src/utils/kWayMerge.js +42 -0
- package/src/utils/kWayMerge.test.js +25 -0
- package/src/utils/layout/flexLayout.js +336 -0
- package/src/utils/layout/flexLayout.test.js +296 -0
- package/src/utils/layout/padding.js +107 -0
- package/src/utils/layout/point.js +23 -0
- package/src/utils/layout/rectangle.js +282 -0
- package/src/utils/layout/rectangle.test.js +171 -0
- package/src/utils/mergeObjects.js +99 -0
- package/src/utils/mergeObjects.test.js +41 -0
- package/src/utils/numberExtractor.js +24 -0
- package/src/utils/numberExtractor.test.js +5 -0
- package/src/utils/point.js +14 -0
- package/src/utils/propertyCacher.js +70 -0
- package/src/utils/propertyCacher.test.js +84 -0
- package/src/utils/propertyCoalescer.js +37 -0
- package/src/utils/propertyCoalescer.test.js +21 -0
- package/src/utils/reservationMap.js +103 -0
- package/src/utils/reservationMap.test.js +19 -0
- package/src/utils/scaleNull.js +19 -0
- package/src/utils/setOperations.js +75 -0
- package/src/utils/smoothstep.js +10 -0
- package/src/utils/throttle.js +34 -0
- package/src/utils/topK.js +76 -0
- package/src/utils/topK.test.js +63 -0
- package/src/utils/transition.js +74 -0
- package/src/utils/ui/tooltip.js +189 -0
- package/src/utils/url.js +22 -0
- package/src/utils/variableTools.js +24 -0
- package/src/utils/variableTools.test.js +12 -0
- package/src/view/axisResolution.js +135 -0
- package/src/view/axisResolution.test.js +200 -0
- package/src/view/axisView.js +746 -0
- package/src/view/channel.js +5 -0
- package/src/view/concatView.js +296 -0
- package/src/view/containerView.js +141 -0
- package/src/view/decoratorView.js +510 -0
- package/src/view/facetView.js +488 -0
- package/src/view/flowBuilder.js +362 -0
- package/src/view/flowBuilder.test.js +124 -0
- package/src/view/importView.js +19 -0
- package/src/view/layerView.js +60 -0
- package/src/view/rendering.d.ts +44 -0
- package/src/view/renderingContext/compositeViewRenderingContext.js +51 -0
- package/src/view/renderingContext/deferredViewRenderingContext.js +174 -0
- package/src/view/renderingContext/layoutRecorderViewRenderingContext.js +128 -0
- package/src/view/renderingContext/simpleViewRenderingContext.js +62 -0
- package/src/view/renderingContext/svgViewRenderingContext.js +121 -0
- package/src/view/renderingContext/viewRenderingContext.js +41 -0
- package/src/view/scaleResolution.js +756 -0
- package/src/view/scaleResolution.test.js +571 -0
- package/src/view/scaleResolutionApi.d.ts +40 -0
- package/src/view/testUtils.js +48 -0
- package/src/view/unitView.js +368 -0
- package/src/view/view.js +589 -0
- package/src/view/view.test.js +213 -0
- package/src/view/viewContext.d.ts +57 -0
- package/src/view/viewFactory.js +179 -0
- package/src/view/viewFactory.test.js +16 -0
- package/src/view/viewUtils.js +420 -0
|
@@ -0,0 +1,488 @@
|
|
|
1
|
+
import { isFacetFieldDef, isFacetMapping } from "./viewUtils";
|
|
2
|
+
import ContainerView from "./containerView";
|
|
3
|
+
import UnitView from "./unitView";
|
|
4
|
+
import { cross } from "d3-array";
|
|
5
|
+
import { mapToPixelCoords } from "../utils/layout/flexLayout";
|
|
6
|
+
import { OrdinalDomain } from "../utils/domainArray";
|
|
7
|
+
import Rectangle from "../utils/layout/rectangle";
|
|
8
|
+
import coalesce from "../utils/coalesce";
|
|
9
|
+
import { field as vegaField } from "vega-util";
|
|
10
|
+
import DecoratorView from "./decoratorView";
|
|
11
|
+
import Padding from "../utils/layout/padding";
|
|
12
|
+
|
|
13
|
+
const DEFAULT_SPACING = 20;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {"column" | "row"} FacetChannel
|
|
17
|
+
* @type {FacetChannel[]}
|
|
18
|
+
*/
|
|
19
|
+
const FACET_CHANNELS = ["column", "row"];
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @type {Record<FacetChannel, FacetChannel>}
|
|
23
|
+
*/
|
|
24
|
+
// eslint-disable-next-line no-unused-vars
|
|
25
|
+
const PERPENDICULAR_FACET_CHANNELS = {
|
|
26
|
+
column: "row",
|
|
27
|
+
row: "column",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// https://vega.github.io/vega-lite/docs/header.html#labels
|
|
31
|
+
// TODO: Configurable
|
|
32
|
+
const headerConfig = {
|
|
33
|
+
labelFontSize: 12,
|
|
34
|
+
labelColor: "black",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** @type {Record<FacetChannel, any>} */
|
|
38
|
+
const headerConfigs = {
|
|
39
|
+
column: {
|
|
40
|
+
...headerConfig,
|
|
41
|
+
labelAngle: 0,
|
|
42
|
+
},
|
|
43
|
+
row: {
|
|
44
|
+
...headerConfig,
|
|
45
|
+
labelAngle: -90,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Implements (a subset of) the Vega-Lite's Facet-operator:
|
|
51
|
+
* https://vega.github.io/vega-lite/docs/facet.html
|
|
52
|
+
*
|
|
53
|
+
* TODO:
|
|
54
|
+
* - Facet channel titles
|
|
55
|
+
* - Suppress redundant axes
|
|
56
|
+
* - Make this thing configurable
|
|
57
|
+
*
|
|
58
|
+
* @typedef {import("./view").default} View
|
|
59
|
+
* @typedef {import("./layerView").default} LayerView
|
|
60
|
+
* @typedef {import("./viewUtils").FacetFieldDef} FacetFieldDef
|
|
61
|
+
* @typedef {import("./viewUtils").FacetMapping} FacetMapping
|
|
62
|
+
* @typedef {import("../utils/layout/flexLayout").LocSize} LocSize
|
|
63
|
+
* @typedef {import("../utils/layout/flexLayout").SizeDef} SizeDef
|
|
64
|
+
*
|
|
65
|
+
* @typedef {object} FacetDimension Stuff for working with facet dimensions
|
|
66
|
+
* @prop {function} accessor
|
|
67
|
+
* @prop {boolean[] | string[] | number[]} factors
|
|
68
|
+
* @prop {FacetFieldDef} facetFieldDef
|
|
69
|
+
*
|
|
70
|
+
*/
|
|
71
|
+
export default class FacetView extends ContainerView {
|
|
72
|
+
/**
|
|
73
|
+
*
|
|
74
|
+
* @param {import("./viewUtils").FacetSpec} spec
|
|
75
|
+
* @param {import("./viewUtils").ViewContext} context
|
|
76
|
+
* @param {ContainerView} parent
|
|
77
|
+
* @param {string} name
|
|
78
|
+
*/
|
|
79
|
+
constructor(spec, context, parent, name) {
|
|
80
|
+
super(spec, context, parent, name);
|
|
81
|
+
|
|
82
|
+
this.spec = spec;
|
|
83
|
+
|
|
84
|
+
this.child = /** @type { UnitView | LayerView | DecoratorView } */ (
|
|
85
|
+
context.createView(spec.spec, this, `facet`)
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Faceted views for displaying the facet labels
|
|
90
|
+
*
|
|
91
|
+
* @type {Record<FacetChannel, UnitView>}
|
|
92
|
+
*/
|
|
93
|
+
this._labelViews = Object.fromEntries(
|
|
94
|
+
FACET_CHANNELS.map((channel) => [
|
|
95
|
+
channel,
|
|
96
|
+
new UnitView(
|
|
97
|
+
createLabelViewSpec(headerConfigs[channel]),
|
|
98
|
+
this.context,
|
|
99
|
+
this,
|
|
100
|
+
`facetLabel-${channel}`
|
|
101
|
+
),
|
|
102
|
+
])
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
/** @type {Record<FacetChannel, FacetDimension>} */
|
|
106
|
+
this._facetDimensions = { column: undefined, row: undefined };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @returns {IterableIterator<View>}
|
|
111
|
+
*/
|
|
112
|
+
*[Symbol.iterator]() {
|
|
113
|
+
yield this.child;
|
|
114
|
+
for (const view of Object.values(this._labelViews)) {
|
|
115
|
+
yield view;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* @param {import("./view").default} child
|
|
121
|
+
* @param {import("./view").default} replacement
|
|
122
|
+
*/
|
|
123
|
+
replaceChild(child, replacement) {
|
|
124
|
+
if (child !== this.child) {
|
|
125
|
+
throw new Error("Not my child!");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
this.child = /** @type {UnitView | LayerView | DecoratorView} */ (
|
|
129
|
+
replacement
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
transformData() {
|
|
134
|
+
super.transformData();
|
|
135
|
+
// A hacky solution for updating facets. TODO: Something more robust.
|
|
136
|
+
this.updateFacets();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
*
|
|
141
|
+
* @param {"row" | "column"} channel
|
|
142
|
+
*/
|
|
143
|
+
getAccessor(channel) {
|
|
144
|
+
/** @type {import("./viewUtils").FacetMapping} */
|
|
145
|
+
let facetMapping;
|
|
146
|
+
if (isFacetMapping(this.spec.facet)) {
|
|
147
|
+
facetMapping = this.spec.facet; // Mark provides encodings with defaults and possible modifications
|
|
148
|
+
} else if (isFacetFieldDef(this.spec.facet)) {
|
|
149
|
+
// TODO: Check "columns"
|
|
150
|
+
facetMapping = {
|
|
151
|
+
column: this.spec.facet,
|
|
152
|
+
};
|
|
153
|
+
} else {
|
|
154
|
+
throw new Error(
|
|
155
|
+
"Invalid facet specification: " +
|
|
156
|
+
JSON.stringify(this.spec.facet)
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (facetMapping[channel]) {
|
|
161
|
+
return this.context.accessorFactory.createAccessor(
|
|
162
|
+
facetMapping[channel]
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
updateFacets() {
|
|
168
|
+
/** @type {import("./viewUtils").FacetMapping} */
|
|
169
|
+
let facetMapping;
|
|
170
|
+
if (isFacetMapping(this.spec.facet)) {
|
|
171
|
+
facetMapping = this.spec.facet; // Mark provides encodings with defaults and possible modifications
|
|
172
|
+
} else if (isFacetFieldDef(this.spec.facet)) {
|
|
173
|
+
// TODO: Check "columns"
|
|
174
|
+
facetMapping = {
|
|
175
|
+
column: this.spec.facet,
|
|
176
|
+
};
|
|
177
|
+
} else {
|
|
178
|
+
throw new Error(
|
|
179
|
+
"Invalid facet specification: " +
|
|
180
|
+
JSON.stringify(this.spec.facet)
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
for (const channel of FACET_CHANNELS) {
|
|
185
|
+
const facetFieldDef = facetMapping[channel];
|
|
186
|
+
if (!facetFieldDef) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const accessor = this.context.accessorFactory.createAccessor(
|
|
191
|
+
facetMapping[channel]
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const factors = new OrdinalDomain().extendAllWithAccessor(
|
|
195
|
+
this.getData(),
|
|
196
|
+
accessor
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
// TODO: Configurable sorting
|
|
200
|
+
factors.sort();
|
|
201
|
+
|
|
202
|
+
this._facetDimensions[channel] = {
|
|
203
|
+
accessor,
|
|
204
|
+
factors,
|
|
205
|
+
facetFieldDef,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
updateLabels() {
|
|
211
|
+
for (const channel of FACET_CHANNELS) {
|
|
212
|
+
const facetDimension = this._facetDimensions[channel];
|
|
213
|
+
this._labelViews[channel].updateData(
|
|
214
|
+
facetDimension
|
|
215
|
+
? facetDimension.factors.map((d) => ({ data: d }))
|
|
216
|
+
: []
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Returns an accessor that returns a (composite) key for partitioning the data
|
|
223
|
+
*
|
|
224
|
+
* @param {View} [whoIsAsking] Passed to the immediate parent. Allows for
|
|
225
|
+
* selectively breaking the inheritance.
|
|
226
|
+
* @return {function(object):any}
|
|
227
|
+
*/
|
|
228
|
+
getFacetAccessor(whoIsAsking) {
|
|
229
|
+
const { column, row } = this._facetDimensions;
|
|
230
|
+
|
|
231
|
+
if (Object.values(this._labelViews).includes(whoIsAsking)) {
|
|
232
|
+
// Label views are faceted by the facet labels
|
|
233
|
+
return vegaField("data");
|
|
234
|
+
} else if (column && row) {
|
|
235
|
+
const columnField = vegaField(column.facetFieldDef.field);
|
|
236
|
+
const rowField = vegaField(row.facetFieldDef.field);
|
|
237
|
+
return /** @param {object} d */ (d) =>
|
|
238
|
+
columnField(d) + "," + rowField(d);
|
|
239
|
+
} else if (column) {
|
|
240
|
+
return vegaField(column.facetFieldDef.field);
|
|
241
|
+
} else if (row) {
|
|
242
|
+
return vegaField(row.facetFieldDef.field);
|
|
243
|
+
} else {
|
|
244
|
+
throw new Error("updateFacets() must be called first!");
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
getFacetGroups() {
|
|
249
|
+
const { column, row } = this._facetDimensions;
|
|
250
|
+
|
|
251
|
+
if (column && row) {
|
|
252
|
+
return cross(
|
|
253
|
+
column.factors,
|
|
254
|
+
row.factors,
|
|
255
|
+
(col, row) => col + "," + row
|
|
256
|
+
);
|
|
257
|
+
} else if (column) {
|
|
258
|
+
return column.factors;
|
|
259
|
+
} else if (row) {
|
|
260
|
+
return row.factors;
|
|
261
|
+
} else {
|
|
262
|
+
throw new Error("updateFacets() must be called first!");
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
getSize() {
|
|
267
|
+
// TODO: IMPLEMENT!
|
|
268
|
+
return super.getSize();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* @param {import("./renderingContext/viewRenderingContext").default} context
|
|
273
|
+
* @param {import("../utils/layout/rectangle").default} coords
|
|
274
|
+
* @param {import("./view").RenderingOptions} [options]
|
|
275
|
+
*/
|
|
276
|
+
render(context, coords, options = {}) {
|
|
277
|
+
if (!this.isVisible()) {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
coords = coords.shrink(this.getPadding());
|
|
282
|
+
context.pushView(this, coords);
|
|
283
|
+
|
|
284
|
+
// Size of the view that is being repeated for all the facets
|
|
285
|
+
const childSize = this.child.getSize();
|
|
286
|
+
|
|
287
|
+
// Fugly hack. TODO: Figure out a systematic phase for doing this
|
|
288
|
+
if (!this._labelsUpdated) {
|
|
289
|
+
this.updateLabels();
|
|
290
|
+
this._labelsUpdated = true;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// TODO: Validate. Columns is not compatible with row channel
|
|
294
|
+
const wrap = this.spec.columns && this._facetDimensions.column;
|
|
295
|
+
|
|
296
|
+
// We use two flexLayouts to create a grid for the facets.
|
|
297
|
+
// Stride and offset control how the cells in the grid are allocated
|
|
298
|
+
// for the facets and the intervening facet labels.
|
|
299
|
+
const xStride = 1;
|
|
300
|
+
const xOffset = wrap ? 0 : this._facetDimensions.row ? 1 : 0;
|
|
301
|
+
const yStride = wrap ? 2 : 1;
|
|
302
|
+
const yOffset = this._facetDimensions.column ? 1 : 0;
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* @param {SizeDef} childSize
|
|
306
|
+
* @param {number} count
|
|
307
|
+
* @param {number} stride
|
|
308
|
+
* @param {number} offset
|
|
309
|
+
*/
|
|
310
|
+
const calculateCellSizes = (childSize, count, stride, offset) => {
|
|
311
|
+
// TODO: take the channel into account
|
|
312
|
+
const labelSize = { px: headerConfig.labelFontSize };
|
|
313
|
+
|
|
314
|
+
/** @type {SizeDef[]} */
|
|
315
|
+
const cellSizes = [];
|
|
316
|
+
|
|
317
|
+
for (let i = 0; i < offset; i++) {
|
|
318
|
+
cellSizes.push(labelSize);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
for (let i = 0; i < count; i++) {
|
|
322
|
+
for (let j = 1; j < stride; j++) {
|
|
323
|
+
cellSizes.push(labelSize);
|
|
324
|
+
}
|
|
325
|
+
cellSizes.push(childSize);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return cellSizes;
|
|
329
|
+
};
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
*
|
|
333
|
+
* @param {FacetChannel} channel
|
|
334
|
+
* @param {number} count Number of factors
|
|
335
|
+
*/
|
|
336
|
+
const computeFlexCoords = (channel, count) => {
|
|
337
|
+
const dimension = this._facetDimensions[channel];
|
|
338
|
+
|
|
339
|
+
const spacing = coalesce(
|
|
340
|
+
dimension ? dimension.facetFieldDef.spacing : undefined,
|
|
341
|
+
this.spec.spacing,
|
|
342
|
+
DEFAULT_SPACING
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
const cellSizes =
|
|
346
|
+
channel == "column"
|
|
347
|
+
? calculateCellSizes(
|
|
348
|
+
childSize.width,
|
|
349
|
+
count,
|
|
350
|
+
xStride,
|
|
351
|
+
xOffset
|
|
352
|
+
)
|
|
353
|
+
: calculateCellSizes(
|
|
354
|
+
childSize.height,
|
|
355
|
+
count,
|
|
356
|
+
yStride,
|
|
357
|
+
wrap ? 0 : yOffset
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
return mapToPixelCoords(
|
|
361
|
+
cellSizes,
|
|
362
|
+
coords[channel == "column" ? "width" : "height"],
|
|
363
|
+
{
|
|
364
|
+
spacing,
|
|
365
|
+
devicePixelRatio: window.devicePixelRatio,
|
|
366
|
+
}
|
|
367
|
+
);
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
let nCols = 0;
|
|
371
|
+
let nRows = 0;
|
|
372
|
+
let n = 0;
|
|
373
|
+
|
|
374
|
+
if (wrap) {
|
|
375
|
+
// Wrapping layout
|
|
376
|
+
n = this._facetDimensions.column.factors.length;
|
|
377
|
+
nCols = this.spec.columns;
|
|
378
|
+
nRows = Math.ceil(n / nCols);
|
|
379
|
+
} else {
|
|
380
|
+
/** @param {FacetDimension} facetDimension */
|
|
381
|
+
const getCount = (facetDimension) =>
|
|
382
|
+
facetDimension ? facetDimension.factors.length : 1;
|
|
383
|
+
|
|
384
|
+
nCols = getCount(this._facetDimensions.column);
|
|
385
|
+
nRows = getCount(this._facetDimensions.row);
|
|
386
|
+
n = nCols * nRows;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const columnFlexCoords = computeFlexCoords("column", nCols);
|
|
390
|
+
const rowFlexCoords = computeFlexCoords("row", nRows);
|
|
391
|
+
|
|
392
|
+
const axisSizes =
|
|
393
|
+
this.child instanceof DecoratorView
|
|
394
|
+
? this.child.getAxisSizes()
|
|
395
|
+
: Padding.createUniformPadding(0);
|
|
396
|
+
|
|
397
|
+
const facetIds = this.getFacetGroups();
|
|
398
|
+
|
|
399
|
+
// Render column labels
|
|
400
|
+
if (this._facetDimensions.column) {
|
|
401
|
+
const factors = this._facetDimensions.column.factors;
|
|
402
|
+
for (let x = 0; x < factors.length; x++) {
|
|
403
|
+
// Take wrapping labels into account
|
|
404
|
+
const xCell = columnFlexCoords[(x % nCols) * xStride + xOffset];
|
|
405
|
+
const yCell = rowFlexCoords[Math.floor(x / nCols) * yStride];
|
|
406
|
+
this._labelViews.column.render(
|
|
407
|
+
context,
|
|
408
|
+
Rectangle.create(
|
|
409
|
+
xCell.location + axisSizes.left,
|
|
410
|
+
yCell.location,
|
|
411
|
+
xCell.size - axisSizes.width,
|
|
412
|
+
yCell.size
|
|
413
|
+
).translateBy(coords),
|
|
414
|
+
{ ...options, facetId: factors[x] }
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Render row labels
|
|
420
|
+
if (this._facetDimensions.row) {
|
|
421
|
+
const factors = this._facetDimensions.row.factors;
|
|
422
|
+
for (let y = 0; y < factors.length; y++) {
|
|
423
|
+
const xCell = columnFlexCoords[0];
|
|
424
|
+
const yCell = rowFlexCoords[y * yStride + yOffset];
|
|
425
|
+
this._labelViews.row.render(
|
|
426
|
+
context,
|
|
427
|
+
Rectangle.create(
|
|
428
|
+
xCell.location,
|
|
429
|
+
yCell.location + axisSizes.top,
|
|
430
|
+
xCell.size,
|
|
431
|
+
yCell.size - axisSizes.height
|
|
432
|
+
).translateBy(coords),
|
|
433
|
+
{ ...options, facetId: factors[y] }
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Render facets
|
|
439
|
+
let i = 0;
|
|
440
|
+
for (let x = 0; x < nCols; x++) {
|
|
441
|
+
for (let y = 0; y < nRows; y++) {
|
|
442
|
+
if (i >= n) break;
|
|
443
|
+
|
|
444
|
+
const xCell = columnFlexCoords[x * xStride + xOffset];
|
|
445
|
+
const yCell = rowFlexCoords[y * yStride + yOffset];
|
|
446
|
+
this.child.render(
|
|
447
|
+
context,
|
|
448
|
+
new Rectangle(
|
|
449
|
+
xCell.location,
|
|
450
|
+
yCell.location,
|
|
451
|
+
xCell.size,
|
|
452
|
+
yCell.size
|
|
453
|
+
).translateBy(coords),
|
|
454
|
+
{ ...options, facetId: facetIds[i] }
|
|
455
|
+
);
|
|
456
|
+
i++;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
context.popView(this);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
*
|
|
466
|
+
*/
|
|
467
|
+
function createLabelViewSpec(headerConfig) {
|
|
468
|
+
// TODO: Support styling: https://vega.github.io/vega-lite/docs/header.html#labels
|
|
469
|
+
|
|
470
|
+
/** @type {import("./viewUtils").UnitSpec} */
|
|
471
|
+
const titleView = {
|
|
472
|
+
data: {
|
|
473
|
+
values: [],
|
|
474
|
+
},
|
|
475
|
+
mark: {
|
|
476
|
+
type: "text",
|
|
477
|
+
clip: false,
|
|
478
|
+
angle: headerConfig.labelAngle,
|
|
479
|
+
},
|
|
480
|
+
encoding: {
|
|
481
|
+
text: { field: "data", type: "nominal" },
|
|
482
|
+
size: { value: headerConfig.labelFontSize },
|
|
483
|
+
color: { value: headerConfig.labelColor },
|
|
484
|
+
},
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
return titleView;
|
|
488
|
+
}
|