1.svg text文本换行的方法.
常用的有两种方法:
方法一. 使用 text 添加多个 tspan 绘制文字
用 tspan
把 text
拆成多行,重新计算每个tspan
的 y
坐标。
<svg xmlns="http://www.w3.org/2000/svg">
<text font-size="14">
<tspan x="0" y="10">医药制造业国家</tspan>
<tspan x="0" y="28">统计局分类</tspan>
</text>
</svg>
d3 代码中添加:
// 给text标签加唯一的class,以便后面为每个拆分的文字添加tspan
let texts = g.selectAll('g.' + type + 'Node')
.append('text')
.attr('class', function (d) {
return 'text-id' + d.data.id.toString().replace(/\./g, '')
})
.attr('width', rectWidth)
.attr('height', rectHeight)
.attr('x', 0)
.attr('y', 5)
.style('font-size',fontSize+'px')
// 设置text文字自动换行
texts.each(item => {
var text = g.selectAll('text.text-id' + item.data.id.toString().replace(/\./g, ''));
item.data.names = insertEnter(item.data.name, rectWidth);
let x = +text.attr('x'),
y = +text.attr('y');
let lineHight = fontSize + 4;
for (let i = 0; i < item.data.names.length; i++) {
text
.append("tspan")
.attr('x', x)
.attr('y', function (d) {
return y + lineHight * i
})
.text(function (d) {
return d.data.names[i];
})
.append('title')
.text(d => d.data.title)
}
})
方法二. 使用 foreignObject 添加 DOM 节点绘制文字(IE浏览器不支持)
用 foreignObject
包裹 DOM 元素,利用 DOM 的文本布局能力自动处理换行。
<svg xmlns="http://www.w3.org/2000/svg">
<foreignObject width="120" height="50">
<body xmlns="http://www.w3.org/1999/xhtml">
<p style="font-size:14px;margin:0;">医药制造业国家统计局分类</p>
</body>
</foreignObject>
</svg>
d3 代码中添加:
g.selectAll('g.' + type + 'Node')
.append("foreignObject")
.attr('width',rectWidth)
.attr('height',rectHeight)
.attr('x',0)
.attr('y',-15)
// 必须加 xhtml: 前缀,否则文字不显示
.append('xhtml:p')
.attr('title',function(d){
return d.data.title
})
.style('width',rectWidth)
.style('height',rectHeight)
.style('box-sizing','border-box')
.style('color','#000')
.style('font-size',fontSize+'px')
.style('text-align',function(d){
return 'center'
})
.text(function (d) {
return d.data.name ;
})
2. 举个完整的栗子
<html>
<head>
<meta charset="utf-8">
<title>d3-text文字换行</title>
</head>
<body>
<div id="svg" style="width: 100%;height: 100%;"></div>
<script src="https://cdn.bootcdn.net/ajax/libs/d3/5.16.0/d3.min.js"></script>
<script>
// 节点宽度
var rectWidth = 110;
// 节点高度
var rectHeight = 46;
// 箭头长度
let markerWidth = 10;
// 树状图数据
var data = getData();
treeInit();
// 树状图数据
function getData(){
let data = [
{"id":'1',"name":"医药制造业国家统计局分类",clickData:{url:"https://baike.baidu.com/item/%E5%8C%BB%E8%8D%AF%E5%88%B6%E9%80%A0%E4%B8%9A/9217195?fr=aladdin"},"title": "",
"children":[
{"id":'1.1',"name":"化学药品原料药制造",clickData:{url:""},"title": "指供进一步加工化学药品制剂、生物药品制剂所需的原料药生产",},
{"id":'1.2',"name":"化学药品制剂制造",clickData:{url:"https://baike.baidu.com/item/%E5%8C%96%E5%AD%A6%E8%8D%AF%E5%93%81%E5%88%B6%E5%89%82%E5%88%B6%E9%80%A0/7411169?fr=aladdin"},"title": "指直接用于人体疾病防治、诊断的化学药品制剂的制造",},
{"id":'1.3',"name":"中药饮片加工",clickData:{url:""},"title": "指对采集的天然或人工种植、养殖的动物、植物和矿物的药材部位进行加工、炮制,使其符合中药处方调剂或中成药生产使用的活动",},
{"id":'1.4',"name":"中成药生产",clickData:{url:""},"title": "指直接用于人体疾病防治的传统药的加工生产活动",},
{"id":'1.5',"name":"兽用药品制造",clickData:{url:""},"title": "指用于动物疾病防治医药的制造",},
{"id":'1.6',"name":"生物药品制品制造",clickData:{url:""},"title": "指利用生物技术生产生物化学药品、基因工程药物和疫苗的制剂生产活动",
"children":[
{"id":'1.6.1',"name":"生物药品制造",clickData:{url:""},"children":null,"title": "指利用生物技术生产生物化学药品的生产活动",},
{"id":'1.6.2',"name":"基因工程药物和疫苗制造",clickData:{url:""},"children":null,"title": "",},
]
},
{"id":'1.7',"name":"卫生材料及医药用品制造",clickData:{url:""},"title": "指卫生材料、外科敷料以及其他内、外科用医药制品的制造",},
{"id":'1.8',"name":"药用辅料及包装材料",clickData:{url:""},"title": "指药品辅料和包装材料等制造",},
]
}
];
return data;
}
// 树状图初始化
function treeInit(){
let svg_container = document.getElementById('svg');
let width = window.getComputedStyle(svg_container).width.replace('px', '');
let height = window.getComputedStyle(svg_container).height.replace('px', '');
// 缩放比例
let zoom = height / 650;
let ratio = (data.height ? data.height : 100) / 100 * 0.8;
let scaleRat = (width >= 1300 ? 1 : 0.88) * zoom;
let svg = d3.select(`#svg`)
.append('svg')
.attr('width', width)
.attr('height', height)
// 树状图默认从上到下绘制,转成从左至右绘制后,x 代表纵轴,y 代表横轴.
let g = svg.append('g')
.attr('transform', `translate(${width / 3 - 160 * zoom}, ${height / 2 + 40 * zoom * ratio}) scale(${scaleRat})`)
drawMark(g);
for (let key in data) {
drawTree(data[key], g, key)
}
}
//画可复用的三角形
function drawMark(g){
g.append('defs')
.append('marker')
.attr('id', 'markerArrow')
.attr('markerWidth', markerWidth)
.attr('markerHeight', markerWidth)
.attr('refX', '2')
.attr('refY', '4')
.attr('orient', 'auto')
.append('path')
.attr('d', 'M10,4 L0,0 L0,8 z')
.attr('fill', '#6F6F6F')
}
// 绘制树状图
function drawTree(data, g, key) {
let tree = d3.tree()
.nodeSize([rectHeight + 10, rectWidth + 50])
// 初始化json数据
let hierarchyData = d3.hierarchy(data)
// 初始化树状图
let treeData = tree(hierarchyData)
// 获取节点
let nodes = treeData.descendants();
let type = 'key' + key;
let idx = 0;
nodes.forEach((element, index) => {
// 可以对节点进行处理
});
// 获取边,也就是连线
let links = treeData.links()
if (data.showType != 'rect-box') {
drawLink(g, links, type)
}
// 绘制文本和节点
drawNode(g, nodes, type)
drawText(g, type)
}
// 绘制节点
function drawNode(g, nodes, type, title) {
nodes = data.noRoot ? nodes.slice(1, nodes.length) : nodes
g.selectAll('g.' + type + 'Node')
.data(nodes)
.enter().append('g')
.attr('class', function (d) {
var result = type + 'Node Node';
return result
})
.attr('transform', function (d) { return 'translate(' + d.y + ',' + d.x + ')' })
.on('click', function (evt) {
let eventData = evt.data.clickData;
if (eventData && eventData.url) {
// 有url链接可点击跳转
window.open(eventData.url, '_block');
}
});
g.selectAll('g.' + type + 'Node').append("rect")
.attr('y', function (d) {
return -15
})
.attr('width', function (d) {
if (d.data.rectWidth) {
return d.data.rectWidth
}
return rectWidth
})
.attr('height', function (d) {
return rectHeight
})
.attr('class', function (d) {
if (d.data.clickData && d.data.clickData.type) {
return 'Select'
}
})
.attr("rx", 2)
// 矩形背景色以及边框颜色宽度
.attr('fill', d => {
if (d.data.color) {
return d.data.color
}
if(!d.data.name){
return 'rgba(255,255,255,0)'
}
return 'rgba(208,239,255,1)'
})
.append("title")
.text(d => d.data.title)
}
// 绘制连线
function drawLink(g, links, type) {
let arrow = data.arrowReverse ? 'marker-end' : 'marker-start';
let link = g.selectAll('g.' + type + 'Link').data(links)
link.enter().append('path')
.attr('class', type + 'Link')
.attr('fill', 'none')
.attr('stroke-width', 1)
.attr('stroke', '#858585')
.attr(arrow, function (d) {
return d.source.data.noArrow ? '' : 'url(#markerArrow)'
})
.attr('d', function (d) {
if (d.target.data.noLink) {
return ''
}
let sourceX = d.source.x + rectHeight/5,
sourceY = d.source.y + rectWidth,
targetX = d.target.x + rectHeight/5,
targetY = d.target.y,
trans = 24;
if (d.target.data.customLink) {
return d.target.data.customLink
}
return `M${sourceY + markerWidth - 2},${sourceX}H${sourceY}H${targetY - 15}V${targetX}H${targetY}`
})
.attr('id', (d, i) => 'my_path' + i)
link.enter().append('text')
.attr('text-anchor', 'mid')
.attr('class', 'link-text')
.style('font-size', '12px')
.append('textPath')
.attr('xlink:href', (d, i) => '#my_path' + i)
.attr('startOffset', '10%')
.text(function (d) {
if (d.target.data.preLinkLabel) {
return d.target.data.preLinkLabel
}
})
}
// 绘制文本
function drawText(g, type) {
let fontSize = 14;
// 方法一 使用 text,手动设置换行
appendText(g,type,fontSize);
// 方法二 使用 foreignObject 插入 DOM 节点
// IE 浏览器不支持
// appendXhtml(g,type,fontSize);
}
// 使用 text 添加多个 tspan 绘制文字(方法一)
function appendText(g,type,fontSize){
// 给text标签加唯一的class,以便后面为每个拆分的文字添加tspan
let texts = g.selectAll('g.' + type + 'Node')
.append('text')
.attr('class', function (d) {
return 'text-id' + d.data.id.toString().replace(/\./g, '')
})
.attr('width', function (d) {
let rectWidth1 = d.data.rectWidth ? d.data.rectWidth : rectWidth;
return rectWidth1
})
.attr('height', rectHeight)
.attr('x', function (d) {
let rectWidth1 = d.data.rectWidth ? d.data.rectWidth : rectWidth;
let nameLen = d.data.name.length * fontSize;
let x = 0;
if (nameLen > rectWidth1) {
x = 5
} else {
x = (rectWidth1 - nameLen) / 2
}
return x
})
.attr('y', 5)
.style('font-size',fontSize+'px')
// 可跳转的链接加样式
.style('text-decoration', function (d) {
return d.data.clickData && d.data.clickData.url ? 'underline' : 'none'
})
.style('cursor', function (d) {
return d.data.clickData && d.data.clickData.url ? 'pointer' : 'default'
})
// 设置text文字自动换行
texts.each(item => {
var text = g.selectAll('text.text-id' + item.data.id.toString().replace(/\./g, ''));
let rectWidth1 = item.data.rectWidth ? item.data.rectWidth : rectWidth;
item.data.names = insertEnter(item.data.name, rectWidth1);
let x = +text.attr('x'),
y = +text.attr('y');
let lineHight = fontSize + 4;
for (let i = 0; i < item.data.names.length; i++) {
text
.append("tspan")
.attr('x', x)
.attr('y', function (d) {
return y + lineHight * i
})
.text(function (d) {
return d.data.names[i];
})
.append('title')
.text(d => d.data.title)
}
})
// 拆分文字字符串 (文字字符串,外框宽度)
function insertEnter(name, width) {
// 文字宽度
let nameLen = name.length * fontSize;
// 每行字数,超过换行
let num = 4;
// 文字宽度大于rect宽度时,计算每行最大字数
console.log('nameLen',nameLen,width,name)
if (nameLen > width) {
num = Math.floor(width / fontSize)
} else {
num = Math.floor(nameLen / fontSize)
}
if(!num) num = 1;
var s = name,
reg = new RegExp(`.{1,${num}}`, 'g'),
rs = s.match(reg);
if (name.length <= num) {
return [name];
} else {
rs.push(s.substring(rs.join('').length));
}
console.log('rs',rs)
return rs;
}
}
// 使用 foreignObject 添加 DOM 节点绘制文字(方法二,IE浏览器不支持)
function appendXhtml(g,type,fontSize){
g.selectAll('g.' + type + 'Node')
.append("foreignObject")
.attr('width',function(d){
let rectWidth1 = d.data.rectWidth ? d.data.rectWidth : rectWidth;
return rectWidth1
})
.attr('height',rectHeight)
.attr('x',function(d){
return 0
})
.attr('y',-15)
// 必须加 xhtml: 前缀,否则文字不显示
.append('xhtml:p')
.attr('title',function(d){
return d.data.title
})
.style('width',function(d){
let rectWidth1 = d.data.rectWidth ? d.data.rectWidth : rectWidth;
return rectWidth1
})
.style('height',function(d){
return rectHeight
})
.style('box-sizing','border-box')
.style('color','#000')
.style('font-size',fontSize+'px')
.style('text-align',function(d){
return 'center'
})
// 可跳转的链接加样式
.style('text-decoration',function(d){
return d.data.clickData && d.data.clickData.url ? 'underline' : 'none'
})
.style('cursor',function(d){
return d.data.clickData && d.data.clickData.url ? 'pointer' : 'default'
})
.style('margin','0px')
.text(function (d) {
return d.data.name ;
})
}
</script>
</body>
</html>
效果图: