【问题标题】:Drag-rotate projection in D3 v4D3 v4 中的拖动旋转投影
【发布时间】:2017-05-04 02:42:14
【问题描述】:

我无法让我的正交投影通过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);
};

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

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

【问题讨论】:

  • 它看起来应该可以工作 - 你能发布一个完整的 sn-p 或 bl.ock 并拖动损坏吗?

标签: d3.js


【解决方案1】:

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

除此之外,没有d3.event.transform 用于拖动事件。根据API,这些是事件对象中的属性:

  • target - 关联的拖动行为。
  • type - 字符串“start”、“drag”或“end”;参见drag.on。
  • subject - 拖动主题,由 drag.subject 定义。
  • x - 主题的新 x 坐标;请参阅 drag.container。
  • y - 主题的新 y 坐标;请参阅 drag.container。
  • dx - 自上次拖动事件以来 x 坐标的变化。
  • dy - 自上次拖动事件以来 y 坐标的变化。
  • 标识符 - 字符串“鼠标”或数字触摸标识符。
  • active - 当前活动的拖动手势的数量
  • sourceEvent - 底层输入事件,例如 mousemove 或 触摸移动。

所以,您的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>

【讨论】:

  • 谢谢!解决方案 2 完美运行。但是使用解决方案 1,当我在缩放后尝试拖动时,位置会跳跃。你知道为什么吗?
  • @sirallen 这很奇怪,它没有跳到我身边。
  • 不是吗?尝试放大,然后单击任意位置 - jsfiddle.net/sirallen/pbzwm0fd
  • @sirallen 现在我明白了。这是因为 transform.xtransform.y 在缩放时发生变化。我现在没有时间更好地修复它(随意发布一个新问题),但这里有一个 hack:jsfiddle.net/usze5ej2。我也刚刚编辑了答案。
  • @PBrockmann 该代码不是我的,我只是为 OP 提供了解决方案。这样做时我不会整体修改他们的代码。
猜你喜欢
  • 2017-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-14
  • 2019-05-03
  • 2018-08-21
  • 2013-06-28
  • 1970-01-01
相关资源
最近更新 更多