AntV/x6文档第三篇(Cell)

AntV/x6基础第三篇,Cell配置和节点配置

Posted by catmai on March 15, 2024

弄清楚了的配置信息,可以开始着手学习shape的内容了。

shape是博客里的名称,官网的名称是model,有三种类型:Cell、Node、Edge。

官方文档

Cell

官网内容:

Cell 是 NodeEdge 的基类,包含节点和边的通用属性和方法定义,如属性样式、可见性、业务数据等,并且在实例化、样式定制、默认选项、自定义选项等方面具有相同的行为。

可以知道的是,cell是最基础的节点,cell的各项配置node和edge都可以修改。

构造函数

选项 类型 默认值 必选 描述
id string     节点/边的唯一标识,推荐使用具备业务意义的 ID,默认使用自动生成的 UUID。
markup Markup     节点/边的 SVG/HTML 片段。
attrs Attr.CellAttrs     节点/边属性样式。
shape string     渲染节点/边的图形。节点对应的默认值为 ‘rect’,边对应的默认值为 ‘edge’。
view string     渲染节点/边的视图。
zIndex number     节点/边在画布中的层级,默认根据节点/边添加顺序自动确定。
visible boolean true   节点/边是否可见。
parent string     父节点。
children string[]     子节点/边。
tools ToolItem | ToolItem[] | Tools     工具选项。
data any     节点/边关联的业务数据。

构造函数中的内容就是初始化cell时可选的配置,很多都可以望文生义,下面挑一些重要的仔细学习一下。

markup

markup应该是最复杂的一项,markup也很关键,关系到节点的形状和样式。

先贴一些原文:

markup 指定了渲染节点/边时使用的 SVG/HTML 片段,使用 JSON 格式描述。例如内置节点 Shape.Rect 的 markup 定义如下:

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

表示该节点内部包含 两个 SVG 元素,渲染到页面之后,节点对应的 SVG 元素看起来像下面这样。

<g data-cell-id="c2e1dd06-15c6-43a4-987a-712a664b8f85" class="x6-cell x6-node" transform="translate(40,40)">
  <rect fill="#fff" stroke="#000" stroke-width="2" fill-opacity="0.5" width="100" height="40"></rect>
  <text font-size="14" xml:space="preserve" fill="#333" text-anchor="middle" font-family="Arial, helvetica, sans-serif" transform="matrix(1,0,0,1,50,20)">
    <tspan dy="0.3em" class="v-line">rect</tspan>
  </text>
</g>

通过上面的介绍,我们大致了解了 Markup 的结构,下面我们将详细介绍 Markup 定义。

interface Markup {
  tagName: string
  ns?: string
  selector?: string
  groupSelector?: string | string[]
  attrs?: { [key: string]: string | number }
  style?: { [key: string]: string | number }
  className?: string | string[]
  textContent?: string
  children?: Markup[]
}

可配置参数:

选项 类型 默认值 必选 描述
tagName string   SVG/HTML 元素标签名。
ns string “http://www.w3.org/2000/svg”   SVG/HTML 元素命名空间。
selector string -   该元素的选择器(唯一),通过选择器来定位该元素或为该元素指定属性样式。
groupSelector string -   该元素的群组选择器,可以同时为该群组对应的多个元素指定样式。
attrs Attr.SimpleAttrs -   该元素的默认属性键值对。
style JQuery.PlainObject<string | number> -   该元素的行内样式键值对。
className string -   该元素的 CSS 样式名。
textContent string -   该元素的文本内容。
children Markup[] -   嵌套的子元素。

tagname

通过 tagName 指定需要创建哪种 SVG/HTML 元素。

ns

该元素的命名空间。需要与 tagName 指定的的元素类型对应,默认使用 SVG 元素命名空间 “http://www.w3.org/2000/svg”。

  • SVG 元素命名空间为 “http://www.w3.org/2000/svg”
  • HTML 元素的命名空间为 “http://www.w3.org/1999/xhtml”

这里需要暂停一下,个人理解,tagname是指创建的元素类型是什么,ns是元素里的内容。

按我目前的理解,应该是这样:

<div id="tagname">
  <input id="ns">
    
  </input>
</div>

selector

该元素的唯一选择器,通过选择器为该元素指定属性样式。例如,为内置节点 Shape.Rect 指定 元素的属性样式:

