SVG: how to center multiple lines of text evenly inside of element? - html

This answer and this answer explain how to show multiple lines of text and how to center one line of text with SVG, but how do you center multiple lines of text?
As you can see from this Code Pen, the text block is not centered because of the dy attribute, which is needed to display multiply lines.
The goal is to allow dynamic insertion/deletion of lines while preserving the centered nature of the text block. So the user might add a fourth line or delete two lines. In both cases, the text block should remain centered.
One approach is to modify the dy values each time a line is removed/inserted as some suggested, but is there a non-JS approach to vertically centering a block of text?
<svg style="border:1px solid black" width="200" height="300">
<text x="50%" y="50%" font-size="15">
<tspan x="0" dy="1.2em" dominant-baseline="central">tspan line 1</tspan>
<tspan x="0" dy="1.2em" dominant-baseline="central">tspan line 2</tspan>
<tspan x="0" dy="1.2em" dominant-baseline="central">tspan line 3</tspan>
</text>
</svg>

This is how I would do it:I'm centring everything around the center of the SVG canvas and I'm offsetting the first and the last line with dy
text{text-anchor:middle;dominant-baseline:central;}
<svg style="border:1px solid black" width="200" height="300">
<text x="50%" y="50%" font-size="15">
<tspan x="100" y="150" dy="-1.2em" >tspan line 1</tspan>
<tspan x="100" y="150" >tspan line 2</tspan>
<tspan x="100" y="150" dy="1.2em" >tspan line 3</tspan>
</text>
</svg>
update
The OP commented that they: updated the question to reflect the need for dynamic insert/deletion of lines.
In this case I would put the whole text inside a group and I would use the bounding box of the group to center the text:
The red circle I've added is just in order to see the center of the SVG canvas.
let bb = txt.getBBox(); console.log()
let X = 100;
let Y = 150 - bb.y - (bb.height)/2;
txt.setAttributeNS(null,"transform",`translate(${X},${Y})`)
text{text-anchor:middle;dominant-baseline:central;font-size:15;}
<svg style="border:1px solid black" width="200" height="300">
<text id="txt"><!--
--><tspan x="0" y="0">tspan line 1</tspan><!--
--><tspan x="0" y="1.2em">tspan line 2</tspan><!--
--><tspan x="0" y="2.4em" >tspan line 3</tspan><!--
--><tspan x="0" y="3.6em" >tspan line 4</tspan>
</text>
<circle cx="100" cy="150" r="3" fill="red"/>
</svg>

Related

SVG text doesn't render on Safari, but renders on Chrome

I have some and SVG component that has text in the center - this text renders perfectly fine on Chrome, but just doesn't appear on Safari.
Here are the differences between the 2 browsers:
I'm not sure what the issue is. How can I resolve this?
Here's a link to the code:
https://codepen.io/iamegamind/pen/bGwmpVg?editors=1100
I've done this using the following code:
<svg style="width: 250px; height: 250px"
viewBox="0 0 150 120"
class="progress-card"
xmlns="http://www.w3.org/2000/svg">
<circle style=" stroke-dasharray: 339.29; stroke-dashoffset: 439.29; stroke-width: 8"
r="40"
cx="50%"
cy="50%"/>
<text class="title" x="63" y="50">
<tspan text-anchor="middle" alignment-baseline="central">
Your credit score
</tspan>
</text>
<text class="score" x="63" y="65" text-anchor="middle" alignment-baseline="central">
510
</text>
<text class="status" style="fill: rgb(2, 171, 118)" x="63" y="85" text-anchor="middle" alignment-baseline="central">
Excellent!
</text>
<text class="min" x="30" y="105" text-anchor="middle" alignment-baseline="central">
0
</text>
<text class="max" x="95" y="105" text-anchor="middle" alignment-baseline="central">
710
</text>
</svg>
Safari is hit or miss on supporting certain CSS properties on certain SVG sub-elements. In this case, it's the transform on the SVG text element that it's freaking out about. If you remove it, it will render just fine.
While you could add SVG transforms on the text element, my recommendation might be to not to put a rotate transform on the SVG element class, but instead put the rotate transform on a g element wrapping the circles within the SVG (why do you need two SVG elements btw?). Then position the text using x,y without the need to reverse the transform on the SVG element itself.

How to adjust the size of the multiline text in SVG and position it in the middle

