Как перетащить и повернуть ортографическую карту (глобус) с помощью d3.js - PullRequest
2 голосов
/ 12 марта 2019

SetUp: Я создаю приложение-глобус, чтобы лучше визуально представлять данные регионов мира.Он построен на d3.js с использованием topojson для построения геометрии.

В настоящее время я реализую перетаскивание, которое было достигнуто ivyywang здесь .(не заблудитесь в математических функциях, если у вас нет статуса: "математический гуру")

Мой проект в настоящее время здесь .

Проблема: Я спроецировал глобус на орфографию и успешно реализовал функцию перетаскивания ... кроме.Я могу только щелкнуть и перетащить глобус, пока мой курсор находится за пределами страны.Как я могу проецировать SVG так, чтобы весь холст реагировал на мое событие перетаскивания?

Соответствующий код:

Сначала я получаю некоторые данные из запроса MySQL и сохраняю их.в стране статистика.И я запускаю его с помощью следующей функции, чтобы лучше индексировать ее.

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")

Как вы можете видеть в конце этого блока кода, я вызываю .attr ("fill" ... для элемента SVG, ноне удается получить цвет для рендеринга. Возможно, это связано с тем, что мой курсор не отвечает в этом пространстве.

продолжение ...

  //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)

Я подозреваю, что переменная стран чуть выше может быть виновником, в основном потому, что я неЯ не совсем понимаю этот код. В этой переменной я связываю свои данные countryStatistics, обработанные keyIdToData(), как «свойства» вложенного объекта с моими данными 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?Или, возможно, я вмешиваюсь в конструкцию SVG, когда я изменяю данные и добавляю свой dataById в мой топойсон?

Спасибо за то, что сделали эту далекую клавиатуру ниндзя.Есть идеи?

1 Ответ

2 голосов
/ 12 марта 2019

Проблема

Ваше взаимодействие с мышью происходит только в том месте, где пути нарисованы в родительском 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 может взаимодействовать только с теми элементами, которые существуют внутри него.Для g атрибут fill не будет ничего делать напрямую, он применяется только к элементам презентации (и анимациям):

В качестве атрибута презентации он может быть [fill]применяется к любому элементу, но влияет только на следующие одиннадцать элементов: <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.Технически не являющийся частью спецификации geojson, d3 распознает геойсон типа shere как охватывающий всю планету (как таковой, он не принимает координат).Перед добавлением стран к земному шару, добавьте сферу, это дает элемент для событий перетаскивания, чтобы взаимодействовать с:

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>
...