Javascript 向自定义贴图点添加缩放行为

Javascript 向自定义贴图点添加缩放行为,javascript,d3.js,Javascript,D3.js,作为学习D3的练习,我使用了以前一个关于世界各地机场位置和名称的项目中的数据集。我正在使用D3.csv将其加载到我的网页中,并使用topojson在地图上绘制点 在我的练习中,我正在尝试添加一个功能,使用户可以放大和缩小世界地图。正如你可以想象的,有很多机场,地图变得拥挤,因为我还没有添加任何过滤逻辑 最糟糕的是,我可以让缩放行为在国家工作,但我不确定如何让它在我画的圆圈上工作。如果我使用滚轮放大地图,地图会放大,但圆圈保持不变 <script src="http://d3js.org/d

作为学习D3的练习,我使用了以前一个关于世界各地机场位置和名称的项目中的数据集。我正在使用D3.csv将其加载到我的网页中,并使用topojson在地图上绘制点

在我的练习中,我正在尝试添加一个功能,使用户可以放大和缩小世界地图。正如你可以想象的,有很多机场,地图变得拥挤,因为我还没有添加任何过滤逻辑

最糟糕的是,我可以让缩放行为在国家工作,但我不确定如何让它在我画的圆圈上工作。如果我使用滚轮放大地图,地图会放大,但圆圈保持不变

<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<style type="text/css">
    .feature {
        fill: none;
        stroke: grey;
        stroke-width: 1px;
        stroke-linejoin: round;
    }
    .mesh {
        fill: none;
        stroke: lightgrey;
        stroke-width: 2px;
        stroke-linejoin: round;
    }
    h1 {
        font-family: sans-serif;
    }

    svg {
  background: #eee;
}

.sphere {
  fill: #fff;
}

.land {
  fill: #000;
}

.boundary {
  fill: none;
  stroke: #fff;
  stroke-linejoin: round;
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
}

.overlay {
  fill: none;
  pointer-events: all;
}

circle{
  fill: steelblue;
  stroke-width: 1.5px;  
}

.d3-tip {
  line-height: 1;
  font-weight: bold;
  padding: 12px;
  background: rgba(0, 0, 0, 0.8);
  color: #fff;
  border-radius: 2px;
}

/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
  box-sizing: border-box;
  display: inline;
  font-size: 10px;
  width: 100%;
  line-height: 1;
  color: rgba(0, 0, 0, 0.8);
  content: "\25BC";
  position: absolute;
  text-align: center;
}

/* Style northward tooltips differently */
.d3-tip.n:after {
  margin: -1px 0 0 0;
  top: 100%;
  left: 0;
}
</style>
</head>
<body>
    <h1>Lots of airports across the world</h1>

<script type="text/javascript">
var width = 950,
    height = 550;
    scale0 = (width - 1) / 2 / Math.PI;

var projection = d3.geo.mercator();

var zoom = d3.behavior.zoom()
    .translate([width / 2, height / 2])
    .scale(scale0)
    .scaleExtent([scale0, 8 * scale0])
    .on("zoom", zoomed);

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

var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height)
  .append("g");

var g = svg.append("g");

var circle = svg.append("circle");

svg.append("rect")
    .attr("class", "overlay")
    .attr("width", width)
    .attr("height", height);

svg
    .call(zoom)
    .call(zoom.event);

var tip = d3.tip()
    .attr("class", "d3-tip")
    .offset([-10, 0])
    .html(function(d) {
        return "Name" + ": " + d[2] + "<br>" + "Location" + ": " + d[3];
    });

svg.call(tip);

d3.json("world-110m.v1.json", function(error, world) {
  if (error) throw error;

  g.append("g")
      .attr("d", path)
      .on("click", clicked)
      .on("zoom", zoomed);

  g.append("path")
      .datum({type: "Sphere"})
      .attr("class", "sphere")
      .attr("d", path);

  g.append("path")
      .datum(topojson.merge(world, world.objects.countries.geometries))
      .attr("class", "land")
      .attr("d", path);

  g.append("path")
      .datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
      .attr("class", "boundary")
      .attr("d", path)
      .on("click", clicked);

    d3.csv("output.csv",
        function(data) {return {name: data.Airport_name, location: data.Location_served, 
            long : +data.Longitude, lat : +data.Latitude}},
        function(data) {
        var new_array = data.map(function (d) {return [d.long, d.lat, d.name, d.location]});
        console.log("new", new_array)

        svg.selectAll("circle")
            .data(new_array)
            .enter()
            .append("circle")
            .attr("cx", function (d) { return projection(d)[0]; })
            .attr("cy", function (d) { return projection(d)[1]; })
            .attr("r", "2px")
            .on("mouseover", tip.show)
            .on("mouseout", tip.hide);
        });

    }) //closes the json, do not move.

