如何使用d3.js拖动和旋转正交地图(globe)

时间:2019-03-11 21:58:24

标签: javascript d3.js svg drag

设置:我正在构建一个Globe应用程序,以更好地形象地表示世界各地区的数据。它是使用topojson构建d3.js来构建几何图形的。

我目前正在执行ivyywang here所实现的拖动。 (除非您处于状态:“数学书呆子大师”,否则不要迷失在数学函数中)

我的项目当前为here

问题:我对地球仪进行了正交投影,并成功实现了拖动功能...除外。只要光标位于某个国家/地区的范围内,我就只能单击并拖动地球。如何投影我的SVG,以便整个画布响应我的拖动事件?

相关代码:

首先,我从MySQL请求中获取一些数据并将其存储在countryStattistics中。然后通过以下函数运行它,以更好地对其进行索引。

var countryStatistics = (returned from mySQL query)

  //this function build dataById[] setting data keyed to idTopo
function keyIdToData(d){
  countryStatistics.forEach(function(d) {
    dataById[d.idTopo] = d;
  });  
}    

 function visualize(statisticalData, mapType){
  //pass arguments each function call to decide what data to viasually display, and what map type to use

var margin = {top: 100, left: 100, right: 100, bottom:100},
    height = 800 - margin.top - margin.bottom, 
    width = 1200 - margin.left - margin.right;

  //a simple color scale to correlate to data
var colorScale = d3.scaleLinear()
  .domain([0, 100])
  .range(["#646464", "#ffff00"])


 //create svg
var svg = d3.select("#map")
      .append("svg")
      .attr("height", height + margin.top + margin.bottom)
      .attr("width", width + margin.left + margin.right)
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
        //here I attmpt to fill the svg with a different color.  but it is unresponsive
      .attr("fill", "blue")

如您在代码块末尾所见,我在SVG元素上调用.attr(“ fill” ...,但无法获得要呈现的颜色。也许这与为什么我的光标没有响应这个空间。

继续...

  //set projection type to 2D map or 3d globe dependinding on argument passed see function below
var projection = setMapType(mapType, width, height);

      //a function to call on visualize() to set projection type for map style.
function setMapType(mapType, width, height) {
  if(mapType === "mercator") {
    let projection = d3.geoMercator()
    .translate([ width / 2, height / 2 ])
    .scale(180)
    return projection;
  }else if (mapType === "orthographic"){
    let projection = d3.geoOrthographic()
    .clipAngle(90)
    .scale(240);
    return projection;
  }

  //pass path lines to projections
var path = d3.geoPath()
  .projection(projection);

  //here I create and call the drag function only when globe projection is displayed elected
if(mapType == "orthographic"){
  var drag = d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged);
    svg.call(drag);
}


  //coordinate variables
var gpos0, 
    o0;

function dragstarted(){
  gpos0 = projection.invert(d3.mouse(this));
  o0 = projection.rotate();  
}

function dragged(){
  var gpos1 = projection.invert(d3.mouse(this));
  o0 = projection.rotate();

  var o1 = eulerAngles(gpos0, gpos1, o0);
  projection.rotate(o1);

  svg.selectAll("path").attr("d", path);
}

  //load in the topojson file
d3.queue()
  .defer(d3.json, "world110m.json")
  .await(ready)  

以上功能是指计算正交旋转所需的数学功能。您可以在顶部的第一个链接中的ivyywang在代码块中看到它们。

function ready (error, data){
    if (error) throw error;
    //output data to see what is happening
  console.log("topojson data: ")
  console.log(data);

    //I suspect there may be an issue with this code. 
  countries = topojson.feature(data, data.objects.countries)
    //bind dataById data into countries topojson variable
  .features.map(function(d) {
    d.properties = dataById[d.id];
    return d
  });

  console.log("countries:")
  console.log(countries)

我怀疑上面的国家/地区变量可能是罪魁祸首,主要是因为我不完全理解此代码。在此变量中,我将由keyIdToData()处理的countryStatistics数据与我的topojson数据绑定为嵌套对象“属性”。我将其记录下来以查看数据。

  svg.selectAll(".country")
    .data(countries)
    .enter().append("path")
    .attr("class", "country")
    .attr("d", path)

    //make fill gradient depend on data
    .attr("fill", function(countries){
        //if no data, country is grey
      if(countries.properties == undefined){
        return "rgb(100 100 100)";
      }
        //else pass data to colorScale()
      return colorScale(countries.properties.literacy)
    })
    .on('mouseover', function(d) {
        //on hover set class hovered which simply changes color with a transition time
      d3.select(this).classed("hovered", true)
    })
    .on('mouseout', function(d) {
      d3.select(this).classed("hovered", false)
    })
  }
};

最后我们有了这个小功能

  //this function build dataById[] setting data keyed to idTopo
function keyIdToData(d){
  countryStatistics.forEach(function(d) {
    dataById[d.idTopo] = d;
  });  
}  

可能性::似乎我的SVG渲染排除了我的空白(非国家/地区)区域。我的SVG构造可能有问题吗?还是当我更改数据并将dataById附加到我的topojson中时,我正在干扰SVG的构造?

感谢您将它作为键盘忍者。有什么想法吗?

1 个答案:

答案 0 :(得分:2)

问题

您的鼠标交互仅在父g内绘制路径的位置,而不是在它们之间的空间。您应用于g的填充会被应用于子路径的样式所覆盖。


查看您所拥有的内容,变量svg包含一个g

//create svg
var svg = d3.select("#map")
  .append("svg")
  ...
  .append("g")  // return a newly created and selected g
  ...
  .attr("fill", "blue") // returns same g

对于鼠标交互,g仅可与其中存在元素的地方进行交互。对于gfill属性不会直接做任何事情,它仅应用于表示元素(和动画):

  

作为演示文稿属性,它[填充]可以应用于任何元素,但是它   仅对以下11个元素有效:<altGlyph>,   <circle><ellipse><path><polygon><polyline><rect><text>,   <textPath><tref><tspan>MDN

fill上使用g是为子元素,即路径着色。尽管您直接为它们着色,所以蓝色没有视觉效果:

var g = d3.select("body")
  .append("svg")
  .append("g")
  .attr("fill","orange");
  
// Inherit fill:
g.append("rect")
  .attr("width",50)
  .attr("height",50)
  
// Override inheritable fill:
g.append("rect")
  .attr("x", 100)
  .attr("width",50)
  .attr("height",50)
  .attr("fill","steelblue");
  
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

解决方案

您需要创建一个与之交互的元素,以将其拖动到当前没有路径的地方。


现在,我认为您不希望将整个svg背景设置为蓝色,而只是将地球上不属于某个国家的部分设置为蓝色。您可以使用geojson球体执行此操作。从技术上讲,d3并不是geojson规范的一部分,但它识别出shere类型的geojson覆盖了整个星球(因此,它不需要任何坐标)。在将国家/地区添加到地球之前,添加一个球体,这为拖动事件提供了与之交互的元素:

svg.append("path")
  .attr("d", path({type:"Sphere"})
  .attr("fill","blue");

这充满了海洋(和陆地),我们可以在上面附加国家。现在,由于球体和国家都属于同一g的一部分,因此我们可以像现在一样在整个地球上进行拖动,但是现在没有孔可以进行鼠标交互了。

这是一个具有正交投影和最基本的拖动功能的快速演示:

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

var projection = d3.geoOrthographic()
  .translate([250,250])
  
var path = d3.geoPath().projection(projection);

d3.json("https://unpkg.com/world-atlas@1/world/110m.json").then( function(data) {

  var world = {type:"Sphere"}
  
  svg.append("path")
    .datum(world)
    .attr("d", path)
    .attr("fill","lightblue");
    
  svg.selectAll(null)
    .data(topojson.feature(data,data.objects.land).features)
    .enter()
    .append("path")
    .attr("fill","lightgreen")
    .attr("d",path);
  
  
  svg.call(d3.drag()
    .on("drag", function() {
      var xy = d3.mouse(this);
      projection.rotate(xy)
      svg.selectAll("path")
       .attr("d",path);
    }))
  

 
 
 
 
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>

<svg width="500" height="500"></svg>