@perspective-dev/viewer-charts 5.2.0 → 5.3.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/cdn/perspective-viewer-charts.js +2 -2
- package/dist/cdn/perspective-viewer-charts.js.map +4 -4
- package/dist/esm/charts/candlestick/candlestick-render.d.ts +3 -0
- package/dist/esm/charts/cartesian/cartesian.d.ts +1 -0
- package/dist/esm/charts/chart-base.d.ts +22 -9
- package/dist/esm/charts/chart.d.ts +4 -0
- package/dist/esm/charts/common/category-axis-resolver.d.ts +1 -1
- package/dist/esm/charts/common/tree-chart.d.ts +1 -0
- package/dist/esm/charts/heatmap/heatmap.d.ts +1 -0
- package/dist/esm/charts/series/series-type.d.ts +18 -0
- package/dist/esm/charts/series/series.d.ts +4 -1
- package/dist/esm/charts/sunburst/sunburst-render.d.ts +4 -0
- package/dist/esm/perspective-viewer-charts.js +2 -2
- package/dist/esm/perspective-viewer-charts.js.map +4 -4
- package/dist/esm/plugin/plugin.d.ts +8 -1
- package/dist/esm/theme/gradient.d.ts +31 -0
- package/dist/esm/theme/overrides.d.ts +19 -0
- package/dist/esm/transport/protocol.d.ts +13 -2
- package/dist/esm/utils/css.d.ts +4 -0
- package/dist/esm/worker/renderer.worker.d.ts +27 -0
- package/package.json +1 -1
- package/src/ts/charts/candlestick/candlestick-build.ts +1 -0
- package/src/ts/charts/candlestick/candlestick-render.ts +8 -0
- package/src/ts/charts/candlestick/candlestick.ts +4 -3
- package/src/ts/charts/cartesian/cartesian-render.ts +5 -0
- package/src/ts/charts/cartesian/cartesian.ts +4 -0
- package/src/ts/charts/chart-base.ts +42 -10
- package/src/ts/charts/chart.ts +5 -0
- package/src/ts/charts/common/category-axis-resolver.ts +44 -5
- package/src/ts/charts/common/tree-chart.ts +4 -0
- package/src/ts/charts/heatmap/heatmap-build.ts +7 -1
- package/src/ts/charts/heatmap/heatmap-interact.ts +10 -1
- package/src/ts/charts/heatmap/heatmap-render.ts +5 -0
- package/src/ts/charts/heatmap/heatmap.ts +8 -3
- package/src/ts/charts/series/series-build.ts +1 -0
- package/src/ts/charts/series/series-interact.ts +2 -13
- package/src/ts/charts/series/series-render.ts +5 -0
- package/src/ts/charts/series/series-type.ts +65 -0
- package/src/ts/charts/series/series.ts +21 -8
- package/src/ts/charts/sunburst/sunburst-render.ts +9 -0
- package/src/ts/charts/treemap/treemap-render.ts +5 -0
- package/src/ts/layout/ticks.ts +36 -12
- package/src/ts/plugin/plugin.ts +95 -10
- package/src/ts/theme/gradient.ts +241 -1
- package/src/ts/theme/overrides.ts +76 -0
- package/src/ts/transport/protocol.ts +14 -2
- package/src/ts/transport/renderer-transport.ts +24 -10
- package/src/ts/utils/css.ts +12 -0
- package/src/ts/worker/renderer.worker.ts +113 -5
|
@@ -87,8 +87,10 @@ export declare class HTMLPerspectiveViewerWebGLPluginElement extends HTMLElement
|
|
|
87
87
|
*/
|
|
88
88
|
static setBlitMode(mode: "direct" | "blit"): void;
|
|
89
89
|
get_static_config(): PluginStaticConfig;
|
|
90
|
-
column_config_schema(column_type: string,
|
|
90
|
+
column_config_schema(column_type: string, group: string | undefined, column_name: string, current_value: Record<string, unknown> | null, viewer_config?: {
|
|
91
|
+
columns?: (string | null)[];
|
|
91
92
|
group_by?: string[];
|
|
93
|
+
split_by?: string[];
|
|
92
94
|
group_rollup_mode?: string;
|
|
93
95
|
}): {
|
|
94
96
|
fields: (Record<string, unknown> & {
|
|
@@ -103,6 +105,11 @@ export declare class HTMLPerspectiveViewerWebGLPluginElement extends HTMLElement
|
|
|
103
105
|
kind: string;
|
|
104
106
|
})[];
|
|
105
107
|
};
|
|
108
|
+
private _resolvedTheme;
|
|
109
|
+
private static readonly GRADIENT_PALETTE_FALLBACK_COUNT;
|
|
110
|
+
private _themeSeriesPaletteHex;
|
|
111
|
+
private _themeSeriesColorHex;
|
|
112
|
+
private _themeGradientStopsSpec;
|
|
106
113
|
draw(view: View): Promise<void>;
|
|
107
114
|
update(view: View): Promise<void>;
|
|
108
115
|
/**
|
|
@@ -15,6 +15,37 @@ export interface GradientStop {
|
|
|
15
15
|
* that never set the gradient still produce sane output.
|
|
16
16
|
*/
|
|
17
17
|
export declare function parseCssGradient(src: string | null | undefined): GradientStop[];
|
|
18
|
+
/**
|
|
19
|
+
* A color literal — `#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`, `rgb()` or
|
|
20
|
+
* `rgba()` — as a `[0, 1]` RGB triple, or `null` for anything else.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseCssColorStrict(src: string): [number, number, number] | null;
|
|
23
|
+
/**
|
|
24
|
+
* The shared `linear-gradient(...)` tokenizer of the viewer's CSS-valued
|
|
25
|
+
* config grammar.
|
|
26
|
+
*/
|
|
27
|
+
export declare function tokenizeLinearGradient(src: string): Array<[[number, number, number], number | null]> | null;
|
|
28
|
+
/**
|
|
29
|
+
* Strict gradient reader: a `linear-gradient(...)` with ≥ 2 stops,
|
|
30
|
+
* positions optional, offsets clamped to `[0, 1]` and sorted, or `null`
|
|
31
|
+
* on malformed input.
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseCssGradientStrict(src: string): GradientStop[] | null;
|
|
34
|
+
/**
|
|
35
|
+
* Strict palette reader: a `linear-gradient(...)` of ≥ 1 colors with no
|
|
36
|
+
* positions, or `null` on malformed input.
|
|
37
|
+
*/
|
|
38
|
+
export declare function parseCssColorList(src: string): Array<[number, number, number]> | null;
|
|
39
|
+
/**
|
|
40
|
+
* The viewer's canonical gradient string for `stops`: `linear-gradient(to
|
|
41
|
+
* right, #rrggbb P%, …)`, sorted, positions at 0.1% resolution.
|
|
42
|
+
*/
|
|
43
|
+
export declare function stopsToCss(stops: GradientStop[]): string;
|
|
44
|
+
/**
|
|
45
|
+
* The viewer's canonical palette string for `colors`: the same frame
|
|
46
|
+
* with bare `#rrggbb` entries and no positions.
|
|
47
|
+
*/
|
|
48
|
+
export declare function colorsToCss(colors: Array<[number, number, number]>): string;
|
|
18
49
|
/**
|
|
19
50
|
* Piecewise-linear color sample at `t ∈ [0, 1]`. Returns RGBA in [0, 1].
|
|
20
51
|
* Clamps `t` to the gradient's first/last stop outside `[0, 1]`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type GradientStop } from "./gradient";
|
|
2
|
+
import type { Theme } from "./theme";
|
|
3
|
+
import type { Vec3 } from "./palette";
|
|
4
|
+
/**
|
|
5
|
+
* Per-column color-scale overrides from the viewer's `columns_config`,
|
|
6
|
+
* patched into the resolved `Theme`'s `seriesPalette` / `gradientStops`
|
|
7
|
+
* fields.
|
|
8
|
+
*/
|
|
9
|
+
export declare function applyColumnColorOverrides(theme: Theme, columnsConfig: Record<string, any> | undefined, colorColumn: string | null): Theme;
|
|
10
|
+
/**
|
|
11
|
+
* Parse a stored `palette` value into the `seriesPalette` shape, or
|
|
12
|
+
* `null` on anything malformed.
|
|
13
|
+
*/
|
|
14
|
+
export declare function parsePaletteOverride(raw: unknown): Vec3[] | null;
|
|
15
|
+
/**
|
|
16
|
+
* Parse a stored `gradient` value into sorted `GradientStop[]`, or
|
|
17
|
+
* `null` on malformed input or fewer than 2 stops.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseGradientOverride(raw: unknown): GradientStop[] | null;
|
|
@@ -49,8 +49,19 @@ export interface InitMsg {
|
|
|
49
49
|
* `dpr` and there is no host-side GL drawing buffer.
|
|
50
50
|
*/
|
|
51
51
|
glCanvas?: OffscreenCanvas;
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
/**
|
|
53
|
+
* The gridlines (bottom) and chrome/axes (top) 2D layers.
|
|
54
|
+
* Transferred via `transferControlToOffscreen` on the host, present
|
|
55
|
+
* iff `renderMode === "direct"` — their pixels reach the screen
|
|
56
|
+
* through the compositor. In blit mode the renderer allocates
|
|
57
|
+
* worker-local surfaces instead and composites both layers into the
|
|
58
|
+
* shipped `FrameBitmapMsg`, so every layer of the frame rides the
|
|
59
|
+
* host's staged-present hold (a transferred canvas presents on the
|
|
60
|
+
* compositor's own schedule, unsynchronized with the host's layout
|
|
61
|
+
* commits — the one-frame axes warp on anticipated resizes).
|
|
62
|
+
*/
|
|
63
|
+
gridlinesCanvas?: OffscreenCanvas;
|
|
64
|
+
chromeCanvas?: OffscreenCanvas;
|
|
54
65
|
/**
|
|
55
66
|
* `MessagePort` to the host's `ProxySession`. Worker mode only —
|
|
56
67
|
* the worker bootstraps a fresh `Client` and bridges it through
|
package/dist/esm/utils/css.d.ts
CHANGED
|
@@ -13,6 +13,17 @@ export declare class WorkerRenderer {
|
|
|
13
13
|
cssWidth: number;
|
|
14
14
|
cssHeight: number;
|
|
15
15
|
dpr: number;
|
|
16
|
+
/**
|
|
17
|
+
* Blit-mode compose surface: gridlines + GL frame + chrome are
|
|
18
|
+
* drawn here each `endFrame` and shipped as ONE `ImageBitmap`, so
|
|
19
|
+
* every layer rides the host's staged-present hold (see
|
|
20
|
+
* `InitMsg.gridlinesCanvas`). `null` in direct mode.
|
|
21
|
+
*/
|
|
22
|
+
private _composeCanvas;
|
|
23
|
+
private _composeCtx;
|
|
24
|
+
private _lastPlot;
|
|
25
|
+
private _frameSerial;
|
|
26
|
+
private _overlayPresentQueued;
|
|
16
27
|
client: Client;
|
|
17
28
|
view: View;
|
|
18
29
|
/**
|
|
@@ -104,6 +115,22 @@ export declare class WorkerRenderer {
|
|
|
104
115
|
* mouse-event branches fall through.
|
|
105
116
|
*/
|
|
106
117
|
private _tooltip;
|
|
118
|
+
/**
|
|
119
|
+
* Blit-mode frame composite: draw the gridlines (bottom), the GL
|
|
120
|
+
* plot bitmap (middle) and the chrome/axes (top) into the compose
|
|
121
|
+
* surface and transfer the result — the single `ImageBitmap` the
|
|
122
|
+
* host blits, and the unit the staged-present hold stages. Runs in
|
|
123
|
+
* `endFrame`, which the scheduler orders after `render2D`'s 2D
|
|
124
|
+
* flush, so both layers hold this frame's content.
|
|
125
|
+
*
|
|
126
|
+
* A layer whose buffer differs from the plot's dimensions is
|
|
127
|
+
* skipped rather than scaled: its content is a stale-sized frame
|
|
128
|
+
* mid-resize (the 2D draws re-size their canvas to `css × dpr` on
|
|
129
|
+
* the next flush), and scaling it would re-introduce exactly the
|
|
130
|
+
* warp this composite exists to prevent.
|
|
131
|
+
*/
|
|
132
|
+
private _composeFrame;
|
|
133
|
+
private _presentOverlay;
|
|
107
134
|
/**
|
|
108
135
|
* Composite the three layers into a single PNG `Blob`.
|
|
109
136
|
*/
|
package/package.json
CHANGED
|
@@ -230,7 +230,15 @@ export function renderCandlestickFrame(
|
|
|
230
230
|
chart._defer2D(() => renderCandlestickChromeOverlay(chart));
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Draw axes chrome + (optional) tooltip onto the overlay canvas.
|
|
235
|
+
*/
|
|
233
236
|
export function renderCandlestickChromeOverlay(chart: CandlestickChart): void {
|
|
237
|
+
paintCandlestickChromeOverlay(chart);
|
|
238
|
+
chart.presentOverlay();
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function paintCandlestickChromeOverlay(chart: CandlestickChart): void {
|
|
234
242
|
if (
|
|
235
243
|
!chart._chromeCanvas ||
|
|
236
244
|
!chart._lastLayout ||
|
|
@@ -212,9 +212,10 @@ export class CandlestickChart extends CategoricalYChart {
|
|
|
212
212
|
|
|
213
213
|
const catIdx = this._candles.catIdx[idx];
|
|
214
214
|
const splitIdx = this._candles.splitIdx[idx];
|
|
215
|
-
const groupByValues: (string | null)[] =
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
const groupByValues: (string | number | null)[] =
|
|
216
|
+
this._categoryAxisMode === "numeric" && this._categoryPositions
|
|
217
|
+
? [this._categoryPositions[catIdx] ?? null]
|
|
218
|
+
: this._rowPaths.map((level) => level.labels[catIdx] ?? null);
|
|
218
219
|
const splitKey = this._splitPrefixes[splitIdx] ?? "";
|
|
219
220
|
const splitByValues =
|
|
220
221
|
this._splitBy.length > 0 && splitKey !== ""
|
|
@@ -699,6 +699,11 @@ function renderFacetedFrame(
|
|
|
699
699
|
* Redraw the chrome canvas only. Used for lightweight hover updates.
|
|
700
700
|
*/
|
|
701
701
|
export function renderCartesianChromeOverlay(chart: CartesianChart): void {
|
|
702
|
+
paintCartesianChromeOverlay(chart);
|
|
703
|
+
chart.presentOverlay();
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function paintCartesianChromeOverlay(chart: CartesianChart): void {
|
|
702
707
|
if (
|
|
703
708
|
!chart._chromeCanvas ||
|
|
704
709
|
!chart._lastLayout ||
|
|
@@ -61,6 +61,10 @@ export class CartesianChart extends AbstractChart {
|
|
|
61
61
|
this.glyph = glyph;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
protected override colorScaleColumn(): string | null {
|
|
65
|
+
return this._colorName || null;
|
|
66
|
+
}
|
|
67
|
+
|
|
64
68
|
/**
|
|
65
69
|
* Rendering pipeline selector. `"cartesian"` is the default —
|
|
66
70
|
* draws axes, gridlines, and ticks via the chrome canvas.
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
import type { PerspectiveClickDetail } from "../event-detail";
|
|
45
45
|
import type { ViewConfig } from "@perspective-dev/client";
|
|
46
46
|
import { resolveThemeFromVars, type Theme } from "../theme/theme";
|
|
47
|
+
import { applyColumnColorOverrides } from "../theme/overrides";
|
|
47
48
|
import { requestRender as scheduleRender } from "../render/scheduler";
|
|
48
49
|
|
|
49
50
|
// TODO I don't know if this is the behavior we want. On the plus side, this
|
|
@@ -141,6 +142,7 @@ export abstract class AbstractChart implements ChartImplementation {
|
|
|
141
142
|
_glManager: WebGLContextManager | null = null;
|
|
142
143
|
_gridlineCanvas: HTMLCanvasElement | OffscreenCanvas | null = null;
|
|
143
144
|
_chromeCanvas: HTMLCanvasElement | OffscreenCanvas | null = null;
|
|
145
|
+
_overlayPresenter: (() => void) | null = null;
|
|
144
146
|
|
|
145
147
|
/**
|
|
146
148
|
* 2D-canvas draw closures collected during `_fullRender` (the GL
|
|
@@ -259,12 +261,16 @@ export abstract class AbstractChart implements ChartImplementation {
|
|
|
259
261
|
|
|
260
262
|
/**
|
|
261
263
|
* Cached resolved theme — populated on first `_resolveTheme()` call,
|
|
262
|
-
* cleared by `invalidateTheme()` (driven from `plugin.restyle()`)
|
|
263
|
-
*
|
|
264
|
-
*
|
|
264
|
+
* cleared by `invalidateTheme()` (driven from `plugin.restyle()`)
|
|
265
|
+
* and by `setColumnsConfig` (per-column color overrides patch the
|
|
266
|
+
* resolved theme). `getComputedStyle` / `getPropertyValue` reads
|
|
267
|
+
* cost ~100µs each; zoom/hover dispatch redraws at 60Hz so we
|
|
268
|
+
* resolve once and reuse.
|
|
265
269
|
*/
|
|
266
270
|
_theme: Theme | null = null;
|
|
267
271
|
|
|
272
|
+
_themeColorColumn: string | null = null;
|
|
273
|
+
|
|
268
274
|
/**
|
|
269
275
|
* On-demand single-row fetcher used by lazy tooltip column
|
|
270
276
|
* lookups. Reset on every `setView` call; subclasses read
|
|
@@ -287,6 +293,18 @@ export abstract class AbstractChart implements ChartImplementation {
|
|
|
287
293
|
this._chromeCanvas = canvas;
|
|
288
294
|
}
|
|
289
295
|
|
|
296
|
+
setOverlayPresenter(cb: () => void): void {
|
|
297
|
+
this._overlayPresenter = cb;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Present a chrome-overlay repaint that ran outside the scheduler's
|
|
302
|
+
* frame chain.
|
|
303
|
+
*/
|
|
304
|
+
presentOverlay(): void {
|
|
305
|
+
this._overlayPresenter?.();
|
|
306
|
+
}
|
|
307
|
+
|
|
290
308
|
setTheme(vars: Record<string, string>): void {
|
|
291
309
|
this._themeVars = vars;
|
|
292
310
|
this._theme = null;
|
|
@@ -434,6 +452,8 @@ export abstract class AbstractChart implements ChartImplementation {
|
|
|
434
452
|
setColumnsConfig(cfg: Record<string, any>): void {
|
|
435
453
|
this._columnsConfig = cfg ?? {};
|
|
436
454
|
this._rebuildColumnFormatters();
|
|
455
|
+
|
|
456
|
+
this._theme = null;
|
|
437
457
|
}
|
|
438
458
|
|
|
439
459
|
/**
|
|
@@ -599,16 +619,28 @@ export abstract class AbstractChart implements ChartImplementation {
|
|
|
599
619
|
cfg.series_zoom_mode === "dynamic";
|
|
600
620
|
}
|
|
601
621
|
|
|
622
|
+
protected colorScaleColumn(): string | null {
|
|
623
|
+
return null;
|
|
624
|
+
}
|
|
625
|
+
|
|
602
626
|
/**
|
|
603
|
-
* Lazily decode the host-supplied theme vars
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
627
|
+
* Lazily decode the host-supplied theme vars, then patch in any
|
|
628
|
+
* per-column color-scale override for `colorScaleColumn()`.
|
|
629
|
+
* Subsequent calls hit the cache until `invalidateTheme()` /
|
|
630
|
+
* `setColumnsConfig` clears it or the color column changes.
|
|
631
|
+
* Render-path callers should always read theme values through this
|
|
632
|
+
* method so the parsed `Theme` (gradient stops, palette, etc.)
|
|
633
|
+
* amortizes across an entire frame.
|
|
608
634
|
*/
|
|
609
635
|
_resolveTheme(): Theme {
|
|
610
|
-
|
|
611
|
-
|
|
636
|
+
const colorColumn = this.colorScaleColumn();
|
|
637
|
+
if (!this._theme || this._themeColorColumn !== colorColumn) {
|
|
638
|
+
this._themeColorColumn = colorColumn;
|
|
639
|
+
this._theme = applyColumnColorOverrides(
|
|
640
|
+
resolveThemeFromVars(this._themeVars),
|
|
641
|
+
this._columnsConfig,
|
|
642
|
+
colorColumn,
|
|
643
|
+
);
|
|
612
644
|
}
|
|
613
645
|
|
|
614
646
|
return this._theme;
|
package/src/ts/charts/chart.ts
CHANGED
|
@@ -84,6 +84,11 @@ export interface ChartImplementation {
|
|
|
84
84
|
*/
|
|
85
85
|
setChromeCanvas?(canvas: HTMLCanvasElement | OffscreenCanvas): void;
|
|
86
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Install the renderer's overlay-present hook.
|
|
89
|
+
*/
|
|
90
|
+
setOverlayPresenter?(cb: () => void): void;
|
|
91
|
+
|
|
87
92
|
/**
|
|
88
93
|
* Hand the chart a pre-computed CSS-variable map produced on the
|
|
89
94
|
* main thread via `snapshotThemeVars(el)`, which it can decode into
|
|
@@ -203,15 +203,27 @@ export function synthesizeStringLevel(
|
|
|
203
203
|
const seen = new Map<string, number>();
|
|
204
204
|
seen.set("", 0);
|
|
205
205
|
|
|
206
|
+
const valueIdx = new Map<number, number>();
|
|
207
|
+
|
|
206
208
|
for (let r = 0; r < numRows; r++) {
|
|
207
209
|
const isValid = valid ? !!((valid[r >> 3] >> (r & 7)) & 1) : true;
|
|
210
|
+
if (!isValid) {
|
|
211
|
+
indices[r] = 0;
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
|
|
208
215
|
const v = values[r] as number;
|
|
209
|
-
|
|
210
|
-
let dictIdx = seen.get(label);
|
|
216
|
+
let dictIdx = valueIdx.get(v);
|
|
211
217
|
if (dictIdx === undefined) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
218
|
+
const label = formatLevelValue(v, true, levelType);
|
|
219
|
+
dictIdx = seen.get(label);
|
|
220
|
+
if (dictIdx === undefined) {
|
|
221
|
+
dictIdx = dictionary.length;
|
|
222
|
+
dictionary.push(label);
|
|
223
|
+
seen.set(label, dictIdx);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
valueIdx.set(v, dictIdx);
|
|
215
227
|
}
|
|
216
228
|
|
|
217
229
|
indices[r] = dictIdx;
|
|
@@ -240,7 +252,34 @@ export function resolveCategoryAxis(
|
|
|
240
252
|
numRows: number,
|
|
241
253
|
groupByLen: number,
|
|
242
254
|
levelTypes: string[] = [],
|
|
255
|
+
axisMode?: AxisMode,
|
|
243
256
|
): CategoryAxisResult {
|
|
257
|
+
if (axisMode?.mode === "numeric") {
|
|
258
|
+
const rp = columns.get("__ROW_PATH_0__");
|
|
259
|
+
if (rp && rp.type !== "string" && rp.values) {
|
|
260
|
+
let rowOffset = 0;
|
|
261
|
+
const values = rp.values;
|
|
262
|
+
const valid = rp.valid;
|
|
263
|
+
while (rowOffset < numRows) {
|
|
264
|
+
const isValid = valid
|
|
265
|
+
? !!((valid[rowOffset >> 3] >> (rowOffset & 7)) & 1)
|
|
266
|
+
: true;
|
|
267
|
+
const v = values[rowOffset] as number;
|
|
268
|
+
if (isValid && v != null && !Number.isNaN(v)) {
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
rowOffset++;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return {
|
|
276
|
+
rowPaths: [],
|
|
277
|
+
numCategories: Math.max(0, numRows - rowOffset),
|
|
278
|
+
rowOffset,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
244
283
|
type RawLevel = { indices: Int32Array; dictionary: string[] };
|
|
245
284
|
const rawRowPaths: RawLevel[] = [];
|
|
246
285
|
for (let n = 0; ; n++) {
|
|
@@ -40,6 +40,10 @@ export function firstNonMetadataColumn(columns: ColumnDataMap): string {
|
|
|
40
40
|
* layout modules can read/write them without friction.
|
|
41
41
|
*/
|
|
42
42
|
export abstract class TreeChartBase extends AbstractChart {
|
|
43
|
+
protected override colorScaleColumn(): string | null {
|
|
44
|
+
return this._columnSlots[1] || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
43
47
|
// Shared column-slot resolution
|
|
44
48
|
_sizeName = "";
|
|
45
49
|
_colorName = "";
|
|
@@ -128,7 +128,13 @@ export function buildHeatmapPipeline(
|
|
|
128
128
|
rowPaths: xLevels,
|
|
129
129
|
numCategories: numX,
|
|
130
130
|
rowOffset,
|
|
131
|
-
} = resolveCategoryAxis(
|
|
131
|
+
} = resolveCategoryAxis(
|
|
132
|
+
columns,
|
|
133
|
+
numRows,
|
|
134
|
+
groupBy.length,
|
|
135
|
+
levelTypes,
|
|
136
|
+
xAxisMode,
|
|
137
|
+
);
|
|
132
138
|
|
|
133
139
|
// Numeric X domain: sourced from `__ROW_PATH_0__`'s raw values when
|
|
134
140
|
// the single group_by is non-string.
|
|
@@ -215,6 +215,7 @@ export function renderHeatmapTooltip(chart: HeatmapChart): void {
|
|
|
215
215
|
let layout: import("../../layout/plot-layout").PlotLayout | null;
|
|
216
216
|
let xLevels: CategoricalLevel[];
|
|
217
217
|
let yLevels: CategoricalLevel[];
|
|
218
|
+
let xPositions: Float64Array | null;
|
|
218
219
|
let facetLabel: string | null = null;
|
|
219
220
|
|
|
220
221
|
if (chart._hoveredFacetIdx >= 0) {
|
|
@@ -226,6 +227,7 @@ export function renderHeatmapTooltip(chart: HeatmapChart): void {
|
|
|
226
227
|
layout = facet.layout;
|
|
227
228
|
xLevels = facet.pipeline.xLevels;
|
|
228
229
|
yLevels = facet.pipeline.yLevels;
|
|
230
|
+
xPositions = facet.pipeline.xPositions;
|
|
229
231
|
facetLabel = facet.label;
|
|
230
232
|
} else {
|
|
231
233
|
if (!chart._lastLayout) {
|
|
@@ -235,6 +237,7 @@ export function renderHeatmapTooltip(chart: HeatmapChart): void {
|
|
|
235
237
|
layout = chart._lastLayout;
|
|
236
238
|
xLevels = chart._xLevels;
|
|
237
239
|
yLevels = chart._yLevels;
|
|
240
|
+
xPositions = chart._xPositions;
|
|
238
241
|
}
|
|
239
242
|
|
|
240
243
|
const cell = chart._hoveredCell;
|
|
@@ -249,7 +252,13 @@ export function renderHeatmapTooltip(chart: HeatmapChart): void {
|
|
|
249
252
|
lines.push(facetLabel);
|
|
250
253
|
}
|
|
251
254
|
|
|
252
|
-
const xPath =
|
|
255
|
+
const xPath =
|
|
256
|
+
chart._xAxisMode.mode === "numeric" && xPositions
|
|
257
|
+
? chart.getColumnFormatter(
|
|
258
|
+
chart._groupBy[0],
|
|
259
|
+
"value",
|
|
260
|
+
)(xPositions[cell.xIdx])
|
|
261
|
+
: formatHierarchicalPath(xLevels, cell.xIdx);
|
|
253
262
|
const yPath = formatHierarchicalPath(yLevels, cell.yIdx);
|
|
254
263
|
if (xPath) {
|
|
255
264
|
lines.push(xPath);
|
|
@@ -392,6 +392,11 @@ function drawCellsInstanced(
|
|
|
392
392
|
* Chrome overlay: X axis + Y axis + color legend + (optional) tooltip.
|
|
393
393
|
*/
|
|
394
394
|
export function renderHeatmapChromeOverlay(chart: HeatmapChart): void {
|
|
395
|
+
paintHeatmapChromeOverlay(chart);
|
|
396
|
+
chart.presentOverlay();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function paintHeatmapChromeOverlay(chart: HeatmapChart): void {
|
|
395
400
|
if (!chart._chromeCanvas) {
|
|
396
401
|
return;
|
|
397
402
|
}
|
|
@@ -69,6 +69,10 @@ export interface HeatmapFacet {
|
|
|
69
69
|
* scale and a single legend.
|
|
70
70
|
*/
|
|
71
71
|
export class HeatmapChart extends AbstractChart {
|
|
72
|
+
protected override colorScaleColumn(): string | null {
|
|
73
|
+
return this._columnSlots[0] || null;
|
|
74
|
+
}
|
|
75
|
+
|
|
72
76
|
_program: WebGLProgram | null = null;
|
|
73
77
|
_locations: HeatmapLocations | null = null;
|
|
74
78
|
_cornerBuffer: WebGLBuffer | null = null;
|
|
@@ -176,9 +180,10 @@ export class HeatmapChart extends AbstractChart {
|
|
|
176
180
|
xIdx: number,
|
|
177
181
|
yIdx: number,
|
|
178
182
|
): Promise<void> {
|
|
179
|
-
const groupByValues: (string | null)[] =
|
|
180
|
-
|
|
181
|
-
|
|
183
|
+
const groupByValues: (string | number | null)[] =
|
|
184
|
+
this._xAxisMode.mode === "numeric" && this._xPositions
|
|
185
|
+
? [this._xPositions[xIdx] ?? null]
|
|
186
|
+
: this._xLevels.map((level) => level.labels[xIdx] ?? null);
|
|
182
187
|
const splitByValues: (string | null)[] = this._yLevels
|
|
183
188
|
.slice(0, this._splitBy.length)
|
|
184
189
|
.map((level) => level.labels[yIdx] ?? null);
|
|
@@ -628,19 +628,8 @@ export function formatBarCategoryPath(
|
|
|
628
628
|
chart: SeriesChart,
|
|
629
629
|
catIdx: number,
|
|
630
630
|
): string {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
// the single group_by level is non-string.
|
|
634
|
-
if (chart._categoryAxisMode === "numeric" && chart._numericCategoryDomain) {
|
|
635
|
-
const bars = chart._bars;
|
|
636
|
-
let v: number | null = null;
|
|
637
|
-
for (let i = 0; i < bars.count; i++) {
|
|
638
|
-
if (bars.catIdx[i] === catIdx) {
|
|
639
|
-
v = bars.xCenter[i];
|
|
640
|
-
break;
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
|
|
631
|
+
if (chart._categoryAxisMode === "numeric" && chart._categoryPositions) {
|
|
632
|
+
const v = chart._categoryPositions[catIdx];
|
|
644
633
|
if (v == null) {
|
|
645
634
|
return "";
|
|
646
635
|
}
|
|
@@ -1088,6 +1088,11 @@ export function layoutForRecord(
|
|
|
1088
1088
|
* Draw axes chrome + legend + tooltip onto the overlay canvas.
|
|
1089
1089
|
*/
|
|
1090
1090
|
export function renderBarChromeOverlay(chart: SeriesChart): void {
|
|
1091
|
+
paintBarChromeOverlay(chart);
|
|
1092
|
+
chart.presentOverlay();
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
function paintBarChromeOverlay(chart: SeriesChart): void {
|
|
1091
1096
|
if (
|
|
1092
1097
|
!chart._chromeCanvas ||
|
|
1093
1098
|
!chart._lastLayout ||
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
+
import { parseCssColorList, parseCssColorStrict } from "../../theme/gradient";
|
|
14
|
+
|
|
13
15
|
export type ChartType = "bar" | "line" | "scatter" | "area";
|
|
14
16
|
|
|
15
17
|
/**
|
|
@@ -45,6 +47,18 @@ export interface ColumnChartConfig {
|
|
|
45
47
|
* `"skip"`). Default `"skip"`. No effect on bar / scatter.
|
|
46
48
|
*/
|
|
47
49
|
interpolate?: InterpolateMode;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Single series-color override (`#rrggbb`) — the no-`split_by`
|
|
53
|
+
* shape.
|
|
54
|
+
*/
|
|
55
|
+
color?: string;
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Discrete color palette cycled over this column's split-generated
|
|
59
|
+
* series — the `split_by` shape.
|
|
60
|
+
*/
|
|
61
|
+
palette?: string;
|
|
48
62
|
}
|
|
49
63
|
|
|
50
64
|
/**
|
|
@@ -98,6 +112,57 @@ export function resolveAltAxis(
|
|
|
98
112
|
return cfg?.[aggName]?.alt_axis === true;
|
|
99
113
|
}
|
|
100
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Resolve a per-series color override from `columns_config`, or `null`
|
|
117
|
+
* when the theme palette applies.
|
|
118
|
+
*/
|
|
119
|
+
export function resolveSeriesColorOverride(
|
|
120
|
+
series: { aggName: string; splitIdx: number },
|
|
121
|
+
facetActive: boolean,
|
|
122
|
+
cfg: Record<string, ColumnChartConfig> | undefined,
|
|
123
|
+
): [number, number, number] | null {
|
|
124
|
+
const entry = cfg?.[series.aggName];
|
|
125
|
+
if (!entry) {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const palette =
|
|
130
|
+
typeof entry.palette === "string"
|
|
131
|
+
? parsePaletteCached(entry.palette)
|
|
132
|
+
: null;
|
|
133
|
+
if (palette && palette.length > 0) {
|
|
134
|
+
const idx = facetActive ? 0 : series.splitIdx % palette.length;
|
|
135
|
+
return palette[idx];
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (typeof entry.color === "string") {
|
|
139
|
+
return parseCssColorStrict(entry.color) ?? null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const PALETTE_PARSE_CACHE = new Map<
|
|
146
|
+
string,
|
|
147
|
+
Array<[number, number, number]> | null
|
|
148
|
+
>();
|
|
149
|
+
|
|
150
|
+
function parsePaletteCached(
|
|
151
|
+
src: string,
|
|
152
|
+
): Array<[number, number, number]> | null {
|
|
153
|
+
let parsed = PALETTE_PARSE_CACHE.get(src);
|
|
154
|
+
if (parsed === undefined) {
|
|
155
|
+
parsed = parseCssColorList(src);
|
|
156
|
+
if (PALETTE_PARSE_CACHE.size > 256) {
|
|
157
|
+
PALETTE_PARSE_CACHE.clear();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
PALETTE_PARSE_CACHE.set(src, parsed);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return parsed;
|
|
164
|
+
}
|
|
165
|
+
|
|
101
166
|
/**
|
|
102
167
|
* Resolve the interpolation mode for this aggregate.
|
|
103
168
|
*/
|