skip to Main Content

It looks like when I use the “insert” function, the new element is always inserted as a child – Not as a sibling.

I still don’t understand why this is, since the insert function should actually insert a new element before the specified element – and not in the specified element as a child.

Basically I am trying to create a "rect" element next to the "text" elements

The goal is to put a background color behind my texts in my d3 sankey diagram.

 // NODE TEXT
this.nodes
  .selectAll('text')
  .data(this.data.nodes)
  .join(
    (enter) =>
      enter
        .append('text')
        .text((d) => `${d.name + ': ' + d.value}`)
        .style('fill', '#000000')
        .attr('text-anchor', 'right')
        .attr('x', (d, i) => {
          return d.x0 + (d.x1 - d.x0) / 0.5;
        })
        .attr('y', (d, i) => {
          return d.y0 + (d.y1 - d.y0) / 2;
        })
        .attr('dy', '0.35em')
        .attr('transform', (d, i) => {
          const x = d.x0 + (d.x1 - d.x0) / 2;
          const y = d.y0 + (d.y1 - d.y0) / 2;
          return `rotate(0, ${x}, ${y})`;
        })
        .call(getTextBox)
        .insert('rect', 'text')
        .attr('width', function (d) {
          return d.bbox.width;
        })
        .attr('height', function (d) {
          return d.bbox.height;
        })
        .attr('x', function (d) {
          return d.bbox.x;
        })
        .attr('y', function (d) {
          return d.bbox.y;
        })
        .style('fill', 'grey'),
    (update) =>
      update
        .transition()
        .duration(1000)
        .ease(d3_easeQuadInOut)
        .attr('x', (d, i) => {
          return d.x0 + (d.x1 - d.x0) / 2;
        })
        .attr('y', (d, i) => {
          return d.y0 + (d.y1 - d.y0) / 2;
        })
        .attr('transform', (d, i) => {
          const x = d.x0 + (d.x1 - d.x0) / 2;
          const y = d.y0 + (d.y1 - d.y0) / 2;
          return `rotate(0, ${x}, ${y})`;
        })
  );


function getTextBox(selection) {
  selection.each(function (d) {
    d.bbox = this.getBBox();
  });
}

Here is a picture of the rendered HTML elements from my browser.

Notice that the “rect” element is a child of the text and not a sibling as desired.

HTML Elements

2

Answers


  1. Iterate over data & bind both text/rect in parent wrapper

    Hope this helps : link

    Login or Signup to reply.
  2. The result in the code you have is expected: it happens because the insert is called on a selection of texts, which acts as a parent.

    The first, simple solution, is breaking the chain, so you have a selection.append and a selection.insert. For instance:

    const svg = d3.select("svg"),
      data = d3.range(5).map(e => ({
        value: e
      }));
    
    svg.selectAll(null)
      .data(data)
      .join(enter => {
        enter.append("text")
          .attr("x", 20)
          .attr("y", d => 20 + 30 * d.value)
          .text(d => `text #${d.value}`)
          .call(getTextBox);
    
        enter.insert("rect", "text")
          .attr('width', d => d.bbox.width)
          .attr('height', d => d.bbox.height)
          .attr('x', d => d.bbox.x)
          .attr('y', d => d.bbox.y)
          .style('fill', 'grey')
    
      })
    
    function getTextBox(selection) {
      selection.each(function(d) {
        d.bbox = this.getBBox();
      });
    }
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <svg></svg>

    However, this SVG will have this structure:

    <rect></rect>
    <rect></rect>
    <rect></rect>
    ...
    <text></text>
    <text></text>
    <text></text>
    ...
    

    That said, if you want the following structure instead…

    <rect></rect>
    <text></text>
    <rect></rect>
    <text></text>
    <rect></rect>
    <text></text>
    ...
    

    … you’ll need a bit more elaborated insert. In this example I’m getting the respective text elements by their IDs:

    const svg = d3.select("svg"),
      data = d3.range(5).map(e => ({
        value: e
      }));
    
    svg.selectAll(null)
      .data(data)
      .join(enter => {
        enter.append("text")
          .attr("id", d => `text${d.value}`)
          .attr("x", 20)
          .attr("y", d => 20 + 30 * d.value)
          .text(d => `text #${d.value}`)
          .call(getTextBox);
    
        enter.insert("rect", d => d3.select(`#text${d.value}`).node())
          .attr('width', d => d.bbox.width)
          .attr('height', d => d.bbox.height)
          .attr('x', d => d.bbox.x)
          .attr('y', d => d.bbox.y)
          .style('fill', 'grey')
    
      })
    
    function getTextBox(selection) {
      selection.each(function(d) {
        d.bbox = this.getBBox();
      });
    }
    <script src="https://d3js.org/d3.v7.min.js"></script>
    <svg></svg>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search