I am writing you because I'm struggling with D3.js (D3-selection) for moving existing elements positions in the SVG.
I've seen a lot of examples for creating new elements, but here, my elements are already created.
I have this structure in my svg :
<g id='maingroup' class='main'>
<title>titlemain</main>
<text id='text'>textContent</text>
</g>
<g id='othergroup1' class='othergroup'>
<title>othergroup1</title>
<text id='text1'>textContent1</text>
<ellipse fill="#ac08b6" cx="198.5" cy="25" rx="27" ry="18"></ellipse>
</g>
<g id='othergroup2' class='othergroup'>
<title>othergroup2</title>
<text id='text2'>textContent2</text>
<ellipse fill="#23d5f6" cx="198.5" cy="25" rx="27" ry="18"></ellipse>
</g>
My goal is to move all ellipses into the main group to get :
<g id='maingroup' class='main'>
<title>titlemain</main>
<text id='text'>textContent</text>
<ellipse fill="#ac08b6" cx="198.5" cy="25" rx="27" ry="18"></ellipse>
<ellipse fill="#23d5f6" cx="198.5" cy="25" rx="27" ry="18"></ellipse>
</g>
I have succeeded to do this with a part in D3.js and a part with DOM manipulation.
svg = d3.select('svg')
allellipses = svg.selectAll('ellipse').nodes()
for (ell of allellipses) {document.querySelector('.main').append(ell)}
Is there a way to do it only with D3.js ? I would like to replace document.querySelector by a D3.js function. At least, to know and understand how it's working to append existing elements.
But maybe it's more efficient to use only simple DOM manipulation for this operation.
selection.remove() removes nodes from the DOM returning a selection of those nodes.
selection.append() can be provided a function that appends a given node.
So we can remove the nodes, use the nodes as a data array and enter/append the ellipses we remove:
var ellipse = svg.selectAll("ellipse").remove();
svg.select("#maingroup")
.selectAll(null)
.data(ellipse.nodes())
.enter()
.append(d=>d);
var svg = d3.select("svg");
var ellipse = svg.selectAll("ellipse").remove();
svg.select("#maingroup")
.selectAll(null)
.data(ellipse.nodes())
.enter()
.append(d=>d);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg>
<g id='maingroup' class='main'>
<title>titlemain</main>
<text id='text'>textContent</text>
</g>
<g id='othergroup1' class='othergroup'>
<title>othergroup1</title>
<text id='text1'>textContent1</text>
<ellipse fill="#ac08b6" cx="198.5" cy="25" rx="30" ry="20"></ellipse>
</g>
<g id='othergroup2' class='othergroup'>
<title>othergroup2</title>
<text id='text2'>textContent2</text>
<ellipse fill="#23d5f6" cx="198.5" cy="25" rx="27" ry="18"></ellipse>
</g>
</svg>
Of course we skip the data binding and use selection.remove() with selection.each() to append the removed elements to a different parent:
var ellipse = svg.selectAll("ellipse")
.remove()
.each(function() {
svg.select("#maingroup").append(()=>this);
})
var svg = d3.select("svg");
var ellipse = svg.selectAll("ellipse")
.remove()
.each(function() {
svg.select("#maingroup").append(()=>this);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg>
<g id='maingroup' class='main'>
<title>titlemain</main>
<text id='text'>textContent</text>
</g>
<g id='othergroup1' class='othergroup'>
<title>othergroup1</title>
<text id='text1'>textContent1</text>
<ellipse fill="#ac08b6" cx="198.5" cy="25" rx="30" ry="20"></ellipse>
</g>
<g id='othergroup2' class='othergroup'>
<title>othergroup2</title>
<text id='text2'>textContent2</text>
<ellipse fill="#23d5f6" cx="198.5" cy="25" rx="27" ry="18"></ellipse>
</g>
</svg>
The use of selection.insert() rather than selection.append() can provide a bit more flexibility in terms of ordering the appended elements as well.
Lastly, adapting your code with minimal change, we can just use selection.append() with a function returning the node in combination with the for loop:
var ellipses = svg.selectAll("ellipse").remove();
for(ellipse of ellipses) svg.select("#maingroup").append(()=>ellipse);
var svg = d3.select("svg");
var ellipses = svg.selectAll("ellipse").remove();
for(ellipse of ellipses) svg.select("#maingroup").append(()=>ellipse);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
<svg>
<g id='maingroup' class='main'>
<title>titlemain</main>
<text id='text'>textContent</text>
</g>
<g id='othergroup1' class='othergroup'>
<title>othergroup1</title>
<text id='text1'>textContent1</text>
<ellipse fill="#ac08b6" cx="198.5" cy="30" rx="30" ry="20"></ellipse>
</g>
<g id='othergroup2' class='othergroup'>
<title>othergroup2</title>
<text id='text2'>textContent2</text>
<ellipse fill="#23d5f6" cx="198.5" cy="25" rx="27" ry="18"></ellipse>
</g>
</svg>
Of course having a selection of the main group prior will be more efficient than selecting it every iteration, and plain javascript should tend to be more performative.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With