有前端佬晓得relation-graph吗?求relation-graph的VIP的代码demo

或者有没有佬推荐另外一个关系数据展示组件

6 Likes

推荐一个

2 Likes

多谢 我研究下

2 Likes

要那个demo,发下地址,我拷给你

佬 这个

1 Like

插个眼,没写过这个需求 :tieba_087: :tieba_087:

1 Like

vue2还是vue3的
vue2

<template>
  <div>
    <div style="height: calc(100vh - 50px)">
      <RelationGraph
        ref="graphRef"
        :options="graphOptions"
        :on-contextmenu="onContextmenu"
      >
        <template #graph-plug>
          <div
              v-if="isShowNodeTipsPanel"
              :style="{left: nodeMenuPanelPosition.x + 'px', top: nodeMenuPanelPosition.y + 'px' }"
              class="c-right-menu-panel"
          >
            <div class="c-object-info">
              <div>当前右键事件信息:</div>
              <div>类型:{{currentObjectType}}</div>
              <div v-if="currentObjectType==='link'">{{currentObject.fromNode.text}} -> {{currentObject.toNode.text}}</div>
              <div v-if="currentObjectType==='node'">ID:{{currentObject.id}}</div>
              <div v-if="currentObjectType==='node'">Text:{{currentObject.text}}</div>
              <div>你可以对这个对象做以下操作:</div>
            </div>
            <div v-if="currentObjectType==='canvas'" class="c-node-menu-item" @click="addNode">添加节点</div>
            <div v-if="currentObjectType==='node'" class="c-node-menu-item" @click="deleteNode">删除节点</div>
            <div v-if="currentObjectType==='node'" class="c-node-menu-item" @click="createLineFromNode">添加连线</div>
            <div v-if="currentObjectType==='link'" class="c-node-menu-item" @click="deleteLink">删除关系</div>
          </div>
        </template>
      </RelationGraph>
    </div>
  </div>
</template>

<script>
// 如果您没有在main.js文件中使用Vue.use(RelationGraph); 就需要使用下面这一行代码来引入relation-graph
// import RelationGraph from "relation-graph";
const graphOptions = {
  allowSwitchLineShape: true,
  allowSwitchJunctionPoint: true,
  allowShowDownloadButton: true,
  defaultJunctionPoint: 'border'
};
export default {
  name: 'ObjectEdit',
  components: { },
  data() {
    return {
      isShowNodeTipsPanel: false,
      nodeMenuPanelPosition: { x: 0, y: 0 },
      currentObjectType: null,
      currentObject: '',
      newNodeIdIndex: 1,
      newLineIdIndex: 1,
      graphOptions
    };
  },
  mounted() {
    this.showGraph();
  },
  methods: {
    showGraph() {
      const __graph_json_data = {
        rootId: 'a',
        nodes: [
          { id: 'a', text: 'A', borderColor: 'yellow' },
          { id: 'b', text: 'B', color: '#43a2f1', fontColor: 'yellow' },
          { id: 'c', text: 'C', nodeShape: 1, width: 80, height: 60 },
          { id: 'e', text: 'E', nodeShape: 0, width: 150, height: 150 }
        ],
        lines: [
          { from: 'a', to: 'b', text: '关系1', color: '#43a2f1' },
          { from: 'a', to: 'c', text: '关系2' },
          { from: 'a', to: 'e', text: '关系3' },
          { from: 'b', to: 'e', text: '', color: '#67C23A' }
        ]
      };
      const graphRef = this.$refs.graphRef
      graphRef.setJsonData(__graph_json_data, (graphInstance) => {
          // 这些写上当图谱初始化完成后需要执行的代码.
        }
      );
    },
    onContextmenu($event, objectType, object) {
      const graphInstance = this.$refs.graphRef.getInstance();
      this.currentObjectType = objectType;
      this.currentObject = object;
      const _base_position = graphInstance.getBoundingClientRect();
      console.log('showNodeMenus:', $event, _base_position);
      this.isShowNodeTipsPanel = true;
      this.nodeMenuPanelPosition.x = $event.clientX - _base_position.x + 10;
      this.nodeMenuPanelPosition.y = $event.clientY - _base_position.y + 10;
      const hideContentMenu = () => {
        this.isShowNodeTipsPanel = false;
        document.body.removeEventListener('click', hideContentMenu)
      }
      document.body.addEventListener('click', hideContentMenu)
    },
    addNode($event) {
      const graphInstance = this.$refs.graphRef.getInstance();
      const _base_position = graphInstance.getBoundingClientRect();
      const canvasCoordinate = graphInstance.getCanvasCoordinateByClientCoordinate({
        x: this.nodeMenuPanelPosition.x - 10 + _base_position.x,
        y: this.nodeMenuPanelPosition.y - 10 + _base_position.y
      });
      const newId = this.newNodeIdIndex++
      graphInstance.addNodes([{
        id: 'addFromCanvas-' + newId,
        text: 'New-' + newId,
        color: '#5da0f8',
        x: canvasCoordinate.x,
        y: canvasCoordinate.y
      }]);
    },
    deleteNode($event) {
      const graphInstance = this.$refs.graphRef.getInstance();
      graphInstance.removeNodeById(this.currentObject.id);
    },
    deleteLink($event) {
      const graphInstance = this.$refs.graphRef.getInstance();
      graphInstance.removeLinkById(this.currentObject.seeks_id);
    },
    createLineFromNode(e) {
      const graphInstance = this.$refs.graphRef.getInstance();
      graphInstance.startCreatingLinePlot(e, {
        template: {
          lineWidth: 3,
          color: '#e85f84',
          text: '新连线'
        },
        fromNode: this.currentObject,
        onCreateLine: (from, to) => {
          console.log('新增连线:', from, to);
          if (to.id) { // 创建的连线的起点一定是节点,但终点可以是空白处,终点没有选择成节点时to不是一个节点,to.id不会有值,这里做了判断,只处理to为节点的情况
            const newLineId = this.newLineIdIndex++;
            graphInstance.addLines([{
              from: from.id,
              to: to.id,
              lineWidth: 3,
              color: '#e85f84',
              text: '新连线' + newLineId
            }]);
          }
        }
      });
    }
  },
};
</script>

