antv g6 4.x vue 决策树

原型:https://g6-v4.antv.vision/examples/case/treeDemos/#decisionTree

 

<template>
  <div class="main-content-box">
    <div id="container"></div>
  </div>
</template>

<script>
import api from '@/api';
import G6 from '@antv/g6';
import insertCss from 'insert-css';
const colors = {
  B: '#5B8FF9',
  R: '#F46649',
  Y: '#EEBC20',
  G: '#5BD8A6',
  DI: '#A7A7A7'
};

export default {
  indexName: 'SubjectDependenceTree',
  components: {},
  props: {
    isTreeDialog: Boolean
  },
  data() {
    return {
      list: [], //扁平结构的数据(从接口获取)
      noRepeatList: [], //存放剔除重复值之后的数据
      mockData: {},
      nodeChildren: [], //单个node子级数据
      childrenNode: {} //存放子节点点击的node
    };
  },
  mounted() {
    let params = { indexCode: 'POLICY_AWARDS_PUNISH_MANAGE', reportTheme: 0, isFirst: true };
    this.searchQueryIndexTree(params);
  },
  methods: {
    getInit() {
      let vm = this;
      G6.registerNode(
        'flow-rect',
        {
          shapeType: 'flow-rect',
          draw(cfg, group) {
            const { indexName = '', indexCode, isChild, collapsed, indexCategory, status } = cfg;

            const grey = '#CED4D9';
            // background: #0092ee;
            // color: white;
            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();

            //科目编码
            group.addShape('text', {
              attrs: {
                ...textConfig,
                x: 8 + nodeOrigin.x,
                y: 23 + nodeOrigin.y,
                text: indexCode.length > 23 ? indexCode.substr(0, 23) + '...' : indexCode,
                fontSize: 12,
                fill: '#000',
                opacity: 0.85,
                cursor: 'pointer'
              },
              name: 'name-shape'
            });

            //  科目名称
            const price = group.addShape('text', {
              attrs: {
                ...textConfig,
                x: 12 + nodeOrigin.x,
                y: rectBBox.maxY - 12,
                text: indexName.length > 10 ? indexName.substr(0, 10) + '...' : indexName,
                fontSize: 12,
                opacity: 0.85,
                fill: '#000'
              }
            });

            // 科目类型
            group.addShape('text', {
              attrs: {
                ...textConfig,
                x: price.getBBox().maxX + 5,
                y: rectBBox.maxY - 12,
                text: indexCategory == 1 ? '离线指标' : indexCategory == 2 ? '指标填报' : '自定义',
                fontSize: 10,
                fill: '#0092ee',
                opacity: 0.75
              }
            });

            // rect  展开节点( + -边框)
            if (cfg.isChild) {
              group.addShape('rect', {
                attrs: {
                  x: rectConfig.width / 2 - 8,
                  y: -8,
                  width: 16,
                  height: 16,
                  stroke: 'rgba(0, 0, 0, 0.25)',
                  cursor: 'pointer',
                  radius: 8,
                  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;
          },
          //       * 响应节点的状态变化。
          //  * 在需要使用动画来响应状态变化时需要被复写
          setState(name, value, item) {
            if (name === 'collapse') {
              const group = item.getContainer();
              const collapseText = group.find((e) => e.get('name') === 'collapse-text');
              if (collapseText) {
                console.log(value);
                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'
      );
      const container = document.getElementById('container');
      const width = container.scrollWidth;
      const height = container.scrollHeight || 500;

      const graph = new G6.TreeGraph({
        container: 'container',
        width,
        height,
        padding: [20, 50],
        // defaultLevel: 3,
        defaultZoom: 0.3,
        // minZoom: 0.3,
        // maxZoom: 1.5,
        modes: {
          default: ['zoom-canvas', 'drag-canvas']
        },
        fitView: false, //是否启用画布自适应,适配画布大小
        animate: false, //是否启用全部动画
        defaultNode: {
          type: 'flow-rect'
        },
        // 设置边的参数
        defaultEdge: {
          type: 'cubic-horizontal',
          style: {
            stroke: '#CED4D9'
          }
        },
        layout: {
          type: 'indented',
          direction: 'LR',
          dropCap: false,
          preventOverlap: true, // 防止节点重叠
          indent: 300,
          getHeight: () => {
            return 40;
          }
        }
      });

      graph.data(this.mockData[0]);
      graph.render();
      graph.fitView(100); //适配视图,自动缩放和平移以适应内容大小,默认留白为 30px
      graph.zoom(0.6); // 设置缩放级别为50%

      const handleCollapse = async (e) => {
        this.childrenNode = [];
        const target = e.target;
        const id = target.get('modelId');
        const item = graph.findById(id);
        const nodeModel = item.getModel();
        const children = nodeModel.children;
        if (!children || children.length === 0) {
          this.childrenNode = nodeModel;
          let params = {
            indexCode: nodeModel.indexCode, //科目编码
            reportTheme: 0, //主题
            isFirst: false //初始化三层依赖树true,后面展开第四层或往后 false
          };
          this.addTree(params);
          if (!nodeModel.children) {
            nodeModel.children = [];
          }
          // 如果childData是一个数组,则直接赋值给parentData.children
          // 如果是一个对象,则使用parentData.children.push(obj)
          setTimeout(() => {
            if (this.nodeChildren && this.nodeChildren.length > 0) {
              nodeModel.isChild = true;
              nodeModel.children = this.nodeChildren;
              nodeModel.collapsed = false;
            } else {
              //子级存在单个重复数据
              nodeModel.isChild = false;
              nodeModel.children = [];
              nodeModel.collapsed = true;
            }
            item.update(nodeModel);
          }, 300);
        } else {
          nodeModel.collapsed = !nodeModel.collapsed;
          // graph.updateItem(id, nodeModel);
          item.update(nodeModel);
        }
        setTimeout(() => {
          graph.render();
          // graph.refresh();
          // graph.layout();
          item.updatePosition(nodeModel);
          graph.focusItem(item); //将当前的节点设置为焦点
        }, 500);
      };
      graph.on('collapse-text:click', (e) => {
        handleCollapse(e);
      });
      graph.on('collapse-back:click', (e) => {
        handleCollapse(e);
      });
      this.$nextTick(() => {
        if (typeof window !== 'undefined')
          window.onresize = () => {
            if (!graph || graph.get('destroyed')) return;
            if (!container || !container.scrollWidth || !container.scrollHeight) return;
            graph.changeSize(container.scrollWidth, container.scrollHeight);
          };
      });
    },
    // 展开子结构
    async addTree(params) {
      this.list = [];
      this.nodeChildren = [];
      const res = await api.queryIndexTree(params);
      if (res.success) {
        this.list = res.data;
        this.nodeChildren = this.comparativeData(); //子级剔除重复数据
        this.noRepeatList = this.noRepeatList.concat(this.comparativeData()); //剔除重复数据
      } else {
        this.$message.error(`${res.message}`);
      }
    },

    // 查询结构树初始化前三层
    async searchQueryIndexTree(params) {
      this.list = [];
      const res = await api.queryIndexTree(params);
      if (res.success) {
        this.list = res.data;
        this.noRepeatList = this.noRepeatList.concat(this.comparativeData()); //剔除重复数据
        this.mockData = this.toTree(this.noRepeatList); //将扁平化数据转换为树结构

        this.$nextTick(() => {
          setTimeout(() => {
            this.getInit();
          }, 500);
        });
      } else {
        this.$message.error(`${res.message}`);
      }
    },
    //对比是否存在重复数据,同层级存在重复数据后点击的节点isChild=false,collapsed=false
    comparativeData() {
      let arr = [];
      let hasUsefulNode = false;
      let result = this.list.map((i) => {
        let one = this.noRepeatList.findIndex((k) => k.indexCode == i.indexCode);
        if (one < 0) {
          hasUsefulNode = true;
          arr.push({
            id: i.id,
            indexCode: i.indexCode,
            indexName: i.indexName,
            parentId: i.parentId,
            indexCategory: i.indexCategory, //类型
            collapsed: !i.isOpen, //控制是否展开
            isChild: i.isChild // 控制是否有子级
          });
        }
      });
      if (!hasUsefulNode) {
        let one = this.noRepeatList.find((k) => k.indexCode == this.childrenNode.indexCode);
        if (one) {
          one.isChild = false;
          one.collapsed = true;
        }
      }
      return arr;
    },

    //打平数据转换为树结构
    toTree(items) {
      const tree = []; //存放最终的树状结构
      const itemMap = {}; //存放每个节点数据

      for (const item of items) {
        const { indexCode } = item;
        itemMap[indexCode] = { ...item, children: [] }; //每个节点增加一个children属性,用来存放子节点
      }

      // 遍历所有节点,将每个节点放到其父节点的children数组中
      for (const item of items) {
        const { indexCode, parentId } = item;

        // 如果是根节点,则直接放入结果数组中
        if (indexCode == parentId) {
          //if (parentId == null || parentId == 0) {
          tree.push(itemMap[indexCode]);
        } else {
          // 如果不是根节点,则将当前节点放入其父节点的children数组中
          // 子元素的parentId 等于  父节点的id  itemMap[parentId] 父节点
          //itemMap[indexCode] 当前节点
          if (itemMap[parentId]) itemMap[parentId].children.push(itemMap[indexCode]);
        }
      }
      return tree;
    }
  }
};
</script>
<style lang="scss" scoped>
.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;
}
</style>

 

posted @ 2025-03-06 09:59  潇可爱❤  阅读(127)  评论(0)    收藏  举报