00.前言

“两点一线构成图”,相信这是大家都对图的一个基本认知。在我们的业务开发中,往往也有大量场景需要对图进行可视化,如下图所示,不同的领域中,图均发挥着关键作用领域关系图

如果你也需要对关系图进行可视化,不妨来试试 AntV 推出的图可视化工具 Graphin ~ 接下来让我们跟随这个在线教程 DEMO,来看看 Graphin 如何帮助我们完成一个图分析应用~

友情提示:本网站通过 dumi 生成,因此可以直接点击 demo 示例的右下角,展开源代码查看。

01.安装依赖

如果你是使用 React 的 Web 开发者,那么你大可将 Graphin 当作一个普通的 React 组件来使用。

本文采用 yarn 安装依赖,使用 npm 也可以。以下分别安装 Graphin 的核心组件@antv/graphin和 分析组件@antv/graphin-components,以及 Graphin 官方提供的图标库@antv/graphin-icons

yarn add @antv/graphin@latest --save
yarn add @antv/graphin-components@latest --save
yarn add @antv/graphin-icons --save

02.把关系数据可视化出来

完成一个图分析产品的第一步,就是将关系数据可视化出来。关系数据是非常典型的图结构,由节点 Node 和边 Edge 组成。Node 中只有 id 是必须参数,Edge 中只有 sourcetarget 是必须参数,它分别代表边的开始节点和结束节点的 id

关系数据
{
  "nodes": [
    {
      "id": "node-0",
      "x": 100,
      "y": 100
    },
    {
      "id": "node-1",
      "x": 200,
      "y": 200
    },
    {
      "id": "node-2",
      "x": 100,
      "y": 300
    }
  ],
  "edges": [
    {
      "source": "node-0",
      "target": "node-1"
    }
  ]
}
可视化结果

03.视觉通道映射

第一步我们完成了节点和边的渲染,但是它们的样式太过简单,如何将更多的信息呈现在节点和边上呢?这个时候可以利用视觉通道映射,除了常规的大小,形状,样式,Graphin 内置的节点和边 进行了规范。内置的节点 graphin-circle 由 5 部分图形组成,分别是 keyshape,label,icon,badges,halo,他们均放置在节点的style字段中,如下图node-0节点所示:

注意 ⚠️:这里为了演示效果,这里将全量配置文件展示出来,实际开发中,我们有Utils.genDefaultNodeStyle工具函数,帮助我们根据主题样式快速生成配置文件。同时,每个节点也仅携带自己需要的部分样式(Graphin 在内部 与 每个节点的样式 做 deepMerge)

关系数据

{
  nodes: [
    {
      id: 'node-0',
      x: 200,
      y: 240,
      style: {
        // 节点的主要形状,即圆形容器,可以在这里设置节点的大小,border,填充色
        keyshape: {
          /** 容器的宽度 */
          lineWidth: 3,
          /** 节点的大小 */
          size: 80,
          /** 包围边颜色 */
          stroke: parimaryColor,
          /** 填充色 */
          fill: Utils.hexToRgbaToHex(parimaryColor, 0.2),
          /** 透明度 */
          opacity: 1,
          /** 鼠标样式 */
          cursor: 'pointer',
        },
        // 是节点的标签,可以设置标签的值 和样式:放置方位,大小,字体颜色,偏移位置
        label: {
          /** label的名称 */
          value: '节点-0',
          /** 展示位置  'top' | 'bottom' | 'left' | 'right' | 'center'; */
          position: 'top',
          /** 文本填充色 */
          fill: '#000',
          /** 文本大小 */
          fontSize: 16,
          fontFamily: 'normal',
          textAlign: 'center',
          /** 文本在各自方向上的偏移量,主要为了便于调整文本位置 */
          offset: 0,
        },
        // 是节点的中心 ICON 区域,icon 可以是图片,可以是文本,也可以是字体图标。
        icon: {
          /** 类型可以为字体图标,可以为网络图片,可以为纯文本 'font' | 'image' | 'text' */
          type: 'font',
          /** 根据类型,填写对应的值 */
          value: icons.user,
          /** 图标大小 */
          size: 40,
          /** 字体图标的填充色 */
          fill: parimaryColor,
          /** 字体Family */
          fontFamily: 'graphin',
        },
        // 节点的徽标区域,是一个数组,可以分别在不同方位放置,其内容区域可以是文本,数字,也可以是图标。
        badges: [
          {
            /** 放置的位置,ef:LT(left top)左上角 */
            position: 'RT',
            /** 类型可以为字体图标,可以为网络图片,可以为纯文本 */
            type: 'text',
            value: '10',
            // type = image 时生效,表示图片的宽度和高度
            size: [25, 25],
            /** 徽标填充色 */
            fill: Utils.hexToRgbaToHex(parimaryColor, 1),
            /** 徽标描边色 */
            stroke: '',
            /** 徽标内文本的颜色 */
            color: '#fff',
            fontSize: 14,
            fontFamily: '',
            // badge 中文本距离四周的偏移量
            padding: 0,
            // badge 在 x 和 y 方向上的偏移量
            offset: [0, 0],
          },
          {
            /** 放置的位置,ef:LT(left top)左上角 */
            position: 'LB',
            /** 类型可以为字体图标,可以为网络图片,可以为纯文本 */
            type: 'text',
            value: 'Pin',
            // type = image 时生效,表示图片的宽度和高度
            size: [25, 25],
            /** 徽标填充色 */
            fill: Utils.hexToRgbaToHex(parimaryColor, 1),
            /** 徽标描边色 */
            stroke: '',
            /** 徽标内文本的颜色 */
            color: '#fff',
            fontSize: 12,
            fontFamily: '',
            // badge 中文本距离四周的偏移量
            padding: 0,
            // badge 在 x 和 y 方向上的偏移量
            offset: [0, 0],
          },
        ],
        // 节点的光环,在节点交互过程中(hover,selected,disabled,active)等,可以打开光环,默认是隐藏的,也可以自定义
        halo: {
          /** 光晕 */
          fill: Utils.hexToRgbaToHex(parimaryColor, 0.2),
          /** 透明度 */
          opacity: 1,
          /** 是否展示 */
          visible: false,
          /** 鼠标Hover上去样式 */
          cursor: 'pointer',
        },
        // 节点的交互样式,默认 Graphin 已经内置了交互样式(即将 halo 图形显示出来),也可以自定义
        status: {
          hover: {
            halo: {
              visible: true,
            },
          },
          selected: {
            halo: {
              visible: true,
            },
            keyshape: {
              lineWidth: 10,
            },
          },
        },
      },
    },
  ],
  edges: [],
}
  
  
可视化结果

