Antv X6 血缘关系图原始图表方案

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两部分组成,我们使用的标签基类中也就是 两个svg元素组成,我们可以重构他的基类cell进行节点的重构,使内部标签变为html元素,这样就可以通过css控制展示…了

文档描述
选择器(Selector)通过节点的 markup 确定,如 Shape.Rect 节点定义了 ‘body’(代表 元素) 和 ‘label’(代表 元素) 两个选择器
属性:
tagName
SVG/HTML 元素标签名。
https://antv-x6.gitee.io/zh/docs/tutorial/basic/cell

3、在attr中存在属性:textWrap ,通过这个属性的介绍,该属性会为文本换行,自动添加省略号,而我们想要的是:文本不换行,自动添加省略号。这成功将问题转移为 如果使用这个属性怎么让文本不换行?反正我是没使用成功…
文档描述
text
仅适用于 元素,用于设置文本内容。如果提供的文本是单行文本(不包含换行符 ‘\n’),那么文本被直接设置为 元素的内容;否则为每一行文本将创建一个 元素,然后将该元素添加到 元素中,
textWrap
仅适用于 元素,用于设置文本内容。与 text 属性不同的是,该属性将自动为文本添加换行,使提供的文本完全被包围在参照元素的包围盒(bounding box)中。
当提供的文本超出显示范围时,文本将被自动截断,如果 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:} 设置弧度 但是弧度并未达到预期效果,因此暂设置为直线

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值