Position text element at left side of parent svg - javascript

Inside my SVG elment is a g element which contains several rect, circle & text nodes.
<svg id="familyTreeSvg" xmlns="http://www.w3.org/2000/svg">
<g id="familyTreeSvgG" transform="translate(-3640.998879446216,-3805.319191946216) scale(1)">
<rect class="lifeline" x="3999.329665532318" y="3911.819191946216" width="441.67051879637665" height="20"
rx="10"
ry="10" id="lifelineRectSamanthaBishopI10" style="fill: red; opacity: 1;"></rect>
<rect class="lifeline" x="3981.819191946216" y="3986.819191946216" width="459.1809923824785" height="40" rx="10"
ry="10" id="lifelineRectOliverLionI1" style="fill: rgb(255, 255, 255); opacity: 1;"></rect>
<g class="name">
<text x="4006.819191946216" y="4011.819191946216" style="fill-opacity: 1;">Oliver Lion</text>
</g>
<g class="name">
<text x="4024.329665532318" y="3926.819191946216" style="fill-opacity: 1;">Samantha Bishop</text>
</g>
<g class="node M" id="rootNode" transform="translate(3991.819191946216,4006.819191946216)">
<circle class="node" r="20" style="fill: rgb(255, 255, 255);"></circle>
</g>
<g class="node F" id="SamanthaBishopI10" transform="translate(4009.32958984375, 3921.819091796875)">
<circle class="node" r="13" style="fill: rgb(255, 255, 255);"></circle>
</g>
</g>
</svg>
I use D3js to zoom and drag the g-element.
When a name (text-element) would translate outside the left side I want it to stick to the left side rather than translating to the left, so that the name is still visible.
My current attempt is:
function movePersonNames(person, lifeLine) {
let boundingBox = lifeLine.getBoundingClientRect();
let lifeLineWidth = boundingBox.width;
let lifeLineCoordinate = boundingBox.x;
let lifeLineVisible = lifeLineWidth + lifeLineCoordinate;
if (lifeLineCoordinate < 0 && lifeLineVisible > 0) {
let moveText = "translate(" + lifeLineCoordinate * -1 + ",0)";
d3.select(person).attr("transform", moveText);
}
}
but than some names are still partly invisible, while others are too far away:
It's even worse when the scale is not 1.
I also tried to use the inverted d3.event.transform.x:
d3.select(person).attr("transform", "translate(" + d3.event.transform.x * -1 + ",0)");
but than all the names jump too far and are no longer visible.
That's how i want it:
How can I achieve this?
Let me know if you need further information.

Related

SVG - how to calculate x and y coordinates after changing scale?

