Reputation: 65530
I'm using D3.js
. I'd like to find an SVG equivalent to this CSS class, which adds ellipses if text flows out of its containing div:
.ai-ellipsis {
display: block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
-moz-binding: url(<q>assets/xml/ellipsis.xml#ellipsis</q>);
}
This is my SVG:
<g class="bar" transform="translate(0,39)">
<text class="label" x="-3" y="6.5" dy=".35em" text-anchor="start">Construction</text>
<rect height="13" width="123"></rect>
</g>
It's generated as follows:
barEnter.append("text").attr("class", "label")
.attr("x", -3).attr("y", function() { return y.rangeBand() / 2})
.attr("dy", ".35em").attr("text-anchor", "start")
.text(function(d) {
return d.Name;
});
Currently the text is overflowing and overlapping the rect element.
Is there any way I can say "if text is more than a certain width, crop it and add ellipses"?
Upvotes: 41
Views: 52410
Reputation: 148
Mauro Colella's excellent answer, in Typescript:
export const svgTextEllipsis = (textNode: SVGTextElement, padding = 0) => {
const d3Node = d3.select(textNode);
const targetWidth = Number(d3Node.attr("width")) - padding;
const initialText = d3Node.text();
const precision = 25;
const maxIterations = 3;
let textWidth = d3Node.node()?.getComputedTextLength() ?? 0;
let textLength = initialText.length;
let text = initialText;
let i = 0;
if (textWidth < targetWidth) return;
while (
i < maxIterations &&
text.length > 0 &&
Math.abs(targetWidth - textWidth) > precision
) {
text =
textWidth >= targetWidth
? text.slice(0, -textLength * 0.15)
: initialText.slice(0, textLength * 1.15);
d3Node.text(`${text}…`);
textWidth = d3Node.node()?.getComputedTextLength() ?? 0;
textLength = text.length;
i += 1;
}
d3Node.text(d3Node.text().replace(/…+/, "…"));
};
Upvotes: 0
Reputation: 1917
This function does not depend on d3:
function textEllipsis(el, text, width) {
if (typeof el.getSubStringLength !== "undefined") {
el.textContent = text;
var len = text.length;
while (el.getSubStringLength(0, len--) > width) {
el.textContent = text.slice(0, len) + "...";
}
} else if (typeof el.getComputedTextLength !== "undefined") {
while (el.getComputedTextLength() > width) {
text = text.slice(0,-1);
el.textContent = text + "...";
}
} else {
// the last fallback
while (el.getBBox().width > width) {
text = text.slice(0,-1);
// we need to update the textContent to update the boundary width
el.textContent = text + "...";
}
}
}
Upvotes: 10
Reputation: 59
If you write CSS it won't work on . Instead of that write logic and append '...' in string.
Upvotes: -1
Reputation: 187
function trimText(text, threshold) {
if (text.length <= threshold) return text;
return text.substr(0, threshold).concat("...");
}
Use this function to set the SVG node text. The value for the threshold (eg. 20) depends on you. This means that you will display up to 20 characters from your node text. All the texts grater than 20 characters will be trim and display "..." at the end of the trim text.
Usage eg. :
var self = this;
nodeText.text(x => self.trimText(x.name, 20)) // nodeText is the text element of the SVG node
Upvotes: 3
Reputation: 2832
a wrapper function for overflowing text:
function wrap() {
var self = d3.select(this),
textLength = self.node().getComputedTextLength(),
text = self.text();
while (textLength > (width - 2 * padding) && text.length > 0) {
text = text.slice(0, -1);
self.text(text + '...');
textLength = self.node().getComputedTextLength();
}
}
usage:
text.append('tspan').text(function(d) { return d.name; }).each(wrap);
Upvotes: 81
Reputation: 109232
I am not aware of an equivalent CSS class for SVG, but you can use foreignObject
to embed HTML in SVG. This gives you access to this functionality and is more flexible in general (e.g. you can do automatic line breaking easily).
See here for a complete example.
Upvotes: 16