Mult-line Svg tooltip - javascript

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.

Related

Extract Text From SVG [duplicate]

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>

Click event coordinates in SVG

This HTML containing SVG:
<div class="container">
<div class="spacer"></div>
<svg>
<g id="polygonGroup" transform="translate(80, 50)">
<polygon points="-60,-10 -35,-30 -10,-10 -10,30 -60,30"></polygon>
<polygon points="10,-10 35,-30 60,-10 60,30 10,30"></polygon>
<polygon class="origin" points="-4,0 0,4 4,0 0,-4"></polygon>
</g>
<g id="textGroup" transform="translate(80, 50)">
<text x="-35" y="10">Text</text>
<text x="35" y="10">Text</text>
</g>
</svg>
</div>
and this simple jQuery click event handler:
function clicked(event) {
console.log(event.offsetX, event.offsetY);
}
$('svg').click(clicked);
as seen here: https://jsfiddle.net/1ht0L8y6/6/ have different behaviors in different browsers:
Chrome: The coordinates are based on the top left of the SVG element, no matter where I click inside the SVG. This is the behavior I want.
Firefox: The coordinates are based on the top left of whatever element I'm in, which may be SVG, polygon, or text.
IE and Edge:
When in the SVG but not in any of its sub-elements, the coordinates
are based on the SVG element.
When in a polygon, the coordinates are
based on the origin of the <g> group, with its translate offset
(i.e., the black diamond). Negative coordinates are possible this way,
unlike in Chrome or Firefox.
I have observed a different behavior for
text elements in these browsers: They would give coordinates based on
the bottom middle of the text element. But I couldn't manage to
reproduce this in the fiddle; in the fiddle text elements behave
the same as polygons in these browsers.
What is a reliable cross-browser way to get the coordinates of the click?
I've added to your code a function to detect the mouse position in SVG.
let svg = document.querySelector('svg')
function clicked(event) {
let m = oMousePosSVG(event);
console.log(m.x,m.y);
}
svg.addEventListener("click", clicked)
function oMousePosSVG(e) {
var p = svg.createSVGPoint();
p.x = e.clientX;
p.y = e.clientY;
var ctm = svg.getScreenCTM().inverse();
var p = p.matrixTransform(ctm);
return p;
}
svg{border:1px solid}
<div class="container">
<div class="spacer"></div>
<svg>
<g id="polygonGroup" transform="translate(80, 50)">
<polygon points="-60,-10 -35,-30 -10,-10 -10,30 -60,30"></polygon>
<polygon points="10,-10 35,-30 60,-10 60,30 10,30"></polygon>
<polygon class="origin" points="-4,0 0,4 4,0 0,-4"></polygon>
</g>
<g id="textGroup" transform="translate(80, 50)" fill="red">
<text x="-35" y="10">Text</text>
<text x="35" y="10">Text</text>
</g>
</svg>
</div>
To read more about mouse detection in SVG I recommend this book: Using SVG with CSS3 and HTML5: Vector Graphics for Web Design
I hope it helps.

Set X and Y value for g element of SVG

I am relatively new in SVG drawing with HTML5.
What I want to do is to make a group of elements in SVG with g element so that all elements inside of that g element can work like a group and all the element's base x and y value can be received from the upper g element.
So, what I have done is something like this-
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<g x="1000" y="1000">
<title>SVG Title Demo example</title>
<rect width="200" height="50"
style="fill:wheat; stroke:blue; stroke-width:1px"/>
<text style="text-anchor: middle;" class="small">My Text</text>
</g>
</svg>
What I expected is all the elements inside the g element will get x="1000" and y="1000" so my expected output is like this-
But I am getting this-
Re-
I don't like to set x and y element in text element. I just want to set relative x and y into the text element if needed, but that should be relative to g element.
Can anyone help me what I need to do to achieve my target with a group in SVG?
<g> elements don't support x or y attributes. You can use a transform instead though.
I've decreased the values from 1000 to 100 as otherwise the output is outside the 500 x 300 canvas of the outer <svg> element.
I've provided additional x and y attributes on the text element so it appears positioned as in your example. If wanted you could put the text itself in a <g> element or an <svg> element.
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(100, 100)">
<title>SVG Title Demo example</title>
<rect width="200" height="50"
style="fill:wheat; stroke:blue; stroke-width:1px"/>
<text x="100" y="30" style="text-anchor: middle;" class="small">My Text</text>
</g>
</svg>
or using an additional <g> element to avoid x and y on the text itself.
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<g transform="translate(100, 100)">
<title>SVG Title Demo example</title>
<rect width="200" height="50"
style="fill:wheat; stroke:blue; stroke-width:1px"/>
<g transform="translate(100, 30)">
<text style="text-anchor: middle;" class="small">My Text</text>
</g>
</g>
</svg>
Alternatively you could use an inner <svg> element instead of a <g> element as that does support x and y attributes
<svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<svg x="100" y="100">
<title>SVG Title Demo example</title>
<rect width="200" height="50"
style="fill:wheat; stroke:blue; stroke-width:1px"/>
<text x="100" y="30" style="text-anchor: middle;" class="small">My Text</text>
</svg>
</svg>

SVG Textpath - animate startOffset (text to slide down SVG path) - in JS