<style>
</style>

<style scoped>
.c-node-menu-item{
  line-height: 30px;padding-left: 10px;cursor: pointer;color: #444444;font-size: 14px;border-top:#efefef solid 1px;
}
.c-node-menu-item:hover{
  background-color: rgba(66,187,66,0.2);
}
.c-object-info{
  line-height: 18px;padding-left: 10px;color: #888888;font-size: 12px;
}
.c-right-menu-panel{
  z-index: 999;padding:10px;background-color: #ffffff;border:#eeeeee solid 1px;box-shadow: 0px 0px 8px #cccccc;position: absolute;
  border-radius: 10px;
  width:200px;
}
</style>

vue3

<template>
  <div>
    <div style="height: calc(100vh)">
      <RelationGraph ref="graphRef" :options="graphOptions" @contextmenu="onContextmenu">
        <template #graph-plug>
            <div class="c-my-panel">
                <div class="c-option-name">Please try right-clicking on the artboard, node, or line</div>
            </div>
          <div
            v-if="isShowNodeTipsPanel"
            :style="{ left: nodeMenuPanelPosition.x + 'px', top: nodeMenuPanelPosition.y + 'px' }"
            class="c-right-menu-panel"
          >
            <div class="c-object-info">
              <div>Current right-click event information:</div>
              <div>Type: {{ currentObjectType }}</div>
              <div v-if="currentObjectType === 'link'">
                {{ currentObject.fromNode.text }} -> {{ currentObject.toNode.text }}
              </div>
              <div v-if="currentObjectType === 'node'">ID: {{ currentObject.id }}</div>
              <div v-if="currentObjectType === 'node'">Text: {{ currentObject.text }}</div>
              <div>You can perform the following operations on this object:</div>
            </div>
            <div v-if="currentObjectType === 'canvas'" class="c-node-menu-item" @click="addNode">
              Add Node
            </div>
            <div v-if="currentObjectType === 'node'" class="c-node-menu-item" @click="deleteNode">
              Delete Node
            </div>
            <div v-if="currentObjectType === 'node'" class="c-node-menu-item" @click="createLineFromNode">
              Add Link
            </div>
            <div v-if="currentObjectType === 'link'" class="c-node-menu-item" @click="deleteLink">
              Delete Relation
            </div>
          </div>
        </template>
      </RelationGraph>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { ref, onMounted } from 'vue';
