menu

Questions & Answers

d3.js looping through array not rendering to create multiple tspans elements

Building a D3 property that renders text. My loop prints to console.log() just fine and the loop before works ok too. Below we are looking at the label array[] that I will loop through, loop through links then through label e.g.

Data:

links: [
      {
        id: 1,
        source: 1,
        target: 2,
        type: 'Foo',
        label: ['Jim', 'Phil'],
        since: 2010,
      }
]

D3 snippet:

const edgelabels = zoomContainer
  .selectAll('.edgelabel')
  .data(links)
  .enter()
  .append('text')
  .style('pointer-events', 'none')
  .attr('class', 'edgelabel')
  .attr('id', function (d, i) {
    return 'edgelabel' + i;
  })
  .attr('font-size', 10)
  .attr('fill', '#aaa');
edgelabels
  .append('textPath')
  .attr('xlink:href', function (d, i) {
    return '#edgepath' + i;
  })
  .style('text-anchor', 'middle')
  .style('pointer-events', 'none')
  .attr('startOffset', '50%')
  .append('tspan')
  .text((d) => d.label)
 // .text((d) => d.label.forEach((x) => x))
  .attr('x', -10)
  .attr('dx', 10)
  .attr('dy', 22);

The above renders this:

<text class="edgelabel" id="edgelabel0" font-size="10" fill="#aaa" style="pointer-events: none;" transform="rotate(0)">

<textPath xlink:href="#edgepath0" startOffset="50%" style="text-anchor: middle; pointer-events: none;">

<tspan x="-10" dx="10" dy="22">
Jim,Phil
</tspan>

</textPath>

</text>

But what I want is this:

<text class="edgelabel" id="edgelabel0" font-size="10" fill="#aaa" style="pointer-events: none;" transform="rotate(0)">

<textPath xlink:href="#edgepath0" startOffset="50%" style="text-anchor: middle; pointer-events: none;">

<tspan x="-10" dx="10" dy="22">
Jim
</tspan>

<tspan x="-10" dx="10" dy="22">
Phil
</tspan>

</textPath>

</text>

So the label array items are in their own <tspan>.

Ive replaced .text((d) => d.label) with .text((d) => d.label.forEach((x) => { return x;})) and that seems to console.log() correctly, separating "Jim" and "Phil". However, the <tspan> remains empty and not adding another <tspan> for additional items in the array.

I've looked into using .each for the approach as that would be preferable to d3 so it seems and maybe that was part of the issue? Any help from somebody with more d3 experience would be appreciated. thanks

Here is a demo (lines 196/197)

Answers(1) :

No need to use any looping techniques... hope this helps somebody else.

const edgelabels = zoomContainer
      .selectAll('.edgelabel')
      .data(links)
      .enter()
      .append('text')
      .style('pointer-events', 'none')
      .attr('class', 'edgelabel')
      .attr('id', function (d, i) {
        return 'edgelabel' + i;
      })
      .attr('font-size', 10)
      .attr('fill', '#aaa');
    edgelabels
      .append('textPath')
      .attr('xlink:href', function (d, i) {
        return '#edgepath' + i;
      })
      .style('text-anchor', 'middle')
      .style('pointer-events', 'none')
      .attr('startOffset', '50%')
      .selectAll('tspan.textPath')
      .data((d, i) => d.label)
      .enter()
      .append('tspan')
      .attr('class', 'text')
      .text((d) => d)
      .attr('x', -10)
      .attr('dx', 10)
      .attr('dy', 22);