Bas
Bas

Reputation: 38

How can I merge or join d3 selections from different d3.selects?

In my code I have a problem with merging selections. The condensed code is as follows.

<!DOCTYPE html>
<html lang="en">
  <head>
    <script src="https://d3js.org/d3.v6.min.js"></script>
  </head>
  <body>
    <svg>
      <g id="g1" class="testg"><text x="20" y="20">bla1</text></g>
    </svg>
    <svg>
      <g id="g2" class="testg"><text x="20" y="20">bla2</text></g>
    </svg>

    <script>
      let s0 = d3.selectAll("g");
      s0.style("fill", "red"); // works, all are red;

      let s1 = d3.select("g#g1");
      let s2 = d3.select("g#g2");

      s1.style("fill", "green"); //works g1 green
      s2.style("fill", "yellow"); //works g2 yellow

      let s3 = s1.merge(s2);
      s3.style("fill", "pink"); // does not work: only g1 is pink
    </script>
  </body>
</html>

I would expect this to work, but it doesn't. How can I merge s1 and s2 into one selection? In my live code, they are not easily selectable by attributes.

Edit: Of course I can work around the problem like this:

  s1.classed("select_for_merge", true);
  s2.classed("select_for_merge", true);
  s3 = d3.selectAll("g.select_for_merge");
  s3.classed("select_for_merge", false);

It just seems unwieldy, considering I already have multiple selections, only from different parts of the application. Giving them all extra classes is unwieldy and hampers performance, in the live code it consists of many elements in 4 dimensions.

Upvotes: 2

Views: 822

Answers (1)

altocumulus
altocumulus

Reputation: 21578

The behavior you witnessed is expected and it is well documented:

# selection.merge(other) · Source

This method is not intended for concatenating arbitrary selections, however: if both this selection and the specified other selection have (non-null) elements at the same index, this selection’s element is returned in the merge and the other selection’s element is ignored.

This explains why only the #g1 element is selected after the merge operation.

Although there are multiple ways doing this, my preferred and, maybe the most elegant way, was made possible with the release of D3 v6. Selections now implement an iterator and are thus iterable themselves making them accessible for many language feature introduced by ES6! In general, concatenating arrays can easily be implemented using spread syntax:

const concatArray = [...array1, array2];

Since selection are now iterable you can concatenate their respective nodes the same way:

const concatNodes = [...selection1, ...selection2];

Apart from being iterable, selections can also be created by passing in an iterable object which results in the following code for merging two selections:

const mergedSelection = d3.selectAll([...selection1, ...selection2]);

Given your example this can be implemented as follows:

const s0 = d3.selectAll("g");
s0.style("fill", "red"); // works, all are red;

const s1 = d3.select("g#g1");
const s2 = d3.select("g#g2");

const s3 = d3.selectAll([...s1, ...s2]);
s3.style("fill", "blue"); // works, all are blue
<script src="https://d3js.org/d3.v6.js"></script>
<svg>
  <g id="g1" class="testg"><text x="20" y="20">bla1</text></g>
</svg>
<svg>
  <g id="g2" class="testg"><text x="20" y="20">bla2</text></g>
</svg>

Upvotes: 2

Related Questions