import RelationGraph from 'relation-graph-vue3';
import type { RGJsonData, RGNode, RGLine, RGLink, RGUserEvent, RGOptions, RelationGraphComponent } from 'relation-graph-vue3';

const graphOptions: RGOptions = {
    allowSwitchLineShape: true,
    allowSwitchJunctionPoint: true,
    allowShowDownloadButton: true,
    defaultJunctionPoint: 'border'
};

const isShowNodeTipsPanel = ref(false);
const nodeMenuPanelPosition = ref({ x: 0, y: 0 });
const currentObjectType = ref(null);
const currentObject = ref('');
let newNodeIdIndex = 1;
let newLineIdIndex = 1;

const showGraph = async() => {
    const __graph_json_data: RGJsonData = {
        rootId: 'a',
        nodes: [
            { id: 'a', text: 'A', borderColor: 'yellow' },
            { id: 'b', text: 'B', color: '#43a2f1', fontColor: 'yellow' },
            { id: 'c', text: 'C', nodeShape: 1, width: 80, height: 60 },
            { id: 'e', text: 'E', nodeShape: 0, width: 150, height: 150 }
        ],
        lines: [
            { from: 'a', to: 'b', text: 'Relation 1', color: '#43a2f1' },
            { from: 'a', to: 'c', text: 'Relation 2' },
            { from: 'a', to: 'e', text: 'Relation 3' },
            { from: 'b', to: 'e', text: '', color: '#67C23A' }
        ]
    };
    const graphInstance = graphRef.value?.getInstance();
    if (graphInstance) {
        await graphInstance.setJsonData(__graph_json_data);
        await graphInstance.moveToCenter();
        await graphInstance.zoomToFit();
    }
};

const onContextmenu = ($event: RGUserEvent, objectType: string, object: any) => {
    const graphInstance = graphRef.value.getInstance();
    currentObjectType.value = objectType;
    currentObject.value = object;
    const _base_position = graphInstance.getBoundingClientRect();
    console.log('showNodeMenus:', $event, _base_position);
    isShowNodeTipsPanel.value = true;
    nodeMenuPanelPosition.value.x = $event.clientX - _base_position.x + 10;
    nodeMenuPanelPosition.value.y = $event.clientY - _base_position.y + 10;
    const hideContentMenu = () => {
        isShowNodeTipsPanel.value = false;
        document.body.removeEventListener('click', hideContentMenu);
    };
    document.body.addEventListener('click', hideContentMenu);
};

const addNode = ($event: MouseEvent) => {
    const graphInstance = graphRef.value.getInstance();
    const _base_position = graphInstance.getBoundingClientRect();
    const canvasCoordinate = graphInstance.getCanvasCoordinateByClientCoordinate({
        x: nodeMenuPanelPosition.value.x - 10 + _base_position.x,
        y: nodeMenuPanelPosition.value.y - 10 + _base_position.y

    });
    const newId = newNodeIdIndex++;
    graphInstance.addNodes([
        {
            id: 'addFromCanvas-' + newId,
            text: 'New-' + newId,
            color: '#5da0f8',
            x: canvasCoordinate.x,
            y: canvasCoordinate.y

        }
    ]);
};

const deleteNode = ($event: MouseEvent) => {
    const graphInstance = graphRef.value.getInstance();
    graphInstance.removeNodeById(currentObject.value.id);
};

const deleteLink = ($event: MouseEvent) => {
    const graphInstance = graphRef.value.getInstance();
    graphInstance.removeLinkById(currentObject.value.seeks_id);
};