I have such SVG:
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="630"
height="430"
>
<rect
rx="0"
ry="0"
width="100%"
height="100%"
fill="rgba(92, 95, 108, 1.0)"
/>
<line x1="0" y1="176" x2="630" y2="176" style="stroke:rgb(255,0,0);stroke-width:2" />
<g
transform="scale(0.2) translate(1300.370746612549 818.9994964599609)"
style="outline: black solid 2px;"
>
<path
fill="rgba(198, 199, 205, 1.0)"
d="M414.578,62.68L93.932,122.853c-22.91,4.448-33.326,15.876-33.326,35.457v221.359 c0,19.581,6.1,39.759,32.654,35.457l154.561-29.073l-9.639,63.268l110.875-82.335l65.695-12.019 c23.564-4.135,36.643-15.875,36.643-35.457V98.137C451.395,78.556,434.16,62.68,414.578,62.68z M354.711,235.385 c-5.002,53.956-55.426,97.697-112.623,97.697c-57.203,0-99.508-43.741-94.506-97.697c5.004-53.969,55.428-97.711,112.623-97.711 C317.408,137.674,359.721,181.416,354.711,235.385z M215.049,282.888c-3.109-20.511-6.363-56.128-9.063-76.632 c-1.416-10.769,4.857-14.876,15.098-12.905c28.838,5.537,82.398,16.59,82.398,16.59c11.623,1.416,17.576,11.9,6.578,20.643 c-19.873,15.793-56.912,48.904-78.846,61.547C224.207,296.168,216.229,290.638,215.049,282.888z"
/>
</g>
</svg>
As you can see, there is a group element with a scale of 0.2. Also, this group is "touching" the line. This is fine. However, after changing the scale to 0.25, the group element suddenly moves into different location.
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="630"
height="430"
>
<rect
rx="0"
ry="0"
width="100%"
height="100%"
fill="rgba(92, 95, 108, 1.0)"
/>
<line x1="0" y1="176" x2="630" y2="176" style="stroke:rgb(255,0,0);stroke-width:2" />
<g
transform="scale(0.25) translate(1300.370746612549 818.9994964599609)"
style="outline: black solid 2px;"
>
<path
fill="rgba(198, 199, 205, 1.0)"
d="M414.578,62.68L93.932,122.853c-22.91,4.448-33.326,15.876-33.326,35.457v221.359 c0,19.581,6.1,39.759,32.654,35.457l154.561-29.073l-9.639,63.268l110.875-82.335l65.695-12.019 c23.564-4.135,36.643-15.875,36.643-35.457V98.137C451.395,78.556,434.16,62.68,414.578,62.68z M354.711,235.385 c-5.002,53.956-55.426,97.697-112.623,97.697c-57.203,0-99.508-43.741-94.506-97.697c5.004-53.969,55.428-97.711,112.623-97.711 C317.408,137.674,359.721,181.416,354.711,235.385z M215.049,282.888c-3.109-20.511-6.363-56.128-9.063-76.632 c-1.416-10.769,4.857-14.876,15.098-12.905c28.838,5.537,82.398,16.59,82.398,16.59c11.623,1.416,17.576,11.9,6.578,20.643 c-19.873,15.793-56.912,48.904-78.846,61.547C224.207,296.168,216.229,290.638,215.049,282.888z"
/>
</g>
</svg>
My question is: how to update x and y coordinates so that the group is still touching the line?
This is my solution: I'm putting the path inside the g element and I'm scaling the path. I'm using theg.getBBox() values to change the x and y of the <use> element.
let bb = testg.getBBox()
let x = 315 - bb.x - bb.width/2;
let y = 176 - bb.y;
theUse.setAttributeNS(null,"x", x);
theUse.setAttributeNS(null,"y", y);
theRange.addEventListener("input", ()=>{
thePath.setAttributeNS(null, "transform", `scale(${parseInt(theRange.value)/100})`)
let bb = testg.getBBox()
let x = 315 - bb.x - bb.width/2;
let y = 176 - bb.y;
theUse.setAttributeNS(null,"x", x);
theUse.setAttributeNS(null,"y", y);
})
<input id="theRange" type="range" value ="25" /><svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="630"
height="430"
>
<defs>
<g id="testg">
<path id="thePath" transform="scale(.45)"
fill="rgba(198, 199, 205, 1.0)"
d="M414.578,62.68L93.932,122.853c-22.91,4.448-33.326,15.876-33.326,35.457v221.359 c0,19.581,6.1,39.759,32.654,35.457l154.561-29.073l-9.639,63.268l110.875-82.335l65.695-12.019 c23.564-4.135,36.643-15.875,36.643-35.457V98.137C451.395,78.556,434.16,62.68,414.578,62.68z M354.711,235.385 c-5.002,53.956-55.426,97.697-112.623,97.697c-57.203,0-99.508-43.741-94.506-97.697c5.004-53.969,55.428-97.711,112.623-97.711 C317.408,137.674,359.721,181.416,354.711,235.385z M215.049,282.888c-3.109-20.511-6.363-56.128-9.063-76.632 c-1.416-10.769,4.857-14.876,15.098-12.905c28.838,5.537,82.398,16.59,82.398,16.59c11.623,1.416,17.576,11.9,6.578,20.643 c-19.873,15.793-56.912,48.904-78.846,61.547C224.207,296.168,216.229,290.638,215.049,282.888z"
/></g>
</defs>
<rect
rx="0"
ry="0"
width="100%"
height="100%"
fill="rgba(92, 95, 108, 1.0)"
/>
<line x1="0" y1="176" x2="630" y2="176" style="stroke:rgb(255,0,0);stroke-width:2" />
<line x1="315" y1="0" x2="315" y2="100%" style="stroke:rgb(255,0,0);stroke-width:2" />
<use id="theUse" xlink:href="#testg" y="161" />
</svg>
temporarily remove the transform attribute completely
find out the upper left corner of the path element
move the path such that its upper left corner sits at (0, 0)
define a transform attribute that translates the group to the place where you want the upper left corner to sit permanently
to the right in the transform attribute, define the scale of your choice.
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="630"
height="430"
>
<rect
rx="0"
ry="0"
width="100%"
height="100%"
fill="rgba(92, 95, 108, 1.0)"
/>
<line x1="0" y1="176" x2="630" y2="176" style="stroke:rgb(255,0,0);stroke-width:2" />
<g
transform="translate(260 176) scale(0.25)"
style="outline: black solid 2px;"
>
<path transform="translate(-60.6, -62.7)"
fill="rgba(198, 199, 205, 1.0)"
d="M414.578,62.68L93.932,122.853c-22.91,4.448-33.326,15.876-33.326,35.457v221.359 c0,19.581,6.1,39.759,32.654,35.457l154.561-29.073l-9.639,63.268l110.875-82.335l65.695-12.019 c23.564-4.135,36.643-15.875,36.643-35.457V98.137C451.395,78.556,434.16,62.68,414.578,62.68z M354.711,235.385 c-5.002,53.956-55.426,97.697-112.623,97.697c-57.203,0-99.508-43.741-94.506-97.697c5.004-53.969,55.428-97.711,112.623-97.711 C317.408,137.674,359.721,181.416,354.711,235.385z M215.049,282.888c-3.109-20.511-6.363-56.128-9.063-76.632 c-1.416-10.769,4.857-14.876,15.098-12.905c28.838,5.537,82.398,16.59,82.398,16.59c11.623,1.416,17.576,11.9,6.578,20.643 c-19.873,15.793-56.912,48.904-78.846,61.547C224.207,296.168,216.229,290.638,215.049,282.888z"
/>
</g>
</svg>
So, how do you find out the upper left corner of the path element? Load it into a browser and, on the Javascript command line, execute
document.querySelector('path').getBBox()

