从0到1实现流程图应用03-图形篇

开始

在流程图中我们需要通过拖拽交互往画布中添加节点,X6 不仅内置了强大的拖拽功能,还内置了很多常用的图形,接下来我们一起实现基础的流程图图形以及图形拖拽功能。

实现

图形定义

首先来看下一个简单的矩形节点的基础配置:

graph.addNode({
  shape: 'rect',
  x: 100,
  y: 100,
  width: 80,
  height: 40,
  attrs: {
    body: {
      stroke: 'red'
    }
  }
})
1.png

shape:定义图形的形状,X6 内置了 rect、circle、ellipse、polygon、polyline、image、html 等基础形状
x/y:定义图形的左上角坐标
width/height:定义图形的尺寸
看到 attrs 大家可能比较奇怪,这是什么东西?其实可以将 attrs 看做 css 样式集合,其中 body 类似于 css 选择器,body 的值是被选中元素的属性。那 body 又是哪来的呢?这里就要说的另一个重要的配置项markup,markup 表示的是图形的 DOM 结构,内置的 rect 的默认 markup 为:

[
  {
    tagName: 'rect',
    selector: 'body',
  },
  {
    tagName: 'text',
    selector: 'label',
  },
]

渲染完成后,实际生效的 DOM 为:

<g data-cell-id="ca715562-8faf-4c88-a242-2b18d4ce47a6" data-shape="rect" class="x6-cell x6-node" transform="translate(100,100)">
  <rect fill="#ffffff" stroke="red" stroke-width="2" width="80" height="40"></rect>
  <text font-size="14" fill="#000000" text-anchor="middle" text-vertical-anchor="middle" font-family="Arial, helvetica, sans-serif" transform="matrix(1,0,0,1,40,20)"></text>
</g>

所以说一个图形是由 markup 和 attrs 来决定结构和样式,我们可以通过设置 markup 和 attrs 来定义自己业务中的图形,看下面的例子:

graph.addNode({
  shape: 'rect',
  x: 60,
  y: 60,
  width: 70,
  height: 70,
  markup: [
    {
      tagName: 'rect',
      selector: 'r1'
    },
    {
      tagName: 'circle',
      selector: 'c1'
    },
    {
      tagName: 'circle',
      selector: 'c2'
    },
    {
      tagName: 'circle',
      selector: 'c3'
    },
    {
      tagName: 'circle',
      selector: 'c4'
    }
  ],
  attrs: {
    r1: {
      width: 70,
      height: 70,
      stroke: '#ccc',
      rx: 12,
      ry: 12,
    },
    c1: {
      r: 10,
      cx: 20,
      cy: 20,
      fill: '#000'
    },
    c2: {
      r: 10,
      cx: 50,
      cy: 20,
      fill: '#000'
    },
    c3: {
      r: 10,
      cx: 20,
      cy: 50,
      fill: '#000'
    },
    c4: {
      r: 10,
      cx: 50,
      cy: 50,
      fill: '#000'
    },
  }
})
2.png

上面可以看到定义图形的时候,属性是固定写死的,在业务场景中,经常需要动态修改节点的样式,X6 中也提供了非常便利的方法:

const node = graph.addNode({
  shape: 'rect',
  x: 100,
  y: 100,
  width: 80,
  height: 40,
  attrs: {
    body: {
      stroke: 'red'
    }
  }
})
node.attr('body/stroke', 'green')
node.attr('body/fill', 'yellow')

我们的图形将会变成下面这样:


3.jpeg

那么问题来了,如果多个图形的结构和样式相似度很高,每次定义图形都要写很多类似的代码,有没有一种方式可以将图形之间的公共属性抽象出来呢?X6 提供了很优雅的方式来解决这个问题,首先注册自定义的节点类型,在这里配置公共的属性,然后在添加节点的时候指定 shape 值为刚才注册的节点类型。

Graph.registerNode('custom-rect', {
  inherit: 'rect', // 继承自 Shape.Rect
  width: 300, // 默认宽度
  height: 40, // 默认高度
  attrs: {
    body: {
      rx: 10, // 圆角矩形
      ry: 10,
      strokeWidth: 1,
      fill: '#5755a1',
      stroke: '#5755a1',
    },
    label: {
      fill: '#fff',
      fontSize: 18,
      refX: 10, // x 轴偏移,类似 css 中的 margin-left
      textAnchor: 'left', // 左对齐
    }
  },
})

graph.addNode({
  shape: 'custom-rect',
  x: 50,
  y: 50,
  width: 100,
  height: 50,
  label: 'rect1'
})
graph.addNode({
  shape: 'custom-rect',
  x: 200,
  y: 50,
  width: 100,
  height: 50,
  label: 'rect2',
  attrs: {
    body: {
      fill: '#ccc'
    }
  }
})
4.png

图形拖拽

图形定义好了,接下来我们要实现图形拖拽功能,X6 提供了 Dnd 插件来提供基础的拖拽能力,并在 Dnd 基础上的进一步封装,提供了一个类似侧边栏的 UI 组件 Stencil,支持分组、折叠、搜索等能力。
首先提供一个 Stencil 的容器:

<!-- stencil 容器需要设置 position:relative 的样式 -->
<div id="stencil" style="position:relative"></div>

然后进行初始化(具体配置见官网):

const stencil = new Addon.Stencil({
  title: 'Flowchart',
  target: this.graph,
  stencilGraphWidth: 214,
  stencilGraphHeight: document.body.offsetHeight - 105,
  layoutOptions: {
    columns: 4,
    columnWidth: 48,
    rowHeight: 30,
    marginY: 30,
  },
})
const stencilContainer = document.querySelector('#stencil')
if (stencilContainer) {
  stencilContainer.appendChild(stencil.container)
}

然后将我们定义的图形加载到 stencil 中:

const r1 = graph.createNode({
  shape: 'rect',
  width: 30,
  height: 15,
})
stencil.load([r1])
5.jpeg

当我们将图形拖拽到画布的时候,想将 Stencil 中的图形等比例放大。查看官网,拖拽的流程为:

  1. 拖拽过程中可以在 getDragNode 中返回新的节点来自定义拖拽节点样式
  2. 拖拽结束可以在 getDropNode 中返回新的节点来自定义放置在画布中节点样式

下面的代码就是将拖拽节点等比例放大 3 倍后放置到画布中:

const stencil = new Addon.Stencil({
  getDropNode(node) {
    const size = node.size()
    return node.clone().size(size.width * 3, size.height * 3)
  }
})

最终效果:


6.gif

最后

X6 不仅支持上文提到的基础的 SVG 节点,还具备在节点中渲染 React、Vue 组件的能力。在实际业务场景中,如果对 SVG 不熟悉或者节点内容复杂,我们可以根据技术栈选择 React/Vue 渲染,这样在节点内部,我们可以用熟悉的方式绘制各种复杂的内容,可谓为所欲为。

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

推荐阅读更多精彩内容