const rect = new Shape.Rect({
  x: 40,
  y: 40,
  width: 100,
  height: 40,
  attrs: {
    // 指定 rect 元素的样式
    body: { 
      stroke: '#000', // 边框颜色
      fill: '#fff',   // 填充颜色
    },
    // 指定 text 元素的样式
    label: { 
      text: 'rect', // 文字
      fill: '#333', // 文字颜色
    },
  },
})

这块不是很理解。

后补充,这里理解了,selector就是markup下的一项配置,可以在attr中指定其样式。说简单点就是markup中元素的id,需要唯一。

groupSelector

该元素的群组选择器,通过群组选择器可以为该群组关联的多个元素指定样式。例如,下面 Markup 中两个 具备相同的 groupSelector 值 'group1':

{
  markup: [
    {
      tagName: 'rect',
      selector: 'body',
      groupSelector: 'group1',
    }, 
    {
      tagName: 'rect',
      selector: 'wrap',
      groupSelector: 'group1',
    }, 
    {
      tagName: 'text',
      selector: 'label',
    },
  ],
    }

创建节点时,我们可以像下面这样来指定群组样式:

new SomeNode({
  attrs: { 
    group1: {
      fill: '#2ECC71',
    },
  },
})

个人理解:group1相当于是存储了的通用样式。

attrs

注:这块感觉是关键,内容最多的部分,决定了整个shape的样式

该元素的默认属性键值对,通常用于定义那些不变的通用属性,这些默认属性也可以在实例化节点时被覆盖。需要注意的是 markup 中的 attrs 属性只支持原生的 SVG 属性,也就是说 X6 的自定义属性在这里不可用。

例如,我们为内置节点 Shape.Rect 的 元素指定了如下默认属性:

{
  markup: [
    {
      tagName: 'rect',
      selector: 'body',
      attrs: {
        fill: '#fff',
        stroke: '#000',
        strokeWidth: 2,
      }
    }, 
    {
      tagName: 'text',
      selector: 'label',
      attrs: {
        fill: '#333',
        textAnchor: 'middle',
        textVerticalAnchor: 'middle',
      }
    },
  ],
}

style

该元素的行内样式键值对。

这个跟attr里的内容有什么区别?存疑

className

该元素的 CSS 样式名。

同style的疑问。

textContent

该元素的文本内容。

children

嵌套的子元素。

attrs

属性选项 attrs 是一个复杂对象,该对象的 Key 是节点 Markup 定义中元素的选择器(selector),对应的值是应用到该 SVG 元素的 SVG 属性值(如 fillstroke),如果你对 SVG 属性还不熟悉,可以参考 MDN 提供的填充和边框入门教程。

例如,内置节点 Shape.Rect 的 Markup 定义了 ‘body’(代表 元素) 和 'label'(代表 元素) 两个选择器,我们可以像下面这样为该节点中的元素指定属性样式:

const rect = new Shape.Rect({
  x: 40,
  y: 40,
  width: 100,
  height: 40,
  attrs: { 
    body: {
      fill: '#2ECC71',
      stroke: '#000',
    },
    label: {
      text: 'rect',
      fill: '#333',
      fontSize: 13,
    },
  },
})

节点渲染到画布后的 DOM 结构看起来像下面这样:

<g data-cell-id="3ee1452c-6d75-478d-af22-88e03c6d513b" class="x6-cell x6-node" transform="translate(40,40)">
  <rect fill="#2ECC71" stroke="#000" stroke-width="2" width="100" height="40"></rect>
  <text font-size="13" xml:space="preserve" fill="#333" text-anchor="middle" font-family="Arial, helvetica, sans-serif" transform="matrix(1,0,0,1,50,20)">
    <tspan dy="0.3em" class="v-line">
      rect
    </tspan>
  </text>
</g>

另外,我们还可以使用 CSS 选择器来指定节点样式,这样我们就不用记住预定义的选择器名称,只需要根据渲染后的 DOM 结构来定义样式即可。使用 CSS 选择器时需要注意,指定的 CSS 选择器可能选中多个元素,这时对应的属性样式将同时应用到多个元素上。

const rect = new Shape.Rect({
  x: 40,
  y: 40,
  width: 100,
  height: 40,
  attrs: { 
    rect: { // 使用 rect 这个 css 选择器替代预定义的 body 选择器
      fill: '#2ECC71',
      stroke: '#000',
    },
    text: { // 使用 text 这个 css 选择器替代预定义的 label 选择器
      text: 'rect',
      fill: '#333',
      fontSize: 13,
    },
  },
})

