G6的插件系统

析构薄雾
• 阅读 3516

G6的插件系统做的相当完善, 可惜文档没有具体说到. 这里整理一下g6的插件.

插件大致分为四种类型:

  1. behaviour 行为, 可以理解为事件处理
  2. node, edge的插件, 就是node和edge的样式, 同样是插件
  3. layout插件, node的布局之类, 这部分涉及的算法比较多
  4. Util插件, 就是自定义工具函数, 将其内置G6.Util中

这四种插件都有各自的写法以及api, 但是文档中没有提到, 这里简单介绍一下. 一下都以官方插件为例.

behaviour 行为

写完发现其实官方有这部分的文档: https://www.yuque.com/antv/g6/custom-interaction

请看下面代码, 这部分是注册一个右键拖动的行为:

// g6/plugins/behaviour.analysis/index.js
function panCanvas(graph, button = 'left', panBlank = false) {
  let lastPoint;
  if (button === 'right') {
    graph.behaviourOn('contextmenu', ev => {
      ev.domEvent.preventDefault();
    });
  }
  graph.behaviourOn('mousedown', ev => {
    if (button === 'left' && ev.domEvent.button === 0 ||
    button === 'right' && ev.domEvent.button === 2) {
      if (panBlank) {
        if (!ev.shape) {
          lastPoint = {
            x: ev.domX,
            y: ev.domY
          };
        }
      } else {
        lastPoint = {
          x: ev.domX,
          y: ev.domY
        };
      }
    }
  });


// 鼠标右键拖拽画布空白处平移画布交互
G6.registerBehaviour('rightPanBlank', graph => {
  panCanvas(graph, 'right', true);
})

然后在实例化graph的时候在modes中引入:

new Graph({
  modes: {
    default: ['panCanvas']
  }
})

其实到这里我们已经知道了, 只要是在一些内置事件中注册一下自定义事件再引入我们就可以称之为一个行为插件. 但是我们还需要再深入一点, 看到底是不是这样的.

// g6/src/mixin/mode.js
behaviourOn(type, fn) {
    const eventCache = this._eventCache;
    if (!eventCache[type]) {
      eventCache[type] = [];
    }
    eventCache[type].push(fn);
    this.on(type, fn);
},

照老虎画猫我们最终可以实现一个自己的行为插件:

// 未经过验证
function test(graph) {
 graph.behaviourOn('mousedown' () => alert(1) ) 
}


// 鼠标右键拖拽画布空白处平移画布交互
G6.registerBehaviour('test', graph => {
  test(graph);
})

new Graph({
  modes: {
    default: ['test']
  }
})

node, edge的插件

关于node, edge的插件的插件其实官方文档上面的自定义形状和自定义边.

// g6/plugins/edge.polyline/index.js
G6.registerEdge('polyline', {
  offset: 10,
  getPath(item) {
    const points = item.getPoints();
    const source = item.getSource();
    const target = item.getTarget();
    return this.getPathByPoints(points, source, target);
  },
  getPathByPoints(points, source, target) {
    const polylinePoints = getPolylinePoints(points[0], points[points.length - 1], source, target, this.offset);
    // FIXME default
    return Util.pointsToPolygon(polylinePoints);
  }
});

G6.registerEdge('polyline-round', {
  borderRadius: 9,
  getPathByPoints(points, source, target) {
    const polylinePoints = simplifyPolyline(
      getPolylinePoints(points[0], points[points.length - 1], source, target, this.offset)
    );
    // FIXME default
    return getPathWithBorderRadiusByPolyline(polylinePoints, this.borderRadius);
  }
}, 'polyline');

这部分那么多代码其实最重要的还是上面的部分, 注册一个自定义边, 直接引入就可以在shape中使用了, 具体就不展开了.
自定义边
自定义节点

layout插件

layout在初始化的时候即可以在 layout 字段中初始化也可以在plugins中.

const graph = new G6.Graph({
  container: 'mountNode',
  layout: dagre
})

/* ---- */

const graph = new G6.Graph({
  container: 'mountNode',
  plugins: [ dagre ]
})

原因在于写插件的时候同时也把布局注册为一个插件了:

// g6/plugins/layout.dagre/index.js
class Plugin {
  constructor(options) {
    this.options = options;
  }
  init() {
    const graph = this.graph;
    graph.on('beforeinit', () => {
      const layout = new Layout(this.options);
      graph.set('layout', layout);
    });
  }
}

G6.Plugins['layout.dagre'] = Plugin;

通过查看源码我们可以知道自定义布局的核心方法就是execute, 再具体一点就是我们需要在每个布局插件中都有execute方法:

// g6/plugins/layout.dagre/layout.js
// 执行布局
  execute() {
    const nodes = this.nodes;
    const edges = this.edges;
    const nodeMap = {};
    const g = new dagre.graphlib.Graph();
    const useEdgeControlPoint = this.useEdgeControlPoint;
    g.setGraph({
      rankdir: this.getValue('rankdir'),
      align: this.getValue('align'),
      nodesep: this.getValue('nodesep'),
      edgesep: this.getValue('edgesep'),
      ranksep: this.getValue('ranksep'),
      marginx: this.getValue('marginx'),
      marginy: this.getValue('marginy'),
      acyclicer: this.getValue('acyclicer'),
      ranker: this.getValue('ranker')
    });
    g.setDefaultEdgeLabel(function() { return {}; });
    nodes.forEach(node => {
      g.setNode(node.id, { width: node.width, height: node.height });
      nodeMap[node.id] = node;
    });
    edges.forEach(edge => {
      g.setEdge(edge.source, edge.target);
    });
    dagre.layout(g);
    g.nodes().forEach(v => {
      const node = g.node(v);
      nodeMap[v].x = node.x;
      nodeMap[v].y = node.y;
    });
    g.edges().forEach((e, i) => {
      const edge = g.edge(e);
      if (useEdgeControlPoint) {
        edges[i].controlPoints = edge.points.slice(1, edge.points.length - 1);
      }
    });
  }

上面是官方插件有向图的核心代码, 用到了dagre算法, 再简化一点其实可以理解为就是利用某种算法确定节点和边的位置.

最终执行布局的地方:

// g6/src/controller/layout.js
graph._executeLayout(processor, nodes, edges, groups)

Util插件

这类插件相对简单许多, 就是将函数内置到Util中. 最后直接在G6.Util中使用即可

比如一个生成模拟数据的:

// g6/plugins/util.randomData/index.js
const G6 = require('@antv/g6');
const Util = G6.Util;
const randomData = {
  // generate chain graph data
  createChainData(num) {
    const nodes = [];
    const edges = [];
    for (let index = 0; index < num; index++) {
      nodes.push({
        id: index
      });
    }
    nodes.forEach((node, index) => {
      const next = nodes[index + 1];
      if (next) {
        edges.push({
          source: node.id,
          target: next.id
        });
      }
    });
    return {
      nodes,
      edges
    };
  },
  // generate cyclic graph data
  createCyclicData(num) {
    const data = randomData.createChainData(num);
    const { nodes, edges } = data;
    const l = nodes.length;
    edges.push({
      source: data.nodes[l - 1].id,
      target: nodes[0].id
    });
    return data;
  },
  // generate num * num nodes without edges
  createNodesData(num) {
    const nodes = [];
    for (let index = 0; index < num * num; index++) {
      nodes.push({
        id: index
      });
    }
    return {
      nodes
    };
  }
};
Util.mix(Util, randomData);
点赞
收藏
评论区
推荐文章
徐小夕 徐小夕
5年前
30分钟开发一款抓取网站图片资源的浏览器插件
前言由于业务需求,笔者要为公司开发几款实用的浏览器插件,所以大致花了一天的时间,看完了谷歌浏览器插件开发文档,在这里特地总结一下经验,并通过一个实际案例来复盘插件开发的流程和注意事项.你将收获如何快速上手浏览器插件开发浏览器插件开发的核心概念浏览器插件的通信机制浏览器插件的数据存储浏览器插件的应用场景开发一款抓取网站图片资源
Wesley13 Wesley13
4年前
vs2015中electron开发入门 01
安装vs2015插件\node.jsTools\1.在vs工具扩展更新,查找node关键字,找到如图!node.jsTools(https://static.oschina.net/uploads/img/201605/23225301_AcB3.png"node.jsTools")2.下载安装插件创建项目
Stella981 Stella981
4年前
IntelliJ IDEA优秀插件(编程通用)
一、IntelliJIDEA开发最近大部分开发IDE工具都切换到了,所以也花了点心思去找了相关的插件。这里整理的适合各种语言开发的通用插件,也排除掉IntelliJIDEA自带的常用插件了(有些插件在安装IntelliJIDEA的时候可以安装)。二、IDEA插件安装IDEA的插件安装
Stella981 Stella981
4年前
Atom的python插件和常用插件
python:simplifiedchinesemenu:中文汉化(英文差的)代码高亮:Atom自带自动补全:autocompletepython语法检查:linterflake8定义跳转:pythontools代码运行:atomrunner(只能输出,不能输入),atompythonrun(Windows,可以输入
Easter79 Easter79
4年前
Sublime添加Sass插件,语法高亮
在sublime中安装sass插件和sassbuild插件了,打开我们的sublime首先你要看的是在preference选项下有没有packagecontrol这个选项,如果没有的话,就表示你没有PackageControl插件(一个方便 Sublimetext管理插件的插件),这时,你就要从菜单ViewShowConsole或
程序员小五 程序员小五
1年前
融云IM干货丨如何确保插件与UNI-app的兼容性?
确保插件与UNIapp的兼容性,可以采取以下几个步骤:1.使用官方插件市场:尽量在寻找插件,因为官方市场提供的插件会有UNIapp兼容性描述,而第三方市场如npm的插件可能没有兼容性描述,容易下载到无法跨平台的、仅适配web的插件。2.检查平台兼容性:在插
程序员小五 程序员小五
1年前
融云IM干货丨如何优化插件以减少内存占用?
为了优化插件以减少内存占用,以下是一些具体的策略和方法:1.代码瓶颈优化:重写热点函数,采用更高效的算法和数据结构,减少不必要的计算和内存分配。2.资源瓶颈处理:实现分批处理和惰性加载机制,减少对内存和CPU的即时需求。3.插件加载优化:重构插件架构,使用
写一个Chrome浏览器插件
作者:京东工业焦丁一、什么是浏览器插件浏览器插件是依附于浏览器,用来拓展网页能力的程序。插件具有监听浏览器事件、获取和修改网页元素、拦截网络请求、添加快捷菜单等功能。使用浏览器插件可以实现很多有趣的功能。二、浏览器插件有哪些种类•以chromium为内核的
程序员小五 程序员小五
1年前
融云IM干货丨uni-app的插件生态系统具体有哪些功能?
UNIapp的插件生态系统提供了丰富的功能,具体包括以下几个方面:1.基础功能插件:这些插件提供基本的功能,如网络请求、本地存储、事件处理等,对于大多数UniApp应用都是必需的。2.UI组件插件:提供各种用户界面组件,例如按钮、列表、表单、弹窗等,帮助开
程序员小五 程序员小五
1年前
融云IM干货丨uni-app插件生态系统支持跨平台开发吗?
UNIapp的插件生态系统确实支持跨平台开发,以下是一些关键点:1.开放兼容的插件系统:UNIapp积极拥抱社区,创建了开放、兼容的插件系统。插件市场(https://ext.dcloud.net.cn)是UNIapp官方插件生态的集中地,支持前端组件、U
程序员小五 程序员小五
1年前
融云IM干货丨如何为UNI-app项目选择适合的插件?
为UNIapp项目选择适合的插件时,可以遵循以下步骤和考虑因素:1.明确需求:首先,明确你的项目需要哪些功能。根据项目需求,确定需要的插件类型,比如UI组件、工具类插件、第三方服务插件等。2.插件市场搜索:访问UNIapp官方插件市场(https://ex