Detect click inside an svg javascript - javascript

<div id="anim">
<svg>
<defs><clipPath id="__lottie_element_2"></defs>
<g>
<g class="aaaaa"></g>
<g id="bbbbb"></g>
<g id="ccccc"></g>
</g>
</svg>
</div>
Detect when I click on class="aaaa" inside id="anim" and throw an alert. alert("you have pressed class" aaaa)
Detect when I click on id="bbbbb" inside id="anim" and throw an alert. alert("you have pressed id" bbbbb)
thank you
window.addEventListener("load", function () {
let element = document.querySelector("div.anim > svg > g > g").getAttribute("id");
element.addEventListener("click", function() {
alert("You clicked");
});

You can use the closest() function to find the parent <g> that was clicked. The <g> does not itself take any click events, so you rely on the content of the <g>.
And you just need one event listener for the entire thing. e.target is the element that was clicked (depending on how you use pointer-events if that is something you can use in your case).
window.addEventListener("load", function() {
let element = document.querySelector("div#anim");
element.addEventListener("click", e => {
let gclicked = e.target.closest('g');
alert(`You have pressed class ${gclicked.getAttribute('class')}`);
});
})
<div id="anim">
<svg viewBox="0 0 30 10" width="300">
<g class="aaaaa">
<circle r="5" cx="5" cy="5" fill="red"/>
</g>
<g class="bbbbb">
<circle r="5" cx="15" cy="5" fill="blue"/>
</g>
<g class="ccccc">
<circle r="5" cx="25" cy="5" fill="orange"/>
</g>
</svg>
</div>

window.addEventListener("load", function() {
// `div.anim` means div with class anim
// why would you use `getAttribute` here?
let element = document.querySelector("div#anim > svg > g > g");
element.addEventListener("click", function() {
alert("You clicked");
});
})
<div id="anim">
<svg>
<!-- you forgot to close `<clipPath>` -->
<defs><clipPath id="__lottie_element_2"></clipPath></defs>
<g>
<g class="aaaaa"></g>
<g id="bbbbb"></g>
<g id="ccccc"></g>
</g>
</svg>
</div>

Related

onmouseenter event triggered on any movement in an SVG use element

When the "mouseenter" event is triggered on the green circle a 1 is printed in the console, when it is triggered on the blue circle a 2 is printed in the console. Note that when the mouse enters the green circle, 1 is printed exactly once. When the mouse enters the blue circle any mouse movement within the circle causes a 2 to be printed.
var gr = document.getElementById("greenOne");
var bl = document.getElementById("blueOneCopy");
gr.onmouseenter = function() {gr.parentNode.appendChild(gr); console.log(1);};
bl.onmouseenter = function() {bl.parentNode.appendChild(bl); console.log(2);};
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<svg height="500px" width="500px">
<defs>
<circle id="blueOne" cx="100" cy="100" r="50" style="fill: #0091EA"></circle>
</defs>
<use id="blueOneCopy" href="#blueOne"></use>
<circle id="greenOne" cx="150" cy="100" r="50" style="fill: #00C853"></circle>
</svg>
</body>
</html>
I've managed to get blueOneCopy to behave similarly to greenOne by checking whether the relatedTarget property of the mouse event isn't blueOne before executing the rest of the function. I'm not sure if this is the best solution, but it appears to work.
var gr = document.getElementById("greenOne");
var bl = document.getElementById("blueOneCopy");
gr.onmouseenter = function() {
gr.parentNode.appendChild(gr);
console.log(1);
};
bl.onmouseenter = function(e) {
if (e.relatedTarget.id !== "blueOne") {
bl.parentNode.appendChild(bl);
console.log(2);
}
};
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<svg height="500px" width="500px">
<defs>
<circle id="blueOne" cx="100" cy="100" r="50" style="fill: #0091EA"></circle>
</defs>
<use id="blueOneCopy" href="#blueOne"></use>
<circle id="greenOne" cx="150" cy="100" r="50" style="fill: #00C853"></circle>
</svg>
</body>
</html>
Another option is to check whether the element is the last one in the SVG before moving it there. See below
I expect that this issue will have something to do with how events behave when Shadow DOM elements are attached to the DOM. Since Chrome and Firefox behave the same, I expect it is behaviour described in the spec. I'm just too lazy right now to check :)
Note that, with both mine and RFoxtea's solutions, the mouseenter event is still being fired. We are just detecting the first occurrence. Be careful you don't slow down your app by putting code in the event that unnecessarily runs every time.
var gr = document.getElementById("greenOne");
var bl = document.getElementById("blueOneCopy");
gr.onmouseenter = bringToFront;
bl.onmouseenter = bringToFront;
function bringToFront(evt) {
let el = evt.target;
if (el.nextSibling != null) {
el.parentNode.appendChild(el);
console.log(el.id == 'greenOne' ? 1 : 2);
}
};
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
</head>
<body>
<svg height="500px" width="500px">
<defs>
<circle id="blueOne" cx="100" cy="100" r="50" style="fill: #0091EA"></circle>
</defs>
<use id="blueOneCopy" href="#blueOne"></use>
<circle id="greenOne" cx="150" cy="100" r="50" style="fill: #00C853"></circle>
</svg>
</body>
</html>

How to make multiple animations in one page with Lazy Line Painter

I'm making a card where I want to put svgs with this drawing effect. I found a very good tool that makes this for me, I download any svg from the internet and it makes the effect for me and gives me the code to use on my page.
It works great but when I put more than one svg on the page, the first one stops working. It's only working if I only use one svg.
Am I doing something wrong? Is there any tool that also does this?
Note that on the first card, svg is gone.
https://pastebin.com/DhR8kfGU
<div class="card d-xl-flex text-center d-flex justify-content-center align-items-center">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="casa" x="0px" y="0px" viewBox="0 0 58.365 58.365" style="enable-background:new 0 0 58.365 58.365;" xml:space="preserve" width="512px" height="512px" class="lazy-line-painter" data-llp-composed="true">
<g>
<path d="M57.863,26.632L29.182,0L0.502,26.632c-0.404,0.376-0.428,1.009-0.052,1.414c0.374,0.404,1.009,0.427,1.413,0.052 l3.319-3.082v33.349h16h16h16V25.015l3.319,3.082c0.192,0.179,0.437,0.267,0.681,0.267c0.269,0,0.536-0.107,0.732-0.319 C58.291,27.641,58.267,27.008,57.863,26.632z M23.182,56.365v-16c0-3.309,2.691-6,6-6s6,2.691,6,6v16H23.182z M51.182,56.365h-14 v-16c0-4.411-3.589-8-8-8s-8,3.589-8,8v16h-14V23.158l22-20.429l22,20.429V56.365z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#004D88" data-llp-id="casa-0" data-llp-duration="2000" data-llp-delay="0" fill-opacity="0" style="" />
</g>
</svg>
<p class="text-center d-flex flex-column m-xl-3 title">
Residencial
</p>
<p class="card-information">Gás para sua casa</p>
<p class="m-1 card-information">P 13</p>
<p class=" card-information">P 45</p>
Saiba mais
</div>
<div class="card d-xl-flex text-center d-flex justify-content-center align-items-center">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="tent" x="0px" y="0px" viewBox="0 0 483.32 483.32" style="enable-background:new 0 0 483.32 483.32;" xml:space="preserve" width="512px" height="512px" class="lazy-line-painter" data-llp-composed="true">
<g>
<g>
<g>
<rect x="341.664" y="465.66" width="16" height="16" data-original="#000000" class="active-path" data-old_color="#000000" fill="#004D88" data-llp-id="tent-0" data-llp-duration="890" data-llp-delay="0" fill-opacity="0" style="" />
</g>
</g>
<g>
<g>
<rect x="313.674" y="338.617" transform="matrix(0.8805 -0.4741 0.4741 0.8805 -150.0898 200.0408)" width="16" height="118.12" data-original="#000000" class="active-path" data-old_color="#000000" fill="#004D88" data-llp-id="tent-1" data-llp-duration="890" data-llp-delay="890" fill-opacity="0" style="" />
</g>
</g>
<g>
<g>
<path d="M96.008,231.316l11.312-11.312l-45.656-45.656v-25.376l34.344,34.344l11.312-11.312l-48-48l-0.056,0.056 c-2.961-3.123-7.892-3.255-11.016-0.294c-0.085,0.08-0.167,0.162-0.248,0.246l-48,48l11.312,11.312l34.352-34.352v25.376 L0.008,220.004l11.312,11.312l34.344-34.344v25.376L0.008,268.004l11.312,11.312l34.344-34.344v25.376L0.008,316.004 l11.312,11.312l34.344-34.344V481.66h16V292.972l34.344,34.344l11.312-11.312l-45.656-45.656v-25.376l34.344,34.344l11.312-11.312 l-45.656-45.656v-25.376L96.008,231.316z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#004D88" data-llp-id="tent-2" data-llp-duration="890" data-llp-delay="1780" fill-opacity="0" style="" />
</g>
</g>
<g>
<g>
<path d="M472.008,263.316l11.312-11.312l-45.656-45.656v-25.376l34.344,34.344l11.312-11.312l-48-48l-0.056,0.056 c-2.961-3.123-7.893-3.255-11.016-0.294c-0.085,0.08-0.167,0.162-0.248,0.246l-48,48l11.312,11.312l34.352-34.352v25.376 l-45.656,45.656l11.312,11.312l34.344-34.344v25.376l-45.656,45.656l11.312,11.312l34.344-34.344v76.688l-16.8-35.416 c-1.316-2.788-4.117-4.571-7.2-4.584h-104v-60.688l34.344,34.344l11.312-11.312l-45.656-45.656v-25.376l34.344,34.344 l11.312-11.312l-45.656-45.656v-25.376l34.344,34.344l11.312-11.312l-45.656-45.656v-25.376l34.344,34.344l11.312-11.312l-48-48 l-0.056,0.056c-2.961-3.123-7.893-3.255-11.016-0.294c-0.085,0.08-0.167,0.162-0.248,0.246l-48,48l11.312,11.312l34.352-34.352 v25.376l-45.656,45.656l11.312,11.312l34.344-34.344v25.376l-45.656,45.656l11.312,11.312l34.344-34.344v25.376l-45.656,45.656 l11.312,11.312l34.344-34.344v60.688h-112c-2.257,0.013-4.4,0.992-5.888,2.688c-0.473,0.473-0.874,1.012-1.192,1.6l-80,152 c-2.05,3.914-0.539,8.749,3.375,10.799c1.143,0.599,2.415,0.912,3.705,0.913h240v-16h-75.2l-71.552-136H392.6l64.424,136h-83.36 v16h96c4.418-0.017,7.986-3.612,7.97-8.03c-0.004-1.174-0.267-2.333-0.77-3.394l-39.2-82.808V276.972l34.344,34.344l11.312-11.312 l-45.656-45.656v-25.376L472.008,263.316z M157.664,465.66H98.912l58.752-111.624V465.66z M232.416,465.66h-58.752V354.036 L232.416,465.66z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#004D88" data-llp-id="tent-3" data-llp-duration="890" data-llp-delay="2670" fill-opacity="0" style="" />
</g>
</g>
<g>
<g>
<path d="M153.664,1.66c-4.418,0.001-7.999,3.583-7.998,8.002c0,2.121,0.843,4.154,2.342,5.654 c8.262,7.425,13.601,17.556,15.056,28.568c1.516,13.506-8.203,25.684-21.709,27.2c-1.825,0.205-3.667,0.205-5.491,0l0.024-0.024 c-11.012-1.455-21.143-6.794-28.568-15.056c-3.125-3.124-8.19-3.123-11.314,0.002c-1.499,1.5-2.342,3.534-2.342,5.654 c0,33.137,26.863,60,60,60s60-26.863,60-60S186.801,1.66,153.664,1.66z M193.287,80.838 c-10.592,21.871-36.908,31.015-58.778,20.423c-8.904-4.312-16.095-11.499-20.412-20.401c6.371,3.432,13.381,5.515,20.592,6.12 c12.063,1.097,23.998-3.169,32.632-11.664c13.512-14.532,15.773-36.253,5.544-53.256 C194.735,32.652,203.878,58.968,193.287,80.838z" data-original="#000000" class="active-path" data-old_color="#000000" fill="#004D88" data-llp-id="tent-4" data-llp-duration="890" data-llp-delay="3560" fill-opacity="0" style="" />
</g>
</g>
</g>
</svg>
<p class="text-center m-xl-3 title">
outdoor
</p>
<p class="">Lorem Ipsum.</p>
<p class="m-1 card-information"></p>
<p class="card-information">P 2</p>
Saiba mais
</div>
<script type="text/javascript">
(function() {
document.onreadystatechange = () => {
if (document.readyState === 'complete') {
/**
* Setup your Lazy Line element.
* see README file for more settings
*/
let el = document.querySelector('#tent');
let myAnimation = new LazyLinePainter(el, {
"ease": "easeLinear",
"strokeWidth": 4,
"strokeOpacity": 1,
"strokeColor": "004d88",
"strokeCap": "square"
});
myAnimation.paint();
}
}
})();
// svg prepared using Lazy Line Composer
// http://lazylinepainter.info/#composer
let wrap = document.querySelector("#tent");
let el = document.querySelector("#tent");
let myAnimation = new LazyLinePainter(el, {
ease: "easeInOutQuad"
});
// Timeline Events
myAnimation.on("start", event => {
wrap.classList.remove("tl-complete");
});
myAnimation.on("complete", event => {
wrap.classList.add("tl-complete");
});
// Path Events
myAnimation.on("start:llpface-0", event => {
event.el.classList.remove("path-complete");
});
myAnimation.on("complete:llpface-0", event => {
event.el.classList.add("path-complete");
});
el.addEventListener("click", paint, false);
function paint() {
myAnimation.paint();
}
paint();
</script>
<script type="text/javascript">
(function() {
document.onreadystatechange = () => {
if (document.readyState === 'complete') {
/**
* Setup your Lazy Line element.
* see README file for more settings
*/
let el = document.querySelector('#casa');
let myAnimation = new LazyLinePainter(el, {
"ease": "easeLinear",
"strokeWidth": 1,
"strokeOpacity": 1,
"strokeColor": "004d88",
"strokeCap": "square"
});
myAnimation.paint();
}
}
})();
// svg prepared using Lazy Line Composer
// http://lazylinepainter.info/#composer
let wrap = document.querySelector("#casa");
let el = document.querySelector("#casa");
let myAnimation = new LazyLinePainter(el, {
ease: "easeInOutQuad"
});
// Timeline Events
myAnimation.on("start", event => {
wrap.classList.remove("tl-complete");
});
myAnimation.on("complete", event => {
wrap.classList.add("tl-complete");
});
// Path Events
myAnimation.on("start:llpface-0", event => {
event.el.classList.remove("path-complete");
});
myAnimation.on("complete:llpface-0", event => {
event.el.classList.add("path-complete");
});
el.addEventListener("click", paint, false);
function paint() {
myAnimation.paint();
}
paint();
</script>
http://lazylinepainter.info/

Add a prefix to mutliple elements from the parent attribute

I'm trying to figure out how to add a unique prefix to ID's and other reference links.
I have multiple identical SVGs on a page. These SVGs serve as a wrapper (they're devices - laptop, phone, etc.) which have image link that is inserted into the SVG after load. The problem is, the ID's for these svgs are identical so they all conflict with each other. What I'm trying to do (but am open to better solutions) is to insert a unique ID to each id,xlink:href, url(#, etc. but pass over the <image> href attribute and fill and stroke attributes.
EDIT: I added/tweaked the code provided by #Temani, which got me closer to my desired result, however, I'm now getting all the ID's added to each SVG element. Formatted the code to be executable.
Desired output:
<div class="device" data-screen="[[IMAGE TO USE]]" data-name"[[UNIQUE ID]]">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="635" height="420" viewBox="0 0 635 420">
<defs>
<path id="[[UNIQUE ID]]-path-1"/>
<rect id="[[UNIQUE ID]]-path-3" />
<pattern id="[[UNIQUE ID]]-pattern-4">
<use transform="scale(13.1875)" xlink:href="#[[UNIQUE ID]]-image-5"/>
</pattern>
<image id="[[UNIQUE ID]]-image-5" href="[[IMAGE LINK THAT IS INSERTED AFTER LOAD]]"/>
</defs>
<g fill="none" fill-rule="evenodd">
<rect fill="#fff" stroke="#2D8EFF" />
<g >
<mask id="[[UNIQUE ID]]-mask-2" fill="#fff">
<use xlink:href="#[[UNIQUE ID]]-path-1"/>
</mask>
<g mask="url(#[[UNIQUE ID]]-mask-2)">
<mask id="mask-6" fill="#fff">
<use xlink:href="#[[UNIQUE ID]]-path-3"/>
</mask>
</g>
</g>
</g>
</svg>
</div>
Right now - both ID's (first and second) gets added to each SVG element. I also
$('.device').each(function() {
//we get the needed id using $(this) that refer to actual device
var id = $(this).data('name');
//we check all the element with ID
$(this).find("path, rect, pattern, image, mask").each(function() {
//now (this) refer to the actual element
if($(this).attr("id"))
$(this).attr("id", id+"-"+$(this).attr("id"));
});
//we update the <use>
$(this).find("use").each(function() {
//now (this) refer to the actual element
$(this).attr("xlink:href","#"+id+"-"+$(this).attr("xlink:href").substring(1));
});
// Also, hoping to combine g[mask], u[fill], etc into 1 function.
// Basically any attribute that starts with "url(#"
$(this).find("g[mask^='url']").each(function() {
$(this).attr("mask","url(#"+id+"-"+$(this).attr("mask").substring(5));
});
$(this).find("use[fill^='url']").each(function() {
$(this).attr("fill","url(#"+id+"-"+$(this).attr("fill").substring(5));
});
$(this).find("use[filter^='url']").each(function() {
$(this).attr("filter","url(#"+id+"-"+$(this).attr("filter").substring(5));
});
// grab data-screen value
var data = $(this).data('screen');
// replace this with the link inside svg
if (data != '') {
$(this).find("svg defs image").attr("href", data).attr("xlink:href", data);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="device" data-name="first" data-screen="image-1.png">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
<defs>
<rect id="w-circle-stroke-a" width="24" height="24"/>
<path id="w-circle-stroke-b" d="M12,2 C17.52,2 22,6.48 22,12 C22,17.52 17.52,22 12,22 C6.48,22 2,17.52 2,12 C2,6.48 6.48,2 12,2 Z M12,3.81818182 C7.48415409,3.81818182 3.81818182,7.48415409 3.81818182,12 C3.81818182,16.5158459 7.48415409,20.1818182 12,20.1818182 C16.5158459,20.1818182 20.1818182,16.5158459 20.1818182,12 C20.1818182,7.48415409 16.5158459,3.81818182 12,3.81818182 Z M10.5553177,13.4773237 L15.155405,8.80967806 C15.5597962,8.4027095 16.222261,8.39598875 16.6350615,8.79466684 C16.6382917,8.79778661 16.6600317,8.81952282 16.7002813,8.85987545 C17.0999062,9.26113743 17.0999062,9.90402237 16.7002813,10.3052843 L10.5553177,16.5 L7.29971874,13.2228714 C6.90252847,12.8240541 6.8997633,12.1859262 7.29348277,11.7837778 L7.33224151,11.7441893 C7.73340831,11.3344341 8.39555055,11.3228774 8.8111776,11.7183766 C8.81566955,11.722651 9.39704957,12.3089667 10.5553177,13.4773237 Z"/>
<image id="image-5" href="image-to-be-inserted.jpg"/>
</defs>
<g fill="none" fill-rule="evenodd">
<mask id="w-circle-stroke-c" fill="#fff">
<use xlink:href="#w-circle-stroke-b"/>
</mask>
<g fill="#2D8EFF" mask="url(#w-circle-stroke-c)">
<rect width="24" height="24"/>
</g>
<use fill="url(#pattern-4)" xlink:href="#path-3"/>
<use fill="#000" filter="url(#filter-10)" xlink:href="#path-9"/>
</g>
</svg>
</div>
<div class="device" data-name="second" data-screen="image-1.png">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
<defs>
<rect id="w-circle-stroke-a" width="24" height="24"/>
<path id="w-circle-stroke-b" d="M12,2 C17.52,2 22,6.48 22,12 C22,17.52 17.52,22 12,22 C6.48,22 2,17.52 2,12 C2,6.48 6.48,2 12,2 Z M12,3.81818182 C7.48415409,3.81818182 3.81818182,7.48415409 3.81818182,12 C3.81818182,16.5158459 7.48415409,20.1818182 12,20.1818182 C16.5158459,20.1818182 20.1818182,16.5158459 20.1818182,12 C20.1818182,7.48415409 16.5158459,3.81818182 12,3.81818182 Z M10.5553177,13.4773237 L15.155405,8.80967806 C15.5597962,8.4027095 16.222261,8.39598875 16.6350615,8.79466684 C16.6382917,8.79778661 16.6600317,8.81952282 16.7002813,8.85987545 C17.0999062,9.26113743 17.0999062,9.90402237 16.7002813,10.3052843 L10.5553177,16.5 L7.29971874,13.2228714 C6.90252847,12.8240541 6.8997633,12.1859262 7.29348277,11.7837778 L7.33224151,11.7441893 C7.73340831,11.3344341 8.39555055,11.3228774 8.8111776,11.7183766 C8.81566955,11.722651 9.39704957,12.3089667 10.5553177,13.4773237 Z"/>
<image id="image-5" href="image-to-be-inserted.jpg"/>
</defs>
<g fill="none" fill-rule="evenodd">
<mask id="w-circle-stroke-c" fill="#fff">
<use xlink:href="#w-circle-stroke-b"/>
</mask>
<g fill="#2D8EFF" mask="url(#w-circle-stroke-c)">
<rect width="24" height="24"/>
</g>
<use fill="url(#pattern-4)" xlink:href="#path-3"/>
<use fill="#000" filter="url(#filter-10)" xlink:href="#path-9"/>
</g>
</svg>
</div>
The issue is that you don't have to use $('.device') when inside and you need to refer to $(this) and since you have 2 each() nested you need to pay attention to the scope of this.
$('.device').each(function() {
//we get the needed id using $(this) that refer to actual device
var id = $(this).data('name');
//we check all the element with ID
$(this).find("path, rect, pattern, image, mask").each(function() {
//now (this) refer to the actual element
if($(this).attr("id"))
$(this).attr("id", id+"-"+$(this).attr("id"));
});
//we update the <use>
$(this).find("use").each(function() {
//now (this) refer to the actual element
$(this).attr("xlink:href","#"+id+"-"+$(this).attr("xlink:href").substring(1));
});
// grab data-screen value
var data = $(this).data('screen');
// replace this with the link inside svg
if (data != '') {
$(this).find("svg defs image").attr("href", data).attr("xlink:href", data);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="device" data-screen="[[IMAGE TO USE]]" data-name="[[UNIQUE ID]]">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="635" height="420" viewBox="0 0 635 420">
<defs>
<path id="path-1"/>
<rect id="path-3" />
<pattern id="pattern-4">
<use transform="scale(13.1875)" xlink:href="#image-5"/>
</pattern>
<image id="image-5" href="[[IMAGE LINK THAT IS INSERTED AFTER LOAD]]"/>
</defs>
<g fill="none" fill-rule="evenodd">
<rect fill="#fff" stroke="#2D8EFF" />
<g >
<mask id="mask-2" fill="#fff">
<use xlink:href="#path-1"/>
</mask>
<g mask="url(#mask-2)">
<mask id="mask-6" fill="#fff">
<use xlink:href="#path-3"/>
</mask>
</g>
</g>
</g>
</svg>
</div>

Adding child elements to specific nodes in a force-directed graph using d3js

I'd like to add different child elements to my nodes depending on the node type. Therefore the node has an attribute called type. All nodes should consist of a g element with the dependent child elements.
I tried this by using D3s filter functionality but i'm stuck as my code doesn't add the child elements only once per node, but adds the wanted child elements multiple times (the same amount as i have nodes). So i guess i'm doing something wrong with the selection.
The nodes and links of my graph change over time, so what i did is to store the selection first and when a node gets added to self.nodes i call the draw function (I'll leave out the link code).
self.domNodes = this.svg.append('g').attr('class', 'nodes').selectAll('.node')
function draw() {
self.domNodes = self.domNodes.data(self.nodes, (node) => node.id)
self.domNodes.exit().remove()
// all nodes
self.domNodes.enter()
.append('g')
.attr('class', (node) => `node ${node.type}`)
.merge(self.domNodes)
// contributions
self.domNodes.filter((d) => d.type === 'contribution')
.append('circle')
.attr('r', 4)
.attr('fill', 'blue')
// persons
self.domNodes.filter((d) => d.type === 'person')
.append('other elements and attributes...')
self.simulation.nodes(self.nodes)
self.simulation.force('link').links(self.links)
self.simulation.alpha(1).restart()
}
What works is, that it does differentiate between person and contribution and adds the elements i want specifically for this type, but it doesn't add only one per g node, but it adds a multiple of those (the number of nodes i have) into every g node. If i keep calling the draw function it will append more and more circles to my g elements.
<svg>
<g>
<g class="nodes">
<g class="node contribution" transform="translate(466, 442)">
<circle r="4" fill="blue"></circle>
<circle r="4" fill="blue"></circle>
<circle r="4" fill="blue"></circle>
</g>
<g class="node contribution" transform="translate(466, 442)">
<circle r="4" fill="blue"></circle>
<circle r="4" fill="blue"></circle>
<circle r="4" fill="blue"></circle>
</g>
<g class="node contribution" transform="translate(466, 442)">
<circle r="4" fill="blue"></circle>
<circle r="4" fill="blue"></circle>
<circle r="4" fill="blue"></circle>
</g>
<g class="node person" transform="translate(400, 200)">
<someotherthings></someotherthings>
<someotherthings></someotherthings>
</g>
<g class="node person" transform="translate(400, 200)">
<someotherthings></someotherthings>
<someotherthings></someotherthings>
</g>
</g>
</g>
</svg>
What am i doing wrong here? I only want the circle and other elements only append once per node.
<svg>
<g>
<g class="nodes">
<g class="node contribution" transform="translate(466, 442)">
<circle r="4" fill="blue"></circle>
</g>
<g class="node contribution" transform="translate(466, 442)">
<circle r="4" fill="blue"></circle>
</g>
<g class="node contribution" transform="translate(466, 442)">
<circle r="4" fill="blue"></circle>
</g>
<g class="node person" transform="translate(400, 200)">
<someotherthings></someotherthings>
</g>
<g class="node person" transform="translate(400, 200)">
<someotherthings></someotherthings>
</g>
</g>
</g>
</svg>
Any help is appreciated.
After reading selection.data on the d3 wiki again, i finally got it working.
I merged my notes beforehand, so my selection included the enter and update nodes. What i did now was first to create the enter nodes, then do the selections and filters on them and merge them afterwards.
function draw() {
self.domNodes = self.domNodes.data(self.nodes, (node) => node.id)
self.domNodes.exit().remove()
// all nodes
const enterNodes = self.domNodes.enter()
.append('g')
.attr('class', (node) => `node ${node.type}`)
// contributions
enterNodes.filter((d) => d.type === 'contribution')
.append('circle')
.attr('r', 4)
.attr('fill', 'blue')
// persons
enterNodes.filter((d) => d.type === 'person')
.append('other elements and attributes...')
self.domNodes = self.domNodes.merge(enterNodes)
self.simulation.nodes(self.nodes)
self.simulation.force('link').links(self.links)
self.simulation.alpha(1).restart()
}

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"));
});

Categories

Resources