让我们继续我们的案例,现在 node-0 是一个用户,node-1 是一家企业,node-2 是另一家企业。通过上述 Graphin 内置的节点,重新整理,我们可以用节点的大小来代表企业的规模,节点的 icon 来展示不同的属性,颜色加以区分。

  • 1.先给每个节点加上标签
  • 2.将企业的规模data.count映射为节点的大小
  • 3.将不同的数据类型data.type,映射为节点的 icon
  • 4.节点-0节点-2 的背后实际控制人,可以映射为虚线边
关系数据
{
  "nodes": [
    {
      "id": "node-0",
      "x": 100,
      "y": 100,
      "data": {
        "type": "user"
      },
      "style": {
        "label": {
          "value": "node-0"
        },
        "keyshape": {
          "size": 30,
          "stroke": "#FF6A00",
          "fill": "#FF6A00",
          "fillOpacity": 0.2,
          "strokeOpacity": 1
        },
        "icon": {
          "type": "font",
          "value": "",
          "size": 15,
          "fill": "#FF6A00",
          "fontFamily": "graphin"
        }
      }
    },
    {
      "id": "node-1",
      "x": 200,
      "y": 200,
      "data": {
        "type": "company",
        "count": 300
      },
      "style": {
        "label": {
          "value": "node-1"
        },
        "keyshape": {
          "size": 60,
          "stroke": "#46a7a6",
          "fill": "#46a7a6",
          "fillOpacity": 0.2,
          "strokeOpacity": 1
        },
        "icon": {
          "type": "font",
          "value": "",
          "size": 30,
          "fill": "#46a7a6",
          "fontFamily": "graphin"
        }
      }
    },
    {
      "id": "node-2",
      "x": 100,
      "y": 300,
      "data": {
        "type": "company",
        "count": 200
      },
      "style": {
        "label": {
          "value": "node-2"
        },
        "keyshape": {
          "size": 40,
          "stroke": "#46a7a6",
          "fill": "#46a7a6",
          "fillOpacity": 0.2,
          "strokeOpacity": 1
        },
        "icon": {
          "type": "font",
          "value": "",
          "size": 20,
          "fill": "#46a7a6",
          "fontFamily": "graphin"
        }
      }
    }
  ],
  "edges": [
    {
      "source": "node-0",
      "target": "node-1"
    },
    {
      "source": "node-0",
      "target": "node-2",
      "style": {
        "keyshape": {
          "lineDash": [
            2,
            2
          ],
          "stroke": "#FF6A00"
        },
        "label": {
          "value": "实际控制人",
          "fill": "#FF6A00"
        }
      }
    }
  ]
}
可视化结果

04.选择合适的布局

在图可视化中,最富魅力的就是布局了。目前为止,我们接触到的第一个布局是preset,它的作用是将用户指定的 x,y 位置信息 渲染到画布上。想象下,上述的例子,真实场景下,数据是由服务端返回的。数据量未知,每个数据都不带布局信息,因此我们再也不能使用preset预设布局了。 那这个时候,合适的布局算法就显得尤为重要。 让我们用 Graphin 内置的工具函数Utils.mock 模拟一些数据,看看不同布局算法下,图可视化的展示效果

同心圆布局算法:Concentric
有向分层算法:Dagre

