1、血缘关系图
节点数据结构:
{
x: 节点x轴位置
y: 节点y轴位置
width: 节点宽度
id: 唯一标识
data:{} 放置数据
attrs:{
body: {} 节点的body颜色、圆角设置
label: {} 节点的文字相关设置,text文本内容 style中可以设置css但设置超出展示...并不生效。ellipsis属性超出展示...但是会换行,换行填充满超出后才展示...
}
}
边数据结构:
{
source: 起始节点-可以是节点id,或者是整个节点对象
target: 目标节点
attrs:{} 连线的样式
}
整个绘图过程是将所有节点创建然后将所有节点进行连接,所以以上结构为固定数据结构
主要处理点:
1、依据设计图的标准每个节点的宽度并非一致,根据文字宽度自适应,两侧预留16px的内padding,超出320px后展示…
2、上下游节点期望均匀分布在中心节点两侧
宽度处理方案:
节点的宽度处理:
需求:已知一段文字,当文字没有超过最大宽度320px-32px(两侧padding各16px)时,文字宽度+32px为节点宽度(节点中的文字会自动居中)。当文字宽度超过最大宽度时,截取其中小于288px-10px的部分并增加…(10px)
首先,我们的兵器库里只有一件精确兵器:getTextWidth 可以求出指定文字宽度的方法。
因此:当文字宽度超过最大宽度时,我这里使用了二分法递归截取中间的,直至截取到的文字误差 <12 或者 剩余一个字符,(因为中文一个字符宽度甚至可以达到20,虽然我这里不会出现中文.)
/**
* 获取字符串宽度
* @param text 需判断的字符串
* @param font `font-size font-family`
*/
let measureCanvas: any
export const getTextWidth = (text: string, font: string) => {
measureCanvas = measureCanvas || document.createElement('canvas')
const context = measureCanvas.getContext('2d')
context.font = font
const measure = context.measureText(text)
return Math.ceil(measure.width)
}
// 判断文字宽度
let resText: any = data.displayText
let width = getTextWidth(resText, `12px PingFangSC-Regular`) + 32
/**
* 二分法计算宽度
* @param str 传入的字符
* @param maxWidth 剩余长度
* @param resStr 结果字符串
* @centerIndex 截取中点的index
* @leftStr 左侧字符串
* @rightStr 右侧字符串
* @leftWidth 左侧字符串宽度
* ...(10字符)
*/
const calculateWidth = (str, maxWidth, resStr) => {
const centerIndex = Math.ceil(str.length / 2)
const leftStr = str.slice(0, centerIndex)
const rightStr = str.slice(centerIndex)
const leftWidth = getTextWidth(resStr + leftStr, `12px PingFangSC-Regular`)
// 最终返回: 1个字符||字符宽度<12||保留12个字符误差
if (str.length <= 1 || maxWidth < 12 || (leftWidth < maxWidth && leftWidth > maxWidth - 12)) {
return resStr + leftStr
}
return leftWidth > maxWidth
? calculateWidth(leftStr, maxWidth, resStr)
: calculateWidth(rightStr, maxWidth, resStr + leftStr)
}
一些其他方案的猜想:
1、注册新节点:使用antv-x6提供的自定义节点注册新节点 Graph.registerNode,注册的新节点可以处理成ui图的样式,问题:我在实现过程中遇到 注册只能注册一次,如果放到组件中注册,当组件重新打开时会提示重复注册的报错
2、在上面的节点结构中可以看出,使用x6的内置节点是body和label两部分组成,我们使用的标签基类中也就是
文档描述
选择器(Selector)通过节点的 markup 确定,如 Shape.Rect 节点定义了 ‘body’(代表
属性:
tagName
SVG/HTML 元素标签名。
https://antv-x6.gitee.io/zh/docs/tutorial/basic/cell
3、在attr中存在属性:textWrap ,通过这个属性的介绍,该属性会为文本换行,自动添加省略号,而我们想要的是:文本不换行,自动添加省略号。这成功将问题转移为 如果使用这个属性怎么让文本不换行?反正我是没使用成功…
文档描述
text
仅适用于
textWrap
仅适用于
当提供的文本超出显示范围时,文本将被自动截断,如果 ellipsis 选项被设置为 true ,则在截断文本的末尾添加一个省略号 …。
4、attr中还存在 jquery的html和css,于是我去查看了jQuery的文档,并且失败的没有解决问题. 不过看描述应该是有可能解决的
文档描述
style
使用 jQuery 的 css() 方法为指定的元素应用行内 CSS 样式。
html
使用 jQuery 的 html() 方法为指定的元素设置 innerHTML。
第四个方案证实是真实有效的…,但是又产生了如下问题:但是不是用的attr中的html,而是与attr同级的html
1、可以通过自定义html元素将内部元素设置成div, 但是css不能写在scope的style中, 可以定义一个特殊的类名放到全局上
2、在图上的html中可以看到,即使定义了html元素是div, 在其上面依然有一个svg的rect标签, 并且此标签的宽度依旧需要被提前定义好, 设置auto、不设置、100%都不生效, 并且在图中可以看到标签右侧空出了很大一块空白区域, 就是因为设置了宽度。
3、因此,可以将两种方案结合使用,这样也可以解决hover和点击的样式问题,但是因为有误差问题,所以自定义的…和css产生的…只能保留一个
后续:图表升级为2.0版本后有了更优的解决方案…
高度处理方案
因为点是直接在画布上渲染的,设计图又期望两侧的点均匀分布在中心点两侧,因此我们在创建点前就将点的位置计算好,直接渲染即可
画布的渲染时可以执行自动居中,所以我们只需要保证:
x轴方向:中心节点两侧的间距是相同的,并且超过节点的最大宽度320
y轴方向:两侧可能会有多个节点,如果是奇数个节点,纵向中间的节点跟中心节点y坐标一致,如果是偶数个,纵向均分在中心节点两侧
/**
* 处理高度
* y方向总高度为400
* centerIndex 找到数组的中间index
* 中间的高度固定为200,上下两部分为200
* 200/个数 求出每个的间隔,就可以均分在中点上下两侧
* @param xPosition x方向位置
*/
const handleHeightArr = (arr, xPosition) => {
const len = arr.length
if (len > 0) {
// 处理高度
let centerIndex = 0 // 中心点index
let interval = 0 // 间隔
// 偶数
if (len % 2 === 0) {
centerIndex = len / 2 - 0.5 // 2-0.5、4-1.5、6-2.5、8-3.5
interval = Math.floor(200 / len / 2) // 200、100、66、50
} else {
centerIndex = Math.floor(len / 2) // 1-0、3-1、5-2、7-3
interval = Math.ceil(200 / centerIndex) // Infinity、200、100、66
}
arr.forEach((ele, i) => {
ele.data = ele
ele.id = ele.guid
ele.x = xPosition
if (i < centerIndex) {
ele.y = i * interval
}
if (i === centerIndex) {
ele.y = 200
}
if (i > centerIndex) {
ele.y = 200 + interval * (i - Math.floor(centerIndex))
}
})
}
return arr
}
遗留未解决问题:
1、节点hover状态展示ui图样式,因为中间是svg图,使用css的样式的hover无法实现hover效果。x6有提供hover节点的回调,但是在回调中通过setAttrs改变节点填充色和边颜色效果不尽人意,所以暂没实现
2、点击瞬间active状态的样式 同上
3、连线弧度,可以通过connector的 name: 'smooth’定义弧线,并通过args:{radius:} 设置弧度 但是弧度并未达到预期效果,因此暂设置为直线