news 2026/9/24 13:41:46

G6 鱼眼放大镜(Fisheye)插件完全指南:focus+context 交互式局部放大实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
G6 鱼眼放大镜(Fisheye)插件完全指南:focus+context 交互式局部放大实战

G6 鱼眼放大镜(Fisheye)插件完全指南:focus+context 交互式局部放大实战

【免费下载链接】G6♾ A Graph Visualization Framework in JavaScript.项目地址: https://gitcode.com/gh_mirrors/g6/G6

Fisheye 鱼眼放大镜是 G6 图可视化框架内置的交互插件,专为 focus+context(焦点+上下文)探索场景设计:它用一个可移动的圆形透镜放大关注区域内的节点,同时保持周边上下文以及上下文与关注中心的关系不丢失。本文基于 G6 官方文档与源码实现,系统讲解 Fisheye 插件的配置项、交互触发方式、畸变算法原理、样式定制与动态更新 API,并给出可直接运行的完整代码示例。读完本文,你将能够在自己的 G6 应用中一键接入鱼眼放大镜,并针对半径、畸变因子、外观样式和节点样式做精细化定制。

概述:为什么需要鱼眼放大镜

在大规模图可视化场景中,节点数量多、布局密集,直接放大画布往往导致查看区域外的重要上下文被裁剪。鱼眼放大镜(Fisheye)采用的是一种“非线性放大”策略:以透镜圆心为中心,对半径范围内的节点做距离相关的放大变换——越靠近中心放大倍数越大,越靠近边缘越接近原始尺寸,从而在突出关注区域的同时保留整体视图的连续感。

从源码注释(packages/g6/src/plugins/fisheye/index.ts)可以看到其设计目标:

Fisheye 鱼眼放大镜是为 focus+context 的探索场景设计的,它能够保证在放大关注区域的同时,保证上下文以及上下文与关注中心的关系不丢失。

典型使用场景

  • 演示与汇报:在演示过程中需要突出展示某些区域内容,引导观众视线聚焦;
  • 局部细节审查:需要局部放大查看密集区域的细节(如节点标签、连边走向)时,同时不想失去整体视图;
  • 大图探索:在包含大量节点和边的大型关系图中,通过透镜扫视不同区域,快速定位感兴趣的子图。

基本用法:一行配置接入鱼眼

Fisheye 是 G6 内置插件,无需单独安装扩展包,直接在Graphplugins数组中声明即可。最简单的配置方式只需要插件类型字符串:

const graph = new Graph({ plugins: ['fisheye'], });

带完整参数的最基本初始化示例:

