@perspective-dev/viewer-charts 5.2.0 → 5.3.1
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/cdn/perspective-viewer-charts.js +1 -2
- package/dist/cdn/perspective-viewer-charts.js.map +4 -4
- package/dist/esm/axis/legend.d.ts +53 -5
- package/dist/esm/axis/map-ticks.d.ts +15 -0
- package/dist/esm/charts/candlestick/candlestick-interact.d.ts +2 -6
- package/dist/esm/charts/candlestick/candlestick-render.d.ts +3 -0
- package/dist/esm/charts/cartesian/cartesian.d.ts +7 -0
- package/dist/esm/charts/cartesian/glyph.d.ts +2 -1
- package/dist/esm/charts/cartesian/glyphs/density.d.ts +2 -1
- package/dist/esm/charts/cartesian/glyphs/lines.d.ts +2 -1
- package/dist/esm/charts/cartesian/glyphs/points.d.ts +2 -1
- package/dist/esm/charts/cartesian/tooltip-lines.d.ts +1 -1
- package/dist/esm/charts/chart-base.d.ts +24 -9
- package/dist/esm/charts/chart.d.ts +81 -6
- package/dist/esm/charts/common/category-axis-resolver.d.ts +1 -1
- package/dist/esm/charts/common/draw-tooltip-box.d.ts +2 -1
- package/dist/esm/charts/common/tree-chart.d.ts +1 -0
- package/dist/esm/charts/common/tree-interact.d.ts +1 -1
- package/dist/esm/charts/heatmap/heatmap.d.ts +1 -0
- package/dist/esm/charts/series/series-build.d.ts +7 -0
- package/dist/esm/charts/series/series-interact.d.ts +1 -1
- package/dist/esm/charts/series/series-type.d.ts +18 -0
- package/dist/esm/charts/series/series.d.ts +10 -1
- package/dist/esm/charts/sunburst/sunburst-render.d.ts +4 -0
- package/dist/esm/index.d.ts +2 -0
- package/dist/esm/interaction/axis-indicators.d.ts +11 -0
- package/dist/esm/interaction/host-sink-dom.d.ts +3 -2
- package/dist/esm/interaction/host-sink-message.d.ts +3 -2
- package/dist/esm/interaction/lazy-tooltip.d.ts +2 -2
- package/dist/esm/interaction/legend-controller.d.ts +246 -0
- package/dist/esm/interaction/tooltip-controller.d.ts +20 -95
- package/dist/esm/interaction/tooltip-grid.d.ts +33 -0
- package/dist/esm/layout/facet-grid.d.ts +6 -0
- package/dist/esm/layout/ticks.d.ts +1 -0
- package/dist/esm/map/tile-source.d.ts +77 -8
- package/dist/esm/perspective-viewer-charts.js +1 -2
- package/dist/esm/perspective-viewer-charts.js.map +4 -4
- package/dist/esm/plugin/charts.d.ts +2 -0
- package/dist/esm/plugin/format-defaults.d.ts +9 -0
- package/dist/esm/plugin/plugin.d.ts +28 -1
- package/dist/esm/theme/gradient.d.ts +32 -0
- package/dist/esm/theme/overrides.d.ts +19 -0
- package/dist/esm/transport/protocol.d.ts +58 -5
- package/dist/esm/transport/renderer-transport.d.ts +3 -0
- package/dist/esm/utils/css.d.ts +4 -0
- package/dist/esm/worker/renderer.worker.d.ts +46 -1
- package/package.json +1 -1
- package/src/css/perspective-viewer-charts.css +0 -1
- package/src/ts/axis/bar-axis.ts +2 -7
- package/src/ts/axis/legend.ts +351 -24
- package/src/ts/axis/map-ticks.ts +94 -0
- package/src/ts/axis/numeric-axis.ts +2 -9
- package/src/ts/charts/candlestick/candlestick-build.ts +1 -0
- package/src/ts/charts/candlestick/candlestick-interact.ts +46 -40
- package/src/ts/charts/candlestick/candlestick-render.ts +81 -10
- package/src/ts/charts/candlestick/candlestick.ts +4 -3
- package/src/ts/charts/cartesian/cartesian-interact.ts +9 -3
- package/src/ts/charts/cartesian/cartesian-render.ts +374 -75
- package/src/ts/charts/cartesian/cartesian.ts +13 -0
- package/src/ts/charts/cartesian/glyph.ts +6 -2
- package/src/ts/charts/cartesian/glyphs/density.ts +2 -2
- package/src/ts/charts/cartesian/glyphs/lines.ts +15 -11
- package/src/ts/charts/cartesian/glyphs/points.ts +9 -5
- package/src/ts/charts/cartesian/tooltip-lines.ts +8 -8
- package/src/ts/charts/chart-base.ts +76 -42
- package/src/ts/charts/chart.ts +111 -7
- package/src/ts/charts/common/category-axis-resolver.ts +44 -5
- package/src/ts/charts/common/draw-tooltip-box.ts +13 -30
- package/src/ts/charts/common/tree-chart.ts +4 -0
- package/src/ts/charts/common/tree-chrome.ts +102 -23
- package/src/ts/charts/common/tree-interact.ts +18 -17
- package/src/ts/charts/heatmap/heatmap-build.ts +7 -1
- package/src/ts/charts/heatmap/heatmap-interact.ts +75 -8
- package/src/ts/charts/heatmap/heatmap-render.ts +105 -24
- package/src/ts/charts/heatmap/heatmap.ts +8 -3
- package/src/ts/charts/map/map.ts +3 -5
- package/src/ts/charts/series/series-build.ts +15 -2
- package/src/ts/charts/series/series-interact.ts +14 -24
- package/src/ts/charts/series/series-render.ts +310 -132
- package/src/ts/charts/series/series-type.ts +65 -0
- package/src/ts/charts/series/series.ts +31 -8
- package/src/ts/charts/sunburst/sunburst-render.ts +16 -1
- package/src/ts/charts/treemap/treemap-render.ts +12 -1
- package/src/ts/data/view-reader.ts +1 -4
- package/src/ts/index.ts +2 -0
- package/src/ts/interaction/axis-indicators.ts +137 -0
- package/src/ts/interaction/host-sink-dom.ts +66 -2
- package/src/ts/interaction/host-sink-message.ts +8 -2
- package/src/ts/interaction/lazy-tooltip.ts +5 -5
- package/src/ts/interaction/legend-controller.ts +1084 -0
- package/src/ts/interaction/tooltip-controller.ts +43 -131
- package/src/ts/interaction/tooltip-grid.ts +187 -0
- package/src/ts/layout/facet-grid.ts +8 -1
- package/src/ts/layout/ticks.ts +48 -12
- package/src/ts/map/tile-loader.ts +16 -1
- package/src/ts/map/tile-source.ts +199 -53
- package/src/ts/map/tile-sources.json +18 -0
- package/src/ts/plugin/charts.ts +40 -6
- package/src/ts/plugin/format-defaults.ts +37 -0
- package/src/ts/plugin/plugin.ts +330 -76
- package/src/ts/shaders/line.vert.glsl +5 -2
- package/src/ts/shaders/scatter.vert.glsl +0 -7
- package/src/ts/theme/gradient.ts +265 -1
- package/src/ts/theme/overrides.ts +76 -0
- package/src/ts/transport/protocol.ts +60 -4
- package/src/ts/transport/renderer-transport.ts +107 -14
- package/src/ts/utils/css.ts +12 -0
- package/src/ts/worker/dispatch.ts +6 -2
- package/src/ts/worker/renderer.worker.ts +191 -7
package/src/ts/theme/gradient.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
-
import { parseCSSColorToVec3 } from "../utils/css";
|
|
13
|
+
import { parseCSSColorToVec3, vec3ToHexColor } from "../utils/css";
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* A single stop on a parsed CSS gradient. `offset` ∈ [0, 1].
|
|
@@ -178,6 +178,246 @@ export function parseCssGradient(
|
|
|
178
178
|
return result;
|
|
179
179
|
}
|
|
180
180
|
|
|
181
|
+
/**
|
|
182
|
+
* A color literal — `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`, `rgb()` or
|
|
183
|
+
* `rgba()` — as a `[0, 1]` RGB triple, or `null` for anything else.
|
|
184
|
+
*/
|
|
185
|
+
export function parseCssColorStrict(
|
|
186
|
+
src: string,
|
|
187
|
+
): [number, number, number] | null {
|
|
188
|
+
const s = src.trim();
|
|
189
|
+
if (s.startsWith("#")) {
|
|
190
|
+
const hex = s.slice(1);
|
|
191
|
+
if (!/^[0-9a-f]+$/i.test(hex)) {
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (hex.length === 3 || hex.length === 4) {
|
|
196
|
+
return [
|
|
197
|
+
parseInt(hex[0] + hex[0], 16) / 255,
|
|
198
|
+
parseInt(hex[1] + hex[1], 16) / 255,
|
|
199
|
+
parseInt(hex[2] + hex[2], 16) / 255,
|
|
200
|
+
];
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (hex.length === 6 || hex.length === 8) {
|
|
204
|
+
return [
|
|
205
|
+
parseInt(hex.slice(0, 2), 16) / 255,
|
|
206
|
+
parseInt(hex.slice(2, 4), 16) / 255,
|
|
207
|
+
parseInt(hex.slice(4, 6), 16) / 255,
|
|
208
|
+
];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const m = s.match(/^rgba?\(([^)]*)\)$/i);
|
|
215
|
+
if (!m) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const tokens = m[1]
|
|
220
|
+
.split("/")[0]
|
|
221
|
+
.split(/[\s,]+/)
|
|
222
|
+
.filter((x) => x.length > 0);
|
|
223
|
+
if (tokens.length < 3 || tokens.length > 4) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const channel = (token: string): number | null => {
|
|
228
|
+
const pct = token.endsWith("%");
|
|
229
|
+
const n = parseFloat(pct ? token.slice(0, -1) : token);
|
|
230
|
+
if (!isFinite(n)) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const v = pct ? (n / 100) * 255 : n;
|
|
235
|
+
return Math.max(0, Math.min(255, Math.round(v))) / 255;
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const r = channel(tokens[0]);
|
|
239
|
+
const g = channel(tokens[1]);
|
|
240
|
+
const b = channel(tokens[2]);
|
|
241
|
+
return r === null || g === null || b === null ? null : [r, g, b];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* The shared `linear-gradient(...)` tokenizer of the viewer's CSS-valued
|
|
246
|
+
* config grammar.
|
|
247
|
+
*/
|
|
248
|
+
export function tokenizeLinearGradient(
|
|
249
|
+
src: string,
|
|
250
|
+
): Array<[[number, number, number], number | null]> | null {
|
|
251
|
+
const m = src.trim().match(/^linear-gradient\s*\((.*)\)$/is);
|
|
252
|
+
if (!m) {
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const body = m[1];
|
|
257
|
+
const parts: string[] = [];
|
|
258
|
+
let depth = 0;
|
|
259
|
+
let start = 0;
|
|
260
|
+
for (let i = 0; i < body.length; i++) {
|
|
261
|
+
const ch = body[i];
|
|
262
|
+
if (ch === "(") {
|
|
263
|
+
depth++;
|
|
264
|
+
} else if (ch === ")") {
|
|
265
|
+
depth--;
|
|
266
|
+
} else if (ch === "," && depth === 0) {
|
|
267
|
+
parts.push(body.substring(start, i));
|
|
268
|
+
start = i + 1;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
parts.push(body.substring(start));
|
|
273
|
+
const out: Array<[[number, number, number], number | null]> = [];
|
|
274
|
+
for (let i = 0; i < parts.length; i++) {
|
|
275
|
+
const piece = parts[i].trim();
|
|
276
|
+
if (!piece) {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const lower = piece.toLowerCase();
|
|
281
|
+
if (
|
|
282
|
+
i === 0 &&
|
|
283
|
+
(lower.startsWith("to ") ||
|
|
284
|
+
/^[-\d.]+(deg|rad|grad|turn)$/.test(lower))
|
|
285
|
+
) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const posMatch = piece.match(/\s([^\s)]+)$/);
|
|
290
|
+
let color = piece;
|
|
291
|
+
let offset: number | null = null;
|
|
292
|
+
if (posMatch) {
|
|
293
|
+
const tail = posMatch[1];
|
|
294
|
+
if (tail.endsWith("%")) {
|
|
295
|
+
const n = parseFloat(tail.slice(0, -1));
|
|
296
|
+
if (!isFinite(n)) {
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
color = piece.substring(0, posMatch.index).trim();
|
|
301
|
+
offset = n / 100;
|
|
302
|
+
} else if (/^[-\d.]/.test(tail)) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const rgb = parseCssColorStrict(color);
|
|
308
|
+
if (!rgb) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
out.push([rgb, offset]);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return out;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Strict gradient reader: a `linear-gradient(...)` with ≥ 2 stops,
|
|
320
|
+
* positions optional, offsets clamped to `[0, 1]` and sorted, or `null`
|
|
321
|
+
* on malformed input.
|
|
322
|
+
*/
|
|
323
|
+
export function parseCssGradientStrict(src: string): GradientStop[] | null {
|
|
324
|
+
const entries = tokenizeLinearGradient(src);
|
|
325
|
+
if (!entries || entries.length < 2) {
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const offsets: Array<number | null> = entries.map(([, p]) => p);
|
|
330
|
+
const last = offsets.length - 1;
|
|
331
|
+
if (offsets[0] === null) {
|
|
332
|
+
offsets[0] = 0;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (offsets[last] === null) {
|
|
336
|
+
offsets[last] = 1;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
for (let i = 1; i < last; i++) {
|
|
340
|
+
if (offsets[i] !== null) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
let j = i + 1;
|
|
345
|
+
while (offsets[j] === null) {
|
|
346
|
+
j++;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const before = offsets[i - 1]!;
|
|
350
|
+
const after = offsets[j]!;
|
|
351
|
+
const span = j - (i - 1);
|
|
352
|
+
for (let k = i; k < j; k++) {
|
|
353
|
+
offsets[k] = before + ((k - (i - 1)) / span) * (after - before);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
i = j - 1;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const stops: GradientStop[] = entries.map(([rgb], i) => ({
|
|
360
|
+
offset: Math.max(0, Math.min(1, offsets[i]!)),
|
|
361
|
+
color: [rgb[0], rgb[1], rgb[2], 1],
|
|
362
|
+
}));
|
|
363
|
+
|
|
364
|
+
stops.sort((a, b) => a.offset - b.offset);
|
|
365
|
+
return stops;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Strict palette reader: a `linear-gradient(...)` of ≥ 1 colors with no
|
|
370
|
+
* positions, or `null` on malformed input.
|
|
371
|
+
*/
|
|
372
|
+
export function parseCssColorList(
|
|
373
|
+
src: string,
|
|
374
|
+
): Array<[number, number, number]> | null {
|
|
375
|
+
const entries = tokenizeLinearGradient(src);
|
|
376
|
+
if (!entries || entries.length === 0) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const out: Array<[number, number, number]> = [];
|
|
381
|
+
for (const [rgb, offset] of entries) {
|
|
382
|
+
if (offset !== null) {
|
|
383
|
+
return null;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
out.push(rgb);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return out;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function formatPercent(offset: number): string {
|
|
393
|
+
const rounded = Math.round(Math.max(0, Math.min(1, offset)) * 1000) / 10;
|
|
394
|
+
return `${rounded}%`;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* The viewer's canonical gradient string for `stops`: `linear-gradient(to
|
|
399
|
+
* right, #rrggbb P%, …)`, sorted, positions at 0.1% resolution.
|
|
400
|
+
*/
|
|
401
|
+
export function stopsToCss(stops: GradientStop[]): string {
|
|
402
|
+
const body = [...stops]
|
|
403
|
+
.sort((a, b) => a.offset - b.offset)
|
|
404
|
+
.map(
|
|
405
|
+
(stop) =>
|
|
406
|
+
`${vec3ToHexColor([stop.color[0], stop.color[1], stop.color[2]])} ${formatPercent(stop.offset)}`,
|
|
407
|
+
)
|
|
408
|
+
.join(", ");
|
|
409
|
+
|
|
410
|
+
return `linear-gradient(to right, ${body})`;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* The viewer's canonical palette string for `colors`: the same frame
|
|
415
|
+
* with bare `#rrggbb` entries and no positions.
|
|
416
|
+
*/
|
|
417
|
+
export function colorsToCss(colors: Array<[number, number, number]>): string {
|
|
418
|
+
return `linear-gradient(to right, ${colors.map(vec3ToHexColor).join(", ")})`;
|
|
419
|
+
}
|
|
420
|
+
|
|
181
421
|
/**
|
|
182
422
|
* Piecewise-linear color sample at `t ∈ [0, 1]`. Returns RGBA in [0, 1].
|
|
183
423
|
* Clamps `t` to the gradient's first/last stop outside `[0, 1]`.
|
|
@@ -257,6 +497,30 @@ export function colorValueToT(
|
|
|
257
497
|
return t < 0 ? 0 : t > 1 ? 1 : t;
|
|
258
498
|
}
|
|
259
499
|
|
|
500
|
+
export function colorRangePivot(
|
|
501
|
+
colorMin: number,
|
|
502
|
+
colorMax: number,
|
|
503
|
+
): [number, number] {
|
|
504
|
+
if (!isFinite(colorMin) || !isFinite(colorMax) || colorMin >= colorMax) {
|
|
505
|
+
return [0, 0];
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
let denom: number;
|
|
509
|
+
if (colorMin >= 0) {
|
|
510
|
+
denom = colorMax;
|
|
511
|
+
} else if (colorMax <= 0) {
|
|
512
|
+
denom = -colorMin;
|
|
513
|
+
} else {
|
|
514
|
+
denom = Math.max(-colorMin, colorMax);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (denom <= 0) {
|
|
518
|
+
return [0, 0];
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return [-denom, denom];
|
|
522
|
+
}
|
|
523
|
+
|
|
260
524
|
/**
|
|
261
525
|
* Convert a discrete series palette (from `--psp-charts--series-N--color`)
|
|
262
526
|
* into a `GradientStop[]` with stops at `i / (N - 1)`. The resulting
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
parseCssColorList,
|
|
15
|
+
parseCssGradientStrict,
|
|
16
|
+
type GradientStop,
|
|
17
|
+
} from "./gradient";
|
|
18
|
+
import type { Theme } from "./theme";
|
|
19
|
+
import type { Vec3 } from "./palette";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Per-column color-scale overrides from the viewer's `columns_config`,
|
|
23
|
+
* patched into the resolved `Theme`'s `seriesPalette` / `gradientStops`
|
|
24
|
+
* fields.
|
|
25
|
+
*/
|
|
26
|
+
export function applyColumnColorOverrides(
|
|
27
|
+
theme: Theme,
|
|
28
|
+
columnsConfig: Record<string, any> | undefined,
|
|
29
|
+
colorColumn: string | null,
|
|
30
|
+
): Theme {
|
|
31
|
+
if (!colorColumn) {
|
|
32
|
+
return theme;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const cfg = columnsConfig?.[colorColumn];
|
|
36
|
+
if (!cfg) {
|
|
37
|
+
return theme;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let out = theme;
|
|
41
|
+
const gradient = parseGradientOverride(cfg.gradient);
|
|
42
|
+
if (gradient) {
|
|
43
|
+
out = { ...out, gradientStops: gradient };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const palette = parsePaletteOverride(cfg.palette);
|
|
47
|
+
if (palette) {
|
|
48
|
+
out = { ...out, seriesPalette: palette };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Parse a stored `palette` value into the `seriesPalette` shape, or
|
|
56
|
+
* `null` on anything malformed.
|
|
57
|
+
*/
|
|
58
|
+
export function parsePaletteOverride(raw: unknown): Vec3[] | null {
|
|
59
|
+
if (typeof raw !== "string") {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return parseCssColorList(raw);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Parse a stored `gradient` value into sorted `GradientStop[]`, or
|
|
68
|
+
* `null` on malformed input or fewer than 2 stops.
|
|
69
|
+
*/
|
|
70
|
+
export function parseGradientOverride(raw: unknown): GradientStop[] | null {
|
|
71
|
+
if (typeof raw !== "string") {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return parseCssGradientStrict(raw);
|
|
76
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import type { FacetConfig, PluginConfig } from "../charts/chart";
|
|
14
14
|
import type { PerspectiveClickDetail } from "../event-detail";
|
|
15
15
|
import type { ThemeSnapshot } from "../theme/theme";
|
|
16
|
+
import type { TileSourceSpec } from "../map/tile-source";
|
|
16
17
|
import type { ViewConfig } from "@perspective-dev/client";
|
|
17
18
|
|
|
18
19
|
export type { ThemeSnapshot };
|
|
@@ -53,6 +54,7 @@ export type WorkerMsg =
|
|
|
53
54
|
| SetCursorMsg
|
|
54
55
|
| UserClickMsg
|
|
55
56
|
| UserSelectMsg
|
|
57
|
+
| PluginConfigDeltaMsg
|
|
56
58
|
| LoadAndRenderAckMsg
|
|
57
59
|
| ResizeAckMsg
|
|
58
60
|
| FrameBitmapMsg
|
|
@@ -100,8 +102,20 @@ export interface InitMsg {
|
|
|
100
102
|
* `dpr` and there is no host-side GL drawing buffer.
|
|
101
103
|
*/
|
|
102
104
|
glCanvas?: OffscreenCanvas;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The gridlines (bottom) and chrome/axes (top) 2D layers.
|
|
108
|
+
* Transferred via `transferControlToOffscreen` on the host, present
|
|
109
|
+
* iff `renderMode === "direct"` — their pixels reach the screen
|
|
110
|
+
* through the compositor. In blit mode the renderer allocates
|
|
111
|
+
* worker-local surfaces instead and composites both layers into the
|
|
112
|
+
* shipped `FrameBitmapMsg`, so every layer of the frame rides the
|
|
113
|
+
* host's staged-present hold (a transferred canvas presents on the
|
|
114
|
+
* compositor's own schedule, unsynchronized with the host's layout
|
|
115
|
+
* commits — the one-frame axes warp on anticipated resizes).
|
|
116
|
+
*/
|
|
117
|
+
gridlinesCanvas?: OffscreenCanvas;
|
|
118
|
+
chromeCanvas?: OffscreenCanvas;
|
|
105
119
|
|
|
106
120
|
/**
|
|
107
121
|
* `MessagePort` to the host's `ProxySession`. Worker mode only —
|
|
@@ -173,6 +187,17 @@ export interface InitMsg {
|
|
|
173
187
|
columnsConfig?: Record<string, any>;
|
|
174
188
|
defaultChartType?: string;
|
|
175
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Resolved spec for `pluginConfig.map_tile_provider`, when the
|
|
192
|
+
* plugin realm's registry knows the id — see
|
|
193
|
+
* {@link SetPluginConfigMsg.tileSource} for the invariant. Applied
|
|
194
|
+
* to the worker realm's registry before the chart impl is
|
|
195
|
+
* constructed. Bundled [map/tile-sources.json] entries ship inside
|
|
196
|
+
* the worker bundle, so this matters only for runtime-registered
|
|
197
|
+
* providers.
|
|
198
|
+
*/
|
|
199
|
+
tileSource?: TileSourceSpec;
|
|
200
|
+
|
|
176
201
|
/**
|
|
177
202
|
* Pre-resolved CSS-variable theme snapshot from the host.
|
|
178
203
|
*/
|
|
@@ -259,6 +284,18 @@ export interface SetColumnsConfigMsg {
|
|
|
259
284
|
export interface SetPluginConfigMsg {
|
|
260
285
|
kind: "setPluginConfig";
|
|
261
286
|
cfg: PluginConfig;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Resolved spec for `cfg.map_tile_provider`, when the plugin
|
|
290
|
+
* realm's registry knows the id. Riding the config keeps the two
|
|
291
|
+
* realms' registries convergent with NO eager mirroring: the
|
|
292
|
+
* worker registers this spec before applying `cfg`, so it can
|
|
293
|
+
* never hold a config whose provider it cannot resolve —
|
|
294
|
+
* regardless of when `registerTileSource` ran relative to
|
|
295
|
+
* renderer construction. Absent for unknown ids (the worker falls
|
|
296
|
+
* back to the default basemap).
|
|
297
|
+
*/
|
|
298
|
+
tileSource?: TileSourceSpec;
|
|
262
299
|
}
|
|
263
300
|
|
|
264
301
|
export interface SetBufferMaxCapacityMsg {
|
|
@@ -305,6 +342,7 @@ export interface LoadAndRenderMsg {
|
|
|
305
342
|
export interface LoadAndRenderAckMsg {
|
|
306
343
|
kind: "loadAndRenderAck";
|
|
307
344
|
msgId: number;
|
|
345
|
+
error?: string;
|
|
308
346
|
}
|
|
309
347
|
|
|
310
348
|
export interface RedrawMsg {
|
|
@@ -405,7 +443,8 @@ export interface SnapshotPngReqMsg {
|
|
|
405
443
|
export interface SnapshotPngReplyMsg {
|
|
406
444
|
kind: "snapshotPngReply";
|
|
407
445
|
requestId: number;
|
|
408
|
-
blob
|
|
446
|
+
blob?: Blob;
|
|
447
|
+
error?: string;
|
|
409
448
|
}
|
|
410
449
|
|
|
411
450
|
export interface DestroyMsg {
|
|
@@ -469,7 +508,9 @@ export interface ErrorMsg {
|
|
|
469
508
|
*/
|
|
470
509
|
export interface PinTooltipMsg {
|
|
471
510
|
kind: "pinTooltip";
|
|
472
|
-
|
|
511
|
+
|
|
512
|
+
grid: string[][];
|
|
513
|
+
style: { opacity: number; maxColumnPx: number };
|
|
473
514
|
pos: { px: number; py: number };
|
|
474
515
|
bounds: { cssWidth: number; cssHeight: number };
|
|
475
516
|
}
|
|
@@ -489,6 +530,21 @@ export interface SetCursorMsg {
|
|
|
489
530
|
cursor: string;
|
|
490
531
|
}
|
|
491
532
|
|
|
533
|
+
/**
|
|
534
|
+
* Renderer → host: a completed legend gesture (sidebar width drag,
|
|
535
|
+
* floating move / resize) produced new values for the legend's
|
|
536
|
+
* `plugin_config` fields. Posted ONCE per gesture, at pointerup — never
|
|
537
|
+
* per pointermove — with only the fields the gesture changed. The host
|
|
538
|
+
* plugin persists them through the viewer's public `restore` surface
|
|
539
|
+
* (a user-gesture echo), which merges the host bucket, refreshes the
|
|
540
|
+
* settings form, and echoes one `setPluginConfig` back with the same
|
|
541
|
+
* values (a no-op by the worker's legend-field equality guard).
|
|
542
|
+
*/
|
|
543
|
+
export interface PluginConfigDeltaMsg {
|
|
544
|
+
kind: "pluginConfigDelta";
|
|
545
|
+
fields: Record<string, string | number | boolean>;
|
|
546
|
+
}
|
|
547
|
+
|
|
492
548
|
/**
|
|
493
549
|
* Renderer → host: a user click landed on a chart glyph. Host
|
|
494
550
|
* re-dispatches as `CustomEvent<PerspectiveClickDetail>` on the
|