我有一个 D3
条形图,x 轴上有中文和英文文本。每当中文文本出现时,标签就会重叠。我无法将文本换行成多行。如果它只是英文文本,我可以将其包装起来。如果还有中文文本,有没有办法换行?
片段
var margin = {
top: 10,
right: 0,
bottom: 58,
left: 40
};
var width = 400 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var barWidth = 40;
var graph;
var xScale;
var yScale;
var dataSet;
dataSet = [{
desc: '即使句子没有空格',
val: 20
},
{
desc: 'Sample text.即使句子没有空格',
val: 40
},
{
desc: 'test3',
val: 60
},
{
desc: 'test4',
val: 80
},
{
desc: 'some dummy text here',
val: 120
}
];
xScale = d3.scaleBand()
.domain(dataSet.map(function(d) {
return d.desc;
}))
.range([0, width]);
yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, 1.15 * d3.max(dataSet, function(d) {
return d.val;
})]);
graph = d3.select("#graph")
.append("svg")
.attr("class", "bar-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
graph.append("g")
.attr("class", "x-scale")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale))
.selectAll(".tick text")
.call(wrap, xScale.bandwidth());
graph.append("g")
.attr("class", "y-scale")
.call(d3.axisLeft(yScale).tickPadding(10));
graph
.append("g")
.attr('class', 'graph-placeholder')
.selectAll("rect")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar1")
.attr("height", height)
.attr("width", barWidth)
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2);
graph
.append("g")
.attr('class', 'graph-main')
.selectAll("bar1")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar2")
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2)
.attr("y", function(d) {
return yScale(d.val);
})
.attr("height", function(d) {
return height - yScale(d.val);
})
.attr("width", barWidth);
graph
.append("g")
.attr('class', 'bar-label')
.selectAll("text")
.data(dataSet)
.enter()
.append("text")
.text(d => d.val + '%')
.attr("y", function(d) {
return yScale(d.val) - 5;
}).attr('x', function(d) {
return xScale(d.desc) + ((xScale.bandwidth() - barWidth) / 2);
});
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1,
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
.bar-chart {
background-color: #ccc;
}
.bar2 {
fill: steelblue;
}
.bar1 {
fill: #f2f2f2;
}
text {
font-size: 12px;
/* text-anchor: middle; */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
<div id="graph"></div>
</div>
Fiddle对于同一个片段。
最佳答案
您的 wrap
函数当前在空格 (/\s+/
) 上拆分,并将这些部分包装在它们自己的
它需要更智能才能自动换行。
简单的解决方法是将每个字符包装在它自己的
var margin = {
top: 10,
right: 0,
bottom: 58,
left: 40
};
var width = 400 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var barWidth = 40;
var graph;
var xScale;
var yScale;
var dataSet;
dataSet = [{
desc: '即使句子没有空格',
val: 20
},
{
desc: 'Sample text.即使句子没有空格',
val: 40
},
{
desc: 'test3',
val: 60
},
{
desc: 'test4',
val: 80
},
{
desc: 'some dummy text here',
val: 120
}
];
xScale = d3.scaleBand()
.domain(dataSet.map(function(d) {
return d.desc;
}))
.range([0, width]);
yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, 1.15 * d3.max(dataSet, function(d) {
return d.val;
})]);
graph = d3.select("#graph")
.append("svg")
.attr("class", "bar-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
graph.append("g")
.attr("class", "x-scale")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale))
.selectAll(".tick text")
.call(wrap, xScale.bandwidth());
graph.append("g")
.attr("class", "y-scale")
.call(d3.axisLeft(yScale).tickPadding(10));
graph
.append("g")
.attr('class', 'graph-placeholder')
.selectAll("rect")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar1")
.attr("height", height)
.attr("width", barWidth)
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2);
graph
.append("g")
.attr('class', 'graph-main')
.selectAll("bar1")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar2")
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2)
.attr("y", function(d) {
return yScale(d.val);
})
.attr("height", function(d) {
return height - yScale(d.val);
})
.attr("width", barWidth);
graph
.append("g")
.attr('class', 'bar-label')
.selectAll("text")
.data(dataSet)
.enter()
.append("text")
.text(d => d.val + '%')
.attr("y", function(d) {
return yScale(d.val) - 5;
}).attr('x', function(d) {
return xScale(d.desc) + ((xScale.bandwidth() - barWidth) / 2);
});
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
// split on each character
words = text.text().split('').reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1,
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
// join with empty string
tspan.text(line.join(""));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
// join with empty string
tspan.text(line.join(""));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
.bar-chart {
background-color: #ccc;
}
.bar2 {
fill: steelblue;
}
.bar1 {
fill: #f2f2f2;
}
text {
font-size: 12px;
/* text-anchor: middle; */
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
<div id="graph"></div>
</div>
但是这样做,我们的行为与 word-break: break-all
相同,也就是说,它甚至会打破非 CJK 文本。
我们想要的是 word-break: normal
行为。为此,最好的办法是使用 HTML 和 CSS。
通过生成一个虚拟的 HTML 元素,我们可以检查每个字符应该在哪里呈现,感谢 Range.getBoundingClientRect方法:
var margin = {
top: 10,
right: 0,
bottom: 58,
left: 40
};
var width = 400 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
var barWidth = 40;
var graph;
var xScale;
var yScale;
var dataSet;
dataSet = [{
desc: '即使句子没有空格',
val: 20
},
{
desc: 'Sample text.即使句子没有空格',
val: 40
},
{
desc: 'test3',
val: 60
},
{
desc: 'test4',
val: 80
},
{
desc: 'some dummy text here',
val: 120
}
];
xScale = d3.scaleBand()
.domain(dataSet.map(function(d) {
return d.desc;
}))
.range([0, width]);
yScale = d3.scaleLinear()
.range([height, 0])
.domain([0, 1.15 * d3.max(dataSet, function(d) {
return d.val;
})]);
graph = d3.select("#graph")
.append("svg")
.attr("class", "bar-chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
graph.append("g")
.attr("class", "x-scale")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(xScale))
.selectAll(".tick text")
.call(wrap, xScale.bandwidth());
graph.append("g")
.attr("class", "y-scale")
.call(d3.axisLeft(yScale).tickPadding(10));
graph
.append("g")
.attr('class', 'graph-placeholder')
.selectAll("rect")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar1")
.attr("height", height)
.attr("width", barWidth)
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2);
graph
.append("g")
.attr('class', 'graph-main')
.selectAll("bar1")
.data(dataSet)
.enter()
.append("rect")
.attr("class", "bar2")
.attr('x', d => xScale(d.desc) + (xScale.bandwidth() - barWidth) / 2)
.attr("y", function(d) {
return yScale(d.val);
})
.attr("height", function(d) {
return height - yScale(d.val);
})
.attr("width", barWidth);
graph
.append("g")
.attr('class', 'bar-label')
.selectAll("text")
.data(dataSet)
.enter()
.append("text")
.text(d => d.val + '%')
.attr("y", function(d) {
return yScale(d.val) - 5;
}).attr('x', function(d) {
return xScale(d.desc) + ((xScale.bandwidth() - barWidth) / 2);
});
function getLines(text, width) {
// create a dummy element
var dummy = d3.select('body')
.append('p')
.classed('dummy-text-wrapper', true)
// set its size to the one we want
.style('width', width + 'px')
.text(text);
var textNode = dummy.node().childNodes[0];
var lines = [''];
var range = document.createRange();
var current = 0;
// get default top value
range.setStart(textNode, 0);
var prevTop = range.getBoundingClientRect().top;
var nextTop = prevTop;
// iterate through all characters
while (current < text.length) {
// move the cursor
range.setStart(textNode, current+1);
// check top position
nextTop = range.getBoundingClientRect().top;
if(nextTop !== prevTop) {
// new line
lines.push("");
}
// add the current character to the last line
lines[lines.length - 1] += text[current++];
prevTop = nextTop;
}
// clean up the DOM
dummy.remove();
return lines;
}
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text(),
lines = getLines(words, width),
line = [],
lineHeight = 1,
y = text.attr("y"),
dy = parseFloat(text.attr("dy"));
text.text('');
lines.forEach(function(words, lineNumber) {
text.append("tspan")
.attr("x", 0)
.attr("y", y)
.attr("dy", lineNumber * lineHeight + dy + "em")
.text(words);
});
});
}
.bar-chart {
background-color: #ccc;
}
.bar2 {
fill: steelblue;
}
.bar1 {
fill: #f2f2f2;
}
text {
font-size: 12px;
/* text-anchor: middle; */
}
.dummy-text-wrapper {
word-break: normal;
display: inline-block;
opacity: 0;
position: absolute;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div class="container">
<div id="graph"></div>
</div>
关于javascript - 无法在 D3 条形图中的 x 轴上换行中文文本,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54414079/