perspective-viewer Web Component 实战指南:Perspective 数据可视化组件的前端集成、主题与配置持久化
【免费下载链接】perspectiveA data visualization and analytics component, especially well-suited for large and/or streaming datasets.项目地址: https://gitcode.com/GitHub_Trending/pe/perspective
<perspective-viewer>是 Perspective 数据可视化与分析库的官方 UI 组件,它以 Web Component(Custom Element)的形式为浏览器应用提供完整的图形化配置界面,可直接绑定本地 Web Worker、远程 WebSocket 服务端或虚拟数据源中的table(),并将查询结果渲染为数据表格与图表。阅读本文后,你将掌握如何引入并注册该组件、加载与共享数据表、切换主题、通过save()/restore()序列化与恢复视图配置,以及监听更新与点击事件,实现可直接复用的大数据量、流式数据可视化前端方案。
<perspective-viewer>Custom Element 库
<perspective-viewer>为配置perspective库并格式化其输出到各类可视化插件提供了完整的图形化界面。该组件位于 rust/perspective-viewer 包中,npm 包名为@perspective-dev/viewer(版本信息见 rust/perspective-viewer/package.json),与其配套的插件包还包括@perspective-dev/viewer-datagrid(数据表格)与@perspective-dev/viewer-charts(图表)。
引入与注册机制
如果你使用esbuild或其他支持 ES6 模块的打包器,只需在应用的任意位置导入perspective-viewer相关库即可——这些模块不导出任何实际内容,导入的副作用是注册 Custom Element,供站点中的普通 HTML 直接使用:
import "@perspective-dev/viewer"; import "@perspective-dev/viewer-datagrid"; import "@perspective-dev/viewer-charts";这一“无导出、仅注册”的设计在源码中有明确体现:在 rust/perspective-viewer/src/ts/perspective-viewer.ts 的模块注释中说明,导入该模块的副作用是将PerspectiveViewerElement类注册为 custom element;由于 WebAssembly 编译使该模块成为动态模块,为保证 Custom Element 的扩展方法随包导入同步注册,所有 API 方法都被设计为async(必须等待 wasm 模块实例就绪)。
导入完成后,<perspective-viewer>Web Component 即可用于站点任意标准 HTML 中。一个最简单的例子:
<perspective-viewer id="view1"></perspective-viewer>或者在 JavaScript 中以编程方式创建:
const viewer = document.createElement("perspective-viewer");从源码结构看,组件的主入口(custom element 定义、load/save/restore/reset等 API 实现)位于 rust/perspective-viewer/src/rust/custom_elements/viewer.rs,前端引导逻辑在 rust/perspective-viewer/src/ts/bootstrap.ts 与 rust/perspective-viewer/src/ts/perspective-viewer.ts。
Theming 主题系统
perspective-viewer及其配套插件支持主题(Theming)。perspective-viewer内置了多套主题,你可以直接在应用中导入任意主题,所有perspective-viewer将随之应用对应主题:
// 基于 Thought Merchants's Prospective 设计的主题 import "@perspective-dev/viewer/dist/css/pro.css"; import "@perspective-dev/viewer/dist/css/pro-dark.css"; // 其他主题 import "@perspective-dev/viewer/dist/css/solarized.css"; import "@perspective-dev/viewer/dist/css/solarized-dark.css"; import "@perspective-dev/viewer/dist/css/monokai.css"; import "@perspective-dev/viewer/dist/css/vaporwave.css";或者使用打包了所有默认主题的themes.css:
import "@perspective-dev/viewer/dist/css/themes.css";仓库中的主题源文件集中存放在 rust/perspective-viewer/src/themes,除上述主题外还包含botanical.css、dracula.css、gruvbox.css、gruvbox-dark.css、phosphor.css、defaults.css、icons.css、intl.css以及聚合入口themes.css,你可以在该目录下查看每个主题的完整 CSS 定义。
如果你选择不自行打包主题,也可以通过 CDN 直接引用,并在 HTML 中通过<link>引入:
<link rel="stylesheet" crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/@perspective-dev/viewer/dist/css/pro.css" />主题自动检测与 resetThemes()
注意上述<link>标签中的crossorigin="anonymous"属性。当从跨域上下文引入主题时,该属性可能是让<perspective-viewer>检测到主题所必需的。如果检测失败——例如在<perspective-viewer>初始化之后才向document添加额外的主题,或因其他任何原因导致主题自动检测失败——你可以通过.resetThemes()方法手动告知<perspective-viewer>可用的主题名称:
// 重新自动检测主题 viewer.resetThemes(); // 显式设置可用主题(这些主题仍必须先作为 CSS 导入!) viewer.resetThemes(["Pro Light", "Pro Dark"]);resetThemes()的实现位于 rust/perspective-viewer/src/rust/custom_elements/viewer.rs:传入None时重新解析document中的 CSS 以自动检测主题,传入字符串数组时则显式限定可用主题集合;调用后组件会同步当前激活主题,若新集合中不包含当前主题,则可能触发主题切换。与之配套的还有getThemes()方法(同文件第 1704 行),用于查询当前可用的主题名称列表。主题配置的底层状态管理见 rust/perspective-viewer/src/rust/config/viewer_config.rs 与 rust/perspective-viewer/src/rust/presentation.rs 中的reset_available_themes。
初始主题设置
<perspective-viewer>初始化时会默认使用第一个加载的主题。你可以通过.restore()覆盖默认值,或通过设置theme属性来提供初始主题:
<perspective-viewer theme="Pro Light"></perspective-viewer>或
const viewer = document.querySelector("perspective-viewer"); await viewer.restore({ theme: "Pro Dark" });向<perspective-viewer>加载数据
数据可以通过load()方法,以Table()或Promise<Table>的形式加载到<perspective-viewer>中:
// 创建一个新的 worker,然后在 worker 上创建 table promise。 const worker = await perspective.worker(); const table = await worker.table(data); // 将 viewer 元素绑定到这个 table。 await viewer.load(table);load()的绑定逻辑见 rust/perspective-viewer/src/rust/custom_elements/viewer.rs,它接受一个JsClientLoad(既可以是本地/Worker 端 client,也可以是远程虚拟表 client)并建立会话。
在多个perspective-viewer之间共享一个table()
多个perspective-viewer可以通过把同一个table()传入每个 viewer 的load()方法来共享该表。当底层table()被更新时,每个perspective-viewer都会同步更新;但table.delete()在所有引用它的perspective-viewer实例也被删除之前会失败:
const viewer1 = document.getElementById("viewer1"); const viewer2 = document.getElementById("viewer2"); // 创建一个新的 WebWorker const worker = await perspective.worker(); // 在该 worker 中创建 table const table = await worker.table(data); // 将同一个 table 加载到两个不同的 <perspective-viewer> 元素中 await viewer1.load(table); await viewer2.load(table); // `viewer1` 和 `viewer2` 都会反映这次更新 await table.update([{ x: 5, y: "e", z: true }]);通过WebSocketServer()与 Node.js 实现仅服务端模式
加载一个虚拟的(仅服务端)Table与加载本地/Web Worker 的Table完全一样——只需将虚拟Table传给viewer.load():
在浏览器端:
const elem = document.getElementsByTagName("perspective-viewer")[0]; // 绑定到服务器的 worker,而不是实例化一个 Web Worker。 const websocket = await perspective.websocket( window.location.origin.replace("http", "ws"), ); // 将 viewer 绑定到预加载的数据源。`table` 和 `view` 对象都驻留在服务器上。 const server_table = await websocket.open_table("table_one"); await elem.load(server_table); // 或者通过 view 加载 table 的数据。浏览器此时在其自身的 `table` 中 // 也拥有一份该 view 的副本,其更新通过 Apache Arrow 传输到浏览器。 const worker = await perspective.worker(); const server_view = await server_table.view(); const client_table = worker.table(server_view); await elem.load(client_table);以这种方式绑定的<perspective-viewer>实例,与依赖 Web Worker 的<perspective-viewer>在其余方面没有区别,甚至可以与依赖 Web Worker 绑定的table()共处同一个宿主应用中。二者都使用相同的基于 promise 的 API 与服务器端实例化的view()通信,只是此处通过 websocket 进行。仓库中关于服务端集成的完整说明可参见 docs/md/explanation/virtual_servers.md 与 docs/md/explanation/architecture.md。
通过save()/restore()持久化<perspective-viewer>配置
<perspective-viewer>是**持久化(persistent)**的:它除数据本身之外的整个状态都可以被序列化或反序列化。这包括所有列(columns)、过滤器(filters)、透视(pivots)、表达式(expressions)等属性,以及数据表格的样式设置、配置面板的可见性等。这个重载功能覆盖了一系列使用场景:
- 在
load()调用之后设置<perspective-viewer>的初始状态。 - 更新单个或部分属性,而不修改其他属性。
- 将部分或全部属性重置为与数据相关的默认值。
- 将用户的配置持久化到
localStorage或服务器。
序列化与反序列化 viewer 状态
要获取整个状态的 JSON-ready JavaScript 对象,使用save()方法。save()还支持其他格式,如"arraybuffer"和"string"(base64,而非 JSON),你可以在占用空间与迁移/手工编辑便利性之间做出取舍:
const json_token = await elem.save(); const string_token = await elem.save("string");save()的 Rust 侧实现见 rust/perspective-viewer/src/rust/custom_elements/viewer.rs:它通过get_viewer_config读取面板当前的ViewerConfig并编码返回。对于任何格式,序列化得到的 token 都可以通过restore()方法恢复到任何具有相同 schema 的Table的<perspective-viewer>上。需要注意的是,save()返回的 token 中的数据可能不同,但其 schema 一般不能不同,因为许多其他设置依赖于列名和类型:
await elem.restore(json_token); await elem.restore(string_token);由于restore()是根据 token 的类型进行分派的,因此务必保证这些类型匹配!一个常见的错误来源是:将 JSON-stringified 的 token 传给restore(),此时字符串 token 会被假定为 base64 编码的 msgpack:
// 这将会报错! await elem.restore(JSON.stringify(json_token));restore()的实现(含suppress_errors选项与单面板/全面板的分派逻辑)见 rust/perspective-viewer/src/rust/custom_elements/viewer.rs。
更新单个属性
使用 JSON 格式,<perspective-viewer>配置的每一个方面都可以通过 JavaScript 的restore()方法进行操作。属性的合法结构由ViewerConfig与内嵌的ViewConfig类型声明描述(前者见 rust/perspective-viewer/src/ts/ts-rs/ViewerConfig.d.ts,后者见 rust/perspective-js/src/rust 下 client/view 相关类型),关于每个ViewConfig属性的交互式示例可参阅文档的 View 章节。
// 设置插件(同时会把 `columns` 更新为插件默认值) await elem.restore({ plugin: "X Bar" }); // 更新插件和列(只绘制一次) await elem.restore({ plugin: "X Bar", columns: ["Sales"] }); // 打开配置面板 await elem.restore({ settings: true }); // 创建表达式 await elem.restore({ columns: ['"Sales" + 100'], expressions: { "New Column": '"Sales" + 100' }, }); // 如果列不存在于 schema 或 expressions 中则报错 // await elem.restore({columns: ["\"Sales\" + 100"], expressions: {}}); // 添加过滤器 await elem.restore({ filter: [["Sales", "<", 100]] }); // 添加排序,但不移除过滤器 await elem.restore({ sort: [["Prodit", "desc"]] }); // 只重置过滤器,保留排序 await elem.restore({ filter: undefined }); // 将全部属性重置为默认值,例如在 `load()` 之后 await elem.reset();reset()的实现位于 rust/perspective-viewer/src/rust/custom_elements/viewer.rs:reset_all参数为true时还会清除表达式和列样式设置;可通过{panel: "p1"}选项只重置指定面板,省略时重置所有面板。
另一种快速生成目标配置 token 的有效方式是:在浏览器中手动设置好视图后,直接复制save()返回的 token。JSON 格式是人类可读的,且save()会返回所有属性的默认设置,因此生成的 token 非常容易手工微调。你可以在应用代码中调用save(),也可以通过 Chrome 开发者控制台调用:
// 复制到剪贴板 copy(await document.querySelector("perspective-viewer").save());对于多面板(workspace)场景,<perspective-viewer>还提供了saveWorkspace()/restoreWorkspace()对,用于序列化/恢复整个 workspace 配置(包括布局、面板与调色板),实现见 rust/perspective-viewer/src/rust/custom_elements/viewer.rs。
更新事件 Update events
每当<perspective-viewer>底层的table()通过load()或update()方法发生变化时,会触发perspective-view-updateDOM 事件。类似地,通过 Attribute API 或用户交互触发的view()更新会触发perspective-config-update事件:
elem.addEventListener("perspective-config-update", function (event) { var config = elem.save(); console.log("The view() config has changed to " + JSON.stringify(config)); });从源码看,事件系统基于 PubSub 通道向 JavaScriptCustomEvent的扇出机制实现,核心代码在 rust/perspective-viewer/src/rust/custom_events.rs:wire_element_events(第 209 行起)负责元素级事件(主题变化、设置面板开关等),wire_panel_events(第 326 行起)负责单个面板的session/renderer通道;所有perspective-*事件均以bubbles+composed方式派发,perspective-config-update的detail为裸的ViewerConfig(第 147-202 行dispatch_config_update),可直接用于viewer.restore(e.detail)的往返操作。除本文涉及的事件外,该文件还实现了layout-update、active-panel-update、global-filter-update、select、column-style-change、table-delete等事件。
点击事件 Click events
每当<perspective-viewer>的表格或图表被点击时,会触发perspective-clickDOM 事件,其 detail 对象包含config、column_names和row三个字段。
config对象包含一组filters,可以通过restore()应用到<perspective-viewer>,使其显示过滤后的数据子集。
column_names属性包含匹配列的数组,row属性返回对应的行数据。
elem.addEventListener("perspective-click", function (event) { var config = event.detail.config; elem.restore(config); });在源码中,perspective-click监听器也被用于 master/detail 联动:点击 master 面板(如柱状图的柱子、平面表格的单元格)被视为一次选区贡献,会路由为MasterContribution消息(见 rust/perspective-viewer/src/rust/components/viewer/wiring.rs 与 rust/perspective-viewer/src/rust/components/viewer/msg.rs),从而驱动全局过滤联动——这意味着你可以在自己的应用中基于perspective-click构建跨图表的交互式筛选体验。
结语与延伸阅读
至此,你已经掌握了@perspective-dev/viewer组件从引入注册、数据加载与共享、服务端 WebSocket 绑定、主题定制,到配置序列化持久化与事件响应的完整用法。以上 API 全部建立在asyncpromise 模型之上,既适用于单页应用内的本地 Worker 模式,也适用于与 Python/Rust 服务端配合的远程模式。
如果你想进一步深入,可以继续阅读仓库中的以下资料:
- docs/md/explanation/view.md:
ViewConfig各属性的交互式示例 - docs/md/how_to/javascript/virtual_server.md:JavaScript 端虚拟服务器接入
- docs/md/how_to/javascript/save_restore.md:保存与恢复状态的进阶用法
- docs/md/how_to/javascript/theming.md:主题定制的详细说明
- docs/md/how_to/javascript/events.md:完整事件清单
- rust/perspective-viewer/src/ts:组件 TypeScript 侧入口与类型声明
- rust/perspective-viewer/src/rust:组件 Rust 侧实现源码
【免费下载链接】perspectiveA data visualization and analytics component, especially well-suited for large and/or streaming datasets.项目地址: https://gitcode.com/GitHub_Trending/pe/perspective
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考