const graph = new Graph({ plugins: [ { type: 'fisheye', trigger: 'drag', // 通过拖拽移动鱼眼 d: 1.5, // 设置畸变因子 r: 120, // 设置鱼眼半径 showDPercent: true, // 显示畸变程度 }, ], });

执行graph.render()后,将鼠标移动到画布上即可看到圆形透镜跟随出现,透镜内的节点被放大并重新分布。

配置项全解

Fisheye 插件的全部配置项定义在源码接口FisheyeOptions中(packages/g6/src/plugins/fisheye/index.ts),默认值见Fisheye.defaultOptions(同文件第 156-166 行)。下表为完整配置项说明:

属性描述类型默认值必选
type插件类型stringfisheye
key插件的唯一标识,可用于获取插件实例或更新插件选项string-
trigger控制鱼眼放大镜的移动方式,支持三种配置:pointermove(始终跟随鼠标移动)、click(点击画布时移动到点击位置)、drag(通过拖拽移动)pointermove|drag|clickpointermove
r鱼眼放大镜半径number120
maxR鱼眼放大镜可调整的最大半径(配合scaleRBy使用)number画布宽高的最小值的一半
minR鱼眼放大镜可调整的最小半径number0
d畸变因子number1.5
maxD鱼眼放大镜可调整的最大畸变因子number5
minD鱼眼放大镜可调整的最小畸变因子number0
scaleRBy调整鱼眼放大镜范围半径的方式:wheel(滚轮)或drag(拖拽)wheel|drag-
scaleDBy调整鱼眼放大镜畸变因子的方式:wheel(滚轮)或drag(拖拽)wheel|drag-
showDPercent是否在鱼眼放大镜中显示畸变因子数值booleantrue
style鱼眼放大镜(圆形透镜)样式,详见下文 style 小节object-
nodeStyle在鱼眼放大镜中的节点样式NodeStyle | ((datum: NodeData) => NodeStyle){ label: true }
preventDefault是否阻止默认事件booleantrue

关键参数详解

  • r(半径):透镜的物理作用范围,单位为画布坐标像素。源码中透镜实际渲染为一个圆形元素,其直径等于r * 2(见 renderLens)。默认值 120,适合中等画布;画布较大时可适当调大。
  • d(畸变因子):决定透镜内节点被拉伸的“强度”。d越大,靠近中心的节点被放得越大,边缘到中心的尺寸过渡越陡峭。默认值 1.5,取值区间建议在 0~5 之间(minD/maxD默认即为此范围)。
  • trigger:透镜移动方式。默认为pointermove,即透镜始终跟随鼠标;click模式适合触摸屏或需要精确落点的场景;drag模式适合在探索大图时“抓住”透镜拖动浏览。
  • nodeStyle:透镜内节点的渲染样式,默认{ label: true },即进入透镜的节点自动显示标签。它既可以是普通样式对象,也可以是接收NodeData返回样式的函数,便于按节点数据动态定制。
  • preventDefault:默认true,会阻止滚轮等事件触发的浏览器默认行为(如页面滚动),避免与透镜缩放操作冲突。

style:透镜圆形样式

style对应 G6 圆形元素(Circle)的样式属性CircleStyleProps,用于配置鱼眼放大镜本身的外观(填充、描边、透明度、阴影、线段端点等)。源码中的默认透镜样式(defaultLensStyle)为:

{ fill: '#ccc', // 填充颜色 fillOpacity: 0.1, // 填充透明度 lineWidth: 2, // 线宽 stroke: '#000', // 描边颜色 strokeOpacity: 0.8, // 描边透明度 labelFontSize: 12, // 畸变百分比标签字号 }

完整可配置属性如下:

属性描述类型默认值
fill填充颜色string | Pattern | null#ccc
stroke描边颜色string | Pattern | null#000
opacity整体透明度number | string-
fillOpacity填充透明度number | string0.1
strokeOpacity描边透明度number | string-
lineWidth线宽度number | string2
lineCap线段端点样式butt|round|square-
lineJoin线段连接处样式miter|round|bevel-
shadowColor阴影颜色string-
shadowBlur阴影模糊程度number-
shadowOffsetX阴影 X 方向偏移number-
shadowOffsetY阴影 Y 方向偏移number-

完整样式属性参考 元素 - 节点 - 内置节点 - 通用样式属性 - style。

交互缩放控制:半径与畸变因子的动态调整

通过scaleRByscaleDBy可以分别控制透镜半径与畸变因子的实时调整方式,支持滚轮(wheel)和拖拽(drag)两种手势。示例:

const graph = new Graph({ plugins: [ { type: 'fisheye', // 通过滚轮调整半径 scaleRBy: 'wheel', // 通过拖拽调整畸变因子 scaleDBy: 'drag', // 设置半径和畸变因子的范围 minR: 50, maxR: 200, minD: 1, maxD: 3, }, ], });

手势与优先级规则(重要)

triggerscaleRByscaleDBy三者可能同时使用拖拽(drag)手势时,同一手势只能绑定给一个配置项,优先级顺序为:

trigger>scaleRBy>scaleDBy

即如果三者都设为'drag',只会为trigger绑定拖拽事件,移动透镜优先;同理,如果scaleRByscaleDBy同时设为'wheel',只会为scaleRBy绑定滚轮事件,此时滚轮只调节半径。

这一优先级逻辑与源码 bindEvents 中的事件绑定分支完全一致:代码依次判断triggerscaleRByscaleDBy,三者都要求drag时按三目运算符链trigger === 'drag' ? this.onDrag : scaleRBy === 'drag' ? this.scaleRByDrag : this.scaleDByDrag取优先级最高者;滚轮同理取scaleRBy === 'wheel' ? this.scaleRByWheel : this.scaleDByWheel

调整步长与范围约束

源码定义了两个增量常量(index.ts):

const R_DELTA = 0.05; // 半径每次缩放的比例系数 const D_DELTA = 0.1; // 畸变因子每次调整的步长
  • 半径:滚轮上滚/拖拽正方向时按r / (1 - R_DELTA)放大,下滚时按r * (1 - R_DELTA)缩小,并始终被约束在[minR, maxR]内;maxR未设置时取画布宽高最小值的二分之一(见 scaleR)。
  • 畸变因子:每次+0.1-0.1,被约束在[minD, maxD]内(见 scaleD)。

代码示例:从基础到深度定制

基础用法

最简配置方式:

const graph = new Graph({ plugins: ['fisheye'], });

自定义样式:透镜外观 + 节点样式

可以同时定制透镜本身的外观,以及透镜内节点的样式(包括基础样式、标签、图标等):

const graph = new Graph({ plugins: [ { type: 'fisheye', r: 150, d: 2, style: { fill: '#2f54eb', // 鱼眼区域的填充颜色 fillOpacity: 0.2, // 填充区域的透明度 stroke: '#1d39c4', // 鱼眼边框的颜色 strokeOpacity: 0.8, // 边框的透明度 lineWidth: 1.5, // 边框的线宽 shadowColor: '#1d39c4', // 阴影颜色 shadowBlur: 10, // 阴影的模糊半径 shadowOffsetX: 0, // 阴影的水平偏移 shadowOffsetY: 0, // 阴影的垂直偏移 cursor: 'pointer', // 鼠标悬停时的指针样式 }, nodeStyle: { // 节点基础样式 size: 40, // 节点大小 fill: '#d6e4ff', // 节点填充颜色 stroke: '#2f54eb', // 节点边框颜色 lineWidth: 2, // 节点边框宽度 shadowColor: '#2f54eb', // 节点阴影颜色 shadowBlur: 5, // 节点阴影模糊半径 cursor: 'pointer', // 鼠标悬停时的指针样式 // 标签样式 label: true, // 是否显示标签 labelFontSize: 14, // 标签字体大小 labelFontWeight: 'bold', // 标签字体粗细 labelFill: '#1d39c4', // 标签文字颜色 labelBackground: true, // 是否显示标签背景 labelBackgroundFill: '#fff', // 标签背景填充颜色 labelBackgroundStroke: '#1d39c4', // 标签背景边框颜色 labelBackgroundOpacity: 0.8, // 标签背景透明度 labelBackgroundPadding: [4, 8, 4, 8], // 标签背景内边距 [上,右,下,左] // 图标样式 icon: true, // 是否显示图标 iconFontFamily: 'iconfont', // 图标字体 iconText: '\ue6f6', // 图标的 Unicode 编码 iconFill: '#1d39c4', // 图标颜色 iconSize: 16, // 图标大小 iconFontWeight: 'normal', // 图标字体粗细 }, }, ], });

官方文档中该示例对应的完整可运行版本(含 5 个节点、5 条边的图数据与graph.render()):

import { Graph } from '@antv/g6'; const graph = new Graph({ container: 'container', width: 400, height: 300, data: { nodes: [ { id: 'node-1', style: { x: 150, y: 100 } }, { id: 'node-2', style: { x: 250, y: 100 } }, { id: 'node-3', style: { x: 200, y: 180 } }, { id: 'node-4', style: { x: 120, y: 180 } }, { id: 'node-5', style: { x: 280, y: 180 } }, ], edges: [ { id: 'edge-1', source: 'node-1', target: 'node-2' }, { id: 'edge-2', source: 'node-1', target: 'node-3' }, { id: 'edge-3', source: 'node-2', target: 'node-3' }, { id: 'edge-4', source: 'node-3', target: 'node-4' }, { id: 'edge-5', source: 'node-3', target: 'node-5' }, ], }, node: { style: { size: 30, fill: '#e6f7ff', stroke: '#1890ff', lineWidth: 1, label: false, icon: false, }, }, edge: { style: { stroke: '#91d5ff', lineWidth: 1, }, }, plugins: [ { type: 'fisheye', key: 'fisheye', r: 100, d: 2, style: { fill: '#2f54eb', fillOpacity: 0.2, stroke: '#1d39c4', strokeOpacity: 0.8, lineWidth: 1.5, shadowColor: '#1d39c4', shadowBlur: 10, shadowOffsetX: 0, shadowOffsetY: 0, cursor: 'pointer', }, nodeStyle: { size: 40, fill: '#d6e4ff', stroke: '#2f54eb', lineWidth: 2, shadowColor: '#2f54eb', shadowBlur: 5, cursor: 'pointer', label: true, labelFontSize: 14, labelFontWeight: 'bold', labelFill: '#1d39c4', labelBackground: true, labelBackgroundFill: '#fff', labelBackgroundStroke: '#1d39c4', labelBackgroundOpacity: 0.8, labelBackgroundPadding: [4, 8, 4, 8], icon: true, iconFontFamily: 'iconfont', iconText: '\ue6f6', iconFill: '#1d39c4', iconSize: 16, iconFontWeight: 'normal', }, }, ], }); graph.render();

实际案例:在关系大图上使用鱼眼

官方文档提供了一个真实场景案例——基于relations.json关系数据,节点大小由id长度动态计算,使用分组调色板着色,并接入drag-canvas行为与鱼眼插件(透镜内显示标签和图标):

import { Graph, iconfont } from '@antv/g6'; const style = document.createElement('style'); style.innerHTML = `@import url('${iconfont.css}');`; document.head.appendChild(style); fetch('https://assets.antv.antgroup.com/g6/relations.json') .then((res) => res.json()) .then((data) => { const graph = new Graph({ container: 'container', autoFit: 'view', data, node: { style: { size: (datum) => datum.id.length * 2 + 10, label: false, labelText: (datum) => datum.id, labelBackground: true, icon: false, iconFontFamily: 'iconfont', iconText: '\ue6f6', iconFill: '#fff', }, palette: { type: 'group', field: (datum) => datum.id, color: ['#1783FF', '#00C9C9', '#F08F56', '#D580FF'], }, }, edge: { style: { stroke: '#e2e2e2', }, }, plugins: [{ key: 'fisheye', type: 'fisheye', nodeStyle: { label: true, icon: true } }], }); graph.render(); });

说明:iconfont是 G6 内置的图标字体资源,为节点图标提供字体定义;关系数据也可替换为仓库自带的 packages/g6/tests/dataset/relations.json 进行本地实验。

底层原理:鱼眼畸变算法与渲染流程

G6 的 Fisheye 插件本质是一个继承自BasePlugin的运行时插件类(export class Fisheye extends BasePlugin<FisheyeOptions>,见 packages/g6/src/plugins/fisheye/index.ts),并在 packages/g6/src/plugins/index.ts 统一导出注册。

透镜渲染

透镜是一个绘制在transient(瞬态)图层上的Circle圆形元素(index.ts):首次创建时new Circle({ style })appendChild到瞬态层,之后每次移动只更新位置(size: r * 2)与畸变百分比标签。这样透镜本身不会污染主画布的元素树,销毁时随插件destroy()一并清除。

畸变映射公式

renderFocusElements(index.ts)对半径r内的每个节点执行非线性映射:

分子 = (d + 1) * r 放大后的距离 = 分子 * 原始距离 / (d * 原始距离 + r) 新位置 = 圆心 + 放大后的距离 * (节点相对圆心的单位方向向量)
  • 当节点恰好在圆心(距离为 0)时,映射后仍在圆心;
  • 当节点在透镜边缘(距离 = r)时,放大后的距离 = r,即边缘节点保持原位,与透镜外上下文无缝衔接;
  • d越大,靠近圆心的节点被拉伸得越远,形成更明显的“放大镜”效果。

这也是鱼眼透镜能与外部上下文平滑过渡、不产生视觉断裂的根本原因。

样式更新与差异计算

updateStyle(index.ts)利用arrayDiff对比上一次与当前放大节点集合的enter / keep / exit差异:进入或保持的节点应用放大后的样式;退出的节点恢复为进入透镜前记录的原始样式(prevOriginStyleMap),随后更新这些节点关联的边,保证边端点与节点新位置同步,实现拖拽过程中的平滑过渡。

事件系统与手势绑定

事件绑定(bindEvents)遵循“同一手势只绑定一个功能”的原则:

  • triggerclick/drag时监听画布CLICK;为pointermove时监听POINTER_MOVE
  • 需要拖拽时监听DRAG_START/DRAG_END,并按优先级选择DRAG处理函数(移动/调半径/调畸变);
  • 滚轮缩放监听原生WHEEL事件并设置{ passive: false },以便preventDefault生效;
  • 滚轮/拖拽的有效性校验要求鼠标位置位于透镜半径r内(见isWheelValidisDragValid),避免误操作。

插件还实现了update(options)destroy()(index.ts):更新时先解绑旧事件、合并新配置、再重新绑定,运行时动态调整参数无需重建实例。

运行时动态更新与参数调试

Fisheye 插件配置了key后,即可通过 G6 的插件管理能力在运行时获取实例或更新参数:

const graph = new Graph({ plugins: [{ type: 'fisheye', key: 'fisheye' }], }); // 运行时更新:切换移动方式为 click,调整畸变因子 graph.updatePlugin({ key: 'fisheye', trigger: 'click', d: 2.5, }); graph.render(); // 获取插件实例(类型为 Fisheye) const fisheye = graph.getPluginInstance('fisheye');

官方文档配套的交互演示(packages/site/common/api/plugins/fisheye.md)正是利用graph.updatePlugin({ key: 'fisheye', [property]: value })配合 GUI 面板,实时调节triggerrdscaleRByscaleDByshowDPercentpreventDefault等参数,非常适合作为理解各配置项手感差异的调试入口。

测试验证与快照保障

仓库为 Fisheye 插件提供了完整的单元测试 packages/g6/tests/unit/plugins/fisheye.spec.ts,覆盖了:

  • pointermove/drag/click三种方式移动透镜的快照;
  • 滚轮与拖拽调节半径(scaleRBy)与畸变因子(scaleDBy)的增大/缩小行为;
  • showDPercent开关对畸变百分比标签的影响;
  • 自定义透镜样式(如虚线lineDash: [5, 5])与节点样式(如halo: true)的渲染效果。

测试通过dispatchCanvasEvent模拟鼠标与滚轮事件、通过toMatchSnapshot对比渲染结果,是验证插件行为、防止回归的重要参考。对应的 demo 入口为 packages/g6/tests/demos/plugin-fisheye.ts,可在本地pnpm dev环境下直接体验。

小结

Fisheye 鱼眼放大镜是 G6 内置插件中实现 focus+context 交互最直接的方案:通过trigger控制透镜移动方式,r/d控制放大范围与强度,scaleRBy/scaleDBy支持运行时交互微调,style/nodeStyle提供视觉定制,updatePlugin支持运行时动态改参。结合本文给出的完整代码示例与源码原理分析,你可以快速在关系图、知识图谱、组织架构等可视化场景中落地局部聚焦能力。

【免费下载链接】G6♾ A Graph Visualization Framework in JavaScript.项目地址: https://gitcode.com/gh_mirrors/g6/G6

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 13:38:58

Java实现PING程序:ICMP协议与原始套接字详解

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/24 13:34:51

3步跑通大麦自动抢票:Python+Selenium+Appium双端购票自动化完整攻略

3步跑通大麦自动抢票&#xff1a;PythonSeleniumAppium双端购票自动化完整攻略 【免费下载链接】ticket-purchase 大麦自动抢票&#xff0c;支持人员、城市、日期场次、价格选择 项目地址: https://gitcode.com/GitHub_Trending/ti/ticket-purchase 开票 30 秒就售罄&am…

作者头像 李华
网站建设 2026/9/24 13:34:37

1GW 机房:Anthropic 降本 40% 的底气?

模型的价格写在官网上,随时能改;算力的价格写在租约里,一签就是好几年。9 月 23 日,Anthropic 把这两件事同时摆上了台面。 模型那一侧:Opus 5.5 已在亚马逊云、谷歌云和微软 Azure 上线,官方称它在大多数任务上性能接近 Claude Fable 5.1,运行成本比 Opus 5 低 40%,定…

作者头像 李华