frankenapps
frankenapps

Reputation: 8241

How can I add an external group to an existing svg with d3.js?

Suppose I have created a svg with d3.js like that:

svg = d3.select('.svgContainer').append("svg")
    .attr("width", '100%')
    .attr("height", '100%')
    .call(d3.zoom().on("zoom", function () {
      svg.attr("transform", d3.event.transform)
   }))
   .append("g");

and I now want to add different groups to it (dynamically and from different files). For example that one:

<svg>
    <g id="myGroup">
        <rect width="100" height="100" fill="blue" />
    </g>
</svg>

I know, that I could add the whole svg like that (suppose the file is called test.svg):

d3.xml("test.svg").then(function(xml) { 
    var svgNode = xml.getElementsByTagName("svg")[0];
    svg.node().appendChild(svgNode);
  });

However, this procuces the following DOM:

svg
 -svg
 --myGroup

But because I need to transform my Group with respect to the main svg, I need the following DOM structure:

 svg
 -myGroup
 --(eventually more dynamically added groups)

I have tried the following and got the correct DOM, but the group does not show up inside of my svg:

d3.xml("test.svg").then(function(xml) { 
    var svgGroup = xml.getElementById("myGroup");
    svg.node().append(svgGroup);
  });

EDIT: I found out, this should already work, the problem was I had several critical <defs> inside of my SVG (for example for gradients). I lost them, when I only appended the group. I ended up, wrapping my <g> around the <defs> and everything works fine now.

Upvotes: 3

Views: 1178

Answers (2)

Gerardo Furtado
Gerardo Furtado

Reputation: 102174

Just as a complement to the accepted answer: if you want to a pure D3 solution, you can use selection.append with a function. In your case:

d3.xml(svgfile).then(function(xml) { 
    var svgNode = d3.select(xml).select("#MyGroup");
    svg.append(() => svgNode.node()) ;       
});

Or even shorter:

d3.xml(svgfile).then(function(xml) { 
    svg.append(() => d3.select(xml).select("#MyGroup").node()) ;       
});

Here is a demo using the Wikipedia SVG linked in the other answer:

var svgfile = "https://simple.wikipedia.org/static/images/mobile/copyright/wikipedia-wordmark-en.svg";

var svg = d3.select('body').append("svg")
  .attr("width", '120')
  .attr("height", '20');

d3.xml(svgfile).then(function(xml) {
  var svgNode = d3.select(xml).select("#Wikipedia");
  svg.append(() => svgNode.node());
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Upvotes: 3

vqf
vqf

Reputation: 2628

You are almost there. I have illustrated both ways to add elements in this codepen. The problem with the first approach is that svgNode is not a node, but a nodeList. You can add each node in the list with a loop, as you can see in the code:

...
var toAdd = svgNode.childNodes;
for (var i = 0; i < svgNode.childElementCount; i++){     
  svg.node().appendChild(toAdd[i]);
}
...

Regarding the second approach, I cannot say that there is anything wrong. I have imported a path from a different svg file and it is displayed in the lower part. The only detail that I changed is that I set the size of the svg element explicitly, not as 100%. Is it possible that it was not rendered because it was outside the visible part?

Upvotes: 1

Related Questions