svg text with clip-path not clipping properly? - svg

We have an issue with applying a clipPath to a text element in the svg below.
<!DOCTYPE html>
<html>
<head>
<title>Svg clipping issue</title>
</head>
<body>
<div style="width:500px;height:180px;">
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" height="100%"
width="100%">
<defs>
<clipPath id="myClip">
<rect transform="matrix(1,-0,-0,1,-0,-25.7478256)" x="198.1017" y="98.565216" width="65" height="25"></rect>
</clipPath>
</defs>
<polygon points="198.1017,98.565216 263.84082,98.565216 263.84082,72.817383 198.1017,72.817383" clip-path="url(#myClip)"
fill="#FF0000" stroke-width="none"></polygon>
<text transform="matrix(1,-0,-0,1,-0,-25.7478256)" clip-path="url(#myClip)" font-family="Microsoft Sans Serif"
font-size="8.25pt" fill="#000000">
<tspan text-anchor="middle" x="230.6017" y="108.755646">Line 1</tspan>
<tspan text-anchor="middle" x="230.6017" y="121.205841">A very very long line</tspan>
</text>
<rect transform="matrix(1,-0,-0,1,-0,-25.7478256)" x="198" y="98" width="65" height="25" fill="none" stroke="#0000FF"></rect>
</svg>
</div>
</body>
</html>
The clipPath is applied to the polygon and to the text element. On the polygon the clipPath is working correctly, as we still see the polygon.
On the text it has the issue that the entire text is clipped, but is should look like this.
If we remove the clipPath from the text element we obviously can we see the entire text, but this is not what we want.
Anybody an idea what's going on or is this a rendering bug in the browser? We get the same result on Firefox, Chrome, Edge, IE.

