@perspective-dev/viewer-charts 5.5.0 → 5.6.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.
@@ -1,4 +1,4 @@
1
- import type { DateFormatConfig, NumberFormatConfig } from "@perspective-dev/viewer/src/ts/column-format.js";
1
+ import type { DateFormatConfig, NumberFormatConfig } from "@perspective-dev/viewer/column-format";
2
2
  /**
3
3
  * The chart plugin's default per-column formats, declared to the viewer in
4
4
  * `column_config_schema()` and applied by the render path under sparse or
@@ -112,7 +112,7 @@ export declare class HTMLPerspectiveViewerWebGLPluginElement extends HTMLElement
112
112
  group_by?: string[];
113
113
  split_by?: string[];
114
114
  group_rollup_mode?: string;
115
- }): {
115
+ }, _plugin_config?: Record<string, unknown> | null, role?: "column" | "group_by" | "split_by"): {
116
116
  fields: (Record<string, unknown> & {
117
117
  kind: string;
118
118
  })[];
@@ -141,17 +141,17 @@ export declare class WebGLContextManager {
141
141
  */
142
142
  setFrameCallback(cb: ((bitmap: ImageBitmap) => void) | null): void;
143
143
  /**
144
- * Ship the current canvas contents as an `ImageBitmap` to the host
145
- * when a frame callback is registered and the surface is an
144
+ * Ship a copy of the current canvas contents as an `ImageBitmap` to
145
+ * the host when a frame callback is registered and the surface is an
146
146
  * `OffscreenCanvas`. Otherwise no-op (direct mode paints straight to
147
147
  * the visible drawing buffer).
148
148
  */
149
- endFrame(): void;
149
+ endFrame(): Promise<void>;
150
150
  /**
151
151
  * Resolve when every GL command submitted up to this call has been
152
- * executed by the GPU. WebGL2 issues a `fenceSync` and polls
153
- * `clientWaitSync` (yielding to the task queue between polls); WebGL1
154
- * falls back to a blocking `gl.finish()` (acceptable in a worker).
152
+ * executed by the GPU, polling a WebGL2 fence with a flush on every
153
+ * poll and falling back to a blocking `gl.finish()` on WebGL1 or on
154
+ * `WAIT_FAILED`.
155
155
  */
156
156
  awaitGpuFence(): Promise<void>;
157
157
  ensureBufferCapacity(totalRows: number): void;
