Dynamic Text in SVG def - html

I'm trying to use an svg def tag to show some dimensions in a creative manner.
For an example, I have below example svg def.
<svg>
<defs>
<g id="aaa" stroke-opacity="null" stroke-width="0" stroke-linecap="null" stroke-linejoin="null" fill="#6bfc46" >
<circle cx="300" cy="400" r="30" />
<rect x="300" y="400" width="30" height="30" />
<circle cx="300" cy="400" r="25" fill="#ffffff" />
<text x="300" y="400" text-anchor="middle" stroke="#6bfc46" stroke-width="2px" dy=".3em">5</text>
</g>
</defs>
<svg/>
What I need to achieve is I should be able to use this defined text in different locations as below.
<use xlink:href="#aaa" x="480" y="550" changing_text="3">
<use xlink:href="#aaa" x="580" y="650" changing_text="9">
My Question is
1) Is this possible with SVG ?
2) How can we achieve this ?

Related

SVG text not horizontally center aligning

I generated a SVG using Adobe XD. They use transform for positioning things but the text in my mini computer screen is not always the same width (it is dynamically generated). I have tried anchored, anything I could find but it still didn't work. This is how it looks with the current code:
Here is the code:
<svg xmlns="http://www.w3.org/2000/svg" width="903.5" height="860.5" viewBox="0 0 1200 1041">
<g transform="translate(-397)">
<g
transform="translate(507 975)"
fill="#fff"
stroke="#707070"
strokeWidth="1"
>
<rect width="907" height="66" rx="33" stroke="none" />
<rect x="0.5" y="0.5" width="906" height="65" rx="32.5" fill="none" />
</g>
<rect width="119" height="395" transform="translate(901 613)" fill="#fff" />
<g
transform="translate(397)"
fill="#232323"
stroke="#fff"
stroke-width="30"
>
<rect width="1127" height="627" rx="103" stroke="none" />
<rect x="15" y="15" width="1097" height="597" rx="88" fill="none" />
</g>
<text
fill="white"
fontSize="96"
fontFamily="Fredoka"
>
{screenText}
</text>
</g>
</svg>
You can use transform/translate, text-anchor and dominant-baseline to place a text in the middle of something.
body {
background: gray;
}
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 1041">
<g
transform="translate(600 975)"
fill="#fff"
stroke="#707070"
stroke-width="1">
<rect x="-453.5" width="907" height="66" rx="33" stroke="none" />
<rect x="-454" y="0.5" width="906" height="65" rx="32.5" fill="none" />
</g>
<rect width="119" height="395" transform="translate(545.5 613)" fill="#fff" />
<g transform="translate(50)"
fill="#232323"
stroke="#fff"
stroke-width="30">
<rect width="1127" height="627" rx="103" stroke="none" />
<rect x="15" y="15" width="1097" height="597" rx="88" fill="none" />
</g>
<text
fill="#fff"
font-size="96"
font-family="Fredoka"
transform="translate(600 300)"
text-anchor="middle"
dominant-baseline="middle">
{screenText}
</text>
</svg>
Thanks to Buhan Yu's comment I learned that you need to specify x and y to center align it. I set x="50%" and it worked!

Task Timeline with SVG - Cannot animate final transition

