d3.js - Hexbins 未显示在地理图 block map D3.JS 上

标签 d3.js hexagonal-tiles

我有一张用 d3-tile 创建的 map 。我添加了代码来显示根据经度和纬度从数据集中收集的十六进制。但六边形没有显示。我只找到了在十六进制中绘制普通笛卡尔数据的示例,而不是在用 d3-tile 制作的 map 上绘制十六进制的纬度经度。为什么我的 hexbin 没有显示?

以下是我定义投影、图 block 和六边形的方法:

var projection = d3.geo.mercator()
   .scale((1 << 22) / 2 / Math.PI) 
   .translate([width / 2, height / 2]);

var tile = d3.geo.tile()
  .size([width, height]);

// define hexbins
var hexbin = d3.hexbin()
   .size([width, height])
   .radius(4);

以下是我处理数据并将十六进制添加到 map 的方法:

data.forEach(function (d) {
    d.lat = +d.lat;
    d.lon = +d.lon;
});

points = [];

// x,y maps to lng,lat - ref[2]
data.forEach(function (d) {
   d.lat = +d.lat;
   d.lon = +d.lon;
   var x = projection([d.lon, d.lat])[0];
   var y = projection([d.lon, d.lat])[1];
   points.push([x, y]);
});

// bin coords
var bins = hexbin(points);
var bins_n = []; // points per hexbin
bins.forEach(function (d) {
    bins_n.push(d.length);
});

// second of two scales for linear hexagon fill - ref[1]
var extent = d3.extent(bins_n);
var fill_scale2 = d3.scale.linear()
    .domain([extent[0], extent[1]])
    .range([0, 1]);

hex.selectAll(".hexagon")
    .data(hexbin(points))
    .enter()
    .append("path")
    .attr("class", function (d) { return "hexagon bin_" + d.length; })
    .attr("d", hexbin.hexagon())
    .attr("transform", function (d) {
         return "translate(" + d.x + "," + d.y + ")";
     })
     .style("fill", function (d) {
        return fill_scale1(fill_scale2(d.length));
     });

最佳答案

我会用这个example来构建我的答案。这个可缩放的示例,与此 example 相反一、可缩放。

投影比例

首先,我将使用上面的示例来解释比例。它使用起始比例为 1/tau 的投影:世界的 2 π 弧度拉伸(stretch)到一个像素上。平移为 [0,0],因此 0°N、0°E 位于 SVG 的 [0,0] 处。 map 的比例和平移由 d3.zoom 管理:

projection.scale(transform.k / Math.PI / 2)
  .translate([transform.x, transform.y]);

由于 k 表示缩放系数,而我们的起始 map 宽度为 1,因此 k 表示 map 宽度和高度。除以 tau,我们就可以得到 map 上每个弧度对应的像素数。翻译使 map 居中。

在示例中看不到任何六边形的原因是因为您使用的比例将地球延伸到 4194304 像素宽(1<<22)的区域,但您的六边形仅延伸了大小的区域你的SVG。您看不到六边形,因为您的 SVG 范围仅代表一小部分地理范围 - 白令海以北的某些海洋区域。

另供引用: map 比例尺和 map 宽度之间的关系在所有 map 投影中并不一致

添加十六进制(已修复)

如果我们想要六角形分箱,无论缩放如何,分箱都保持相同的地理尺寸,我们可以设置半径和范围来反射(reflect)我们的初始投影(在应用缩放之前):

var hexbin = d3.hexbin()
    .radius(0.01)
    .extent([[-0.5, -0.5], [0.5, 0.5]]); 

然后我们可以使用初始投影传递投影点,并根据缩放比例变换六边形,同时根据缩放比例缩放六边形的笔划宽度:

var width = Math.max(960, window.innerWidth),
    height = Math.max(500, window.innerHeight);
	
var svg = d3.select("svg")
    .attr("width", width)
    .attr("height", height);
	
// Projection details:
var projection = d3.geoMercator()
    .scale(1 / Math.PI / 2)
    .translate([0, 0]);

var center = projection([0,0]);	