Whats the correct way to get the bounding box of DOM elements containing svg

I have the following html and get strange results with getBoundingClientRect, if there are svg elements inside:
<html>
<head>
<meta charset="utf-8">
<title>Test</title>
</head>
<body>
<svg>
<g transform="translate(10,10) scale(1)">
<g class="nodes">
<g style="inline" transform="translate(20,20)">
<rect style="stroke: rgb(170, 170, 170); stroke-width: 1; fill: rgb(248, 248, 248);" width="100" height="90"></rect>
<g class="nodeparent">
<rect class="noderect" style="fill: none; stroke: rgb(182, 204, 216); stroke-width: 0;" x="0" y="0" height="20" width="100"></rect>
<text class="nodetext" x="3" y="15">Text 1</text>
</g>
<g class="nodeparent">
<rect class="noderect" style="fill: none; stroke: rgb(221, 185, 172); stroke-width: 0;" x="0" y="22" height="20" width="100"></rect>
<text class="nodetext" x="3" y="37">Test 2</text>
</g>
<g class="nodeparent">
<rect class="noderect" style="fill: none; stroke: rgb(221, 185, 180); stroke-width: 0;" x="0" y="44" height="20" width="100"></rect>
<text class="nodetext" x="3" y="59">Test 3</text>
</g>
<g class="nodebox">
<rect class="noderect" style="fill: rgb(236, 163, 154); stroke: rgb(212, 139, 130); stroke-width: 2;" x="0" y="66" height="20" width="100"></rect>
<text class="nodetext" x="3" y="81">Test 4</text>
<g class="nodeicon">
<svg width="16" height="16" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" x="80" y="68">
<rect height="14" width="14" y="1" x="1" stroke="#888888" stroke-width="1" fill="#ffffff" fill-opacity="0.5"></rect>
<line x1="4" y1="8" x2="12" y2="8" stroke="#888888" stroke-width="2"></line>
</svg>
</g>
</g>
</g>
</g>
</g>
</svg>
</body>
</html>
I get a much greater rectangle than I would expect in Firefox.
When I inspect the objects, the displayed bounding box for the inner svg element is fine, but the surrounding g element (class nodeicon) is outside.
If I remove this g element, the next surrounding g element is outside.
The following picture shows this:
It looks like the offset of the svg is applied twice.
Is getBoundingClientRect the correct way to get the position and size of elements (e.g. the g element with class nodes) for this? Is there something wrong with the HTML or svg element or did I run into a Firefox bug?
I am using the current version of Firefox (58.0 64bit).
A problem that you have here is that the svg tag nested inside the g tag (.nodeicon) is starting a new viewport context. Strictly speaking it should not be nested inside a g tag anyway, but regardless, it isn't really necessary as you're using it as a method of grouping the two elements inside it - which is the purpose of the g tag.
Try removing the svg tag nested inside .nodeicon, and move the coordinates in that svg's x and y attributes to a transform attribute on the g tag.
i.e:
<g class="nodeicon" transform="translate(80, 68)">
<rect height="14" width="14" y="1" x="1" stroke="#888888" stroke-width="1" fill="#ffffff" fill-opacity="0.5"></rect>
<line x1="4" y1="8" x2="12" y2="8" stroke="#888888" stroke-width="2"></line>
</g>