const createLineFromNode = (e: MouseEvent) => {
    const graphInstance = graphRef.value.getInstance();
    graphInstance.startCreatingLinePlot(e, {
        template: {
            lineWidth: 3,
            color: '#e85f84',
            text: 'New Link'
        },
        fromNode: currentObject.value,
        onCreateLine: (from: RGNode, to: RGNode) => {
            console.log('New Link:', from, to);
            if (to.id) {
                const newLineId = newLineIdIndex++;
                graphInstance.addLines([
                    {
                        from: from.id,
                        to: to.id,
                        lineWidth: 3,
                        color: '#e85f84',
                        text: 'New Link ' + newLineId

                    }
                ]);
            }
        }
    });
};

const graphRef = ref<RelationGraphComponent>();

onMounted(() => {
    showGraph();
});
</script>

<style>
.c-node-menu-item {
  line-height: 30px;
  padding-left: 10px;
  cursor: pointer;
  color: #444444;
  font-size: 14px;
  border-top: #efefef solid 1px;
}
.c-node-menu-item:hover {
  background-color: rgba(66, 187, 66, 0.2);
}
.c-object-info {
  line-height: 18px;
  padding-left: 10px;
  color: #888888;
  font-size: 12px;
}
.c-right-menu-panel {
  z-index: 999;
  padding: 10px;
  background-color: #ffffff;
  border: #eeeeee solid 1px;
  box-shadow: 0px 0px 8px #cccccc;
  position: absolute;
  border-radius: 10px;
  width: 200px;
}

.c-my-panel {
    position: absolute;
    left: 10px;
    top: 10px;
    border-radius: 10px;
    z-index: 800;
    background-color: #efefef;
    border: #eeeeee solid 1px;
    padding: 20px;

    .c-option-name{
        color: #666666;
        font-size: 14px;
        line-height: 40px;
        padding-left:10px;
        padding-right:10px;
    }
    .c-my-button{
        border-radius: 5px;
        height: 42px;
        width: 150px;
        text-align: center;
        line-height: 30px;
        padding:5px;
        cursor: pointer;
        background-color: #ffffff;
        border:rgba(79, 30, 30, 1) solid 1px;
        &:hover {
            transition: background-color .2s ease,outline .2s ease,color .2s ease,-webkit-box-shadow .2s ease;
            box-shadow: 0 0 0 5px rgba(79, 30, 30, 0.3);
        }
    }
    .c-my-button-checked{
        transition: background-color .2s ease,outline .2s ease,color .2s ease,-webkit-box-shadow .2s ease;
        box-shadow: 0 0 0 5px rgba(79, 30, 30, 0.3);
        background-color: rgba(79, 30, 30, 1);
        color: #ffffff;
    }
}
</style>

太感谢佬了,Vue2的就行

这个能帮忙拷下吗

<template>
  <div>
    <div style="height: calc(100vh - 50px)">
      <RelationGraph
        ref="graphRef"
        :options="graphOptions"
      >
        <template #tool-bar>
          <ObjectEditToolBar />
        </template>
      </RelationGraph>
    </div>
  </div>
</template>

<script>
// 如果您没有在main.js文件中使用Vue.use(RelationGraph); 就需要使用下面这一行代码来引入relation-graph
// import RelationGraph from "relation-graph";
import ObjectEditToolBar from './VipObjectEditToolBar';
const graphOptions = {
  debug: false,
  allowSwitchLineShape: true,
  allowSwitchJunctionPoint: true,
  allowShowDownloadButton: true,
  defaultJunctionPoint: 'border'
};
export default {
  name: 'ObjectEdit',
  components: { ObjectEditToolBar },
  data() {
    return {
      isShowNodeTipsPanel: false,
      nodeMenuPanelPosition: { x: 0, y: 0 },
      currentObjectType: null,
      currentObject: '',
      newNodeIdIndex: 1,
      newLineIdIndex: 1,
      graphOptions
    };
  },
  mounted() {
    this.showGraph();
  },
  methods: {
    showGraph() {
      const __graph_json_data = {
        rootId: 'a',
        nodes: [
          { id: 'a', text: 'A', borderColor: 'yellow' },
          { id: 'b', text: 'B', color: '#43a2f1', fontColor: 'yellow' },
          { id: 'c', text: 'C', nodeShape: 1, width: 80, height: 60 },
          { id: 'e', text: 'E', nodeShape: 0, width: 150, height: 150 }
        ],
        lines: [
          { from: 'a', to: 'b', text: '关系1', color: '#43a2f1' },
          { from: 'a', to: 'c', text: '关系2' },
          { from: 'a', to: 'e', text: '关系3' },
          { from: 'b', to: 'e', text: '', color: '#67C23A' }
        ]
      };
      const graphRef = this.$refs.graphRef
      graphRef.setJsonData(__graph_json_data, (graphInstance) => {
          // 这些写上当图谱初始化完成后需要执行的代码.
        }
      );
    }
  },
};
</script>