I'm trying to generate a task timeline coded as SVG in a generic way, and managed to do it until this point:
https://jsfiddle.net/dx7uryob/
What remains to do is the following animation (on any task point):
From:
:
To the "task" label rotating 90 deg until displayed perfectly horizontally and centered above the task point. I managed to do this by adding rotate(90, x+45, y) into the transform attribute of the according <text> tag, but this fails to rotate gradually when I use svg text { transition: transform 1s; }.
I would really like to know how to animate that (working also in IE, so I guess animateTransform is not an option..?).
Then, the final step would be to blend in a second label below the rotated "task" label, ideally by turning an opacity to 1 to gradually make it appear / disappear. For this, I could not even find out how to place a second text label below the first one in an SVG.
Concering solutions; I'd really prefer to stick to SVG, as my solution copied in the snippet can be generated in a generic way and then scaled as you please; it's just these last two pieces missing and I can't find them out.
.label-below-task-point {
text-anchor: end;
}
<svg version="1.1" baseProfile="full" viewBox="0 0 1000 200" xmlns="http://www.w3.org/2000/svg" id="timeline-container">
<line x1="11" x2="200" y1="11" y2="142.2" stroke="black" />
<line x1="200" x2="400" y1="142.2" y2="121" stroke="black" />
<line x1="400" x2="600" y1="121" y2="167.6" stroke="black" />
<line x1="600" x2="989" y1="167.6" y2="11" stroke="black" />
<rect stroke="black" stroke-width="2" fill="white" width="20" height="20" x="11" y="11" transform="translate(-10,-10)" />
<text x="11" y="11" transform="translate(6,20) rotate(-90,11,11)" class="label-below-task-point">task</text>
<rect stroke="black" stroke-width="2" fill="white" width="20" height="20" x="200" y="142.2" transform="translate(-10,-10)" />
<text x="200" y="142.2" transform="translate(6,-20) rotate(-90,200,142.2)" class="label-above-task-point">task</text>
<rect stroke="black" stroke-width="2" fill="white" width="20" height="20" x="400" y="121" transform="translate(-10,-10)" />
<text x="400" y="121" transform="translate(6,-20) rotate(-90,400,121)" class="label-above-task-point">task</text>
<rect stroke="black" stroke-width="2" fill="white" width="20" height="20" x="600" y="167.6" transform="translate(-10,-10)" />
<text x="600" y="167.6" transform="translate(6,-20) rotate(-90,600,167.6)" class="label-above-task-point">task</text>
<rect stroke="black" stroke-width="2" fill="white" width="20" height="20" x="989" y="11" transform="translate(-10,-10)" />
<text x="989" y="11" transform="translate(6,20) rotate(-90,989,11)" class="label-below-task-point">task</text>
</svg>
Am I maybe better off using a canvas here? I've no experience with it, but I wonder if it's the better choice for what I'm trying to do..
I have solved it for the second text, task2, using transform-origin :
.label-below-task-point {
transform: rotate(90deg);
transition: transform 1s;
}
.label-below-task-point:hover {
transform: rotate(0deg);
}
<svg version="1.1" baseProfile="full" viewBox="0 0 1000 200" xmlns="http://www.w3.org/2000/svg" id="timeline-container">
<line x1="11" x2="200" y1="11" y2="142.2" stroke="black" />
<line x1="200" x2="400" y1="142.2" y2="121" stroke="black" />
<line x1="400" x2="600" y1="121" y2="167.6" stroke="black" />
<line x1="600" x2="989" y1="167.6" y2="11" stroke="black" />
<rect stroke="black" stroke-width="2" fill="white" width="20" height="20" x="11" y="11" transform="translate(-10,-10)" />
<text x="11" y="11" class="label-below-task-point">task</text>
<rect stroke="black" stroke-width="2" fill="white" width="20" height="20" x="200" y="142.2" transform="translate(-10,-10)" />
<text x="180" y="125" transform-origin="199px 120px" class="label-below-task-point">task2</text>
<rect stroke="black" stroke-width="2" fill="white" width="20" height="20" x="400" y="121" transform="translate(-10,-10)" />
<text x="400" y="121" transform="translate(6,-20) rotate(-90,400,121)" class="label-above-task-point">task</text>
<rect stroke="black" stroke-width="2" fill="white" width="20" height="20" x="600" y="167.6" transform="translate(-10,-10)" />
<text x="600" y="167.6" transform="translate(6,-20) rotate(-90,600,167.6)" class="label-above-task-point">task</text>
<rect stroke="black" stroke-width="2" fill="white" width="20" height="20" x="989" y="11" transform="translate(-10,-10)" />
<text x="989" y="11" transform="translate(6,20) rotate(-90,989,11)" class="label-below-task-point">task</text>
</svg>

CSS Select #shadow-root