注:也就是说这里两段代码是一个含义。

值得一提的是,支持使用小驼峰(camelCase)格式的属性名,如 ‘fontSize’,这就避免了 ‘font-size’ 这种属性名作为对象 Key 时需要加引号的麻烦。

除了标准的 SVG 属性,我们在 X6 中还定义了一系列特殊属性,详情请参考如何使用特殊属性如何自定义属性。另外,我们还可以使用 CSS 来定制样式,节点和边渲染到画布后分别有 ‘x6-node’ 和 ‘x6-edge’ 两个样式名,默认的样式定义参考这里。例如,我们可以像下面这样来指定节点中 元素的样式:

.x6-node rect {
  fill: #2ECC71;
  stroke: #000;
}

创建节点/边后,我们可以调用实例上的 attr() 方法来修改节点属性样式。看下面代码,通过 / 分割的路径修改样式,label 选择器对应到 元素,text 则是该元素的属性名,'hello' 是新的属性值。

shape

节点/边的图形,图形类似 MVC 模式中的 Model,决定了节点/边的结构化数据。该选项通常在使用 graph.addNode 和 graph.addEdge 两个方法添加节点和边时使用。

const rect = graph.addNode({
  shape: 'rect',
  x: 100,
  y: 200,
  width: 80,
  height: 40,
  label: 'rect', 
})

const circle = graph.addNode({
  shape: 'circle',
  x: 280,
  y: 200,
  width: 60,
  height: 60,
  label: 'circle', 
})

const edge = graph.addEdge({
  shape: 'edge',
  source: rect,
  target: circle,
})

在 X6 内部实现中,我们通过 shape 指定的图形找到对应的构造函数来初始化节点/边,并将其添加到画布。

该选项的默认值为:

  • graph.addNode 方法中 shape 的默认值为 ‘rect’
  • graph.addEdge 方法中 shape 的默认值为 ‘edge’

同时,我们在 X6 中内置了一系列节点和边。

内置节点构造函数与 shape 名称对应关系如下表。

构造函数 shape 名称 描述
Shape.Rect rect 矩形。
Shape.Circle circle 圆形。
Shape.Ellipse ellipse 椭圆。
Shape.Polygon polygon 多边形。
Shape.Polyline polyline 多段线。
Shape.Path path 路径。
Shape.Image image 图片。
Shape.HTML html HTML 节点,使用 foreignObject 渲染 HTML 片段。
Shape.TextBlock text-block 文本节点,使用 foreignObject 渲染文本。
Shape.BorderedImage image-bordered 带边框的图片。
Shape.EmbeddedImage image-embedded 内嵌入矩形的图片。
Shape.InscribedImage image-inscribed 内嵌入椭圆的图片。
Shape.Cylinder cylinder 圆柱。

注:shape可选项有以上几种,html的部分还不清楚是怎么做的。

内置边构造函数与 shape 名称对应关系如下表。

构造函数 shape 名称 描述
Shape.Edge edge 边。
Shape.DoubleEdge double-edge 双线边。
Shape.ShadowEdge shadow-edge 阴影边。

tools

节点/边的小工具。小工具可以增强节点/边的交互能力,我们分别为节点和边提供了以下内置工具:

节点

  • button 在指定位置处渲染一个按钮,支持自定义按钮的点击交互。
  • button-remove 在指定的位置处,渲染一个删除按钮,点击时删除对应的节点。
  • boundary 根据节点的包围盒渲染一个包围节点的矩形。注意,该工具仅仅渲染一个矩形,不带任何交互。

  • vertices 路径点工具,在路径点位置渲染一个小圆点,拖动小圆点修改路径点位置,双击小圆点删除路径点,在边上单击添加路径点。
  • segments 线段工具。在边的每条线段的中心渲染一个工具条,可以拖动工具条调整线段两端的路径点的位置。
  • boundary 根据边的包围盒渲染一个包围边的矩形。注意,该工具仅仅渲染一个矩形,不带任何交互。
  • button 在指定位置处渲染一个按钮,支持自定义按钮的点击交互。
  • button-remove 在指定的位置处,渲染一个删除按钮,点击时删除对应的边。
  • source-arrowhead-和-target-arrowhead 在边的起点或终点渲染一个图形(默认是箭头),拖动该图形来修改边的起点或终点。