I have a text that I want to adjust it to the size of svg container and position it in the middle (horizontally and vertically). I am looking for relative way, not absolute. So far I have tried putting the text inside svg and adjust it with viewBox attribute and also the transform: scale function.
Is there any standard way to do this?
UPDATE:
With the help of commentators I was able to put the text in the middle of the svg container. Thank you!
However, I am still unable to put multiline text in the middle. The second code snippet is the farthest I came to the solution.
Working code for one line text:
<svg width="890" height="500"overflow="hidden;">
<g>
<rect x="0" y="0" width="542" height="495" fill="#6fdd6f"></rect>
<svg x="0" y="0" width="542" height="495" viewBox="0 0 100 100">
<text alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" x="50%" y="50%">TXT</text>
</svg>
</g>
</svg>
Code with multiline that needs to be adjusted to center:
<svg width="890" height="500"overflow="hidden;">
<g>
<rect x="0" y="0" width="542" height="495" fill="#6fdd6f"></rect>
<svg x="0" y="0" width="542" height="495" viewBox="0 0 100 100">
<text alignment-baseline="middle" dominant-baseline="middle" text-anchor="middle" >
<tspan x="50%" y="50%">TXT</tspan>
<tspan dy="1em" x="50%" y="50%">more TXT</tspan>
<tspan dy="2em" x="50%" y="50%">end of TXT</tspan>
</text>
</svg>
</g>
</svg>

SVG: scale text with enclosing rectangle or SVG element?

There are similar questions like this, but none are identical.
The goal is to scale text as its enclosing rectangle or SVG element scales.
In the example below, the enclosing SVG element is 200x300.
The goal is for the whole element to preserve proportionality, including text.
If you scale by 0.5 to 100x150, the text should scale accordingly.
However, changing the size to 100x150 scales the SVG element correctly, but the text remains the same size and no longer retains the same proportions to the enclosing container.
Code pen: https://codepen.io/anon/pen/BvLZKv?page=1&
<svg width="200" height="300">
<g>
<rect x="0" y="0" width="100%" height="100%" fill="red"></rect>
<text x="50%" y="50%" font-family="Verdana" font-size="20" fill="blue" dominant-baseline="middle" text-anchor="middle">Hello</text>
</g>
</svg>
You can create the base svg and scale that entire svg with a scale value, then the text also will be scaled.
Here is a demo in which I scaled the svg to .5, the entire svg with the text is scaled to half.
svg {
transform: scale(.5);
transform-origin: 0% 0%;
}
<svg width="200" height="300">
<g>
<rect x="0" y="0" width="100%" height="100%" fill="red"></rect>
<text x="50%" y="50%" font-family="Verdana" font-size="82" fill="blue" dominant-baseline="middle" text-anchor="middle">Hello</text>
</g>
</svg>

SVG text not centering firefox

I have a SVG logo with text, text is centered.
And this works fine in chrome and IE but not firefox, the text is slightly moved to the left.
<text transform="matrix(0.9287 0 0 1 60.9023 137.7646)">
<tspan x="0" y="0" fill="#FFFFFF" stroke="#FAFAF8" stroke-miterlimit="10" font-family="'Consolas'" font-size="71.5163" letter-spacing="9.691">SOC</tspan>
<tspan x="-24.809" y="85.819" fill="#FFFFFF" stroke="#FAFAF8" stroke-miterlimit="10" font-family="'Consolas'" font-size="71.5163" letter-spacing="9.691">KING</tspan>
</text>
Please help.
use text-anchor="middle" to center the text (then its x coordinate points to the center)
OK so I came up with a work around to get letter spacing the same in both chrome and firefox. What I did was add both to text-transform: letter-spacing="9.691" textLength="780px" and adjusted the x until both browsers displayed the text in the center of the image. Its not pretty but it works.
<text transform="matrix(0.9287 0 0 1 60.9023 137.7646)" letter-spacing="9.691" textLength="780px" font-weight="bold">
<tspan x="1" y="0" fill="#FFFFFF" font-family="'Consolas'" font-size="71.5163">SOC</tspan>
<tspan x="-21.8" y="85.819" fill="#FFFFFF" font-family="'Consolas'" font-size="71.5163">KING</tspan>
</text>

How can I limit or clip text in SVG?