Moving image on scroll through svg path

I want to move object through svg path on scroll=) I was trying to add parts of path on scroll into path, but it still doesn't work. Help!!!=)
https://jsfiddle.net/YuriiBielozertsev/Ltx9ed0L/
<?xml version="1.0"?>
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Draw the outline of the motion path in grey, along with 2 small circles at key points -->
<path d="M10,110 A120,120 -45 0,1 110 10 A120,120 -45 0,1 10,110" stroke="green" stroke-width="2" fill="none" id="theMotionPath"/>
<circle cx="10" cy="110" r="3" fill="#000"/>
<circle cx="110" cy="10" r="3" fill="#000"/>
<!-- Red circle which will be moved along the motion path. -->
<circle cx="0" cy="" r="5" fill="red">
<!-- Define the motion path animation -->
<animateMotion dur="6s" repeatCount="indefinite">
<mpath xlink:href="#theMotionPath"/>
</animateMotion>
</circle>
</svg>
Something like this?
How this works:
When we get a scroll event we:
Calculate how far down the page we are
Convert this percentage to a position on the path using the <path> element functions getTotalLength() and getPointAtLength().
Reposition the dot so that it appears at this point.
function positionTheDot() {
// What percentage down the page are we?
var scrollPercentage = (document.documentElement.scrollTop + document.body.scrollTop) / (document.documentElement.scrollHeight - document.documentElement.clientHeight);
// Get path length
var path = document.getElementById("theMotionPath");
var pathLen = path.getTotalLength();
// Get the position of a point at <scrollPercentage> along the path.
var pt = path.getPointAtLength(scrollPercentage * pathLen);
// Position the red dot at this point
var dot = document.getElementById("dot");
dot.setAttribute("transform", "translate("+ pt.x + "," + pt.y + ")");
};
// Update dot position when we get a scroll event.
window.addEventListener("scroll", positionTheDot);
// Set the initial position of the dot.
positionTheDot();
.verylong {
height: 2000px;
}
svg {
position: fixed;
width: 200px;
height: 200px;
}
<svg viewBox="0 0 120 120" xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Draw the outline of the motion path in grey, along with 2 small circles at key points -->
<path d="M10,110 A120,120 -45 0,1 110 10 A120,120 -45 0,1 10,110" stroke="green" stroke-width="2" fill="none" id="theMotionPath"/>
<circle cx="10" cy="110" r="3" fill="#000"/>
<circle cx="110" cy="10" r="3" fill="#000"/>
<!-- Red circle which will be moved along the motion path. -->
<circle cx="0" cy="0" r="5" fill="red" id="dot"/>
</svg>
<div class="verylong">
</div>

SVG Clear group<g>

Suppose i have an svg element like so
<svg id="svgCanvas" class="pan" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="none">
<g id="viewport">
//Filled with an arbitrary amount of lines and cirles - examples below.
<line x1="632" y1="357.5" x2="682" y2="270.89745962155615" class="line" style="stroke: rgb(128, 128, 128); stroke-width: 1.3px;"></line>
<circle cx="82.08376766398476" cy="367.0988235405059" r="16.5" stroke="blue" fill="white" class="circle"></circle>
</g>
</svg>
how would i go about clearing everything from the that group, whilst also keeping the group element itself?
Just using DOM methods without the need for any framework you could go for:
var el = document.getElementById("viewport");
while (el.firstChild) {
el.removeChild(el.firstChild);
}
document.addEventListener('load', function(){
document.getElementById("viewport").innerHTML = "";
});

Accessing SVG file directly from Javascript code

