首页 > 解决方案 > 如何正确排序 d3 气泡图的数据,以使较小的气泡显示在较大的气泡之上?

问题描述

我正在制作与此类似的气泡图:https ://observablehq.com/@d3/bubble-map

一切正常,只是我的小气泡并不总是出现在较大的气泡之上。我不明白为什么,因为我在画圆圈之前已经对数据进行了排序。谁能看到我做错了什么?

这是一个 plunker: https ://plnkr.co/edit/JKWeQKkhN2TQwvNZ?open=lib%2Fscript.js

代码如下。其他文件太大而无法堆栈溢出,但可以通过 Plunker 访问。

<!DOCTYPE html>
<meta charset="utf-8">
<style>
  .states {
    fill: #d3d3d3;
    stroke: #ffffff;
    stroke-linejoin: round;
  }

  div.tooltip {
    position: absolute;
    left: 75px;
    text-align: center;
    height: 12px;
    padding: 8px;
    font-size: 13px;
    font-family: 'Proxima-Nova', sans-serif;
    background: #FFFFFF;
    border: 1px solid #989898;
    pointer-events: none;
  }

  .block {
    width: 18%;
    height: 15px;
    display: inline-block;
  }
</style>

<body>

  <div class="g-chart"></div>
</body>

<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/geo-albers-usa-territories@0.1.0/dist/geo-albers-usa-territories.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/queue-async/1.0.7/queue.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>

<script>
  var div = d3.select("body").append("div")
    .attr("class", "tooltip")
    .style("opacity", 0);

  var margin = { top: 10, left: 10, bottom: 10, right: 10 },
    width = window.outerWidth,
    width = width - margin.left - margin.right,
    mapRatio = .5,
    height = width * mapRatio;

  const epsilon = 1e-6;

  var projection = geoAlbersUsaTerritories.geoAlbersUsaTerritories()
    .scale(width)
    .translate([width / 2, height / 2]);

  var path = d3.geoPath()
    .projection(projection);

  var map = d3.select(".g-chart").append("svg")
    .style('height', height + 'px')
    .style('width', width + 'px')
    .call(d3.zoom().on("zoom", function () {
      map.attr("transform", d3.event.transform)
      d3.selectAll()
    }))
    .append("g");


  queue()
    .defer(d3.json, "us.json")
    .defer(d3.csv, "test.csv")
    .await(ready);

  d3.selection.prototype.moveToFront = function () {
    return this.each(function () {
      this.parentNode.appendChild(this);
    });
  };

  d3.selection.prototype.moveToBack = function () {
    return this.each(function () {
      var firstChild = this.parentNode.firstChild;
      if (firstChild) {
        this.parentNode.insertBefore(this, firstChild);
      }
    });
  };

  function ready(error, us, data) {
    if (error) throw error;


    data.forEach(function (d) {
      d.amount = +d.amount;
    })

    map.append("g")
      .attr("class", "states")
      .selectAll("path")
      .data(topojson.feature(us, us.objects.states).features)
      .enter().append("path")
      .attr("d", path);

    // sort by descending size so that the smaller circles are drawn on top - not working

    map.append('g')
      .attr('class', 'facility')
      .selectAll("circle")
      .data(data.sort((a, b) => +b.amount - +a.amount))
      .enter()
      .append("circle")
      .attr("cx", function (d) {
        return projection([d.longitude, d.latitude])[0];
      })
      .attr("cy", function (d) {
        return projection([d.longitude, d.latitude])[1];
      })
      .attr('r', function (d) {
        if (d.amount <= 25) { return 3 }
        else if (d.amount > 25 && d.amount <= 50) { return 5 }
        else if (d.amount > 50 && d.amount <= 75) { return 7 }
        else { return 9 }
      })
      .style("fill", "#EF4136")
      .style("stroke", "#BE2C2D")
      .style("stroke-width", 1)
      .style("opacity", 0.5)

      .on("mouseover", function (d) {
        var sel = d3.select(this);
        sel.moveToFront();
        d3.select(this).transition().duration(0)
          .style("opacity", 0.8)
          .style("stroke", "#FFFFFF")
        div.transition().duration(0)
          .style("opacity", .85)
        div.text(d.amount)
          .style("left", (d3.event.pageX) + "px")
          .style("top", (d3.event.pageY - 30) + "px");
      })
      .on("mouseout", function () {
        var sel = d3.select(this);
        d3.select(this)
          .transition().duration(0)
          .style("opacity", 0.5)
          .style("stroke", "#BE2C2D")
        div.transition().duration(0)
          .style("opacity", 0);
      });


    //RESPONSIVENESS
    d3.select(window).on('resize', resize);

    function resize() {

      var w = d3.select(".g-chart").node().clientWidth;

      width = w - margin.left - margin.right;
      height = width * mapRatio;

      var newProjection = d3.geoAlbersUsa()
        .scale(width)
        .translate([width / 2, height / 2]);

      path = d3.geoPath()
        .projection(newProjection);

      map.selectAll("circle")
        .attr("cx", function (d) {
          return newProjection([d.longitude, d.latitude])[0];
        })
        .attr("cy", function (d) {
          return newProjection([d.longitude, d.latitude])[1];
        })
        .attr("r", 5)

      map
        .style('width', width + 'px')
        .style('height', height + 'px');

      map.selectAll("path").attr('d', path);
    }

  }

</script>

标签: javascriptd3.js

解决方案


我建议您将数据拆分为按大小分组的几个单独的数据集,并g为每个数据集创建不同的组(元素)。这也将解决圆圈突出显示的问题。

我稍微更新了您的 plunker 以使其按描述工作(检查第 91-167 行)https://plnkr.co/edit/rayo5IZQrBqfqBWR?open=lib%2Fscript.js&preview

还要检查提升降低方法。它们可能是您moveToFrontmoveToBack方法的良好替代品。 https://riptutorial.com/d3-js/example/18029/svg--the-drawing-order


推荐阅读