ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

deck.gl widgets 示例应用完全指南:从零搭建带交互控件的可视化应用

deck.gl widgets 示例应用完全指南:从零搭建带交互控件的可视化应用 deck.gl widgets 示例应用完全指南从零搭建带交互控件的可视化应用【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl本文基于 deck.gl 仓库中的 widgets 示例应用 展开该示例使用 Vite 作为构建与开发服务器通过 6 个独立 Demo 系统演示了 deck.gl widgets 模块deck.gl/widgets的用法。读完本文你将掌握如何安装依赖、启动开发服务器、构建生产产物并理解 Zoom、Compass、Info、Popup、ContextMenu 等 20 余个内置控件的真实配置方式以及它们如何在纯 JavaScript 与 React 两种 API 下组织使用。示例应用概览与目录结构该示例位于仓库的 test/apps/widgets 目录是一个用于验证与演示 widgets 模块能力的独立 Vite 应用。整个目录包含以下文件文件作用README.md应用说明与使用命令package.json依赖声明与 npm scriptsindex.html入口页列出 6 个 Demo 的跳转链接geospatial.ts / geospatial.html纯 JS 地理空间场景MapViewmultiview.ts / multiview.html纯 JS 多视图分割场景SplitterWidgetinfovis.ts / infovis.html纯 JS 信息可视化场景Orbit/Orthographicreact-geospatial.tsx / react-geospatial.htmlReact 地理空间场景react-multiview.tsx / react-multiview.htmlReact 多视图分割场景react-infovis.tsx / react-infovis.htmlReact 信息可视化场景入口 index.html 是一个纯链接导航页在main中列出了六个 Demo 的入口Pure JS - Geospatial、Pure JS - Multiview、Pure JS - Infovis以及对应的三个 React 版本。这 6 个 Demo 分别对应「地图地理空间」「多视图分割」「非地理信息可视化」三大典型使用场景且每种场景都同时给出纯 JS 与 React 两种写法便于对照学习。快速开始安装、启动与构建按 README.md 中的说明该应用使用 Vite 进行打包与本地伺服。安装依赖npm install # 或者使用 yarn yarn项目脚本定义在 package.json 中命令作用npm start开发模式启动 Vite 开发服务器并自动打开浏览器vite --open支持热更新npm start-local以仓库根目录的 vite.config.local.mjs 作为配置启动vite --config ../vite.config.local.mjs用于以本地源码方式调试 deck.gl 模块npm run build生产构建生成最终 bundle 并写入磁盘vite build其中start-local是 deck.gl 仓库开发者常用的一条命令——它通过指向仓库根目录的本地 Vite 配置将deck.gl/*模块解析到本地源码modules 目录从而无需发布即可联调核心库改动。普通使用者直接使用npm start即可。依赖分析package.json 中的依赖揭示了 widgets 示例的构成dependenciesdeck.gl/core、deck.gl/layers、deck.gl/widgets版本^9.3.0本仓库当前源码版本为 9.4.0-beta.4。核心渲染依赖 core图层使用 layers控件体系来自 widgetsdevDependenciesvite^7.3.1仅用于构建与开发服务器。注意type: module声明应用以 ESM 方式运行示例源码均为.ts/.tsx可直接被 Vite 原生编译。场景一地理空间应用Geospatialgeospatial.ts 构建了一个以伦敦经纬度[-0.45, 51.47]zoom 4pitch 30为中心的地图应用叠加了 WMS 底图、国家边界、机场点与飞行弧线四类图层const INITIAL_VIEW_STATE { latitude: 51.47, longitude: 0.45, zoom: 4, bearing: 0, pitch: 30 }; const deck new Deck({ parent: document.getElementById(map) as HTMLDivElement, views: new MapView({repeat: true}), // 平铺重复地图 initialViewState: INITIAL_VIEW_STATE, controller: true, layers: getLayers(), widgets: [ /* 见下文 */ ] });图层数据来自 Natural Earth 公开 GeoJSON国家边界与机场点其中机场层启用了pickable与autoHighlight并结合 DataFilterExtension 的getFilterValue/filterRange按机场等级scalerank做数据过滤为后续 Timeline 控件联动提供基础。widgets 数组一次性装配 16 个控件该 Demo 在Deck构造参数的widgets数组中一次性注册了 16 个控件是 widgets 模块能力的集中展示widgets: [ new _GeocoderWidget({geocoder: coordinates, _geolocation: true}), new ZoomWidget(), new CompassWidget(), new FullscreenWidget(), new ScreenshotWidget(), new ResetViewWidget(), new LoadingWidget(), new _ScaleWidget({placement: bottom-right}), new ThemeWidget(), new ContextMenuWidget({ /* ... */ }), new InfoWidget({mode: hover, getTooltip, arrow: 10, offset: 10}), new PopupWidget({ /* ... */ }), new _TimelineWidget({ /* ... */ }), new _StatsWidget({type: deck}), new IconWidget({ /* ... */ }), new ToggleWidget({ /* ... */ }), new SelectorWidget({ /* ... */ }) ]对照 deck.gl/widgets 的导出清单这些控件可分为几类导航类ZoomWidget缩放、CompassWidget指南针/方位、ResetViewWidget重置视角、GimbalWidget三维旋转指示见 infovis 场景地理空间类_GeocoderWidget地理编码搜索、_ScaleWidget比例尺视图类FullscreenWidget全屏、_SplitterWidget视图分割见 multiview 场景信息类InfoWidget悬停/点击信息浮层、PopupWidget固定位置弹窗、ContextMenuWidget右键菜单、ScrollbarWidget滚动条控制类IconWidget自定义图标按钮、ToggleWidget开关按钮、SelectorWidget选项切换器、_TimelineWidget时间轴播放器工具类ScreenshotWidget截图导出、ThemeWidget明暗主题切换、LoadingWidget加载指示、_StatsWidget渲染统计。控件参数详解ContextMenuWidget —— 按拾取对象生成右键菜单。getMenuItems接收PickingInfo当拾取到机场点时动态生成菜单项new ContextMenuWidget({ getMenuItems: (info: PickingInfo) { const name info.layer?.id airports info.object?.properties.name; return ( name [ {label: Airport: ${name}}, {value: open, label: Open in new tab}, {value: favorite, label: Set as favorite}, {value: filter, label: Exclude from filter} ] ); }, onMenuItemSelected: console.log })onMenuItemSelected接收用户点击的菜单项 value示例中直接打印到控制台实际应用中可在此执行打开链接、收藏、过滤等业务逻辑。InfoWidget —— 悬停/点击信息浮层。通过mode: hover指定悬停模式也可用clickgetTooltip(info, widget)返回浮层内容与定位new InfoWidget({mode: hover, getTooltip, arrow: 10, offset: 10}) function getTooltip(info: PickingInfo, widget: InfoWidget) { if (!info.object || info.layer?.id ! airports) { return null; // 未命中机场点时返回 null不显示浮层 } let text: string; switch (widget.props.mode) { case hover: text ${info.object.properties.name} (${info.object.properties.abbrev}); break; case click: text ${info.object.properties.name} (${info.object.properties.abbrev})\n...; break; } return { position: info.object.geometry.coordinates, // 浮层锚定在地理坐标处 text, style: {minWidth: 200px} }; }arrow控制箭头尺寸像素、offset控制浮层与锚点的偏移距离。PopupWidget —— 固定内容弹窗。可指定世界坐标position、marker自定义锚点 DOM 元素示例用createPin()生成 SVG 图钉、placement: top、offset: 20与closeOnClickOutside: truenew PopupWidget({ position: [-5, 52], marker: {element: createPin()}, placement: top, offset: 20, content: Im here!, closeOnClickOutside: true })TimelineWidget —— 时间轴与数据联动。这是示例中最具交互价值的控件timeRange: [2, 9]定义时间区间step: 1为步长playInterval: 500为播放间隔毫秒onTimeChange回调中通过deck.setProps重设图层的数据过滤区间实现随时间轴播放动态过滤机场点的效果new _TimelineWidget({ _container: document.getElementById(controls) as HTMLDivElement, timeRange: [2, 9], step: 1, playInterval: 500, onTimeChange: time deck.setProps({ layers: getLayers([2, time]) // 更新 filterRange 上界 }) })IconWidget / ToggleWidget / SelectorWidget —— 自定义按钮组。三者均支持placementtop-right 等、SVG data URL 图标与label。ToggleWidget额外提供onIcon/onLabel/onColor定义激活态onChange(checked)回调开关事件SelectorWidget通过initialValue与options数组提供选项切换示例给出了单视图/水平分割/垂直分割三选项onChange打印当前值。场景二信息可视化Infovisinfovis.ts 展示了非地理坐标系下的控件应用窗口左半为OrbitView轨道视角右半为OrthographicView正射视角共享一个 500 个随机三维散点的ScatterplotLayernew Deck({ views: [ new OrbitView({id: orbit-view, x: 0, width: 50%, controller: true}), new OrthographicView({ id: ortho-view, x: 50%, width: 50%, controller: {maxBounds: [[-50, -50, -50], [50, 50, 50]]} // 限制平移边界 }) ], initialViewState: INITIAL_VIEW_STATE, layers: [ /* ScatterplotLayer */ ], widgets: [ new FullscreenWidget(), new GimbalWidget(), new ResetViewWidget({id: reset-orbit, viewId: orbit-view, placement: top-right}), new ScrollbarWidget({ id: scroll-orbit, viewId: orbit-view, orientation: horizontal, contentBounds: [[-50, -50], [50, 50]] }), new ResetViewWidget({id: reset-ortho, viewId: ortho-view, placement: top-right}), new ZoomWidget({viewId: ortho-view}), new ThemeWidget({darkModeTheme: DarkTheme, lightModeTheme: LightTheme}), new InfoWidget({viewId: ortho-view, mode: hover, getTooltip: ({object}) (object ? point : null)}), new ScrollbarWidget({id: scrollbar-v, viewId: ortho-view, placement: bottom-right, orientation: vertical}), new ScrollbarWidget({id: scrollbar-h, viewId: ortho-view, placement: bottom-right, orientation: horizontal}) ] })该场景引入了两个关键概念viewId 绑定在多视图场景下控件通过viewId属性精确绑定到某个视图——例如两个ResetViewWidget分别用viewId: orbit-view与viewId: ortho-view独立重置各自视角ZoomWidget与InfoWidget也只作用于正射视图ScrollbarWidget 滚动条orientation指定水平/垂直方向contentBounds定义内容世界的坐标范围配合placement如 bottom-right放置代码中还注释展示了decorations装饰contentBounds color title的用法。此外GimbalWidget用于显示并操控轨道视角的旋转姿态是三维信息可视化点云、模型浏览中的常用控件。场景三多视图分割Multiviewmultiview.ts 是_SplitterWidget的用法示范通过一个viewLayout描述树状视图布局——外层水平分割出左右两区右区再垂直分割为上、下两个MapView形成「左 右上 右下」的联动地图const VIEW_LAYOUT: SplitterWidgetProps[viewLayout] { orientation: horizontal, views: [ new MapView({id: left, controller: true}), { orientation: vertical, views: [ new MapView({id: right-top, controller: true}), new MapView({id: right-bottom, controller: true}) ] } ] }; new Deck({ initialViewState: INITIAL_VIEW_STATE, layers: LAYERS, widgets: [new SplitterWidget({viewLayout: VIEW_LAYOUT})] });SplitterWidget会自动从viewLayout编译出实际的 view 组合对应 view-layout/build-views-from-view-layout.ts 的实现并渲染可拖拽的分割条三个子视图复用同一份 GeoJSON/Arc 图层数据但可独立平移缩放适合多视角对比类应用如左右眼立体、上下游对比。Pure JS 与 React 双 API 对照三个场景均提供 React 版本react-geospatial.tsx、react-infovis.tsx、react-multiview.tsx二者差异体现在 API 形态上1. 导入来源不同。纯 JS 从deck.gl/core导入Deck从deck.gl/widgets导入控件React 版从deck.gl/react导入DeckGL与控件。这是因为 deck.gl/react 重新导出了 widgets 的全部控件并额外提供useWidgetHook 与各控件 props 类型。示例中 React 版还显式使用createRoot(...).render(App /)挂载应用。2. 声明方式不同。纯 JS 用new Deck({...})的widgets数组声明React 版将控件作为DeckGL的子组件 JSX 声明例如DeckGL views{new MapView({repeat: true})} initialViewState{INITIAL_VIEW_STATE} controller layers{layers} _GeocoderWidget geocodercoordinates _geolocation / ZoomWidget / CompassWidget / {/* ... */} /DeckGL3. 状态管理方式不同。React 版利用组件化优势管理动态状态react-geospatial.tsx用useState(() getLayers())持有图层_TimelineWidget的onTimeChange中调用setLayers(getLayers([2, time]))触发重渲染react-infovis.tsx用viewStateonViewStateChange受控管理双视图状态const onViewStateChange useCallback(({viewId, viewState}) { setViewState(curr ({...curr, [viewId]: viewState})); }, []);react-multiview.tsx则先用useState维护一个单一MapView配合SplitterWidget在交互后切换到完整布局。主题、样式与实验性控件说明所有 Demo 都在模块代码中引入了默认样式表import deck.gl/widgets/stylesheet.css;该样式表由 stylesheet.css 经 PostCSS 构建后发布为deck.gl/widgets/stylesheet.css见 modules/widgets/package.json 的 build 脚本。不引入它将导致控件无样式渲染。主题切换ThemeWidget支持明暗主题。默认使用内置主题也可如 infovis 示例所示显式传入自定义主题new ThemeWidget({darkModeTheme: DarkTheme, lightModeTheme: LightTheme})DarkTheme/LightTheme以及LightGlassTheme/DarkGlassTheme由 themes.ts 导出类型为DeckWidgetTheme开发者可据此自定义配色。下划线前缀实验性源码导出清单中_GeocoderWidget、_ScaleWidget、_SplitterWidget、_TimelineWidget、_StatsWidget等均带下划线前缀见 modules/widgets/src/index.ts 中export {X as _X}的写法表示其 API 尚未稳定、后续版本可能调整生产环境使用需谨慎。该清单还导出了_ButtonGroup、_IconButton、_Tooltip、_DropdownMenu、_RangeInput等基于 Preact 的实验性基础组件以及_GoogleGeocoder、_MapboxGeocoder、_OpenCageGeocoder、_CoordinatesGeocoder、_CurrentLocationGeocoder等实验性地理编码器源码位于 modules/widgets/src/lib/geocode。底层实现要点deck.gl/widgets 依赖preact与floating-ui/dom见 package.json前者负责控件 UI 渲染后者负责浮层Tooltip/Popup/Dropdown的定位计算其 peerDependencies 要求deck.gl/core与luma.gl/core版本匹配构建示例时需保证依赖版本一致。延伸阅读控件 API 完整参考docs/api-reference/widgets含 24 个控件文档其他官方示例本仓库 examples/website 下各展示页同样大量使用 widgetsReact 集成方式docs/get-started/using-with-react.md更多渲染回归测试本示例属于 test/apps 系列仓库另有widgets-layerlist等 widgets 相关测试应用可供参考【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进