示例:

graph.addNode({
  x: 40,
  y: 40,
  width: 100,
  height: 40,
  tools: 'button-remove' // or { name: 'button-remove' }
})
graph.addNode({
  x: 40,
  y: 40,
  width: 100,
  height: 40,
  tools: { 
    name: 'button-remove',
    args: {
      x: 10, // 按钮的 x 坐标,相对于节点的左上角
      y: 10, // 按钮的 y 坐标,相对于节点的左上角
    },
  },
})
graph.addNode({
  x: 40,
  y: 40,
  width: 100,
  height: 40,
  tools: [
    'button-remove',
    {
      name: 'boundary',
      args: {
        padding: 5,
      },
    },
  ],
})

data

与节点/边关联的业务数据。例如,我们在实际使用时通常会将某些业务数据存在节点/边的 data 上。

const rect = new Shape.Rect({
  x: 40,
  y: 40,
  width: 100,
  height: 40,
  data: { 
    bizID: 125,
    date: '20200630',
    price: 89.00,
  }
})

相关函数:

setData()

设置关联的业务数据。

默认情况触发 ‘change:data’ 事件和画布重绘,当 options.silent 为 true 时不触发 ‘change:data’ 事件和画布重绘。

参数:

名称 类型 必选 默认值 描述
data any    
options.overwrite boolean   false 为 true 时替换现有值,否则根据 options.deep 选项进行深度或浅度 merge。
options.deep boolean   true 当 options.overwrite 为 false 时有效, 为 true 时进行深度 merge,否则进行浅 merge。
options.silent boolean   false 为 true 时不触发 ‘change:data’ 事件和画布重绘。
options…others object     其他自定义键值对,可以在事件回调中使用。

默认与原数据进行深度 merge,并触发 ‘change:data’ 事件和画布重绘:

cell.setData(data)

当 options.overwrite 为 true 时,替换旧数据:

cell.setData(data, { overwrite: true })

当 options.deep 为 false 时,与原数据进行浅 merge:

cell.setData(data, { overwrite: true })

当 options.silent 为 true 时,不触发 ‘change:data’ 事件和画布重绘:

cell.setData(data, { silent: true })

在选项中支持其他自定义键值对,可以在事件回调用使用:

cell.setData(data, { otherKey: 'otherValue', ... })

replaceData(…)

替换数据

replaceData(data: any, options: Cell.SetOptions = {}): this

用指定的数据替换原数据,相当于调用 setData(data, { …options, overwrite: true })。

名称 类型 必选 默认值 描述
data any    
options.silent boolean   false 为 true 时不触发 ‘change:data’ 事件和画布重绘。
options…others object     其他自定义键值对,可以在事件回调中使用。

Node

Node 是所有节点的基类,继承自 Cell,并定义了节点的通用属性和方法。

其他内容跟Cell差不多,多了一些额外的配置项。

其中 Node.Metadata 是创建节点的选项,除了从 Cell 继承markupattrszIndex 等选项外,还支持以下选项。

选项 类型 默认值 必选 描述
size { width: number; height: number } { width: 1, height: 1 }   节点大小。
position { x: number; y: number } -   节点位置。
angle number -   节点的旋转角度。
ports object -   链接桩。
portMarkup Markup object   链接桩的 DOM 结构。
portLabelMarkup Markup object   链接桩标签的 DOM 结构。

可以看到,主要多的是size和桩的配置。

桩的信息在第二章捋了一遍,这边重点看一下其他的内容。

使用vue组件作为节点

官网有个例子,可以按照例子来。

渲染-vue-节点

但是,使用的时候遇到了一些问题。

问题

这里的getNode提示没有,问题类似于:https://github.com/antvis/X6/issues/3544

inject: ["getGraph", "getNode"],

尝试过更改版本等方法,不能避免这个问题。

如果不能解决getNode的问题,没有办法动态变更node节点内参数。毕竟后期肯定是要做复杂的节点的,需要用到vue的组件功能来制作复杂节点。

辅助文档:

这位大佬的demo我也跑不通,会出现版本异常。但是看他的代码,如果复杂节点,数据传递会很绕很复杂。

https://www.jianshu.com/p/cb6070193cbf

antv x6-v2的文档在这儿,也是这位大佬的。

https://www.jianshu.com/p/f8c8bfbba9e5

如果有相关的解决方案的话可以在下方讨论。