I have done a table in SVG, and I want to fill it with data dynamically. That means that I don't know how much space the text takes, and I want to clip or hide the overlapping text. How can I do that in SVG?
My HTML document with SVG looks like:
<!DOCTYPE html>
<html>
<body>
<svg>
<text x="100" y="100">Orange</text> <text x="160" y="100">12</text>
<text x="100" y="115">Pear</text> <text x="160" y="115">7</text>
<text x="100" y="130">Banana</text> <text x="160" y="130">9</text>
<text x="100" y="145">Pomegranate</text><text x="160" y="145">2</text>
<line x1="157" y1="85" x2="157" y2="155" style="stroke:rgb(100,100,100)"/>
</svg>
</body>
</html>
And this will render to:
Is there any way I can clip the text i my SVG-"table"?
Implemented solution from Erik's answer:
<!DOCTYPE html>
<html>
<body>
<svg>
<text x="10" y="20" clip-path="url(#clip1)">Orange</text>
<text x="10" y="35" clip-path="url(#clip1)">Pear</text>
<text x="10" y="50" clip-path="url(#clip1)">Banana</text>
<text x="10" y="65" clip-path="url(#clip1)">Pomegranate</text>
<text x="70" y="20">12</text>
<text x="70" y="35">7</text>
<text x="70" y="50">9</text>
<text x="70" y="65">2</text>
<line x1="67" y1="5" x2="67" y2="75" style="stroke:rgb(100,100,100)"/>
<clipPath id="clip1">
<rect x="5" y="5" width="57" height="90"/>
</clipPath>
</svg>
</body>
</html>
You can use clip-path to clip to whatever shape you want, see e.g masking-path-01 from the svg testsuite.
Relevant parts, defining the clip path:
<clipPath id="clip1">
<rect x="200" y="10" width="60" height="100"/>
... you can have any shapes you want here ...
</clipPath>
and then apply the clip path like this:
<g clip-path="url(#clip1)">
... your text elements here ...
</g>
If for some reason you don't want to use clipping, you can also use a nested SVG tag:
<svg>
<svg x="10" y="10" width="10" height="10">
<text x="0" y="0">Your text</text>
</svg>
</svg>
This way, your text will be cut off when it's outside the nested SVG viewport. Note that the x and y of the text tag refer to the coordinate system of the nested SVG, and correspond to 10 in the coordinate system of the outer SVG.
As Marcin said in point (2) of his answer (unfortunately downvoted but actually this is a good point) an alternative way to achieve the effect is to overpaint the part not wanted with a white rectangle.
<!DOCTYPE html>
<html>
<body>
<svg>
<text x="100" y="100">Orange</text>
<text x="100" y="115">Pear</text>
<text x="100" y="130">Banana</text>
<text x="100" y="145">Pomegranate</text>
<!-- Overpaint the overflowing text -->
<rect x="155" y="85" width="100" height="100" fill="white" />
<line x1="157" y1="85" x2="157" y2="155" style="stroke:rgb(100,100,100)"/>
<text x="160" y="100">12</text>
<text x="160" y="115">7</text>
<text x="160" y="130">9</text>
<text x="160" y="145">2</text>
</svg>
</body>
</html>
Reference to the SVG specification: SVG 2.0 Rendering Order
(1) There is no reason to use SVG for tables. Use HTML tables.
(2) By "clipping" I understand you to mean that the excess text will be obscured. SVG uses a "painter's model" whereby elements specified later in the document are drawn above elements specified earlier. This will allow you to clip regions.
(3) If you really needed to do this in an SVG document you could use a foreign object, and embed HTML.
If you don't want to use a clip-path, which can be a pain if each element has a different size, then you can also use nested <svg> elements for clipping. Just make sure the svg elements have the CSS style overflow:hidden.
<!DOCTYPE html>
<html>
<body>
<svg>
<svg width="57" height="15" x="10" y="5"><text y="15">Orange</text></svg>
<svg width="57" height="15" x="10" y="20"><text y="15">Pear</text></svg>
<svg width="57" height="15" x="10" y="35"><text y="15">Banana</text></svg>
<svg width="57" height="15" x="10" y="50"><text y="15">Pomegranate</text></svg>
<text x="70" y="20">12</text>
<text x="70" y="35">7</text>
<text x="70" y="50">9</text>
<text x="70" y="65">2</text>
<line x1="67" y1="5" x2="67" y2="75" style="stroke:rgb(100,100,100)"/>
</svg>
</body>
</html>