How can I animate the SVG textpath startOffset parameter in WebAnimation/JS (NOT CSS!):
I want to let the text slide down the path...
I tried numbers,%, px with no success.
<svg id="text-on-path-svg" width="400" height="400" style="border:1px solid #00f">
<path id="myPathforText" fill="none" stroke="#000" d="M90,90C90,160 250,160 300,300"/>
<text >
<textpath id="slideText" xlink:href="#myPathforText" startOffset="50%" >Text laid out along a path.</textpath>
</text>
<script type="text/ecmascript">
<![CDATA[
var slideText=document.getElementById("slideText");
var slideTextPlayer=slideText.animate(
[{startOffset:'0%'},
{startOffset:'100%'}],
{duration:3000,delay:0,iterations:Infinity});
]]>
</script>
</svg>
JSFiddle: https://jsfiddle.net/509c8pmj/
Help would be much appreciated.
One way is by placing an animate element inside your textpath element, like this:
<textpath id="slideText" xlink:href="#myPathforText" startOffset="50%" >
Text laid out along a path.
<animate attributeName="startOffset" from="0%" to ="100%" begin="0s" dur="3s" repeatCount="indefinite"/>
</textpath>
You can use repeatCount="indefinite" if you want it to keep looping.
I've been learning some of the SVG + SMIL syntax from articles on https://css-tricks.com (but their site was down for me as I'm writing this.)

Repeatedly selecting groups out of loaded SVG with Snap.svg

I'm kinda new with the whole SVG business, now I'm stumbling upon a problem that I really don't understand.
I'm animating icons at the moment and noticed that repeatedly selecting g's and applying them doesn't work for me. For the sake of the question I've made an example. What I would like it to do is: Load svg > Apply the first 'G' with mask > on click animate the child 'G' upwards > clear > Select a specific 'G' (this time again the first one) > animate it from the bottom to the center
For some reason in stead of grabbing the first 'G' it iterates over the 'G's in the SVG. I've tried this with using Select with Id's, but it's giving me the same troubles.
Sooo what am I doing wrong?
html
<svg id="svg"></svg>
js
var s = Snap("#svg");
s.attr({ viewBox: "0 0 300 300" });
var bigCircle = s.circle(150, 150, 100);
var bigCircle2 = s.circle(150, 150, 100);
bigCircle2.attr({
fill:"none",
stroke: "#0000e6",
strokeWidth: 6
});
bigCircle.attr({
fill: "#fff",
stroke: "#fff",
strokeWidth: 6
});
Snap.load("images/numbers.svg", function (f) {
var apply = function(number){
g = f.select("svg g:nth-child("+number+")");
s.append(g);
g.attr({
mask: bigCircle,
});
p = g.select("g");
};
apply(1); //Please give me the first g in the svg
var g_animate = function(){
p.animate({ transform: 't0,-200' }, 500, mina.easeout, function(){
p.remove();
apply(1); //Let's repeat the first g in the svg
p.attr({transform: 't0,200'});
p.animate({ transform: 't0,0' }, 500, mina.easein)
})
};
$("#svg").click(function(){
g_animate();
});
});
loaded svg
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="300px" height="300px" viewBox="0 0 300 300" enable-background="new 0 0 300 300" xml:space="preserve">
<g>
<g>
<path d="M162.367,212.803v-34.396h-58.358v-11.016l56.04-80.193h18.357v78.068h17.584v13.141h-17.584v34.396H162.367z
M162.367,165.266v-41.932c0-6.57,0.193-13.14,0.58-19.71h-0.58c-3.865,7.343-6.957,12.753-10.436,18.551l-30.724,42.705v0.387
H162.367z"/>
<rect fill="none" width="300" height="300"/>
</g>
</g>
<g>
<g>
<path d="M115.411,193.479c4.83,2.898,15.846,7.73,27.826,7.73c21.642,0,28.599-13.721,28.406-24.35
c-0.193-17.584-16.039-25.119-32.464-25.119h-9.469v-12.754h9.469c12.367,0,28.019-6.377,28.019-21.256
c0-10.049-6.377-18.938-22.029-18.938c-10.048,0-19.71,4.444-25.12,8.309l-4.638-12.367c6.764-4.83,19.517-9.662,33.043-9.662
c24.734,0,35.943,14.687,35.943,29.952c0,13.141-7.924,24.155-23.189,29.759v0.387c15.459,2.898,27.826,14.493,28.02,32.077
c0,20.098-15.846,37.682-45.796,37.682c-14.106,0-26.474-4.443-32.657-8.502L115.411,193.479z"/>
<rect fill="none" width="300" height="300"/>
</g>
</g>
<g>
<g>
<path d="M109.903,213.865v-10.435l13.334-12.947c32.077-30.531,46.764-46.764,46.764-65.7c0-12.754-5.991-24.542-24.735-24.542
c-11.4,0-20.869,5.798-26.666,10.628l-5.411-11.98c8.503-7.149,21.062-12.754,35.362-12.754c27.054,0,38.454,18.551,38.454,36.522
c0,23.188-16.812,41.933-43.285,67.439l-9.854,9.275v0.387h56.231v14.106H109.903z"/>
<rect fill="none" width="300" height="300"/>
</g>
</g>
<g>
<g>
<path d="M154.541,103.043h-0.387l-21.835,11.787l-3.285-12.946l27.439-14.687h14.494v125.605h-16.426V103.043z"/>
<rect fill="none" width="300" height="300"/>
</g>
</g>
</svg>
It won't keep repeating the same g element, as you remove it from the DOM with this...
p.remove();
So next time it selects, it the 2nd initial g will now be the first one.
Edit:
Its also worth looking at what happens with 'append'. If you append an element from a fragment (the data loaded from the file) into the DOM, then that element is no longer in the fragment f. So if you now select a new element, it will be the next g element (as the other one has been removed and added to the DOM). So if you wanted the SAME element, you want to use a css selector on s rather than f.
I'd be tempted to use a selectAll initially on the svg to get all of the g elements into an array and use those to reference them, it may feel more intuitive.

Categories

Resources