d3 change class of clicked node text - javascript

I'm trying to add a class to the label, that was clicked in my force diagram. For this I use a "click" function, which hands the clicked item to the update function (it updates the class of the item). I tried using this but then the console will report "nodeClicked.childNodes[1].classed is not a function".
Then I googled and tried to use "d3.select(this).select("text");" which does not report an error, nor update the text.
A node is a < g > Element which has two children: a circle and the text (the text I want to give a css class)
If you want to, you can look at my code snippet:
// Toggle children on click.
// Also save information about what was clicked and update the GUI
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
}
else{
d.children = d._children;
d._children = null;
}
// Save which node was just clicked now
nodeClicked = d3.select(this).select("text");
// Save the d3 data of this node
nodeClickedData = d;
}
To change the class I tested both:
$(nodeClicked).addClass("nodeGreenMarked");
and:
nodeClicked.classed("nodeBlueMarked", true);
But none did anything. If I check in the console what content nodeClicked is, it tells me "Array[1]" and when I open it: "0:< text >"

You can add class in two ways.
d3.select(this).select("text").attr("class",className);
OR
d3.select(this).select("text").classed(className,true);
Working Snippet:
d3.selectAll(".node").on("click", function() {
//check if node is already selected
var text = d3.select(this).select("text");
if (text.classed("selectedText")) {
text.classed("selectedText", false);
//Remove class selectedNode
} else {
text.classed("selectedText", true);
//Adds class selectedNode
}
});
.node {
cursor: pointer;
}
.node circle {
fill: #fff;
stroke: steelblue;
stroke-width: 3px;
}
.node text {
font: 12px sans-serif;
}
.link {
fill: none;
stroke: #ccc;
stroke-width: 2px;
}
.selectedText {
fill: red;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
<svg width="960" height="500">
<g transform="translate(120,20)">
<path class="link" d="M0,262.85714285714283C90,262.85714285714283 90,197.1428571428571 180,197.1428571428571"></path>
<path class="link" d="M0,262.85714285714283C90,262.85714285714283 90,328.57142857142856 180,328.57142857142856"></path>
<path class="link" d="M180,197.1428571428571C270,197.1428571428571 270,131.42857142857142 360,131.42857142857142"></path>
<path class="link" d="M180,197.1428571428571C270,197.1428571428571 270,262.85714285714283 360,262.85714285714283"></path>
<g class="node" transform="translate(180,328.5714416503906)">
<circle r="10" style="fill: rgb(255, 255, 255);"></circle>
<text x="13" dy=".35em" text-anchor="start" style="fill-opacity: 1;">Level 2: B</text>
</g>
<g class="node" transform="translate(180,197.14285278320312)">
<circle r="10" style="fill: rgb(255, 255, 255);"></circle>
<text x="-13" dy=".35em" text-anchor="end" style="fill-opacity: 1;">Level 2: A</text>
</g>
<g class="node" transform="translate(0,262.8571472167969)">
<circle r="10" style="fill: rgb(255, 255, 255);"></circle>
<text x="-13" dy=".35em" text-anchor="end" style="fill-opacity: 1;">Top Level</text>
</g>
<g class="node" transform="translate(360,262.8571472167969)">
<circle r="10" style="fill: rgb(255, 255, 255);"></circle>
<text x="13" dy=".35em" text-anchor="start" style="fill-opacity: 1;">Daughter of A</text>
</g>
<g class="node" transform="translate(360,131.42857360839844)">
<circle r="10" style="fill: rgb(255, 255, 255);"></circle>
<text x="13" dy=".35em" text-anchor="start" style="fill-opacity: 1;">Son of A</text>
</g>
</g>
</svg>

D3 did somehow not correctly address the correct element (the text of the group element), no matter which of the possible methods I used (jQuery did somehow also fail). I must have keeping to oversee something really important, though I checked everything like 5 times.
No matter, I found a working solution: by adressing the DOM elements with pure javascript. The command I used to solve this and get the class applied correctly was
nodeClicked = this;
...
nodeClicked.classList.add("greenNode");
This adds the class greenNode to the group element. In my CSS file I wrote .nodeClicked text and put in the necessary CSS. So if you once also have problems addressing the correct element, you can also try to use this method.

Related

SVG: Remove element from svg using javascript or jquery

I add a foreignObject insider g element in svg the html is as follow:
<g id="group" transform="matrix(1,0,0,1,3700,20)">
<rect x="80" y="80" stroke="black" id="occ" style="stroke-width: 10px; stroke: rgb(0, 128, 255);" width="140" height="140" fill="white" class="marked"></rect>
<foreignObject id="fo_1" x="29" y="29" width="18" height="18" transform="scale(4,4)"><div style="color: brown; stroke: rgb(0, 128, 255); stroke-width: 10px;" class="marked"><i class="fa fa-circle"></i></div></foreignObject>
</g>
I added the foreignObject when the rect is clicked with command:
element.parentElement.insertAdjacentHTML('beforeend', '<foreignObject ...>);
But then I fail to remove the foreignObject again if the rect is clicked again. At the moment, I have successfully find the foreignObject element by using ID, but still cannot remove it.
I tried the code like below:
$('svg').find('foreignObject#fo_1').remove();
or this code:
var foreignObj = document.getElementById('fo_1');
var parent = foreignObj.parentNode;
parent.removeChild(foreignObj);
Anyone can help please? Thanks.

Position text element at left side of parent svg

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.

Grab text tag content and replace it with custom text using javascript

Hi Below is my svg section
<g transform="translate(86.91003147398621,-21.306040772755345)" class="nv-label">
<rect style="fill-opacity: 0; stroke-opacity: 0;"></rect>
<text dy=".35em" style="fill-opacity: 1; fill: rgb(85, 85, 85); text-anchor: start;">Test Content</text>
</g>
Here, I need to get the text "Test Content" and replace it with some number, how can I do this with javascript?
Any help would be appreciated.
This could help you, take a look :D
function changeText(){
var svg = document.querySelector('g');
svg.lastElementChild.innerText = "hello World"
}
changeText();
<g transform="translate(86.91003147398621,-21.306040772755345)" class="nv-label">
<rect style="fill-opacity: 0; stroke-opacity: 0;"></rect>
<text dy=".35em" style="fill-opacity: 1; fill: rgb(85, 85, 85); text-anchor: start;">Test Content</text>
</g>
As you have mentioned there are multiple g tags, you can use document.querySelectorAll('g text')[1]; to get the element reference of second text inside g tag and then change the value using textContent. (answer updated based on OP comment)
var textElem = document.querySelectorAll('g text')[1];
textElem.textContent = 1111;
<g transform="translate(86.91003147398621,-21.306040772755345)" class="nv-label">
<rect style="fill-opacity: 0; stroke-opacity: 0;"></rect>
<text dy=".35em" style="fill-opacity: 1; fill: rgb(85, 85, 85); text-anchor: start;">Test Content</text>
</g>
<br/>
<g transform="translate(86.91003147398621,-21.306040772755345)" class="nv-label">
<rect style="fill-opacity: 0; stroke-opacity: 0;"></rect>
<text dy=".35em" style="fill-opacity: 1; fill: rgb(85, 85, 85); text-anchor: start;">Test Content</text>
</g>
<br/>
<g transform="translate(86.91003147398621,-21.306040772755345)" class="nv-label">
<rect style="fill-opacity: 0; stroke-opacity: 0;"></rect>
<text dy=".35em" style="fill-opacity: 1; fill: rgb(85, 85, 85); text-anchor: start;">Test Content</text>
</g>
What I think many other answers are neglecting to give you is advice on how to
set up your SVG so that you can easily find your text element later.
The normal way to do that is to give the text element an id attribute. Then later you can find it by calling getElementById().
var textElem = document.getElementById("mytext");
textElem.textContent = "1234";
<svg>
<text id="mytext" y="100">Test Content</text>
</svg>
There are other ways you could mark your element. You could do it by class:
var allTextElems = document.getElementsByClassName("mytext");
for (var i=0; i< allTextElems.length; i++) {
allTextElems.item(i).textContent = "1234";
};
<svg>
<text class="mytext" y="75">Test Content</text>
<text class="notmytext" y="100">Test Content</text>
<text class="mytext" y="125">Test Content</text>
</svg>
Or you could do it using a data attribute:
var allTextElems = document.querySelectorAll('[data-key=mytext]');
allTextElems.forEach(function(item) {
item.textContent = "1234";
});
<svg>
<text data-key="mytext" y="75">Test Content</text>
<text data-key="notmytext" y="100">Test Content</text>
<text data-key="mytext" y="125">Test Content</text>
</svg>
If you can't modify the SVG for some reason, then the next obvious solution would be to locate the text element using its position in the DOM tree relative to other elements.
// Select the first text element in the group which has class="two"
var textElem = document.querySelector("g.two text:first-child");
textElem.textContent = "1234";
<svg>
<g class="one">
<text y="50">Text in group 1</text>
</g>
<g class="two">
<text id="mytext" y="100">Test Content</text>
<text y="125">Other text in group 2</text>
</g>
</svg>
If you don't know where in the file the target text will be, you only know what the text contains, then you would need to search all the text elements to find the one with the correct text.
// Select the SVG we want to search
// (This is an example of what you would need to do if you had more than one svg on the page)
var mySvg = document.querySelector("#mysvg");
// Find all the text elements in that svg
var allTextElems = document.querySelectorAll("text");
// Check all the text elements found, and if they contain our text
// then replace it with the new text.
allTextElems.forEach(function(item) {
if (item.textContent === "Test Content") {
item.textContent = "1234";
}
});
<svg id="mysvg">
<g class="one">
<text y="50">Text in group 1</text>
</g>
<g class="two">
<text id="mytext" y="100">Test Content</text>
<text y="125">Other text in group 2</text>
</g>
</svg>

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>

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