Extract Text From SVG [duplicate] - javascript

I have this javascript...
window.writeText = function(form) {
var text;
form.catnumber2.value = "PING";
text = document.getElementByName('cat2Number').innerHtml;
return alert(text);
};
But I get no alert box as expected.
The svg does not show up when I view source via view/developer/view source (I'm in chrome) BUT when I use view/developer/developer tools....I can see the following svg....
<svg height="594">
<g ID="MasterG">
<text name="cat2Number">"$1234"</text>
</g>
</svg>
Any idea what I'm doing wrong? Why is it I can't see the svg code in "view source" but I can in "developer tools" ? Is that the cause of my problem? Is that why my alert box won't "alert"?

After a couple of seconds of googling and finding this https://stackoverflow.com/a/9602772/1217408
I created this JSFiddle example of using textContent: http://jsfiddle.net/hVyTJ/1/
The original http://jsfiddle.net/hVyTJ/ uses standard DOM traversal to get to the text element from the root SVG element. While the update targets the text element directly by ID.
As for finding attribute values you can use getAttributeNS as described here: http://www.carto.net/svg/manipulating_svg_with_dom_ecmascript/
EDIT:
As pointed out by Phrogz, a simple getAttribute call is often sufficient. Read the comment for more details.

you can invoke text() to return the text content of an svg:text element.
// assume svgCont is an svg element
var label = svgCont.append("svg:text").text("hello, world!");
// print the text content to the console
console.log( label.text() );

Without all the unnecessary discussion:
Grab your SVG element:
svg = document.getElementById("my_svg_id");
Grab the inner text from the SVG:
var text = svg.textContent

For the ones who need only the displayed text, you can use the Selection API and its Range interface.
Simply using .textContent would also grab all text nodes that aren't displayed:
const svg = document.querySelector("svg");
console.log(svg.textContent);
<svg>
<defs>
<desc>This text is not displayed, it shouldn't be grabbed</desc>
<!-- same for all the new-lines in the markup -->
</defs>
<rect width="100%" height="100%" fill="red" />
<circle cx="150" cy="100" r="80" fill="green" />
<text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text>
</svg>
Since SVG elements don't have an innerText property, we need to iterate ourselves over all text nodes, and check if when selecting them we get a BBox.
const svg = document.querySelector("svg");
const range = new Range();
let textContent = "";
const walker = document.createTreeWalker(svg, NodeFilter.SHOW_TEXT, null);
while(walker.nextNode() && walker.currentNode) {
range.selectNode(walker.currentNode);
if (range.getClientRects().length) {
textContent += walker.currentNode.textContent;
}
}
console.log(textContent);
<svg>
<defs>
<desc>This text is not displayed, it shouldn't be grabbed</desc>
<!-- same for all the new-lines in the markup -->
</defs>
<rect width="100%" height="100%" fill="red" />
<circle cx="150" cy="100" r="80" fill="green" />
<text x="150" y="125" font-size="60" text-anchor="middle" fill="white">SVG</text>
</svg>

just using the dom method
const svg = document.querySelector(`[data-uuid="live_map_svg"]`);
const shape = svg.querySelector(`text`);
const text = shape.innerHTML;
// const text = shape.textContent;
setTimeout(() => {
svg.insertAdjacentHTML(`beforebegin`, text);
}, 1000);
.svg-box{
width: 500px;
height: 500px;
background: #ccc;
color: red;
font-size: 16px;
}
[data-uuid="live_map_svg"]{
font-size: 16px;
}
<div class="svg-box">
<svg
data-uuid="live_map_svg" id="live_map_svg"
width="100%" height="100%"
viewBox="0 0 100 100"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<text x="30" y="50" fill="#369acd">A Area</text>
</svg>
</div>

Related

Can SVG elements be tagged for later retrieval?

I have many dynamically generated SVGs inside my HTML document. They contain (among other elements) some <text> elements, sort of like this:
<svg>
// bunch of other elements
<text x="50" y="25" text-anchor="middle" dominant-baseline="central" class="myclass">info 1</text>
<text x="50" y="75" text-anchor="middle" dominant-baseline="central" class="myclass">info 2</text>
// more other elements
</svg>
Is there anything like an id or name attribute that I can use for these two pieces of text, so that I can dynamically change them later? id is supposed to be unique in the whole document, and I have many of these SVGs. name appears to mean something different in SVG (as opposed to HTML forms), and in any case might be deprecated. I'm hesitant to use class, since this really isn't about styling and I don't want weird conflicts with other styles that might be present in the document. (I don't have control over the document my code is used in, or its stylesheets.) I also don't want to have to search the whole document for the element I'm looking for, since I'll already have a pointer to its parent. Ideally, I'd do something like this:
mySVG.getChildByType("type1").innerHTML = "here's new text";
(With the caveat that every similar SVG has one of these "type1"` children, that can be grabbed and messed with.)
Thanks.
I'm not sure why you think id is unusable. You say you are dynamically generating the SVGs. I presume that means at run-time. So just generate an id at the same time. Prefix it, if you need to, so that you can be sure it is going to be unique.
textElement.setAttribute("id", "my-prefix-" + nextTextElemIndex);
There's also nothing stopping you from adding your own custom attribute. Then style the elements using attribute selectors.
circle[foo="middle"] {
fill: green;
}
<svg viewBox="0 0 300 100">
<circle cx="50" cy="50" r="50" fill="red" foo="left"/>
<circle cx="150" cy="50" r="50" fill="red" foo="middle"/>
<circle cx="250" cy="50" r="50" fill="red" foo="right"/>
</svg>
It's not exactly clear what you want; it sounds like you want a way to get the text elements inside of a specific SVG?
In that case, use something like the following:
document.querySelectorAll("svg > text")[0].textContent = "I am the new string";
Now querySelectorAll() just uses CSS selectors, so you can replace svg in there with a .class, #id, or just use whatever CSS selector you want.
You could use rather verbose class names to avoid accidentally overridden styles inherited from a global css.
E.g a BEM-like naming scheme as commonly used in scripts/libs like image sliders, media players, map helpers.
If your svg text elements are dynamically compiled you might run a function adding index based IDs/class properties:
const parentSvgs = document.querySelectorAll('.mySvg');
setAutoIds(parentSvgs);
function setAutoIds(parents) {
parents.forEach(function(parent, p) {
let parentIdentifier = 'mySvg__parent--' + p;
parent.id = parentIdentifier;
parent.classList.add(parentIdentifier);
let children = parent.querySelectorAll('.mySvg__text');
children.forEach(function(child, c) {
let childIdentifier = `mySvg__text--${p}-${c}`;
child.id = childIdentifier;
child.classList.add(childIdentifier);
child.classList.add(`mySvg__text--${c}`);
})
})
}
//auto content example
setIndexedContent(parentSvgs);
function setIndexedContent(parents) {
parents.forEach(function(parent, p) {
let children = parent.querySelectorAll('.mySvg__text');
children.forEach(function(child, c) {
child.textContent = `svg nr.${p+1} info nr.${c+1}`;
});
});
}
// single text element selected by ID or class
document.getElementById('mySvg__text--1-3').textContent = 'unique text by ID';
document.getElementById('mySvg__text--1-2').textContent = 'unique text by Class';
.mySvg {
display: inline-block;
width: 20vw;
border: 1px dotted #ccc;
}
.mySvg__text {
font-size: 10px;
}
.mySvg__text--0 {
fill: orange
}
.mySvg__text--1 {
fill: green
}
.mySvg__text--2 {
fill: red
}
<svg class="mySvg" viewBox="0 0 100 100">
<text x="50" y="25" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
<text x="50" y="50" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
<text x="50" y="75" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
</svg>
<svg class="mySvg" viewBox="0 0 100 100">
<text x="50" y="10" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
<text x="50" y="20" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
<text x="50" y="30" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
<text x="50" y="40" text-anchor="middle" dominant-baseline="central" class="mySvg__text">info</text>
</svg>
This way you could apply shared css styles but also select individual elements according to their global index.

How do I get the fill color of an svg element without rendering it?

I want to access a svg element online which I have the URL for but without showing it on the screen. The svg has only one fill color. Is there a way with javascript to grab this color? ("#013299")
<svg width="50px" height="50px" viewBox="0 0 50 50" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="mysvg" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<rect id="Background" fill="#013299" x="0" y="0" width="50" height="50"></rect>
</g>
</svg>
You can use fetch to download the SVG and parse it as text, then set the innerHTML of a new DIV to that text, and then it'll be just like any other SVG in a div, except you don't need to append it to the DOM anywhere:
const container = document.createElement('div');
fetch(SVG_URL)
.then(response => response.text())
.then(text => {
container.innerHTML = text;
const fillColor = container.getElementById('Background').getAttribute('fill');
})
.catch(console.error);
The console.error can be changed to your own error handling callback if the SVG can't be downloaded for some reason.
You can easily get the background fill using basic DOM manipulation:
let backgroundFill = document.getElementById("Background").getAttribute("fill")

How to switch position of D3 SVG elements

I have some SVG elements on my page that I created with D3. All are children of one parent SVG. Each contains some other D3 elements like paths and text. On the click of a button, I want two of these child SVGs to switch positions, so they move up or down on the page (all are placed above/below each other).
I already tried creating groups ("g") instead of the child SVGs and accessing/changing their positions. However, I can't seem to access the y position of the element.
I also tried using "insertAfter" but this only works with divs, not with SVGs (however, I'm looking for a similar behaviour).
$(".move_up").click(function() {
var svg = $("#second_child"); //ID is actually retrieved from an attribute of the button
svg.insertBefore(svg.prev()); //does obviously not work
});
HTML for move up button (one per child SVG exists):
<a class="move_up">
<span class="grey glyphicon glyphicon-chevron-up" title="Move up"></span>
</a>
HTML for SVG:
<div>
<svg id="parent">
<svg id="first_child"
<path></path>
<rect></rect>
<text></text>
...
</svg>
<svg id="second_child"
<path></path>
<rect></rect>
<text></text>
...
</svg>
<rect></rect>
<text></text>
...
</svg>
</div>
I want the first and second child SVGs to switch positions, when the move up (or respectively a move down) button is used.
This is what I ended up doing:
let group = $("#first_group");
let next_group = $('#second_group");
let diff = 58; // Height of a group
let translate_y = 0;
let translate_y_next = 0;
if (next_group.offset()) {
if (group.attr("transform")) {
let string = group.attr("transform");
translate_y = parseInt(string.substring(string.indexOf("(")+1, string.indexOf(")")).split(",")[1]);
}
if (prev_group.attr("transform")) {
let string_next = prev_group.attr("transform");
translate_y_next = parseInt(string_next.substring(string_next.indexOf("(")+1, string_next.indexOf(")")).split(",")[1]);
}
group.attr("transform", `translate(0, ${translate_y + diff})`);
next_group.attr("transform", `translate(0, ${translate_y_next - diff})`);
}
Works similar for a "Move up" button. Just make sure to change the sign in the last two lines!
May not be super elegant, but does the job.
You are using an SVG as wrapper and the positions are different to html. In SVG You need to define the X and Y position.
let ids = ['ex1', 'ex2', 'ex3', 'ex4', 'ex5']
let btn = document.getElementById('move')
const sortArrayAsYouWish = (array) => {
array.sort(() => Math.random() - 0.5);
}
const changeOrder = () => {
let posY = 35
sortArrayAsYouWish(ids) // change order
ids.forEach((id, i) => {
let $el = document.getElementById(id)
$el.style.transform = `translate(0, ${posY*i}px)`
})
}
btn.onclick = changeOrder
changeOrder()
svg {
width: 500px;
height: 340px;
border: solid 1px #ccc;
}
g {
transition: transform 0.4s;
}
text {
fill: #fff;
text-anchor: middle;
}
#ex2 rect {
fill: blue;
}
#ex3 rect {
fill: yellow;
}
#ex4 rect {
fill: red;
}
#ex5 rect {
fill: cyan;
}
<div><button id="move">Move</button></div>
<svg>
<g id="ex1">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 1</text>
</g>
<g id="ex2">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 2</text>
</g>
<g id="ex3">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 3</text>
</g>
<g id="ex4">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 4</text>
</g>
<g id="ex5">
<rect width="120" height="30" x="0" y="0" />
<text x="60" y="15">Hello example 5</text>
</g>
</svg>

Change fill on circle, within an SVG, within an object

So I chose to create an object to put an SVG object into, because I want to replicate it 100 times or more. Here is the SVG:
<svg xmlns="http://www.w3.org/2000/svg" class="ballsvg" viewBox="0 0 98 98">
<g>
<circle class="changeCircle" cx="50%" cy="51%" r="48%" stroke="red" stroke-width="3" fill="blue" />
<text class="changeText" text-anchor="middle" x="50%" y="65%" font-family="Verdana" font-size="30" fill="white" ><tspan>0</tspan> </text>
</g>
</svg>
And my JavaScript that builds out the objects to the div #svgMain:
for(var i=0; i<addingTo; i++){
var obj = '<div class="clickBall" data-id="'+String(i+1+totalBalls)+'" ><object class="ball" data="ball.svg"></object></div>'
appendHTMLNodes += obj;
}
Then I found online how to dynamically change the text on that object:
$( "object.ball" ).each(function( index ) {
var textNode = $(this.contentDocument);
textNode = textNode.find(".changeText");
textNode = textNode.children().first();
textNode.text(index+1);
});
Then I wanted to change the fill color when the item is clicked:
$(document).on('click', '.clickBall', function(event) {
var mySVG = $(this).find("object.ball");
mySVG = $(mySVG.contentDocument);
mySVG = mySVG.find(".changeCircle");
console.log(mySVG);
mySVG.attr("fill", "#FF0000");
});
But I cannot figure out how to get the right element, because it never changes the fill, even though I could change the text before.
Thanks in advance for any help.
You can directly select the .changeCircle and use $(this) property to fill svg. Try this code:
$(".changeCircle").on('click', function() {
$(this).css({ fill: "#ff0000" });
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<svg xmlns="http://www.w3.org/2000/svg" class="ballsvg" viewBox="0 0 98 98">
<g>
<circle class="changeCircle" cx="50%" cy="51%" r="48%" stroke="red" stroke-width="3" fill="blue" />
<text class="changeText" text-anchor="middle" x="50%" y="65%" font-family="Verdana" font-size="30" fill="white" ><tspan>0</tspan> </text>
</g>
</svg>
UPDATE:
tcoulson wanted to update SVG inside a object tag. Here is how you can access SVG object:
$(function(){
$("#obj").contents().find(".changeCircle").attr({"fill":"lime"});
console.log($("#obj").contents().find(".changeCircle"));
});

Mult-line Svg tooltip

I have created numerous polygon shapes in SVG format. and grouped them together.
When the user hovers over the group a tooltip box appear. I have used ecmascript.
What i am looking to do is make the tooltip box a multiline box.
Any ideas how to do this?
<script type="text/ecmascript">
<![CDATA[
function init(evt)
{
if ( window.svgDocument == null )
{
svgDocument = evt.target.ownerDocument;
}
tooltip = svgDocument.getElementById('tooltip');
tooltip_bg = svgDocument.getElementById('tooltip_bg');
}
function ShowTooltip(evt, mouseovertext)
{
tooltip.setAttributeNS(null,"x",evt.clientX+17);
tooltip.setAttributeNS(null,"y",evt.clientY+14);
tooltip.firstChild.data = mouseovertext;
tooltip.setAttributeNS(null,"visibility","visible");
length = tooltip.getComputedTextLength();
tooltip_bg.setAttributeNS(null,"width",length+8);
tooltip_bg.setAttributeNS(null,"x",evt.clientX+14);
tooltip_bg.setAttributeNS(null,"y",evt.clientY+1);
tooltip_bg.setAttributeNS(null,"visibility","visibile");
}
function HideTooltip(evt)
{
tooltip.setAttributeNS(null,"visibility","hidden");
tooltip_bg.setAttributeNS(null,"visibility","hidden");
}
]]>
</script>
<SVG>
<g onmousemove="ShowTooltip(evt, 'GHANA 2000')" onmouseout="HideTooltip(evt)">
<path fill="#EEEEEE" d="M250,0c47,0,85.183,10.506,125,33.494L250,250V0z"/>
<path id="score" d="M250,57c36.284,0,65.761,8.11,96.5,25.857L250,250V57z"/>
<path fill="none" stroke="#FFFFFF" stroke-width="2" stroke-miterlimit="10" d="M250,0c47,0,85.183,10.506,125,33.494L250,250V0z"/>
<text transform="matrix(1 0 0 1 283.9883 92.0024)" fill="#FFFFFF" font-family="'WalkwayBlack'" font-size="16">62</text>
</g>
<rect class="tooltip_bg" id="tooltip_bg" x="0" y="0" width="55" height="17" visibility="hidden"/>
<text class="tooltip" id="tooltip" x="0" y="0" visibility="hidden">Tooltip</text>
<SVG>
You could create more <text> elements or <tspan> elements within the existing text element, put subsequent lines of text in the additional elements and then position each text/tspan below the previous ones by giving them the same x attribute value and increase the y attribute by the height of the bounding box of the previous line.
Alternatively and more simply, just create <title> elements as children of the polygon shape elements and put the multiline text directly within that and the tooltips will be shown by most UAs as multiline tooltips, it certainly works in Firefox and Opera. Here's a html example but it should work just as well with SVG except that SVG has a title element rather than a title attribute.

Categories

Resources