@perspective-dev/react 5.0.0 → 5.2.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/README.md +148 -0
- package/dist/esm/index.d.ts +51 -2
- package/dist/esm/index.js.map +2 -2
- package/dist/esm/utils.d.ts +4 -0
- package/dist/esm/viewer.d.ts +90 -2
- package/package.json +1 -1
- package/src/index.tsx +51 -2
- package/src/utils.tsx +4 -0
- package/src/viewer.tsx +101 -2
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# `@perspective-dev/react`
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@perspective-dev/react)
|
|
4
|
+
|
|
5
|
+
React bindings for [Perspective](https://perspective-dev.github.io/), an
|
|
6
|
+
interactive analytics and data visualization component for large, real-time
|
|
7
|
+
and streaming datasets. This package wraps the
|
|
8
|
+
[`<perspective-viewer>`](https://perspective-dev.github.io/viewer/modules/perspective-viewer.html)
|
|
9
|
+
Custom Element in an idiomatic, declarative React component,
|
|
10
|
+
`<PerspectiveViewer>`, which manages the element's imperative
|
|
11
|
+
`load()`/`restore()`/`delete()` lifecycle for you.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @perspective-dev/react
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`@perspective-dev/client` and `@perspective-dev/viewer` are installed as
|
|
20
|
+
dependencies, but you'll also want at least one plugin package for the
|
|
21
|
+
visualizations themselves:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @perspective-dev/viewer-datagrid @perspective-dev/viewer-charts
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
Perspective's engine and UI are WebAssembly binaries which must be initialized
|
|
30
|
+
once, before the first `<PerspectiveViewer>` renders. Plugins register
|
|
31
|
+
themselves via import side effects. See the
|
|
32
|
+
[User Guide's bundling section](https://perspective-dev.github.io/guide/how_to/javascript/importing.html)
|
|
33
|
+
for bundler configuration details.
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import perspective from "@perspective-dev/client";
|
|
37
|
+
import perspective_viewer from "@perspective-dev/viewer";
|
|
38
|
+
import "@perspective-dev/viewer-datagrid";
|
|
39
|
+
import "@perspective-dev/viewer-charts";
|
|
40
|
+
import "@perspective-dev/viewer/dist/css/themes.css";
|
|
41
|
+
|
|
42
|
+
import SERVER_WASM from "@perspective-dev/server/dist/wasm/perspective-server.wasm";
|
|
43
|
+
import CLIENT_WASM from "@perspective-dev/viewer/dist/wasm/perspective-viewer.wasm";
|
|
44
|
+
|
|
45
|
+
await Promise.all([
|
|
46
|
+
perspective.init_server(fetch(SERVER_WASM)),
|
|
47
|
+
perspective_viewer.init_client(fetch(CLIENT_WASM)),
|
|
48
|
+
]);
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
Create a `Table` (here in a Web Worker `Client`) and pass it — or a `Promise`
|
|
54
|
+
of it — to `<PerspectiveViewer>`:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import * as React from "react";
|
|
58
|
+
import { PerspectiveViewer } from "@perspective-dev/react";
|
|
59
|
+
|
|
60
|
+
const WORKER = await perspective.worker();
|
|
61
|
+
|
|
62
|
+
const TABLE = WORKER.table(
|
|
63
|
+
fetch("superstore.lz4.arrow").then((resp) => resp.arrayBuffer()),
|
|
64
|
+
{ name: "superstore" },
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const App: React.FC = () => (
|
|
68
|
+
<PerspectiveViewer
|
|
69
|
+
client={TABLE}
|
|
70
|
+
config={{ group_by: ["State"], plugin: "Y Bar" }}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Props
|
|
76
|
+
|
|
77
|
+
| Prop | Type | Description |
|
|
78
|
+
| :--------------- | :------------------------------------------------------------ | :-------------------------------------------------------------- |
|
|
79
|
+
| `client` | `Client \| Table \| Promise<Client> \| Promise<Table>` | Data source. When `undefined`, the viewer `eject()`s. |
|
|
80
|
+
| `config` | `ViewerConfigUpdate \| WorkspaceConfigUpdate` | Declarative viewer state, applied via `restore()`. |
|
|
81
|
+
| `onConfigUpdate` | `(config: ViewerConfigUpdate) => void` | Called when the user reconfigures the viewer through its UI. |
|
|
82
|
+
| `onClick` | `(detail: PerspectiveClickEventDetail) => void` | Called when the user clicks a datapoint. |
|
|
83
|
+
| `onSelect` | `(detail: PerspectiveSelectEventDetail) => void` | Called when the user selects (or deselects) a datapoint or row. |
|
|
84
|
+
|
|
85
|
+
A subset of standard HTML attributes — `className`, `id`, `style`, `hidden`,
|
|
86
|
+
`slot`, `tabIndex` and `title` — is forwarded to the underlying element.
|
|
87
|
+
|
|
88
|
+
### `client`
|
|
89
|
+
|
|
90
|
+
The viewer's data source, forwarded to
|
|
91
|
+
[`viewer.load()`](https://perspective-dev.github.io/viewer/modules/perspective-viewer.html)
|
|
92
|
+
whenever it changes:
|
|
93
|
+
|
|
94
|
+
- A `Table` (or `Promise<Table>`) displays that table directly.
|
|
95
|
+
- A `Client` (e.g. from `perspective.worker()` or a WebSocket connection to a
|
|
96
|
+
remote server) connects the viewer to every table hosted by that client;
|
|
97
|
+
the table each panel displays is chosen by `config` or interactively by
|
|
98
|
+
the user.
|
|
99
|
+
- `undefined` ejects the viewer, returning it to an unloaded state without
|
|
100
|
+
unmounting it.
|
|
101
|
+
|
|
102
|
+
The component does not take ownership of the `Table` — delete it yourself
|
|
103
|
+
when it is no longer needed (e.g. `table.delete({ lazy: true })`).
|
|
104
|
+
|
|
105
|
+
### `config`
|
|
106
|
+
|
|
107
|
+
Declarative viewer state — group-bys, splits, filters, sorts, expressions,
|
|
108
|
+
plugin and plugin config — applied with `restore()` whenever it (or `client`)
|
|
109
|
+
changes. A config with a `panels` property is treated as a multi-panel
|
|
110
|
+
workspace layout and applied with `restoreWorkspace()` instead. Configs are
|
|
111
|
+
compared structurally, so passing a fresh-but-equal object literal on each
|
|
112
|
+
render does not re-apply.
|
|
113
|
+
|
|
114
|
+
Combine `config` with `onConfigUpdate` to make the viewer a controlled
|
|
115
|
+
component — store the user's latest configuration in state (or persist it) and
|
|
116
|
+
pass it back down:
|
|
117
|
+
|
|
118
|
+
```tsx
|
|
119
|
+
const App: React.FC = () => {
|
|
120
|
+
const [config, setConfig] = React.useState<pspViewer.ViewerConfigUpdate>({
|
|
121
|
+
group_by: ["Category"],
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<PerspectiveViewer
|
|
126
|
+
client={TABLE}
|
|
127
|
+
config={config}
|
|
128
|
+
onConfigUpdate={setConfig}
|
|
129
|
+
/>
|
|
130
|
+
);
|
|
131
|
+
};
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Lifecycle
|
|
135
|
+
|
|
136
|
+
On unmount, the component calls the element's `delete()` method, freeing the
|
|
137
|
+
viewer's WebAssembly resources. Tables and clients are created outside the
|
|
138
|
+
component and are yours to manage; a `Table` passed as `client` survives
|
|
139
|
+
unmount and can be shown again by a later mount.
|
|
140
|
+
|
|
141
|
+
## See also
|
|
142
|
+
|
|
143
|
+
- [`react-example`](https://github.com/perspective-dev/perspective/tree/master/examples/react-example)
|
|
144
|
+
— a complete bundler-configured project using this package, including a
|
|
145
|
+
multi-panel workspace config.
|
|
146
|
+
- [Perspective User Guide](https://perspective-dev.github.io/guide/)
|
|
147
|
+
- [`<perspective-viewer>` API documentation](https://perspective-dev.github.io/viewer/modules/perspective-viewer.html)
|
|
148
|
+
- [`@perspective-dev/client` API documentation](https://perspective-dev.github.io/browser/modules/src_ts_perspective.browser.ts.html)
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,9 +1,58 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* React bindings for [Perspective](https://perspective-dev.github.io/).
|
|
3
|
+
*
|
|
4
|
+
* This module exports {@link PerspectiveViewer}, a declarative React wrapper
|
|
5
|
+
* for the `<perspective-viewer>` Custom Element. The component manages the
|
|
6
|
+
* element's imperative lifecycle — `load()` and `restore()` in response to
|
|
7
|
+
* prop changes, `delete()` on unmount — and exposes the element's Custom
|
|
8
|
+
* Events as React-style callback props.
|
|
9
|
+
*
|
|
10
|
+
* Perspective's WebAssembly engine and UI must be initialized (and at least
|
|
11
|
+
* one plugin package imported) before the first `<PerspectiveViewer>`
|
|
12
|
+
* renders:
|
|
13
|
+
*
|
|
14
|
+
* ```tsx
|
|
15
|
+
* import perspective from "@perspective-dev/client";
|
|
16
|
+
* import perspective_viewer from "@perspective-dev/viewer";
|
|
17
|
+
* import "@perspective-dev/viewer-datagrid";
|
|
18
|
+
* import "@perspective-dev/viewer-charts";
|
|
19
|
+
*
|
|
20
|
+
* import SERVER_WASM from "@perspective-dev/server/dist/wasm/perspective-server.wasm";
|
|
21
|
+
* import CLIENT_WASM from "@perspective-dev/viewer/dist/wasm/perspective-viewer.wasm";
|
|
22
|
+
*
|
|
23
|
+
* await Promise.all([
|
|
24
|
+
* perspective.init_server(fetch(SERVER_WASM)),
|
|
25
|
+
* perspective_viewer.init_client(fetch(CLIENT_WASM)),
|
|
26
|
+
* ]);
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* Then pass a `Table` (or `Client`, or a `Promise` of either) and an optional
|
|
30
|
+
* config:
|
|
31
|
+
*
|
|
32
|
+
* ```tsx
|
|
33
|
+
* import { PerspectiveViewer } from "@perspective-dev/react";
|
|
34
|
+
*
|
|
35
|
+
* const WORKER = await perspective.worker();
|
|
36
|
+
* const TABLE = WORKER.table(
|
|
37
|
+
* fetch("superstore.lz4.arrow").then((resp) => resp.arrayBuffer()),
|
|
38
|
+
* { name: "superstore" },
|
|
39
|
+
* );
|
|
40
|
+
*
|
|
41
|
+
* const App: React.FC = () => (
|
|
42
|
+
* <PerspectiveViewer
|
|
43
|
+
* client={TABLE}
|
|
44
|
+
* config={{ group_by: ["State"], plugin: "Y Bar" }}
|
|
45
|
+
* />
|
|
46
|
+
* );
|
|
47
|
+
* ```
|
|
2
48
|
*
|
|
3
49
|
* # See Also
|
|
4
50
|
*
|
|
5
|
-
* [`react-example`](https://github.com/perspective-dev/perspective/tree/master/examples/react-example)
|
|
6
|
-
*
|
|
51
|
+
* - [`react-example`](https://github.com/perspective-dev/perspective/tree/master/examples/react-example)
|
|
52
|
+
* project from the Perspective GitHub repo, a complete bundler-configured
|
|
53
|
+
* application including a multi-panel workspace config.
|
|
54
|
+
* - [Perspective User Guide](https://perspective-dev.github.io/guide/)
|
|
55
|
+
* - [`<perspective-viewer>` API documentation](https://perspective-dev.github.io/viewer/modules/perspective-viewer.html)
|
|
7
56
|
*
|
|
8
57
|
* @module
|
|
9
58
|
*/
|
package/dist/esm/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/viewer.tsx", "../../src/utils.tsx"],
|
|
4
|
-
"sourcesContent": ["// \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n// \u2503 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2584 \u2580\u2588\u2588\u2588 \u2588 \u2503\n// \u2503 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2584\u2584\u2584\u2584\u2584 \u2584\u2584\u2584\u2584\u2584\u2588 \u2580\u2580\u2580\u2580\u2580\u2588\u2580\u2580\u2580\u2580\u2580 \u2588 \u2580\u2580\u2580\u2580\u2580\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2584 \u2580\u2588 \u2588 \u2580\u2580\u2580\u2580\u2580 \u2503\n// \u2503 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2588\u2588\u2580\u2580 \u2584\u2584\u2584\u2584\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584 \u2503\n// \u2503 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2580\u2588\u2584 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2503\n// \u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252B\n// \u2503 Copyright (c) 2017, the Perspective Authors. \u2503\n// \u2503 \u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C \u2503\n// \u2503 This file is part of the Perspective library, distributed under the terms \u2503\n// \u2503 of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). \u2503\n// \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251B\n\nimport * as React from \"react\";\nimport type * as psp from \"@perspective-dev/client\";\nimport type * as pspViewer from \"@perspective-dev/viewer\";\nimport { usePspListener } from \"./utils\";\n\nfunction PerspectiveViewerImpl(props: PerspectiveViewerProps) {\n const [viewer, setViewer] =\n React.useState<pspViewer.HTMLPerspectiveViewerElement | null>(null);\n\n React.useEffect(() => {\n return () => {\n viewer?.delete();\n };\n }, [viewer]);\n\n React.useEffect(() => {\n if (!viewer) {\n return;\n }\n\n let cancelled = false;\n (async () => {\n if (!props.client) {\n await viewer.eject();\n return;\n }\n\n await viewer.load(props.client);\n if (cancelled || !props.config) {\n return;\n }\n\n if (\"panels\" in props.config) {\n await viewer.restoreWorkspace(props.config);\n } else {\n await viewer.restore(props.config);\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [viewer, props.client, JSON.stringify(props.config)]);\n\n usePspListener(viewer, \"perspective-click\", props.onClick);\n usePspListener(viewer, \"perspective-select\", props.onSelect);\n usePspListener(\n viewer,\n \"perspective-config-update\",\n props.onConfigUpdate,\n (e) => e.detail.getConfig(),\n );\n\n return (\n <perspective-viewer\n ref={setViewer}\n id={props.id}\n className={props.className}\n hidden={props.hidden}\n slot={props.slot}\n style={props.style}\n tabIndex={props.tabIndex}\n title={props.title}\n />\n );\n}\n\n/**\n * Props for the `<PerspectiveViewer>` component.\n */\nexport interface PerspectiveViewerProps {\n client?: psp.Client | Promise<psp.Client> | psp.Table | Promise<psp.Table>;\n config?: pspViewer.ViewerConfigUpdate | pspViewer.WorkspaceConfigUpdate;\n onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;\n onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;\n onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;\n\n // Applicable props from `React.HTMLAttributes`, which we cannot extend\n // directly because Perspective changes the signature of `onClick`.\n className?: string | undefined;\n hidden?: boolean | undefined;\n id?: string | undefined;\n slot?: string | undefined;\n style?: React.CSSProperties | undefined;\n tabIndex?: number | undefined;\n title?: string | undefined;\n}\n\n/**\n * A React wrapper component for `<perspective-viewer>` Custom Element.\n */\nexport const PerspectiveViewer: React.FC<PerspectiveViewerProps> = React.memo(\n PerspectiveViewerImpl,\n);\n", "// \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n// \u2503 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2584 \u2580\u2588\u2588\u2588 \u2588 \u2503\n// \u2503 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2584\u2584\u2584\u2584\u2584 \u2584\u2584\u2584\u2584\u2584\u2588 \u2580\u2580\u2580\u2580\u2580\u2588\u2580\u2580\u2580\u2580\u2580 \u2588 \u2580\u2580\u2580\u2580\u2580\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2584 \u2580\u2588 \u2588 \u2580\u2580\u2580\u2580\u2580 \u2503\n// \u2503 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2588\u2588\u2580\u2580 \u2584\u2584\u2584\u2584\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584 \u2503\n// \u2503 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2580\u2588\u2584 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2503\n// \u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252B\n// \u2503 Copyright (c) 2017, the Perspective Authors. \u2503\n// \u2503 \u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C \u2503\n// \u2503 This file is part of the Perspective library, distributed under the terms \u2503\n// \u2503 of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). \u2503\n// \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251B\n\nimport * as React from \"react\";\n\nexport function usePspListener<A>(\n el: HTMLElement | undefined | null,\n event: string,\n cb?: (x: A) => void,\n map: (e: CustomEvent) => A = (e) => e.detail,\n) {\n React.useEffect(() => {\n if (!cb || !el) return;\n const ctx = new AbortController();\n const callback = (e: Event) => cb(map(e as CustomEvent));\n el?.addEventListener(event, callback, { signal: ctx.signal });\n return () => ctx.abort();\n }, [el, cb]);\n}\n"],
|
|
5
|
-
"mappings": "AAYA,UAAYA,MAAW,QCAvB,UAAYC,MAAW,
|
|
4
|
+
"sourcesContent": ["// \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n// \u2503 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2584 \u2580\u2588\u2588\u2588 \u2588 \u2503\n// \u2503 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2584\u2584\u2584\u2584\u2584 \u2584\u2584\u2584\u2584\u2584\u2588 \u2580\u2580\u2580\u2580\u2580\u2588\u2580\u2580\u2580\u2580\u2580 \u2588 \u2580\u2580\u2580\u2580\u2580\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2584 \u2580\u2588 \u2588 \u2580\u2580\u2580\u2580\u2580 \u2503\n// \u2503 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2588\u2588\u2580\u2580 \u2584\u2584\u2584\u2584\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584 \u2503\n// \u2503 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2580\u2588\u2584 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2503\n// \u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252B\n// \u2503 Copyright (c) 2017, the Perspective Authors. \u2503\n// \u2503 \u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C \u2503\n// \u2503 This file is part of the Perspective library, distributed under the terms \u2503\n// \u2503 of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). \u2503\n// \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251B\n\nimport * as React from \"react\";\nimport type * as psp from \"@perspective-dev/client\";\nimport type * as pspViewer from \"@perspective-dev/viewer\";\nimport { usePspListener } from \"./utils\";\n\nfunction PerspectiveViewerImpl(props: PerspectiveViewerProps) {\n const [viewer, setViewer] =\n React.useState<pspViewer.HTMLPerspectiveViewerElement | null>(null);\n\n React.useEffect(() => {\n return () => {\n viewer?.delete();\n };\n }, [viewer]);\n\n React.useEffect(() => {\n if (!viewer) {\n return;\n }\n\n let cancelled = false;\n (async () => {\n if (!props.client) {\n await viewer.eject();\n return;\n }\n\n await viewer.load(props.client);\n if (cancelled || !props.config) {\n return;\n }\n\n if (\"panels\" in props.config) {\n await viewer.restoreWorkspace(props.config);\n } else {\n await viewer.restore(props.config);\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [viewer, props.client, JSON.stringify(props.config)]);\n\n usePspListener(viewer, \"perspective-click\", props.onClick);\n usePspListener(viewer, \"perspective-select\", props.onSelect);\n usePspListener(\n viewer,\n \"perspective-config-update\",\n props.onConfigUpdate,\n (e) => e.detail.getConfig(),\n );\n\n return (\n <perspective-viewer\n ref={setViewer}\n id={props.id}\n className={props.className}\n hidden={props.hidden}\n slot={props.slot}\n style={props.style}\n tabIndex={props.tabIndex}\n title={props.title}\n />\n );\n}\n\n/**\n * Props for the {@link PerspectiveViewer} component.\n */\nexport interface PerspectiveViewerProps {\n /**\n * The viewer's data source, forwarded to the element's `load()` method\n * whenever it changes.\n *\n * - A `Table` (or `Promise<Table>`) displays that table directly.\n * - A `Client` (e.g. from `perspective.worker()` or a WebSocket\n * connection to a remote server) connects the viewer to every table\n * hosted by that client; the table each panel displays is chosen by\n * {@link PerspectiveViewerProps.config} or interactively by the user.\n * - `undefined` calls `eject()`, returning the viewer to an unloaded\n * state without unmounting it.\n *\n * The component does not take ownership of the `Table` \u2014 delete it\n * yourself when it is no longer needed, e.g.\n * `table.delete({ lazy: true })`.\n */\n client?: psp.Client | Promise<psp.Client> | psp.Table | Promise<psp.Table>;\n\n /**\n * Declarative viewer state \u2014 group-bys, splits, filters, sorts,\n * expressions, plugin and plugin config \u2014 applied with the element's\n * `restore()` method whenever it (or\n * {@link PerspectiveViewerProps.client}) changes. A config with a\n * `panels` property is treated as a multi-panel workspace layout and\n * applied with `restoreWorkspace()` instead.\n *\n * Configs are compared structurally, so passing a fresh-but-equal object\n * literal on each render does not re-apply. Combine with\n * {@link PerspectiveViewerProps.onConfigUpdate} to use the viewer as a\n * controlled component.\n */\n config?: pspViewer.ViewerConfigUpdate | pspViewer.WorkspaceConfigUpdate;\n\n /**\n * Called with the viewer's complete configuration whenever the user\n * reconfigures it through its UI (the element's\n * `\"perspective-config-update\"` Custom Event). Use this to persist the\n * user's view or reflect it back through\n * {@link PerspectiveViewerProps.config}.\n */\n onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;\n\n /**\n * Called when the user clicks a datapoint (the element's\n * `\"perspective-click\"` Custom Event), with the clicked row's data and a\n * filter config which would select it.\n */\n onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;\n\n /**\n * Called when the user selects or deselects a datapoint or row (the\n * element's `\"perspective-select\"` Custom Event).\n */\n onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;\n\n // Applicable props from `React.HTMLAttributes`, which we cannot extend\n // directly because Perspective changes the signature of `onClick`.\n\n /** Forwarded to the element's `class` attribute. */\n className?: string | undefined;\n\n /** Forwarded to the element's `hidden` attribute. */\n hidden?: boolean | undefined;\n\n /** Forwarded to the element's `id` attribute. */\n id?: string | undefined;\n\n /** Forwarded to the element's `slot` attribute. */\n slot?: string | undefined;\n\n /** Forwarded to the element's inline `style`. */\n style?: React.CSSProperties | undefined;\n\n /** Forwarded to the element's `tabindex` attribute. */\n tabIndex?: number | undefined;\n\n /** Forwarded to the element's `title` attribute. */\n title?: string | undefined;\n}\n\n/**\n * A declarative React wrapper for the `<perspective-viewer>` Custom Element.\n *\n * `<PerspectiveViewer>` manages the element's imperative lifecycle: it\n * `load()`s the {@link PerspectiveViewerProps.client}, `restore()`s the\n * {@link PerspectiveViewerProps.config} whenever either changes, subscribes\n * the `on*` callback props to the element's Custom Events, and `delete()`s\n * the viewer (freeing its WebAssembly resources) on unmount. Tables and\n * clients are created outside the component and are yours to manage \u2014 a\n * `Table` passed as `client` survives unmount and can be shown again by a\n * later mount.\n *\n * The component is memoized, so re-rendering a parent with unchanged props\n * does not touch the viewer.\n *\n * @example\n * ```tsx\n * const WORKER = await perspective.worker();\n * const TABLE = WORKER.table(\n * fetch(\"superstore.lz4.arrow\").then((resp) => resp.arrayBuffer()),\n * { name: \"superstore\" },\n * );\n *\n * const App: React.FC = () => {\n * const [config, setConfig] = React.useState<pspViewer.ViewerConfigUpdate>({\n * group_by: [\"State\"],\n * plugin: \"Y Bar\",\n * });\n *\n * return (\n * <PerspectiveViewer\n * client={TABLE}\n * config={config}\n * onConfigUpdate={setConfig}\n * />\n * );\n * };\n * ```\n */\nexport const PerspectiveViewer: React.FC<PerspectiveViewerProps> = React.memo(\n PerspectiveViewerImpl,\n);\n", "// \u250F\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2513\n// \u2503 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588 \u2588 \u2588 \u2588 \u2588\u2584 \u2580\u2588\u2588\u2588 \u2588 \u2503\n// \u2503 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2584\u2584\u2584\u2584\u2584 \u2584\u2584\u2584\u2584\u2584\u2588 \u2580\u2580\u2580\u2580\u2580\u2588\u2580\u2580\u2580\u2580\u2580 \u2588 \u2580\u2580\u2580\u2580\u2580\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2584 \u2580\u2588 \u2588 \u2580\u2580\u2580\u2580\u2580 \u2503\n// \u2503 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2580\u2580\u2580\u2580 \u2588\u2580\u2588\u2588\u2580\u2580 \u2584\u2584\u2584\u2584\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2584\u2584\u2584\u2584\u2584\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2584\u2584\u2584\u2584\u2584 \u2503\n// \u2503 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2580\u2588\u2584 \u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588 \u2588\u2588\u2588\u258C\u2590\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2584 \u2588 \u2503\n// \u2523\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u252B\n// \u2503 Copyright (c) 2017, the Perspective Authors. \u2503\n// \u2503 \u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C\u254C \u2503\n// \u2503 This file is part of the Perspective library, distributed under the terms \u2503\n// \u2503 of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). \u2503\n// \u2517\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u251B\n\nimport * as React from \"react\";\n\n/**\n * Subscribe `cb` to a Custom Event on `el` for the lifetime of the component,\n * projecting the event through `map` (by default, `e.detail`).\n */\nexport function usePspListener<A>(\n el: HTMLElement | undefined | null,\n event: string,\n cb?: (x: A) => void,\n map: (e: CustomEvent) => A = (e) => e.detail,\n) {\n React.useEffect(() => {\n if (!cb || !el) return;\n const ctx = new AbortController();\n const callback = (e: Event) => cb(map(e as CustomEvent));\n el?.addEventListener(event, callback, { signal: ctx.signal });\n return () => ctx.abort();\n }, [el, cb]);\n}\n"],
|
|
5
|
+
"mappings": "AAYA,UAAYA,MAAW,QCAvB,UAAYC,MAAW,QAMhB,SAASC,EACZC,EACAC,EACAC,EACAC,EAA8BC,GAAMA,EAAE,OACxC,CACQ,YAAU,IAAM,CAClB,GAAI,CAACF,GAAM,CAACF,EAAI,OAChB,IAAMK,EAAM,IAAI,gBACVC,EAAYF,GAAaF,EAAGC,EAAIC,CAAgB,CAAC,EACvD,OAAAJ,GAAI,iBAAiBC,EAAOK,EAAU,CAAE,OAAQD,EAAI,MAAO,CAAC,EACrD,IAAMA,EAAI,MAAM,CAC3B,EAAG,CAACL,EAAIE,CAAE,CAAC,CACf,CDdA,SAASK,EAAsBC,EAA+B,CAC1D,GAAM,CAACC,EAAQC,CAAS,EACd,WAAwD,IAAI,EAEtE,OAAM,YAAU,IACL,IAAM,CACTD,GAAQ,OAAO,CACnB,EACD,CAACA,CAAM,CAAC,EAEL,YAAU,IAAM,CAClB,GAAI,CAACA,EACD,OAGJ,IAAIE,EAAY,GAChB,OAAC,SAAY,CACT,GAAI,CAACH,EAAM,OAAQ,CACf,MAAMC,EAAO,MAAM,EACnB,MACJ,CAEA,MAAMA,EAAO,KAAKD,EAAM,MAAM,EAC1B,EAAAG,GAAa,CAACH,EAAM,UAIpB,WAAYA,EAAM,OAClB,MAAMC,EAAO,iBAAiBD,EAAM,MAAM,EAE1C,MAAMC,EAAO,QAAQD,EAAM,MAAM,EAEzC,GAAG,EAEI,IAAM,CACTG,EAAY,EAChB,CACJ,EAAG,CAACF,EAAQD,EAAM,OAAQ,KAAK,UAAUA,EAAM,MAAM,CAAC,CAAC,EAEvDI,EAAeH,EAAQ,oBAAqBD,EAAM,OAAO,EACzDI,EAAeH,EAAQ,qBAAsBD,EAAM,QAAQ,EAC3DI,EACIH,EACA,4BACAD,EAAM,eACLK,GAAMA,EAAE,OAAO,UAAU,CAC9B,EAGI,gBAAC,sBACG,IAAKH,EACL,GAAIF,EAAM,GACV,UAAWA,EAAM,UACjB,OAAQA,EAAM,OACd,KAAMA,EAAM,KACZ,MAAOA,EAAM,MACb,SAAUA,EAAM,SAChB,MAAOA,EAAM,MACjB,CAER,CA6HO,IAAMM,EAA4D,OACrEP,CACJ",
|
|
6
6
|
"names": ["React", "React", "usePspListener", "el", "event", "cb", "map", "e", "ctx", "callback", "PerspectiveViewerImpl", "props", "viewer", "setViewer", "cancelled", "usePspListener", "e", "PerspectiveViewer"]
|
|
7
7
|
}
|
package/dist/esm/utils.d.ts
CHANGED
|
@@ -1 +1,5 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Subscribe `cb` to a Custom Event on `el` for the lifetime of the component,
|
|
3
|
+
* projecting the event through `map` (by default, `e.detail`).
|
|
4
|
+
*/
|
|
1
5
|
export declare function usePspListener<A>(el: HTMLElement | undefined | null, event: string, cb?: (x: A) => void, map?: (e: CustomEvent) => A): void;
|
package/dist/esm/viewer.d.ts
CHANGED
|
@@ -2,23 +2,111 @@ import * as React from "react";
|
|
|
2
2
|
import type * as psp from "@perspective-dev/client";
|
|
3
3
|
import type * as pspViewer from "@perspective-dev/viewer";
|
|
4
4
|
/**
|
|
5
|
-
* Props for the
|
|
5
|
+
* Props for the {@link PerspectiveViewer} component.
|
|
6
6
|
*/
|
|
7
7
|
export interface PerspectiveViewerProps {
|
|
8
|
+
/**
|
|
9
|
+
* The viewer's data source, forwarded to the element's `load()` method
|
|
10
|
+
* whenever it changes.
|
|
11
|
+
*
|
|
12
|
+
* - A `Table` (or `Promise<Table>`) displays that table directly.
|
|
13
|
+
* - A `Client` (e.g. from `perspective.worker()` or a WebSocket
|
|
14
|
+
* connection to a remote server) connects the viewer to every table
|
|
15
|
+
* hosted by that client; the table each panel displays is chosen by
|
|
16
|
+
* {@link PerspectiveViewerProps.config} or interactively by the user.
|
|
17
|
+
* - `undefined` calls `eject()`, returning the viewer to an unloaded
|
|
18
|
+
* state without unmounting it.
|
|
19
|
+
*
|
|
20
|
+
* The component does not take ownership of the `Table` — delete it
|
|
21
|
+
* yourself when it is no longer needed, e.g.
|
|
22
|
+
* `table.delete({ lazy: true })`.
|
|
23
|
+
*/
|
|
8
24
|
client?: psp.Client | Promise<psp.Client> | psp.Table | Promise<psp.Table>;
|
|
25
|
+
/**
|
|
26
|
+
* Declarative viewer state — group-bys, splits, filters, sorts,
|
|
27
|
+
* expressions, plugin and plugin config — applied with the element's
|
|
28
|
+
* `restore()` method whenever it (or
|
|
29
|
+
* {@link PerspectiveViewerProps.client}) changes. A config with a
|
|
30
|
+
* `panels` property is treated as a multi-panel workspace layout and
|
|
31
|
+
* applied with `restoreWorkspace()` instead.
|
|
32
|
+
*
|
|
33
|
+
* Configs are compared structurally, so passing a fresh-but-equal object
|
|
34
|
+
* literal on each render does not re-apply. Combine with
|
|
35
|
+
* {@link PerspectiveViewerProps.onConfigUpdate} to use the viewer as a
|
|
36
|
+
* controlled component.
|
|
37
|
+
*/
|
|
9
38
|
config?: pspViewer.ViewerConfigUpdate | pspViewer.WorkspaceConfigUpdate;
|
|
39
|
+
/**
|
|
40
|
+
* Called with the viewer's complete configuration whenever the user
|
|
41
|
+
* reconfigures it through its UI (the element's
|
|
42
|
+
* `"perspective-config-update"` Custom Event). Use this to persist the
|
|
43
|
+
* user's view or reflect it back through
|
|
44
|
+
* {@link PerspectiveViewerProps.config}.
|
|
45
|
+
*/
|
|
10
46
|
onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;
|
|
47
|
+
/**
|
|
48
|
+
* Called when the user clicks a datapoint (the element's
|
|
49
|
+
* `"perspective-click"` Custom Event), with the clicked row's data and a
|
|
50
|
+
* filter config which would select it.
|
|
51
|
+
*/
|
|
11
52
|
onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;
|
|
53
|
+
/**
|
|
54
|
+
* Called when the user selects or deselects a datapoint or row (the
|
|
55
|
+
* element's `"perspective-select"` Custom Event).
|
|
56
|
+
*/
|
|
12
57
|
onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;
|
|
58
|
+
/** Forwarded to the element's `class` attribute. */
|
|
13
59
|
className?: string | undefined;
|
|
60
|
+
/** Forwarded to the element's `hidden` attribute. */
|
|
14
61
|
hidden?: boolean | undefined;
|
|
62
|
+
/** Forwarded to the element's `id` attribute. */
|
|
15
63
|
id?: string | undefined;
|
|
64
|
+
/** Forwarded to the element's `slot` attribute. */
|
|
16
65
|
slot?: string | undefined;
|
|
66
|
+
/** Forwarded to the element's inline `style`. */
|
|
17
67
|
style?: React.CSSProperties | undefined;
|
|
68
|
+
/** Forwarded to the element's `tabindex` attribute. */
|
|
18
69
|
tabIndex?: number | undefined;
|
|
70
|
+
/** Forwarded to the element's `title` attribute. */
|
|
19
71
|
title?: string | undefined;
|
|
20
72
|
}
|
|
21
73
|
/**
|
|
22
|
-
* A React wrapper
|
|
74
|
+
* A declarative React wrapper for the `<perspective-viewer>` Custom Element.
|
|
75
|
+
*
|
|
76
|
+
* `<PerspectiveViewer>` manages the element's imperative lifecycle: it
|
|
77
|
+
* `load()`s the {@link PerspectiveViewerProps.client}, `restore()`s the
|
|
78
|
+
* {@link PerspectiveViewerProps.config} whenever either changes, subscribes
|
|
79
|
+
* the `on*` callback props to the element's Custom Events, and `delete()`s
|
|
80
|
+
* the viewer (freeing its WebAssembly resources) on unmount. Tables and
|
|
81
|
+
* clients are created outside the component and are yours to manage — a
|
|
82
|
+
* `Table` passed as `client` survives unmount and can be shown again by a
|
|
83
|
+
* later mount.
|
|
84
|
+
*
|
|
85
|
+
* The component is memoized, so re-rendering a parent with unchanged props
|
|
86
|
+
* does not touch the viewer.
|
|
87
|
+
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```tsx
|
|
90
|
+
* const WORKER = await perspective.worker();
|
|
91
|
+
* const TABLE = WORKER.table(
|
|
92
|
+
* fetch("superstore.lz4.arrow").then((resp) => resp.arrayBuffer()),
|
|
93
|
+
* { name: "superstore" },
|
|
94
|
+
* );
|
|
95
|
+
*
|
|
96
|
+
* const App: React.FC = () => {
|
|
97
|
+
* const [config, setConfig] = React.useState<pspViewer.ViewerConfigUpdate>({
|
|
98
|
+
* group_by: ["State"],
|
|
99
|
+
* plugin: "Y Bar",
|
|
100
|
+
* });
|
|
101
|
+
*
|
|
102
|
+
* return (
|
|
103
|
+
* <PerspectiveViewer
|
|
104
|
+
* client={TABLE}
|
|
105
|
+
* config={config}
|
|
106
|
+
* onConfigUpdate={setConfig}
|
|
107
|
+
* />
|
|
108
|
+
* );
|
|
109
|
+
* };
|
|
110
|
+
* ```
|
|
23
111
|
*/
|
|
24
112
|
export declare const PerspectiveViewer: React.FC<PerspectiveViewerProps>;
|
package/package.json
CHANGED
package/src/index.tsx
CHANGED
|
@@ -11,11 +11,60 @@
|
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
+
* React bindings for [Perspective](https://perspective-dev.github.io/).
|
|
15
|
+
*
|
|
16
|
+
* This module exports {@link PerspectiveViewer}, a declarative React wrapper
|
|
17
|
+
* for the `<perspective-viewer>` Custom Element. The component manages the
|
|
18
|
+
* element's imperative lifecycle — `load()` and `restore()` in response to
|
|
19
|
+
* prop changes, `delete()` on unmount — and exposes the element's Custom
|
|
20
|
+
* Events as React-style callback props.
|
|
21
|
+
*
|
|
22
|
+
* Perspective's WebAssembly engine and UI must be initialized (and at least
|
|
23
|
+
* one plugin package imported) before the first `<PerspectiveViewer>`
|
|
24
|
+
* renders:
|
|
25
|
+
*
|
|
26
|
+
* ```tsx
|
|
27
|
+
* import perspective from "@perspective-dev/client";
|
|
28
|
+
* import perspective_viewer from "@perspective-dev/viewer";
|
|
29
|
+
* import "@perspective-dev/viewer-datagrid";
|
|
30
|
+
* import "@perspective-dev/viewer-charts";
|
|
31
|
+
*
|
|
32
|
+
* import SERVER_WASM from "@perspective-dev/server/dist/wasm/perspective-server.wasm";
|
|
33
|
+
* import CLIENT_WASM from "@perspective-dev/viewer/dist/wasm/perspective-viewer.wasm";
|
|
34
|
+
*
|
|
35
|
+
* await Promise.all([
|
|
36
|
+
* perspective.init_server(fetch(SERVER_WASM)),
|
|
37
|
+
* perspective_viewer.init_client(fetch(CLIENT_WASM)),
|
|
38
|
+
* ]);
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* Then pass a `Table` (or `Client`, or a `Promise` of either) and an optional
|
|
42
|
+
* config:
|
|
43
|
+
*
|
|
44
|
+
* ```tsx
|
|
45
|
+
* import { PerspectiveViewer } from "@perspective-dev/react";
|
|
46
|
+
*
|
|
47
|
+
* const WORKER = await perspective.worker();
|
|
48
|
+
* const TABLE = WORKER.table(
|
|
49
|
+
* fetch("superstore.lz4.arrow").then((resp) => resp.arrayBuffer()),
|
|
50
|
+
* { name: "superstore" },
|
|
51
|
+
* );
|
|
52
|
+
*
|
|
53
|
+
* const App: React.FC = () => (
|
|
54
|
+
* <PerspectiveViewer
|
|
55
|
+
* client={TABLE}
|
|
56
|
+
* config={{ group_by: ["State"], plugin: "Y Bar" }}
|
|
57
|
+
* />
|
|
58
|
+
* );
|
|
59
|
+
* ```
|
|
14
60
|
*
|
|
15
61
|
* # See Also
|
|
16
62
|
*
|
|
17
|
-
* [`react-example`](https://github.com/perspective-dev/perspective/tree/master/examples/react-example)
|
|
18
|
-
*
|
|
63
|
+
* - [`react-example`](https://github.com/perspective-dev/perspective/tree/master/examples/react-example)
|
|
64
|
+
* project from the Perspective GitHub repo, a complete bundler-configured
|
|
65
|
+
* application including a multi-panel workspace config.
|
|
66
|
+
* - [Perspective User Guide](https://perspective-dev.github.io/guide/)
|
|
67
|
+
* - [`<perspective-viewer>` API documentation](https://perspective-dev.github.io/viewer/modules/perspective-viewer.html)
|
|
19
68
|
*
|
|
20
69
|
* @module
|
|
21
70
|
*/
|
package/src/utils.tsx
CHANGED
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
|
|
13
13
|
import * as React from "react";
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Subscribe `cb` to a Custom Event on `el` for the lifetime of the component,
|
|
17
|
+
* projecting the event through `map` (by default, `e.detail`).
|
|
18
|
+
*/
|
|
15
19
|
export function usePspListener<A>(
|
|
16
20
|
el: HTMLElement | undefined | null,
|
|
17
21
|
event: string,
|
package/src/viewer.tsx
CHANGED
|
@@ -78,28 +78,127 @@ function PerspectiveViewerImpl(props: PerspectiveViewerProps) {
|
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
|
-
* Props for the
|
|
81
|
+
* Props for the {@link PerspectiveViewer} component.
|
|
82
82
|
*/
|
|
83
83
|
export interface PerspectiveViewerProps {
|
|
84
|
+
/**
|
|
85
|
+
* The viewer's data source, forwarded to the element's `load()` method
|
|
86
|
+
* whenever it changes.
|
|
87
|
+
*
|
|
88
|
+
* - A `Table` (or `Promise<Table>`) displays that table directly.
|
|
89
|
+
* - A `Client` (e.g. from `perspective.worker()` or a WebSocket
|
|
90
|
+
* connection to a remote server) connects the viewer to every table
|
|
91
|
+
* hosted by that client; the table each panel displays is chosen by
|
|
92
|
+
* {@link PerspectiveViewerProps.config} or interactively by the user.
|
|
93
|
+
* - `undefined` calls `eject()`, returning the viewer to an unloaded
|
|
94
|
+
* state without unmounting it.
|
|
95
|
+
*
|
|
96
|
+
* The component does not take ownership of the `Table` — delete it
|
|
97
|
+
* yourself when it is no longer needed, e.g.
|
|
98
|
+
* `table.delete({ lazy: true })`.
|
|
99
|
+
*/
|
|
84
100
|
client?: psp.Client | Promise<psp.Client> | psp.Table | Promise<psp.Table>;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Declarative viewer state — group-bys, splits, filters, sorts,
|
|
104
|
+
* expressions, plugin and plugin config — applied with the element's
|
|
105
|
+
* `restore()` method whenever it (or
|
|
106
|
+
* {@link PerspectiveViewerProps.client}) changes. A config with a
|
|
107
|
+
* `panels` property is treated as a multi-panel workspace layout and
|
|
108
|
+
* applied with `restoreWorkspace()` instead.
|
|
109
|
+
*
|
|
110
|
+
* Configs are compared structurally, so passing a fresh-but-equal object
|
|
111
|
+
* literal on each render does not re-apply. Combine with
|
|
112
|
+
* {@link PerspectiveViewerProps.onConfigUpdate} to use the viewer as a
|
|
113
|
+
* controlled component.
|
|
114
|
+
*/
|
|
85
115
|
config?: pspViewer.ViewerConfigUpdate | pspViewer.WorkspaceConfigUpdate;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Called with the viewer's complete configuration whenever the user
|
|
119
|
+
* reconfigures it through its UI (the element's
|
|
120
|
+
* `"perspective-config-update"` Custom Event). Use this to persist the
|
|
121
|
+
* user's view or reflect it back through
|
|
122
|
+
* {@link PerspectiveViewerProps.config}.
|
|
123
|
+
*/
|
|
86
124
|
onConfigUpdate?: (config: pspViewer.ViewerConfigUpdate) => void;
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Called when the user clicks a datapoint (the element's
|
|
128
|
+
* `"perspective-click"` Custom Event), with the clicked row's data and a
|
|
129
|
+
* filter config which would select it.
|
|
130
|
+
*/
|
|
87
131
|
onClick?: (data: pspViewer.PerspectiveClickEventDetail) => void;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Called when the user selects or deselects a datapoint or row (the
|
|
135
|
+
* element's `"perspective-select"` Custom Event).
|
|
136
|
+
*/
|
|
88
137
|
onSelect?: (data: pspViewer.PerspectiveSelectEventDetail) => void;
|
|
89
138
|
|
|
90
139
|
// Applicable props from `React.HTMLAttributes`, which we cannot extend
|
|
91
140
|
// directly because Perspective changes the signature of `onClick`.
|
|
141
|
+
|
|
142
|
+
/** Forwarded to the element's `class` attribute. */
|
|
92
143
|
className?: string | undefined;
|
|
144
|
+
|
|
145
|
+
/** Forwarded to the element's `hidden` attribute. */
|
|
93
146
|
hidden?: boolean | undefined;
|
|
147
|
+
|
|
148
|
+
/** Forwarded to the element's `id` attribute. */
|
|
94
149
|
id?: string | undefined;
|
|
150
|
+
|
|
151
|
+
/** Forwarded to the element's `slot` attribute. */
|
|
95
152
|
slot?: string | undefined;
|
|
153
|
+
|
|
154
|
+
/** Forwarded to the element's inline `style`. */
|
|
96
155
|
style?: React.CSSProperties | undefined;
|
|
156
|
+
|
|
157
|
+
/** Forwarded to the element's `tabindex` attribute. */
|
|
97
158
|
tabIndex?: number | undefined;
|
|
159
|
+
|
|
160
|
+
/** Forwarded to the element's `title` attribute. */
|
|
98
161
|
title?: string | undefined;
|
|
99
162
|
}
|
|
100
163
|
|
|
101
164
|
/**
|
|
102
|
-
* A React wrapper
|
|
165
|
+
* A declarative React wrapper for the `<perspective-viewer>` Custom Element.
|
|
166
|
+
*
|
|
167
|
+
* `<PerspectiveViewer>` manages the element's imperative lifecycle: it
|
|
168
|
+
* `load()`s the {@link PerspectiveViewerProps.client}, `restore()`s the
|
|
169
|
+
* {@link PerspectiveViewerProps.config} whenever either changes, subscribes
|
|
170
|
+
* the `on*` callback props to the element's Custom Events, and `delete()`s
|
|
171
|
+
* the viewer (freeing its WebAssembly resources) on unmount. Tables and
|
|
172
|
+
* clients are created outside the component and are yours to manage — a
|
|
173
|
+
* `Table` passed as `client` survives unmount and can be shown again by a
|
|
174
|
+
* later mount.
|
|
175
|
+
*
|
|
176
|
+
* The component is memoized, so re-rendering a parent with unchanged props
|
|
177
|
+
* does not touch the viewer.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```tsx
|
|
181
|
+
* const WORKER = await perspective.worker();
|
|
182
|
+
* const TABLE = WORKER.table(
|
|
183
|
+
* fetch("superstore.lz4.arrow").then((resp) => resp.arrayBuffer()),
|
|
184
|
+
* { name: "superstore" },
|
|
185
|
+
* );
|
|
186
|
+
*
|
|
187
|
+
* const App: React.FC = () => {
|
|
188
|
+
* const [config, setConfig] = React.useState<pspViewer.ViewerConfigUpdate>({
|
|
189
|
+
* group_by: ["State"],
|
|
190
|
+
* plugin: "Y Bar",
|
|
191
|
+
* });
|
|
192
|
+
*
|
|
193
|
+
* return (
|
|
194
|
+
* <PerspectiveViewer
|
|
195
|
+
* client={TABLE}
|
|
196
|
+
* config={config}
|
|
197
|
+
* onConfigUpdate={setConfig}
|
|
198
|
+
* />
|
|
199
|
+
* );
|
|
200
|
+
* };
|
|
201
|
+
* ```
|
|
103
202
|
*/
|
|
104
203
|
export const PerspectiveViewer: React.FC<PerspectiveViewerProps> = React.memo(
|
|
105
204
|
PerspectiveViewerImpl,
|