简体   繁体   中英

wrapping text on circular textpath

I am building a chart using D3.js , which shows some info about employee's competencies.

screenshot: 在此输入图像描述

As you can see, some text is larger than container element size, because of that, part of the text, is cut. I want to wrap these texts inside container.

I found this example , but I was not able to apply some solution to my chart.

Help would be appreciated...

Here is charts codepen url and here is full screen view

ps I need text to be wrapped by words

In order to wrap the labels, you need to adjust Mike's solution to deal with textPath elements.

For this, we need several things:

1. Get the available width, reaching which the labels should wrap

You could compute the length of the arc itself, but I've done this by computing the segment created by the endpoints of your invisible paths that your labels follow. This will provide us with a little side margin as well, as the segment's length is shorter than the arc's length.

The distance between two points is computed as follows:

d = sqrt((x2 - x1)^2 + (y2 - y1)^2)

2. Wrap the labels when they rich available width and keep the aligned to center

For managing this one, I had to dig into the SVG documentation on the textPath element to see how it can be wrapped and shifted along the y axis.

Initially, I tried setting several textPath elements within one text label, but I couldn't manage to shift them along the y axis. It turns out, that for this you need to add tspan elements within textPath elements. But here another problem arose - I couldn't manage to keep them centrally aligned.

In the end, to achieve shift along y axis and central alignment, you need to use one textPath element (for horizontal alignment) with one tspan element inside (for vertical alignment).

3. Wrap the labels by letters, not by words

This is the point that I have assumed that you'll need namely letter wrapping (at the moment of writing, I didn't get the answer from OP), because on small sizes of your chart, there are words too long to fit into one line.

This was the easiest problem to solve. Just adjust the splitting and joining operations to switch from words to letters:

 letters = text.text().split('').reverse(); // instead of .split(/\\s+/) ... tspan.text(line.join("")); // instead of .join(" ") 

And here's the whole code that was changed, with relevant comments:

 outerSvg.selectAll(".outerCircleText") .data(pie(behaviorsDatasetOuterCircle)) .enter().append("text") .attr("class", "outerCircleText") //Move the labels below the arcs for those slices with an end angle greater than 90 degrees .attr("dy", function (d, i) { di = i; return (d.startAngle >= 90 * Math.PI / 180 ? 18 : -11); }) .text(function(d) { return d.data.name; }) .call(wrap); // Do not add `textPath` elements here. Instead, add them in the `wrap` function function wrap(text) { text.each(function() { var text = d3.select(this), letters = text.text().split('').reverse(), letter, line = [], lineNumber = 0, lineHeight = 1.1, // ems y = text.attr("y"), dy = parseFloat(text.attr("dy")), textPath = text.text(null).append("textPath") // Add a textPath element .attr("startOffset", '50%') .style("text-anchor", "middle") .attr("xlink:href", function(d) { return "#outerArc" + di; }), tspan = textPath.append('tspan'), // Inslide textPath, add a tspan element, for offset feature later. path = d3.select(text.select('textPath').attr('xlink:href')); // Get the path to compute width of text later. var startLoc = /M(.*?)A/; var newStart = path.attr('d').match(startLoc)[1]; var newEnd = path.attr('d').indexOf(' 0 0 0 ') > -1 ? path.attr('d').split(' 0 0 0 ')[1] : path.attr('d').split(' 0 0 1 ')[1] ; // Compute the start/end coordinate points of the arc that the text will follow. var x1 = parseFloat(newStart.split(' ')[0]), y1 = parseFloat(newStart.split(' ')[1]), x2 = parseFloat(newEnd.split(' ')[0]), y2 = parseFloat(newEnd.split(' ')[1]); // Compute the length of the segment between the arc start/end points. This will be the // width which the labels should wrap when reaching it. var width = Math.sqrt(Math.pow((x2 - x1), 2) + Math.pow((y2 - y1), 2)); // And then we go on (with slight changes) with the example from Mike Bostock // from here https://bl.ocks.org/mbostock/7555321 while (letter = letters.pop()) { line.push(letter); tspan.text(line.join("")); if (tspan.node().getComputedTextLength() > width) { line.pop(); tspan.text(line.join("")); line = [letter]; // Instead of adding only a tspan element, add a new textPath so that the wrapped // letters will be aligned to center. Without it, the letters will start drawing // from right with part of them invisible, like if the labels are not wrapped. textPath = text.append("textPath") .attr("startOffset", '50%') .style("text-anchor", "middle") .attr("xlink:href", function(d) { return "#outerArc" + di; }), // Add a tspan element to offset the wrapped letters from the previous line tspan = textPath.append("tspan") .attr('dy', '1em') .attr('text-anchor', 'middle') .text(letter); } } }); } 

In the end, it was an interesting challenge. Here is a fork of your codepen with a working example (the changes are starting with line 749).

The codepen has only the outer labels wrapped. I have left the inner labels for you to implement the approach described here. Good luck with that!

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