<style>
</style>

<style lang="scss" scoped>
::v-deep .relation-graph {
  .my-node-template{
    //transform: translateX(-60px) translateY(-60px) !important;
    cursor: default;
  }
}
.c-node-menu-item{
  line-height: 30px;padding-left: 10px;cursor: pointer;color: #444444;font-size: 14px;border-top:#efefef solid 1px;
}
.c-node-menu-item:hover{
  background-color: rgba(66,187,66,0.2);
}
</style>

佬 少个VipObjectEditToolBar这个

简单的关联图就没必要用这个了吧,后续维护还不好整。echart上面能满足的话,就用echart还好

<template>
  <div style="position: absolute;z-index: 30;top:10px;left: 40px; padding:10px;width:400px;height:70px;background-color: #ffffff;border:#efefef solid 1px;box-shadow: 0 3px 9px rgba(0,21,41,.08);border-radius: 10px;">
    <div style="display: flex;justify-content: center;place-items: center;gap: 5px;">
      <div class="c-mb-button" style="line-height:40px;font-size: 12px;text-align: center;" @click="zoomToFit">
        {{ options.canvasZoom }}%
      </div>
      <div :class="{'c-mb-button-on':options.creatingNodePlot}" class="c-mb-button c-mb-button-c" style="width: 80px;" @click="startAddNode">
        <i class="el-icon-football" />
        <div class="c-mb-text">节点(点击)</div>
      </div>
      <div :class="{'c-mb-button-on':options.creatingNodePlot}" class="c-mb-button c-mb-button-c" style="width: 80px;" @mousedown="startAddNode">
        <i class="el-icon-football" />
        <div class="c-mb-text">节点(拖拽)</div>
      </div>
      <div :class="{'c-mb-button-on':options.creatingLinePlot}" class="c-mb-button c-mb-button-c" style="width: 50px;" @click="startAddLine">
        <i class="el-icon-share" />
        <div class="c-mb-text">线条</div>
      </div>
      <div class="c-mb-button" @click="zoomToFit">
        <i class="el-icon-full-screen" />
        <div class="c-mb-text">适应</div>
      </div>
      <div class="c-mb-button" @click="refresh">
        <i class="el-icon-refresh-right" />
        <div class="c-mb-text">刷新</div>
      </div>
    </div>
  </div>
</template>

<script>