package/package.json CHANGED
@@ -1,7 +1,32 @@
1
1
  {
2
2
  "name": "@perspective-dev/viewer-charts",
3
- "version": "5.5.0",
4
- "description": "Perspective.js WebGL Plugin",
3
+ "version": "5.6.0",
4
+ "description": "WebGL charts for `<perspective-viewer>`: bar, line, scatter, heatmap, treemap, sunburst, candlestick and map plugins for large, streaming datasets.",
5
+ "keywords": [
6
+ "perspective",
7
+ "datagrid",
8
+ "data-grid",
9
+ "pivot-table",
10
+ "pivot",
11
+ "charts",
12
+ "visualization",
13
+ "dashboard",
14
+ "analytics",
15
+ "bi",
16
+ "streaming",
17
+ "real-time",
18
+ "wasm",
19
+ "webassembly",
20
+ "arrow",
21
+ "webgl",
22
+ "chart",
23
+ "heatmap",
24
+ "treemap",
25
+ "candlestick",
26
+ "map",
27
+ "web-component"
28
+ ],
29
+ "homepage": "https://perspective-dev.github.io",
5
30
  "unpkg": "./dist/cdn/perspective-viewer-charts.js",
6
31
  "jsdelivr": "./dist/cdn/perspective-viewer-charts.js",
7
32
  "type": "module",
@@ -31,14 +56,14 @@
31
56
  "author": "",
32
57
  "license": "Apache-2.0",
33
58
  "devDependencies": {
59
+ "@perspective-dev/client": "^5.6.0",
60
+ "@perspective-dev/viewer": "^5.6.0",
61
+ "@perspective-dev/esbuild-plugin": "^5.6.0",
62
+ "@perspective-dev/test": "^5.6.0",
34
63
  "@types/node": ">=22",
35
64
  "lightningcss": "^1.29.0",
36
65
  "typescript": ">=5 <6",
37
- "webpack-glsl-minify": "1.5.0",
38
- "@perspective-dev/client": "^5.5.0",
39
- "@perspective-dev/test": "^5.5.0",
40
- "@perspective-dev/esbuild-plugin": "^5.5.0",
41
- "@perspective-dev/viewer": "^5.5.0"
66
+ "webpack-glsl-minify": "1.5.0"
42
67
  },
43
68
  "scripts": {
44
69
  "build": "node ./build.mjs",
@@ -20,7 +20,7 @@ import {
20
20
  } from "../../../webgl/instanced-attrs";
21
21
  import { compileProgram } from "../../../webgl/program-cache";
22
22
  import { colorRangePivot } from "../../../theme/gradient";
23
- import { formatTickValue, formatDateTickValue } from "../../../layout/ticks";
23
+ import { buildPointRowTooltipLines } from "../tooltip-lines";
24
24
  import lineVert from "../../../shaders/line.vert.glsl";
25
25
  import lineFrag from "../../../shaders/line.frag.glsl";
26
26
 
@@ -122,41 +122,11 @@ export class LineGlyph implements Glyph {
122
122
 
123
123
  // helpers
124
124
 
125
- async buildTooltipLines(
125
+ buildTooltipLines(
126
126
  chart: CartesianChart,
127
127
  flatIdx: number,
128
128
  ): Promise<string[][]> {
129
- const grid: string[][] = [];
130
- if (!chart._xData || !chart._yData) {
131
- return grid;
132
- }
133
-
134
- if (chart._splitGroups.length > 0 && chart._seriesCapacity > 0) {
135
- const seriesIdx = Math.floor(flatIdx / chart._seriesCapacity);
136
- const sg = chart._splitGroups[seriesIdx];
137
- if (sg) {
138
- grid.push([sg.prefix]);
139
- }
140
- }
141
-
142
- const xVal = chart._xData[flatIdx];
143
- const yVal = chart._yData[flatIdx];
144
-
145
- const xType = chart._columnTypes[chart._xLabel] || "";
146
- const xIsDate = xType === "date" || xType === "datetime";
147
- const xFormatted = xIsDate
148
- ? formatDateTickValue(xVal)
149
- : formatTickValue(xVal);
150
- grid.push([chart._xLabel || "Row", xFormatted]);
151
-
152
- const yType = chart._columnTypes[chart._yLabel] || "";
153
- const yIsDate = yType === "date" || yType === "datetime";
154
- const yFormatted = yIsDate
155
- ? formatDateTickValue(yVal)
156
- : formatTickValue(yVal);
157
- grid.push([chart._yLabel, yFormatted]);
158
-
159
- return grid;
129
+ return buildPointRowTooltipLines(chart, flatIdx);
160
130
  }
161
131
 
162
132
  tooltipOptions() {
@@ -18,7 +18,7 @@ import {
18
18
  sourceColumn,
19
19
  type NumberFormatConfig,
20
20
  type DateFormatConfig,
21
- } from "@perspective-dev/viewer/src/ts/column-format.js";
21
+ } from "@perspective-dev/viewer/column-format";
22
22
  import type { ColumnDataMap } from "../data/view-reader";
23
23
  import { LazyRowFetcher } from "../data/lazy-row";
24
24
  import {
@@ -49,4 +49,4 @@ export interface PerspectiveClickDetail {
49
49
  panel?: string;
50
50
  }
51
51
 
52
- export { PerspectiveSelectDetail } from "@perspective-dev/viewer/src/ts/extensions.js";
52
+ export { PerspectiveSelectDetail } from "@perspective-dev/viewer/select-detail";
@@ -13,7 +13,7 @@
13
13
  import type {
14
14
  DateFormatConfig,
15
15
  NumberFormatConfig,
16
- } from "@perspective-dev/viewer/src/ts/column-format.js";
16
+ } from "@perspective-dev/viewer/column-format";
17
17
 
18
18
  /**
19
19
  * The chart plugin's default per-column formats, declared to the viewer in
@@ -255,6 +255,24 @@ const GLOBAL_STYLES = (() => {
255
255
  */
256
256
  let BLIT_MODE: "direct" | "blit" = RENDER_BLIT_MODE;
257
257
 
258
+ /**
259
+ * A column's `date_format` / `number_format` controls, which are the only ones
260
+ * that apply to a value rendered as an axis tick or tooltip label.
261
+ */
262
+ function format_fields(
263
+ column_type: string,
264
+ ): Array<Record<string, unknown> & { kind: string }> {
265
+ if (column_type === "integer" || column_type === "float") {
266
+ return [{ kind: "NumberFormat", default: CHART_NUMBER_DEFAULTS }];
267
+ } else if (column_type === "datetime") {
268
+ return [{ kind: "DatetimeFormat", default: CHART_DATETIME_DEFAULTS }];
269
+ } else if (column_type === "date") {
270
+ return [{ kind: "DatetimeFormat", default: CHART_DATE_DEFAULTS }];
271
+ }
272
+
273
+ return [];
274
+ }
275
+
258
276
  export class HTMLPerspectiveViewerWebGLPluginElement
259
277
  extends HTMLElement
260
278
  implements IPerspectiveViewerPlugin
@@ -601,9 +619,15 @@ export class HTMLPerspectiveViewerWebGLPluginElement
601
619
  split_by?: string[];
602
620
  group_rollup_mode?: string;
603
621
  },
622
+ _plugin_config?: Record<string, unknown> | null,
623
+ role?: "column" | "group_by" | "split_by",
604
624
  ) {
605
625
  const fields: Array<Record<string, unknown> & { kind: string }> = [];
606
626
 
627
+ if (role === "group_by" || role === "split_by") {
628
+ return { fields: format_fields(column_type) };
629
+ }
630
+
607
631
  if (group === "Color") {
608
632
  const numeric_gradient =
609
633
  this._chartType.category === "Hierarchical Charts"
@@ -721,23 +745,7 @@ export class HTMLPerspectiveViewerWebGLPluginElement
721
745
 
722
746
  // Per-column formatter widgets. Surfaced for every chart type so
723
747
  // axes / tooltips / legends honor the user's format choice.
724
- if (column_type === "integer" || column_type === "float") {
725
- fields.push({
726
- kind: "NumberFormat",
727
- default: CHART_NUMBER_DEFAULTS,
728
- });
729
- } else if (column_type === "datetime") {
730
- fields.push({
731
- kind: "DatetimeFormat",
732
- default: CHART_DATETIME_DEFAULTS,
733
- });
734
- } else if (column_type === "date") {
735
- fields.push({
736
- kind: "DatetimeFormat",
737
- default: CHART_DATE_DEFAULTS,
738
- });
739
- }
740
-
748
+ fields.push(...format_fields(column_type));
741
749
  return { fields };
742
750
  }
743
751
 
@@ -380,18 +380,16 @@ async function present(entry: Entry): Promise<void> {
380
380
  // on resize. Failures fall through to the same present-failed
381
381
  // path as the fence.
382
382
  entry.render2D();
383
- entry.glManager.endFrame();
383
+ await entry.glManager.endFrame();
384
384
  for (const w of entry.waiters) {
385
385
  w.resolve();
386
386
  }
387
387
  } catch (err) {
388
388
  console.error("scheduler: present failed", err);
389
- // Still call `endFrame` so the canvas state is consistent —
390
- // `transferToImageBitmap` clears the offscreen, and skipping
391
- // it would leave a stale image bound to a context the host
392
- // already considers presented.
389
+ // Still ship a frame so the host does not hold a stale image for
390
+ // a context it already considers presented.
393
391
  try {
394
- entry.glManager.endFrame();
392
+ await entry.glManager.endFrame();
395
393
  } catch {
396
394
  // Swallow: already in a failure path.
397
395
  }
@@ -156,8 +156,8 @@ export class WebGLContextManager {
156
156
  this._cssHeight = cssHeight;
157
157
  this._dpr = dpr;
158
158
 
159
- const width = Math.round(cssWidth * dpr);
160
- const height = Math.round(cssHeight * dpr);
159
+ const width = Math.max(1, Math.round(cssWidth * dpr));
160
+ const height = Math.max(1, Math.round(cssHeight * dpr));
161
161
  const canvas = this._ctx.canvas;
162
162
  if (canvas.width !== width || canvas.height !== height) {
163
163
  canvas.width = width;
@@ -284,19 +284,19 @@ export class WebGLContextManager {
284
284
  }
285
285
 
286
286
  /**
287
- * Ship the current canvas contents as an `ImageBitmap` to the host
288
- * when a frame callback is registered and the surface is an
287
+ * Ship a copy of the current canvas contents as an `ImageBitmap` to
288
+ * the host when a frame callback is registered and the surface is an
289
289
  * `OffscreenCanvas`. Otherwise no-op (direct mode paints straight to
290
290
  * the visible drawing buffer).
291
291
  */
292
- endFrame(): void {
292
+ async endFrame(): Promise<void> {
293
293
  if (!this._frameCallback) {
294
294
  return;
295
295
  }
296
296
 
297
297
  // A context lost mid-frame (our own `destroy`, or a browser
298
- // eviction between paint and present) makes
299
- // `transferToImageBitmap` throw. Bail quietly.
298
+ // eviction between paint and present) makes the bitmap copy
299
+ // throw. Bail quietly.
300
300
  if (this.isContextLost()) {
301
301
  return;
302
302
  }
@@ -311,15 +311,15 @@ export class WebGLContextManager {
311
311
  return;
312
312
  }
313
313
 
314
- const bitmap = (canvas as OffscreenCanvas).transferToImageBitmap();
314
+ const bitmap = await createImageBitmap(canvas as OffscreenCanvas);
315
315
  this._frameCallback(bitmap);
316
316
  }
317
317
 
318
318
  /**
319
319
  * Resolve when every GL command submitted up to this call has been
320
- * executed by the GPU. WebGL2 issues a `fenceSync` and polls
321
- * `clientWaitSync` (yielding to the task queue between polls); WebGL1
322
- * falls back to a blocking `gl.finish()` (acceptable in a worker).
320
+ * executed by the GPU, polling a WebGL2 fence with a flush on every
321
+ * poll and falling back to a blocking `gl.finish()` on WebGL1 or on
322
+ * `WAIT_FAILED`.
323
323
  */
324
324
  async awaitGpuFence(): Promise<void> {
325
325
  if (!this._ctx.isWebGL2) {
@@ -335,9 +335,12 @@ export class WebGLContextManager {
335
335
  }
336
336
 
337
337
  try {
338
- let flags: GLbitfield = gl.SYNC_FLUSH_COMMANDS_BIT;
339
338
  while (true) {
340
- const status = gl.clientWaitSync(fence, flags, 0);
339
+ const status = gl.clientWaitSync(
340
+ fence,
341
+ gl.SYNC_FLUSH_COMMANDS_BIT,
342
+ 0,
343
+ );
341
344
  if (
342
345
  status === gl.ALREADY_SIGNALED ||
343
346
  status === gl.CONDITION_SATISFIED
@@ -350,7 +353,6 @@ export class WebGLContextManager {
350
353
  return;
351
354
  }
352
355
 
353
- flags = 0;
354
356
  await this._yieldToTask();
355
357
 
356
358
  if (this._destroyed) {