气泡中的文本在力模拟中重叠

时间:2017-11-06 07:57:31

标签: javascript d3.js force-layout bubble-chart

我有一个脚本,我在其中使用气泡和气泡上的文本进行力模拟。对于气泡彼此相邻的一些数据,文本会重叠。我在模拟中使用了forceCollide,为什么文本仍然重叠? 我的数据集实例的图像如下:

enter image description here

我的脚本如下:

//var margin = {top: 30, right: 20, bottom: 30, left: 50}

    var margin = 40,
    width = 600,
    height = 400;

    var xscale = d3.scaleLinear()
        .domain([0, d3.max(data, function (d) {
            return +d.student_percentile;
        })])
        .nice() 
        .range([0, width]);

    var yscale = d3.scaleLinear()
            .domain([0, d3.max(data, function (d) {
                return +d.rank;
            })])
            .nice()
            .range([height, 0]);

    var svg = d3.select('.chart')
        .classed("svg-container", true)
        .append('svg')
        .attr('class', 'chart')
        .attr("viewBox", "0 0 680 490")
        .attr("preserveAspectRatio", "xMinYMin meet")
        .classed("svg-content-responsive", true)
        .append("g")
        .attr("transform", "translate(" + margin + "," + margin + ")");

    simulation = d3.forceSimulation()
        .force("x", d3.forceX(function(d) {
            return xscale(+d.student_percentile);
        }))
        .force("y", d3.forceY(function(d) {
            return yscale(+d.rank);
        }))
        .force("collide", d3.forceCollide(24)); 

    var local = d3.local();
    circles = svg.selectAll("circle")
      .data(data)
      .enter()
      .append("circle")
      .attr("opacity", 0.3)
      .attr("r", 20)
      .style("fill", function(d){
            if(+d.admit_probability <= 40){
                return "red";
            }
            else if(+d.admit_probability > 40 && +d.admit_probability <= 75){
                return "yellow";
            }
            else{
                return "green";
            }
      })
      .on('mouseover', function(d, i) {
            local.set(this, d3.select(this).style("fill"));
            d3.select(this)
              .transition()
              .duration(1000)
              .ease(d3.easeBounce)
              .attr("r", 32)
              .style("cursor", "pointer")
              .attr("text-anchor", "middle");
              var d = this.__data__;
              show_details(d);
        }
       )
      .on('mouseout', function(d, i) {
            d3.select(this).style("fill", local.get(this));
            d3.select(this).transition()
              .style("opacity", 0.3)
              .attr("r", 20)
              .style("cursor", "default")
            .transition()
            .duration(1000)
            .ease(d3.easeBounce);
            remove_details();
      });

    texts = svg.selectAll(null)
      .data(data)
      .enter()
      .append('text')
      .attr("text-anchor", "middle")
      .text(function(d) {
        return d.abbreviation;
      })
      .attr("pointer-events", "none")
      .attr("font-family", "sans-serif")
      .attr("font-size", "10px")
      .attr("fill", "black");

    simulation.nodes(data).on("tick", function() {
        circles.attr("cx", function(d) {
                return d.x = Math.max(20, Math.min(width - 20, d.x));
            })
            .attr("cy", function(d) {
                return d.y = Math.max(20, Math.min(height - 20, d.y));
            })
        texts.attr("x", function(d) {
                return d.x;
            })
            .attr("y", function(d) {
                return d.y;
            });
    });

我希望所有圈子和文字都不重叠。

1 个答案:

答案 0 :(得分:1)

问题在于:

.force("collide", d3.forceCollide(24)); 

您将24的半径分配给所有文本,无论其大小如何。这不正确。半径应取决于文本的大小。

查看下面的演示。在这里,我使用getComputedTextLength()来获取文本的长度并设置新属性(size)。然后,在模拟中,我使用该属性:

.force("collide", d3.forceCollide(function(d) {
    return d.size
}))

以下是演示:

&#13;
&#13;
var svg = d3.select("svg");
var data = [{
  text: "some text"
}, {
  text: "a longer text here"
}, {
  text: "an even longer text here"
}, {
  text: "short text"
}, {
  text: "a long text"
}];
var texts = svg.selectAll(null)
  .data(data)
  .enter()
  .append("text")
  .text(function(d) {
    return d.text
  });
texts.each(function(d) {
  d.size = this.getComputedTextLength() / 2
})
var simulation = d3.forceSimulation(data)
  .force("center", d3.forceCenter(250, 150))
  .force("collide", d3.forceCollide(function(d) {
    return d.size
  }))
  .on("tick", tick);

function tick() {
  texts.attr("x", function(d) {
    return d.x;
  }).attr("y", function(d) {
    return d.y;
  })
}
&#13;
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="500" height="300"></svg>
&#13;
&#13;
&#13;