防止D3气泡图中气泡重叠

时间:2019-04-16 01:33:37

标签: javascript d3.js charts data-visualization

我通过遵循此示例https://www.d3-graph-gallery.com/graph/bubble_tooltip.html(仅在提取csv时将回调替换为promise)来使用D3 v5创建气泡图。到目前为止,效果很好。

现在,我需要能够对气泡不会彼此重叠的可视化应用物理效果,我相信我需要的是forceCollide。我已经尝试了一段时间,但似乎无法正常工作。这是我用于创建气泡的代码:

// Before I set dimensions, read the data, create x and y axis
// add bubble scales and create tooltips 
// ...

//Add dots
  var nodes = svg
    .append("g")
    .selectAll("circle")
    .data(data);

  nodes
    .enter()
    .append("circle")
    .attr("class", "bubbles")
    .attr("cx", function(d) {
      return x(d.gdpPercap);
    })
    .attr("cy", function(d) {
      return y(d.lifeExp);
    })
    .attr("r", function(d) {
      return z(d.pop);
    })
    .style("fill", function(d) {
      return myColor(d.continent);
    })
    // -3- Trigger the functions
    .on("mouseover", showTooltip)
    .on("mousemove", moveTooltip)
    .on("mouseleave", hideTooltip);


任何帮助将不胜感激!

1 个答案:

答案 0 :(得分:0)

