0
    var data = [
            {"name": "Lincoln", "pid":1, "sex": "M"},
            {"name": "Tad", "pid":2, "sex":"M"},
            {"name": "Mary", "pid":3, "sex": "F"},
          ];
    
    var nodes = svg.append("g")
          .selectAll("rect")
          .selectAll("circle")
          .data(information.descendants())
          .enter()
          .append(function(d){
            getPerson(d.data.child).sex === "M" ? "rect" : "circle"
          })

I would like to set either a circle or a rectangle based on the gender of the person. I do not know if there is a proper convention for setting these conditions. I don't know how to properly switch between the two since if its a male and a "rect" then the .attr("x") and .attr("y") needs to be set. If it is the "circle" then the .attr("cx") and .attr("cy") and .attr("r") need to be set as well.

information.descendants() is based on a d3.stratify() passed into a tree structure (d3.tree()).

djo
  • 119
  • 2
  • 14

1 Answers1

4

There are numerous ways to achieve this. The first option below uses only rectangles, the second and third option use svg paths (all three options simplify positioning, modifiction of selection, changing shape from one to the other.)

The fourth option is an append where the elements vary between rect and circle.

Round some Rectangles

If your shapes are circle and square, perhaps the easiest is to use rectangles with rx, ry properties so that some rectangles appear like rectangles, and others like circles:

var data = [
  {x: 100, y: 100, circle: true},
  {x: 200, y: 100, circle: false}
]

var width = 20;

d3.select("body")
  .append("svg")
  .selectAll(null)
  .data(data)
  .enter()
  .append("rect")
  .attr("x",d=>d.x)
  .attr("y",d=>d.y)
  .attr("rx",d=>d.circle?width/2:0)
  .attr("ry",d=>d.circle?width/2:0)
  .attr("width", width)
  .attr("height",width);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Use D3.symbol

Instead of rects or circles, we could use paths, d3.symbol returns pathing data and has a circle and a rectangle, d3.symbol has a size method if we wish to alter the size (symbols are centered on their centering coordinate, like a circle):

var data = [
  {x: 100, y: 100, circle: true},
  {x: 200, y: 100, circle: false}
]

var width = 20;

d3.select("body")
  .append("svg")
  .selectAll(null)
  .data(data)
  .enter()
  .append("path")
  .attr("transform",d=>"translate("+[d.x,d.y]+")")
  .attr("d", function(d) {
    var symbol = d.circle ? d3.symbolCircle : d3.symbolSquare;
    return d3.symbol().type(symbol).size(300)();
  })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Make your own Paths

Instead of symbols we can just have a function that returns the path data for a circle or a rect, gives you more flexibility than d3.symbol, but loses some of the simplicity of it (such as easy rescaling of symbols):

var data = [
  {x: 100, y: 100, circle: true},
  {x: 200, y: 100, circle: false}
]

var width = 20;

d3.select("body")
  .append("svg")
  .selectAll(null)
  .data(data)
  .enter()
  .append("path")
  .attr("transform",d=>"translate("+[d.x,d.y]+")")
  .attr("d", pather)
  
function pather(d) {
  if(d.circle) {
    return "M10,10m-10,0a10,10 0 1,0 20,0a 10,10 0 1,0 -20,0"
  }
  else {
    return "M0,0L0,20L20,20L20,0Z";
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

Create rects and circles at the same time with one append

selection.append() can take a function - whatever element is returned by the function is appended to the DOM. To make our node, we'll create a detached SVG element and add a circle to that.

var data = [
  {x: 100, y: 100, circle: true},
  {x: 200, y: 100, circle: false}
]

var width = 20;

d3.select("body")
  .append("svg")
  .selectAll(null)
  .data(data)
  .enter()
  .append(makeShape)
  
  
function makeShape(d) {
  // create a temporary svg
  let svg = document.createElementNS(d3.namespaces.svg, "svg")
  // create an element:
  if(d.circle) {
    return d3.select(svg)
      .append("circle")
      .attr("cx",d.x)
      .attr("cy",d.y)
      .attr("r", 10)
      .node() // return node, not selection.
  }
  else {
    return d3.select(svg)
      .append("rect")
      .attr("x",d.x)
      .attr("y",d.y)
      .attr("width", 20)
      .attr("height", 20)
      .node();
  }  
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

But now you have a selection of circles and rectangles - much more awkward to deal with later.

Andrew Reid
  • 37,021
  • 7
  • 64
  • 83