var tile = d3.tile()
    .size([width, height]);

// Zoom details:
var zoom = d3.zoom()
    .scaleExtent([1 << 11, 1 << 14])
    .on("zoom", zoomed);

// Layers for map
var raster = svg.append("g");  // holds tiles
var vector = svg.append("g");  // holds hexagons
var hexes;					   // to hold hexagons

// Hexbin:
var hexbin = d3.hexbin()
    .radius(0.01)
    .extent([[-0.5, -0.5], [0.5, 0.5]]); // extent of the one pixel projection.

var color = d3.scaleLinear()
	.range(["rgba(255,255,255,0.1)","orange"])
    .domain([0, 5]);

d3.json("https://unpkg.com/world-atlas@1/world/110m.json", function(error, world) {
  
 // Create some hexbin data:
 var land = topojson.feature(world, world.objects.land);
 var data = d3.range(500).map(function(d) {
	while(true) {
		var lat = Math.random() * 170 - 70;
		var lon = Math.random() * 360 - 180;
		if(d3.geoContains(land,[lon,lat])) return projection([lon,lat]);
	}
 })
 
 // Create hex bins:
 hexes = vector.selectAll()
  .data(hexbin(data))
  .enter()
  .append("path")
	.attr("d", hexbin.hexagon(0.0085))
  .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
  .attr("fill", function(d) { return color(d.length); })
	.attr("stroke","black")

  svg
   .call(zoom)
   .call(zoom.transform, d3.zoomIdentity
   .translate(width / 2, height / 2)
   .scale(1 << 11)
   .translate(-center[0], -center[1]));
 
});		  
		  
function zoomed() {
  var transform = d3.event.transform;
  
  var tiles = tile
      .scale(transform.k)
      .translate([transform.x, transform.y])
      ();

  // Update projection
  projection
      .scale(transform.k / Math.PI / 2)
      .translate([transform.x, transform.y]);

  // Update vector holding hexes:
  vector.attr("transform","translate("+[transform.x,transform.y]+")scale("+transform.k+")" )
    .attr("stroke-width", 1/transform.k);

  // Update tiles:
  var image = raster
      .attr("transform", stringify(tiles.scale, tiles.translate))
    .selectAll("image")
    .data(tiles, function(d) { return d; });

  image.exit().remove();

  image.enter().append("image")
      .attr("xlink:href", function(d) { return "http://" + "abc"[d[1] % 3] + ".tile.openstreetmap.org/" + d[2] + "/" + d[0] + "/" + d[1] + ".png"; })
      .attr("x", function(d) { return d[0] * 256; })
      .attr("y", function(d) { return d[1] * 256; })
      .attr("width", 256)
      .attr("height", 256);
}

