antv/g6的使用

1,987 阅读5分钟

要实现如下动态的效果,随着点击传入的数据变化而变化

根据设计图要求,主要有以下及部分

  • 控制箭头样式

  • 左侧整个层的name显示

  • 每个zone的显示与正常值的差别时间

  • 每个zone根据时间显示大小

  • 异常的zone置红

  • 要显示两层结构,大小要随着div变化

  • 其他.....

使用angular8开始吧

html部分

<div class="flow-charts">  
    <div id="report-flow-charts" class="flow-components"></div>
</div>

引入g6,初始定义一些参数

  1.  graph: any; //方便本页面使用g6实例化的实体

  2. configData 初始的配置

import G6 from "@antv/g6";

export class FlowChartDisplayComponent implements OnInit, OnDestroy {  
/**   * g6生成流程图的配置参数   */  
configData = {    // g6默认的配置    
container: "", // 指定图画布的容器 id,与第 9 行的容器对应    // 画布宽高   
 width: null,    
height: null,    
fitView: false, // node大小和连接线大小自适应   
 linkCenter: true,    
fitViewPadding: 20,   
 animate: true, // Boolean,可选,全局变化时否使用动画过度  
  modes: {      // 弹框的样式      
default: [        
{          type: "tooltip", // 提示框        
  formatText(model: any) {            // 提示框文本内容          
  if (model.data.nodeType === "nodesItem") {              
const text = `diff_time:${model.label}<br/>step_time:${model.data.step_time}S<br/>target_time:${model.data.target_time}S`;              return text;
            }          },          shouldBegin: (e) => {            // 当点击的节点/边/ combo 的 id 为 'id1' 时,不允许该 behavior 发生            if (e.item.getModel().data.nodeType == "zoneNode") return false;            return true;          },        },      ],      edit: [],    },    plugins: [],  };
 graph:any
}

modes用于中可以设置g6的交互模式,tooltip用来显示提示框,可在里面定义提示框内容

 {          type: "tooltip", // 提示框          formatText(model: any) {            // 提示框文本内容            if (model.data.nodeType === "nodesItem") {              const text = `diff_time:${model.label}<br/>step_time:${model.data.step_time}S<br/>target_time:${model.data.target_time}S`;              return text;            }          },          shouldBegin: (e) => {            // 当点击的节点/边/ combo 的 id'id1' 时,不允许该 behavior 发生            if (e.item.getModel().data.nodeType == "zoneNode") return false;            return true;          },        },
  1. formatText:提示框内容,return 出来
  2. shouldBegin:定义显示提示框的条件,return true则显示,否则不显示

开始g6相关方法的定义

第一步:constructor中发送ajax

constructor(){}

第二步:把ajax得到的数据处理成g6需要的数据格式。先定义处理中公共的部分,当然封装起来是更好的!具体的都有备注。后面将用到

assignG6Init(data: ReprtFlowchartRes){
//公用数据部分    const nodeOverFillColor = "#DF0024"; // 超出部分颜色    const nodeDefaultFillColor = "#5A7C91"; // 节点正常色    const configDOM = $("#report-flow-charts");    const configDOMWidth = configDOM.width();    const configDOMHeigth = configDOM.height();    let configTextWidth = 0; // 固定文字的宽度    const configRightIconWidth = 50;    let bkZondeNum = 0; // 背景zone的数量    let nodeKeyIDNum = 0; // 连线zone的数量    let nodeItemHeight = 0; // 每个Node节点的高度    let nodeDeafultHeight = 0; //节点固定宽度大小    // 节点的总数据    let g6InitData = {      nodes: null,      edges: null,    };//转换节点连接线部分    let edgesData = [];
}

开始转换数据了,并不复杂,封装起来引入调用,当然是更好的,其中都有一些简单注释!

 data.LinkInfoList.forEach((item) => {      let edgesItem = {        source: item.source,        target: item.target,        type: "line-arrow",        size: 1,        color: "#5A7C91",        // 该边连入 source 点的第 0  anchorPoint,        sourceAnchor: 0,        // 该边连入 target 点的第 0  anchorPoint,        targetAnchor: 1,        style: {          radius: 20,          offset: 2,          // endArrow: true,          lineWidth: 2,          stroke: "#5A7C91",          // offset: 2,  // 拐弯处距离节点最小距离          // radius: 10,  // 拐弯处的圆角弧度,若不设置则为直角          // lineWidth: 1,          // stroke: '#5A7C91',          color: "#5A7C91",          size: 1,          lineDash: [0, 0],          cursor: "pointer",          endArrow: {            path: "M 0,0 L 8,4 L 8,-4 Z",            // 箭头的偏移量,负值代表向 x 轴正方向移动            d: -20,            // v3.4.1 后支持各样式属性            fill: "#5A7C91",            // opacity: 0.8,          },        },      };      if (item.line_type === 1) {        edgesItem.style.lineDash = [1, 1];      }      edgesData.push(edgesItem);    });    g6InitData.edges = edgesData;    // line_type: 1    // 转换nodes节点部分    let g6DataList = [];    let g6BKInitList = [];    data.DataList.forEach((item, index) => {      // 设置背景图      let zoneNode: any;      if (!g6BKInitList.includes(item.zone_id)) {        g6BKInitList.push(item.zone_id);        let bkData = JSON.parse(JSON.stringify(item));        bkData.nodeType = "zoneNode";        bkZondeNum++;        zoneNode = {          id: item.zone_id,          type: "rect",          x: configDOMWidth / 2,          y: null,          label: item.zone_name.trim(),          anchorPoints: [],          size: null,          style: {            fill: "#fff",            stroke: "#000",            opacity: 0.1,            fontSize: 20,            lineWidth: 1,          },          labelCfg: {            // 节点上的标签文本样式配置            position: "left",            offset: 0,            // refX: 300,            style: {              fill: "#000", // 节点标签文字颜色              writingMode: "vertical - lr",              fontSize: 14,              fontWeight: 700,              lineWidth: 2,              cursor: "pointer",            },          },          data: bkData,        };      }      const nodeData = JSON.parse(JSON.stringify(item));      nodeData.nodeType = "nodesItem";      nodeKeyIDNum++;      let nodesItem = {        id: item.key_id,        type: "rect",        x: null,        y: null,        label:          item.diff_time > 0            ? "+" + item.diff_time + "S"            : item.diff_time + "S",        size: null,        anchorPoints: [          // [0, 0],          // [0.5, 0],          [1, 0.5],          [0.5, 0],        ],        style: {          fill: item.is_over ? nodeOverFillColor : nodeDefaultFillColor, // 节点填充色          stroke: "",          lineWidth: 0,          cursor: "pointer",        },        labelCfg: {          // 节点上的标签文本样式配置          position: "bottom",          offset: 10,          // refX: -20,          style: {            fill: "#333", // 节点标签文字颜色          },        },        data: nodeData,      };      if (zoneNode) {        g6DataList.push(zoneNode);      }      g6DataList.push(nodesItem);    });    // 画背景图    let isZondeNodeIndex = -1;    let configDOMMaxWidth = 0;    let lastZoneIndex = 0;    g6DataList.forEach((item, index) => {      if (item.data.nodeType === "zoneNode") {        lastZoneIndex = index;      }    })    g6DataList.forEach((item, index) => {      if (item.data.nodeType === "zoneNode") {        isZondeNodeIndex += 1;        // 先画背景图        // index == 0 ? configDOMHeigth / 6 : (index + 1) * configDOMHeigth / 3 - configDOMHeigth / 6        //isZondeNodeIndex == 0 ? configDOMHeigth / (bkZondeNum * 2) : (isZondeNodeIndex + 1) * configDOMHeigth / bkZondeNum - configDOMHeigth / (bkZondeNum * 2)        let labelWidth = (item.label.length + 1) * 8 ;        if (labelWidth > configTextWidth) {          configTextWidth = labelWidth;        }        item.labelCfg.offset = lastZoneIndex === index ? -configTextWidth + (configTextWidth / 18) : -configTextWidth ;        item.y =          ((isZondeNodeIndex + 1) * configDOMHeigth) / bkZondeNum -          configDOMHeigth / (bkZondeNum * 2);        item.size = [configDOMWidth, configDOMHeigth / bkZondeNum];      }    });    let isNodeItemIndex = -1;    let isNodeStepSum = 0;    g6DataList.forEach((item) => {      if (item.data.nodeType === "nodesItem") {        isNodeStepSum += item.data.step_time;      }    });    // 画各个节点之间的位置    let beforNodeAllWidth: number = 0; // 之前node的节点宽度    g6DataList.forEach((item, index) => {      if (item.data.nodeType === "nodesItem") {        isNodeItemIndex += 1;        // (configDOMWidth - configTextWidth) /setp_time总和 * 单个step_time/2        const nowWidth =          ((configDOMWidth - configTextWidth) / isNodeStepSum) *          (item.data.step_time / 2);        // const nowX = nowWidth + configTextWidth;        // 计算出大小        item.size = [nowWidth, configDOMHeigth / nodeKeyIDNum / 3];        // 获取高度        if (!nodeItemHeight) {          nodeItemHeight = configDOMHeigth / nodeKeyIDNum / 3;        }        //计算x        //  beforNodeAllWidth === 0 ? nowWidth / 2 + configTextWidth : nowWidth + beforNodeAllWidth;        item.x =          beforNodeAllWidth === 0            ? nowWidth / 2 + configTextWidth            : nowWidth + beforNodeAllWidth;        beforNodeAllWidth = item.x + nowWidth;        // item.x + nowWidth * nodeKeyIDNum / 2        let nodeFdIndex = g6DataList.findIndex((ele) => {          return ele.id === item.data.zone_id;        });        item.y = g6DataList[nodeFdIndex].y;      }    });    //  设置连接线样式    g6InitData.edges.forEach((item) => {      // 设置箭头样式      item.style.endArrow.d = nodeItemHeight ? -nodeItemHeight / 2 : -20;    });    g6InitData.nodes = g6DataList;    this.g6InitData = g6InitData;

第三步:开始渲染g6的DOM

当然需要区分开了,第一次初始化,还是更新操作。不然很影响渲染性能。

g6Init(    clientData: any,    containerID: string,    divWidth: number,    divHeight: number,    isChanged: boolean  ) {
}

有数据后,开始操作把

初次渲染的方法

  this.configData.container = containerID; // 指定图画布的容器 id,与第 9 行的容器对应      // 画布宽高      this.configData.width = divWidth;      this.configData.height = divHeight;      // 重新计算画布大小      this.graph.changeSize(divWidth, divHeight);      // graph是Graph的实例      this.g6ConfigEvent(this.graph);      this.graph.changeData(data);

数据变化更新DOM的方法

  this.configData.container = containerID; // 指定图画布的容器 id,与第 9 行的容器对应      // 画布宽高      this.configData.width = divWidth;      this.configData.height = divHeight;      G6.registerEdge(        "line-arrow",        {          getPath(points) {            const startPoint = points[0];            const endPoint = points[1];            return [              ["M", startPoint.x, startPoint.y],              ["L", endPoint.x, startPoint.y],              ["L", endPoint.x, endPoint.y],            ];          },        },        "line"      );      /**       * 实例化一些插件       */      // 实例化 minimap 插件      const minimap = new G6.Minimap({        size: [50, 80],        className: "minimap",        type: "delegate",        width: 50,        height: 20,      });      // 实例化 grid 插件      // const grid = new G6.Grid();      // this.configData.plugins = [grid];      // 创建 G6 图实例      this.graph = new G6.Graph(this.configData);      // plugins: [minimap], // 将 minimap 实例配置到图上      // 点击节点      this.g6ConfigEvent(this.graph);      // 读取数据      this.graph.data(data);      // 渲染图      this.graph.render();      // 动态更新组件样式

请注意,graph.render()用来更新DOM,不然会出现多个g6的DOM的情况。更新数据时,不要忘了加。

第四步:公共操作方法的封装,只写一部分,后面大家自己记得写

将g6实例传入,其他封装类似下面代码。还有其他的方法就省略了,

// g6配置的事件绑定函数等  g6ConfigEvent(graph: any) {    // 点击节点    graph.on("node:click", (e) => {    });  }

感兴趣的移步g6官网,g6做图形,很个性化,能解决一部分定制化需求。

antv-g6.gitee.io/zh

后面会对具体用到的一部分功能写的更清楚些。