您曾经能够找到解决方案吗?来自jsfiddle.net/30e1dga6:

   data = [{
     company: "Walmart",
     total: 48,
     women: 13,
     men: 34,
     unknown: 1,
     percentage: 28
   }, {
     company: "Chevron",
     total: 17,
     women: 2,
     men: 15,
     unknown: 0,
     percentage: 12
   }, {
     company: "General Motors Company",
     total: 28,
     women: 5,
     men: 22,
     unknown: 1,
     percentage: 5
   }, {
     company: "ConocoPhillips",
     total: 17,
     women: 4,
     men: 13,
     unknown: 0,
     percentage: 2
   }, {
     company: "General Electric Company",
     total: 23,
     women: 7,
     men: 15,
     unknown: 1,
     percentage: 8
   }, {
     company: "Citigroup Inc.",
     total: 39,
     women: 4,
     men: 32,
     unknown: 3,
     percentage: 38
   }, {
     company: "Bank of America Corporation",
     total: 25,
     women: 7,
     men: 18,
     unknown: 0,
     percentage: 30
   }, {
     company: "AT&T Inc.",
     total: 18,
     women: 2,
     men: 16,
     unknown: 0,
     percentage: 14
   }, {
     company: "JPMorgan Chase & Co.",
     total: 88,
     women: 21,
     men: 64,
     unknown: 3,
     percentage: 17
   }, {
     company: "AIG",
     total: 28,
     women: 1,
     men: 27,
     unknown: 0,
     percentage: 20
   }, {
     company: "Hewlett-Packard",
     total: 44,
     women: 7,
     men: 37,
     unknown: 0,
     percentage: 22
   }]

   $(document).ready(function() {
     // var data= getPercentage();

     var width = 1000 //max size of the bubbles
     var height = 800
     color = d3.scale.linear()
       .domain([0, 40]) //map colors based on value
       .range(['#47DFFF', 'green']);; //color category

     var bubble = d3.layout.pack()
       .sort(null)
       .size([width, height])
       .padding(.5);

     var svg = d3.select("#data-container").append("svg")
       .attr("width", width)
       .attr("height", height)
       .attr("class", "bubble");

     // Define the div for the tooltip
     var div = d3.select("#data-container").append("div")
       .attr("class", "tooltip")
       .style("opacity", 0);

     //convert numerical values from strings to numbers
     data = data.map(function(d) {
       d.value = +d["percentage"];
       return d;
     });

     //bubbles needs very specific format, convert data to this.
     var nodes = bubble.nodes({
       children: data
     }).filter(function(d) {
       return !d.children;
     });


     // Charge Function
     function charge(d) {
       return -Math.pow(d.radius / 2, 2.0) / 8;
     }
     //force layout
     var force = d3.layout.force()
       .gravity(.9)
       .charge(charge)
       .nodes(nodes)
       .friction(0.9);


     //setup the chart
     var bubbles = svg.append("g")
       .attr("transform", "translate(0,0)")
       .selectAll(".bubble")
       .data(nodes)
       .enter();

     //create the bubbles
     bubbles.append("circle")
       .classed('bubble', true)
       .attr("r", function(d) {
         return d.r;
       })
       .attr("cx", function(d) {
         return d.x;
       })
       .attr("cy", function(d) {
         return d.y;
       })
       .attr('stroke', 'black')
       .attr('stroke-width', 0)
       //mouseover hover box
       .on('mouseover', function(d) {
         div.transition()
           .duration(100)
           .style("opacity", 1)
         div.html(d.company + "<br>" + d.percentage + "% Female<br>Men: " + d.men + "<br>Women: " + d.women)
           .style("left", (d3.event.pageX) + "px")
           .style("top", (d3.event.pageY - 28) + "px");
         d3.select(this)
           .transition()
           .duration(100)
           .attr('stroke-width', 2)
       })
       .on('mouseout', function(d) {
         div.transition()
           .duration(100)
           .style("opacity", 0);
         d3.select(this)
           .transition()
           .duration(1000)
           .attr('stroke-width', 0)
       })
       .style("fill", function(d) {
         return color(d.value)
       })
       .call(force.drag);


     var texts = svg.selectAll("text")
     var bubbles = svg.selectAll(".bubble")

     // groupBubbles();

     force.on("tick", function(e) {
       bubbles.attr("transform", function(d) {
         return 'translate(' + [d.x, d.y] + ')';
       });

     });

     force.start();
     var splitCenters = {
       1: {
         x: width / 3,
         y: height / 2
       },
       2: {
         x: width / 2,
         y: height / 2
       },
       3: {
         x: 2 * width / 3,
         y: height / 2
       }
     };

     var center = {
       x: width / 2,
       y: height / 2
     }

     function groupBubbles() {
       force.on('tick', function(e) {
         var q = d3.geom.quadtree(bubbles),
           i = 0,
           n = bubbles.length;
         while (++i < n) {
           q.visit(collide(bubbles[i]));
         }
         bubbles.each(moveToCenter(e.alpha))
           // .transition()
           .attr('cx', function(d) {
             return d.x;
           })
           .attr('cy', function(d) {
             return d.y;
           })
       })
       force.start()
     }

     function moveToCenter(alpha) {
       return function(d) {
         d.x = d.x + (center.x - d.x)
         d.y = d.y + (center.y - d.y)
       };
     }

     function splitBubbles() {
       force.on('tick', function(e) {
         var q = d3.geom.quadtree(bubbles),
           i = 0,
           n = bubbles.length;
         while (++i < n) {
           q.visit(collide(bubbles[i]));
         }
         bubbles.each(moveToSplit(e.alpha))
           .transition()
           .attr('cx', function(d) {
             return d.x;
           })
           .attr('cy', function(d) {
             return d.y;
           });

       });

       force.start()
     }

     function moveToSplit(alpha) {
       return function(d) {
         // console.log(d.percentage)
         var target;
         if (d.percentage <= 10) {
           target = splitCenters[1]
         } else if (d.percentage > 10 && d.percentage < 20) {
           target = splitCenters[2]
         } else {
           target = splitCenters[3]
         }
         d.x = d.x + (target.x - d.x)
         d.y = target.y
       }
     }

     $("#splitBubbles").click(splitBubbles)
     $("#groupBubbles").click(groupBubbles)


     function collide(node) {
       var r = node.radius + 16,
         nx1 = node.x - r,
         nx2 = node.x + r,
         ny1 = node.y - r,
         ny2 = node.y + r;
       return function(quad, x1, y1, x2, y2) {
         if (quad.point && (quad.point !== node)) {
           var x = node.x - quad.point.x,
             y = node.y - quad.point.y,
             l = Math.sqrt(x * x + y * y),
             r = node.radius + quad.point.radius;
           if (l < r) {
             l = (l - r) / l * .5;
             node.x -= x *= l;
             node.y -= y *= l;
             quad.point.x += x;
             quad.point.y += y;
           }
         }
         return x1 > nx2 || x2 < nx1 || y1 > ny2 || y2 < ny1;
       };
     }


   });
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<button id="splitBubbles">Split</button>
  <button id="groupBubbles">Group</button>
<div id="data-container">