在D3 v4中拖动旋转投影

时间:2017-05-04 02:42:15

标签: d3.js

我无法通过d3.drag()将正投影投影旋转。它没有反应:

var projection = d3.geoOrthographic()
  .scale(100)
  .translate([500, 300])
  .rotate([55,-40])
  .center([0,0])
  .clipAngle(90);

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

svg.call(d3.drag().on('drag', dragged));

function dragged() {
    var transform = d3.event.transform;
    var r = {x: λ(transform.x), y: φ(transform.y)};
    projection.rotate([origin.x + r.x * k, origin.y + r.y]);
    updatePaths(svg, graticule, geoPath);
};

我尝试用dragged()替换console.log(),即使在这种情况下也没有发生任何事情(显然函数没有被调用)。我以类似的方式实现了d3.zoom()而没有任何问题。我做错了什么?

jsFiddle:https://jsfiddle.net/sirallen/2L4ajskL/

1 个答案:

答案 0 :(得分:5)

您的拖动无响应的原因是您已经在该SVG上调用缩放,并且缩放处理平移。因此,要使d3.drag起作用,您必须使拖动行为优先于平移。

除此之外,拖拽事件没有d3.event.transform。根据{{​​3}},这些是事件对象中的属性:

  
      
  • target - 关联的拖动行为。
  •   
  • type - 字符串“start”,“drag”或“end”;见drag.on。
  •   
  • subject - 拖动主题,由drag.subject定义。
  •   
  • x - 主题的新x坐标;见drag.container。
  •   
  • y - 主题的新y坐标;见drag.container。
  •   
  • dx - 自上次拖动事件以来x坐标的变化。
  •   
  • dy - 自上次拖动事件以来y坐标的变化。
  •   
  • identifier - 字符串“mouse”或数字触摸标识符。
  •   
  • 有效 - 当前有效拖动手势的数量
  •   
  • sourceEvent - 基础输入事件,例如mousemove或   touchmove。
  •   

因此,您的transform将为undefined ... ,如果,您可以致电dragged

解决方案1 ​​

只需放下d3.drag,使用缩放的点击并拖动(平移)来旋转地球。

这是您的代码,只需进行少量更改(我将所有内容都放在zoomed中)。使用鼠标滚轮进行缩放,然后单击地球拖动它:

function createMap() {
  var width = 400,
    height = 300,
    scale = 100,
    lastX = 0,
    lastY = 0,
    origin = {
      x: 55,
      y: -40
    };

  var svg = d3.select('body').append('svg')
    .style('width', 400)
    .style('height', 300)
    .style('border', '1px lightgray solid')

  var projection = d3.geoOrthographic()
    .scale(scale)
    .translate([width / 2, height / 2])
    .rotate([origin.x, origin.y])
    .center([0, 0])
    .clipAngle(90);

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

  var graticule = d3.geoGraticule();

  // zoom AND rotate
  svg.call(d3.zoom().on('zoom', zoomed));

  // code snippet from http://stackoverflow.com/questions/36614251
  var λ = d3.scaleLinear()
    .domain([-width, width])
    .range([-180, 180])

  var φ = d3.scaleLinear()
    .domain([-height, height])
    .range([90, -90]);

  svg.append('path')
    .datum(graticule)
    .attr('class', 'graticule')
    .attr('d', geoPath);

  function zoomed() {
    var transform = d3.event.transform;
    var r = {
      x: λ(transform.x),
      y: φ(transform.y)
    };
    var k = Math.sqrt(100 / projection.scale());
    if (d3.event.sourceEvent.wheelDelta) {
      projection.scale(scale * transform.k)
      transform.x = lastX;
      transform.y = lastY;
    } else {
      projection.rotate([origin.x + r.x, origin.y + r.y]);
      lastX = transform.x;
      lastY = transform.y;
    }
    updatePaths(svg, graticule, geoPath);
  };

};

function updatePaths(svg, graticule, geoPath) {
  svg.selectAll('path.graticule').datum(graticule).attr('d', geoPath);
};

createMap();
.graticule {
  fill: none;
  stroke: #777;
  stroke-width: .5px;
  stroke-opacity: .5;
}
<script src="https://d3js.org/d3.v4.min.js"></script>

解决方案2

如果您真的想使用d3.drag,这是我提出的解决方案:

首先,创建一个组来容纳你的地球:

var group = svg.append("g")

然后,将拖动调用到此组:

group.call(d3.drag().on('drag', dragged));

最后,在dragged函数中,使用d3.event.xd3.event.y

function dragged(d) {
    var r = {
        x: λ((d.x = d3.event.x)),
        y: φ((d.y = d3.event.y))
    };
    projection.rotate([origin.x + r.x, origin.y + r.y]);
    updatePaths(svg, graticule, geoPath);
};

另外,不要忘记将路径的pointer-events设置为all:由于填充为none,因此很难完全点击细线。

这是更新的代码,点击地球并拖动它:

function createMap() {
  var width = 400,
    height = 300,
    scale = 100,
    origin = {
      x: 55,
      y: -40
    };

  var svg = d3.select('body').append('svg')
    .style('width', 400)
    .style('height', 300)
    .style('border', '1px lightgray solid');

  var group = svg.append("g").datum({
    x: 0,
    y: 0
  })

  var projection = d3.geoOrthographic()
    .scale(scale)
    .translate([width / 2, height / 2])
    .rotate([origin.x, origin.y])
    .center([0, 0])
    .clipAngle(90);

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

  var graticule = d3.geoGraticule();

  // zoom AND rotate
  svg.call(d3.zoom().on('zoom', zoomed));

  group.call(d3.drag().on('drag', dragged));

  // code snippet from http://stackoverflow.com/questions/36614251
  var λ = d3.scaleLinear()
    .domain([-width, width])
    .range([-180, 180])

  var φ = d3.scaleLinear()
    .domain([-height, height])
    .range([90, -90]);

  group.append('path')
    .datum(graticule)
    .attr('class', 'graticule')
    .attr('d', geoPath);

  function dragged(d) {
    var r = {
      x: λ((d.x = d3.event.x)),
      y: φ((d.y = d3.event.y))
    };
    projection.rotate([origin.x + r.x, origin.y + r.y]);
    updatePaths(svg, graticule, geoPath);
  };

  function zoomed() {
    var transform = d3.event.transform;
    var k = Math.sqrt(100 / projection.scale());
    projection.scale(scale * transform.k)
    updatePaths(svg, graticule, geoPath);
  };

};

function updatePaths(svg, graticule, geoPath) {
  svg.selectAll('path.graticule').datum(graticule).attr('d', geoPath);
};

createMap();
.graticule {
  fill: none;
  stroke: #777;
  stroke-width: .5px;
  stroke-opacity: .5;
  pointer-events: all;
}
<script src="https://d3js.org/d3.v4.min.js"></script>