创新项目实训 —— 使用relation-graph组件搭建一个功能模型图工具

目录

一、实现目标

二、组件的使用

三、功能的具体实现

图像的初始化

产生新节点

衍生后继结点

节点编辑

根据节点类型分别显示

删除节点

删除关系

建立连接关系


一、实现目标

实现一个功能关系图形工具,可以用点和线的形式描节点(分为有用节点和有害节点)之间的关系,并对节点和连线进行增删改查等操作。

具体功能实现成果如下:

1. 从根节点衍生功能部件

2. 从功能部件中衍生后继功能部件

 3. 编辑功能部件

 4. 删除功能部件

 5. 选择节点,另其与其他节点建立联系

 6. 删除联系

 7. 重置图形

8. 按部件类型分类显示

 

二、组件的使用

组件引入:

npm install --save relation-graph

yarn add relation-graph --save

在Vue Component中引入

  import SeeksRelationGraph from 'relation-graph'

  export default {
    components: {
      SeeksRelationGraph
    },
}

组件的使用

<!--    作图工具    -->

<div style="width: calc(100% - 20px); height:calc(100vh - 100px); padding-top: 20px">
    <SeeksRelationGraph
       ref="seeksRelationGraph"
       :options="graphOptions"
       :on-node-click="onNodeClick"
       :on-line-click="onLineClick"/>         
</div>
     

组件库文档

relation-graphicon-default.png?t=M3C8http://www.relation-graph.com/#/docs/start

三、功能的具体实现

图像的初始化

relation-graph的图形结构由三部分组成:根节点、节点列表、连接列表,其中根节点和节点列表中根节点信息是初始化时必不可少的,是整个图形的基础。

setGraphData() {
        let init_graph_data = {
          rootId: 'root',
          nodes: [{id: 'root', name: '你的发明创造', color: '#c9c0d3', nodeShape: 1, width: 130, height: 60}],
          links: []
        }
        this.$refs.seeksRelationGraph.setJsonData(init_graph_data, (seeksRGGraph) => {
          // 这些写上当图谱初始化完成后需要执行的代码
          this.graphs = init_graph_data
        })
      },

根节点是整个图形中不可删除的部分,因此,根据需要,若不希望显示根节点,可以设置根节点的isHide属性为true。

(上述处理方式可能会在图像存取方面出现问题,若没有存取反复使用的需求可忽略)

另一方面,在初始化图形数据(或进行节点、关系的操作)后,图形会自动添加上一些其他的属性,这使得官方的getJSONData获得的并不是单纯的图形数据。因此,需要单独使用一个JSON对象记录当前的图形数据(可选择存入session中)。

产生新节点

这里的产生新节点指的是从根节点衍生节点,作为整个功能模型的基础功能。

addNewNode() {
        if (this.nodeName === '' || this.radio === ''){
          this.$message.error('请完善节点信息');
          return
        }
        for (let i = 0; i < this.graphs.nodes.length; i++){
          if (this.nodeName === this.graphs.nodes[i].name){
            this.$message.error('该功能部件已存在');
            return
          }
        }
        const graph = this.$refs.seeksRelationGraph;
        let node_color = '';
        if (this.radio === 1)
          node_color = '#89B8CA'
        else
          node_color = '#ca8989'
        const __graph_json_data = {
          nodes: [
            {
              id: this.nodeName,
              name: this.nodeName,
              color: node_color,
              x: (graph.getNodes().length + 4) * 150,
              y: 200,
            }
          ],
          links: [
            {from: 'root', to: this.nodeName, text: '需要'}
          ]
        };
        graph.appendJsonData(__graph_json_data, true, (seeksRGGraph) => {
          // 这些写上当图谱初始化完成后需要执行的代码
          this.$notification.open({
            title: '成功添加功能部件',
            type: 'success',
            message: '添加功能部件:' + this.nodeName
          })
          this.nodeName = ''
          this.radio = ''
          this.visible = false
          this.graphs.nodes.push(__graph_json_data.nodes[0])
          this.graphs.links.push(__graph_json_data.links[0])
          // console.log(this.graphs)
          this.selectingNode = false
          sessionStorage.setItem('graph', JSON.stringify(this.graphs))
        })
      },

这里设定了节点的id和名称相同,并限定创建节点时,节点名称不得相同。

官方文档中使用的初始化方式为根据 当前节点列表长度+1 作为下一个节点的id,这样的处理方式会在删除某个节点后重新添加节点时出现问题。

另一方面,节点初始化后产生的位置是重叠的,若存在一次添加多个节点的情况,可以设定节点坐标分散优化用户体验。

衍生后继结点

