vue使用GraphVis开发无限拓展的关系图谱的实现

1、去GraphVis官网下载对应的js,新版和旧版的js有所不同,看自己需求引入旧版还是新版(GraphVis官方网址:http://www.graphvis.cn/

  • visgraph.min.js (基本配置js)
  • visgraph-layout.min.js(配置布局js)

2、在需要的vue文件引入js文件

import VisGraph from '@/assets/js/GraphVis/old/visgraph.min.js' // 自己对应的js文件位置
import LayoutFactory from '@/assets/js/GraphVis/old/visgraph-layout.min.js' // 自己对应的js文件位置
export default { components: { VisGraph, LayoutFactory } }

3、加载画布和配置

配置(自己根据需求修改配置):

config: {
  // 节点配置
          node: {
            label: { // 标签配置
              show: true, // 是否显示
              color: '250,250,250', // 字体颜色
              font: 'normal 14px Microsoft YaHei', // 字体大小及类型
              textPosition: 'Middle_Center', // 字体位置
              wrapText: true // 节点包裹文字(该属性为true时只对于字体位置为Middle_Center时有效)
            },
            shape: 'circle', // 节点形状 circle,rect,square,ellipse,triangle,star,polygon,text
            // width: 60, // 节点宽度(只对于shape为rect时有效)
            // height: 60, // 节点高度(只对于shape为rect时有效)
            color: '62,160,250', // 节点颜色
            borderColor: '62,160,250', // 节点边框颜色
            borderWidth: 0, // 节点边框宽度
            borderRadius: 0, // 节点圆角
            lineDash: [0], // 节点边框线条类型 [0] 表示实线 [5,8] 表示虚线 borderWidth > 0有效
            alpha: 1, // 节点透明度
            size: 60, // 节点大小
            selected: { // 节点选中后样式
              borderColor: '136,198,255', // 选中时边框颜色
              borderAlpha: 1, // 选中时的边框透明
              borderWidth: 3, // 选中是的边框宽度
              showShadow: true, // 是否展示阴影
              shadowColor: '136,198,255' // 选中是的阴影颜色
            }
          },
          // 线条配置
          link: {
            label: { // 标签配置
              show: true, // 是否显示
              color: '100,100,200', // 标签颜色
              font: 'normal 10px Arial' // 标签文字大小及类型
            },
            lineType: 'direct', // 线条类型direct,curver,vlink,hlink,bezier,vbezier,hbezier
            colorType: 'defined', // 连线颜色类型 source:继承source颜色,target:继承target颜色 both:用双边颜色,defined:自定义
            color: '200,200,200', // 线条颜色
            alpha: 1, // 连线透明度
            lineWidth: 1, // 连线宽度
            lineDash: [0], // 虚线间隔样式如:[5,8]
            showArrow: true, // 显示箭头
            selected: { // 选中时的样式设置
              color: '20,250,50', // 选中时的颜色
              alpha: 1, // 选中时的透明度
              lineWidth: 4, // 选中线条宽度
              showShadow: true, // 显示阴影
              shadowColor: '50,250,50' // 阴影颜色
            }
          },
          highLightNeiber: true, // 相邻节点高度标志
          wheelZoom: 0.8 // 滚轮缩放开关,不使用时不设置[0,1]
}

加载画布:

this.visgraph = new VisGraph(
        document.getElementById(this.canvasId),
        this.canvasConfig
      )
this.visgraph.clearAll()
this.visgraph.drawData(this.graphData)

4、拓展功能:

  无限拓展子节点,双击节点触发(ondblClick): 

this.visgraph.restoreHightLight() // 取消高亮
const allNodes = this.visgraph.getVisibleData()
this.currentNode.push(node.id)
allNodes.nodes.forEach(item => {
    if (this.currentNode.indexOf(item.id) === (-1)) {
         this.visgraph.deleteNode(item)
    }
})
const findNodeNum = Math.round(Math.random() * 50)
const increamData = this.buildIncreamData(node, findNodeNum)
this.visgraph.activeAddNodeLinks(increamData.nodes, increamData.links)
this.visgraph.translateToCenter()

完整代码(relation.vue):

<!--
 * @Author: CarlYang
 * @Date: 2021-07-23 15:31:51
 * @LastEditTime: 2021-07-30 09:46:05
 * @LastEditors: Please set LastEditors
 * @Description: 关系图谱
 * @FilePath: \vue-g6\src\views\GraphVis\company.vue
-->
<template>
  <div id="container">
    <!-- ============================================= 画布视图 ============================================= -->
    <div
      id="graph-panel"
      ref="graphpanel"
      @contextmenu="globalClickedDispatch"
    ></div>

    <!-- ============================================= 左侧工具栏 ============================================= -->
    <div class="left-toolbar">
      <ul>
        <li @click="setZoomOut" title="放大">
          <i class="iconfont icon-zoomin"></i>
        </li>
        <li @click="setZoomIn" title="缩小">
          <i class="iconfont icon-zoomout"></i>
        </li>
        <li @click="saveImage" title="保存图片">
          <i class="iconfont icon-baocun-"></i>
        </li>
        <li @click="exportJson" title="导出JSON">
          <i class="iconfont icon-json"></i>
        </li>
        <li @click="showOverView" title="缩略图">
          <i class="iconfont icon-suolvetu" style="font-size: 14px"></i>
        </li>
        <li @click="clockwiseRotate" title="顺时针旋转">
          <i class="iconfont icon-shunshizhenfangxiangclockwise4" style="font-size: 14px"></i>
        </li>
        <li @click="counterclockwiseRotate" title="逆时针旋转">
          <i class="iconfont icon-nishizhencounterclockwise3" style="font-size: 14px"></i>
        </li>
        <li @click="setMouseModel('normal')" title="正常模式">
          <i class="iconfont icon-pointer-up"></i>
        </li>
        <li @click="setMouseModel('drag')" title="拖拽模式">
          <i class="iconfont icon-line-dragmovetuozhuai-01"></i>
        </li>
        <li @click="setMouseModel('select')" title="框选模式">
          <i class="iconfont icon-kuangxuan1"></i>
        </li>
        <li @click="fullScreen" title="全屏显示">
          <i class="iconfont icon-quanping" style="font-size: 20px"></i>
        </li>
      </ul>
    </div>

    <!-- ============================================= 右键菜单 ============================================= -->
    <div id="nodeMenuDialog" class="nodeMenuDialog">
      <ul>
        <li @click="clickNodeInfo">节点信息</li>
        <li @click="settingNode">配置节点</li>
        <li @click="selectRelation">选中关联</li>
        <li @click="deleteNode">删除节点</li>
        <li @click="contractNode">收起节点</li>
        <li @click="expandedNode">展开节点</li>
      </ul>
    </div>

    <!-- ============================================= 节点信息弹框 ============================================= -->
    <el-drawer
      title="节点信息"
      :visible.sync="nodeInfoDrawer"
      direction="rtl"
      :modal="false"
      size="20%"
    >
      <div class="nodeInfo">
        <el-form class="nodeInfoForm" ref="nodeInfoForm" :model="nodeInfoForm" label-width="80px">
          <el-form-item label="节点名称">
            <el-input v-model="nodeInfoForm.label"></el-input>
          </el-form-item>
          <el-form-item label="节点ID">
            <el-input v-model="nodeInfoForm.id"></el-input>
          </el-form-item>
        </el-form>
        <el-tabs v-model="nodeInfoActiveName" :stretch="true" class="nodeInfoTabs">
          <el-tab-pane label="关联关系" name="first">
            <div class="nodeInfoRelation">
              <el-collapse v-model="nodeInfoRelationActive">
                <el-collapse-item title="目标节点" name="1">
                  <template slot="title">
                    <el-badge :value="nodeInfoSourceList.length">目标节点</el-badge>
                  </template>
                  <table
                    border="0"
                    cellspacing="0"
                    cellpadding="0"
                    class="nodeInfo-table"
                    v-if="nodeInfoSourceList.length > 0"
                  >
                    <thead>
                      <tr>
                        <th>实体对象</th>
                        <th>关系类型</th>
                      </tr>
                    </thead>
                    <tbody>
                      <tr v-for="(item, index) in nodeInfoSourceList" :key="index">
                        <td
                          :style="{ color: item.color }"
                          style="cursor: pointer;"
                          @click="moveCenterThisNode(item.id)"
                        >{{ item.label }}</td>
                        <td>{{ item.relationType }}</td>
                      </tr>
                    </tbody>
                  </table>
                  <p v-else>无数据</p>
                </el-collapse-item>
                <el-collapse-item title="来源节点" name="2">
                  <template slot="title">
                    <el-badge :value="nodeInfoTargetList.length">来源节点</el-badge>
                  </template>
                  <table
                    border="0"
                    cellspacing="0"
                    cellpadding="0"
                    class="nodeInfo-table"
                    v-if="nodeInfoTargetList.length > 0"
                  >
                    <thead>
                      <tr>
                        <th>实体对象</th>
                        <th>关系类型</th>
                      </tr>
                    </thead>
                    <tbody>
                      <tr v-for="(item, index) in nodeInfoTargetList" :key="index">
                        <td
                          :style="{ color: item.color }"
                          style="cursor: pointer;"
                          @click="moveCenterThisNode(item.id)"
                        >{{ item.label }}</td>
                        <td>{{ item.relationType }}</td>
                      </tr>
                    </tbody>
                  </table>
                  <p v-else>无数据</p>
                </el-collapse-item>
              </el-collapse>
            </div>
          </el-tab-pane>
          <el-tab-pane label="属性" name="second">
            <div class="nodeInfoAttribute">
              <el-table :data="nodeInfoAttributeList" border style="width: 100%">
                <el-table-column prop="name" label="属性名"></el-table-column>
                <el-table-column prop="value" label="属性值"></el-table-column>
              </el-table>
            </div>
          </el-tab-pane>
        </el-tabs>
      </div>
    </el-drawer>

    <!-- ============================================= 节点配置 ============================================= -->
    <el-drawer
      title="节点配置"
      :visible.sync="nodeConfigDrawer"
      direction="rtl"
      :modal="false"
      size="20%"
    >
      <div class="nodeConfig">
        <el-form ref="form" :model="nodeConfigForm" label-width="80px" label-position="left">
          <el-form-item label="名称">
            <el-input v-model="nodeConfigForm.label" placeholder="请输入节点名称"></el-input>
          </el-form-item>
          <el-form-item label="类型">
            <el-select v-model="nodeConfigForm.shape" placeholder="请选择节点类型">
              <el-option
                v-for="(item, index) in nodeTypeList"
                :key="'node' + index"
                :label="item.label"
                :value="item.value"
              ></el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="颜色">
            <div class="form-color">
              <el-input
                v-model="nodeConfigForm.fillColor"
                class="form-input"
                placeholder="请选择颜色"
                readonly
              ></el-input>
              <el-color-picker
                v-model="nodeConfigForm.hexColor"
                class="form-color-select"
                @change="colorChange(nodeConfigForm.hexColor, 'fillColor')"
              ></el-color-picker>
            </div>
          </el-form-item>
          <el-form-item label="大小">
            <el-input v-model="nodeConfigForm.size" placeholder="请输入节点大小" type="number"></el-input>
          </el-form-item>
          <el-form-item label="边框宽度">
            <el-input v-model="nodeConfigForm.borderWidth" placeholder="请输入边框宽度" type="number"></el-input>
          </el-form-item>
          <el-form-item label="边框虚线">
            <el-select v-model="nodeConfigForm.borderDash" placeholder="请选择边框虚线">
              <el-option label="否" :value="false"></el-option>
              <el-option label="是" :value="true"></el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="边框颜色">
            <div class="form-color">
              <el-input
                v-model="nodeConfigForm.borderColor"
                class="form-input"
                placeholder="请选择边框颜色"
                readonly
              ></el-input>
              <el-color-picker
                v-model="nodeConfigForm.borderHexColor"
                class="form-color-select"
                @change="colorChange(nodeConfigForm.borderHexColor, 'borderColor')"
              ></el-color-picker>
            </div>
          </el-form-item>
          <el-form-item label="字体位置">
            <el-select v-model="nodeConfigForm.textPosition" placeholder="请选择字体位置">
              <el-option
                v-for="(item, index) in textPositionList"
                :key="index"
                :label="item.label"
                :value="item.value"
              ></el-option>
            </el-select>
          </el-form-item>
          <el-form-item label="字体样式">
            <el-input v-model="nodeConfigForm.font" placeholder="请输入字体样式"></el-input>
          </el-form-item>
          <el-form-item label="字体颜色">
            <div class="form-color">
              <el-input
                v-model="nodeConfigForm.fontColor"
                class="form-input"
                placeholder="请选择字体颜色"
                readonly
              ></el-input>
              <el-color-picker
                v-model="nodeConfigForm.fontHexColor"
                class="form-color-select"
                @change="colorChange(nodeConfigForm.fontHexColor, 'fontColor')"
              ></el-color-picker>
            </div>
          </el-form-item>
          <el-form-item label="字体背景">
            <div class="form-color">
              <el-input
                v-model="nodeConfigForm.fontBgColor"
                class="form-input"
                placeholder="请选择字体背景"
                readonly
              ></el-input>
              <el-color-picker
                v-model="nodeConfigForm.fontBgHexColor"
                class="form-color-select"
                @change="colorChange(nodeConfigForm.fontBgHexColor, 'fontBgColor')"
              ></el-color-picker>
            </div>
          </el-form-item>
          <el-form-item label="字体偏移">
            <el-input
              v-model="nodeConfigForm.textOffset"
              placeholder="请输入字体偏移"
              type="number"
              max="100"
              min="-100"
            ></el-input>
          </el-form-item>
        </el-form>
        <div class="save-setting">
          <el-button type="primary" @click="saveSetting">保存配置</el-button>
        </div>
      </div>
    </el-drawer>
  </div>
</template>
<script>
import VisGraph from '@/assets/js/GraphVis/old/visgraph.min.js'
import LayoutFactory from '@/assets/js/GraphVis/old/visgraph-layout.min.js'
import screenfull from 'screenfull'
import {
  company
} from '@/assets/js/company.js'
export default {
  name: 'DesignGraph',
  components: {
    VisGraph,
    LayoutFactory
  },
  data() {
    return {
      // 画布实例
      visgraph: null,
      // 中心节点
      centerNode: null,
      // 已选中节点
      currentNode: [],
      // 选中节点信息
      checkedNodeInfo: {},
      // 图谱配置
      config: {
        node: {
          label: {
            show: true,
            color: '250,250,250',
            font: 'normal 14px Microsoft YaHei',
            textPosition: 'Middle_Center',
            borderWidth: 0,
            wrapText: true
          },
          shape: 'circle',
          width: 60,
          height: 60,
          color: '62,160,250',
          borderColor: '62,160,250',
          borderWidth: 0,
          borderRadius: 0,
          lineDash: [0],
          alpha: 1,
          selected: {
            borderColor: '136,198,255',
            borderAlpha: 1,
            borderWidth: 3,
            showShadow: true,
            shadowColor: '136,198,255'
          },
          onClick: (event, node) => {
            // this.visgraph.highLightNeiberNodes(node, 1) // 高亮
          },
          ondblClick: (event, node) => {
            this.visgraph.restoreHightLight() // 取消高亮
            const allNodes = this.visgraph.getVisibleData()
            this.currentNode.push(node.id)
            allNodes.nodes.forEach(item => {
              if (this.currentNode.indexOf(item.id) === (-1)) {
                this.visgraph.deleteNode(item)
              }
            })
            const findNodeNum = Math.round(Math.random() * 50)
            const increamData = this.buildIncreamData(node, findNodeNum)
            this.visgraph.activeAddNodeLinks(increamData.nodes, increamData.links)
            this.visgraph.translateToCenter()
          },
          onMouseOver: (event, node) => { },
          onMouseOut: (event, node) => { }
        },
        link: {
          label: {
            show: true,
            color: '100,100,200',
            font: 'normal 10px Arial'
          },
          lineType: 'direct',
          colorType: 'defined',
          color: '200,200,200',
          alpha: 1,
          lineWidth: 1,
          lineDash: [0],
          showArrow: true,
          selected: {
            color: '20,250,50',
            alpha: 1,
            lineWidth: 4,
            showShadow: true,
            shadowColor: '50,250,50'
          }
        },
        highLightNeiber: true,
        wheelZoom: 0.8,
        noElementClick: (event, _graphvis) => {
          // 点击画布其他位置,弹框隐藏
          this.nodeMenuDialogClose()
        }
      },
      // 节点信息弹框
      nodeInfoDrawer: false,
      // 节点信息表单
      nodeInfoForm: {
        label: '',
        id: ''
      },
      // 节点信息弹框tab选项名称
      nodeInfoActiveName: 'first',
      // 关联关系
      nodeInfoRelationActive: ['1', '2'],
      // 目标节点列表
      nodeInfoTargetList: [],
      // 来源节点列表
      nodeInfoSourceList: [],
      // 节点属性列表
      nodeInfoAttributeList: [],
      // 节点配置弹框
      nodeConfigDrawer: false,
      // 节点配置表单
      nodeConfigForm: {
        label: '',
        shape: '',
        fillColor: '',
        hexColor: '',
        size: '',
        borderWidth: '',
        borderDash: '',
        borderColor: '',
        borderHexColor: '',
        textPosition: '',
        font: '',
        fontColor: '',
        fontHexColor: '',
        fontBgColor: '',
        fontBgHexColor: '',
        textOffset: ''
      },
      // 节点类型列表
      nodeTypeList: [
        { value: 'circle', label: '圆形' },
        { value: 'rect', label: '矩形' },
        { value: 'ellipse', label: '椭圆形' },
        { value: 'star', label: '五角形' },
        { value: 'triangle', label: '三角形' },
        { value: 'polygon', label: '六边形' }
      ],
      // 字体位置列表
      textPositionList: [
        { value: 'Middle_Center', label: '居中' },
        { value: 'Bottom_Center', label: '底部' },
        { value: 'top_Center', label: '顶部' },
        { value: 'Middle_Left', label: '左方' },
        { value: 'Middle_right', label: '右方' },
      ]
    }
  },
  mounted() {
    this.handleData(company)
    window.onresize = () => {
      if (this.visgraph) {
        this.visgraph.moveCenter()
      }
    }
  },
  methods: {
    /**
     * 处理数据
     * @date 2021-07-23
     * @param {Object} data
     */
    handleData(data) {
      const obj = {
        nodes: [],
        links: []
      }
      const nodes = data.nodes
      nodes.forEach(item => {
        if (item.label === '总公司') {
          const nodeObj = {
            id: item.id,
            label: item.label,
            properties: item,
            color: '38,186,191',
            selectedBorderColor: '131,218,228',
            shadowColor: '131,218,228'
          }
          nodeObj.size = 80
          nodeObj.x = 250
          nodeObj.y = 250
          this.centerNode = nodeObj
          this.currentNode.push(item.id)
        } else {
          const nodeObj = {
            id: item.id,
            label: item.label,
            properties: item,
            size: 60
          }
          switch (item.type) {
            case 1:
              nodeObj.color = '242,105,97'
              nodeObj.selectedBorderColor = '249,179,157'
              nodeObj.shadowColor = '249,179,157'
              break
          }
          obj.nodes.push(nodeObj)
        }
      })
      const links = data.edges
      links.forEach(item => {
        const linkObj = {
          id: item.id,
          target: item.to,
          source: item.from,
          label: item.label,
          properties: item
          // strokeColor: this.getRandomColor()
        }
        switch (item.type) {
          case 11:
            linkObj.color = '40,194,199'
            linkObj.selectedColor = '40,194,199'
            linkObj.shadowColor = '40,194,199'
            break
          case 12:
            linkObj.color = '250,108,100'
            linkObj.selectedColor = '250,108,100'
            linkObj.shadowColor = '250,108,100'
            break
          case 13:
            linkObj.color = '0,132,255'
            linkObj.selectedColor = '0,132,255'
            linkObj.shadowColor = '0,132,255'
            break
          case 15:
            linkObj.color = '250,108,100'
            linkObj.selectedColor = '250,108,100'
            linkObj.shadowColor = '250,108,100'
            break
        }
        obj.links.push(linkObj)
      })
      this.buildData(obj)
    },
    /**
     * 搭建实例
     * @date 2021-07-23
     * @param {Object} gxData
     */
    buildData(gxData) {
      this.visgraph = new VisGraph(document.getElementById('graph-panel'), this.config)
      const nodeCount = gxData.nodes.length
      const xyArr = this.getXY(this.centerNode, nodeCount, nodeCount * 20)
      gxData.nodes.forEach((n, i) => {
        n.x = xyArr[i].x
        n.y = xyArr[i].y
        n.size = 60
      })
      gxData.nodes.push(this.centerNode)
      this.visgraph.drawData(gxData)
      this.visgraph.setZoom()
    },
    /**
     * 遍布选中节点周围的点坐标
     * @date 2021-07-23
     * @param {中心节点} centerNode
     * @param {节点数量} nodeCount
     * @param {距离中心点距离} raduis
     * @returns {Array}
     */
    getXY(centerNode, nodeCount, raduis) {
      const aop = 360.0 / nodeCount
      const arr = []
      for (let i = 0; i < nodeCount; i++) {
        let r1 = raduis
        if (nodeCount > 10) {
          r1 = (i % 2 === 0 ? raduis + 35 : raduis - 35)
        }
        const ao = i * aop
        const o1 = {}
        o1.x = centerNode.x + r1 * Math.cos(ao * Math.PI / 180)
        o1.y = centerNode.y + r1 * Math.sin(ao * Math.PI / 180)
        arr[i] = o1
      }
      return arr
    },
    /**
     * 随机产生节点
     * @date 2021-07-23
     * @param {当前选中节点} centerNode
     * @param {节点数量} nodeNum
     * @returns {Object}
     */
    buildIncreamData(centerNode, nodeNum) {
      const gxData = {
        nodes: [],
        links: []
      }
      const count = nodeNum
      const nodeIdPrefix = 'node_' + Math.round(Math.random() * 1000) + '_'
      for (let i = 0; i < count; i++) {
        gxData.nodes.push({
          id: nodeIdPrefix + i,
          label: '子节点+' + i,
          size: 60
          // color: this.getRandomColor()
        })
        gxData.links.push({
          source: centerNode.id,
          target: nodeIdPrefix + i,
          label: '关系' + i
        })
      }
      return gxData
    },
    /**
     * 画布右键事件
     * @date 2021-07-26
     * @param {Object} event
     */
    globalClickedDispatch(event) {
      if (event.button === 2) {
        if (this.visgraph.currentNode) {
          this.nodeMenuDialogOpen(event, this.visgraph.currentNode)
        }
      }
    },
    /**
     * 右键节点菜单显示
     * @date 2021-07-26
     * @param {Object} event
     * @param {Object} node
     */
    nodeMenuDialogOpen(event, node) {
      let nodeMenuDialog = document.getElementById("nodeMenuDialog");
      nodeMenuDialog.style.left = event.clientX + "px";
      nodeMenuDialog.style.top = (event.clientY - 76) + "px";
      nodeMenuDialog.style.display = "block";
      this.checkedNodeInfo = node;
      event.stopPropagation();
    },
    /**
     * 关闭节点菜单
     * @date 2021-07-26
     */
    nodeMenuDialogClose() {
      let nodeMenuDialog = document.getElementById("nodeMenuDialog");
      nodeMenuDialog.style.display = "none";
    },
    /**
     * 点击节点信息
     * @date 2021-07-26
     */
    clickNodeInfo() {
      this.nodeInfoDrawer = true
      // 赋值表单
      this.nodeInfoForm = this.checkedNodeInfo
      // 关联节点
      // 出节点
      const k = this.checkedNodeInfo
      const g = (k.outLinks || []).map((link) => {
        return {
          id: link.target.id,
          label: link.target.label,
          type: link.target.type,
          color: 'rgb(' + link.target.fillColor + ')',
          relationType: link.type || link.label || '--'
        }
      })
      // 入节点
      const h = (k.inLinks || []).map((link) => {
        return {
          id: link.source.id,
          label: link.source.label,
          type: link.source.type,
          color: 'rgb(' + link.source.fillColor + ')',
          relationType: link.type || link.label || '--'
        }
      })
      this.nodeInfoTargetList = h
      this.nodeInfoSourceList = g
      // 属性赋值
      const list = []
      const nameList = ['id', 'label', 'type', 'cluster', 'fillColor', 'shape', 'size', 'font', 'fontColor', 'x', 'y']
      nameList.forEach(item => {
        const obj = {
          name: item,
          value: this.checkedNodeInfo[item]
        }
        list.push(obj)
      })
      this.nodeInfoAttributeList = list
      this.nodeMenuDialogClose()
    },
    /**
     * 选中关联操作
     * @date 2021-07-26
     */
    selectRelation() {
      this.visgraph.rightMenuOprate('selRelate')
    },
    /**
     * 删除指定节点
     * @date 2021-07-26
     * @returns {any}
     */
    deleteNode() {
      this.visgraph.deleteNode(this.visgraph.currentNode)
      this.nodeMenuDialogClose()
    },
    /**
     * 收起指定节点
     * @date 2021-07-26
     * @returns {any}
     */
    contractNode() {
      if (this.visgraph.currentNode.outLinks.length > 0) {
        this.visgraph.contract(this.visgraph.currentNode)
        this.nodeMenuDialogClose()
      } else {
        this.$message.warning('当前节点无子节点,无法收起')
      }
    },
    /**
     * 展开指定节点
     * @date 2021-07-26
     * @returns {any}
     */
    expandedNode() {
      if (this.visgraph.currentNode.outLinks.length > 0) {
        this.visgraph.expanded(this.visgraph.currentNode)
        this.nodeMenuDialogClose()
      } else {
        this.$message.warning('当前节点无子节点,无法展开')
      }
    },
    /**
     * 以指定节点为中心移动
     * @date 2021-07-26
     * @param {String} id
     */
    moveCenterThisNode(id) {
      const node = this.visgraph.findNodeById(id)
      this.visgraph.moveNodeToCenter(node)
    },
    /**
     * 节点配置
     * @date 2021-07-30
     * @returns {any}
     */
    settingNode () {
      this.nodeMenuDialogClose()
      const {
        id,
        label,
        shape,
        fillColor,
        size,
        borderWidth,
        lineDash,
        borderColor,
        textPosition,
        font,
        fontColor,
        labelBackGround,
        textOffsetX
      } = this.visgraph.currentNode
      this.nodeConfigForm.id = id
      this.nodeConfigForm.label = label
      this.nodeConfigForm.shape = shape
      this.nodeConfigForm.fillColor = 'rgb(' + fillColor + ')'
      this.nodeConfigForm.hexColor = this.rgbToHex('rgb(' + fillColor + ')')
      this.nodeConfigForm.size = size
      this.nodeConfigForm.borderWidth = borderWidth
      this.nodeConfigForm.borderDash = lineDash.length === 2
      this.nodeConfigForm.borderColor = 'rgb(' + borderColor + ')'
      this.nodeConfigForm.borderHexColor = this.rgbToHex('rgb(' + borderColor + ')')
      this.nodeConfigForm.textPosition = textPosition
      this.nodeConfigForm.font = font
      this.nodeConfigForm.fontColor = 'rgb(' + fontColor + ')'
      this.nodeConfigForm.fontHexColor = this.rgbToHex('rgb(' + fontColor + ')')
      this.nodeConfigForm.fontBgColor = labelBackGround ? 'rgb(' + labelBackGround + ')' : ''
      this.nodeConfigForm.fontBgHexColor = labelBackGround ? this.rgbToHex('rgb(' + labelBackGround + ')') : ''
      this.nodeConfigForm.textOffset = textOffsetX
      this.nodeConfigDrawer = true
    },
    /**
     * 保存节点配置
     * @date 2021-07-30
     * @returns {any}
     */
    saveSetting () {
      const {
        id,
        label,
        shape,
        fillColor,
        hexColor,
        size,
        borderWidth,
        borderDash,
        borderColor,
        borderHexColor,
        textPosition,
        font,
        fontColor,
        fontHexColor,
        fontBgColor,
        fontBgHexColor,
        textOffset
      } = this.nodeConfigForm
      const b = this.visgraph.findNodeByAttr('id', id)
      if (b) {
        b.label = label
        b.size = size
        b.shape = shape
        b.fillColor = this.getColorRgb(fillColor)
        b.textPosition = textPosition
        b.fontColor = this.getColorRgb(fontColor)
        b.labelBackGround = this.getColorRgb(fontBgColor)
        b.font = font;
        b.textOffsetY = Number(textOffset) || 2
        b.borderWidth = Number(borderWidth) || 2
        b.borderColor = this.getColorRgb(borderColor)
        b.lineDash = (borderDash ? [8, 5] : [0])
        this.visgraph.refresh()
        this.$message({
          message: '修改配置成功!',
          type: 'success',
          duration: 2000
        })
        this.nodeConfigDrawer = false
      } else {
        this.$message({
          message: '无法找到选中节点!',
          type: 'error',
          duration: 2000
        })
      }
    },
    /**
     * 随机获取颜色
     * @date 2021-07-20
     * @returns {String} 样式
     */
    getRandomColor() {
      const r = Math.floor(Math.random() * 256)
      const g = Math.floor(Math.random() * 256)
      const b = Math.floor(Math.random() * 256)
      return 'rgb(' + r + ',' + g + ',' + b + ')'
    },
    /**
     * 颜色选择框变化赋值
     * @date 2021-07-26
     * @param {String} val
     * @param {String} kay
     */
    colorChange(val, key) {
      this.nodeConfigForm[key] = this.hexToRgba(val)
    },
    /**
     * 16进制色值转rgb
     * @date 2021-07-26
     * @param {String} hex
     * @returns {String}
     */
    hexToRgba(hex) {
      return "rgb(" + parseInt("0x" + hex.slice(1, 3)) + "," + parseInt("0x" + hex.slice(3, 5)) + "," + parseInt("0x" + hex.slice(5, 7)) + ")"
    },
    /**
     * rgb色值转16进制
     * @date 2021-07-26
     * @param {String} color
     * @returns {String}
     */
    rgbToHex(color) {
      const rgb = color.split(',');
      const r = parseInt(rgb[0].split('(')[1]);
      const g = parseInt(rgb[1]);
      const b = parseInt(rgb[2].split(')')[0]);
      const hex = "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
      return hex;
    },
    /**
     * 去掉rgb
     * @date 2021-07-30
     * @param {String} a
     * @returns {String}
     */
    getColorRgb (a) {
      if (a && a.length > 0) {
        a = a.replace('rgb(', '').replace(')', '')
      } else {
        a = null
      }
      return a
    },
    /**
     * 保存图片
     * @date 2021-07-23
     */
    saveImage() {
      this.visgraph.saveImage()
    },
    /**
     * 导出json
     * @date 2021-07-30
     */
    exportJson () {
      this.visgraph.exportJsonFile()
    },
    /**
     * 打开缩略图
     * @date 2021-07-23
     */
    showOverView() {
      console.log(this.showOverViewFlag)
      this.showOverViewFlag = !this.showOverViewFlag
      this.visgraph.showOverView(this.showOverView)
    },
    /**
     * 缩小操作
     * @date 2021-07-23
     */
    setZoomIn() {
      this.visgraph.setZoom('zoomIn')
    },
    /**
     * 放大操作
     * @date 2021-07-23
     */
    setZoomOut() {
      this.visgraph.setZoom('zoomOut')
    },
    /**
     * 顺时针旋转
     * @date 2021-07-23
     */
    clockwiseRotate() {
      this.visgraph.rotateGraph(-10)
    },
    /**
     * 逆时针旋转
     * @date 2021-07-23
     */
    counterclockwiseRotate() {
      this.visgraph.rotateGraph(10)
    },
    /**
     * 设置鼠标模式
     * @date 2021-07-23
     * @param {String} type  drag:拖动模式  select:框选模式   normal:正常模式
     */
    setMouseModel(type) {
      this.visgraph.setMouseModel(type)
    },
    /**
     * 全屏显示
     * @date 2021-07-23
     */
    fullScreen() {
      screenfull.request(this.$refs.graphpanel)
    }
  }
}
</script>

<style lang="scss" scoped>
#container {
  width: 100%;
  position: relative;
  #graph-panel {
    width:100%;
    height:100%;
    background:#9dadc1;
    position: absolute;
    z-index: 1;
  }
  /* 测试菜单栏 */
  .left-toolbar {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1000;
    width: 45px;
    height: 100%;
    background-color: #fafafa;
    border-right: 1px solid #e5e2e2;
    ul {
      li {
        text-align: center;
        height: 35px;
        color: #066fba;
        line-height: 35px;
        cursor: pointer;
        padding: 0;
        i {
          font-size: 18px;
        }
        &:hover {
          background-color: #6ea36d;
          color: #fff;
        }
      }
    }
  }

  /* 右键弹框样式 */
  .nodeMenuDialog {
    display: none;
    position: absolute;
    min-width: 100px;
    padding: 2px 3px;
    margin: 0;
    border: 1px solid #e3e6eb;
    background: #f9f9f9;
    color: #333;
    z-index: 100;
    border-radius: 5px;
    box-shadow: 1px 1px 4px rgba(0, 0, 0, 0.2);
    transform: translate(0, 15px) scale(0.95);
    transition: transform 0.1s ease-out, opacity 0.1s ease-out;
    overflow: hidden;
    cursor: pointer;
    li {
      display: block;
      position: relative;
      margin: 0;
      padding: 0 10px;
      border-radius: 5px;
      white-space: nowrap;
      line-height: 30px;
      text-align: center;
      &:hover {
        background-color: #c3e5fd;
      }
    }
  }

  /* 节点信息弹框 */
  .nodeInfo {
    .nodeInfoForm {
      padding: 20px 20px 0 20px;
      border: solid 1px #dcdfe6;
      border-left: none;
      border-right: none;
      margin: 20px 0;
    }
    .nodeInfoRelation {
      padding: 0 20px;
      .nodeInfo-table {
        width: 100%;
        overflow-y: scroll;
        th {
          width: 50%;
          border: 1px solid #ebeef5;
          padding: 9px 0 9px 9px;
          text-align: left;
          &:first-child {
            border-right: none;
          }
        }
        td {
          width: 50%;
          border: 1px solid #ebeef5;
          border-top: none;
          padding: 9px 0 9px 9px;
          &:first-child {
            border-right: none;
          }
        }
      }
      /deep/ .el-badge__content.is-fixed {
        top: 24px;
        right: -7px;
      }
      p {
        text-align: center;
        padding: 20px 0;
      }
    }

    .nodeInfoAttribute {
      padding: 0 20px;
    }
  }

  /* 节点配置弹框 */
  .nodeConfig {
    padding: 20px 20px 0 20px;
    border: solid 1px #dcdfe6;
    border-left: none;
    border-right: none;
    margin: 20px 0;
    .form-color {
      display: flex;
      justify-content: space-between;
      .form-input {
        width: calc(100% - 50px);
      }
    }
    .save-setting {
      width: 100%;
      margin-bottom: 20px;
      .el-button {
        width: 100%;
      }
    }
  }
}
</style>

