Reputation: 45
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.
Upvotes: 3
Views: 148
Reputation: 38151
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.
Upvotes: 3