最近公司要求做一个拓扑流程图,在网上搜寻了一些可行性方案之后,发现好一点的可视化拓扑图都是要收费的,于是决定自己在阿里的antv x6基础上做出一款简单的产品,以便于后期进行修改和操作
项目主要版本号:
vue版本:2.x,
antv:@antv/x6": "^1.17.3", "@antv/x6-vue-shape": "^1.2.0",
css预编译器: "less": "^3.10.3",
ui框架:iview ,"view-design": "^4.1.1",
项目主要贴图gif:
具体的项目代码下载地址,欢迎点赞+关注:https://gitee.com/yanggengzhen/vue-antvx6-demo/tree/master
贴上部分代码:
<template>
<div class="container_warp">
<div id="containerChart"></div>
<RightDrawer class="right_drawer" :drawerType="type" :selectCell="selectCell" :graph="graph" :grid="grid" @deleteNode="deleteNode"></RightDrawer>
<div class="operating">
<div class="btn-group">
<div class="btn" title="圆形节点" @mousedown="startDrag(\'Circle\',$event)">
<i class="iconfont icon-circle"></i>
</div>
<div class="btn" title="正方形节点" @mousedown="startDrag(\'Rect\',$event)">
<i class="iconfont icon-square"></i>
</div>
<div class="btn" title="条件节点">
<i class="iconfont icon-square rotate-square" @mousedown="startDrag(\'polygon\',$event)"></i>
</div>
<div class="btn-group_tips" v-if="showTips">
拖拽生成</br>资产拓扑图形
</div>
</div>
<div class="btn-group">
<Tooltip content="直线箭头" placement="bottom">
<div :class=" [\'btn\',currentArrow === 1?\'currentArrow\':\'\']" @click="changeEdgeType(\'normal\')">
<i class="iconfont icon-ai28"></i>
</div>
</Tooltip>
<Tooltip content="曲线箭头" placement="bottom">
<div :class=" [\'btn\',currentArrow === 2?\'currentArrow\':\'\']" @click="changeEdgeType(\'smooth\')">
<i class="iconfont icon-Down-Right"></i>
</div>
</Tooltip>
<Tooltip content="直角箭头" placement="bottom">
<div :class=" [\'btn\',currentArrow === 3?\'currentArrow\':\'\']" @click="changeEdgeType()">
<i class="iconfont icon-jiantou"></i>
</div>
</Tooltip>
</div>
<div class="btn-group">
<Tooltip content="删除" placement="bottom">
<div class="btn" @click="deleteNode()" style="margin-top: 5px;">
<i class="iconfont icon-shanchu"></i>
</div>
</Tooltip>
<Tooltip content="保存PNG" placement="bottom">
<div class="btn" @click="saveToPNG()" title="保存">
<i class="iconfont icon-baocun"></i>
</div>
</Tooltip>
</div>
</div>
</div>
</template>
<script>
import \'@antv/x6-vue-shape\'
import { Graph,Shape,Addon,FunctionExt,DataUri} from \'@antv/x6\';
import RightDrawer from \'./components/RightDrawer\';
import insertCss from \'insert-css\';
import {startDragToGraph} from \'./Graph/methods.js\'
const data = {};
export default {
data() {
return {
graph:\'\',
value1: true,
type:\'grid\',
selectCell:\'\',
connectEdgeType:{ //连线方式
connector: \'normal\',
router: {
name: \'\'
}
},
showTips:false,
currentArrow:1,
grid:{ // 网格设置
size: 20, // 网格大小 10px
visible: true, // 渲染网格背景
type: \'mesh\',
args: {
color: \'#D0D0D0\',
thickness: 1, // 网格线宽度/网格点大小
factor: 10,
},
}
}
},
components:{
RightDrawer
},
methods: {
initX6(){
var _that = this
this.graph = new Graph({
container: document.getElementById(\'containerChart\'),
width: 1700,
height: \'100%\',
grid: _that.grid,
resizing: { //调整节点宽高
enabled: true,
orthogonal:false,
},
selecting: true, //可选
snapline: true,
interacting: {
edgeLabelMovable: true
},
connecting: { // 节点连接
anchor: \'center\',
connectionPoint: \'anchor\',
allowBlank: false,
snap: true,
createEdge () {
return new Shape.Edge({
attrs: {
line: {
stroke: \'#1890ff\',
strokeWidth: 1,
targetMarker: {
name: \'classic\',
size: 8
},
strokeDasharray: 0, //虚线
style: {
animation: \'ant-line 30s infinite linear\',
},
},
},
label: {
text:\'\'
},
connector: _that.connectEdgeType.connector,
router: {
name: _that.connectEdgeType.router.name || \'\'
},
zIndex: 0
})
},
},
highlighting: {
magnetAvailable: {
name: \'stroke\',
args: {
padding: 4,
attrs: {
strokeWidth: 4,
stroke: \'#6a6c8a\'
}
}
}
},
});
insertCss(`
@keyframes ant-line {
to {
stroke-dashoffset: -1000
}
}
`)
this.graph.fromJSON(data)
this.graph.history.redo()
this.graph.history.undo()
// 鼠标移入移出节点
this.graph.on(\'node:mouseenter\',FunctionExt.debounce(() => {
const container = document.getElementById(\'containerChart\')
const ports = container.querySelectorAll(
\'.x6-port-body\'
)
this.showPorts(ports, true)
}),
500
)
this.graph.on(\'node:mouseleave\', () => {
const container = document.getElementById(\'containerChart\')
const ports = container.querySelectorAll(
\'.x6-port-body\'
)
this.showPorts(ports, false)
})
this.graph.on(\'blank:click\', () => {
this.type = \'grid\'
})
this.graph.on(\'cell:click\', ({ cell }) => {
this.type = cell.isNode() ? \'node\' : \'edge\'
})
this.graph.on(\'selection:changed\', (args) => {
args.added.forEach(cell => {
this.selectCell = cell
if(cell.isEdge()){
cell.isEdge() && cell.attr(\'line/strokeDasharray\', 5) //虚线蚂蚁线
cell.addTools([
{
name: \'vertices\',
args: {
padding: 4,
attrs: {
strokeWidth: 0.1,
stroke: \'#2d8cf0\',
fill: \'#ffffff\',
}
},
},
])
}
})
args.removed.forEach(cell => {
cell.isEdge() && cell.attr(\'line/strokeDasharray\', 0) //正常线
cell.removeTools()
})
})
},
showPorts (ports, show) {
for (let i = 0, len = ports.length; i < len; i = i + 1) {
ports[i].style.visibility = show ? \'visible\' : \'hidden\'
}
},
// 拖拽生成正方形或者圆形
startDrag(type,e){
startDragToGraph(this.graph,type,e)
},
// 删除节点
deleteNode(){
const cell = this.graph.getSelectedCells()
this.graph.removeCells(cell)
this.type = \'grid\'
},
// 保存png
saveToPNG(){
this.$nextTick(()=>{
this.graph.toPNG((dataUri) => {
// 下载
DataUri.downloadDataUri(dataUri, \'资产拓扑图.png\')
},{
backgroundColor: \'white\',
padding: {
top: 50,
right: 50,
bottom: 50,
left: 50
},
quality: 1,
copyStyles:false
})
})
},
// 改变边形状
changeEdgeType(e){
if(e === \'normal\'){
this.connectEdgeType = {
connector: \'normal\',
router: {name: \'\'}
}
this.currentArrow = 1
}else if (e === \'smooth\'){
this.connectEdgeType = {
connector: \'smooth\',
router: {name: \'\'}
}
this.currentArrow = 2
}else{
this.connectEdgeType = {
connector: \'normal\',
router: {name: \'manhattan\'}
}
this.currentArrow = 3
}
}
},
mounted(){
this.initX6()
setTimeout(()=>{
this.showTips = true
},1000)
setTimeout(()=>{
this.showTips = false
},5000)
}
};
</script>
<style lang="less">
@import \'../assets/iconfont.css\';
@import \'./index.less\';
</style>
<template>
<div class="drawer_container">
<div v-if="drawerType === \'grid\'">
<div class="drawer_title">画布背景设置</div>
<div class="drawer_wrap">
<Form label-position="left" :label-width="85">
<FormItem label="是否显示网格" :label-width="100">
<i-switch v-model="showGrid" @on-change="changeGrid" />
</FormItem>
<div v-show="showGrid">
<FormItem label="网格类型">
<RadioGroup v-model="grid.type" @on-change="changeGridType">
<Radio v-for="item in gridTypeList" :label="item.value" :key="item.value">
<span>{{item.label}}</span>
</Radio>
</RadioGroup>
</FormItem>
<FormItem label="网格大小">
<Slider v-model="grid.size" :min="0" :max="30" @on-change="changeGrid"></Slider>
</FormItem>
<FormItem label="网格颜色">
<ColorPicker v-model="grid.args.color" @on-change="changeGrid"/>
</FormItem>
<FormItem label="网格线宽度">
<Slider v-model="grid.args.thickness" :min="0" :max="20" @on-change="changeGrid"></Slider>
</FormItem>
</div>
</Form>
</div>
</div>
<div v-if="drawerType === \'node\'">
<div class="drawer_title">节点设置</div>
<div class="drawer_wrap">
<Form label-position="left" :label-width="80">
<FormItem label="节点文本">
<Input v-model="drawerNode.nodeText" @on-change="changeNodeText"></Input>
</FormItem>
<FormItem label="节点背景">
<ColorPicker v-model="drawerNode.fill" @on-change="changeFill"/>
</FormItem>
<FormItem label="字体大小">
<Slider v-model="drawerNode.fontSize" :min="10" :max="20" @on-change="changefontSize"></Slider>
</FormItem>
<FormItem label="字体颜色">
<ColorPicker v-model="drawerNode.fontFill" @on-change="changeFontFill"/>
</FormItem>
<FormItem label="边框宽度">
<Slider v-model="drawerNode.strokeWidth" :min="0" :max="10" @on-change="changeStrokeWidth"></Slider>
</FormItem>
<FormItem label="边框颜色">
<ColorPicker v-model="drawerNode.stroke" @on-change="changeStroke"/>
</FormItem>
<FormItem label="功能">
<Button type="primary" icon="md-trending-up" @click="toTopZIndex">置顶</Button>
<Button type="error" class="margin-left-10" icon="md-trash" @click="deleteNode">删除</Button>
</FormItem>
</Form>
</div>
</div>
<div v-if="drawerType === \'edge\'">
<div class="drawer_title">线条设置</div>
<div class="drawer_wrap">
<Form label-position="left" :label-width="80">
<FormItem label="线条文本">
<Input v-model="drawerEdge.EdgeText" @on-change="changeEdgeText"></Input>
</FormItem>
<FormItem label="线条宽度">
<Slider v-model="drawerEdge.edgeWidth" :min="1" :max="10" @on-change="changeEdgeWidth"></Slider>
</FormItem>
<FormItem label="线条颜色">
<ColorPicker v-model="drawerEdge.edgeColor" @on-change="changeEdgeColor"/>
</FormItem>
<FormItem label="功能">
<Button type="primary" icon="md-trending-up" @click="toTopZIndex">置顶</Button>
<Button type="error" class="margin-left-10" icon="md-trash" @click="deleteNode">删除</Button>
</FormItem>
</Form>
</div>
</div>
</div>
</template>
<script>
export default {
name:\'RightDrawer\',
data() {
return {
gridTypeList:[
{
label:\'四边网格\',
value:\'mesh\'
},
{
label:\'点状网格\',
value:\'dot\'
}
],
showGrid:true,
drawerNode:{
fill:\'\',
nodeText:\'\',
fontSize:null,
fontFill:\'\',
strokeWidth:null,
stroke:\'\'
},
drawerEdge:{
EdgeText:\'\',
edgeWidth:null,
edgeColor:\'\'
},
};
},
props:{
drawerType: {
type: String
},
selectCell:{
type: String | Object
},
graph:{
type: String | Object
},
grid:{
type: Object
}
},
created() {
},
mounted() {
},
watch:{
selectCell:{
handler(val) {
if(val){
if(val.isNode()){ //节点
this.drawerNode.fill = val.store.data.attrs.body.fill
this.drawerNode.nodeText = val.store.data.attrs.label.text
this.drawerNode.fontFill = val.store.data.attrs.label.fill
this.drawerNode.fontSize = Number(val.store.data.attrs.label.fontSize)
this.drawerNode.strokeWidth = Number(val.store.data.attrs.body.strokeWidth)
this.drawerNode.stroke = val.store.data.attrs.body.stroke
}else{ //边
this.drawerEdge.EdgeText = val.store.data.labels?val.store.data.labels[0].text:\'\'
this.drawerEdge.edgeWidth = Number(val.store.data.attrs.line.strokeWidth)
this.drawerEdge.edgeColor = val.store.data.attrs.line.stroke
}
}
},
immediate: true,
deep: false
},
},
methods: {
// 网格设置
changeGrid(){
this.showGrid?this.graph.showGrid():this.graph.hideGrid()
},
changeGridType(e){
this.grid.type = e
this.changeGrid()
},
changeGrid(){
this.graph.drawGrid({
...this.grid
})
},
// 节点设置
changeStrokeWidth(val){
this.selectCell.attr(\'body/strokeWidth\', val)
},
changefontSize(val){
this.selectCell.attr(\'label/fontSize\',val)
},
changeNodeText(){
this.selectCell.attr(\'label/text\', this.drawerNode.nodeText)
},
changeStroke(val){
this.drawerNode.stroke = val
this.selectCell.attr(\'body/stroke\', this.drawerNode.stroke)
},
changeFontFill(val){
this.drawerNode.fontFill = val
this.selectCell.attr(\'label/fill\', this.drawerNode.fontFill)
},
changeFill(val){
this.drawerNode.fill = val
this.selectCell.attr(\'body/fill\', val)
},
// 边设置
changeEdgeText(){
console.log(this.drawerEdge.EdgeText);
this.selectCell.setLabels(
[{attrs:{label:{text:this.drawerEdge.EdgeText}}}]
)
},
changeEdgeWidth(val){
this.drawerEdge.edgeWidth = val
this.selectCell.attr(\'line/strokeWidth\', this.drawerEdge.edgeWidth)
},
changeEdgeColor(val){
this.drawerEdge.stroke = val
this.selectCell.attr(\'line/stroke\', this.drawerEdge.stroke)
},
// 置顶
toTopZIndex(){
this.selectCell.toFront()
},
// 删除
deleteNode(){
this.$emit(\'deleteNode\')
},
},
};
</script>
<style lang="less" scoped>
.drawer_container {
max-width: 300px;
min-width: 300px;
.drawer_title {
border-bottom: 1px solid #e8eaec;
box-sizing: border-box;
padding: 14px 16px;
color: #333;
font-size: 16px;
}
.drawer_wrap {
box-sizing: border-box;
padding: 20px 10px 20px 20px;
}
}
</style>