I have this HTML code, which is invoking my javascript code. The code is for a gauge. In the javascript code, I am trying to access a SVG file, and modifying the needle (of the gauge) to display the desired value. The code is working fine. However, I do not wish to call "object id" in HTML. I want to access SVG file through javascript directly, instead of using object id in HTML. I tried using el.setAttribute('data', 'gauge.svg'); But then svg_doc isn't able to retrieve the SVG image and modify the needle. Any help would be highly appreciated.
PS : I tried my best to be as thorough in explaining the problem. However, please let me know if I am unclear somewhere.
This is Gauge.png image which is embedded in the svg code I have pasted below https://sphotos-b.xx.fbcdn.net/hphotos-snc6/179594_10150982737360698_1827200234_n.jpg
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g name="gauge" width="122px" height="127px">
<image xlink:href="gauging.png" width="122" height="127"/>
<circle id="led" cx="39" cy="76" r="5" style="fill: #999; stroke: none">
<animateColor id="ledAnimation" attributeName="fill" attributeType="css" begin="0s" dur="1s"
values="none;#f88;#f00;#f88;none;" repeatCount="0"/>
</circle>
<g id="needle" transform="rotate(0,62,62)">
<circle cx="62" cy="62" r="4" style="fill: #c00; stroke: none"/>
<rect transform="rotate(-130,62,62)" name="arrow" x="58" y="38" width="8" height="24" style="fill: #c00; stroke: none"/>
<polygon transform="rotate(-130,62,62)" points="58,39,66,39,62,30,58,39" style="fill: #c00; stroke: none"/>
</g>
<text id="value" x="51" y="98" focusable="false" editable="no" style="stroke:none; fill:#fff; font-family: monospace; font-size: 12px"></text>
</g>
</svg>
HTML+Javascript code
<head>
<title>SVG Gauge example</title>
<script>
function update1(){
var scale=100;
var value;
var value1 = 69;
var el=document.getElementById('gauge1');
if (!el) return;
/* Get SVG document from HTML element */
var svg_doc = el.contentDocument;
if (!svg_doc) return;
/* Rotate needle to display given value */
var needle_el = svg_doc.getElementById('needle');
if (!needle_el) return;
/* Calc rotation angle (0->0%, 260->100%) */
value = parseInt(value1);
scale = parseInt(scale);
if (value > scale) value = scale;
var angle = value / scale * 260;
/* On-the-fly SVG transform */
needle_el.setAttribute('transform','rotate('+angle+',62,62)');
}
document.addEventListener('load', update1, true);
</script>
</head>
<div>
<object id="gauge1" type="image/svg+xml" data="gauge.svg" width="127" height="122"/>
</div>
</html>
As robertc already mentioned, you can embed the javascript code into your SVG file:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g name="gauge" width="122px" height="127px">
<image xlink:href="gauging.png" width="122" height="127"/>
<circle id="led" cx="39" cy="76" r="5" style="fill: #999; stroke: none">
<animateColor id="ledAnimation" attributeName="fill" attributeType="css" begin="0s" dur="1s"
values="none;#f88;#f00;#f88;none;" repeatCount="0"/>
</circle>
<g id="needle" transform="rotate(0,62,62)">
<circle cx="62" cy="62" r="4" style="fill: #c00; stroke: none"/>
<rect transform="rotate(-130,62,62)" name="arrow" x="58" y="38" width="8" height="24" style="fill: #c00; stroke: none"/>
<polygon transform="rotate(-130,62,62)" points="58,39,66,39,62,30,58,39" style="fill: #c00; stroke: none"/>
</g>
<text id="value" x="51" y="98" focusable="false" editable="no" style="stroke:none; fill:#fff; font-family: monospace; font-size: 12px"></text>
</g>
<script type="text/javascript">
var scale=100;
var value;
var value1 = 69;
/* Rotate needle to display given value */
var needle_el = document.getElementById('needle');
/* Calc rotation angle (0->0%, 260->100%) */
value = parseInt(value1);
scale = parseInt(scale);
if (value > scale) value = scale;
var angle = value / scale * 260;
/* On-the-fly SVG transform */
needle_el.setAttribute('transform','rotate('+angle+',62,62)');
</script>
</svg>
I've put the code below the actual SVG contents so that the document is already loaded when the script is executed.
Then, you can view the SVG file directly e.g. in Firefox (I've tested it right now).

Categories

Resources