Antv/X6<图编辑引擎>

1618558278291.jpg
  • 你可以直接查看🌟🌟 官方文档 🌟🌟,文档写的很棒👍👍;
  • 你也可以简单听我BBLL,顺便告诉你我的亲身经历,和常用参数;

遇到问题:

当你遇到下面这几种图形编辑时,你可能会想:
🤔这不是要专门搞一套js代码吗,canvas我也不熟练啊😱,这工程量也太大了,放弃吧!!跟老板说这个需求做不了

2021-04-16 10-46-28.gif

当然x1,起初我也是这么想的,而且我也尝试了自己用canvas写一个这个东西~
当然x2,写起来又是一码事了,什么小问题各种层出不穷,然后果断弃坑~
当然x3,我可以百度找现成的代码啊,网上那么多插件啥的,后来才发现鱼目混杂,根本无从下手,万一遇到问题找谁?
🙆♂️🙆♂️所以,在最后朋友(朋友多还是有那么点用的,致敬 🐯木兄)的推荐下,还是遇到了它 ------🌟🌟 Antv/X6🌟🌟!!

步入正题:

2021-04-16 10-32-55.gif

上图是我结合自身项目需求+ Vue,制作的类似于一个工厂智能小车工作线路图,没听懂的,也可以忽略这句话😓~
🌟🌟 官方文档入口 🌟🌟
常用参数:

      //创建地图
      this.graph = new Graph({
        snapline: true, //对齐线
        history: {
          enabled: true, //历史记录
          ignoreChange: true //ignoreChange 是否忽略属性变化
        },
        panning: true, //支持平移拖拽
        container: document.getElementById('container'),
        width: window.innerWidth,
        height: window.innerHeight,
        background: {
          color: '#fffbe6' // 设置画布背景颜色
        },
        grid: {
          size: 10, // 网格大小 10px
          visible: true // 渲染网格背景
        },
        connecting: {
          allowPort: true, //是否允许边链接到链接桩
          allowEdge: false, //是否允许边链接到另一个边
          allowNode: false, //是否允许边链接到节点(非节点上的链接桩)
          allowLoop: false, //是否允许创建循环连线,即边的起始节点和终止节点为同一节点
          allowMulti: false, //是否允许在相同的起始节点和终止之间创建多条边
          allowBlank: false, //是否允许连接到画布空白位置的点
          // 自动吸附
          snap: {
            radius: 20
          }
        }
      })
//加载地图数据
this.graph.fromJSON(this.data)

右上角工具栏:

image.png

自己写的,主要包含 撤销重做,创建节点,图形放大,图形缩小,重置视图,数据导出

//撤销操作
this.graph.undo()
//重做
this.graph.redo()
//创建节点
 const rect = this.graph.addNode({
  shape: 'rect', // 指定使用何种图形,默认值为 'rect'
  ...
})
//地图放大缩小
 this.graph.zoom(0.1)
 this.graph.zoom(-0.1)
//重置视图
this.graph.centerContent() //画布居中
this.graph.zoom(0)
//序列化/反序列化 数据格式
// https://antv-x6.gitee.io/zh/docs/tutorial/intermediate/serialization
// graph.toJSON() 方法来导出图中的节点和边
this.graph.toJSON()
选中和工具Tools:

2021-04-16 11-13-07.gif

使用工具 Tools文档
节点和边选中代码示例
节点和边选中——事件属性参考文档

//在创建节点的时候,直接加入删除按钮tools
  const rect = this.graph.addNode({
          shape: 'rect', // 指定使用何种图形,默认值为 'rect'
          ...
          attrs: {
          ...
          },
         //使用工具
          tools: [
            {
              name: 'button-remove', // 工具名称
              args: { x: 5, y: 5 } // 工具对应的参数
            }
          ]})
案例代码:
<template>
  <div>
    <span class="toolbar">
      <div class="tool" title="撤销" @click="toUndo()">
        <i class="el-icon-refresh-left"></i>
      </div>
      <div class="tool" title="重做" @click="toRedo()">
        <i class="el-icon-refresh-right"></i>
      </div>
      <div class="tool" title="创建节点" @click="createNodes('rect')">
        <div class="rect"></div>
      </div>
      <div class="tool" @click="mapZoom('+')" title="放大视图">
        <i class="el-icon-zoom-in"></i>
      </div>
      <div class="tool" @click="mapZoom('-')" title="缩小视图">
        <i class="el-icon-zoom-out"></i>
      </div>
      <div class="tool" @click="mapZoom()" title="重置视图">
        <i class="el-icon-full-screen"></i>
      </div>
      <div class="tool" @click="save()" title="保存">
        <i class="el-icon-cpu"></i>
      </div>
    </span>
    <div id="container"></div>
  </div>
</template>