I'd like to use CSS to set the fill of the bottom right circle to green.
The snippet below successfully colors entire shape to green, but I just want to color part of the shape (just the circle).
[row="2"] [col="b"] {fill: green;}
<svg>
<defs>
<symbol id="myShape">
<polygon points="0,0 40,0 20,20" />
<circle r="10" cx="10" cy="10" stroke="black" />
</symbol>
</defs>
<g row="1" translate="transform(0,0)">
<use col="a" xlink:href="#myShape" x="0" />
<use col="b" xlink:href="#myShape" x="50" />
</g>
<g row="2" transform="translate(0,50)">
<use col="a" xlink:href="#myShape" x="0" />
<use col="b" xlink:href="#myShape" x="50" />
</g>
</svg>
If you want something to always be a specific colour, then set it to be that colour explicitly.
[row="2"] [col="b"] {fill: green;}
[row="1"] [col="b"] {fill: blue;}
<svg>
<defs>
<symbol id="myShape">
<polygon points="0,0 40,0 20,20" fill="black" />
<circle r="10" cx="10" cy="10" stroke="black" />
</symbol>
</defs>
<g row="1" translate="transform(0,0)">
<use col="a" xlink:href="#myShape" x="0" />
<use col="b" xlink:href="#myShape" x="50" />
</g>
<g row="2" transform="translate(0,50)">
<use col="a" xlink:href="#myShape" x="0" />
<use col="b" xlink:href="#myShape" x="50" />
</g>
</svg>

Multiple lines in HTML circle?

I would like to write multiple lines in this circles without css, but only if it's needed style tag.
Here's the code:
<svg height="300" width="300">
<circle cx="150" cy="150" r="120" stroke="red" stroke-width=4 fill="#ffffff" />
<text x="50%" y="50%" text-anchor="middle" stroke="#000000" stroke-width="1px" dy=".3em">My code<br/>isn't good</text>
</svg>
Thank you!
this is the best i was able to do. It works but its hardcoded which obviously is not ideal. I looked at this other post btw, might be of some help: How to linebreak an svg text within javascript?
<svg height="300" width="300">
<circle cx="150" cy="150" r="120" stroke="red" stroke-width=4 fill="#ffffff" />
<text text-anchor="middle" stroke="#000000" stroke-width="1px" dy=".3em">
<tspan x="50%" y="40%" dy="1.2em">very long text</tspan>
<tspan x="50%" y="50%" dy="1.2em">I would like to linebreak</tspan>
</text>
</svg>

SVG rect as background to text [duplicate]

