@jbrowse/core 5.0.0-beta.4 → 5.0.0-beta.8
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/README.md +278 -250
- package/esm/PluginManager.js +1 -1
- package/esm/ReExports/publicUi.d.ts +1 -1
- package/esm/ReExports/publicUi.js +1 -1
- package/esm/ReExports/publicUtil.d.ts +1 -1
- package/esm/ReExports/publicUtil.js +1 -1
- package/esm/ReExports/workerNamespaceNames.js +3 -9
- package/esm/assemblyManager/assemblyConfigSchema.d.ts +2 -1
- package/esm/assemblyManager/assemblyConfigSchema.js +2 -1
- package/esm/assemblyManager/expandAssemblyConfigShorthand.d.ts +15 -15
- package/esm/assemblyManager/expandAssemblyConfigShorthand.js +34 -18
- package/esm/configuration/configurationSchema.d.ts +1 -2
- package/esm/configuration/configurationSchema.js +3 -8
- package/esm/configuration/configurationSlot.d.ts +0 -47
- package/esm/configuration/configurationSlot.js +4 -54
- package/esm/configuration/fillLocations.d.ts +14 -0
- package/esm/configuration/fillLocations.js +14 -0
- package/esm/configuration/fullConfSnapshot.d.ts +0 -15
- package/esm/configuration/fullConfSnapshot.js +0 -28
- package/esm/configuration/getConf.d.ts +4 -34
- package/esm/configuration/getConf.js +3 -38
- package/esm/configuration/index.d.ts +3 -4
- package/esm/configuration/index.js +3 -2
- package/esm/configuration/schemaRegistry.d.ts +1 -2
- package/esm/configuration/schemaRegistry.js +1 -2
- package/esm/configuration/slotFacade.d.ts +1 -16
- package/esm/configuration/slotFacade.js +1 -2
- package/esm/configuration/tabixShorthand.js +3 -3
- package/esm/configuration/types.d.ts +4 -16
- package/esm/data_adapters/CytobandAdapter/configSchema.js +3 -4
- package/esm/pluggableElementTypes/DisplayType.d.ts +5 -3
- package/esm/pluggableElementTypes/DisplayType.js +3 -1
- package/esm/pluggableElementTypes/models/BaseTrackModel.d.ts +30 -0
- package/esm/pluggableElementTypes/models/BaseTrackModel.js +45 -17
- package/esm/rpc/RpcRegistry.d.ts +7 -1
- package/esm/rpc/coreRpcMethods.d.ts +1 -0
- package/esm/rpc/coreRpcMethods.js +1 -0
- package/esm/rpc/methods/CoreEncodeFeatures.d.ts +15 -0
- package/esm/rpc/methods/CoreEncodeFeatures.js +62 -0
- package/esm/svg/svgId.d.ts +1 -1
- package/esm/svg/svgId.js +1 -1
- package/esm/ui/LegendSwatchGlyph.js +26 -21
- package/esm/ui/MenuTypes.d.ts +5 -52
- package/esm/ui/SizeSliderRow.d.ts +1 -3
- package/esm/ui/SizeSliderRow.js +4 -6
- package/esm/ui/SvgColorLegend.d.ts +9 -3
- package/esm/ui/SvgColorLegend.js +74 -33
- package/esm/ui/colorScale.d.ts +64 -0
- package/esm/ui/colorScale.js +41 -0
- package/esm/ui/colors.d.ts +11 -0
- package/esm/ui/colors.js +7 -0
- package/esm/ui/index.d.ts +5 -7
- package/esm/ui/index.js +3 -13
- package/esm/ui/legendMenuItem.d.ts +5 -16
- package/esm/ui/legendMenuItem.js +3 -14
- package/esm/ui/legendSpec.d.ts +11 -5
- package/esm/ui/legendSpec.js +8 -11
- package/esm/ui/makeSizeMenu.d.ts +0 -6
- package/esm/ui/makeSizeMenu.js +6 -30
- package/esm/ui/measureLegendText.js +1 -1
- package/esm/ui/menuItemAdornment.d.ts +9 -19
- package/esm/ui/menuItemAdornment.js +10 -31
- package/esm/ui/menuItems.d.ts +2 -6
- package/esm/ui/menuItems.js +0 -4
- package/esm/ui/palette.js +2 -1
- package/esm/ui/toggleMenuItems.d.ts +6 -13
- package/esm/ui/toggleMenuItems.js +13 -29
- package/esm/util/colorRamp.d.ts +3 -3
- package/esm/util/colorRamp.js +2 -2
- package/esm/util/featureTransforms.d.ts +15 -0
- package/esm/util/featureTransforms.js +342 -0
- package/esm/util/freezeDeep.d.ts +8 -10
- package/esm/util/freezeDeep.js +8 -10
- package/esm/util/glyphNames.d.ts +8 -0
- package/esm/util/glyphNames.js +12 -0
- package/esm/util/index.d.ts +0 -1
- package/esm/util/index.js +0 -1
- package/esm/util/markEncoding.d.ts +76 -0
- package/esm/util/markEncoding.js +436 -0
- package/esm/util/markEncodingTypes.d.ts +331 -0
- package/esm/util/markEncodingTypes.js +8 -0
- package/esm/util/renderToStaticMarkup.js +1 -1
- package/esm/util/starFusion.d.ts +26 -0
- package/esm/util/starFusion.js +37 -0
- package/esm/util/trackConfigDelta.d.ts +4 -8
- package/esm/util/trackConfigDelta.js +2 -4
- package/esm/util/tracks.d.ts +7 -1
- package/esm/util/tracks.js +72 -7
- package/esm/util/types/index.d.ts +12 -4
- package/esm/util/whenViewSettled.d.ts +9 -0
- package/esm/util/whenViewSettled.js +12 -4
- package/package.json +13 -3
- package/esm/configuration/promotableDefaults.d.ts +0 -234
- package/esm/configuration/promotableDefaults.js +0 -395
- package/esm/configuration/promotablePin.d.ts +0 -65
- package/esm/configuration/promotablePin.js +0 -1
- package/esm/configuration/promotableResolve.d.ts +0 -108
- package/esm/configuration/promotableResolve.js +0 -117
- package/esm/configuration/promotableSlots.d.ts +0 -12
- package/esm/configuration/promotableSlots.js +0 -45
- package/esm/configuration/slotShape.d.ts +0 -12
- package/esm/configuration/slotShape.js +0 -84
- package/esm/ui/PinAdornment.d.ts +0 -5
- package/esm/ui/PinAdornment.js +0 -67
- package/esm/ui/SvgGradientLegend.d.ts +0 -26
- package/esm/ui/SvgGradientLegend.js +0 -26
- package/esm/ui/promotablePinCoverage.d.ts +0 -58
- package/esm/ui/promotablePinCoverage.js +0 -124
- package/esm/util/openDisplays.d.ts +0 -23
- package/esm/util/openDisplays.js +0 -79
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { GLYPH_DISC } from '@jbrowse/render-core/shaders/pointMarkConsts';
|
|
2
|
+
import { categoricalPalette, categoricalValueColor } from '../ui/colors.js';
|
|
3
|
+
import { cssColorToABGR, cssColorToRgba, packAbgr } from './colorBits.js';
|
|
4
|
+
import { VIRIDIS_STOPS, buildColorRampLut } from './colorRamp.js';
|
|
5
|
+
import Flatbush from './flatbush/index.js';
|
|
6
|
+
import { GLYPH_CODES, GLYPH_NAMES } from './glyphNames.js';
|
|
7
|
+
import { isJexl, stringToJexlExpression } from './jexlStrings.js';
|
|
8
|
+
import { buildJexlContext } from './simpleFeature.js';
|
|
9
|
+
export const DEFAULT_MARK_COLOR = '#0068d1';
|
|
10
|
+
// What a feature paints when its colour field is missing or a `jexl:` colour
|
|
11
|
+
// yields a non-string: visible, so a misconfiguration surfaces rather than
|
|
12
|
+
// vanishing.
|
|
13
|
+
const FALLBACK_COLOR = cssColorToABGR('#808080');
|
|
14
|
+
/**
|
|
15
|
+
* #api
|
|
16
|
+
* The key row a feature with nothing in a categorical field lands on, so the
|
|
17
|
+
* legend says why a mark is grey, or a disc, rather than listing a blank
|
|
18
|
+
* value.
|
|
19
|
+
*/
|
|
20
|
+
export const NO_VALUE_LABEL = '(no value)';
|
|
21
|
+
function jexlExpression(ref, jexl) {
|
|
22
|
+
if (!jexl) {
|
|
23
|
+
throw new Error(`a jexl: channel needs a jexl instance (${ref})`);
|
|
24
|
+
}
|
|
25
|
+
return stringToJexlExpression(ref, jexl);
|
|
26
|
+
}
|
|
27
|
+
function fieldReader(ref, jexl) {
|
|
28
|
+
if (typeof ref === 'function') {
|
|
29
|
+
return ref;
|
|
30
|
+
}
|
|
31
|
+
if (isJexl(ref)) {
|
|
32
|
+
const expr = jexlExpression(ref, jexl);
|
|
33
|
+
return feature => expr.eval(buildJexlContext({ feature }));
|
|
34
|
+
}
|
|
35
|
+
return feature => feature.get(ref);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* #api
|
|
39
|
+
* The field a `y` declaration reads, whichever of its two forms it is
|
|
40
|
+
* written in.
|
|
41
|
+
*/
|
|
42
|
+
export function valueField(y) {
|
|
43
|
+
return typeof y === 'string' ? y : y.field;
|
|
44
|
+
}
|
|
45
|
+
function isGlyphName(glyph) {
|
|
46
|
+
return glyph in GLYPH_CODES;
|
|
47
|
+
}
|
|
48
|
+
function glyphReader(glyph, jexl) {
|
|
49
|
+
if (glyph === undefined) {
|
|
50
|
+
return () => GLYPH_DISC;
|
|
51
|
+
}
|
|
52
|
+
if (typeof glyph === 'function') {
|
|
53
|
+
return glyph;
|
|
54
|
+
}
|
|
55
|
+
if (isGlyphName(glyph)) {
|
|
56
|
+
const code = GLYPH_CODES[glyph];
|
|
57
|
+
return () => code;
|
|
58
|
+
}
|
|
59
|
+
const expr = jexlExpression(glyph, jexl);
|
|
60
|
+
return feature => {
|
|
61
|
+
const v = expr.eval(buildJexlContext({ feature }));
|
|
62
|
+
return typeof v === 'string' && isGlyphName(v) ? GLYPH_CODES[v] : GLYPH_DISC;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function rampStops(ramp) {
|
|
66
|
+
if (ramp === undefined || ramp === 'viridis') {
|
|
67
|
+
return VIRIDIS_STOPS;
|
|
68
|
+
}
|
|
69
|
+
return ramp.map(c => cssColorToRgba(c));
|
|
70
|
+
}
|
|
71
|
+
function lutColorAt(lut, t) {
|
|
72
|
+
const entries = lut.length / 4;
|
|
73
|
+
const i = Math.min(entries - 1, Math.max(0, Math.round(t * (entries - 1))));
|
|
74
|
+
const o = i * 4;
|
|
75
|
+
return packAbgr(lut[o], lut[o + 1], lut[o + 2], lut[o + 3]);
|
|
76
|
+
}
|
|
77
|
+
// The `[0, 1]` fraction a value sits at in the domain: the scale half of the
|
|
78
|
+
// y-channel split (ADR-097), spelled once here for colour. The log arm floors
|
|
79
|
+
// at the domain's own minimum so a domain below 1 still spreads.
|
|
80
|
+
function normalizer(scale, domain) {
|
|
81
|
+
const [min, max] = domain;
|
|
82
|
+
if (scale === 'log') {
|
|
83
|
+
const floorV = min > 0 ? min : 1;
|
|
84
|
+
const logMin = Math.log2(floorV);
|
|
85
|
+
const logRange = Math.log2(Math.max(max, floorV)) - logMin;
|
|
86
|
+
return (v) => logRange <= 0
|
|
87
|
+
? 0
|
|
88
|
+
: Math.min(1, Math.max(0, (Math.log2(Math.max(v, floorV)) - logMin) / logRange));
|
|
89
|
+
}
|
|
90
|
+
const range = max - min;
|
|
91
|
+
return (v) => range <= 0 ? 0 : Math.min(1, Math.max(0, (v - min) / range));
|
|
92
|
+
}
|
|
93
|
+
function categoryOrder(seen, domain) {
|
|
94
|
+
const listed = (domain ?? []).map(String);
|
|
95
|
+
const rest = [...seen.keys()].filter(k => !listed.includes(k));
|
|
96
|
+
const numeric = rest.every(k => k !== '' && Number.isFinite(Number(k)));
|
|
97
|
+
rest.sort(numeric ? (a, b) => Number(a) - Number(b) : (a, b) => a.localeCompare(b));
|
|
98
|
+
return [...listed, ...rest];
|
|
99
|
+
}
|
|
100
|
+
// The categorical arm `color` and `glyph` share: the walk records which
|
|
101
|
+
// distinct value each admitted instance carried, and `resolve` hands every
|
|
102
|
+
// value its range entry once the region's table is known. A listed domain
|
|
103
|
+
// is the author's order and walks the range; without one the entry derives
|
|
104
|
+
// from the value itself, so two regions that met different value sets still
|
|
105
|
+
// agree on every value they share.
|
|
106
|
+
function categoricalChannel(read, n) {
|
|
107
|
+
const categories = new Map();
|
|
108
|
+
const indexOf = new Int32Array(n);
|
|
109
|
+
return {
|
|
110
|
+
indexOf,
|
|
111
|
+
collect(f, at) {
|
|
112
|
+
const raw = read(f);
|
|
113
|
+
if (raw === undefined || raw === null || raw === '') {
|
|
114
|
+
indexOf[at] = -1;
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
const label = String(raw);
|
|
118
|
+
let index = categories.get(label);
|
|
119
|
+
if (index === undefined) {
|
|
120
|
+
index = categories.size;
|
|
121
|
+
categories.set(label, index);
|
|
122
|
+
}
|
|
123
|
+
indexOf[at] = index;
|
|
124
|
+
},
|
|
125
|
+
resolve(domain, range) {
|
|
126
|
+
const ofIndex = Array.from({ length: categories.size });
|
|
127
|
+
const entries = [];
|
|
128
|
+
categoryOrder(categories, domain).forEach((label, slot) => {
|
|
129
|
+
const value = domain
|
|
130
|
+
? range[slot % range.length]
|
|
131
|
+
: categoricalValueColor(label, range);
|
|
132
|
+
const index = categories.get(label);
|
|
133
|
+
if (index !== undefined) {
|
|
134
|
+
ofIndex[index] = value;
|
|
135
|
+
}
|
|
136
|
+
entries.push({ label, value });
|
|
137
|
+
});
|
|
138
|
+
return { ofIndex, entries };
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function hasMissing(indexOf, count) {
|
|
143
|
+
for (let i = 0; i < count; i++) {
|
|
144
|
+
if (indexOf[i] < 0) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* #api
|
|
152
|
+
* Evaluate one encoding over a feature list into dense channel arrays for
|
|
153
|
+
* the lanes named, the scale table each scaled channel came from, the `y`
|
|
154
|
+
* extremes and — when `index` is among the lanes — a hit index.
|
|
155
|
+
*
|
|
156
|
+
* A feature whose `x`, `x2` or (declared and asked-for) `y` is not finite is
|
|
157
|
+
* skipped and counted in `skipped`, so every array stays index-aligned with
|
|
158
|
+
* the Flatbush. Pure: the RPC around it owns the adapter, the filters and the
|
|
159
|
+
* transferables.
|
|
160
|
+
*/
|
|
161
|
+
export function encodeFeatures(features, encoding, lanes, ctx = {}) {
|
|
162
|
+
const { jexl, report } = ctx;
|
|
163
|
+
const n = features.length;
|
|
164
|
+
const has = (lane) => lanes.includes(lane);
|
|
165
|
+
const readX = fieldReader(encoding.x ?? 'start', jexl);
|
|
166
|
+
const readX2 = fieldReader(encoding.x2 ?? 'end', jexl);
|
|
167
|
+
const { y: yEncoding } = encoding;
|
|
168
|
+
const readY = has('y') && yEncoding !== undefined
|
|
169
|
+
? fieldReader(typeof yEncoding === 'object' ? valueField(yEncoding) : yEncoding, jexl)
|
|
170
|
+
: undefined;
|
|
171
|
+
const readRow = has('row') && encoding.row !== undefined
|
|
172
|
+
? fieldReader(encoding.row, jexl)
|
|
173
|
+
: undefined;
|
|
174
|
+
const x = new Uint32Array(n);
|
|
175
|
+
const x2 = new Uint32Array(n);
|
|
176
|
+
const y = has('y') ? new Float32Array(n) : undefined;
|
|
177
|
+
const row = has('row') ? new Uint32Array(n) : undefined;
|
|
178
|
+
const glyph = has('glyph') ? new Uint8Array(n) : undefined;
|
|
179
|
+
const featureIndex = new Uint32Array(n);
|
|
180
|
+
let yMin = Infinity;
|
|
181
|
+
let yMax = -Infinity;
|
|
182
|
+
let count = 0;
|
|
183
|
+
const colorEncoding = encoding.color ?? DEFAULT_MARK_COLOR;
|
|
184
|
+
const declaredScale = typeof colorEncoding === 'object' ? colorEncoding : undefined;
|
|
185
|
+
const rampEncoding = declaredScale && declaredScale.scale !== 'categorical'
|
|
186
|
+
? declaredScale
|
|
187
|
+
: undefined;
|
|
188
|
+
// Which side of the wire a ramp resolves on is the caller's lane choice: a
|
|
189
|
+
// shape that reads the ramp itself names `colorValue` and gets the raw
|
|
190
|
+
// values, and the display unions the regions' extremes into one domain.
|
|
191
|
+
// Anything else names `color` and the walk resolves per region.
|
|
192
|
+
const colorValue = rampEncoding && has('colorValue') ? new Float32Array(n) : undefined;
|
|
193
|
+
const color = has('color') && !colorValue ? new Uint32Array(n) : undefined;
|
|
194
|
+
const wantColor = color !== undefined || colorValue !== undefined;
|
|
195
|
+
const scaled = wantColor ? declaredScale : undefined;
|
|
196
|
+
const readColor = !wantColor
|
|
197
|
+
? undefined
|
|
198
|
+
: typeof colorEncoding === 'function'
|
|
199
|
+
? colorEncoding
|
|
200
|
+
: scaled === undefined
|
|
201
|
+
? colorEvaluator(colorEncoding, jexl)
|
|
202
|
+
: fieldReader(scaled.field, jexl);
|
|
203
|
+
// A scaled channel resolves after the walk, once the table is known: the
|
|
204
|
+
// category per admitted instance, or a ramp's raw value, kept here.
|
|
205
|
+
const colorCategories = scaled?.scale === 'categorical' && readColor
|
|
206
|
+
? categoricalChannel(readColor, n)
|
|
207
|
+
: undefined;
|
|
208
|
+
const rampValues = scaled && scaled.scale !== 'categorical'
|
|
209
|
+
? (colorValue ?? new Float32Array(n))
|
|
210
|
+
: undefined;
|
|
211
|
+
const { glyph: glyphEncoding } = encoding;
|
|
212
|
+
const glyphScaled = glyph && typeof glyphEncoding === 'object' ? glyphEncoding : undefined;
|
|
213
|
+
const glyphCategories = glyphScaled
|
|
214
|
+
? categoricalChannel(fieldReader(glyphScaled.field, jexl), n)
|
|
215
|
+
: undefined;
|
|
216
|
+
const readGlyph = glyph
|
|
217
|
+
? glyphReader(typeof glyphEncoding === 'object' ? undefined : glyphEncoding, jexl)
|
|
218
|
+
: undefined;
|
|
219
|
+
for (let i = 0; i < n; i++) {
|
|
220
|
+
report?.(i);
|
|
221
|
+
const f = features[i];
|
|
222
|
+
const xv = Number(readX(f));
|
|
223
|
+
const x2v = Number(readX2(f));
|
|
224
|
+
const yv = readY ? Number(readY(f)) : 0;
|
|
225
|
+
if (!Number.isFinite(xv) || !Number.isFinite(x2v) || !Number.isFinite(yv)) {
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
x[count] = xv;
|
|
229
|
+
x2[count] = x2v;
|
|
230
|
+
if (y) {
|
|
231
|
+
y[count] = yv;
|
|
232
|
+
}
|
|
233
|
+
if (readY) {
|
|
234
|
+
if (yv < yMin) {
|
|
235
|
+
yMin = yv;
|
|
236
|
+
}
|
|
237
|
+
if (yv > yMax) {
|
|
238
|
+
yMax = yv;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (row && readRow) {
|
|
242
|
+
const rv = Number(readRow(f));
|
|
243
|
+
row[count] = rv > 0 ? rv : 0;
|
|
244
|
+
}
|
|
245
|
+
if (glyphCategories) {
|
|
246
|
+
glyphCategories.collect(f, count);
|
|
247
|
+
}
|
|
248
|
+
else if (glyph && readGlyph) {
|
|
249
|
+
glyph[count] = readGlyph(f);
|
|
250
|
+
}
|
|
251
|
+
featureIndex[count] = i;
|
|
252
|
+
if (colorCategories) {
|
|
253
|
+
colorCategories.collect(f, count);
|
|
254
|
+
}
|
|
255
|
+
else if (rampValues && readColor) {
|
|
256
|
+
rampValues[count] = Number(readColor(f));
|
|
257
|
+
}
|
|
258
|
+
else if (color && readColor) {
|
|
259
|
+
color[count] = readColor(f);
|
|
260
|
+
}
|
|
261
|
+
count++;
|
|
262
|
+
}
|
|
263
|
+
let scale;
|
|
264
|
+
if (scaled?.scale === 'categorical' && colorCategories && color) {
|
|
265
|
+
const palette = (scaled.palette ?? categoricalPalette).map(cssColorToABGR);
|
|
266
|
+
const { ofIndex, entries } = colorCategories.resolve(scaled.domain, palette);
|
|
267
|
+
const { indexOf } = colorCategories;
|
|
268
|
+
for (let i = 0; i < count; i++) {
|
|
269
|
+
const index = indexOf[i];
|
|
270
|
+
color[i] = index < 0 ? FALLBACK_COLOR : ofIndex[index];
|
|
271
|
+
}
|
|
272
|
+
scale = {
|
|
273
|
+
kind: 'categorical',
|
|
274
|
+
field: scaled.field,
|
|
275
|
+
entries: [
|
|
276
|
+
...entries.map(e => ({ label: e.label, color: e.value })),
|
|
277
|
+
...(hasMissing(indexOf, count)
|
|
278
|
+
? [{ label: NO_VALUE_LABEL, color: FALLBACK_COLOR }]
|
|
279
|
+
: []),
|
|
280
|
+
],
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
else if (scaled && scaled.scale !== 'categorical' && rampValues) {
|
|
284
|
+
const extent = finiteExtremes(rampValues, count);
|
|
285
|
+
const domain = scaled.domain ?? extent;
|
|
286
|
+
const lut = buildColorRampLut(rampStops(scaled.ramp));
|
|
287
|
+
if (color) {
|
|
288
|
+
const norm = normalizer(scaled.scale, domain);
|
|
289
|
+
for (let i = 0; i < count; i++) {
|
|
290
|
+
const v = rampValues[i];
|
|
291
|
+
color[i] = Number.isFinite(v)
|
|
292
|
+
? lutColorAt(lut, norm(v))
|
|
293
|
+
: FALLBACK_COLOR;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
scale = {
|
|
297
|
+
kind: 'ramp',
|
|
298
|
+
field: scaled.field,
|
|
299
|
+
scale: scaled.scale,
|
|
300
|
+
domain,
|
|
301
|
+
pinned: scaled.domain !== undefined,
|
|
302
|
+
extent,
|
|
303
|
+
lut,
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
let glyphScale;
|
|
307
|
+
if (glyphScaled && glyphCategories && glyph) {
|
|
308
|
+
const { ofIndex, entries } = glyphCategories.resolve(glyphScaled.domain, glyphScaled.range ?? GLYPH_NAMES);
|
|
309
|
+
const codeOfIndex = Uint8Array.from(ofIndex, name => GLYPH_CODES[name]);
|
|
310
|
+
const { indexOf } = glyphCategories;
|
|
311
|
+
for (let i = 0; i < count; i++) {
|
|
312
|
+
const index = indexOf[i];
|
|
313
|
+
glyph[i] = index < 0 ? GLYPH_DISC : codeOfIndex[index];
|
|
314
|
+
}
|
|
315
|
+
glyphScale = {
|
|
316
|
+
kind: 'glyph',
|
|
317
|
+
field: glyphScaled.field,
|
|
318
|
+
entries: [
|
|
319
|
+
...entries.map(e => ({ label: e.label, glyph: e.value })),
|
|
320
|
+
...(hasMissing(indexOf, count)
|
|
321
|
+
? [{ label: NO_VALUE_LABEL, glyph: 'disc' }]
|
|
322
|
+
: []),
|
|
323
|
+
],
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
let flatbushData;
|
|
327
|
+
if (has('index') && count > 0) {
|
|
328
|
+
const fb = new Flatbush(count, undefined, Float64Array);
|
|
329
|
+
for (let i = 0; i < count; i++) {
|
|
330
|
+
const v = y ? y[i] : 0;
|
|
331
|
+
fb.add(x[i], v, x2[i], v);
|
|
332
|
+
}
|
|
333
|
+
fb.finish();
|
|
334
|
+
flatbushData = fb.data;
|
|
335
|
+
}
|
|
336
|
+
const encoded = {
|
|
337
|
+
count,
|
|
338
|
+
skipped: n - count,
|
|
339
|
+
x: x.subarray(0, count),
|
|
340
|
+
x2: x2.subarray(0, count),
|
|
341
|
+
featureIndex: featureIndex.subarray(0, count),
|
|
342
|
+
yMin,
|
|
343
|
+
yMax,
|
|
344
|
+
};
|
|
345
|
+
if (y) {
|
|
346
|
+
encoded.y = y.subarray(0, count);
|
|
347
|
+
}
|
|
348
|
+
if (row) {
|
|
349
|
+
encoded.row = row.subarray(0, count);
|
|
350
|
+
}
|
|
351
|
+
if (color) {
|
|
352
|
+
encoded.color = color.subarray(0, count);
|
|
353
|
+
}
|
|
354
|
+
if (colorValue) {
|
|
355
|
+
encoded.colorValue = colorValue.subarray(0, count);
|
|
356
|
+
}
|
|
357
|
+
if (glyph) {
|
|
358
|
+
encoded.glyph = glyph.subarray(0, count);
|
|
359
|
+
}
|
|
360
|
+
if (flatbushData) {
|
|
361
|
+
encoded.flatbushData = flatbushData;
|
|
362
|
+
}
|
|
363
|
+
if (scale) {
|
|
364
|
+
encoded.scale = scale;
|
|
365
|
+
}
|
|
366
|
+
if (glyphScale) {
|
|
367
|
+
encoded.glyphScale = glyphScale;
|
|
368
|
+
}
|
|
369
|
+
return encoded;
|
|
370
|
+
}
|
|
371
|
+
function finiteExtremes(values, count) {
|
|
372
|
+
let min = Infinity;
|
|
373
|
+
let max = -Infinity;
|
|
374
|
+
for (let i = 0; i < count; i++) {
|
|
375
|
+
const v = values[i];
|
|
376
|
+
if (Number.isFinite(v)) {
|
|
377
|
+
if (v < min) {
|
|
378
|
+
min = v;
|
|
379
|
+
}
|
|
380
|
+
if (v > max) {
|
|
381
|
+
max = v;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
return min === Infinity ? [0, 1] : [min, max];
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* #api
|
|
389
|
+
* A CSS colour or `jexl:` colour expression as a per-feature packed ABGR —
|
|
390
|
+
* the unscaled arm of {@link ColorEncoding}, on its own for a display that
|
|
391
|
+
* carries a plain `color` slot.
|
|
392
|
+
*/
|
|
393
|
+
export function colorEvaluator(color, jexl) {
|
|
394
|
+
if (isJexl(color)) {
|
|
395
|
+
const expr = jexlExpression(color, jexl);
|
|
396
|
+
// A jexl colour answers from a handful of strings over a million
|
|
397
|
+
// features; parsing each answer once is a third of the arm's cost
|
|
398
|
+
// (packages/core/benches/encodeFeatures.bench.ts).
|
|
399
|
+
const packed = new Map();
|
|
400
|
+
return feature => {
|
|
401
|
+
const v = expr.eval(buildJexlContext({ feature }));
|
|
402
|
+
if (typeof v !== 'string') {
|
|
403
|
+
return FALLBACK_COLOR;
|
|
404
|
+
}
|
|
405
|
+
let c = packed.get(v);
|
|
406
|
+
if (c === undefined) {
|
|
407
|
+
c = cssColorToABGR(v);
|
|
408
|
+
packed.set(v, c);
|
|
409
|
+
}
|
|
410
|
+
return c;
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
const constant = cssColorToABGR(color);
|
|
414
|
+
return () => constant;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* #api
|
|
418
|
+
* The buffers an {@link EncodedChannels} owns, for `rpcResult`'s transfer
|
|
419
|
+
* list.
|
|
420
|
+
*/
|
|
421
|
+
export function encodedChannelTransferables(c) {
|
|
422
|
+
const buffers = [
|
|
423
|
+
c.x.buffer,
|
|
424
|
+
c.x2.buffer,
|
|
425
|
+
c.featureIndex.buffer,
|
|
426
|
+
];
|
|
427
|
+
for (const lane of [c.y, c.row, c.color, c.colorValue, c.glyph]) {
|
|
428
|
+
if (lane) {
|
|
429
|
+
buffers.push(lane.buffer);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
if (c.flatbushData) {
|
|
433
|
+
buffers.push(c.flatbushData);
|
|
434
|
+
}
|
|
435
|
+
return buffers;
|
|
436
|
+
}
|