addPostposition() {
        if (this.nodeName === '' || this.radio === ''){
          this.$message.error('请完善节点信息');
          return
        }
        for (let i = 0; i < this.graphs.nodes.length; i++){
          if (this.nodeName === this.graphs.nodes[i].name){
            this.$message.error('该功能部件已存在');
            return
          }
        }
        const graph = this.$refs.seeksRelationGraph;
        let node_color = '';
        if (this.radio === 1)
          node_color = '#89B8CA'
        else
          node_color = '#ca8989'
        const __graph_json_data = {
          nodes: [
            {
              id: this.nodeName,
              name: this.nodeName,
              color: node_color,
              x: (graph.getNodes().length + 4) * 150,
              y: 200,
            }
          ],
          links: [
            {from: this.currentNode.id, to: this.nodeName, text: '产生'}
          ]
        };
        graph.appendJsonData(__graph_json_data, true, (seeksRGGraph) => {
          // 这些写上当图谱初始化完成后需要执行的代码
          this.$notification.open({
            title: '成功添加功能部件',
            type: 'success',
            message: '添加功能部件:' + this.nodeName
          })
          this.nodeName = ''
          this.radio = ''
          this.visible = false
          this.graphs.nodes.push(__graph_json_data.nodes[0])
          this.graphs.links.push(__graph_json_data.links[0])
          this.selectingNode = false
          this.checkContradiction(this.currentNode)
          sessionStorage.setItem('graph', JSON.stringify(this.graphs))
        })
      },

这一功能的实现与创建新节点十分类似,只需要将连接的from设定为当前选定的节点即可。

节点编辑

editNode() {
        if (this.nodeName === '' || this.radio === ''){
          this.$message.error('请完善节点信息');
          return
        }
        for (let i = 0; i < this.graphs.nodes.length; i++){
          if (this.currentNode.id !== this.graphs.nodes[i].id &&
            this.nodeName === this.graphs.nodes[i].name)
          {
            this.$message.error('该功能部件已存在');
            return
          }
        }
        const graph = this.$refs.seeksRelationGraph;
        let node_color = '';
        if (this.radio === 1)
          node_color = '#89B8CA'
        else
          node_color = '#ca8989'

        for (let i = 0; i < this.graphs.nodes.length; i++){
          if (this.currentNode.id === this.graphs.nodes[i].id){
            this.graphs.nodes[i].id = this.nodeName
            this.graphs.nodes[i].name = this.nodeName
            this.graphs.nodes[i].color = node_color
            this.graphs.nodes[i].text = this.nodeName
          }
        }
        for (let i = 0; i < this.graphs.links.length; i++){
          if (this.currentNode.id === this.graphs.links[i].from){
            this.graphs.links[i].from = this.nodeName
          }
          if (this.currentNode.id === this.graphs.links[i].to){
            this.graphs.links[i].to = this.nodeName
          }
        }
        this.$refs.seeksRelationGraph.setJsonData(this.getUsableData(this.graphs))
        sessionStorage.setItem('graph',JSON.stringify(this.getUsableData(this.graphs)))
        this.visible = false
        this.selectingNode = false
      },

由于前面设定了节点的名称和id相同,因此在编辑节点名称时,节点id也会随之变化。因此需要对该节点相关的连接信息进行重新设定。

根据节点类型分别显示

根据节点颜色区分,通过设定透明度实现。

showNodes(nodeType) {
        const graph = this.$refs.seeksRelationGraph
        for (let i = 0; i < this.graphs.nodes.length; i++){
          graph.getNodeById(this.graphs.nodes[i].id).opacity = 1
        }
        let avilColor = ''
        if (nodeType === 'useful')
          avilColor = '#ca8989'
        else if (nodeType === 'harmful')
          avilColor = '#89B8CA'

        for (let i = 0; i < this.graphs.nodes.length; i++){
          if (this.graphs.nodes[i].color === avilColor){
            graph.getNodeById(this.graphs.nodes[i].id).opacity = 0.3
          }
        }
      },

删除节点

removeNode(){
        let tmpNodes = []
        for (let i = 0; i < this.graphs.nodes.length; i++){
          if (this.currentNode.id !== this.graphs.nodes[i].id){
            tmpNodes.push(this.graphs.nodes[i])
          }
        }
        this.graphs.nodes = tmpNodes
        for (let i = 0; i < this.graphs.links.length; i++){
          if (this.currentNode.id === this.graphs.links[i].from){
            this.graphs.links[i].from = 'root'
            this.graphs.links[i].isHide = true

            // 删除后自动把悬浮点挂在根节点上
            // this.graphs.links[i].text = '需要'
          }
          if (this.currentNode.id === this.graphs.links[i].to){
            this.graphs.links[i].to = 'root'
            this.graphs.links[i].isHide = true
          }
        }
        this.$refs.seeksRelationGraph.setJsonData(this.getUsableData(this.graphs))
        sessionStorage.setItem('graph',JSON.stringify(this.getUsableData(this.graphs)))
        this.selectingNode = false
        // this.selectingLine = false
      },

这是功能中实现起来较为复杂的部分,实现的方式也并不理想。

