d3.js v5:无法获取要在美国地图上显示的数据点
d3.js v5 : Unable to get data points to show on map of US
我可以成功获取要渲染的美国地图,但是我的数据点却没有。 (据我所知,d3.js 对 v5 进行了一些重大更改,因此请注意,之前提出的类似问题不适用)
$(document).ready(function () {
var us = d3.json('https://unpkg.com/us-atlas@1/us/10m.json');
var meteoriteData = d3.json('https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/meteorite-strike-data.json');
var svg = d3.select("svg")
.style("width", "1110px")
.style("height", "714px");
var path = d3.geoPath();
Promise.all([us, meteoriteData]).then(function (values) {
var map = values[0];
console.log("map", map);
var meteoriteData = values[1];
console.log("meteoriteData", meteoriteData);
svg.append("g")
.attr("fill", "#ccc")
.selectAll("path")
.data(topojson.feature(map, map.objects.states).features)
.enter().append("path")
.attr("d", path),
svg.append("path")
.datum(topojson.mesh(map, map.objects.states, (a, b) => a !== b))
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-linejoin", "round")
.attr("pointer-events", "none")
.attr("d", path),
svg.selectAll("circle")
.data(meteoriteData)
.enter()
.append("circle")
.attr("class", "circles")
.attr("cx", function (d) { return ([d.geometry.coordinates[0], d.geometry.coordinates[1]])[1]; })
.attr("cy", function (d) { return ([d.geometry.coordinates[0], d.geometry.coordinates[1]])[0]; })
.attr("r", "1px");
});
});
可以在这里找到工作副本.. https://codepen.io/lady-ace/pen/PooORoy
这里有很多问题:
向 .data 传递一个数组
首先,当使用
svg.selectAll("circle")
.data(meteoriteData)
selectAll().data()
要求您传递函数或数组。在您的情况下,您需要将数据数组传递给它 - 但是, meteoriteData
是一个对象。它是具有以下结构的 geojson 特征集合:
{
"type": "FeatureCollection",
"features": [
/* features */
]
}
所有单独的 geojson 特征都在该对象内的数组中。要获取特征数组,在本例中代表流星的特征,我们需要使用:
svg.selectAll("circle")
.data(meteoriteData.features)
现在我们可以为要素集合中的每个要素创建一个圆圈。如果这样做,您可以在检查 SVG 元素时找到圆圈,但它们不会正确放置。
定位点
如果您进行上述更改,您将不会在正确的位置看到圆圈。您没有在此处正确定位圆圈:
.attr("cx", function (d) { return ([d.geometry.coordinates[0], d.geometry.coordinates[1]])[1]; })
这等同于:
.attr("cx", function(d) { return d.geometry.coordinates[1]; })
这里有两个问题:一,geojson 是 [longitude,latitude]
,或 [x,y]
(你在这里获取的是 y 坐标,但设置的是 x 值)。
但是,更大的问题是您没有投影数据。这是来自您的 geojson 的原始坐标:
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-113,
54.21667
]
...
你是把经度直接变成像素值。但是您的 geojson 使用 3D 坐标 space(3D 地球上的未投影点),单位为度。如果我们简单地将其转换为像素,cx = -113
,您的圆圈将出现在 SVG 左侧的屏幕外。
使用投影
您需要投影您的数据,为此我们将定义一个投影函数并使用类似的东西:
.attr("cx", function(d) { return projection(d.geometry.coordinates)[0] })
这会获取经度和纬度并将它们传递给投影函数,然后获取返回的 x 值并将其设置为 cx 的值。
投影采用三维 space 中的未投影坐标(地球上的点或 long/lat 对),单位为度,returns 二维中的点 space 以像素为单位。
但是,现在我们进入了最困难的部分:
你应该使用什么投影?
我们需要将我们的点与已经绘制的美国特征对齐,但您没有为美国各州定义投影 - 如果您没有向 d3.geoPath 提供投影,它使用一个空投影,它采用提供的坐标并将它们绘制在 SVG 上,就好像它们是像素坐标一样。没有转换发生。我知道你的美国特征是投影的,因为阿拉斯加不是它应该在的地方,topojson 中的坐标值超过经度 +/- 180 度,纬度 +/- 90 度,地图看起来像用阿尔伯斯投影投影。
如果 geoPath 没有使用 d3 投影来投影数据,但数据是按投影方式绘制的,则数据是预投影的 - 此 topojson 存储已经投影的点。
我们有投影数据(美国各州)和未投影数据(流星撞击),将它们混合始终是一个挑战。
这里的挑战是创建一个投影来复制用于创建美国各州数据集的投影函数。我们可以复制该投影,因为它是一个有问题的文件,会导致很多问题。解释了如何这样做 。但这比应该的更复杂:混合投影和未投影的数据是繁重的、不灵活的,而且比需要的更复杂。
我建议您对美国和流星都使用未投影的数据:
var projection = d3.geoAlbersUsa(); // create an Albers USA projection
var path = d3.geoPath().projection(projection); // set the projection for the path
我们可以用同样的方式绘制路径,前提是我们找到一个未投影的美国 topojson/geojson,并且我们可以放置点:
.attr("cx", function(d) { return projection(d.geometry.coordinates)[0]; })
.attr("cy", function(d) { return projection(d.geometry.coordinates)[1]; })
至于找到一个未投影的美国 topojson,here 的一个。
这里是 working version 使用上述方法并投影所有数据(我还过滤了特征以去除没有坐标的特征,那些美国阿尔伯斯可能会破坏的特征,因为它是一个复合投影).
我可以成功获取要渲染的美国地图,但是我的数据点却没有。 (据我所知,d3.js 对 v5 进行了一些重大更改,因此请注意,之前提出的类似问题不适用)
$(document).ready(function () {
var us = d3.json('https://unpkg.com/us-atlas@1/us/10m.json');
var meteoriteData = d3.json('https://raw.githubusercontent.com/FreeCodeCamp/ProjectReferenceData/master/meteorite-strike-data.json');
var svg = d3.select("svg")
.style("width", "1110px")
.style("height", "714px");
var path = d3.geoPath();
Promise.all([us, meteoriteData]).then(function (values) {
var map = values[0];
console.log("map", map);
var meteoriteData = values[1];
console.log("meteoriteData", meteoriteData);
svg.append("g")
.attr("fill", "#ccc")
.selectAll("path")
.data(topojson.feature(map, map.objects.states).features)
.enter().append("path")
.attr("d", path),
svg.append("path")
.datum(topojson.mesh(map, map.objects.states, (a, b) => a !== b))
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-linejoin", "round")
.attr("pointer-events", "none")
.attr("d", path),
svg.selectAll("circle")
.data(meteoriteData)
.enter()
.append("circle")
.attr("class", "circles")
.attr("cx", function (d) { return ([d.geometry.coordinates[0], d.geometry.coordinates[1]])[1]; })
.attr("cy", function (d) { return ([d.geometry.coordinates[0], d.geometry.coordinates[1]])[0]; })
.attr("r", "1px");
});
});
可以在这里找到工作副本.. https://codepen.io/lady-ace/pen/PooORoy
这里有很多问题:
向 .data 传递一个数组
首先,当使用
svg.selectAll("circle")
.data(meteoriteData)
selectAll().data()
要求您传递函数或数组。在您的情况下,您需要将数据数组传递给它 - 但是, meteoriteData
是一个对象。它是具有以下结构的 geojson 特征集合:
{
"type": "FeatureCollection",
"features": [
/* features */
]
}
所有单独的 geojson 特征都在该对象内的数组中。要获取特征数组,在本例中代表流星的特征,我们需要使用:
svg.selectAll("circle")
.data(meteoriteData.features)
现在我们可以为要素集合中的每个要素创建一个圆圈。如果这样做,您可以在检查 SVG 元素时找到圆圈,但它们不会正确放置。
定位点
如果您进行上述更改,您将不会在正确的位置看到圆圈。您没有在此处正确定位圆圈:
.attr("cx", function (d) { return ([d.geometry.coordinates[0], d.geometry.coordinates[1]])[1]; })
这等同于:
.attr("cx", function(d) { return d.geometry.coordinates[1]; })
这里有两个问题:一,geojson 是 [longitude,latitude]
,或 [x,y]
(你在这里获取的是 y 坐标,但设置的是 x 值)。
但是,更大的问题是您没有投影数据。这是来自您的 geojson 的原始坐标:
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [
-113,
54.21667
]
...
你是把经度直接变成像素值。但是您的 geojson 使用 3D 坐标 space(3D 地球上的未投影点),单位为度。如果我们简单地将其转换为像素,cx = -113
,您的圆圈将出现在 SVG 左侧的屏幕外。
使用投影
您需要投影您的数据,为此我们将定义一个投影函数并使用类似的东西:
.attr("cx", function(d) { return projection(d.geometry.coordinates)[0] })
这会获取经度和纬度并将它们传递给投影函数,然后获取返回的 x 值并将其设置为 cx 的值。
投影采用三维 space 中的未投影坐标(地球上的点或 long/lat 对),单位为度,returns 二维中的点 space 以像素为单位。
但是,现在我们进入了最困难的部分:
你应该使用什么投影?
我们需要将我们的点与已经绘制的美国特征对齐,但您没有为美国各州定义投影 - 如果您没有向 d3.geoPath 提供投影,它使用一个空投影,它采用提供的坐标并将它们绘制在 SVG 上,就好像它们是像素坐标一样。没有转换发生。我知道你的美国特征是投影的,因为阿拉斯加不是它应该在的地方,topojson 中的坐标值超过经度 +/- 180 度,纬度 +/- 90 度,地图看起来像用阿尔伯斯投影投影。
如果 geoPath 没有使用 d3 投影来投影数据,但数据是按投影方式绘制的,则数据是预投影的 - 此 topojson 存储已经投影的点。
我们有投影数据(美国各州)和未投影数据(流星撞击),将它们混合始终是一个挑战。
这里的挑战是创建一个投影来复制用于创建美国各州数据集的投影函数。我们可以复制该投影,因为它是一个有问题的文件,会导致很多问题。解释了如何这样做
我建议您对美国和流星都使用未投影的数据:
var projection = d3.geoAlbersUsa(); // create an Albers USA projection
var path = d3.geoPath().projection(projection); // set the projection for the path
我们可以用同样的方式绘制路径,前提是我们找到一个未投影的美国 topojson/geojson,并且我们可以放置点:
.attr("cx", function(d) { return projection(d.geometry.coordinates)[0]; })
.attr("cy", function(d) { return projection(d.geometry.coordinates)[1]; })
至于找到一个未投影的美国 topojson,here 的一个。
这里是 working version 使用上述方法并投影所有数据(我还过滤了特征以去除没有坐标的特征,那些美国阿尔伯斯可能会破坏的特征,因为它是一个复合投影).