// begin click-zoom listeners
function clicked(d) {
    console.log("d:",d)
  var centroid = path.centroid(d),
      translate = projection.translate();

  projection.translate([
    translate[0] - centroid[0] + width / 2,
    translate[1] - centroid[1] + height / 2
  ]);

  zoom.translate(projection.translate());

  g.selectAll("path").transition()
      .duration(700)
      .attr("d", path);
}

function zoomed() {
  projection.translate(d3.event.translate).scale(d3.event.scale);
  g.selectAll("path").attr("d", path);
}
</script>    
</body>

缩放功能执行两项操作,即修改投影并使用修改后的投影更新路径:

function zoomed() {
  projection.translate(d3.event.translate).scale(d3.event.scale); // modify the projection
  g.selectAll("path").attr("d", path);   // update the paths
}
好的,除了使用绑定基准修改每次缩放的路径外,我们还需要修改圆:

function zoomed() {
  projection.translate(d3.event.translate).scale(d3.event.scale); // modify the projection
  g.selectAll("path").attr("d", path);   // update the paths

  // update the circles/points:
  svg.selectAll("circle")
    .attr("cx", function (d) { return projection(d)[0]; })
    .attr("cy", function (d) { return projection(d)[1]; })
  });
}
    svg.selectAll("circle")
        .data(new_array)
        .enter()
        .append("circle")
        .attr("cx", function (d) { return projection(d)[0]; })
        .attr("cy", function (d) { return projection(d)[1]; })

但是,这并不完全有效,我们需要了解如何附加圆:

function zoomed() {
  projection.translate(d3.event.translate).scale(d3.event.scale); // modify the projection
  g.selectAll("path").attr("d", path);   // update the paths

  // update the circles/points:
  svg.selectAll("circle")
    .attr("cx", function (d) { return projection(d)[0]; })
    .attr("cy", function (d) { return projection(d)[1]; })
  });
}
    svg.selectAll("circle")
        .data(new_array)
        .enter()
        .append("circle")
        .attr("cx", function (d) { return projection(d)[0]; })
        .attr("cy", function (d) { return projection(d)[1]; })
如果svg上没有圆圈,这很好-但是有,您在这里添加了一个圆圈:

var circle = svg.append("circle");
这意味着不会添加数组中的第一个机场,因为svg中已经有一个圆圈用于数据数组中的该项。空选择(d3.selectAll(null))将确保为数据数组中的每个项目输入一个项目

这里最重要的是,在加载数据之前,第一个圆没有绑定基准。这将导致调用缩放时出现一些问题,没有用于重新缩放圆的绑定数据,您将得到一个错误。相反,您可以使用类附加机场,并在缩放事件期间选择这些机场

在我的示例中,我使用了空选择来输入机场,并给了它们一个类,这样我就可以根据更新的投影轻松地选择要重新定位的圆。(为了演示,我还简化了世界地图并增加了点半径)

这看起来像:

function zoomed() {
  projection.translate(d3.event.translate).scale(d3.event.scale);
  g.selectAll("path").attr("d", path);

 svg.selectAll(".airport")
   .attr("cx", function (d) { return projection(d)[0]; })
   .attr("cy", function (d) { return projection(d)[1]; }) 
}
输入为:

    svg.selectAll()   // selectAll() is equivilant to selectAll(null)
        .data(new_array)
        .enter()
        .append("circle")
        .attr("class","airport")
        .attr("cx", function (d) { return projection(d)[0]; })
        .attr("cy", function (d) { return projection(d)[1]; })
        .attr("r", "6px")
        .on("mouseover", tip.show)
        .on("mouseout", tip.hide);
    });

“TypeError:无法读取undefined”的属性“0”用于生成我的点的数据是如何在d3.csv函数之外读取的?啊,我没有看到您附加了一个额外的圆:
svg.append(“圆”)这没有绑定的基准。缩放使用绑定到点的数据(与重绘路径时的路径相同),我将进行编辑。虽然如果你能提供你的数据样本,我可以做一个适当的演示。样本添加到主要问题中谢谢,我希望我在更新的答案中说得很清楚-这还为时过早,或者对我来说说得不清楚。这是一个相当好的演示!谢谢,我将进一步阅读附加元素以及添加和操作类属性。