可以看出,同心圆算法布局下的节点,可以清晰看到中心节点,默认它是根据节点的度排序的。在一些团伙欺诈网络中,一眼就能看到中间的关键主犯。有向分层算法下的节点,根据边的流向依次分层排开,在一些有明显流向方向的网络中,比如资金流向网络,就可以清晰看出上下游关系。

但是在一些通用平台中,关系数据并不是一开始就做确定好用什么布局算法。这个时候,最方便的做法就是提供布局切换能力,让用户自主选择。当然基于 AI 的能力,我们根据数据打标,模型训练,可以预测出最佳布局,这部分能力正在建设中

布局切换
 graphin-force
布局预测
还在开发中...

05.好的交互,可以增强用户分析体验

数据也渲染了,也能正确布局了,这个时候,产品经理来找你,提出了一下诉求

  • 画布要支持缩放,不然数据量大的时候,用户都看不全图
  • 点击节点,要有反馈,最好节点要做一些“细腻”的样式变化
  • 节点 Hover 上去的时候,最好把它关联的节点和边高亮,这样可以减少视觉干扰~
  • 要能够批量选择节点,看能不能支持鼠标圈选,或者像 PS 里面魔法棒那样,自己画个区域选择
  • ...(此处省略 100+优化建议)

最后,她再夸奖你一句,你这么棒,一定可以把这个图体验做好的~ 此刻的你,压抑住心中的冲动,真的是产品一张嘴,开发熬肝夜。

确实,好的交互,可以增强用户的分析体验,从这个角度出发,产品经理是对的。Graphin 也是从业务线打磨产出的技术产品,因此我们将交互解构出来,封装成组件,让用户按需引入。

import { Behaviors } from '@antv/graphin';
const {
TreeCollapse, // 树图的展开收起
DragCanvas, // 拖拽画布
ZoomCanvas, //缩放画布
ClickSelect, // 点击选中节点
BrushSelect, //圈选操作
DragNode, // 拖拽节点
ResizeCanvas, // 自动调整画布宽高
LassoSelect, // 拉索操作
DragCombo, // 拖拽Combo
ActivateRelations, // 关联高亮
Hoverable, // Hover操作
} = Behaviors;
网图的 Hover 关联高亮 <ActivateRelations />
树图的 Click 展开收起 <TreeCollapse />

06.好的分析组件,可以提高用户分析效率

随着业务的深入,用户的分析诉求越来越多,简单的画布和图元素操作已经不能满足用户的分析诉求了:

  • 比如对于节点的操作,不仅限于点击节点。还需要对节点进行打标,数据下钻,删除,反选 等。这个时候,就需要一个右键菜单组件。
  • 比如数据分类,需要用轮廓组件或者图例标示出来

Graphin 根据《AntV 图可视分析解决方案》白皮书里的指导,总结整理了 26 款分析组件。

组件梳理Xmind
components-xmind
右键菜单 + 鱼眼放大镜

07.沉淀的这些产品方案,或许你可以用的上

如果你耐着性子看到这里,那么我们可以一起聊聊业务。图可视分析在不同的业务领域应用,其实有一些点是共通的。比如,在知识图谱和金融风控中,分别有一个核心产品功能,前者是 知识推理,后者是风险探查,因为在这些场景下,主要利用的是下钻的数据分析方式,图数据的下钻,特殊点就在于这不仅是一个数据的动态过程,也是一个布局的动态过程。动态图的探索问题,是 Graphin 团队解决的第一个业务痛点问题,如今我们将这一问题抽象为数据驱动 + 渐进布局两个技术方案的组合,希望能对大家的业务产生一些帮助。

无动画扩散
有动画扩散

除了动态图布局,大图探索也是业务中常见的需求。采用 louvain 算法聚合 ,利用 Combo 能力或者节点聚合展示方式,再结合 MiniMap 小地图导航,鱼眼放大镜,可以初步满足大图的探索需求。这块DEMO 可以参考

以上便是我们的快速开始的全部内容:通过零碎的小 DEMO,我们基本上已经看到了 Graphin 的一个基本能力面貌:

  • 支持 树图 和 网图 两种不同数据结构的渲染。
  • 内置多种布局,支持子图布局,增量布局,布局切换。
  • 节点和边 存在组合规范,支持样式自定义。
  • 支持交互行为组合引入:目前已经完成常见的 9 种交互行为,满足日常我们交互需求。
  • 内置丰富的分析组件,目前已经完成用户常用的 6 种组件:右键菜单 ContextMenu,提示框 Tooltip,小地图导航 MiniMap,图例组件 Legend,鱼眼放大镜 FishEye,轮廓组件 Hull。全部有 26 个组件。
  • 从业务种沉淀产品功能,例如动态图探索, 大图探索。

如果你还感兴趣,可以继续阅读深入探索部分,将为大家介绍 Graphin 的扩展机制 和 组件的自定义机制,以及我们接下来重点要做的 GraphinStudio。