antvis / G6

♾ A Graph Visualization Framework in JavaScript.
https://g6.antv.antgroup.com/
MIT License
11.05k stars 1.31k forks source link

自定义DOM节点中使用React或者Vue组件 #1335

Closed ZionDoki closed 4 years ago

ZionDoki commented 4 years ago

请问,自定义 dom 节点有办法使用 React 或者 Vue 组件么?

Yanyan-Wang commented 4 years ago

目前是不可以的,只能用普通的 DOM 标签。

dannyChyang commented 4 years ago

我的解决思路:

  1. 根据数据手动创建组件实例
  2. graph节点渲染出dom容器
  3. layout结束事件后,将组件挂载在graph的节点生成的dom中

出现的问题: zoom、drag时会重绘,组件状态会丢失

<template lang="pug">
  div.graph(ref="main") hello,word
</template>

<script>
import Vue from 'vue'
import G6 from '@antv/g6'
import Panel from './Panel'

// recursion treeData
function _recursion(memo, item) {
  const ins = new (Vue.extend(Panel))({
    propsData: {
      detail: item
    }
  }).$mount()
  memo[item.id] = ins
  if (item.children) {
    item.children.reduce(_recursion, memo)
  }
  return memo
}
function _generateDOMId(id) {
  return `panel-item${id}`
}

G6.registerNode(
  'dom-node',
  {
    draw: (cfg, group) => {
      const {
        size: [w, h],
        domId,
        children,
        ...rest
      } = cfg
      const rectConfig = {
        ...rest,
        children,
        x: 0,
        y: 0,
        width: w,
        height: h
      }

      const rect = group.addShape('dom', {
        attrs: {
          ...rectConfig,
          html: `<div id="${domId}"></div>`
        }
      })
      return rect
    }
  },
  'single-node'
)
export default {
  name: 'MyGraph',
  props: {
    data: {
      type: Array,
      default: () => []
    }
  },
  data() {
    return {
      graphIns: null
    }
  },
  computed: {
    panelInsMap(vm) {
      return vm.data.reduce(_recursion, {})
    }
  },
  mounted() {
    const rect = this.$refs.main.getBoundingClientRect()

    const graph = new G6.TreeGraph({
      container: this.$refs.main,
      renderer: 'svg',
      animate: false,
      width: rect.width,
      height: rect.height,
      defaultNode: {
        type: 'dom-node'
      }
    })
    graph.node(cfg => {
      return {
        ...cfg,
        domId: _generateDOMId(cfg.id)
      }
    })
    graph.on('afterlayout', this.appendVueInsToGNode)
    graph.on('dragend', this.appendVueInsToGNode)
    graph.on('wheelzoom', this.appendVueInsToGNode)
    this.graphIns = graph
  },
  beforeDestroy() {
    if (this.graphIns) {
      this.graphIns.off('afterlayout', this.appendVueInsToGNode)
      this.graphIns.off('dragend', this.appendVueInsToGNode)
      this.graphIns.off('wheelzoom', this.appendVueInsToGNode)
      this.graphIns.destroy()
    }
    // destroy VueIns
    Object.values(this.panelInsMap).forEach(ins => {
      ins.$destroy()
    })
  },
  methods: {
    appendVueInsToGNode() {
      for (let id in this.panelInsMap) {
        const ins = this.panelInsMap[id]
        const container = document.querySelector(`#${_generateDOMId(id)}`)
        container && container.appendChild(ins.$el)
      }
    }
  },
  watch: {
    data: {
      handler(val) {
        const { graphIns } = this
        if (graphIns) {
          graphIns.clear()
          graphIns.data(val[0])
          graphIns.render()
        }
      },
      immediate: true
    }
  }
}
</script>