Sp21nk
Sp21nk

Reputation: 45

D3.js Moving elements in the svg structure

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

Answers (1)

Andrew Reid
Andrew Reid

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

Related Questions