3
\$\begingroup\$

This is the code I currently have for adding various elements inside the svg and then inside a circle. On the text element I added 3 attributes that are the same. But I haven't found a way to remove this to minimize code.

var node = svg.selectAll("circle")
  .data(nodes)
  .enter()
  .append('g')
  .append("circle")
  .style("fill", function (d) {
    return 'rgb(108,181,205)';
  }).on("click", function (d, i) {
    if (i < 5) {
      d.radius *= 1.1;
      d3.select(this).attr("r", d.radius);
      force.resume();
    }
  }).call(force.drag);


// Adding the text
svg.selectAll('g')
  .append('text')
  .attr("text-anchor", "middle")
  .attr("alignment-baseline", "before-edge")
  .attr('font-family', 'FontAwesome')
  .attr('font-size', function(d) { return '1.9em'})
  .append('tspan')
  .attr('x', '0')
  .attr('dy', '-0.2em')
  .style('fill', 'white')
  .style('display', function(d, i) {
    if (i < 5) {
      return 'block';
    }
    else {
      return 'none';
    } 
  })
  .text(function(d, i) {
    return circlesText[i].icon;
  });

svg.selectAll('g')
  .append('text')
  .attr("text-anchor", "middle")
  .attr("alignment-baseline", "before-edge")
  .attr('font-family', 'FontAwesome')
  .attr('font-size', function(d) { return '0.9em'})
  .append('tspan')
  .attr('x', '0')
  .attr('dy', '0.9em')  
  .style('fill', 'white')
  .style('display', function(d, i) {
    if (i < 5) {
      return 'block';
    }
    else {
      return 'none';
    } 
  })
  .text(function(d, i) {
      return circlesText[i].text[0];
  });

svg.selectAll('g')
  .append('text')
  .attr("text-anchor", "middle")
  .attr("alignment-baseline", "before-edge")
  .attr('font-family', 'FontAwesome')
  .attr('font-size', function(d) { return '0.9em'})
  .append('tspan')
  .attr('x', '0')
  .attr('dy', '1.8em')  
  .style('fill', 'white')
  .style('display', function(d, i) {
    if (i < 5) {
      return 'block';
    }
    else {
      return 'none';
    } 
  })
  .text(function(d, i) {
      return circlesText[i].text[1];
  });  

svg.selectAll('g')
  .append('text')
  .attr("text-anchor", "middle")
  .attr("alignment-baseline", "before-edge")
  .attr('font-family', 'FontAwesome')
  .attr('font-size', function(d) { return '1.9em'})
  .append('tspan')
  .attr('x', '0')
  .attr('dy', '1.8em')  
  .style('fill', 'white')
  .style('display', function(d, i) {
    if (i < 5) {
      return 'block';
    }
    else {
      return 'none';
    } 
  })
  .text(function(d, i) {
      return '\uF056';
  });
\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

The simpler option is to create a local function or two.

var appendText = function(fontSize) {
  return svg.selectAll('g')
    .append('text')
    .attr("text-anchor", "middle")
    .attr("alignment-baseline", "before-edge")
    .attr('font-family', 'FontAwesome')
    .attr('font-size', function(d) { return fontSize });
}

var appendTextSpan = function(fontSize) {
  return appendText(fontSize).
    .append('tspan')
    .style('fill', 'white')
    .style('display', function(d, i) {
      return (i < 5) ? 'block' : 'none';
    });
}

appendTextSpan('1.9em')
  .attr('x', '0')
  .attr('dy', '-0.2em')
  .text(function(d, i) {
    return circlesText[i].icon;
  });

appendTextSpan('0.9em')
  .attr('x', '0')
  .attr('dy', '0.9em')  
  .text(function(d, i) {
      return circlesText[i].text[0];
  });

appendTextSpan('0.9em')
  .attr('x', '0')
  .attr('dy', '1.8em')  
  .text(function(d, i) {
      return circlesText[i].text[1];
  });  

appendTextSpan('1.9em')
  .attr('x', '0')
  .attr('dy', '1.8em')  
  .text(function(d, i) {
      return '\uF056';
  });

The more complex one, but arguably better, would be to inject some CSS into the SVG such that you can just use

svg.selectAll('g')
  .append('text')
  .attr('class', 'label')
  .append('tspan')
  ...

How easy that is to do depends on how much control you have of the enclosing context.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks. I just needed an idea on how to improve this code. Thanks a lot \$\endgroup\$
    – Monica
    Nov 28, 2016 at 13:26
1
\$\begingroup\$

Before answering your question, here is an important advice: name your selections. So, instead of this:

svg.selectAll('g').etc...

Do:

var foo = svg.selectAll('g').etc...

This is a good practice because, when you name your selection you can reference it later:

foo.on("baz", etc...

That way, you can easily rebind data, create transitions, filter, translate elements and several other changes by just using the selection's name.

Back to your question:

The most idiomatic way to substantially reduce the amount of code here is using each, which, according to the API:

Invokes the specified function for each selected element, in order, being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element (nodes[i]).

So, in your case, after doing...

var foo = svg.selectAll('g')
    .append('text')
    .attr("text-anchor", "middle")
    .attr("alignment-baseline", "before-edge")
    .attr('font-family', 'FontAwesome')
    .attr('font-size', function(d) { return '1.9em'});

... you would need to do:

foo.each(function(){
    d3.select(this).append("tspan")
    //etc...
});

Unfortunately, we cannot just add the each to your code the way it is right now because, as you can see in the example above, the foo selection has just a single <text> element. But the fix is simple: create your <text> elements inside an "enter" selection, upon which you call the each.

var foo = svg.selectAll(null)
    .data(d3.range(3))//this creates the array [0, 1, 2]
    .enter()
    .append("text")//3 text elements will be appended
    //etc...

That way, you'll have a way shorter, DRY code, with individual <text> elements, each one having how many <tspan>s you want.

Here is a very simple demo:

var svg = d3.select("svg");
var data = ["Main text number 1", "Another main text", "The last main text"];

var texts = svg.selectAll(null)
  .data(data)
  .enter()
  .append("g")
  .attr("transform", function(_, i) {
    return "translate(" + (10 + i * 180) + ",30)"
  })
  .append("text")
  .style("font-family", "Helvetica")
  .style("font-size", "16px")
  .text(String);
  
texts.each(function() {
  d3.select(this).append("tspan")
    .attr("x", 0)
    .attr("dy", "1.3em")
    .style("font-size", "12px")
    .text("This is a tspan");
  d3.select(this).append("tspan")
    .attr("x", 0)
    .attr("dy", "1.3em")
    .style("font-size", "10px")
    .text("And this is a smaller tspan")
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600"></svg>

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.