function stringify(scale, translate) {
  var k = scale / 256, r = scale % 1 ? Number : Math.round;
  return "translate(" + r(translate[0] * scale) + "," + r(translate[1] * scale) + ") scale(" + k + ")";
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-tile.v0.0.min.js"></script>
<script src="https://d3js.org/d3-hexbin.v0.2.min.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>
<svg></svg>

添加十六进制(通过缩放更新)

但是,如果我们想在放大时更改 bin 比例,则代码可能会更容易一些,但计算上会更复杂。为此,我们在应用当前投影后根据点的投影坐标重新计算每次缩放的六边形:

var hexbin = d3.hexbin()
 .radius(30)
 .extent([[0,0], [width,height]]) // extent of projected data (displayed)
 .x(function(d) { return projection(d)[0]; })
 .y(function(d) { return projection(d)[1]; })

范围和半径反射(reflect)了整个 SVG 范围 - 应用缩放后我们将数据投影到的可见范围 - 我们想要添加六边形的范围。下面我重新计算每次缩放/平移的六角形:

var width = Math.max(960, window.innerWidth),
    height = Math.max(500, window.innerHeight);
	
var svg = d3.select("svg")
    .attr("width", width)
    .attr("height", height);
	
// Projection details:
var projection = d3.geoMercator()
    .scale(1 / Math.PI / 2)
    .translate([0, 0]);

var center = projection([0,0]);	

var tile = d3.tile()
    .size([width, height]);

// Zoom details:
var zoom = d3.zoom()
    .scaleExtent([1 << 11, 1 << 14])
    .on("zoom", zoomed);

// Layers for map
var raster = svg.append("g");  // holds tiles
var vector = svg.append("g");  // holds hexagons
var hexes;					   // to hold hexagons

// Hexbin:
var hexbin = d3.hexbin()
    .radius(30)
    .extent([[0,0], [width,height]]) // extent of projected data (displayed)
	.x(function(d) { return projection(d)[0]; })
	.y(function(d) { return projection(d)[1]; })

var color = d3.scaleLinear()
	.range(["rgba(255,255,255,0.1)","orange"])
    .domain([0, 5]);
	
var data;

d3.json("https://unpkg.com/world-atlas@1/world/110m.json", function(error, world) {
  
 // Create some hexbin data:
 var land = topojson.feature(world, world.objects.land);
 data = d3.range(500).map(function(d) {
	while(true) {
		var lat = Math.random() * 170 - 70;
		var lon = Math.random() * 360 - 180;
		if(d3.geoContains(land,[lon,lat])) return [lon,lat];
	}
 })
 
  svg
   .call(zoom)
   .call(zoom.transform, d3.zoomIdentity
   .translate(width / 2, height / 2)
   .scale(1 << 11)
   .translate(-center[0], -center[1]));
 
});		  
		  
function zoomed() {
  var transform = d3.event.transform;
  
  var tiles = tile
      .scale(transform.k)
      .translate([transform.x, transform.y])
      ();

  // Update projection
  projection
      .scale(transform.k / Math.PI / 2)
      .translate([transform.x, transform.y]);

  hexes = vector.selectAll("path")
   .data(hexbin(data)) ;
   
   hexes.exit().remove();
   
   hexes.enter()
   .append("path")
   .merge(hexes)
   .attr("d", hexbin.hexagon(29))
   .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
   .attr("fill", function(d) { return color(d.length); })
   .attr("stroke","black")

  // Update tiles:
  var image = raster
      .attr("transform", stringify(tiles.scale, tiles.translate))
    .selectAll("image")
    .data(tiles, function(d) { return d; });

  image.exit().remove();

  image.enter().append("image")
      .attr("xlink:href", function(d) { return "http://" + "abc"[d[1] % 3] + ".tile.openstreetmap.org/" + d[2] + "/" + d[0] + "/" + d[1] + ".png"; })
      .attr("x", function(d) { return d[0] * 256; })
      .attr("y", function(d) { return d[1] * 256; })
      .attr("width", 256)
      .attr("height", 256);
}

function stringify(scale, translate) {
  var k = scale / 256, r = scale % 1 ? Number : Math.round;
  return "translate(" + r(translate[0] * scale) + "," + r(translate[1] * scale) + ") scale(" + k + ")";
}
<svg></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-tile.v0.0.min.js"></script>
<script src="https://d3js.org/d3-hexbin.v0.2.min.js"></script>
<script src="https://unpkg.com/topojson-client@3"></script>

两个示例都在陆地上随机创建一些数据,这是加载缓慢的主要原因

最后的想法

这两个示例都有很多不足之处,使用 d3-tile 和六边形组织坐标空间的方法有点不太直观 - 并且可能需要一些时间来适应。但是,目前没有太多替代方案。

关于d3.js - Hexbins 未显示在地理图 block map D3.JS 上,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54632774/

相关文章:

d3.js - dc.js 中的双 Y 轴折线图

java - 如何制作六角形 JButton

delphi - 六角格计算

java - Java自动生成六边形网格

python - 如何将这个广泛的 Python 循环转换为 C++?

d3.js - nvd3.jsstackedAreaChart 图表转换。就像在 nvd3.org 上一样

javascript - 从中心缩放矩形元素

javascript - 向下滑动 SVG 元素的简单方法?

javascript - 在 D3.js/v4 中重绘缩放热图

r - 在每个单元格中构建具有自定义颜色的六角形热图