The problem you are having is because any transform that is applied to an element, also gets applied to the clip path attached to it. So your clip path gets transformed twice by the
transform="matrix(1,-0,-0,1,-0,-25.7478256)"
that is on the <text> element and also on the <clipPath> element.
You can fix this in several ways:
Remove the transform from the <text> element and change it's coordinates so it is correctly over the rectangle. Or do the reverse to the rectangle.
Wrap both the rectangle and the text in a group, and apply the clip path to that (as #Mehdi has suggested)
Make a separate <clipPath> without the transform. See example below
<!DOCTYPE html>
<html>
<head>
<title>Svg clipping issue</title>
</head>
<body>
<div style="width:500px;height:180px;">
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" height="100%"
width="100%">
<defs>
<clipPath id="myClip">
<rect transform="matrix(1,-0,-0,1,-0,-25.7478256)" x="198.1017" y="98.565216" width="65" height="25"></rect>
</clipPath>
<clipPath id="myClip2">
<rect x="198.1017" y="98.565216" width="65" height="25"></rect>
</clipPath>
</defs>
<polygon points="198.1017,98.565216 263.84082,98.565216 263.84082,72.817383 198.1017,72.817383" clip-path="url(#myClip)"
fill="#FF0000" stroke-width="none"></polygon>
<text transform="matrix(1,-0,-0,1,-0,-25.7478256)" clip-path="url(#myClip2)" font-family="Microsoft Sans Serif"
font-size="8.25pt" fill="#000000">
<tspan text-anchor="middle" x="230.6017" y="108.755646">Line 1</tspan>
<tspan text-anchor="middle" x="230.6017" y="121.205841">A very very long line</tspan>
</text>
<rect transform="matrix(1,-0,-0,1,-0,-25.7478256)" x="198" y="98" width="65" height="25" fill="none" stroke="#0000FF"></rect>
</svg>
</div>
</body>
</html>

The problem may be related to the fact that clip-path is not applicable to a tspan.
You may work around it by applying the clipping to a group containing the text and polygon:
<g clip-path="url(#myClip)">
<polygon /* ... */></polygon>
<text transform="matrix(1,-0,-0,1,-0,-25.7478256)" font-family="Microsoft Sans Serif" font-size="8.25pt" fill="#000000">
/* ... */
</text>
</g>
Updated snippet:
<!DOCTYPE html>
<html>
<head>
<title>Svg clipping issue</title>
</head>
<body>
<div style="width:500px;height:180px;">
<svg version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" height="100%"
width="100%">
<defs>
<clipPath id="myClip">
<rect transform="matrix(1,-0,-0,1,-0,-25.7478256)" x="198.1017" y="98.565216" width="65" height="25"></rect>
</clipPath>
</defs>
<g clip-path="url(#myClip)">
<polygon points="190.1017,120.565216 263.84082,98.565216 263.84082,72.817383 198.1017,72.817383"
fill="#FF0000" stroke-width="none"></polygon>
<text transform="matrix(1,-0,-0,1,-0,-25.7478256)" font-family="Microsoft Sans Serif" font-size="8.25pt" fill="#000000">
<tspan text-anchor="middle" x="230.6017" y="108.755646">Line 1</tspan>
<tspan text-anchor="middle" x="230.6017" y="121.205841" >A very very long line</tspan>
</text>
</g>
<rect transform="matrix(1,-0,-0,1,-0,-25.7478256)" x="198" y="98" width="65" height="25" fill="none" stroke="#0000FF"></rect>
</svg>
</div>
</body>
</html>

Related

Add inline labels to a horizontal bar with pure SVG

I use the bellow SVG code to build a horizontal bar chart. It is OK, but I need also two inline labels before and after the chart. I know how to add them with HTML and CSS, but I want to solve this only with pure SVG. How to do this?
<svg class="chart" width="300px" height="40">
<g transform="translate(0,0)">
<rect width="82%" height="22" fill="lightskyblue"></rect>
<rect width="100%" height="22" style="fill:none; stroke-width:1; stroke:gray;"></rect>
<text y="30" dx="0.25em" dy=".35em">0</text>
<text x="20%" y="10" dy=".35em" fill="red">|</text>
<text x="20%" y="30" dx="-0.25em" dy=".35em" fill="red">13</text>
<text x="100%" y="30" dx="-1.25em" dy=".35em">63</text>
</g>
</svg>
This is what I have now:
And this is what I want:
In order to make it work I'm using your code as a nested svg inside a larger svg element. Please observe I'm using a viewBox attribute where the x component has a negative value (-50) making space for the text.
svg{border:solid;}
<svg class="chart" width="300px" viewBox="-50 0 400 40">
<text y="20" x="-45">TXT</text>
<text x="345" y="20" text-anchor="end">TXT</text>
<svg viewBox="0 0 300 40" width="300">
<rect width="82%" height="22" fill="lightskyblue"></rect>
<rect width="100%" height="22" style="fill:none; stroke-width:1; stroke:gray;"></rect>
<text y="30" dx="0.25em" dy=".35em">0</text>
<text x="20%" y="10" dy=".35em" fill="red">|</text>
<text x="20%" y="30" dx="-0.25em" dy=".35em" fill="red">13</text>
<text x="100%" y="30" dx="-1.25em" dy=".35em">63</text>
</svg>
</svg>
I must tell you that I wouldn't percentages for the position and size of the rects. I would have done it using user units (without unit identifier) and I wouldn't have needed to wrap it in another svg element.
UPDATE
the OP is commenting
Can you give another example, without percentages for the position and size of the rects and without wrapping it in another svg element
svg{border:solid}
<svg class="chart" width="300px" viewBox="-50 0 400 40">
<text y="20" x="-45">TXT</text>
<text x="345" y="20" text-anchor="end">TXT</text>
<rect width="246" height="22" fill="lightskyblue"></rect>
<rect width="300" height="22" style="fill:none; stroke-width:1; stroke:gray;"></rect>
<text y="30" dx="0.25em" dy=".35em">0</text>
<text x="60" y="10" dy=".35em" fill="red">|</text>
<text x="60" y="30" dx="-0.25em" dy=".35em" fill="red">13</text>
<text x="300" y="30" dx="-1.25em" dy=".35em">63</text>
<svg>

How to make SVG black bar with centered text responsive?

I'm trying to make an SVG element fit to the bottom of the page with "Loading..." text centered regardless of the container's width. This is my current code which isn't working as the message is always in a fixed position.
<svg id="svg-bottom" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs></defs>
<g id="bottom">
<rect height="40" width="100%" y="0" x="0" fill="#000000" fill-opacity="0.676460598" id="panel"></rect>
<text id="status" font-family="GothamRounded-Medium, Gotham Rounded" font-size="20" font-weight="400" fill="#FFFFFF">
<tspan x="190.97" y="16">Loading…</tspan>
</text>
</g>
I solved the above with the following:
<text font-family="GothamRounded-Medium, Gotham Rounded" font-size="20" font-weight="400" fill="#FFFFFF" x="50%" y="50%" alignment-baseline="middle" text-anchor="middle">Loading…</text>

How do I make a inline text in SVG with different sizes?

It's quite easy to create a text with different font sizes for different words in HTML. But what about <text> in a svg?
I think this explains quite well about my question: https://jsfiddle.net/4atoctra/1/
Everything's here below:
<div>
This is a text, and you can have <span style="font-size:25px">big</span> and <span style="font-size:11px">small</span> sizes easily inline.
</div>
<div style="margin-top:50px">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="none" x="0px" y="0px" width="360px" height="126px" viewBox="0 0 360 126">
<text><tspan x="150" text-anchor="middle" y="10" font-family="Tahoma" font-weight="bold" font-size="11" fill="#003863" xml:space="preserve">What about this in a <svg>?</tspan></text></svg>
</div>
You can just change the font-size on each tspan element?
<svg viewBox="0 0 500 100" width="500px">
<text x="0" y="50" font-size="20">It's not <tspan font-size="30">that hard</tspan><tspan font-size="40"> to change size?</tspan></text>
</svg>
You can use a <foreignObject> to insert HTML markup into an SVG document:
<svg width="220" height="120" viewBox="0 0 220 120">
<rect x="10" y="10" width="200" height="100" fill="#ff9" stroke="none" />
<foreignObject x="15" y="15" width="190" height="90">
<div xmlns="http://www.w3.org/1999/xhtml" style="width:190px; height:90px; overflow-y:auto">This is a text, and you can have <span style="font-size:25px">big</span> and <span style="font-size:11px">small</span> sizes easily inline.
</div>
</foreignObject>
</svg>

Cut off half of the SVG's text element

How can I make this
to look like this
So I want to halve the text element. I don't want to hide half of the text outside of SVG. Hiding it outside of g would be ok, but haven't found solution.
<svg width="500" height="500">
<g transform="translate(50,50)">
<rect width="80" height="50" style="fill:rgb(0,0,255);"/>
<text font-size="40" x="0" y="15" fill="black">SVG</text>
</g>
</svg>
JSFIDDLE:
http://jsfiddle.net/64nkLcdy/
Use the clip-path property :
<svg width="500" height="500">
<defs>
<clipPath id="myClip">
<rect width="80" height="50" />
</clipPath>
</defs>
<g transform="translate(50,50)">
<rect width="80" height="50" style="fill:rgb(0,0,255);" />
<text font-size="40" x="0" y="15" fill="black" clip-path="url(#myClip)">SVG</text>
</g>
</svg>
Use an <svg> element rather than a <g> as the svg element will clip its contents by default. The overflow property controls clipping i.e overflow="visible" doesn't clip but overflow="hidden" does.
<svg width="500" height="500">
<svg transform="translate(50,50)" width="80" height="50" overflow="hidden">
<rect width="80" height="50" style="fill:rgb(0,0,255);"/>
<text font-size="40" x="0" y="15" fill="black">SVG</text>
</svg>
</svg>

SVG: About using <defs> and <use> with variable text values

I have the following SVG source code that generates a number of boxes with texts:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/2001/REC-SVG-20050904/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="600" height="600">
<defs>
</defs>
<title>Draw</title>
<g transform="translate(50,40)">
<rect width="80" height="30" x="0" y="-20" style="stroke: black; stroke-opacity: 1; stroke-width: 1; fill: #9dc2de" />
<text text-anchor="middle" x="40">Text</text>
</g>
<g transform="translate(150,40)">
<rect width="80" height="30" x="0" y="-20" style="stroke: black; stroke-opacity: 1; stroke-width: 1; fill: #9dc2de" />
<text text-anchor="middle" x="40">Text 2</text>
</g>
<g transform="translate(250,40)">
<rect width="80" height="30" x="0" y="-20" style="stroke: black; stroke-opacity: 1; stroke-width: 1; fill: #9dc2de" />
<text text-anchor="middle" x="40">Text 3</text>
</g>
</svg>
As you can see, I repeated the <g></g> three times to get three such boxes, when SVG has <defs> and <use> elements that allow reusing elements using id references instead of repeating their definitions. Something like:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/2001/REC-SVG-20050904/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="600" height="600">
<defs>
<marker style="overflow:visible;fill:inherit;stroke:inherit"
id="Arrow1Mend" refX="0.0" refY="0.0" orient="auto">
<path transform="scale(0.4) rotate(180) translate(20,0)"
style="fill-rule:evenodd;stroke-width:2.0pt;marker-start:none;"
d="M 0.0,-15.0 L -20.5,0.0 L 0.0,15.0 "/>
</marker>
<line marker-end="url(#Arrow1Mend)" id="systemthread" x1="40" y1="10" x2="40" y2="410" style="stroke: black; stroke-dasharray: 5, 5; stroke-width: 1; "/>
</defs>
<title>Draw</title>
<use xlink:href="#systemthread" transform="translate(50,40)" />
<use xlink:href="#systemthread" transform="translate(150,40)" />
<use xlink:href="#systemthread" transform="translate(250,40)" />
</svg>
Unfortunately I can't do this with the first SVG code since I need the texts to be different for each box, while the <use> tag simply duplicates 100% what's defined in <defs>.
Is there any way to use <defs> and <use> with some kind of parameters/arguments mechanism like function calls?
I was searching for an answer to my own SVG question. Your question helped me solve my answer, so I am answering yours.
.... Read your question more closely. Included TWO code samples
Sample #1. Boxes with text
Sample #2. Arrows with text
Sample 1
<html>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="600" height="900">
<defs>
<g id="my_box"
desc="my rectangle template">
<rect width="80" height="30" x="0" y="-20" style="stroke: black; stroke-opacity: 1; stroke-width: 1; fill: #9dc2de" />
</g>
</defs>
<g transform="translate(50 40)">
<text text-anchor="middle" x="40"> This little box went to market </text>
<use xlink:href="#my_box" />
</g>
<g transform="translate(150 140)">
<use xlink:href="#my_box" />
<text text-anchor="middle" x="40"> This little box stayed home </text>
</g>
<g transform="translate(250 240)">
<use xlink:href="#my_box" />
<text text-anchor="middle" x="40"> This little box had roast beef </text>
</g>
</svg>
</html>
Note in sample 1 that the order of the box and text are important.
Sample 2
<html>
<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="600" height="900">
<defs>
<g id="arrow"
desc="arrow with a long dashed tail">
<marker style="overflow:visible;fill:inherit;stroke:inherit"
id="Arrow1Mend" refX="0.0" refY="0.0" orient="auto">
<path transform="scale(0.4) rotate(180) translate(20,0)"
style="fill-rule:evenodd;stroke-width:2.0pt;marker-start:none;"
d="M 0.0,-15.0 L -20.5,0.0 L 0.0,15.0 "
desc="The actual commands to draw the arrow head"
/>
</marker>
<line transform="translate(0 -450)"
marker-end="url(#Arrow1Mend)"
x1="40" y1="10" x2="40" y2="410"
style="stroke: black; stroke-dasharray: 5, 5; stroke-width: 1; "
desc="This is the tail of the 'arrow'"
/>
</g>
</defs>
<g transform="translate(100 450)">
<text> Text BEFORE xlink </text>
<use xlink:href="#arrow" />
</g>
<g transform="translate(200 550)">
<use xlink:href="#arrow" />
<text> More to say </text>
</g>
<g transform="translate(300 650)">
<use xlink:href="#arrow" />
<text> The last word </text>
</g>
<g transform="translate(400 750)">
<use xlink:href="#arrow" />
<text> Text AFTER xlink </text>
</g>
</svg>
</html>
A way to achieve this with the current svg recommendation is not known to me.
But there is a working draft for a svg 2.0 module, see: SVG Referenced Parameter Variables. The example with the flowers there is exactly what you are looking for I suppose! But then you probably have to wait until june 2010 or even longer until this is a W3C recommendation and supported by clients I assume.
For now you could probably solve it with scripting.

Resources