簡體   English   中英

如何使用 d3.js 拖動和旋轉正投影圖(地球儀)

[英]How to drag and rotate orthographic map (globe) using d3.js

設置:我正在構建一個地球應用程序,以更好地直觀地表示世界各地區的數據。 它是使用 topojson 構建 d3.js 來構建幾何的。

我目前正在實施 ivyywang 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 構造?

感謝您將其制作成這么遠的鍵盤忍者。 有任何想法嗎?

問題

您的鼠標交互僅在父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屬性不會直接做任何事情,它只應用於演示元素(和動畫):

作為表示屬性,它 [fill] 可以應用於任何元素,但它只對以下十一個元素有效: <altGlyph><circle><ellipse><path><polygon><polyline><rect><text><textPath><tref><tspan> (MDN )

g上使用fill代替着色子元素,您的路徑。 雖然你直接給它們上色,所以藍色沒有視覺效果:

 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 規范的一部分,它識別為覆蓋整個星球的 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>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM