@genome-spy/core 0.19.0 → 0.21.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 +46 -119
- package/dist/schema.json +213 -25
- package/package.json +4 -3
- package/src/data/collector.test.js +2 -0
- package/src/data/dataFlow.test.js +2 -0
- package/src/data/flow.test.js +1 -0
- package/src/data/flowNode.test.js +1 -0
- package/src/data/flowOptimizer.test.js +1 -0
- package/src/data/formats/fasta.test.js +1 -0
- package/src/data/sources/inlineSource.test.js +1 -0
- package/src/data/sources/sequenceSource.test.js +1 -0
- package/src/data/transforms/clone.test.js +1 -0
- package/src/data/transforms/coverage.test.js +1 -0
- package/src/data/transforms/filter.test.js +1 -0
- package/src/data/transforms/flattenDelimited.test.js +1 -0
- package/src/data/transforms/flattenSequence.test.js +1 -0
- package/src/data/transforms/formula.test.js +1 -0
- package/src/data/transforms/identifier.test.js +1 -0
- package/src/data/transforms/pileup.test.js +1 -0
- package/src/data/transforms/project.test.js +1 -0
- package/src/data/transforms/regexExtract.test.js +1 -0
- package/src/data/transforms/regexFold.test.js +1 -0
- package/src/data/transforms/sample.test.js +1 -0
- package/src/data/transforms/stack.test.js +1 -0
- package/src/encoder/accessor.test.js +1 -0
- package/src/encoder/encoder.test.js +1 -0
- package/src/genome/genome.test.js +1 -0
- package/src/genome/scaleIndex.js +3 -2
- package/src/genome/scaleIndex.test.js +23 -6
- package/src/genome/scaleLocus.test.js +1 -0
- package/src/genomeSpy.js +16 -11
- package/src/gl/dataToVertices.js +52 -52
- package/src/gl/includes/common.glsl +12 -12
- package/src/gl/includes/picking.fragment.glsl +0 -2
- package/src/gl/includes/picking.vertex.glsl +0 -2
- package/src/gl/includes/scales.glsl +33 -2
- package/src/gl/point.vertex.glsl +0 -2
- package/src/gl/rule.vertex.glsl +1 -1
- package/src/gl/webGLHelper.js +0 -3
- package/src/marks/link.js +32 -39
- package/src/marks/mark.js +176 -106
- package/src/marks/pointMark.js +28 -59
- package/src/marks/rectMark.js +38 -33
- package/src/marks/rule.js +31 -21
- package/src/marks/text.js +18 -14
- package/src/scale/glslScaleGenerator.js +56 -17
- package/src/scale/scale.test.js +1 -0
- package/src/scale/ticks.test.js +1 -0
- package/src/spec/mark.d.ts +0 -3
- package/src/spec/scale.d.ts +0 -9
- package/src/spec/title.d.ts +102 -0
- package/src/spec/view.d.ts +6 -4
- package/src/tooltip/dataTooltipHandler.js +3 -2
- package/src/utils/addBaseUrl.test.js +1 -0
- package/src/utils/binnedIndex.js +147 -0
- package/src/utils/binnedIndex.test.js +73 -0
- package/src/utils/cloner.test.js +1 -0
- package/src/utils/coalesce.test.js +1 -0
- package/src/utils/concatIterables.test.js +1 -0
- package/src/utils/domainArray.test.js +1 -0
- package/src/utils/indexer.test.js +1 -0
- package/src/utils/iterateNestedMaps.test.js +1 -0
- package/src/utils/kWayMerge.test.js +1 -0
- package/src/utils/layout/flexLayout.js +35 -3
- package/src/utils/layout/flexLayout.test.js +15 -0
- package/src/utils/layout/grid.js +95 -0
- package/src/utils/layout/grid.test.js +71 -0
- package/src/utils/layout/padding.js +13 -0
- package/src/utils/layout/rectangle.js +6 -0
- package/src/utils/layout/rectangle.test.js +1 -0
- package/src/utils/mergeObjects.test.js +1 -0
- package/src/utils/numberExtractor.test.js +1 -0
- package/src/utils/propertyCacher.test.js +1 -0
- package/src/utils/propertyCoalescer.test.js +1 -0
- package/src/utils/reservationMap.test.js +1 -0
- package/src/utils/topK.test.js +1 -0
- package/src/utils/variableTools.test.js +1 -0
- package/src/view/axisResolution.test.js +1 -0
- package/src/view/axisView.js +3 -5
- package/src/view/concatView.js +24 -275
- package/src/view/flowBuilder.test.js +1 -0
- package/src/view/gridView.js +774 -0
- package/src/view/implicitRootView.js +14 -0
- package/src/view/layerView.js +15 -1
- package/src/view/renderingContext/deferredViewRenderingContext.js +3 -1
- package/src/view/renderingContext/simpleViewRenderingContext.js +3 -1
- package/src/view/scaleResolution.js +5 -11
- package/src/view/scaleResolution.test.js +1 -0
- package/src/view/title.js +165 -0
- package/src/view/unitView.js +9 -5
- package/src/view/view.js +35 -14
- package/src/view/view.test.js +1 -0
- package/src/view/viewContext.d.ts +6 -1
- package/src/view/viewFactory.test.js +1 -0
- package/src/view/viewUtils.js +1 -93
- package/src/view/zoom.js +89 -0
- package/src/gl/includes/fp64-arithmetic.glsl +0 -187
- package/src/gl/includes/fp64-utils.js +0 -142
- package/src/gl/includes/scales_fp64.glsl +0 -30
- package/src/utils/binnedRangeIndex.js +0 -83
- package/src/view/decoratorView.js +0 -513
package/src/marks/rule.js
CHANGED
|
@@ -139,6 +139,20 @@ export default class RuleMark extends Mark {
|
|
|
139
139
|
this.createAndLinkShaders(VERTEX_SHADER, FRAGMENT_SHADER);
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
+
finalizeGraphicsInitialization() {
|
|
143
|
+
super.finalizeGraphicsInitialization();
|
|
144
|
+
|
|
145
|
+
this.gl.useProgram(this.programInfo.program);
|
|
146
|
+
|
|
147
|
+
const props = this.properties;
|
|
148
|
+
|
|
149
|
+
setUniforms(this.programInfo, {
|
|
150
|
+
uMinLength: props.minLength,
|
|
151
|
+
uDashTextureSize: this.dashTextureSize,
|
|
152
|
+
uStrokeCap: ["butt", "square", "round"].indexOf(props.strokeCap),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
142
156
|
updateGraphicsData() {
|
|
143
157
|
const collector = this.unitView.getCollector();
|
|
144
158
|
const itemCount = collector.getItemCount();
|
|
@@ -147,13 +161,12 @@ export default class RuleMark extends Mark {
|
|
|
147
161
|
encoders: this.encoders,
|
|
148
162
|
attributes: this.getAttributes(),
|
|
149
163
|
numItems: Math.max(itemCount, this.properties.minBufferSize || 0),
|
|
150
|
-
buildXIndex: this.properties.buildIndex,
|
|
151
164
|
});
|
|
152
165
|
|
|
153
166
|
builder.addBatches(collector.facetBatches);
|
|
154
167
|
|
|
155
168
|
const vertexData = builder.toArrays();
|
|
156
|
-
this.rangeMap
|
|
169
|
+
this.rangeMap.migrateEntries(vertexData.rangeMap);
|
|
157
170
|
|
|
158
171
|
this.updateBufferInfo(vertexData);
|
|
159
172
|
}
|
|
@@ -162,28 +175,26 @@ export default class RuleMark extends Mark {
|
|
|
162
175
|
* @param {import("../view/rendering").GlobalRenderingOptions} options
|
|
163
176
|
*/
|
|
164
177
|
prepareRender(options) {
|
|
165
|
-
super.prepareRender(options);
|
|
166
|
-
|
|
167
|
-
setUniforms(this.programInfo, {
|
|
168
|
-
uMinLength: this.properties.minLength,
|
|
169
|
-
uDashTextureSize: this.dashTextureSize,
|
|
170
|
-
uStrokeCap: ["butt", "square", "round"].indexOf(
|
|
171
|
-
this.properties.strokeCap
|
|
172
|
-
),
|
|
173
|
-
});
|
|
178
|
+
const ops = super.prepareRender(options);
|
|
174
179
|
|
|
175
180
|
if (this.dashTexture) {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
181
|
+
ops.push(() =>
|
|
182
|
+
setUniforms(this.programInfo, {
|
|
183
|
+
uDashTexture: this.dashTexture,
|
|
184
|
+
uStrokeDashOffset: this.properties.strokeDashOffset,
|
|
185
|
+
})
|
|
186
|
+
);
|
|
180
187
|
}
|
|
181
188
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
189
|
+
ops.push(() =>
|
|
190
|
+
setBuffersAndAttributes(
|
|
191
|
+
this.gl,
|
|
192
|
+
this.programInfo,
|
|
193
|
+
this.vertexArrayInfo
|
|
194
|
+
)
|
|
186
195
|
);
|
|
196
|
+
|
|
197
|
+
return ops;
|
|
187
198
|
}
|
|
188
199
|
|
|
189
200
|
/**
|
|
@@ -201,8 +212,7 @@ export default class RuleMark extends Mark {
|
|
|
201
212
|
count,
|
|
202
213
|
offset
|
|
203
214
|
),
|
|
204
|
-
options
|
|
205
|
-
() => this.rangeMap
|
|
215
|
+
options
|
|
206
216
|
);
|
|
207
217
|
}
|
|
208
218
|
}
|
package/src/marks/text.js
CHANGED
|
@@ -214,13 +214,12 @@ export default class TextMark extends Mark {
|
|
|
214
214
|
charCount,
|
|
215
215
|
this.properties.minBufferSize || 0
|
|
216
216
|
),
|
|
217
|
-
buildXIndex: this.properties.buildIndex,
|
|
218
217
|
});
|
|
219
218
|
|
|
220
219
|
builder.addBatches(collector.facetBatches);
|
|
221
220
|
|
|
222
221
|
const vertexData = builder.toArrays();
|
|
223
|
-
this.rangeMap
|
|
222
|
+
this.rangeMap.migrateEntries(vertexData.rangeMap);
|
|
224
223
|
|
|
225
224
|
this.updateBufferInfo(vertexData);
|
|
226
225
|
}
|
|
@@ -229,7 +228,7 @@ export default class TextMark extends Mark {
|
|
|
229
228
|
* @param {import("../view/rendering").GlobalRenderingOptions} options
|
|
230
229
|
*/
|
|
231
230
|
prepareRender(options) {
|
|
232
|
-
super.prepareRender(options);
|
|
231
|
+
const ops = super.prepareRender(options);
|
|
233
232
|
|
|
234
233
|
let q = 0.35; // TODO: Ensure that this makes sense. Now chosen by trial & error
|
|
235
234
|
if (this.properties.logoLetters) {
|
|
@@ -239,17 +238,23 @@ export default class TextMark extends Mark {
|
|
|
239
238
|
q /= 2;
|
|
240
239
|
}
|
|
241
240
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
241
|
+
ops.push(() =>
|
|
242
|
+
setUniforms(this.programInfo, {
|
|
243
|
+
uTexture: this.font.texture,
|
|
244
|
+
uSdfNumerator:
|
|
245
|
+
this.font.metrics.common.base / (this.glHelper.dpr / q),
|
|
246
|
+
})
|
|
247
|
+
);
|
|
247
248
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
249
|
+
ops.push(() =>
|
|
250
|
+
setBuffersAndAttributes(
|
|
251
|
+
this.gl,
|
|
252
|
+
this.programInfo,
|
|
253
|
+
this.vertexArrayInfo
|
|
254
|
+
)
|
|
252
255
|
);
|
|
256
|
+
|
|
257
|
+
return ops;
|
|
253
258
|
}
|
|
254
259
|
|
|
255
260
|
/**
|
|
@@ -267,8 +272,7 @@ export default class TextMark extends Mark {
|
|
|
267
272
|
count,
|
|
268
273
|
offset
|
|
269
274
|
),
|
|
270
|
-
options
|
|
271
|
-
() => this.rangeMap
|
|
275
|
+
options
|
|
272
276
|
);
|
|
273
277
|
}
|
|
274
278
|
}
|
|
@@ -109,8 +109,8 @@ export function generateScaleGlsl(channel, scale, channelDef) {
|
|
|
109
109
|
const domainUniformName = DOMAIN_PREFIX + primary;
|
|
110
110
|
const rangeName = RANGE_PREFIX + primary;
|
|
111
111
|
|
|
112
|
-
const
|
|
113
|
-
const attributeType =
|
|
112
|
+
const hp = isHighPrecisionScale(scale.type);
|
|
113
|
+
const attributeType = hp ? "vec2" : "float";
|
|
114
114
|
|
|
115
115
|
const domainLength = scale.domain ? scale.domain().length : undefined;
|
|
116
116
|
|
|
@@ -127,9 +127,6 @@ export function generateScaleGlsl(channel, scale, channelDef) {
|
|
|
127
127
|
glsl.push("");
|
|
128
128
|
|
|
129
129
|
glsl.push(`#define ${channel}_DEFINED`);
|
|
130
|
-
if (fp64) {
|
|
131
|
-
glsl.push(`#define ${channel}_FP64`);
|
|
132
|
-
}
|
|
133
130
|
|
|
134
131
|
const { transform } = splitScaleType(scale.type);
|
|
135
132
|
|
|
@@ -139,11 +136,7 @@ export function generateScaleGlsl(channel, scale, channelDef) {
|
|
|
139
136
|
*/
|
|
140
137
|
const makeScaleCall = (name, ...args) =>
|
|
141
138
|
// eslint-disable-next-line no-useless-call
|
|
142
|
-
makeFunctionCall.apply(null, [
|
|
143
|
-
name + (fp64 ? "Fp64" : ""),
|
|
144
|
-
"value",
|
|
145
|
-
...args,
|
|
146
|
-
]);
|
|
139
|
+
makeFunctionCall.apply(null, [name, "value", ...args]);
|
|
147
140
|
|
|
148
141
|
let functionCall;
|
|
149
142
|
switch (transform) {
|
|
@@ -181,6 +174,17 @@ export function generateScaleGlsl(channel, scale, channelDef) {
|
|
|
181
174
|
|
|
182
175
|
case "index":
|
|
183
176
|
case "locus":
|
|
177
|
+
functionCall = makeScaleCall(
|
|
178
|
+
"scaleBandHp",
|
|
179
|
+
"domain",
|
|
180
|
+
rangeName,
|
|
181
|
+
scale.paddingInner(),
|
|
182
|
+
scale.paddingOuter(),
|
|
183
|
+
scale.align(),
|
|
184
|
+
// @ts-expect-error TODO: fix typing
|
|
185
|
+
channelDef.band ?? 0.5
|
|
186
|
+
);
|
|
187
|
+
break;
|
|
184
188
|
case "point":
|
|
185
189
|
case "band":
|
|
186
190
|
functionCall = makeScaleCall(
|
|
@@ -296,10 +300,13 @@ export function generateScaleGlsl(channel, scale, channelDef) {
|
|
|
296
300
|
if (functionCall) {
|
|
297
301
|
const name = domainUniformName;
|
|
298
302
|
if (usesDomain) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
+
if (hp) {
|
|
304
|
+
scaleBody.push(`vec3 domain = ${name};`);
|
|
305
|
+
} else {
|
|
306
|
+
scaleBody.push(
|
|
307
|
+
`vec2 domain = vec2(${name}[slot], ${name}[slot + 1]);`
|
|
308
|
+
);
|
|
309
|
+
}
|
|
303
310
|
}
|
|
304
311
|
|
|
305
312
|
scaleBody.push(`float transformed = ${functionCall};`);
|
|
@@ -344,9 +351,9 @@ ${returnType} ${SCALED_FUNCTION_PREFIX}${channel}() {
|
|
|
344
351
|
isContinuous(scale.type) || isDiscretizing(scale.type)
|
|
345
352
|
? domainLength
|
|
346
353
|
: 2;
|
|
347
|
-
domainUniform =
|
|
348
|
-
|
|
349
|
-
|
|
354
|
+
domainUniform = hp
|
|
355
|
+
? `highp vec3 ${domainUniformName};`
|
|
356
|
+
: `mediump float ${domainUniformName}[${length}];`;
|
|
350
357
|
}
|
|
351
358
|
|
|
352
359
|
return {
|
|
@@ -447,3 +454,35 @@ function makeFunctionCall(name, ...args) {
|
|
|
447
454
|
|
|
448
455
|
return `${name}(${fixedArgs.join(", ")})`;
|
|
449
456
|
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
*
|
|
460
|
+
* @param {string} type
|
|
461
|
+
*/
|
|
462
|
+
export function isHighPrecisionScale(type) {
|
|
463
|
+
return type == "index" || type == "locus";
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* @param {number} x
|
|
468
|
+
* @param {number[]} [arr]
|
|
469
|
+
*/
|
|
470
|
+
export function splitHighPrecision(x, arr) {
|
|
471
|
+
// Maximum precise index number is 2^(23 + 11) ~ 17G
|
|
472
|
+
// Higher number increases precision but makes zooming unstable
|
|
473
|
+
const bs = 2 ** 11;
|
|
474
|
+
|
|
475
|
+
const lo = x % bs;
|
|
476
|
+
const hi = Math.round(x - lo);
|
|
477
|
+
arr ??= [];
|
|
478
|
+
arr[0] = hi;
|
|
479
|
+
arr[1] = lo;
|
|
480
|
+
return arr;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* @param {number[]} domain
|
|
485
|
+
*/
|
|
486
|
+
export function toHighPrecisionDomainUniform(domain) {
|
|
487
|
+
return [...splitHighPrecision(domain[0]), domain[1] - domain[0]];
|
|
488
|
+
}
|
package/src/scale/scale.test.js
CHANGED
package/src/scale/ticks.test.js
CHANGED
package/src/spec/mark.d.ts
CHANGED
|
@@ -407,9 +407,6 @@ export interface MarkConfig
|
|
|
407
407
|
*/
|
|
408
408
|
strokeWidth?: number;
|
|
409
409
|
|
|
410
|
-
// TODO: get rid of this
|
|
411
|
-
dynamicData?: boolean;
|
|
412
|
-
|
|
413
410
|
/**
|
|
414
411
|
* Minimum size for WebGL buffers (number of data items).
|
|
415
412
|
* Allows for using `bufferSubData()` to update graphics.
|
package/src/spec/scale.d.ts
CHANGED
|
@@ -221,15 +221,6 @@ export interface Scale {
|
|
|
221
221
|
* If `true` and the scale is used on a positional channel, it can bee zoomed and translated interactively.
|
|
222
222
|
*/
|
|
223
223
|
zoom?: boolean | ZoomParams;
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Use emulated 64bit floating points on the GPU to increase precision.
|
|
227
|
-
*
|
|
228
|
-
* Emulation has a performance cost when compared to the native 32bit processing, but the effect is negligible in the most cases.
|
|
229
|
-
*
|
|
230
|
-
* __Default value:__ `true` for `"locus"` scale, `false` for others.
|
|
231
|
-
*/
|
|
232
|
-
fp64?: boolean;
|
|
233
224
|
}
|
|
234
225
|
|
|
235
226
|
export interface SchemeParams {
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Adapted from
|
|
3
|
+
* https://github.com/vega/vega/blob/main/packages/vega-typings/types/spec/title.d.ts
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) 2015-2018, University of Washington Interactive Data Lab
|
|
6
|
+
* All rights reserved.
|
|
7
|
+
*
|
|
8
|
+
* BSD-3-Clause License: https://github.com/vega/vega-lite/blob/master/LICENSE
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Align, Baseline, FontStyle, FontWeight } from "./font";
|
|
12
|
+
|
|
13
|
+
export type TitleOrient = "none" | "left" | "right" | "top" | "bottom";
|
|
14
|
+
export type TitleAnchor = null | "start" | "middle" | "end";
|
|
15
|
+
export type TitleFrame = "bounds" | "group";
|
|
16
|
+
|
|
17
|
+
export interface Title {
|
|
18
|
+
/**
|
|
19
|
+
* The title text.
|
|
20
|
+
*/
|
|
21
|
+
text: string;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A mark style property to apply to the title text mark. If not specified, a default style of `"group-title"` is applied.
|
|
25
|
+
*/
|
|
26
|
+
style?: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The anchor position for placing the title and subtitle text. One of `"start"`, `"middle"`, or `"end"`. For example, with an orientation of top these anchor positions map to a left-, center-, or right-aligned title.
|
|
30
|
+
*/
|
|
31
|
+
anchor?: TitleAnchor;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The reference frame for the anchor position, one of `"bounds"` (to anchor relative to the full bounding box) or `"group"` (to anchor relative to the group width or height).
|
|
35
|
+
*/
|
|
36
|
+
frame?: TitleFrame;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The orthogonal offset in pixels by which to displace the title group from its position along the edge of the chart.
|
|
40
|
+
*/
|
|
41
|
+
offset?: number;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Default title orientation (`"top"`, `"bottom"`, `"left"`, or `"right"`)
|
|
45
|
+
*/
|
|
46
|
+
orient?: TitleOrient;
|
|
47
|
+
|
|
48
|
+
// ---------- Shared Text Properties ----------
|
|
49
|
+
/**
|
|
50
|
+
* Horizontal text alignment for title text. One of `"left"`, `"center"`, or `"right"`.
|
|
51
|
+
*/
|
|
52
|
+
align?: Align;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Angle in degrees of title and subtitle text.
|
|
56
|
+
*/
|
|
57
|
+
angle?: number;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Vertical text baseline for title and subtitle text. One of `"alphabetic"` (default), `"top"`, `"middle"`, or `"bottom"`.
|
|
61
|
+
*/
|
|
62
|
+
baseline?: Baseline;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Delta offset for title and subtitle text x-coordinate.
|
|
66
|
+
*/
|
|
67
|
+
dx?: number;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Delta offset for title and subtitle text y-coordinate.
|
|
71
|
+
*/
|
|
72
|
+
dy?: number;
|
|
73
|
+
|
|
74
|
+
// ---------- Title Text ----------
|
|
75
|
+
/**
|
|
76
|
+
* Text color for title text.
|
|
77
|
+
*/
|
|
78
|
+
color?: string;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Font name for title text.
|
|
82
|
+
*/
|
|
83
|
+
font?: string;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Font size in pixels for title text.
|
|
87
|
+
*
|
|
88
|
+
* @minimum 0
|
|
89
|
+
*/
|
|
90
|
+
fontSize?: number;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Font style for title text.
|
|
94
|
+
*/
|
|
95
|
+
fontStyle?: FontStyle;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Font weight for title text.
|
|
99
|
+
* This can be either a string (e.g `"bold"`, `"normal"`) or a number (`100`, `200`, `300`, ..., `900` where `"normal"` = `400` and `"bold"` = `700`).
|
|
100
|
+
*/
|
|
101
|
+
fontWeight?: FontWeight;
|
|
102
|
+
}
|
package/src/spec/view.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
MarkType,
|
|
15
15
|
RectProps,
|
|
16
16
|
} from "./mark";
|
|
17
|
+
import { Title } from "./title";
|
|
17
18
|
|
|
18
19
|
export interface SizeDef {
|
|
19
20
|
/** Size in pixels */
|
|
@@ -55,7 +56,7 @@ export type Paddings = Partial<Record<Side, number>>;
|
|
|
55
56
|
|
|
56
57
|
export type PaddingConfig = Paddings | number;
|
|
57
58
|
|
|
58
|
-
export interface
|
|
59
|
+
export interface ViewBackground extends RectProps, FillAndStrokeProps {
|
|
59
60
|
// TODO: style?: string | string[];
|
|
60
61
|
|
|
61
62
|
// TODO: Move to FillAndStrokeProps or something
|
|
@@ -78,7 +79,7 @@ export interface ViewSpecBase extends ResolveSpec {
|
|
|
78
79
|
data?: Data;
|
|
79
80
|
transform?: TransformParams[];
|
|
80
81
|
encoding?: Encoding;
|
|
81
|
-
title?: string;
|
|
82
|
+
title?: string | Title;
|
|
82
83
|
|
|
83
84
|
/**
|
|
84
85
|
* A description of the view. Multiple lines can be provided as an array.
|
|
@@ -115,7 +116,7 @@ export interface ViewSpecBase extends ResolveSpec {
|
|
|
115
116
|
}
|
|
116
117
|
|
|
117
118
|
export interface UnitSpec extends ViewSpecBase, AggregateSamplesSpec {
|
|
118
|
-
view?:
|
|
119
|
+
view?: ViewBackground;
|
|
119
120
|
mark: MarkType | MarkConfigAndType;
|
|
120
121
|
}
|
|
121
122
|
|
|
@@ -125,7 +126,7 @@ export interface AggregateSamplesSpec {
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
export interface LayerSpec extends ViewSpecBase, AggregateSamplesSpec {
|
|
128
|
-
view?:
|
|
129
|
+
view?: ViewBackground;
|
|
129
130
|
layer: (LayerSpec | UnitSpec)[];
|
|
130
131
|
}
|
|
131
132
|
|
|
@@ -218,4 +219,5 @@ export interface HConcatSpec extends ConcatBase {
|
|
|
218
219
|
|
|
219
220
|
export interface ConcatSpec extends ConcatBase {
|
|
220
221
|
concat: (ViewSpec | ImportSpec)[];
|
|
222
|
+
columns: number;
|
|
221
223
|
}
|
|
@@ -47,10 +47,11 @@ export default async function dataTooltipHandler(datum, mark, params) {
|
|
|
47
47
|
</table>
|
|
48
48
|
`;
|
|
49
49
|
|
|
50
|
-
const
|
|
50
|
+
const titleText = mark.unitView.getTitleText();
|
|
51
|
+
const title = titleText
|
|
51
52
|
? html`
|
|
52
53
|
<div class="title">
|
|
53
|
-
<strong>${
|
|
54
|
+
<strong>${titleText}</strong>
|
|
54
55
|
</div>
|
|
55
56
|
`
|
|
56
57
|
: "";
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import clamp from "./clamp";
|
|
2
|
+
|
|
3
|
+
const MAX_INTEGER = 2 ** 31 - 1;
|
|
4
|
+
const MIN_INTEGER = -(2 ** 31);
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @callback Lookup
|
|
8
|
+
* @param {number} start
|
|
9
|
+
* @param {number} end
|
|
10
|
+
* @param {[number, number]} [arr] Store the result into this array (and return it)
|
|
11
|
+
* @returns {[number, number]}
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A binned index for (overlapping) ranges that are sorted by their start position.
|
|
16
|
+
*
|
|
17
|
+
* @param {number} size Number of bins
|
|
18
|
+
* @param {[number, number]} domain
|
|
19
|
+
* @param {(datum: T) => number} accessor
|
|
20
|
+
* @param {(datum: T) => number} [accessor2]
|
|
21
|
+
* @template T
|
|
22
|
+
*/
|
|
23
|
+
export function createBinningRangeIndexer(
|
|
24
|
+
size,
|
|
25
|
+
domain,
|
|
26
|
+
accessor,
|
|
27
|
+
accessor2 = accessor
|
|
28
|
+
) {
|
|
29
|
+
const startIndices = new Int32Array(size);
|
|
30
|
+
startIndices.fill(MAX_INTEGER);
|
|
31
|
+
|
|
32
|
+
let lastIndex = MIN_INTEGER;
|
|
33
|
+
let unordered = false;
|
|
34
|
+
|
|
35
|
+
const endIndices = new Int32Array(size);
|
|
36
|
+
|
|
37
|
+
const start = domain[0];
|
|
38
|
+
const domainLength = domain[1] - domain[0];
|
|
39
|
+
const divisor = domainLength / size;
|
|
40
|
+
|
|
41
|
+
/** @param {number} pos */
|
|
42
|
+
const getBin = (pos) =>
|
|
43
|
+
clamp(Math.floor((pos - start) / divisor), 0, size - 1);
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
*
|
|
47
|
+
* @param {T} datum
|
|
48
|
+
* @param {number} startIndex
|
|
49
|
+
* @param {number} endIndex
|
|
50
|
+
*/
|
|
51
|
+
function binningIndexer(datum, startIndex, endIndex) {
|
|
52
|
+
if (startIndex > lastIndex) {
|
|
53
|
+
lastIndex = startIndex;
|
|
54
|
+
} else if (!unordered) {
|
|
55
|
+
unordered = true;
|
|
56
|
+
// TODO: Contextual info like view path
|
|
57
|
+
console.debug(
|
|
58
|
+
"Items are not ordered properly. Disabling binned index."
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const value = accessor(datum);
|
|
63
|
+
const bin = getBin(value);
|
|
64
|
+
|
|
65
|
+
if (startIndices[bin] > startIndex) {
|
|
66
|
+
startIndices[bin] = startIndex;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (endIndices[bin] < endIndex) {
|
|
70
|
+
endIndices[bin] = endIndex;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
*
|
|
76
|
+
* @param {T} datum
|
|
77
|
+
* @param {number} startIndex
|
|
78
|
+
* @param {number} endIndex
|
|
79
|
+
*/
|
|
80
|
+
function binningRangeIndexer(datum, startIndex, endIndex) {
|
|
81
|
+
if (startIndex > lastIndex) {
|
|
82
|
+
lastIndex = startIndex;
|
|
83
|
+
} else if (!unordered) {
|
|
84
|
+
unordered = true;
|
|
85
|
+
// TODO: Contextual info like view path
|
|
86
|
+
console.debug(
|
|
87
|
+
"Items are not ordered properly. Disabling binned index."
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const start = accessor(datum);
|
|
92
|
+
const end = accessor2(datum);
|
|
93
|
+
const startBin = getBin(start);
|
|
94
|
+
const endBin = getBin(end);
|
|
95
|
+
|
|
96
|
+
// TODO: This loop could probably be done as a more efficient post processing
|
|
97
|
+
// step.
|
|
98
|
+
for (let bin = startBin; bin <= endBin; bin++) {
|
|
99
|
+
if (startIndices[bin] > startIndex) {
|
|
100
|
+
startIndices[bin] = startIndex;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (endIndices[bin] < endIndex) {
|
|
104
|
+
endIndices[bin] = endIndex;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* @type {Lookup}
|
|
111
|
+
*/
|
|
112
|
+
const lookup = (start, end, arr = [0, 0]) => {
|
|
113
|
+
arr[0] = startIndices[getBin(start)];
|
|
114
|
+
arr[1] = Math.max(endIndices[getBin(end)], arr[0]);
|
|
115
|
+
return arr;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const getIndex = () => {
|
|
119
|
+
for (let i = 1; i < endIndices.length; i++) {
|
|
120
|
+
if (endIndices[i] < endIndices[i - 1]) {
|
|
121
|
+
endIndices[i] = endIndices[i - 1];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let tail = true;
|
|
126
|
+
|
|
127
|
+
for (let i = startIndices.length - 1; i > 0; i--) {
|
|
128
|
+
if (tail && startIndices[i] == MAX_INTEGER) {
|
|
129
|
+
startIndices[i] = endIndices[i];
|
|
130
|
+
tail = false;
|
|
131
|
+
} else if (startIndices[i - 1] > startIndices[i]) {
|
|
132
|
+
startIndices[i - 1] = startIndices[i];
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return lookup;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
binningIndexer.getIndex = getIndex;
|
|
140
|
+
binningRangeIndexer.getIndex = getIndex;
|
|
141
|
+
|
|
142
|
+
if (unordered) {
|
|
143
|
+
return undefined;
|
|
144
|
+
} else {
|
|
145
|
+
return accessor == accessor2 ? binningIndexer : binningRangeIndexer;
|
|
146
|
+
}
|
|
147
|
+
}
|