Reputation: 523
This is how the text is displaying inside the circle now:
This is how I would like to display the text inside the circle:
I'm looking for a way to cut off / hide / or? the part of the text that doesn't fit inside the circle and show three dots at the end of these too-long-texts. I've found many answers that explained how to wrap multiple words in a circle, but I couldn't find a way to fit one too-long word in a circle in this way.
These are the styles added to the circles and texts when they are entered:
enterNode = (selection) => {
selection.select('circle')
.attr("r", 30)
.style("fill", function (d) {
return color(d.label)
})
.style("stroke", "#4D5061")
selection.select('text')
.style("fill", "#3D3B30")
.style("font-weight", "600")
.style("text-transform", "uppercase")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle")
}
The circles are part of a d3 force layout displayed in this way
<svg>
<g class="node-group">
<g class="node>
<circle class="circle"></circle>
<text class="text"></text>
</g>
</g>
<g class="link-group">...</g>
</svg>
I would really appreaciate it if you have an idea on how to fix this!
Upvotes: 2
Views: 3955
Reputation: 102174
This is a D3 based solution for SVG elements, since you have an SVG with a D3 force directed graph.
It involves getting the circle's width (which you can do using attr("r")
as a getter, but here I'm using getBBox()
) and the text's length (here I'm using getComputedTextLength()
) and passing those values to a custom function.
First, let's see the circle and the text without any function. This is the text in my demo:
This above all: to thine own self be true, And it must follow, as the night the day, Thou canst not then be false to any man.
As you can see, it's not only longer than the circle, it's actually bigger than the SVG:
var svg = d3.select("svg");
var circle = svg.append("circle")
.attr("cx", 200)
.attr("cy", 110)
.attr("r", 100)
.style("fill", "powderblue")
.style("stroke", "darkslategray");
var text = svg.append("text")
.style("text-anchor", "middle")
.style("dominant-baseline", "central")
.attr("x", 200)
.attr("y", 110)
.text("This above all: to thine own self be true, And it must follow, as the night the day, Thou canst not then be false to any man.");
svg {
border: 1px solid gray;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="400" height="220"></svg>
Now let's call our function, here named crop()
. It has to be called on the text selection, using the circle selection as the second argument, like this:
text.call(crop, circle);
And this is the function:
function crop(text, circle) {
var circleRadius = circle.node().getBBox().width;
while (text.node().getComputedTextLength() > circleRadius) {
text.text(text.text().slice(0, -4) + "...");
}
};
As you can see, it basically takes the values and, in a while
loop, crops the text (with the "..."
) until it fits the space. I'm actually printing the text again at every loop, to get the computed length of the node... however, those modern browsers are so insanely fast that one can't notice.
And here is the demo:
var svg = d3.select("svg");
var circle = svg.append("circle")
.attr("cx", 200)
.attr("cy", 110)
.attr("r", 100)
.style("fill", "powderblue")
.style("stroke", "darkslategray");
var text = svg.append("text")
.style("text-anchor", "middle")
.style("dominant-baseline", "central")
.attr("x", 200)
.attr("y", 110)
.text("This above all: to thine own self be true, And it must follow, as the night the day, Thou canst not then be false to any man.");
text.call(crop, circle);
function crop(text, circle) {
var circleRadius = circle.node().getBBox().width;
while (text.node().getComputedTextLength() > circleRadius) {
text.text(text.text().slice(0, -4) + "...");
}
}
svg {
border: 1px solid gray;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="400" height="220"></svg>
Upvotes: 5
Reputation: 7545
Grab the DOM element with that className
and change the innerText
to whatever you want. Then set a maxWidth
. My function adds that text to the DOM and then checks to see if the length of the DOM element, with the innerText
in it, is too large. If it's smaller, then do nothing, but if it's larger, start using slice
on the string and taking away trailing characters and adding the ...
as well. Rinse and repeat.
I have a setTimeout
to show both situations and prove that it works.
Make sure you're using css that will actually change the width of the html element based on the text inside it, or it will run forever and freeze your browser. That's why I use display: flex
function reduceWord(className, innerText, maxWidth) {
let textElement = document.getElementsByClassName(className)[0] //assuming only one html element named that class
textElement.innerHTML = innerText
if (textElement.offsetWidth > maxWidth) {
let nextInnerText
while (textElement.offsetWidth > maxWidth) {
nextInnerText = textElement.innerHTML
textElement.innerHTML = nextInnerText.slice(0, nextInnerText.length - 4) + '...'
}
}
}
reduceWord('example', 'Programming', 100)
setTimeout(() => reduceWord('example', 'Programming', 50), 5000)
.container {
display: flex;
}
.example {
border: 1px solid black;
display: flex;
}
<div class="container">
<div class="example"> </div>
</div>
Upvotes: 0
Reputation: 6531
I think you'll want something similar to this... Where the text is restricted by the fact it can't draw outside the container of the SVG, so you'll just nest a SVG inside a SVG
<svg width="25%" height="25%" viewBox="0 0 42 42" class="donut">
<circle class="donut-segment" cx="21" cy="21" r="15.91549430918954" fill="transparent" stroke="#ce4b99" stroke-width="3"></circle>
<svg width="25%" height="25%" viewBox="0 0 52 52" x="13" y="15.5">
<g x="0" y="25">
<text x="0" y="27">TEXasT</text>
</g>
<svg>
</svg>
EDIT: O.P wanted ellipsis so this is the new fiddle. It uses absolute positioning and transforms to ALWAYS CENTER the inner text both vertically and horizontally.
#p {
height: 250px;
width: 250px;
position: relative;
}
#c {
width: 112px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
position: absolute;
left: 50%;
transform: translateX(-50%) translateY(-50%);
top: 50%;
}
<div id="p">
<svg width="100%" height="100%" viewBox="0 0 42 42" class="donut">
<circle class="donut-segment" cx="21" cy="21" r="15.91549430918954" fill="transparent" stroke="#ce4b99" stroke-width="3"></circle>
<div id="c">
BLAHAHAHHAasdsadsadas
</div>
</svg>
</div>
Upvotes: 1