
<template>
  <div class="graph-echart"  id="graph" ref="graph"></div>
</template>

<script>
import G6 from '@antv/g6';
// import G6 from '@antv/g6/dist/g6.min'
import insertCss from 'insert-css';
insertCss(`
  .g6-component-tooltip {
    background-color: rgba(0,0,0, 0.65);
    padding: 10px;
    box-shadow: rgb(174, 174, 174) 0px 0px 10px;
    width: fit-content;
    color: #fff;
    border-radius:4px;
  }
`);



export default {
  props: {
      // 数据
      graphData: {
        type: Object,
        default () {
          return {}
        }
      }
  },
  data(){
    return {
      graph:null,
      // graphData:[],
      defaultConfig:{
        width:'100%',
        height:500,
        modes: {
          default: ['zoom-canvas', 'drag-canvas'],
        },
        fitView: true,
        animate: true,
        defaultNode: {
          type: 'flow-rect',
        },
        defaultEdge: {
          type: 'cubic-horizontal',
          style: {
            stroke: '#CED4D9',
          },
        },
        layout: {
          type: 'indented',
          direction: 'LR',
          dropCap: false,
          indent: 300,
          getHeight: () => {
            return 60;
          },
        },
      },
    }
  },
  created(){
    // this.graphData = mockData;
  },
  mounted(){
    let that=this;
    // const data = this.graphData;
    const data = {
    "nodes": [
        {
            "id": "web-service",
            "label": "web-service",
            // "img": require("@/assets/images/Rectangle_this.png")
        },
        {
            "id": "dubbo-chain-web",
            "label": "dubbo-chain-web",
            //  "img": require("@/assets/images/Rectangle_this.png")
        },
        {
            "id": "login-service",
            "label": "login-service",
            //  "img": require("@/assets/images/Rectangle_this.png")
        },
        {
            "id": "data-service",
            "label": "data-service",
            //  "img": require("@/assets/images/Rectangle_this.png")
        },
        {
            "id": "auth-service",
            "label": "auth-service",
            //  "img": require("@/assets/images/Rectangle_this.png")
        }
    ],
    "edges": [
        {
            "source": "web-service",
            "target": "dubbo-chain-web",
            "callCount": 43
        },
        {
            "source": "login-service",
            "target": "web-service",
            "callCount": 1
        },
        {
            "source": "data-service",
            "target": "auth-service",
            "callCount": 2
        },
        {
            "source": "auth-service",
            "target": "login-service",
            "callCount": 1
        }
    ]
}
    const container = document.getElementById('graph');
    const width = window.innerWidth - 330;
    const height = 600;
    // if (this.graph !== '') {
		// 			this.graph.destroy()
		// }
    this.graph = new G6.Graph({
      container: 'graph', // 图例的容器 id 名称
      width,
      height,
      // controlPoints: false,
      modes: {
        // default: ['drag-canvas', 'drag-node', 'activate-relations'],
         default: ['drag-canvas', 'drag-node'],
      },
      layout: {
        type: 'dagre', //图例层级铺开
        // 拓扑图的类型
        // type: 'fruchterman',
        gravity: 7,
        speed: 5,
        nodeSize: [150, 100],
        nodesep: 4,
        ranksep: 4,
        rankdir: 'LR',
        align: 'UR',
        // linkDistance: 200,
      },
      animate: true,
      defaultNode: {
        // size: [80, 80],
        size:80,
        // type: 'cCircle',
        style: {
          lineWidth: 2,
          stroke:'rgb(86, 166, 75)',
          fill:'#fff'
        },
        // 圈内字体大小
        labelCfg: {
          style: {
            fontSize: 10,
            color:'rgb(36, 41, 46)',
          },
          position: "bottom",
        },
      },
      defaultEdge: {
        size: 1,
        color: '#e2e2e2',//线的颜色
        style: {
          endArrow: {
            path: 'M 0,0 L 8,4 L 8,-4 Z',
            fill: '#e2e2e2',//线的颜色
          },
        },
      },
      nodeStateStyles: {
        highlight: {
          opacity: 1,
          lineWidth: 2,
          stroke:'rgb(86, 166, 75)',
          fill:'#fff'
        },
        dark: {
          opacity: 0.3,
        },
        
      },
      edgeStateStyles: {
        highlight: {
          stroke: '#999',
          fill:'#999',
          background: {
              fill: '#f1f1f1',
              stroke: '#999',
              padding: [3, 2, 3, 2],
              radius: 2,
          },
        },
      },
    });

    //设置省略号
    const fittingString = (str, maxWidth, fontSize) => {
      const ellipsis = '...';
      const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
      let currentWidth = 0;
      let res = str;
      const pattern = new RegExp('[\u4E00-\u9FA5]+'); // distinguish the Chinese charactors and letters
      str.split('').forEach((letter, i) => {
        if (currentWidth > maxWidth - ellipsisLength) return;
        if (pattern.test(letter)) {
          // Chinese charactors
          currentWidth += fontSize;
        } else {
          // get the width of single letter according to the fontSize
          currentWidth += G6.Util.getLetterWidth(letter, fontSize);
        }
        if (currentWidth > maxWidth - ellipsisLength) {
          res = `${str.substr(0, i)}${ellipsis}`;
        }
      });
      return res;
    };



    function clearAllStats() {
      that.graph.setAutoPaint(false);
      that.graph.getNodes().forEach(function (node) {
        that.graph.clearItemStates(node);
      });
      that.graph.getEdges().forEach(function (edge) {
        that.graph.clearItemStates(edge);
      });
      that.graph.paint();
      that.graph.setAutoPaint(true);
    }


    this.graph.on('node:mouseenter', function (e) {
      const item = e.item;
      that.graph.setAutoPaint(false);
      that.graph.getNodes().forEach(function (node) {
        that.graph.clearItemStates(node);
        that.graph.setItemState(node, 'dark', true);
      });
      that.graph.setItemState(item, 'dark', false);
      that.graph.setItemState(item, 'highlight active', true);
      that.graph.getEdges().forEach(function (edge) {
        if (edge.getSource() === item) {
          that.graph.setItemState(edge.getTarget(), 'dark', false);
          that.graph.setItemState(edge.getTarget(), 'highlight', true);
          that.graph.setItemState(edge, 'highlight', true);
          edge.toFront();
        } else if (edge.getTarget() === item) {
          that.graph.setItemState(edge.getSource(), 'dark', false);
          that.graph.setItemState(edge.getSource(), 'highlight', true);
          that.graph.setItemState(edge, 'highlight', true);
          edge.toFront();
        } else {
          that.graph.setItemState(edge, 'highlight', false);
        }
      });
      that.graph.paint();
      that.graph.setAutoPaint(true);
    });
    this.graph.on('node:mouseleave', clearAllStats);
    this.graph.on('canvas:click', clearAllStats);


    this.graphData.nodes.forEach(function (node) {
      node.label = fittingString(node.label, node.size, 10);
    });

    this.graph.data(data)
    this.graph.render()

    if (typeof window !== 'undefined')
      window.onresize = () => {
        if (!this.graph || this.graph.get('destroyed')) return;
        if (!container || !container.scrollWidth || !container.scrollHeight) return;
        this.graph.changeSize(container.scrollWidth, container.scrollHeight);
      };
  },
  methods:{
      registerFn(){
        /**
         * 自定义节点
         */
        G6.registerNode(
          'flow-rect',
          {
            shapeType: 'flow-rect',
            draw(cfg, group) {
              const {
                name = '',
                variableName,
                variableValue,
                variableUp,
                label,
                collapsed,
                currency,
                status,
                rate
              } = cfg;

              const grey = '#CED4D9';
              const rectConfig = {
                width: 202,
                height: 60,
                lineWidth: 1,
                fontSize: 12,
                fill: '#fff',
                radius: 4,
                stroke: grey,
                opacity: 1,
              };

              const nodeOrigin = {
                x: -rectConfig.width / 2,
                y: -rectConfig.height / 2,
              };

              const textConfig = {
                textAlign: 'left',
                textBaseline: 'bottom',
              };

              const rect = group.addShape('rect', {
                attrs: {
                  x: nodeOrigin.x,
                  y: nodeOrigin.y,
                  ...rectConfig,
                },
              });

              const rectBBox = rect.getBBox();

              // label title
              group.addShape('text', {
                attrs: {
                  ...textConfig,
                  x: 12 + nodeOrigin.x,
                  y: 20 + nodeOrigin.y,
                  text: name.length > 28 ? name.substr(0, 28) + '...' : name,
                  fontSize: 12,
                  opacity: 0.85,
                  fill: '#000',
                  cursor: 'pointer',
                },
                // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
                name: 'name-shape',
              });

              // price
              const price = group.addShape('text', {
                attrs: {
                  ...textConfig,
                  x: 12 + nodeOrigin.x,
                  y: rectBBox.maxY - 12,
                  text: label,
                  fontSize: 16,
                  fill: '#000',
                  opacity: 0.85,
                },
              });

              // label currency
              group.addShape('text', {
                attrs: {
                  ...textConfig,
                  x: price.getBBox().maxX + 5,
                  y: rectBBox.maxY - 12,
                  text: currency,
                  fontSize: 12,
                  fill: '#000',
                  opacity: 0.75,
                },
              });

              // percentage
              const percentText = group.addShape('text', {
                attrs: {
                  ...textConfig,
                  x: rectBBox.maxX - 8,
                  y: rectBBox.maxY - 12,
                  text: `${((variableValue || 0) * 100).toFixed(2)}%`,
                  fontSize: 12,
                  textAlign: 'right',
                  fill: colors[status],
                },
              });

              // percentage triangle
              const symbol = variableUp ? 'triangle' : 'triangle-down';
              const triangle = group.addShape('marker', {
                attrs: {
                  ...textConfig,
                  x: percentText.getBBox().minX - 10,
                  y: rectBBox.maxY - 12 - 6,
                  symbol,
                  r: 6,
                  fill: colors[status],
                },
              });

              // variable name
              group.addShape('text', {
                attrs: {
                  ...textConfig,
                  x: triangle.getBBox().minX - 4,
                  y: rectBBox.maxY - 12,
                  text: variableName,
                  fontSize: 12,
                  textAlign: 'right',
                  fill: '#000',
                  opacity: 0.45,
                },
              });

              // bottom line background
              const bottomBackRect = group.addShape('rect', {
                attrs: {
                  x: nodeOrigin.x,
                  y: rectBBox.maxY - 4,
                  width: rectConfig.width,
                  height: 4,
                  radius: [0, 0, rectConfig.radius, rectConfig.radius],
                  fill: '#E0DFE3',
                },
              });

              // bottom percent
              const bottomRect = group.addShape('rect', {
                attrs: {
                  x: nodeOrigin.x,
                  y: rectBBox.maxY - 4,
                  width: rate * rectBBox.width,
                  height: 4,
                  radius: [0, 0, 0, rectConfig.radius],
                  fill: colors[status],
                },
              });

              // collapse rect
              if (cfg.children && cfg.children.length) {
                group.addShape('rect', {
                  attrs: {
                    x: rectConfig.width / 2 - 8,
                    y: -8,
                    width: 16,
                    height: 16,
                    stroke: 'rgba(0, 0, 0, 0.25)',
                    cursor: 'pointer',
                    fill: '#fff',
                  },
                  // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
                  name: 'collapse-back',
                  modelId: cfg.id,
                });

                // collpase text
                group.addShape('text', {
                  attrs: {
                    x: rectConfig.width / 2,
                    y: -1,
                    textAlign: 'center',
                    textBaseline: 'middle',
                    text: collapsed ? '+' : '-',
                    fontSize: 16,
                    cursor: 'pointer',
                    fill: 'rgba(0, 0, 0, 0.25)',
                  },
                  // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
                  name: 'collapse-text',
                  modelId: cfg.id,
                });
              }

              this.drawLinkPoints(cfg, group);
              return rect;
            },
            update(cfg, item) {
              const { level, status, name } = cfg;
              const group = item.getContainer();
              let mask = group.find(ele => ele.get('name') === 'mask-shape');
              let maskLabel = group.find(ele => ele.get('name') === 'mask-label-shape');
              if (level === 0) {
                group.get('children').forEach(child => {
                  if (child.get('name')?.includes('collapse')) return;
                  child.hide();
                })
                if (!mask) {
                  mask = group.addShape('rect', {
                    attrs: {
                      x: -101,
                      y: -30,
                      width: 202,
                      height: 60,
                      opacity: 0,
                      fill: colors[status]
                    },
                    // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
                    name: 'mask-shape',
                  });
                  maskLabel = group.addShape('text', {
                    attrs: {
                      fill: '#fff',
                      fontSize: 20,
                      x: 0,
                      y: 10,
                      text: name.length > 28 ? name.substr(0, 16) + '...' : name,
                      textAlign: 'center',
                      opacity: 0,
                    },
                    // must be assigned in G6 3.3 and later versions. it can be any string you want, but should be unique in a custom item type
                    name: 'mask-label-shape',
                  });
                  const collapseRect = group.find(ele => ele.get('name') === 'collapse-back');
                  const collapseText = group.find(ele => ele.get('name') === 'collapse-text');
                  collapseRect?.toFront();
                  collapseText?.toFront();
                } else {
                  mask.show();
                  maskLabel.show();
                }
                mask.animate({ opacity: 1 }, 200);
                maskLabel.animate({ opacity: 1 }, 200);
                return mask;
              } else {
                group.get('children').forEach(child => {
                  if (child.get('name')?.includes('collapse')) return;
                  child.show();
                })
                mask?.animate({ opacity: 0 }, {
                  duration: 200,
                  callback: () => mask.hide()
                });
                maskLabel?.animate({ opacity: 0 }, {
                  duration: 200,
                  callback: () => maskLabel.hide()
                });
              }
              this.updateLinkPoints(cfg, group);
            },
            setState(name, value, item) {
              if (name === 'collapse') {
                const group = item.getContainer();
                const collapseText = group.find((e) => e.get('name') === 'collapse-text');
                if (collapseText) {
                  if (!value) {
                    collapseText.attr({
                      text: '-',
                    });
                  } else {
                    collapseText.attr({
                      text: '+',
                    });
                  }
                }
              }
            },
            getAnchorPoints() {
              return [
                [0, 0.5],
                [1, 0.5],
              ];
            },
          },
          'rect',
        );

        G6.registerEdge(
          'flow-cubic',
          {
            getControlPoints(cfg) {
              let controlPoints = cfg.controlPoints; // 指定controlPoints
              if (!controlPoints || !controlPoints.length) {
                const { startPoint, endPoint, sourceNode, targetNode } = cfg;
                const { x: startX, y: startY, coefficientX, coefficientY } = sourceNode
                  ? sourceNode.getModel()
                  : startPoint;
                const { x: endX, y: endY } = targetNode ? targetNode.getModel() : endPoint;
                let curveStart = (endX - startX) * coefficientX;
                let curveEnd = (endY - startY) * coefficientY;
                curveStart = curveStart > 40 ? 40 : curveStart;
                curveEnd = curveEnd < -30 ? curveEnd : -30;
                controlPoints = [
                  { x: startPoint.x + curveStart, y: startPoint.y },
                  { x: endPoint.x + curveEnd, y: endPoint.y },
                ];
              }
              return controlPoints;
            },
            getPath(points) {
              const path = [];
              path.push(['M', points[0].x, points[0].y]);
              path.push([
                'C',
                points[1].x,
                points[1].y,
                points[2].x,
                points[2].y,
                points[3].x,
                points[3].y,
              ]);
              return path;
            },
          },
          'single-line',
        );
      },
      initGraph(data){
        const container = document.getElementById('container');
        const width = container.scrollWidth;
        const height = container.scrollHeight || 500;
        if (!data) {
          return;
        }
        const { onInit, config } = props;
        const tooltip = new G6.Tooltip({
          // offsetX and offsetY include the padding of the parent container
          offsetX: 20,
          offsetY: 30,
          // the types of items that allow the tooltip show up
          // 允许出现 tooltip 的 item 类型
          itemTypes: ['node'],
          // custom the tooltip's content
          // 自定义 tooltip 内容
          getContent: (e) => {
            const outDiv = document.createElement('div');
            //outDiv.style.padding = '0px 0px 20px 0px';
            const nodeName = e.item.getModel().name;
            let formatedNodeName = '';
            for (let i = 0; i < nodeName.length; i++) {
              formatedNodeName = `${formatedNodeName}${nodeName[i]}`;
              if (i !== 0 && i % 20 === 0) formatedNodeName = `${formatedNodeName}<br/>`;
            }
            outDiv.innerHTML = `${formatedNodeName}`;
            return outDiv;
          },
          shouldBegin: (e) => {
            if (e.target.get('name') === 'name-shape' || e.target.get('name') === 'mask-label-shape') return true;
            return false;
          },
        });
        this.graph = new G6.TreeGraph({
          container: 'container',
          defaultConfig:this.defaultConfig,
          config:config,
          plugins: [tooltip],
        });
        if (typeof onInit === 'function') {
          onInit(graph);
        }
        this.graph.data(data);
        this.graph.render();

        const handleCollapse = (e) => {
          const target = e.target;
          const id = target.get('modelId');
          const item = graph.findById(id);
          const nodeModel = item.getModel();
          nodeModel.collapsed = !nodeModel.collapsed;
          graph.layout();
          graph.setItemState(item, 'collapse', nodeModel.collapsed);
        };
        this.graph.on('collapse-text:click', (e) => {
          handleCollapse(e);
        });
        this.graph.on('collapse-back:click', (e) => {
          handleCollapse(e);
        });

        // 监听画布缩放，缩小到一定程度，节点显示缩略样式
        let currentLevel = 1;
        const briefZoomThreshold = Math.max(this.graph.getZoom(), 0.5);
        this.graph.on('viewportchange', e => {
          if (e.action !== 'zoom') return;
          const currentZoom = graph.getZoom();
          let toLevel = currentLevel;
          if (currentZoom < briefZoomThreshold) {
            toLevel = 0;
          } else {
            toLevel = 1;
          }
          if (toLevel !== currentLevel) {
            currentLevel = toLevel;
            this.graph.getNodes().forEach(node => {
              this.graph.updateItem(node, {
                level: toLevel
              })
            })
          }
        });
      }
  },
  
}
</script>

<style>

</style>