Search code examples
d3.js

D3: How to add a parent and multiple child DOM elements for each data element of a 1D array?


I am having trouble creating a group g, along with two child SVG components (text and rect) for each data entry in a 1D array.

allCorrespondingYValues is an array like [1,2,3,4]

selection.selectAll("g.y-crosshair-tooltip").data(allCorrespondingYValues)
   .join('g')
      .attr("class", "y-crosshair-tooltip")
      .attr("transform", d => "translate(" + (width - margin.right) + "," + (yScale(d)) + ")")
   //.select("text").join('text') // Method 1: Nothing appears, just 1 'g' per data
   //.join("text")                // Method 2: Nothing appears, just 1 'g' per data
   //.append("text")              // Method 3: this will continuously append a new text element inside the parent 'g' every time the crosshair moves and the function this code is contained in is called
   //.selectAll("text").data(d => d).join("text") // Method 4: Throws errors.
   .selectAll("text").data([null]).join("text") // Method 5: This works and creates only 1 child text element for the 'g', but the text is always null because its referencing the null dataset, not the parent dataset.
      .attr("dx", -15) // position relative to its group
      .attr("dy", -15)
      .attr("fill", "black")
      .text(d => "$" + d)

The four methods I tried are in the code above, and the comments next to each explains the problems with them. I am most surprised that Method 1 does not work, because Mike's Selection guide mentions that "select also propagates data from parent to child, whereas selectAll does not" and "Data is bound to elements... Inherited from a parent via append, insert, or select." So I would have expected that by using .select("text").join('text'), I could just reference the data from the parent. Instead, the resulting DOM shows that nothing is added to the parent 'g'.

So my two questions are...

  1. Why doesn't method 1 work?
  2. What is the best-practice way to create a parent and multiple child elements for each data element?

I have found a couple similar stackoverflow questions, but most are older and use enter() instead of join(), or have 2D data arrays, so things like Method 4 would work... whereas for me, I am trying to reference the exact same data from parent to child.

Thank you!


Solution

  • You are attempting to use the "short-hand" version of join which just isn't suitable for your purpose and your attempts just complicate the code. Instead, use the regular version where you can specify explicit functions:

    <!DOCTYPE html>
    
    <html>
      <head>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.8.5/d3.js"></script>
      </head>
    
      <body>
        <svg></svg>
        <script>
          d3.select("svg")
            .attr("width", 300)
            .attr("height", 300)
            .selectAll("g")
            .data([1,2,3,4])
            .join(
              (enter) => {
                const g = enter.append("g")
                  .attr("transform", (d, i) => "translate(" + (i * 16) + ", 20)");
                g.append("text")
                  .text(d => d);
                //<-- append more to the g here...           
              });
        </script>
      </body>
    </html>