注:引入两个js的文件eslint会报错,可以把这个文件忽略,不使用eslint的可以忽略。同时该项目还基于element-ui开发,引入screenfull全屏插件,还有阿里图标库图标,自己按需引入。

Demo演示:

到此这篇关于vue使用GraphVis开发无限拓展的关系图谱的实现的文章就介绍到这了,更多相关vue GraphVis关系图谱内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 使用Vue制作图片轮播组件思路详解

    之前一直都没有认真的写过一个组件.以前在写业务代码的过程中,都是用的别人封装好的组件,这次尝试着写了一个图片轮播组件,虽然比不上知名的轮播组件,但它的功能基本完整,而且在写这个组件的过程中,学的东西也很多,在这里也给大家分享出来,如有疏漏,欢迎指正! 在制作这个组件之前,笔者google了不少关于轮播的文章,发现实现一个轮播的思路虽然各有不同,但是大的逻辑其实差不多,本文主要依据慕课网上焦点轮播图特效这节课,不过慕课网主要用原生JS写,而笔者则用Vue进行了重构,并且进行了一点修改.完成后的组件

  • 在vue中添加Echarts图表的基本使用教程

    前言 我们在项目中经常需要使用一些折线图.柱状图.饼状图等等,之前使用过heightCharts,后来觉得这货不开源,只是做展示的话又点浪费时间,于是看了下eCharts,于是在vue-cli搭建的项目中添加了eCharts,下面是具体步骤和自己的一些学习笔记,参照于Echarts3官网 现在的前端一般需要完成将大量的数据,实现可视化.现在是大数据和云计算的时代,所以数据可视化逐渐变成一种趋势.而ECharts和d3.js则是可视化的成熟框架.对于制作的图表可以说是满足你的创造力. 两者相比,D

  • 使用Vue实现图片上传的三种方式

    项目中需要上传图片可谓是经常遇到的需求,本文将介绍 3 种不同的图片上传方式,在这总结分享一下,有什么建议或者意见,请大家踊跃提出来. 没有业务场景的功能都是耍流氓,那么我们先来模拟一个需要实现的业务场景.假设我们要做一个后台系统添加商品的页面,有一些商品名称.信息等字段,还有需要上传商品轮播图的需求. 我们就以Vue.Element-ui,封装组件为例子聊聊如何实现这个功能.其他框架或者不用框架实现的思路都差不多,本文主要聊聊实现思路. 1.云储存 常见的 七牛云,OSS(阿里云)等,这些云平

  • vue实现点击图片放大效果

    本文实例为大家分享了vue点击图片放大展示的具体代码,供大家参考,具体内容如下 1.建立子组件,来实现图片方法功能: BigImg.vue <template> <!-- 过渡动画 --> <transition name="fade"> <div class="img-view" @click="bigImg"> <!-- 遮罩层 --> <div class="img

  • 基于vue.js实现图片轮播效果

    轮播图效果: 1.html <template> <div class="shuffling"> <div class="fouce fl"> <div class="focus"> <ul class="showimg"> <template v-for='sd in shufflingData'> <li v-if='shufflingId==$

  • 基于vue 动态加载图片src的解决方法

    好久没更博了,最近也不知道在忙啥,反正就是感觉挺忙的,在群里看到陆陆续续有刚入vue小伙伴问vue动态加载图片总是404的状况,这篇就简单的说明为什么会出现以及解决办法有哪些. 首先先说明下vue-cli的assets和static的两个文件的区别,因为这对你理解后面的解决办法会有所帮助 assets:在项目编译的过程中会被webpack处理解析为模块依赖,只支持相对路径的形式,如< img src="./logo.png">和background:url(./logo.p

  • vue+elementUI实现图片上传功能

    本文实例为大家分享了vue+elementUI图片上传的具体代码,供大家参考,具体内容如下 1.html <el-form-item label="图片" prop="logo"> <el-upload name="file" v-if="optype==0" :action="'/upload'" accept=".jpg, .png" list-type="

  • vue使用自定义icon图标的方法

    首先因为elementUI提供的icon太少了,所有自己找找有没有矢量图可以补充的,尝试多种方法,觉得下面方法简单易懂,分享给大家 效果图: 推荐使用阿里爸爸矢量图标管理,iconfont 使用方法 登录账号,找到需要的图标加入购物车 然后添加到项目 再然后下载代码到本地 下载代码文件然后解压出现这列表 打开HTML文件,引用方法教程 补充我人的坑,之前想着在style标签里@import "",结果一直报错,试了很多方法还是没效果,把问题想复杂了 第一步:在index.html引入f

  • Vue项目中设置背景图片方法

    在Vue项目开发中我们经常要向页面中添加背景图片,可是当我们在样式中添加了背景图片后,编译打包后,配置到服务器上时,由于路径解析的问题,图片并不能够正确的显示出来,如下CSS样式: background:url("../../assets/head.jpg"); 这个时候我们就要考虑使用其他的方式了,node中提供了一种比较有效的方式来解决这个问题: 1.在data中定义如下: export default { name: 'productdetailspage', data() {

  • vue使用GraphVis开发无限拓展的关系图谱的实现

    1.去GraphVis官网下载对应的js,新版和旧版的js有所不同,看自己需求引入旧版还是新版(GraphVis官方网址:http://www.graphvis.cn/) visgraph.min.js (基本配置js) visgraph-layout.min.js(配置布局js) 2.在需要的vue文件引入js文件 import VisGraph from '@/assets/js/GraphVis/old/visgraph.min.js' // 自己对应的js文件位置 import Layo

  • Vue组件化开发思考

    一般说到组件,我首先想到的是弹窗,其他就大脑空白了. 因为觉得这个是在项目中最常用的功能,提取出来方便复用的才是组件- 然而我才发现这个想法是有问题的. 我发觉可能从意识上把Vue的组件和UI库的组件(弹窗之类的)混淆了... 缘起于最近的一个表单开发,页面上有2个是联动菜单的选项. 首先想到的是,这个样式和选择地址的那个联动菜单,完全一样哈- (废话,同一个项目 当然要保持ui风格的相同啊!) 不过差别在于 我这个是 一个1级 一个2级, 地址那个是4级的. 然后我就想着把那个地址的组件引进来

  • vue单个组件实现无限层级多选菜单功能

    wTree.vue  原理:每一个多选框都是一个节点,每个节点就是一个wTree组件,有父级(顶级level为0),有子级(底层list[]是空的),组件之间状态传递是通过组件通信传递,对于外部数据checkList数组的修改是通过store实现的.初始化从底层状态传递到上层,一层一层传递.改变状态,不同状态改变,修改checklist数组.大概就这个思路,下面是代码: <template> <div> <div > <span v-for="o in

  • Vue安装浏览器开发工具的步骤详解

    开发vue时,浏览器有一个好的开发调试工具能让开发事半功倍,磨刀不误砍柴工. 步骤 1.下载工具 地址:  https://github.com/vuejs/vue-devtools 2.安装依赖 cmd进入vue-devtools文件夹,安装相关依赖,依次执行npm install,再执行npm run build. 3.修改配置 打开shells>chrome>src>manifest.json,修改"persistent":false为true. 4.浏览器安装

  • 如何使用Laravel Eloquent来开发无限极分类

    目录 概述 数据库迁移 Eloquent 模型和关联关系 路由和控制器方法 视图和递归子视图 概述 我们会创建一个微型项目来展示儿童商店的分类,总共有 5 级,如下: 数据库迁移 简单的数据表结构: Schema::create('categories', function (Blueprint $table) { $table->bigIncrements('id'); $table->string('name'); $table->unsignedBigInteger('catego

  • vue多页面开发和打包正确处理方法

    前段时间做项目,技术栈是vue+webpack,主要就是官网首页加后台管理系统 根据当时情况,分析出三种方案 一个项目代码里面嵌两个spa应用(官网和后台系统) 分开两套项目源码 一套项目源码里面就一个spa应用 思考: 直接否定了一套项目源码里一个spa应用(ui样式会相互覆盖,如果没有代码规范后期比较难维护) 两套源码的话,后台可能开两个端口,然后需要用nginx反向代理可能比较麻烦,而且前端开发也比较麻烦麻烦,毕竟需要维护两个git仓库,两套git上线流程,可能会损耗很多时间. 对自己的技

  • 你不知道的Vue技巧之--开发一个可以通过方法调用的组件(推荐)

    Vue作为最近最炙手可热的前端框架,其简单的入门方式和功能强大的API是其优点.而同时因为其API的多样性和丰富性,所以他的很多开发方式就和一切基于组件的React不同,如果没有对Vue的API(有一些甚至文档都没提到)有一个全面的了解,那么在开发和设计一个组件的时候有可能就会绕一个大圈子,所以我非常推荐各位在学习Vue的时候先要对Vue核心的所有API都有一个了解. 举个例子,通知组件notification基本是现代web开发标配,在很多地方都能用到.而在以Vue作为核心框架的前端项目中,因

  • 详解用Docker搭建Laravel和Vue项目的开发环境

    本文介绍了用Docker搭建Laravel和Vue项目的开发环境,分享给大家,具体如下: 在这篇文章中我们将通过Docker在个人本地电脑上构建一个快速.轻量级.不依赖本地电脑所安装的任何开发套件的可复制的Laravel和Vue项目的开发环境(开发环境的所有依赖都安装在Docker构建容器里),加入Vue只是因为有的项目里会在Laravel项目中使用Vue做前后端分离开发,开发环境中需要安装前端开发需要的工具集,当然前后端也可以分成两个项目开发,这个话题不在本篇文章的讨论范围内. 所以我们的目标

  • Vue.js实现开发购物车功能的方法详解

    本文实例讲述了Vue.js实现开发购物车功能的方法.分享给大家供大家参考,具体如下: 购物车一般包含商品名称.单价.数量等信息,数量可以任意新增或减少,商品项也可删除,还可以支持全选或多选: 我们把这个小项目分为三个文件: index.html (页面) index.js (Vue 脚本) style.css (样式) 1 index.js 首先在 js 中初始化 Vue 实例,整体模板如下: var app = new Vue({ el: '#app', data: { ... }, moun

  • vue单页开发父子组件传值思路详解

    vue单页开发时经常需要父子组件之间传值,自己用过但是不是很熟练,这里我抽空整理了一下思路,写写自己的总结. GitHub地址:https://github.com/leileibrother/wechat-vue 文件目录如下图,example.vue是父组件,exampleChild.vue是子组件. 父组件引入子组件,父组件html的代码如下: <template> <div> <h3>这是父组件</h3> <p style="marg

随机推荐