在官方文档中有删除节点方法,但是调用后再次读取图形时,与该节点相关的节点坐标会被设定为无穷大(由连接关系导致)。无奈之下,只能重写该方法。

直接删除节点会导致关系目标缺失,导致节点无法正确显示,因此这里采取下策,在节点列表中删除节点,然后将与该节点关联的关系隐藏,并将与该节点相关的节点(包括前继节点和后继节点)连接到根节点上。

删除关系

与上述处理类似,这里选择将关系隐藏,否则会出现同样的节点坐标无穷大。

removeRelation(){
        for (let i = 0; i < this.graphs.links.length; i++){
          if (this.currentLink.fromNode.id === this.graphs.links[i].from
            && this.currentLink.toNode.id === this.graphs.links[i].to){
            this.graphs.links[i].isHide = true
          }
        }
        this.$refs.seeksRelationGraph.setJsonData(this.getUsableData(this.graphs))
        sessionStorage.setItem('graph',JSON.stringify(this.getUsableData(this.graphs)))
        this.selectingLine = false
      },

建立连接关系

在建立连接关系之前,需要判断符合建立连接关系的条件:不能是当前节点的前继节点、不能是当前节点的后继节点、不能是根节点。

因此在选中节点时,需要计算出符合建立条件的节点列表。

onNodeClick(nodeObject) {
        const graph = this.$refs.seeksRelationGraph
        this.$notification.open({
          type: 'success',
          message: '选择节点:' + nodeObject.text
        })
        this.currentNode = nodeObject

        this.selectingNode = this.currentNode.id !== 'root'
        this.selectingLine = false

        let posNodes = this.getPosNodes(nodeObject)
        let frontNodes = this.getFrontNodes(nodeObject)

        this.selectableNodes = []

        // 找可建立关系节点
        for (let i = 0; i < this.graphs.nodes.length; i++){
          let nodeItem = {}
          if (this.graphs.nodes[i].id !== 'root' && this.graphs.nodes[i].id !== nodeObject.id
            && !this.array_contain(posNodes, this.graphs.nodes[i].id)
            && !this.array_contain(frontNodes, this.graphs.nodes[i].id)
          )
          {
            nodeItem.id = this.graphs.nodes[i].id
            nodeItem.label = graph.getNodeById(nodeItem.id).text
            this.selectableNodes.push(nodeItem)
          }
        }
        sessionStorage.setItem('selectableNodes', JSON.stringify(this.selectableNodes))
        this.selectableNodes = []
      },
      getPosNodes(nodeObject){
        // 找后置节点
        let posNodes = []
        console.log(this.graphs.links)

        // 可见的连接
        let visibleLinks = []
        for (let i = 0; i < this.graphs.links.length; i++){
          if (this.graphs.links[i].isHide !== true){
            visibleLinks.push(this.graphs.links[i])
          }
        }
        // console.log(visibleLinks)
        for (let i = 0; i < visibleLinks.length; i++){
          if (visibleLinks[i].from === nodeObject.id){
            posNodes.push(visibleLinks[i].to)
          }
        }
        // console.log(posNodes)
        return posNodes
      },
      getFrontNodes(nodeObject){
        // 找前置节点
        let frontNodes = []

        // 可见的连接
        let visibleLinks = []
        for (let i = 0; i < this.graphs.links.length; i++){
          if (this.graphs.links[i].isHide !== true){
            visibleLinks.push(this.graphs.links[i])
          }
        }
        for (let i = 0; i < visibleLinks.length; i++){
          if (visibleLinks[i].to === nodeObject.id){
            frontNodes.push(visibleLinks[i].from)
          }
        }
        return frontNodes
      },

建立连接关系

addNewRelation() {
        if (this.selectedNodes.length === 0){
          this.$message.error('请选择关联节点');
          return
        }
        for (let i = 0; i < this.selectedNodes.length; i++){
          const graph = this.$refs.seeksRelationGraph;
          const __graph_json_data = {
            nodes: [],
            links: [
              {from: this.currentNode.id, to: this.selectedNodes[i].key, text: '产生'}
            ]
          };
          graph.appendJsonData(__graph_json_data, true, (seeksRGGraph) => {
            // 这些写上当图谱初始化完成后需要执行的代码
            this.$notification.open({
              title: '成功添加关系',
              type: 'success',
              message: '添加关系:' + graph.getNodeById(this.currentNode.id).text + ' 产生 ' + graph.getNodeById(this.selectedNodes[i].key).text
            })
            this.selectVisible = false
            this.graphs.links.push(__graph_json_data.links[0])
            this.checkContradiction(this.currentNode)
            // console.log(this.graphs)
            this.selectingNode = false
            sessionStorage.setItem('graph', JSON.stringify(this.graphs))
          })
        }
        this.selectedNodes = []
      },

以上便是重要功能的实现,后续将上传完整代码资源。

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mai゛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值