<script>
import { Graph } from '@antv/x6'
export default {
  data() {
    return {
      graph_zoom: 0, //地图缩放比例
      graph: null,
      //   data: {
      // 节点
      // nodes: [
      //   {
      //     id: 'node1', // String,可选,节点的唯一标识
      //     x: 40, // Number,必选,节点位置的 x 值
      //     y: 40, // Number,必选,节点位置的 y 值
      //     width: 60, // Number,可选,节点大小的 width 值
      //     height: 60, // Number,可选,节点大小的 height 值
      //     label: 'node1', // String,节点标签
      //     tools: ['button-remove']
      //   },
      //   {
      //     id: 'node2', // String,节点的唯一标识
      //     x: 160, // Number,必选,节点位置的 x 值
      //     y: 180, // Number,必选,节点位置的 y 值
      //     width: 60, // Number,可选,节点大小的 width 值
      //     height: 60, // Number,可选,节点大小的 height 值
      //     label: 'node2' // String,节点标签
      //   },
      //   {
      //     id: 'node3', // String,节点的唯一标识
      //     x: 40, // Number,必选,节点位置的 x 值
      //     y: 180, // Number,必选,节点位置的 y 值
      //     width: 60, // Number,可选,节点大小的 width 值
      //     height: 60, // Number,可选,节点大小的 height 值
      //     label: 'node3' // String,节点标签
      //   }
      // ],
      // 边
      // edges: [
      //   {
      //     source: 'node1', // String,必须,起始节点 id
      //     target: 'node2' // String,必须,目标节点 id
      //   }
      // ],
      //   }
      data: []
    }
  },
  mounted() {
    this.init()
  },
  methods: {
    //初始化地图
    init() {
      //创建地图
      this.graph = new Graph({
        snapline: true, //对齐线
        history: {
          enabled: true, //历史记录
          ignoreChange: true //ignoreChange 是否忽略属性变化
        },
        panning: true, //支持平移拖拽
        container: document.getElementById('container'),
        width: window.innerWidth,
        height: window.innerHeight,
        background: {
          color: '#fffbe6' // 设置画布背景颜色
        },
        grid: {
          size: 10, // 网格大小 10px
          visible: true // 渲染网格背景
        },
        connecting: {
          allowPort: true, //是否允许边链接到链接桩
          allowEdge: false, //是否允许边链接到另一个边
          allowNode: false, //是否允许边链接到节点(非节点上的链接桩)
          allowLoop: false, //是否允许创建循环连线,即边的起始节点和终止节点为同一节点
          allowMulti: false, //是否允许在相同的起始节点和终止之间创建多条边
          allowBlank: false, //是否允许连接到画布空白位置的点
          // 自动吸附
          snap: {
            radius: 20
          },
          // createEdge() {
          //   //创建动画虚线边
          //   return new Shape.Edge({
          //     attrs: {
          //       line: {
          //         stroke: '#1890ff',
          //         strokeDasharray: 5,
          //         targetMarker: 'classic',
          //         style: {
          //           animation: 'ant-line 30s infinite linear'
          //         }
          //       }
          //     }
          //   })
          // }
        }
      })
      //加载地图数据
      this.graph.fromJSON(this.data)
      this.graph.centerContent() //画布居中
      //节点点击事件
      this.graph.on('node:click', ({ e, x, y, node, view }) => {
        // console.log(node)
        this.selectReset()
        node.attr('body/stroke', 'orange')
      })
      //边点击事件
      this.graph.on('edge:click', ({ e, x, y, edge, view }) => {
        // console.log(edge)
        this.selectReset()
        edge.attr('line/stroke', 'orange')
        edge.prop('labels/0', {
          attrs: {
            body: {
              stroke: 'orange'
            }
          }
        })
      }),
        //节点双击事件
        this.graph.on('node:dblclick', ({ e, x, y, node, view }) => {
          alert('节点ID:' + node.id)
          console.log(node)
        })
      //边双击事件
      this.graph.on('edge:dblclick', ({ e, x, y, edge, view }) => {
        console.log(edge)
        alert(
          `边ID:${edge.id}, 起始节点: ${edge.source.cell},目标节点: ${edge.target.cell}`
        )
      })
    },
    //保存,获取节点等数据
    save() {
      //序列化/反序列化 数据格式
      // https://antv-x6.gitee.io/zh/docs/tutorial/intermediate/serialization
      // graph.toJSON() 方法来导出图中的节点和边
      console.log(this.graph.toJSON())
    },
    //撤销操作
    toUndo() {
      this.graph.undo()
    },
    //重做
    toRedo() {
      this.graph.redo()
      //   if (this.graph.isHistoryEnabled()) {
      //     this.graph.disableHistory()
      //   } else {
      //     this.graph.enableHistory()
      //   }
    },
    //地图放大缩小
    mapZoom(type) {
      if (type == '+') {
        this.graph.zoom(0.1)
        this.graph_zoom += 0.1
      } else if (type == '-') {
        this.graph.zoom(-0.1)
        this.graph_zoom -= 0.1
      } else {
        this.graph.zoom(
          this.graph_zoom <= 0 ? Math.abs(this.graph_zoom) : -this.graph_zoom
        )
        this.graph.centerContent() //画布居中
        this.graph_zoom = 0
      }
    },
    //创建节点
    createNodes(type) {
      if (type == 'rect') {
        const rect = this.graph.addNode({
          shape: 'rect', // 指定使用何种图形,默认值为 'rect'
          x: 0,
          y: 0,
          width: 60,
          height: 60,
          angle: 0,
          attrs: {
            body: {
              fill: '#fff', // 背景颜色
              stroke: '#000' // 边框颜色
            },
            label: {
              text: 'Node', // 文本
              fill: '#333', // 文字颜色
              fontSize: 13 // 文字大小
            }
          },
          tools: [
            {
              name: 'button-remove', // 工具名称
              args: { x: 5, y: 5 } // 工具对应的参数
            }
          ],
          //连接桩
          //   ports: [
          // {
          //   id: 'port1',
          //   attrs: {
          //     circle: {
          //       r: 6,
          //       //    magnet: true 这个特殊属性,使链接桩在连线交互时可以被连接上
          //       magnet: true,
          //       stroke: '#31d0c6',
          //       strokeWidth: 2,
          //       fill: '#fff'
          //     }
          //   }
          // },
          // {
          //   id: 'port2',
          //   attrs: {
          //     circle: {
          //       r: 6,
          //       magnet: true,
          //       stroke: '#31d0c6',
          //       strokeWidth: 2,
          //       fill: '#fff'
          //     }
          //   }
          // }
          //   ]
          ports: {
            groups: {
              // 输入链接桩群组定义
              left: {
                position: 'left',
                attrs: {
                  circle: {
                    r: 4,
                    magnet: true,
                    stroke: '#31d0c6',
                    strokeWidth: 2,
                    fill: '#fff'
                  }
                }
              },
              right: {
                position: 'right',
                attrs: {
                  circle: {
                    r: 4,
                    magnet: true,
                    stroke: '#31d0c6',
                    strokeWidth: 2,
                    fill: '#fff'
                  }
                }
              },
              top: {
                position: 'top',
                attrs: {
                  circle: {
                    r: 4,
                    magnet: true,
                    stroke: '#31d0c6',
                    strokeWidth: 2,
                    fill: '#fff'
                  }
                }
              },
              bottom: {
                position: 'bottom',
                attrs: {
                  circle: {
                    r: 4,
                    magnet: true,
                    stroke: '#31d0c6',
                    strokeWidth: 2,
                    fill: '#fff'
                  }
                }
              }
            },
            items: [
              {
                id: 'port1-1',
                group: 'left'
              },
              {
                id: 'port1-2',
                group: 'left',
                attrs: {
                  circle: {
                    stroke: '#e9352f'
                  }
                }
              },
              {
                id: 'port2-1',
                group: 'right',
                attrs: {
                  circle: {
                    stroke: '#e9352f'
                  }
                }
              },
              {
                id: 'port2-2',
                group: 'right'
              },
              {
                id: 'port3-1',
                group: 'top',
                attrs: {
                  circle: {
                    stroke: '#e9352f'
                  }
                }
              },
              {
                id: 'port3-2',
                group: 'top'
              },
              {
                id: 'port4-1',
                group: 'bottom'
              },
              {
                id: 'port4-2',
                group: 'bottom',
                attrs: {
                  circle: {
                    stroke: '#e9352f'
                  }
                }
              }
            ]
          }
        })
      }
      //   console.log(this.graph)
    },
    //创建边
    createEdges(type) {
      const rect = this.graph.addEdge({
        shape: 'edge', // 指定使用何种图形,默认值为 'edge'
        source: 'node1',
        target: 'node3'
      })
    },
    //选择节点,边时重置颜色
    selectReset() {
      //   this.graph.drawBackground({ color: '#fff' })
      const nodes = this.graph.getNodes()
      const edges = this.graph.getEdges()

      nodes.forEach(node => {
        node.attr('body/stroke', '#000')
      })

      edges.forEach(edge => {
        edge.attr('line/stroke', 'black')
        edge.prop('labels/0', {
          attrs: {
            body: {
              stroke: 'black'
            }
          }
        })
      })
    }
  }
}
</script>

<style lang="scss" scoped>
.toolbar {
  //   padding: 0 20px;
  box-sizing: border-box;
  height: 40px;
  background: white;
  box-shadow: 0 2px 6px #e1e1e1;
  position: fixed;
  right: 0;
  z-index: 999;
  display: flex;
  align-items: center;
  .tool {
    cursor: pointer;
    width: 40px;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    &:hover {
      background: #f1f1f1;
    }
  }
}
.rect {
  width: 10px;
  height: 10px;
  border: 1px solid #5a5a5a;
  //   color: #e9352f;
}
</style>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容