@genome-spy/core 0.61.0 → 0.62.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/bundle/index.es.js +3947 -3890
- package/dist/bundle/index.js +95 -95
- package/dist/schema.json +19 -4
- package/dist/src/data/collector.d.ts.map +1 -1
- package/dist/src/data/collector.js +40 -15
- package/dist/src/data/transforms/filterScoredLabels.d.ts +4 -3
- package/dist/src/data/transforms/filterScoredLabels.d.ts.map +1 -1
- package/dist/src/data/transforms/filterScoredLabels.js +46 -17
- package/dist/src/data/transforms/measureText.d.ts +3 -3
- package/dist/src/data/transforms/measureText.d.ts.map +1 -1
- package/dist/src/data/transforms/measureText.js +21 -4
- package/dist/src/marks/point.d.ts.map +1 -1
- package/dist/src/marks/point.js +6 -0
- package/dist/src/spec/transform.d.ts +18 -4
- package/dist/src/utils/topK.d.ts +5 -14
- package/dist/src/utils/topK.d.ts.map +1 -1
- package/dist/src/utils/topK.js +14 -45
- package/dist/src/utils/topK.test.js +30 -40
- package/dist/src/view/scaleResolution.d.ts.map +1 -1
- package/dist/src/view/scaleResolution.js +7 -0
- package/package.json +2 -2
package/dist/schema.json
CHANGED
|
@@ -2510,6 +2510,10 @@
|
|
|
2510
2510
|
"FilterScoredLabelsParams": {
|
|
2511
2511
|
"additionalProperties": false,
|
|
2512
2512
|
"properties": {
|
|
2513
|
+
"asMidpoint": {
|
|
2514
|
+
"description": "Outputs the average of pos and pos2 as the midpoint of the element. This is useful for elements that have a width, such as transcripts. The midpoint is clamped to the visible region of the element.",
|
|
2515
|
+
"type": "string"
|
|
2516
|
+
},
|
|
2513
2517
|
"channel": {
|
|
2514
2518
|
"description": "**Default:** `\"x\"`",
|
|
2515
2519
|
"enum": [
|
|
@@ -2520,7 +2524,7 @@
|
|
|
2520
2524
|
},
|
|
2521
2525
|
"lane": {
|
|
2522
2526
|
"$ref": "#/definitions/Field",
|
|
2523
|
-
"description": "An optional field representing element's lane, e.g., if transcripts are shown using a piled up layout."
|
|
2527
|
+
"description": "An optional field representing element's lane, e.g., if transcripts are shown using a piled up layout. Each line is processed separately."
|
|
2524
2528
|
},
|
|
2525
2529
|
"padding": {
|
|
2526
2530
|
"description": "Padding (in pixels) around the element.\n\n**Default:** `0`",
|
|
@@ -2528,7 +2532,11 @@
|
|
|
2528
2532
|
},
|
|
2529
2533
|
"pos": {
|
|
2530
2534
|
"$ref": "#/definitions/Field",
|
|
2531
|
-
"description": "The field representing element's position on the domain."
|
|
2535
|
+
"description": "The field representing element's start position on the domain."
|
|
2536
|
+
},
|
|
2537
|
+
"pos2": {
|
|
2538
|
+
"$ref": "#/definitions/Field",
|
|
2539
|
+
"description": "The field representing element's end position on the domain. If not specified, the `pos` field is used."
|
|
2532
2540
|
},
|
|
2533
2541
|
"score": {
|
|
2534
2542
|
"$ref": "#/definitions/Field",
|
|
@@ -2541,7 +2549,7 @@
|
|
|
2541
2549
|
},
|
|
2542
2550
|
"width": {
|
|
2543
2551
|
"$ref": "#/definitions/Field",
|
|
2544
|
-
"description": "The field representing element's width in pixels"
|
|
2552
|
+
"description": "The field representing element's width in pixels."
|
|
2545
2553
|
}
|
|
2546
2554
|
},
|
|
2547
2555
|
"required": [
|
|
@@ -4554,7 +4562,14 @@
|
|
|
4554
4562
|
"$ref": "#/definitions/Field"
|
|
4555
4563
|
},
|
|
4556
4564
|
"fontSize": {
|
|
4557
|
-
"
|
|
4565
|
+
"anyOf": [
|
|
4566
|
+
{
|
|
4567
|
+
"type": "number"
|
|
4568
|
+
},
|
|
4569
|
+
{
|
|
4570
|
+
"$ref": "#/definitions/ExprRef"
|
|
4571
|
+
}
|
|
4572
|
+
]
|
|
4558
4573
|
},
|
|
4559
4574
|
"type": {
|
|
4560
4575
|
"const": "measureText",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"collector.d.ts","sourceRoot":"","sources":["../../../src/data/collector.js"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH;
|
|
1
|
+
{"version":3,"file":"collector.d.ts","sourceRoot":"","sources":["../../../src/data/collector.js"],"names":[],"mappings":"AAUA;;;;;GAKG;AACH;IAuCI;;OAEG;IACH,qBAFW,OAAO,sBAAsB,EAAE,aAAa,EAiBtD;IAZG,qDAA2C;IAE3C,2CAA2C;IAC3C,WADW,CAAC,CAAS,IAAS,EAAT,SAAS,KAAE,IAAI,CAAC,EAAE,CACpB;IAGnB,yFAAyF;IACzF,cADW,GAAG,CAAC,OAAO,oBAAoB,EAAE,MAAM,EAAE,+BAAO,CACN;IAgHzD;;OAEG;IACH,WAFa,QAAQ,+BAAO,CAqB3B;IAED;;;OAGG;IACH,mBAFW,CAAC,KAAK,+BAAO,KAAK,IAAI,QAUhC;IAED;;OAEG;IACH,uBAMC;IA8CD;;;;OAIG;IACH,8BAFW,MAAM,iCA4BhB;;CACJ;qBA7SyD,eAAe"}
|
|
@@ -40,6 +40,11 @@ export default class Collector extends FlowNode {
|
|
|
40
40
|
*/
|
|
41
41
|
#facetIndices;
|
|
42
42
|
|
|
43
|
+
/**
|
|
44
|
+
* @type {(a: Datum, b: Datum) => number}
|
|
45
|
+
*/
|
|
46
|
+
#comparator;
|
|
47
|
+
|
|
43
48
|
get behavior() {
|
|
44
49
|
return BEHAVIOR_COLLECTS;
|
|
45
50
|
}
|
|
@@ -63,6 +68,8 @@ export default class Collector extends FlowNode {
|
|
|
63
68
|
/** @type {Map<import("../spec/channel.js").Scalar[], Data>} TODO: proper type for key */
|
|
64
69
|
this.facetBatches = new InternMap([], JSON.stringify);
|
|
65
70
|
|
|
71
|
+
this.#comparator = makeComparator(this.params?.sort);
|
|
72
|
+
|
|
66
73
|
this.#init();
|
|
67
74
|
}
|
|
68
75
|
|
|
@@ -100,18 +107,6 @@ export default class Collector extends FlowNode {
|
|
|
100
107
|
// Free some memory
|
|
101
108
|
this.#buffer = [];
|
|
102
109
|
|
|
103
|
-
const sort = this.params?.sort;
|
|
104
|
-
// Vega's "compare" function is incredibly slow (uses megamorphic field accessor)
|
|
105
|
-
// TODO: Implement a replacement for static data types
|
|
106
|
-
const comparator = sort ? compare(sort.field, sort.order) : undefined;
|
|
107
|
-
|
|
108
|
-
/** @param {any[]} data */
|
|
109
|
-
const sortData = (data) => {
|
|
110
|
-
if (comparator) {
|
|
111
|
-
data.sort(comparator);
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
110
|
if (this.params.groupby?.length) {
|
|
116
111
|
if (this.facetBatches.size > 1) {
|
|
117
112
|
throw new Error("TODO: Support faceted data!");
|
|
@@ -137,9 +132,11 @@ export default class Collector extends FlowNode {
|
|
|
137
132
|
}
|
|
138
133
|
}
|
|
139
134
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
135
|
+
if (this.#comparator) {
|
|
136
|
+
for (const data of this.facetBatches.values()) {
|
|
137
|
+
// TODO: Only sort if not already sorted
|
|
138
|
+
data.sort(this.#comparator);
|
|
139
|
+
}
|
|
143
140
|
}
|
|
144
141
|
|
|
145
142
|
this.#buildUniqueIdIndex();
|
|
@@ -329,3 +326,31 @@ function groupBy(data, accessor) {
|
|
|
329
326
|
}
|
|
330
327
|
return groups;
|
|
331
328
|
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Creates a comparator function based on the provided sort parameters.
|
|
332
|
+
*
|
|
333
|
+
* @param {import("../spec/transform.js").CompareParams} sort
|
|
334
|
+
* @returns {(a: Datum, b: Datum) => number}
|
|
335
|
+
*/
|
|
336
|
+
function makeComparator(sort) {
|
|
337
|
+
// For simple cases, create a simple comparator.
|
|
338
|
+
// For more complex cases, use Vega's compare function. However,
|
|
339
|
+
// is uses megamorphic field accessors, which makes it slow.
|
|
340
|
+
if (sort?.field) {
|
|
341
|
+
const fields = asArray(sort.field);
|
|
342
|
+
if (fields.length == 1 && !fields[0].includes(".")) {
|
|
343
|
+
const order = asArray(sort.order)[0] ?? "ascending";
|
|
344
|
+
const fieldName = JSON.stringify(fields[0]);
|
|
345
|
+
return /** @type {(a: Datum, b: Datum) => number} */ (
|
|
346
|
+
new Function(
|
|
347
|
+
"a",
|
|
348
|
+
"b",
|
|
349
|
+
`return a[${fieldName}] ${order === "ascending" ? "-" : "+"} b[${fieldName}];`
|
|
350
|
+
)
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return compare(sort.field, sort.order);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
@@ -9,8 +9,10 @@ export default class FilterScoredLabelsTransform extends Transform {
|
|
|
9
9
|
/** @type {any[]} */
|
|
10
10
|
_data: any[];
|
|
11
11
|
channel: "x" | "y";
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
startPosAccessor: import("vega-util").AccessorFn<any>;
|
|
13
|
+
endPosAccessor: import("vega-util").AccessorFn<any>;
|
|
14
|
+
startPosBisector: import("d3-array").Bisector<any, any>;
|
|
15
|
+
endPosBisector: import("d3-array").Bisector<any, any>;
|
|
14
16
|
scoreAccessor: import("vega-util").AccessorFn<any>;
|
|
15
17
|
widthAccessor: import("vega-util").AccessorFn<any>;
|
|
16
18
|
/** @type {function(any):any} */
|
|
@@ -20,7 +22,6 @@ export default class FilterScoredLabelsTransform extends Transform {
|
|
|
20
22
|
reservationMaps: Map<any, ReservationMap>;
|
|
21
23
|
resolution: import("../../view/scaleResolution.js").default;
|
|
22
24
|
schedule: () => void;
|
|
23
|
-
_scores: any[];
|
|
24
25
|
_filterAndPropagate(): void;
|
|
25
26
|
groups: Map<any, any>;
|
|
26
27
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"filterScoredLabels.d.ts","sourceRoot":"","sources":["../../../../src/data/transforms/filterScoredLabels.js"],"names":[],"mappings":"AAOA;IAKI;;;;OAIG;IACH,oBAHW,OAAO,yBAAyB,EAAE,wBAAwB,QAC1D,OAAO,oBAAoB,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"filterScoredLabels.d.ts","sourceRoot":"","sources":["../../../../src/data/transforms/filterScoredLabels.js"],"names":[],"mappings":"AAOA;IAKI;;;;OAIG;IACH,oBAHW,OAAO,yBAAyB,EAAE,wBAAwB,QAC1D,OAAO,oBAAoB,EAAE,OAAO,EA6C9C;IAxCG,mEAAoB;IAEpB,oBAAoB;IACpB,OADW,GAAG,EAAE,CACD;IAEf,mBAAoC;IAMpC,sDAA8C;IAC9C,oDAAgE;IAChE,wDAAuD;IACvD,sDAAmD;IACnD,mDAA6C;IAC7C,mDAA6C;IAC7C,gCAAgC;IAChC,cADW,CAAS,IAAG,EAAH,GAAG,KAAE,GAAG,CAGd;IACd,gBAAuC;IAEvC,uCAAuC;IACvC,iBADW,GAAG,CAAC,GAAG,EAAE,cAAc,CAAC,CACH;IAEhC,4DAAuD;IAIvD,qBAAuE;IAyB3E,4BAsEC;IAKG,sBAAuB;CAU9B;sBA3JqB,gBAAgB;2BAFX,+BAA+B"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { bisector } from "d3-array";
|
|
2
2
|
import { BEHAVIOR_COLLECTS } from "../flowNode.js";
|
|
3
|
-
import {
|
|
3
|
+
import { topK } from "../../utils/topK.js";
|
|
4
4
|
import ReservationMap from "../../utils/reservationMap.js";
|
|
5
5
|
import { field } from "../../utils/field.js";
|
|
6
6
|
import Transform from "./transform.js";
|
|
@@ -29,8 +29,10 @@ export default class FilterScoredLabelsTransform extends Transform {
|
|
|
29
29
|
throw new Error("Invalid channel: " + this.channel);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
this.
|
|
33
|
-
this.
|
|
32
|
+
this.startPosAccessor = field(this.params.pos);
|
|
33
|
+
this.endPosAccessor = field(this.params.pos2 ?? this.params.pos);
|
|
34
|
+
this.startPosBisector = bisector(this.startPosAccessor);
|
|
35
|
+
this.endPosBisector = bisector(this.endPosAccessor);
|
|
34
36
|
this.scoreAccessor = field(this.params.score);
|
|
35
37
|
this.widthAccessor = field(this.params.width);
|
|
36
38
|
/** @type {function(any):any} */
|
|
@@ -59,10 +61,8 @@ export default class FilterScoredLabelsTransform extends Transform {
|
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
complete() {
|
|
62
|
-
const
|
|
63
|
-
this._data.sort((a, b) =>
|
|
64
|
-
|
|
65
|
-
this._scores = this._data.map(this.scoreAccessor);
|
|
64
|
+
const startPosAccessor = this.startPosAccessor;
|
|
65
|
+
this._data.sort((a, b) => startPosAccessor(a) - startPosAccessor(b));
|
|
66
66
|
|
|
67
67
|
for (const lane of new Set(this._data.map(this.laneAccessor))) {
|
|
68
68
|
this.reservationMaps.set(lane, new ReservationMap(200));
|
|
@@ -91,25 +91,54 @@ export default class FilterScoredLabelsTransform extends Transform {
|
|
|
91
91
|
const k = 70; // TODO: Configurable
|
|
92
92
|
|
|
93
93
|
// Find the maximum of k elements from the visible domain in priority order
|
|
94
|
-
const
|
|
95
|
-
this.
|
|
94
|
+
const topElements = topK(
|
|
95
|
+
this._data,
|
|
96
96
|
k,
|
|
97
|
-
this.
|
|
98
|
-
this.
|
|
97
|
+
this.scoreAccessor,
|
|
98
|
+
this.endPosBisector.left(this._data, domain[0]),
|
|
99
|
+
this.startPosBisector.right(this._data, domain[1])
|
|
99
100
|
);
|
|
100
101
|
|
|
101
102
|
// Try to fit the elements on the available lanes and propagate if there was room
|
|
102
|
-
for (const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
103
|
+
for (const datum of topElements) {
|
|
104
|
+
let startPos = scale(this.startPosAccessor(datum)) * rangeSpan;
|
|
105
|
+
let endPos = scale(this.endPosAccessor(datum)) * rangeSpan;
|
|
106
|
+
|
|
107
|
+
const span = endPos - startPos;
|
|
108
|
+
const width = this.widthAccessor(datum) + this.padding * 2;
|
|
109
|
+
|
|
110
|
+
let midpoint = (startPos + endPos) / 2;
|
|
111
|
+
|
|
112
|
+
// How much extra space we have for adjusting the position so that the
|
|
113
|
+
// text stays inside the range.
|
|
114
|
+
const extra = Math.max(0.0, (span - width) / 2.0);
|
|
115
|
+
if (extra > 0.0) {
|
|
116
|
+
const leftOver = Math.max(0.0, width / 2 - midpoint);
|
|
117
|
+
midpoint += Math.min(leftOver, extra);
|
|
118
|
+
|
|
119
|
+
const rightOver = Math.max(
|
|
120
|
+
0.0,
|
|
121
|
+
width / 2 + midpoint - rangeSpan
|
|
122
|
+
);
|
|
123
|
+
midpoint -= Math.min(rightOver, extra);
|
|
124
|
+
}
|
|
106
125
|
|
|
107
126
|
if (
|
|
108
127
|
this.reservationMaps
|
|
109
128
|
.get(this.laneAccessor(datum))
|
|
110
|
-
.reserve(
|
|
129
|
+
.reserve(midpoint - width / 2, midpoint + width / 2)
|
|
111
130
|
) {
|
|
112
|
-
this.
|
|
131
|
+
if (this.params.asMidpoint) {
|
|
132
|
+
// Clone the datum to avoid side effects
|
|
133
|
+
const clonedDatum = Object.assign({}, datum);
|
|
134
|
+
// @ts-ignore
|
|
135
|
+
clonedDatum[this.params.asMidpoint] = scale.invert(
|
|
136
|
+
midpoint / rangeSpan
|
|
137
|
+
);
|
|
138
|
+
this._propagate(clonedDatum);
|
|
139
|
+
} else {
|
|
140
|
+
this._propagate(datum);
|
|
141
|
+
}
|
|
113
142
|
}
|
|
114
143
|
}
|
|
115
144
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Measures text length. This is mainly intended for
|
|
3
|
-
* in gene annotations.
|
|
2
|
+
* Measures text length. This is mainly intended for strand arrows in gene annotations.
|
|
4
3
|
*/
|
|
5
4
|
export default class MeasureTextTransform extends Transform {
|
|
6
5
|
/**
|
|
7
6
|
*
|
|
8
7
|
* @param {import("../../spec/transform.js").MeasureTextParams} params
|
|
8
|
+
* @param {import("../flowNode.js").ParamMediatorProvider} paramMediatorProvider
|
|
9
9
|
*/
|
|
10
|
-
constructor(params: import("../../spec/transform.js").MeasureTextParams);
|
|
10
|
+
constructor(params: import("../../spec/transform.js").MeasureTextParams, paramMediatorProvider: import("../flowNode.js").ParamMediatorProvider);
|
|
11
11
|
params: import("../../spec/transform.js").MeasureTextParams;
|
|
12
12
|
/**
|
|
13
13
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"measureText.d.ts","sourceRoot":"","sources":["../../../../src/data/transforms/measureText.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"measureText.d.ts","sourceRoot":"","sources":["../../../../src/data/transforms/measureText.js"],"names":[],"mappings":"AAOA;;GAEG;AACH;IAKI;;;;OAIG;IACH,oBAHW,OAAO,yBAAyB,EAAE,iBAAiB,yBACnD,OAAO,gBAAgB,EAAE,qBAAqB,EA0CxD;IArCG,4DAAoB;IAwBpB;;;OAGG;IACH,gBAFW,GAAG,UAUb;CAER;sBAzDqB,gBAAgB"}
|
|
@@ -3,10 +3,10 @@ import fontMetadata from "../../fonts/Lato-Regular.json" with { type: "json" };
|
|
|
3
3
|
import getMetrics from "../../fonts/bmFontMetrics.js";
|
|
4
4
|
import { field } from "../../utils/field.js";
|
|
5
5
|
import Transform from "./transform.js";
|
|
6
|
+
import { isExprRef } from "../../view/paramMediator.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
|
-
* Measures text length. This is mainly intended for
|
|
9
|
-
* in gene annotations.
|
|
9
|
+
* Measures text length. This is mainly intended for strand arrows in gene annotations.
|
|
10
10
|
*/
|
|
11
11
|
export default class MeasureTextTransform extends Transform {
|
|
12
12
|
get behavior() {
|
|
@@ -16,8 +16,9 @@ export default class MeasureTextTransform extends Transform {
|
|
|
16
16
|
/**
|
|
17
17
|
*
|
|
18
18
|
* @param {import("../../spec/transform.js").MeasureTextParams} params
|
|
19
|
+
* @param {import("../flowNode.js").ParamMediatorProvider} paramMediatorProvider
|
|
19
20
|
*/
|
|
20
|
-
constructor(params) {
|
|
21
|
+
constructor(params, paramMediatorProvider) {
|
|
21
22
|
super(params);
|
|
22
23
|
|
|
23
24
|
this.params = params;
|
|
@@ -26,7 +27,23 @@ export default class MeasureTextTransform extends Transform {
|
|
|
26
27
|
const accessor = field(params.field);
|
|
27
28
|
const as = params.as;
|
|
28
29
|
// TODO: Support custom fonts.
|
|
29
|
-
|
|
30
|
+
|
|
31
|
+
let size = 0;
|
|
32
|
+
|
|
33
|
+
// TODO: Refactor this into reusable code.
|
|
34
|
+
if (isExprRef(params.fontSize)) {
|
|
35
|
+
const sizeExpr =
|
|
36
|
+
paramMediatorProvider.paramMediator.createExpression(
|
|
37
|
+
params.fontSize.expr
|
|
38
|
+
);
|
|
39
|
+
size = sizeExpr();
|
|
40
|
+
sizeExpr.addListener(() => {
|
|
41
|
+
size = sizeExpr();
|
|
42
|
+
this.repropagate();
|
|
43
|
+
});
|
|
44
|
+
} else {
|
|
45
|
+
size = params.fontSize;
|
|
46
|
+
}
|
|
30
47
|
|
|
31
48
|
/**
|
|
32
49
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"point.d.ts","sourceRoot":"","sources":["../../../src/marks/point.js"],"names":[],"mappings":"AAmBA;;GAEG;AACH;IAGI;;OAEG;IACH,sBAFW,OAAO,qBAAqB,EAAE,OAAO,
|
|
1
|
+
{"version":3,"file":"point.d.ts","sourceRoot":"","sources":["../../../src/marks/point.js"],"names":[],"mappings":"AAmBA;;GAEG;AACH;IAGI;;OAEG;IACH,sBAFW,OAAO,qBAAqB,EAAE,OAAO,EAmD/C;IA0EO,iDAMC;IAkET,+BAkBC;;CAgDJ;iBAtRgB,WAAW"}
|
package/dist/src/marks/point.js
CHANGED
|
@@ -69,6 +69,12 @@ export default class PointMark extends Mark {
|
|
|
69
69
|
this.#semanticZoomFraction = () => szf;
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
+
|
|
73
|
+
if ("geometricZoomBound" in this.properties) {
|
|
74
|
+
console.warn(
|
|
75
|
+
'geometricZoomBound is deprecated. Use something like the following instead: "size": { "expr": "min(0.5 * pow(zoomLevel, 2), 200)" }.'
|
|
76
|
+
);
|
|
77
|
+
}
|
|
72
78
|
}
|
|
73
79
|
|
|
74
80
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { PositionalChannel } from "./channel.js";
|
|
2
|
+
import { ExprRef } from "./parameter.js";
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* The name of the field or a JavaScript expression for accessing nested properties.
|
|
@@ -425,7 +426,7 @@ export interface MeasureTextParams extends TransformParamsBase {
|
|
|
425
426
|
|
|
426
427
|
field: Field;
|
|
427
428
|
|
|
428
|
-
fontSize: number;
|
|
429
|
+
fontSize: number | ExprRef;
|
|
429
430
|
|
|
430
431
|
as: string;
|
|
431
432
|
|
|
@@ -484,18 +485,31 @@ export interface FilterScoredLabelsParams extends TransformParamsBase {
|
|
|
484
485
|
score: Field;
|
|
485
486
|
|
|
486
487
|
/**
|
|
487
|
-
* The field representing element's width in pixels
|
|
488
|
+
* The field representing element's width in pixels.
|
|
488
489
|
*/
|
|
489
490
|
width: Field;
|
|
490
491
|
|
|
491
492
|
/**
|
|
492
|
-
* The field representing element's position on the domain.
|
|
493
|
+
* The field representing element's start position on the domain.
|
|
493
494
|
*/
|
|
494
495
|
pos: Field;
|
|
495
496
|
|
|
497
|
+
/**
|
|
498
|
+
* The field representing element's end position on the domain.
|
|
499
|
+
* If not specified, the `pos` field is used.
|
|
500
|
+
*/
|
|
501
|
+
pos2?: Field;
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Outputs the average of pos and pos2 as the midpoint of the element.
|
|
505
|
+
* This is useful for elements that have a width, such as transcripts.
|
|
506
|
+
* The midpoint is clamped to the visible region of the element.
|
|
507
|
+
*/
|
|
508
|
+
asMidpoint?: string;
|
|
509
|
+
|
|
496
510
|
/**
|
|
497
511
|
* An optional field representing element's lane, e.g., if transcripts
|
|
498
|
-
* are shown using a piled up layout.
|
|
512
|
+
* are shown using a piled up layout. Each line is processed separately.
|
|
499
513
|
*/
|
|
500
514
|
lane?: Field;
|
|
501
515
|
|
package/dist/src/utils/topK.d.ts
CHANGED
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Finds the top k
|
|
3
|
-
*
|
|
4
|
-
* Based on ideas at https://lemire.me/blog/2017/06/21/top-speed-for-top-k-queries/
|
|
2
|
+
* Finds the top k elements in a slice of the data array, using a priority accessor.
|
|
5
3
|
*
|
|
6
4
|
* @param {T[]} data
|
|
7
5
|
* @param {number} k
|
|
8
6
|
* @param {(datum: T) => number} priorityAccessor
|
|
9
|
-
* @template T
|
|
10
|
-
*/
|
|
11
|
-
export function topK<T>(data: T[], k: number, priorityAccessor: (datum: T) => number): T[];
|
|
12
|
-
/**
|
|
13
|
-
* Takes an array of priorities and returns the top k indices from the
|
|
14
|
-
* specified slice
|
|
15
|
-
*
|
|
16
|
-
* @param {number[]} priorities An array of priorities
|
|
17
|
-
* @param {number} k
|
|
18
7
|
* @param {number} [start] Default: 0
|
|
19
|
-
* @param {number} [end] Exclusive. Default:
|
|
8
|
+
* @param {number} [end] Exclusive. Default: data.length
|
|
9
|
+
* @template T
|
|
10
|
+
* @returns {T[]}
|
|
20
11
|
*/
|
|
21
|
-
export function
|
|
12
|
+
export function topK<T>(data: T[], k: number, priorityAccessor?: (datum: T) => number, start?: number, end?: number): T[];
|
|
22
13
|
//# sourceMappingURL=topK.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"topK.d.ts","sourceRoot":"","sources":["../../../src/utils/topK.js"],"names":[],"mappings":"AAEA
|
|
1
|
+
{"version":3,"file":"topK.d.ts","sourceRoot":"","sources":["../../../src/utils/topK.js"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,qBAHa,CAAC,QALH,CAAC,EAAE,KACH,MAAM,qBACN,CAAC,KAAK,EAAE,CAAC,KAAK,MAAM,UACpB,MAAM,QACN,MAAM,GAEJ,CAAC,EAAE,CAiCf"}
|
package/dist/src/utils/topK.js
CHANGED
|
@@ -1,64 +1,34 @@
|
|
|
1
1
|
import FlatQueue from "flatqueue";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* Finds the top k
|
|
5
|
-
*
|
|
6
|
-
* Based on ideas at https://lemire.me/blog/2017/06/21/top-speed-for-top-k-queries/
|
|
4
|
+
* Finds the top k elements in a slice of the data array, using a priority accessor.
|
|
7
5
|
*
|
|
8
6
|
* @param {T[]} data
|
|
9
7
|
* @param {number} k
|
|
10
8
|
* @param {(datum: T) => number} priorityAccessor
|
|
11
|
-
* @template T
|
|
12
|
-
*/
|
|
13
|
-
export function topK(data, k, priorityAccessor) {
|
|
14
|
-
/** @type {FlatQueue<number>} */
|
|
15
|
-
const queue = new FlatQueue();
|
|
16
|
-
|
|
17
|
-
let i;
|
|
18
|
-
for (i = 0; i < k && i < data.length; i++) {
|
|
19
|
-
queue.push(i, priorityAccessor(data[i]));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
for (; i < data.length; i++) {
|
|
23
|
-
const p = priorityAccessor(data[i]);
|
|
24
|
-
if (p >= queue.peekValue()) {
|
|
25
|
-
queue.push(i, p);
|
|
26
|
-
queue.pop();
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const result = [];
|
|
31
|
-
|
|
32
|
-
let index;
|
|
33
|
-
while ((index = queue.pop()) !== undefined) {
|
|
34
|
-
result.push(data[index]);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return result.reverse();
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Takes an array of priorities and returns the top k indices from the
|
|
42
|
-
* specified slice
|
|
43
|
-
*
|
|
44
|
-
* @param {number[]} priorities An array of priorities
|
|
45
|
-
* @param {number} k
|
|
46
9
|
* @param {number} [start] Default: 0
|
|
47
|
-
* @param {number} [end] Exclusive. Default:
|
|
10
|
+
* @param {number} [end] Exclusive. Default: data.length
|
|
11
|
+
* @template T
|
|
12
|
+
* @returns {T[]}
|
|
48
13
|
*/
|
|
49
|
-
export function
|
|
14
|
+
export function topK(
|
|
15
|
+
data,
|
|
16
|
+
k,
|
|
17
|
+
priorityAccessor = (x) => +x,
|
|
18
|
+
start = 0,
|
|
19
|
+
end = data.length
|
|
20
|
+
) {
|
|
50
21
|
/** @type {FlatQueue<number>} */
|
|
51
22
|
const queue = new FlatQueue();
|
|
52
|
-
|
|
53
23
|
const sliceLength = end - start;
|
|
54
24
|
|
|
55
25
|
let i;
|
|
56
26
|
for (i = 0; i < k && i < sliceLength; i++) {
|
|
57
|
-
queue.push(i,
|
|
27
|
+
queue.push(i, priorityAccessor(data[start + i]));
|
|
58
28
|
}
|
|
59
29
|
|
|
60
30
|
for (; i < sliceLength; i++) {
|
|
61
|
-
const p =
|
|
31
|
+
const p = priorityAccessor(data[start + i]);
|
|
62
32
|
if (p >= queue.peekValue()) {
|
|
63
33
|
queue.push(i, p);
|
|
64
34
|
queue.pop();
|
|
@@ -66,10 +36,9 @@ export function topKSlice(priorities, k, start = 0, end = priorities.length) {
|
|
|
66
36
|
}
|
|
67
37
|
|
|
68
38
|
const result = [];
|
|
69
|
-
|
|
70
39
|
let index;
|
|
71
40
|
while ((index = queue.pop()) !== undefined) {
|
|
72
|
-
result.push(start + index);
|
|
41
|
+
result.push(data[start + index]);
|
|
73
42
|
}
|
|
74
43
|
|
|
75
44
|
return result.reverse();
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { expect, test } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
import { topK, topKSlice } from "./topK.js";
|
|
2
|
+
import { topK } from "./topK.js";
|
|
4
3
|
|
|
5
4
|
test("topK returns top k numbers in priority order", () => {
|
|
6
5
|
/** @param {number} x */
|
|
@@ -16,49 +15,40 @@ test("topK returns top k numbers in priority order", () => {
|
|
|
16
15
|
expect(topK([1, 1, 1], 3, priorityAccessor)).toEqual([1, 1, 1]);
|
|
17
16
|
});
|
|
18
17
|
|
|
19
|
-
test("topK returns top k objects in priority order", () => {
|
|
20
|
-
|
|
21
|
-
const priorityAccessor = (d) =>
|
|
18
|
+
test("topK returns top k objects in priority order within a start-end range", () => {
|
|
19
|
+
const arr = [0, 9, 1, 8, 2, 7, 3, 6, 4, 5].map((x) => ({ priority: x }));
|
|
20
|
+
const priorityAccessor = (/** @type {{priority: number}} */ d) =>
|
|
21
|
+
d.priority;
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
)
|
|
29
|
-
).toEqual([9, 8, 7].map((x) => ({ priority: x })));
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
test("topK returns top k objects in priority order with large datasets", () => {
|
|
33
|
-
/** @param {number} x */
|
|
34
|
-
const priorityAccessor = (x) => x;
|
|
23
|
+
// Range: indices 2 to 8 (1,8,2,7,3,6)
|
|
24
|
+
expect(topK(arr, 2, priorityAccessor, 2, 8)).toEqual([
|
|
25
|
+
{ priority: 8 },
|
|
26
|
+
{ priority: 7 },
|
|
27
|
+
]);
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
// Range: indices 4 to 10 (2,7,3,6,4,5)
|
|
30
|
+
expect(topK(arr, 3, priorityAccessor, 4, 10)).toEqual([
|
|
31
|
+
{ priority: 7 },
|
|
32
|
+
{ priority: 6 },
|
|
33
|
+
{ priority: 5 },
|
|
34
|
+
]);
|
|
39
35
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
36
|
+
// Range: indices 0 to 3 (0,9,1)
|
|
37
|
+
expect(topK(arr, 2, priorityAccessor, 0, 3)).toEqual([
|
|
38
|
+
{ priority: 9 },
|
|
39
|
+
{ priority: 1 },
|
|
40
|
+
]);
|
|
45
41
|
});
|
|
46
42
|
|
|
47
|
-
test("
|
|
48
|
-
|
|
49
|
-
expect(
|
|
50
|
-
expect(
|
|
51
|
-
expect(
|
|
52
|
-
expect(topKSlice([0, 1, 2, 3, 4, 5], 3)).toEqual([5, 4, 3]);
|
|
53
|
-
expect(topKSlice([0, 9, 1, 8, 2, 7, 3, 6, 4, 5], 3)).toEqual([1, 3, 5]);
|
|
54
|
-
expect(new Set(topKSlice([1, 1, 1, 2, 2, 2], 3))).toEqual(
|
|
55
|
-
new Set([3, 4, 5])
|
|
56
|
-
);
|
|
43
|
+
test("topK returns empty array if start >= end", () => {
|
|
44
|
+
const arr = [1, 2, 3, 4, 5];
|
|
45
|
+
expect(topK(arr, 3, (x) => x, 4, 4)).toEqual([]);
|
|
46
|
+
expect(topK(arr, 3, (x) => x, 5, 5)).toEqual([]);
|
|
47
|
+
expect(topK(arr, 3, (x) => x, 6, 6)).toEqual([]);
|
|
57
48
|
});
|
|
58
49
|
|
|
59
|
-
test("
|
|
60
|
-
|
|
61
|
-
expect(
|
|
62
|
-
|
|
63
|
-
]);
|
|
50
|
+
test("topK works with negative and zero priorities in a range", () => {
|
|
51
|
+
const arr = [-10, 0, 5, -2, 3, 0, -1];
|
|
52
|
+
expect(topK(arr, 2, (x) => x, 1, 6)).toEqual([5, 3]);
|
|
53
|
+
expect(topK(arr, 3, (x) => x, 0, 4)).toEqual([5, 0, -2]);
|
|
64
54
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaleResolution.d.ts","sourceRoot":"","sources":["../../../src/view/scaleResolution.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scaleResolution.d.ts","sourceRoot":"","sources":["../../../src/view/scaleResolution.js"],"names":[],"mappings":"AAm/BA;;;;;;;;;;GAUG;AACH,6CAFW,OAAO,WAAW,EAAE,OAAO,GAAG,OAAO,WAAW,EAAE,OAAO,EAAE,QA4BrE;AA3+BD,2BAA4B,cAAc,CAAC;AAC3C,sBAAuB,SAAS,CAAC;AACjC,sBAAuB,SAAS,CAAC;AACjC,oBAAqB,OAAO,CAAC;AAC7B,oBAAqB,OAAO,CAAC;AAE7B;;;;;;;;GAQG;AACH;;;;;;;GAOG;AACH;IA4CI;;OAEG;IACH,2DASC;IARG,8CAAsB;IACtB,yDAAyD;IACzD,SADW,qBAAqB,EAAE,CACjB;IACjB,0FAA0F;IAC1F,MADW,OAAO,oBAAoB,EAAE,IAAI,CAC5B;IAEhB,iEAAiE;IACjE,MADW,MAAM,CACI;IAWzB,2BAMC;IAED;;;;;;;OAOG;IACH,4KAEC;IAED;;;OAGG;IACH,+KAEC;IAcD;;;;;OAKG;IACH,qBAFW,qBAAqB,QAqD/B;IA8MD;;;;OAIG;IACH,+DAOC;IAED;;OAEG;IACH,oBA6CC;IAED;;OAEG;IACH;eApakC,OAAO,kBAAkB,EAAE,KAAK;MA4cjE;IAED,mBAEC;IAED;;OAEG;IACH,oBAFa,mFAA6B,CAOzC;IAED;;;;OAIG;IACH,oBAKC;IAED;;OAEG;IACH,sBAGC;IAUD;;;;;;;OAOG;IACH,kBALW,MAAM,eACN,MAAM,OACN,MAAM,GACJ,OAAO,CAmEnB;IAED;;;;;;OAMG;IACH,eAJW,mFAA6B,aAC7B,OAAO,GAAG,MAAM,iBA0D1B;IAED;;;;OAIG;IACH,qBAcC;IAED;;;;;OAKG;IACH,uBAOC;IAED;;;;;;;OAOG;IACH,wBAoBC;IAiED;;;OAGG;IACH,aAFa,OAAO,qBAAqB,EAAE,OAAO,CAajD;IAID;;;;;OAKG;IACH,uBAFW,MAAM,yDAUhB;IAED;;OAEG;IACH,iBAFW,MAAM,yDAKhB;IAED;;;OAGG;IACH,qBAHW,MAAM,+CAAmB,GACvB,MAAM,CAQlB;IAED;;;OAGG;IACH,8BAHW,kFAA4B,GAC1B,MAAM,EAAE,CAOpB;;CACJ;kCAr2B+B,CAAC,SAApB,6CAAkB;;;;UAGrB,OAAO,eAAe,EAAE,OAAO;aAC/B,CAAC;gBACD,OAAO,oBAAoB,EAAE,mBAAmB;sBAChD,CAAC,OAAO,+CAAkB,EAAE,IAAI,EAAE,OAAO,oBAAoB,EAAE,IAAI,kDAAgB"}
|
|
@@ -469,6 +469,13 @@ export default class ScaleResolution {
|
|
|
469
469
|
scale.props = props;
|
|
470
470
|
this.#configureRange();
|
|
471
471
|
|
|
472
|
+
if (!this.#initialDomain && isContinuous(scale.type)) {
|
|
473
|
+
const domain = scale.domain();
|
|
474
|
+
if (span(domain) > 0) {
|
|
475
|
+
this.#initialDomain = domain;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
472
479
|
if (!domainWasInitialized) {
|
|
473
480
|
this.#initialDomain = scale.domain();
|
|
474
481
|
this.#notifyListeners("domain");
|