背景
加入了公司的新项目组, 业务需要让我们用d3搭建出各个app之间的workflow流向, 由于之前并没有接触可视化相关的内容 , 因此我在这里做一个记录。 我们目前希望实现的demo效果:
效果演示
参考了d3的mobile-patent的例子(observablehq.com/@d3/mobile-…) 从mobile-patent到我们的最终效果, 需要实现以下几个功能:
- circle调整变大
- 增加tooltip,包括circle和中间的link
- 添加文字并实现文字居中,实现text-overflow:ellipsis的效果
- 调整link line为dash 并且调整距离
实现思路
1.构造nodes 和links
可以参考上述的mobile-patent上面的数据节点, 这里省略
2.创建一个力学仿真模型
const simulation = d3
.forceSimulation(nodes)
.force(
'link',
d3
.forceLink(links)
.id(d => d.id)
.distance(150),
)
.force('charge', d3.forceManyBody().strength(-400))
.force('center', d3.forceCenter(width / 2, height / 2))
.force('x', d3.forceX())
.force('y', d3.forceY());
- d3.forceSimulation创建一个没有任何力的仿真模型
- simulation.force(name[, force])表示为仿真添加指定 name 的 力模型 并返回仿真。
- .forceCenter- 创建一个中心作用力.
- .forceManyBody().strength() - 创建一个电荷作用力模型并指定电荷强度
- .forceLink 创建一个
link
(弹簧) 作用力.通过distance和strength可以设置它的强度
3.创建画布
const svg = d3
.selectAll('.graph') // .graph 是之前创建的svg classname
// .attr('viewBox', [-width / 2.4, -height / 2, width, height])
// .attr('viewBox', [width, height, width, height])
.style('font', `${FONTSIZE}px sans-serif`)
.style('color', 'red');
如何创建tooltip
.on('mouseover', function() {
return tooltip.style('visibility', 'visible');
})
.on('mousemove', function(event, d) {
const index = d.index;
const text = index === 0 || index ? dataNodes[index]?.id : '';
tooltip
.style('top', event.pageY + 'px')
.style('left', event.pageX + 'px')
.html(text);
})
.on('mouseout', function() {
tooltip.style('visibility', 'hidden');
});
如何中间的文字居中并加上text-overflow特性
export const svgTextEllipsis = (d3, textNode, padding, fontsize) => {
const d3Nodes = d3.selectAll(textNode).nodes();
d3Nodes.forEach(function(d) {
const d3Node = d3.select(d);
const targetWidth = Number(d3Node.attr('width')) - padding;
const initialText = d3Node.text();
let textWidth = d3Node.node()?.getComputedTextLength() ?? 0;
const restWidth = textWidth - targetWidth;
let text = initialText;
if (textWidth < targetWidth) {
const equalOffest = -restWidth / 2;
d3Node.attr('dx', equalOffest);
return;
}
const restCharacter = Math.ceil(restWidth / fontsize);
text = text.slice(0, -restCharacter - 2) + '....';
d3Node.text(text);
});
};
对于dom样式的文本节点,必须要select一下才可以使用 其次这里的实现思路是: 比较当前text的文字的宽度跟容器的宽度进行比较 , 如果文字宽度大大于了容器宽度 , 看一下超出了后面几个文字 , 就是用文字宽度- 容器宽度然后除文字的大小 ,然后把后面的文字截取 , 然后加上... 居中的实现思路: 我是直接通过设置dx来实现的。 刚刚看到另一个人是通过textpath,我需要再研究一下
对于marker偏移严重的问题
markerUnits属性
首先是画marker的时候必须定义一个'markerUnits'的属性 ,默认的属性是根据 后面被使用的时候stokewidth 来变换marker的大小的, 如果要求不变必须更改属性值为'userSpaceOnUse'
marker的refx 和refy是怎么看的
跟我的认知有偏差,感觉反过来了一样