I want to color the background of svg text similar to background-color in css
I was only able to find documentation on fill, which colors the text itself
Is it even possible?
You could use a filter to generate the background.
<svg width="100%" height="100%">
<defs>
<filter x="0" y="0" width="1" height="1" id="solid">
<feFlood flood-color="yellow" result="bg" />
<feMerge>
<feMergeNode in="bg"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<text filter="url(#solid)" x="20" y="50" font-size="50">solid background</text>
</svg>
No this is not possible, SVG elements do not have background-... presentation attributes.
To simulate this effect you could draw a rectangle behind the text attribute with fill="green" or something similar (filters). Using JavaScript you could do the following:
var ctx = document.getElementById("the-svg"),
textElm = ctx.getElementById("the-text"),
SVGRect = textElm.getBBox();
var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect");
rect.setAttribute("x", SVGRect.x);
rect.setAttribute("y", SVGRect.y);
rect.setAttribute("width", SVGRect.width);
rect.setAttribute("height", SVGRect.height);
rect.setAttribute("fill", "yellow");
ctx.insertBefore(rect, textElm);
The solution I have used is:
<svg>
<line x1="100" y1="100" x2="500" y2="100" style="stroke:black; stroke-width: 2"/>
<text x="150" y="105" style="stroke:white; stroke-width:0.6em">Hello World!</text>
<text x="150" y="105" style="fill:black">Hello World!</text>
</svg>
A duplicate text item is being placed, with stroke and stroke-width attributes. The stroke should match the background colour, and the stroke-width should be just big enough to create a "splodge" on which to write the actual text.
A bit of a hack and there are potential issues, but works for me!
Instead of using a <text> tag, the <foreignObject> tag can be used, which allows for XHTML content with CSS.
No, you can not add background color to SVG elements. You can do it programmatically with d3.
var text = d3.select("text");
var bbox = text.node().getBBox();
var padding = 2;
var rect = self.svg.insert("rect", "text")
.attr("x", bbox.x - padding)
.attr("y", bbox.y - padding)
.attr("width", bbox.width + (padding*2))
.attr("height", bbox.height + (padding*2))
.style("fill", "red");
Answer by Robert Longson (#RobertLongson) with modifications:
<svg width="100%" height="100%">
<defs>
<filter x="0" y="0" width="1" height="1" id="solid">
<feFlood flood-color="yellow"/>
<feComposite in="SourceGraphic" operator="xor"/>
</filter>
</defs>
<text filter="url(#solid)" x="20" y="50" font-size="50"> solid background </text>
<text x="20" y="50" font-size="50">solid background</text>
</svg>
and we have no bluring and no heavy "getBBox" :)
Padding is provided by white spaces in text-element with filter.
It's worked for me
Going further with #dbarton_uk answer, to avoid duplicating text you can use paint-order=stroke style:
<svg>
<line x1="100" y1="100" x2="350" y2="100" style="stroke:grey; stroke-width: 100"/>
<text x="150" y="105" style="stroke:white; stroke-width:0.5em; fill:black; paint-order:stroke; stroke-linejoin:round">Hello World!</text>
</svg>
Note the stroke-linejoin:round which is needed to avoid seeing spikes for the W sharp angle.
You can combine filter with the text.
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>SVG colored patterns via mask</title>
</head>
<body>
<svg viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter x="0" y="0" width="1" height="1" id="bg-text">
<feFlood flood-color="white"/>
<feComposite in="SourceGraphic" operator="xor" />
</filter>
</defs>
<!-- something has already existed -->
<rect fill="red" x="150" y="20" width="100" height="50" />
<circle cx="50" cy="50" r="50" fill="blue"/>
<!-- Text render here -->
<text filter="url(#bg-text)" fill="black" x="20" y="50" font-size="30">text with color</text>
<text fill="black" x="20" y="50" font-size="30">text with color</text>
</svg>
</body>
</html>
this is my favorite hack (not sure it should work). It refer an element that is not yet displayed, and it works pretty well
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 620 40" preserveAspectRatio="xMidYMid meet">
<defs>
<filter x="-0.02" y="0" width="1.04" height="1.1" id="removebackground">
<feFlood flood-color="#00ffff"/>
</filter>
</defs>
<!--Draw the text-->
<use xlink:href="#mygroup" filter="url(#removebackground)" />
<g id="mygroup">
<text id="text1" x="9" y="20" style="text-anchor:start;font-size:14px;">custom text with background</text>
<line x1="200" y1="18" x2="200" y2="36" stroke="#000" stroke-width="5"/>
<line x1="120" y1="27" x2="203" y2="27" stroke="#000" stroke-width="5"/>
</g>
</svg>
For those wondering how to apply padding to a text element when it has a background like in the Robert's answer, do the following:
<svg>
<defs>
<filter x="-0.1" y="-0.1" width="1.2" height="1.2" id="solid">
<feFlood flood-color="#171717"/>
<feComposite in="SourceGraphic" operator="xor" />
</filter>
</defs>
<text filter="url(#solid)" x="20" y="50" font-size="50">Hello</text>
</svg>
In the example above, filter's x and y positions can be used as transform: translate(-10%, -10%) would, and width and height values can be read as 120% and 120%. So we made background 20% bigger, and offsetted it -10%, so background is now 10% bigger on each side of the text.
The previous answers relied on doubling up text and lacked sufficient whitespace.
By using atop and I was able to get the results I wanted.
This example also includes arrows, a common use case for SVG text labels:
<svg viewBox="-105 -40 210 234">
<title>Size Guide</title>
<defs>
<filter x="0" y="0" width="1" height="1" id="solid">
<feFlood flood-color="white"></feFlood>
<feComposite in="SourceGraphic" operator="atop"></feComposite>
</filter>
<marker id="arrow" viewBox="0 0 10 10" refX="5" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse">
<path d="M 0 0 L 10 5 L 0 10 z"></path>
</marker>
</defs>
<g id="garment">
<path id="right-body" fill="none" stroke="black" stroke-width="1" stroke-linejoin="round" d="M0 0 l30 0 l0 154 l-30 0"></path>
<path id="right-sleeve" d="M30 0 l35 0 l0 120 l-35 0" fill="none" stroke-linejoin="round" stroke="black" stroke-width="1"></path>
<use id="left-body" href="#right-body" transform="scale(-1,1)"></use>
<use id="left-sleeve" href="#right-sleeve" transform="scale(-1,1)"></use>
<path id="collar-right-top" fill="none" stroke="black" stroke-width="1" stroke-linejoin="round" d="M0 -6.5 l11.75 0 l6.5 6.5"></path>
<use id="collar-left-top" href="#collar-right-top" transform="scale(-1,1)"></use>
<path id="collar-left" fill="white" stroke="black" stroke-width="1" stroke-linejoin="round" d="M-11.75 -6.5 l-6.5 6.5 l30 77 l6.5 -6.5 Z"></path>
<path id="front-right" fill="white" stroke="black" stroke-width="1" d="M18.25 0 L30 0 l0 154 l-41.75 0 l0 -77 Z"></path>
<line x1="0" y1="0" x2="0" y2="154" stroke="black" stroke-width="1" stroke-dasharray="1 3"></line>
<use id="collar-right" href="#collar-left" transform="scale(-1,1)"></use>
</g>
<g id="dimension-labels">
<g id="dimension-sleeve-length">
<line marker-start="url(#arrow)" marker-end="url(#arrow)" x1="85" y1="0" x2="85" y2="120" stroke="black" stroke-width="1"></line>
<text font-size="10" filter="url(#solid)" fill="black" x="85" y="60" class="dimension" text-anchor="middle" dominant-baseline="middle"> 120 cm</text>
</g>
<g id="dimension-length">
<line marker-start="url(#arrow)" marker-end="url(#arrow)" x1="-85" y1="0" x2="-85" y2="154" stroke="black" stroke-width="1"></line>
<text font-size="10" filter="url(#solid)" fill="black" x="-85" y="77" text-anchor="middle" dominant-baseline="middle" class="dimension"> 154 cm</text>
</g>
<g id="dimension-sleeve-to-sleeve">
<line marker-start="url(#arrow)" marker-end="url(#arrow)" x1="-65" y1="-20" x2="65" y2="-20" stroke="black" stroke-width="1"></line>
<text font-size="10" filter="url(#solid)" fill="black" x="0" y="-20" text-anchor="middle" dominant-baseline="middle" class="dimension"> 130 cm </text>
</g>
<g title="Back Width" id="dimension-back-width">
<line marker-start="url(#arrow)" marker-end="url(#arrow)" x1="-30" y1="174" x2="30" y2="174" stroke="black" stroke-width="1"></line>
<text font-size="10" filter="url(#solid)" fill="black" x="0" y="174" text-anchor="middle" dominant-baseline="middle" class="dimension"> 60 cm </text>
</g>
</g>
</svg>
An obvious workaround to the problem of the blur produced by the filter effect is to render the <text> two times: once for the background (with transparent characters) and once for the characters (without a background filter).
For me, this was the only way to make the text readable in Safari.
<svg width="100%" height="100%">
<filter x="0" y="0" width="1" height="1" id="solid">
<feFlood flood-color="yellow" />
</filter>
<g transform="translate(20, 50)" font-size="50">
<text aria-hidden="true" fill="none" filter="url(#solid)">solid background</text>
<text fill="blue">solid background</text>
</g>
</svg>
The aria-hidden="true" attribute is there to prevent screen readers from speaking the text twice, if the user uses a screen reader.
You can add style to your text:
style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
text-shadow: rgb(255, 255, 255) -2px -2px 0px, rgb(255, 255, 255) -2px 2px 0px,
rgb(255, 255, 255) 2px -2px 0px, rgb(255, 255, 255) 2px 2px 0px;"
White, in this example.
Does not work in IE :)