可视化—gojs 超多超实用经验分享(一)

科技资讯 投稿 7500 0 评论

可视化—gojs 超多超实用经验分享(一)

目录
    1. 设置分组模板,默认样式,统一最小宽度,展开收起状态监听
  • 2. 分组名称显示成员个数: 分组名称+成员个数: name(children
  • 3. 分组成员为空时,不显示 placeholder 占位留白
  • 4. 分组第一次展开请求获取成员接口,监听展开收起状态 subGraphExpandedChanged:fn
  • 5. 设置节点模板
  • 6. 设置节点的 icon,此处以文字为例
  • 7. 文案太长截取显示...,鼠标悬浮显示全部
  • 8. 设置鼠标移入、移出状态,点击节点高亮相关联节点
  • 9. 动态追加节点
  • 10. 节点众多需要不同表现样式时,可定义不同的节点模板
  • 11. 每个节点前后追加 input/output spot 的两种方式
  • 12. 设置连线模板,箭头样式,连线上添加关系文字
  • 13. 动态追加连线 addLinkData(
  • 14. 点击节点使其相关联节点与连线及文字高亮显示
  • 15. 点击画布清空当前高亮状态元素
  • 16. 删除连线的方法
  • 17. 动态根据需求 展开和收起某一个或全部的分组
  • 18. 关闭画布重新渲染时的动画
  • 19. 画布无限拖拽功能
  • 20. 图禁止复制,禁止删除,开启鼠标滚轮缩放,toolTip 触发 hoverDelay 初始定位
  • 21. 消除水印
  • 更多分享 详见下一篇博文

按照可自定义绘图的程度排序: gojs、d3js > antv > echarts 、highcharts

如果有一定程度需要自定义图元素,那么可以看 antv g2/g6 demo 是否能满足需求,可自定义大部分图元素。

gojs 是一个用于构建交互式可视化图的 js 库,使用可自定义的模板和布局构建复杂节点、链接和组,从而构建出简单到复杂的各类图,如流程图、脑图、组织图、甘特图等。而且提供了许多用于用户交互的高级功能,例如拖放、复制和粘贴、就地文本编辑.....。

本文是关于如何使用可视化库 gojs 的介绍及使用时的小技巧。gojs 的高可自定义性,非常适合需求复杂的图交互。

绘制基本流程简单介绍,

    引入库,可以下载,也可以引入 cdn 地址
  • html 文件创建画布容器,并设置宽高
  • 创建实例,定义布局,样式,交互,属性,事件等
  • 绑定节点和连线数据,渲染图表

<template
  >>
  <div>
    <div id="myDiagramDiv" style="height: 1000px;"></div>
  </div>
</template>
<script lang="ts" setup>
  import go from "@/assets/js/go";
  import { onMounted } from "vue";

  let diagram: any = null;
  const $ = go.GraphObject.make;
  onMounted(( => {
    init(;
  };

  function init( {
    // 创建diagram实例,
    diagram = $(go.Diagram, "myDiagramDiv";

    // 分组模板
    diagram.groupTemplate = $(go.Group, "Auto", {
      /* options 后期主要学习部分 */
    };

    // 连线模板
    diagram.linkTemplate = $(go.Link, {
      /* options 后期主要学习部分 */
    };

    // 节点模板
    diagram.nodeTemplate = $(go.Node, "Auto", {
      /* options 后期主要学习部分 */
    };

    // 绘制节点模板 追加新的 自定义的模板类型
    diagram.nodeTemplateMap.add(;

    diagram.layout = $(go.LayeredDigraphLayout, {
      direction: 0, // 布局方向,0 水平 90 垂直
      layerSpacing: 120, // 节点间隔
      isOngoing: false,
    };

    var nodeDataArray = []; // 节点集合
    var linkDataArray = []; // 分组集合
    diagram.model = new go.GraphLinksModel(nodeDataArray, linkDataArray;
  }
</script>

1. 设置分组模板,默认样式,统一最小宽度,展开收起状态监听

// 分组模板
diagram.groupTemplate = $(
  go.Group,
  "Auto",
  {
    layout: $(go.LayeredDigraphLayout, { direction: 0, columnSpacing: 5 },
    isSubGraphExpanded: false, // 默认展开 true 、折叠false.
    subGraphExpandedChanged: function (group {}, //  功能 小 ## 4
  },
  $(
    go.Shape,
    "RoundedRectangle", // 分组形状,圆角矩形
    { parameter1: 5, opacity: 0.7, minSize: new go.Size(120, NaN }, // 圆角  透明度  统一最小宽度
    new go.Binding("fill", "color", // 绑定填充色,如果是固定颜色,可以直接在上述对象中,填写对应的属性,如 fill:"#ccc"
    new go.Binding("stroke", "color" // 绑定描边色,同上
  ,
  $(
    go.Panel,
    "Vertical",
    { defaultAlignment: go.Spot.Left, margin: 4 },
    $(
      go.Panel,
      "Horizontal",
      { defaultAlignment: go.Spot.Top, margin: 4, padding: new go.Margin(5, 5, 5, 2 },
      // 设置收缩按钮,用于展开折叠子图  +/-
      $("SubGraphExpanderButton", { padding: new go.Margin(0, 5, 0, 0 },
      $(
        go.TextBlock,
        { font: "bold 12px Sans-Serif", stroke: "white" },
        new go.Binding("text", "", (node => node.key + `(${node.children}` // 分组名称+成员个数:  name(children
      
    ,
    // 分组展开后的 面板占位
    $(
      go.Placeholder,
      { background: "white" },
      new go.Binding("padding", "", function (node {
        // 每组背景色和边距
        return node.children ? new go.Margin(10, 10 : new go.Margin(0, 10;
      }
    
  
;

2. 分组名称显示成员个数: 分组名称+成员个数: name(children

$(
  go.TextBlock,
  { font: "bold 12px Sans-Serif", stroke: "white" },
  new go.Binding("text", "", (node => node.key + `(${node.children}` // 分组名称+成员个数:  name(children
;

3. 分组成员为空时,不显示 placeholder 占位留白

// 分组展开后的 面板占位
$(
  go.Placeholder,
  { background: "white" },
  new go.Binding("padding", "", function (node {
    // 每组背景色和边距
    return node.children ? new go.Margin(10, 10 : new go.Margin(0, 10;
  }
;

4. 分组第一次展开请求获取成员接口,监听展开收起状态 subGraphExpandedChanged:fn

subGraphExpandedChanged: function (group {
  // 子图展开或收起的状态 group.isSubGraphExpanded
  var groupData = group.part.data; // 获取分组 数据
  if (!groupData.isRequested {
    // 设置一个标识位,标明该分组数据是否 有请求过接口,
    // 未请求过,可以编写请求接口代码,或者添加已知节点代码,
    groupData.isRequested = true;
    diagram.model.addNodeData({
      key: "任意要显示的node节点名称",
      group: groupData.key,
      color: groupData.color,
      icon: groupData.icon,
    };
    // diagram.model.addLinkData({ from: "", to: "" }  // 新增节点 有连线关系则添加连线对象
    diagram.animationManager.stopAnimation(; // 取消动画
  }
  // 请求过 就直接 展开或收起分组 isOngoing 属性true会自动布局,但是会影响用户拖拽效果,因此分组自行布局后,需要在改为false
  diagram.layout.isOngoing = true;
  setTimeout(( => {
    diagram.layout.isOngoing = false;
  };
}

5. 设置节点模板

// 节点模板
diagram.nodeTemplate = $(
  go.Node,
  "Auto",
  {
    mouseEnter: mouseEnter,
    mouseLeave: mouseLeave,
    click: nodeclick,
  },
  $(
    go.Shape,
    "Rectangle",
    { strokeWidth: 1, stroke: "white", fill: "white" },
    new go.Binding("stroke", "isHighlighted", (sel => (sel ? "#1E90FF" : "white".ofObject(, // 鼠标选中高亮样式
    new go.Binding("strokeWidth", "isHighlighted", (sel => (sel ? 3 : 1.ofObject(
  ,
  $(
    go.Panel,
    "Horizontal",
    { width: 280, padding: new go.Margin(5, 5, 5, 2 },
    $(
      go.TextBlock, // 设置 icon
      { font: "bold 12px Sans-Serif", stroke: "white", width: 24, textAlign: "center" },
      new go.Binding("text", "icon", // 绑定icon 图表文案
      new go.Binding("background", "color" // 绑定 背景色
    ,
    $(
      go.TextBlock,
      {
        margin: 5,
        width: 240,
        font: "15px Verdana",
        stroke: "#444",
        maxSize: new go.Size(260, NaN,
        maxLines: 1,
        overflow: go.TextBlock.OverflowEllipsis, // maxSize maxLines overflow 属性联合使用,用于文案截断 显示...
        name: "TEXT", // 命名随意,用于后期 鼠标状态事件,节点成员的获取
      },
      new go.Binding("text", "key"
    ,
    {
      toolTip: $(
        // 鼠标悬浮显示全部文案,默认触发时间比较长,可以通过属性来修改
        "ToolTip",
        $(go.TextBlock, { margin: 4 }, new go.Binding("text", "key"
      ,
    }
  
;

6. 设置节点的 icon,此处以文字为例

$(
  go.TextBlock, // 设置 icon
  { font: "bold 12px Sans-Serif", stroke: "white", width: 24, textAlign: "center" },
  new go.Binding("text", "icon", // 绑定icon 图表文案
  new go.Binding("background", "color" // 绑定 背景色
,

7. 文案太长截取显示...,鼠标悬浮显示全部

{
  toolTip: $(
    // 鼠标悬浮显示全部文案,默认触发时间比较长,可以通过属性来修改
    "ToolTip",
    $(go.TextBlock, { margin: 4 }, new go.Binding("text", "key"
  ;
}

8. 设置鼠标移入、移出状态,点击节点高亮相关联节点

function mouseEnter(e, obj {
  var text = obj.findObject("TEXT";
  text.stroke = "#1E90FF";
}

function mouseLeave(e, obj {
  var text = obj.findObject("TEXT";
  text.stroke = "#444";
}

function nodeclick(e, node {
  var diagram = node.diagram;
  diagram.clearHighlighteds(;
  node.findNodesConnected(.each(function (l {
    l.isHighlighted = true;
  };
  node.linksConnected.each(function (n {
    n.isHighlighted = true;
  };
}

9. 动态追加节点

diagram.model.addNodeData({
  key: "任意要显示的node节点名称",
  group: "分组名",
  color: "节点背景颜色",
  icon: "icon文字",
};

10. 节点众多需要不同表现样式时,可定义不同的节点模板

    仅有一种不同形式时,可使用 diagram.nodeTemplateMap.add(node调用不同的节点模板
  • 多种不同模板时,是封装一个方法,生成模板,在调用diagram.nodeTemplateMap.set(typename, node

11. 每个节点前后追加 input/output spot 的两种方式

方法一: 该方法需要重点关注的方法是 makePort,函数调用位置及返回值

var nodeDataArray = [{ key: "A", category: "Start", text: "节点设置左右连接点" }];

var linkDataArray = [
  { from: "A", to: "B", frompid: "1", topid: "1" }, // createPort方法  portId
  { from: "B", to: "C", frompid: "2", topid: "2" },
];

diagram.model.linkFromPortIdProperty = "frompid"; //  连接点对应名称
diagram.model.linkToPortIdProperty = "topid";

diagram.nodeTemplateMap.add(
  "Start", // nodeDataArray 中的 category
  $(
    go.Node,
    "Auto",
    { width: 260, height: 80 },
    $(go.Shape, "Rectangle", { fill: "white", stroke: "white", strokeWidth: 1 },
    $(
      go.Panel,
      "Vertical",
      { padding: new go.Margin(5, 5, 5, 2 },
      $(
        go.TextBlock,
        "节点设置左右连接点", // 1. 节点文本也可以直接写在这
        { font: "18px Sans-Serif", stroke: "#444", textAlign: "center" },
        new go.Binding("text" // 2.文本也可以通过绑定 nodeDataArray 中的 text, 或者其他任意字段
      
    ,
    $(
      go.Panel,
      "Vertical",
      {
        alignment: go.Spot.Left,
        alignmentFocus: new go.Spot(0, 0.5, 8, 0,
      },
      makePort(2, 3.inSpotList // 需要返回一个数组,表示 2个 入边连接点
    ,
    $(
      go.Panel,
      "Vertical",
      {
        alignment: go.Spot.Right,
        alignmentFocus: new go.Spot(1, 0.5, -8, 0,
      },
      makePort(2, 3.outSpotList // 需要返回一个数组,表示 3个 出边连接点
    
  
;

function makePort(inCount, outCount {
  let inSpot = inCount;
  let outSpot = outCount;
  let inSpotList: any = [];
  let outSpotList: any = [];

  for (let i = 1; i <= inSpot; i++ {
    inSpotList.push(createPort(i, "Left";
  }
  for (let i = 1; i <= outSpot; i++ {
    outSpotList.push(createPort(i, "Right";
  }

  function createPort(portId, pos {
    var port = $(go.Shape, "Rectangle", {
      fill: "gray",
      stroke: null,
      desiredSize: new go.Size(8, 8,
      portId: String(portId, // 该属性比较重要,用于给每一个连接点 命名,
      toMaxLinks: 3,
      cursor: "pointer",
    };

    var panel = $(go.Panel, "Horizontal", { margin: new go.Margin(2, 0 };
    port.fromSpot = go.Spot[pos];
    port.fromLinkable = true;
    panel.alignment = go.Spot["Top" + pos];
    panel.add(port;

    return panel;
  }
  return { inSpotList, outSpotList };
}

方法二: 该方法需要重点关注的方法itemArray,在数据中分别定义了 leftArrayrightArray,用于循环显示子元素

diagram.nodeTemplate = $(
  go.Node,
  "Table",
  $(
    go.Panel,
    "Horizontal",
    { row: 1, column: 2 },
    $(
      go.TextBlock, // 资产名称
      {
        margin: 5,
        width: 240,
        font: "15px Verdana",
        stroke: "#444",
      }
    
  ,
  $(go.Panel, "Vertical", new go.Binding("itemArray", "leftArray", {
    // 节点 左侧 入边连接点 循环显示
    row: 1,
    column: 0,
    itemTemplate: $(
      go.Panel,
      {
        _side: "left",
        fromSpot: go.Spot.Left,
        toSpot: go.Spot.Left,
        cursor: "pointer",
      },
      new go.Binding("portId", "portId",
      $(
        go.Shape,
        "Rectangle",
        {
          stroke: null,
          strokeWidth: 1,
          desiredSize: new go.Size(8, 8,
          margin: new go.Margin(1, 5, 1, 0,
        },
        new go.Binding("fill", "portColor"
      
    ,
  },
  $(go.Panel, "Vertical", new go.Binding("itemArray", "rightArray", {
    // 节点 右侧 出边连接点 循环显示
    row: 1,
    column: 3,
    itemTemplate: $(
      go.Panel,
      {
        _side: "right",
        fromSpot: go.Spot.Right,
        toSpot: go.Spot.Right,
        cursor: "pointer",
      },
      new go.Binding("portId", "portId",
      $(
        go.Shape,
        "Rectangle",
        {
          stroke: null,
          strokeWidth: 0,
          desiredSize: new go.Size(8, 8,
          margin: new go.Margin(1, 0,
        },
        new go.Binding("fill", "portColor"
      
    ,
  }
;
var nodeDataArray = [
  { key: "A", rightArray: [{ portColor: "#33B12C", portId: "left0" }], rightArray: [{ portColor: "#33B12C", portId: "right0" }] },
  { key: "B", rightArray: [{ portColor: "#F29941", portId: "left0" }], rightArray: [{ portColor: "#F29941", portId: "right0" }] },
  { key: "C", rightArray: [{ portColor: "#11C67B", portId: "left0" }], rightArray: [{ portColor: "#11C67B", portId: "right0" }] },
];

var linkDataArray = [
  { from: "A", to: "B", frompid: "right0", topid: "left0" },
  { from: "B", to: "C", frompid: "right0", topid: "left0" },
];

diagram.model.linkFromPortIdProperty = "frompid"; //  连接点对应名称
diagram.model.linkToPortIdProperty = "topid";

12. 设置连线模板,箭头样式,连线上添加关系文字

// 连线模板
diagram.linkTemplate = $(
  go.Link,
  {
    routing: go.Link.Orthogonal,
    corner: 25,
    relinkableFrom: true,
    relinkableTo: true,
  },
  $(go.Shape, { isPanelMain: true, stroke: "transparent" },
  $(go.Shape, { isPanelMain: true, stroke: "#ccc", strokeWidth: 2 }, new go.Binding("stroke", "color", new go.Binding("strokeWidth", "strokeWidth",
  $(
    go.Shape,
    { toArrow: "standard", strokeWidth: 1, fill: "#ccc" }, //  箭头
    new go.Binding("stroke", "color",
    new go.Binding("fill", "color"
  ,
  $(
    go.Panel,
    "Auto", // 连线上的文字
    $(go.Shape, { fill: "white", stroke: "white" },
    $(go.TextBlock, { stroke: "#ff6600", visible: false }, new go.Binding("text", "linkText", new go.Binding("visible", "linkText", (a => (a ? true : false, new go.Binding("stroke", "isHighlighted", (sel => (sel ? "#1E90FF" : "#ff6600".ofObject(
  
;

13. 动态追加连线 addLinkData(

diagram.model.addLinkData({ from: "节点key", to: "节点key", color: "线的颜色", linkText: "连线上的文字" }; // 不指定连接点,直接连
diagram.model.addLinkData({ from: "节点key", to: "节点key", color: "线的颜色", linkText: "连线上的文字", frompid: "right0", topid: "left0" }; // 设置入边和出边的连接点

var nodeDataArray = [
  { key: "A", rightArray: [{ portColor: "#33B12C", portId: "left0" }], rightArray: [{ portColor: "#33B12C", portId: "right0" }] },
  { key: "B", rightArray: [{ portColor: "#F29941", portId: "left0" }], rightArray: [{ portColor: "#F29941", portId: "right0" }] },
  { key: "C", rightArray: [{ portColor: "#11C67B", portId: "left0" }], rightArray: [{ portColor: "#11C67B", portId: "right0" }] },
];

var linkDataArray = [
  { from: "A", to: "B", frompid: "right0", topid: "left0" },
  { from: "B", to: "C", frompid: "right0", topid: "left0" },
];

diagram.model.linkFromPortIdProperty = "frompid"; //  连接点对应名称
diagram.model.linkToPortIdProperty = "topid";

14. 点击节点使其相关联节点与连线及文字高亮显示

function nodeclick(e, node {
  var diagram = node.diagram;
  diagram.clearHighlighteds(;
  node.findNodesConnected(.each(function (l {
    l.isHighlighted = true;
  };
  node.linksConnected.each(function (n {
    n.isHighlighted = true;
  };
}

15. 点击画布清空当前高亮状态元素

diagram.click = function (e {
  e.diagram.commit(function (d {
    d.clearHighlighteds(;
  }, "no highlighteds";
};

16. 删除连线的方法

删除一条: diagram.model.removeLinkData(linkData; ,这个方法,我试了几个都没有成功,可能是linkData获取的不对,又由于我是要全部删除,因此使用了 diagram.model.removeLinkDataCollection方法,进行批量删除,但是在实际过程中发现,调用这个方法只能删除一半,(也不知道是什么原因,如果有耐心的有缘人,读到此处并解决了问题,欢迎留言帮我解惑,但是呢办法总比困难多,写一个 while 循环就可以搞定了

while (diagram.model.linkDataArray.length {
  diagram.model.removeLinkDataCollection(diagram.model.linkDataArray;
}

17. 动态根据需求 展开和收起某一个或全部的分组

展开或收起某一个分组:

// groupKey 在 nodeDataArray节点列表中的,分组节点的 Key值
diagram.findNodeForKey(groupKey.isSubGraphExpanded = true; // 展开
diagram.findNodeForKey(groupKey.isSubGraphExpanded = false; // 收起

展开或收起全部分组: 这个功能我用在了,在每次展开某种特定条件的分组时,先关闭之前所有的分组,在进行新一轮的展开操作

function graphExpandCollaps( {
  const { nodeDataArray } = diagram.model;
  nodeDataArray.forEach((v => {
    if (v.isGroup {
      diagram.findNodeForKey(v.groupName.isSubGraphExpanded = false; // 分组全部收起
    }
  };
}

18. 关闭画布重新渲染时的动画

diagram.animationManager.stopAnimation(; // 取消动画

19. 画布无限拖拽功能

画布固定宽度和高度之后,对拖拽功能很不友好,当内容比较多时,容易拖拽不动,再则在使用 mac 浏览器时,会触发浏览器的前进后退事件。设置画布可以无限滚动后,解决

diagram.scrollMode = go.Diagram.InfiniteScroll;

20. 图禁止复制,禁止删除,开启鼠标滚轮缩放,toolTip 触发 hoverDelay 初始定位

diagram = $(go.Diagram, "myDiagramDiv", {
  "toolManager.hoverDelay": 200, // toolTip触发
  "toolManager.mouseWheelBehavior": go.ToolManager.WheelZoom, // 开启鼠标滚轮缩放
  // initialContentAlignment: go.Spot.Center,   // 居中:第一次 不好使
  // contentAlignment: go.Spot.Center,   // 可以居中,但是居中之后不可以拖动
  initialPosition: new go.Point(-150, -300, // 初始坐标
  allowCopy: false, // 禁止复制
  allowDelete: false, // 禁止删除
  scale: 1, // 缩放会恢复默认值 diagram.scale = 1
  minScale: 0.4, // 缩小 diagram.scale -= 0.1
  maxScale: 1.4, // 放大 diagram.scale += 0.1
};

21. 消除水印

在其他文章中看到要删除一个函数的方法,但是由于 gojs 是压缩过的,每个版本的变量都不一样,因此查到另外一个方式,亲测有效,
全文搜索 String.fromCharCode(a.charCodeAt(g^b[(b[c]+b[d]%256],大家再搜索时,需要注意一个空格,不然可能会导致搜索不到结果。

if (f.indexOf("GoJS 2.2 evaluation" > -1 || f.indexOf("(c 1998-2022 Northwoods Software" > -1 || f.indexOf("Not for distribution or production use" > -1 || f.indexOf("gojs.net" > -1 {
  return "";
} else {
  return f;
}

更多分享 详见下一篇博文

gojs 超多超实用经验分享(二

编程笔记 » 可视化—gojs 超多超实用经验分享(一)

赞同 (31) or 分享 (0)
游客 发表我的评论   换个身份
取消评论

表情
(0)个小伙伴在吐槽