简体   繁体   中英

Unable to wrap the Chinese text on x-axis in D3 bar chart

I've a D3 bar chart with Chinese and English text on x-axis. Whenever the Chinese text comes, the labels are overlapping. I'm unable to wrap the text into multiple lines. If it's only of English text, I'm able to wrap it. Is there a way to wrap the text if it has Chinese text too?

Snippet

 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 for the same snippet.

Your wrap function currently splits on white spaces ( /\\s+/ ) and wraps these parts in their own <tspan> elements.
It needs to be smarter to be able to wrap-words.

The easy fix is to wrap each character in its own <tspan>.

 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> 

But doing so, we have the same behavior as word-break: break-all , that is, it will break even non CJK texts.
What we want is the word-break: normal behavior. And for this, the best is to use HTML and CSS.

By generating a dummy HTML element, we can check where every character should be rendered, thanks to Range.getBoundingClientRect method:

 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> 

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM