D3: Render Flare Graph Titles avoiding text overlap

In my D3 Flare Graph, when I have just one label per circle, the title and the labels overlap. I would like to render the inner labels avoiding the overlap with the circle's title, that it rendered in the middle of each circle.

var flareGraph = function(containerId, options, json) {
  options.width = options.width || 1280; // circle width
  options.height = options.height || 800; // circle height
  options.radius = options.radius || 720; // circle radius
  var w = options.width,
    h = options.height,
    r = options.radius,
    x = d3.scale.linear().range([0, r]),
    y = d3.scale.linear().range([0, r]),
    node,
    root;

  var pack = d3.layout.pack()
    .size([r, r])
    .value(function(d) {
      return d.size;
    })

  var vis = d3.select(containerId).insert("svg:svg", "h2")
    .attr("width", w)
    .attr("height", h)
    .append("svg:g")
    .attr("transform", "translate(" + (w - r) / 2 + "," + (h - r) / 2 + ")");

  function zoom(d, i) {
    var k = r / d.r / 2;
    x.domain([d.x - d.r, d.x + d.r]);
    y.domain([d.y - d.r, d.y + d.r]);

    var t = vis.transition()
      .duration(d3.event.altKey ? 7500 : 750);

    t.selectAll("circle")
      .attr("cx", function(d) {
        return x(d.x);
      })
      .attr("cy", function(d) {
        return y(d.y);
      })
      .attr("r", function(d) {
        return k * d.r;
      });

    t.selectAll("text")
      .attr("x", function(d) {
        return x(d.x);
      })
      .attr("y", function(d) {
        return y(d.y);
      })
      .style("opacity", function(d) {
        return k * d.r > 20 ? 1 : 0;
      });

    node = d;
    d3.event.stopPropagation();
  } //zoom

  node = root = json;
  var nodes = pack.nodes(root);
  vis.selectAll("circle")
    .data(nodes)
    .enter().append("svg:circle")
    .attr("class", function(d) {
      return d.children ? "parent" : "child";
    })
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    })
    .attr("r", function(d) {
      return d.r;
    })
    .on("click", function(d) {
      return zoom(node == d ? root : d);
    });

  vis.selectAll("text")
    .data(nodes)
    .enter().append("svg:text")
    .attr("class", function(d) {
      return d.children ? "parent" : "child";
    })
    .attr("x", function(d) {
      return d.x;
    })
    .attr("y", function(d) {
      return d.y;
    })
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .style("opacity", function(d) {
      return d.r > 20 ? 1 : 0;
    })
    .text(function(d) {
      return d.name;
    });

  d3.select(window).on("click", function() {
    zoom(root);
  });

} //displayFlareGraph
var nodes = {
  "name": "TYPES",
  "children": [{
    "name": "TYPE #iPhone",
    "children": [{
      "name": "iPhone6",
      "size": 0.109375
    }]
  }, {
    "name": "TYPE #OS",
    "children": [{
      "name": "macOS",
      "size": 0.140625
    }]
  }, {
    "name": "Info #Android",
    "children": [{
      "name": "Marshmellow",
      "size": 0.11467116357504215
    }, {
      "name": "Lollipop",
      "size": 0.12228604553119728
    }, {
      "name": "Nougat",
      "size": 0.028667790893760536
    }]
  }, {
    "name": "TYPE #Laptop",
    "children": [{
      "name": "MacbookPro",
      "size": 0.14062499999999997
    }, {
      "name": "Macbook",
      "size": 0.0703125
    }, {
      "name": "Surface",
      "size": 0.0703125
    }]
  }, {
    "name": "TYPE #Browser",
    "children": [{
      "name": "Firefox",
      "size": 0.109375
    }]
  }, {
    "name": "Info #iOS",
    "children": [{
      "name": "iOS11",
      "size": 0.0390625
    }]
  }]
}
flareGraph('#flare', {
  width: 600,
  height: 400,
  radius: 400
}, nodes); // update graph
body>svg {
  position: absolute;
  top: -80px;
  left: -160px;
}

text {
  font-size: 11px;
  pointer-events: none;
}

text.parent {
  fill: blue;
}

circle {
  fill: #ddd;
  stroke: #999;
  pointer-events: all;
}

circle.parent {
  fill: lightblue;
  fill-opacity: .1;
  stroke: gray;
}

circle.parent:hover {
  stroke: #ff7f0e;
  stroke-width: .5px;
}

circle.child {
  pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="flare" />

Answers:

Answer

You're telling all the text labels to display at the same x and y position, so of course they're overlapping. You need to update the label code with the conditional position based on whether it has children or not.

    .attr("y", function(d) {
        return d.children? y(d.y): y(d.y+10);
    })

var flareGraph = function(containerId, options, json) {
  options.width = options.width || 1280; // circle width
  options.height = options.height || 800; // circle height
  options.radius = options.radius || 720; // circle radius
  var w = options.width,
    h = options.height,
    r = options.radius,
    x = d3.scale.linear().range([0, r]),
    y = d3.scale.linear().range([0, r]),
    node,
    root;

  var pack = d3.layout.pack()
    .size([r, r])
    .value(function(d) {
      return d.size;
    })

  var vis = d3.select(containerId).insert("svg:svg", "h2")
    .attr("width", w)
    .attr("height", h)
    .append("svg:g")
    .attr("transform", "translate(" + (w - r) / 2 + "," + (h - r) / 2 + ")");

  function zoom(d, i) {
    var k = r / d.r / 2;
    x.domain([d.x - d.r, d.x + d.r]);
    y.domain([d.y - d.r, d.y + d.r]);

    var t = vis.transition()
      .duration(d3.event.altKey ? 7500 : 750);

    t.selectAll("circle")
      .attr("cx", function(d) {
        return x(d.x);
      })
      .attr("cy", function(d) {
        return y(d.y);
      })
      .attr("r", function(d) {
        return k * d.r;
      });

    t.selectAll("text")
      .attr("x", function(d) {
        return x(d.x);
      })
      .attr("y", function(d) {
        return d.children? y(d.y): y(d.y+10);
      })
      .style("opacity", function(d) {
        return k * d.r > 20 ? 1 : 0;
      });

    node = d;
    d3.event.stopPropagation();
  } //zoom

  node = root = json;
  var nodes = pack.nodes(root);
  vis.selectAll("circle")
    .data(nodes)
    .enter().append("svg:circle")
    .attr("class", function(d) {
      return d.children ? "parent" : "child";
    })
    .attr("cx", function(d) {
      return d.x;
    })
    .attr("cy", function(d) {
      return d.y;
    })
    .attr("r", function(d) {
      return d.r;
    })
    .on("click", function(d) {
      return zoom(node == d ? root : d);
    });

  vis.selectAll("text")
    .data(nodes)
    .enter().append("svg:text")
    .attr("class", function(d) {
      return d.children ? "parent" : "child";
    })
    .attr("x", function(d) {
      return d.x;
    })
    .attr("y", function(d) {
      return d.children? d.y : d.y+10;
    })
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .style("opacity", function(d) {
      return d.r > 20 ? 1 : 0;
    })
    .text(function(d) {
      return d.name;
    });

  d3.select(window).on("click", function() {
    zoom(root);
  });

} //displayFlareGraph
var nodes = {
  "name": "TYPES",
  "children": [{
    "name": "TYPE #iPhone",
    "children": [{
      "name": "iPhone6",
      "size": 0.109375
    }]
  }, {
    "name": "TYPE #OS",
    "children": [{
      "name": "macOS",
      "size": 0.140625
    }]
  }, {
    "name": "Info #Android",
    "children": [{
      "name": "Marshmellow",
      "size": 0.11467116357504215
    }, {
      "name": "Lollipop",
      "size": 0.12228604553119728
    }, {
      "name": "Nougat",
      "size": 0.028667790893760536
    }]
  }, {
    "name": "TYPE #Laptop",
    "children": [{
      "name": "MacbookPro",
      "size": 0.14062499999999997
    }, {
      "name": "Macbook",
      "size": 0.0703125
    }, {
      "name": "Surface",
      "size": 0.0703125
    }]
  }, {
    "name": "TYPE #Browser",
    "children": [{
      "name": "Firefox",
      "size": 0.109375
    }]
  }, {
    "name": "Info #iOS",
    "children": [{
      "name": "iOS11",
      "size": 0.0390625
    }]
  }]
}
flareGraph('#flare', {
  width: 600,
  height: 400,
  radius: 400
}, nodes); // update graph
body>svg {
  position: absolute;
  top: -80px;
  left: -160px;
}

text {
  font-size: 11px;
  pointer-events: none;
}

text.parent {
  fill: blue;
}

circle {
  fill: #ddd;
  stroke: #999;
  pointer-events: all;
}

circle.parent {
  fill: lightblue;
  fill-opacity: .1;
  stroke: gray;
}

circle.parent:hover {
  stroke: #ff7f0e;
  stroke-width: .5px;
}

circle.child {
  pointer-events: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="flare" />

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.