自定义工具栏与跨层提示框
这个示例展示如何关闭 relation-graph 内置工具栏,并在 `RGSlotOnView` 中放置自定义悬浮工具栏。它还演示了跨层悬浮提示系统:覆盖工具栏按钮、view 插槽锚点、canvas 插槽锚点、节点插槽内容以及图渲染的节点/连线,依靠递归 tooltip 发现与图命中检测实现。
构建带跨层提示框的自定义工具栏
这个示例构建了什么
这个示例构建了一个只读的 relation-graph 场景:它用一个悬浮的自定义工具栏替代内置工具栏,并让同一套悬停系统贯穿多个图层表面。用户会看到一个小型示例图、一组浅色的纵向工具栏、位于 view 层的四个提示锚点、位于 canvas 层的另外两个锚点、自定义节点内容,以及当指针在这些元素之间移动时出现的深色提示卡片。
重点并不只是“带英文提示的工具栏图标”。同一个包装层级的悬停流程还覆盖了自定义节点插槽中的一个徽标,并且当指针不再位于自定义 HTML 上时,会回退到图中渲染的节点和连线。这让该演示成为一个紧凑的参考案例,适合那些需要在同一场景中混合 overlay DOM 与原生图对象的产品。
数据如何组织
图数据在 initializeGraph() 中以内联方式组装为一个静态 RGJsonData 对象,其中包含 rootId: 'a'、四个节点和七条连线。节点记录本身已经带有视觉覆盖配置,包括 color、fontColor、nodeShape、width 和 height。连线记录也编码了同一对节点之间的重复边、逐线的 lineShape 覆盖、自定义 fromJunctionPoint,以及一条设置了 showEndArrow: false 的连线。
在调用 setJsonData() 之前没有预处理流水线。组件直接在代码中构造最终数据集,并按原样加载,然后重新居中并让视口适配内容。在真实应用里,这种结构同样可以表示服务与依赖、人员与汇报关系,或者设备与网络路由,而逐节点和逐连线的覆盖配置则可以来自类型、状态或路由元数据。
relation-graph 的使用方式
index.tsx 用 RGProvider 包裹整个页面,MyGraph.tsx 和 MyToolbar.tsx 都通过 RGHooks 访问 relation-graph 的状态和 API。该示例没有设置显式布局选项,因此针对这个小数据集,图会使用 relation-graph 的默认布局行为。选项配置反而主要聚焦在展示和工具栏替换上:lineTextMaxLength、multiLineDistance、defaultLineTextOnPath、defaultLineShape 以及 showToolBar: false。
挂载后,图实例通过 setJsonData(...) 加载内联 JSON,然后调用 moveToCenter() 和 zoomToFit()。从这里开始,图实例成为整个交互模型的核心。MyGraph.tsx 使用 getViewBoundingClientRect() 把悬停的 HTML 元素转换为相对于图视图的提示位置,同时使用 isLine(...)、isNode(...) 和 getViewXyByEvent(...),以便在图中渲染对象上也能保持悬停反馈。MyToolbar.tsx 则使用同一个实例调用 fullscreen()、zoom(...)、setZoom(100)、moveToCenter() 和 zoomToFit()。
三个插槽层承载了自定义 UI。RGSlotOnView 放置悬浮工具栏和四个方向性提示锚点。RGSlotOnCanvas 直接在 canvas 上方添加更多支持提示的 HTML。RGSlotOnNode 替换节点内容,并在节点 e 内插入一个支持提示的徽标。这里没有编辑流程:该图是一个带实用控制与悬停检查能力的查看器。本地 SCSS 也主要聚焦于 .c-tips 中的深色悬停提示样式,以及 .my-toolbar-button 中的白色工具栏按钮样式。
关键交互
- 当指针移动到任何带有
data-tooltip的元素上时,会根据data-tooltipposition打开一个深色提示卡片。 - 这套悬停查找机制同时适用于工具栏按钮、view 插槽中的区块、canvas 插槽中的区块,以及渲染在自定义节点插槽中的徽标。
- 如果找不到支持提示的 HTML 祖先元素,同一个
onMouseMove处理器会回退到isLine(...)和isNode(...),因此图中渲染的连线和节点仍然会显示一个小型悬停提示。 - 点击工具栏按钮会通过图实例或本地点击回调触发全屏、缩放步进、适配视图和个人资料相关操作。
- 下载按钮作为界面装饰存在,但它的处理函数只是一个占位注释,并不会导出图片。
关键代码片段
这个递归辅助函数说明,提示发现机制依赖的是 DOM 祖先链、分配的插槽以及 Shadow DOM 的宿主边界,而不是某一个特定图层。
const getTooltip = (el: HTMLElement | null): HTMLElement | null => {
if (!el) return null;
if (el.dataset.tooltip) return el;
if (el.classList.contains('my-graph')) return null;
let nextParent: HTMLElement | null = null;
if (el.assignedSlot) {
nextParent = el.assignedSlot as HTMLElement;
} else {
nextParent = el.parentElement;
}
这个选项块表明,relation-graph 的内置工具栏被有意禁用,而重复连线的间距和标签渲染则在图级别统一控制。
const graphOptions: RGOptions = {
lineTextMaxLength: 6,
multiLineDistance: 20,
defaultLineTextOnPath: true,
defaultLineShape: RGLineShape.StandardCurve,
showToolBar: false
};
这个数据片段表明,在数据加载进图之前,演示数据本身就已经携带了节点样式、重复链接以及逐线的路径覆盖配置。
const myJsonData: RGJsonData = {
rootId: 'a',
nodes: [
{ id: 'a', text: 'A' },
{ id: 'b', text: 'B', color: '#43a2f1', fontColor: 'yellow' },
{ id: 'c', text: 'C', nodeShape: RGNodeShape.rect, width: 120, height: 80 },
{ id: 'e', text: 'E', nodeShape: RGNodeShape.circle, width: 150, height: 150 }
],
lines: [
{ id: 'l1', from: 'a', to: 'b', text: 'text a -> b', color: '#43a2f1' },
{ id: 'l10', from: 'a', to: 'e', text: 'text a -> e', lineShape: RGLineShape.Curve7 }
]
};
这个 mouse-move 分支证明,HTML 提示锚点会相对于图视图进行测量,并转换成可复用的提示状态。
const tooltipElement = getTooltip(eventTaregtElement);
if (tooltipElement) {
const tooltipText = tooltipElement.dataset.tooltip || '';
const tooltipPostion = tooltipElement.dataset.tooltipposition || 'left';
const tooltipElementRect = tooltipElement.getBoundingClientRect();
const viewRect = graphInstance.getViewBoundingClientRect();
setTooltipInfo({
overObjectType: 'element',
overObject: {
text: tooltipText,
width: tooltipElementRect.width,
height: tooltipElement.height,
这个回退分支展示了当指针不位于自定义 HTML 上时,同一套悬停流程如何仍然支持图中渲染的连线和节点。
const line = graphInstance.isLine(eventTaregtElement);
if (line) {
const basePositionXy = graphInstance.options.fullscreen
? { x: 0, y: 0 }
: graphInstance.getViewXyByEvent($event.nativeEvent);
setTooltipInfo({
overObjectType: 'line',
overObject: line,
position: {
x: basePositionXy.x + 10,
y: basePositionXy.y + 10
}
});
}
这个插槽组合是 relation-graph 的核心模式:一个查看器场景在同一个图实例中同时组合了自定义 view 覆盖层、canvas 覆盖层和节点内容。
<RGSlotOnView>
<MyToolbar onUserAvatarClick={onUserAvatarClick} />
<div data-tooltipposition="top" data-tooltip="Top Tooltip Content">Top</div>
</RGSlotOnView>
<RGSlotOnCanvas>
<div data-tooltipposition="top" data-tooltip="Canvas Slot Content 1">
Canvas Slot Content 1
</div>
</RGSlotOnCanvas>
<RGSlotOnNode>
这个工具栏片段证明,视口控制是通过普通的自定义按钮实现的,这些按钮会直接调用 relation-graph 实例 API。
const zoomToFit = async () => {
graphInstance.setZoom(100);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
};
const addZoom = (buff: number) => {
graphInstance.zoom(buff);
};
这个示例的独特之处
对比数据表明,这个示例的突出点在于:它用同一套提示流程贯穿了 RGSlotOnView、RGSlotOnCanvas、RGSlotOnNode、自定义工具栏按钮,以及图中渲染的节点或连线。这比只针对节点的提示方案覆盖范围更广,也比只定制工具栏的示例更强调交互。
与 toolbar-buttons 相比,这里的重点不是复用 RGToolBar 或 RGMiniToolBar 容器。工具栏被刻意做得很朴素,而大部分实现精力都投入在悬停发现、方向定位,以及从 DOM 锚点回退到图命中检测上。与 node-line-tips-contentmenu 相比,这个示例的命令能力更轻,但覆盖的表面更广:它没有加入右键菜单,却展示了 HTML 覆盖层和图对象如何共享一条悬停管线。与 node-tips 相比,它并不局限于节点悬停卡片,因为它还覆盖了工具栏按钮、canvas 插槽内容以及连线悬停提示。
这种组合使它成为一个很强的起点,适合那些需要让悬停帮助在混合图层之间保持一致、同时又不引入编辑逻辑或为每个表面分别实现提示系统的团队。
这种模式还适用于哪里
这种模式非常适合图形化管理控制台、调查工具、架构地图以及监控仪表盘,这类产品通常会把图原生对象与自定义覆盖控件混合使用。它尤其适用于需求是“所有地方都使用同一种提示语言”,而不是只做一个专门的节点检查器时。
同样的结构还可以扩展出更丰富的提示内容、悬停延时、可固定的详情卡片、挂在占位下载按钮后的导出操作,或面向视口的边界钳制逻辑。它也可以在保持图本身处于查看模式的同时,为图周围增加带品牌风格的实用界面元素。