export default {
  name: 'ObjectEditToolBar',
  data() {
    return {
      showNewNodeTemplate: false,
      newObjectTemplatePosition: {x: 0, y: 0},
      height: 275,
      newNodeIdIndex: 1,
      newLineIdIndex: 1
    };
  },
  inject: ['graph', 'graphInstance'],
  computed: {
    relationGraph() {
      return this.graphInstance();
    },
    options() {
      return this.graph.options;
    }
  },
  mounted() {
    if (this.options.layouts.length > 1) {
      this.height -= 40;
    }
  },
  methods: {
    about() {
      alert('Welcome to Apple Products !');
    },
    refresh() {
      this.relationGraph.refresh();
    },
    switchLayout(layoutConfig) {
      this.relationGraph.switchLayout(layoutConfig, this.options);
      this.refresh();
    },
    toggleAutoLayout() {
      // devLog('this.options.autoLayouting', this.options.autoLayouting)
      this.relationGraph.toggleAutoLayout();
    },
    downloadAsFile(format) {
      // devLog('this.options.autoLayouting', this.options.autoLayouting)
      alert('download as :' + format);
    },
    async zoomToFit() {
      await this.relationGraph.setZoom(100);
      await this.relationGraph.moveToCenter();
      await this.relationGraph.zoomToFit();
    },
    startAddNode(e) {
      this.relationGraph.startCreatingNodePlot(e, {
        templateText: 'Node',
        templateNode: {
          className: 'my-node-template'
        },
        onCreateNode: (x, y) => {
          console.log('新增节点:', x, y);
          const newId = this.newNodeIdIndex++
          this.relationGraph.addNodes([{
            id: 'newNode-' + newId,
            text: '新节点' + newId,
            color: '#5da0f8',
            x: x - 50,
            y: y - 50
          }]);
        }
      });
    },
    startAddLine(e) {
      this.$message({type: 'success', message: '点击节点开始创建线条!'})
      this.relationGraph.startCreatingLinePlot(e, {
        template: {
          lineWidth: 3,
          color: '#8080ff',
          text: '新连线'
        },
        onCreateLine: (from, to) => {
          console.log('新增连线:', from, to);
          if (to.id) { // 创建的连线的起点一定是节点,但终点可以是空白处,终点没有选择成节点时to不是一个节点,to.id不会有值,这里做了判断,只处理to为节点的情况
            const newLineId = this.newLineIdIndex++;
            this.relationGraph.addLines([{
              from: from.id,
              to: to.id,
              lineWidth: 3,
              color: '#8080ff',
              text: '新连线' + newLineId
            }]);
          }
        }
      });
    }
  }
};
</script>

<style lang="scss" scoped>
@keyframes rotate {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(1440deg); }
}
  .rg-icon {
    width: 16px;
    height: 16px;
    vertical-align: -3px;
    fill: currentColor;
    overflow: hidden;
  }
  .c-mb-button{
    height:50px;
    width:50px;
    padding-top:5px;
    background-color: #ffffff;
    opacity: 1;
    text-align: center;
    cursor: pointer;
    color: #999999;
    font-size: 18px;
    box-sizing:border-box;
    position: relative;
    border-radius: 5px;
  }
  .c-mb-button .c-mb-text{
    font-size: 12px;
    line-height: 12px;
    color: #262626;
  }
  .c-mb-button-on{
    background-color: #2E74B5;
    border-top: #2E4E8F solid 1px;
    color: #ffffff;
  }
  .c-mb-button:hover{
    background-color: #00bb00;
    border-top: #00bb00 solid 1px;
    color: #ffffff;
  }
  .c-mb-button:hover .c-mb-text,.c-mb-button-on .c-mb-text{
    color: #ffffff;
  }
  .c-mb-button .c-mb-child-panel{
    height:46px;
    position: absolute;
    top: 0px;
    z-index: 999;
    background-color: #ffffff;
    display: none;
    border: #bbbbbb solid 1px;
    box-shadow: 0px 0px 8px #cccccc;
    box-sizing:border-box;
  }
  .c-mb-button:hover .c-mb-child-panel{
    display: block;
  }
  .c-mb-button .c-mb-button{
    height:44px;
    width: 42px;
    margin:0px;
    border: none;
  }
  .c-mb-button-c .c-mb-text{
    color: #262626 !important;
  }
  .c-mb-button-c:hover .c-mb-text,.c-mb-button-on .c-mb-text{
    color: #ffffff !important;
  }
  .c-loading-icon{
    animation:turn 1s linear infinite;
  }
  @keyframes turn{
    0%{-webkit-transform:rotate(0deg);}
    25%{-webkit-transform:rotate(90deg);}
    50%{-webkit-transform:rotate(180deg);}
    75%{-webkit-transform:rotate(270deg);}
    100%{-webkit-transform:rotate(360deg);}
  }
</style>
<style scoped>

</style>

是的,需求是类思维导图的网状链路图,有点复杂

OK,有需要整理好再留言

这插件功能挺好的,就是Demo都要vip 有点头疼 关键是API写的不太好确实没时间看他的源码

你这跟拓扑连接有点类似,看CSDN

【使用echarts画设备拓扑图 - CSDN App】使用echarts画设备拓扑图_echarts 拓扑图-CSDN博客

是的 当时不用echarts主要考虑节点DOM好操作,后面有动态动节点跟画线等操作

学习了 :tieba_003:

佬拷个react的 :tieba_087:

那个demo呀