D3 V4 在美国地图中正确放置泡泡
D3 V4 Properly placing a bubble in the US Map
我正在创建一张美国地图,我有美国一些地方的一系列实际坐标。我想在地图上的正确位置放置一个点或气泡。我如何scale/translate这些?
这是我得到的:
根据我的尝试:
function USAPlot(divid, data) {
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 1040 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
// formatting the data
data.forEach(function (d) {
d.loc = d.location;
d.count = d.count;
d.lat = d.latitude;
d.lon = d.longitude;
});
var svg = d3.select(divid)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
;
var path = d3.geoPath();
var projection = d3.geoMercator()
.scale(200)
.translate([margin.left + width / 2, margin.top + height / 2])
d3.json("https://d3js.org/us-10m.v1.json", function (error, us) {
if (error) throw error;
svg.append("g")
.attr("class", "states")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path);
svg.append("path")
.attr("class", "state-borders")
.attr("d", path(topojson.mesh(us, us.objects.states, function (a, b) { return a !== b; })));
});
svg.selectAll("myCircles")
.data(data)
.enter()
.append("circle")
.attr("cx", function (d) { return projection([d.lon, d.lat])[0]; })
.attr("cy", function (d) { return projection([d.lon, d.lat])[1]; })
.attr("r", 14) //first testing with fixed radius and then will scale acccording to count
.style("fill", "69b3a2")
.attr("stroke", "#69b3a2")
.attr("stroke-width", 3)
.attr("fill-opacity", 1);
}
我不知道这些气泡是否落在实际位置 - 我肯定在寻找。
至于查看功能是否正确对齐的测试方法,请尝试放置易于识别的地标,我在下面使用西雅图和迈阿密 - 它们位于感兴趣区域的两侧,而且应该很容易判断它们是否在错误的地方(在水中或内陆)。
我不确定它们应该落在哪里,因为我没有坐标,但我可以告诉你它们不在它们应该在的位置。
我之所以知道这一点,是因为您对数据使用了两种不同的投影。
墨卡托投影
您定义其中一个投影并使用它来定位点:
var projection = d3.geoMercator()
.scale(200)
.translate([margin.left + width / 2, margin.top + height / 2])
这是一个以 [0°,0°] 为中心的墨卡托投影(默认)。这是使用该投影投影的世界(具有边距和相同大小的 SVG):
D3 GᴇᴏMᴇʀᴄᴀᴛᴏʀ Wɪᴛʜ Cᴇɴᴛᴇʀ [0,0] ᴀɴᴅ Sᴄᴀʟᴇ 200
您正在根据此投影投影圆的坐标。
为了可重现性,这里有一个片段 - 您应该全屏查看:
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 1040 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/land-50m.json").then(function(json) {
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
var projection = d3.geoMercator()
.scale(200)
.translate([margin.left + width / 2, margin.top + height / 2])
var path = d3.geoPath().projection(projection);
svg.append("g")
.attr("class", "states")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(json, json.objects.land).features)
.enter().append("path")
.attr("d", path);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.js"></script>
一个神秘的投影
第二个投影不明显。如果您查看用于创建上述图像的代码片段,您会注意到它将投影分配给路径:
var path = d3.geoPath().projection(projection);
路径将每个地理坐标(球面 latitude/longitude 对)转换为屏幕上的正确坐标(笛卡尔像素 x,y 值):坐标 [-17°,85 °] 将转换为 [100px,50px].
在您的问题中,您只需使用:
var path = d3.geoPath();
您没有为路径分配投影 - 所以 d3.geoPath() 只是在 geojson/topojson 中绘制每个 vertice/point,就好像坐标包含像素坐标一样: geojson/topojson 中的 [100px,50px] 绘制在 SVG 上 x=100,y=50 处。
尽管没有使用投影,但您的美国各州绘图符合预期。为什么?因为 geojson/topojson 已经 投影了。由于它是预先投影的,所以我们在用 D3 绘制它时不需要使用投影。
预投影几何图形很有用,因为它需要较少的计算来绘制,从而加快渲染速度,但代价是灵活性较低(参见 )。
如果我们用您使用 d3.geoProjection 投影的几何体叠加您的预投影几何体,我们得到:
当然,你可以看出两者之间没有相同的地方。因此,您没有投影点,以便它们正确覆盖预先投影的几何图形。
Cᴏᴍᴘᴀʀɪsᴏɴ ʙᴇᴛᴡᴇᴇɴ ᴛʜᴇ ᴛᴡᴏ ᴘʀᴏᴊᴇᴄᴛɪᴏɴs
要重现的片段:
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 1040 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/land-50m.json").then(function(json) {
var projection = d3.geoMercator()
.scale(200)
.translate([margin.left + width / 2, margin.top + height / 2])
var path = d3.geoPath().projection(projection);
svg.append("g")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(json, json.objects.land).features)
.enter().append("path")
.attr("d", path);
})
d3.json("https://d3js.org/us-10m.v1.json").then(function(us) {
var path = d3.geoPath();
svg.append("g")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.js"></script>
不满意的解决方案
如果没有元数据解释 geojson/topojson 使用的投影和坐标系,我们通常无法复制该投影以覆盖其他特征。
然而,在这种情况下,如果我们仔细查看绘制的美国各州,我们可以看到使用了阿尔伯斯投影来预先投影各州轮廓。
有时,我们可以猜测投影参数。由于我对这个文件 () 相当熟悉,我可以告诉你它使用以下参数:
d3.geoAlbersUsa()
.scale(d3.geoAlbersUsa().scale()*6/5)
.translate([480,300]);
这是一个显示迈阿密和西雅图重叠的示例:
var width = 960,height = 600;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
d3.json("https://d3js.org/us-10m.v1.json").then(function(us) {
var path = d3.geoPath();
var projection = d3.geoAlbersUsa()
.scale(d3.geoAlbersUsa().scale()*6/5)
.translate([width/2,height/2]);
svg.append("g")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path);
var places = [
[-122.3367534,47.5996582],
[-80.1942949,25.7645783]
]
svg.selectAll(null)
.data(places)
.enter()
.append("circle")
.attr("r", 3)
.attr("transform", function(d) {
return "translate("+projection(d)+")";
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.js"></script>
但是,这有一个缺点,即在采用其他屏幕尺寸、平移、中心、比例等时非常迟钝。预投影几何与未投影几何结合使用时也会产生很多混乱。例如,此 显示了在正确调整预投影几何体的大小和居中时常见的挫败感。
更好的解决方案
更好的解决方案是对所有内容使用一个投影。要么先预先投影所有内容(这有点复杂),要么即时投影所有内容(对于浏览器来说真的不需要那么长时间)。这只是在修改可视化或地理数据时更清晰、更容易。
要以相同方式投影所有内容,您需要确保所有数据都未投影,也就是说它使用 lat/long 对作为其坐标/坐标 space。由于您的美国 json 是预先预测的,我们需要找到另一个,也许:
我们只是 运行 通过投影的一切:
Snippet 不会加载资源,而是here's一个bl.ock,代码如下:
var width =960,height = 600;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
d3.json("us.json").then(function(us) {
var projection = d3.geoAlbersUsa()
.scale(150)
.translate([width/2,height/2]);
var path = d3.geoPath().projection(projection);
svg.append("g")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path);
var places = [
[-122.3367534,47.5996582],
[-80.1942949,25.7645783]
]
svg.selectAll(null)
.data(places)
.enter()
.append("circle")
.attr("r", 3)
.attr("transform", function(d) {
return "translate("+projection(d)+")";
})
})
我正在创建一张美国地图,我有美国一些地方的一系列实际坐标。我想在地图上的正确位置放置一个点或气泡。我如何scale/translate这些?
这是我得到的:
根据我的尝试:
function USAPlot(divid, data) {
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 1040 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
// formatting the data
data.forEach(function (d) {
d.loc = d.location;
d.count = d.count;
d.lat = d.latitude;
d.lon = d.longitude;
});
var svg = d3.select(divid)
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
;
var path = d3.geoPath();
var projection = d3.geoMercator()
.scale(200)
.translate([margin.left + width / 2, margin.top + height / 2])
d3.json("https://d3js.org/us-10m.v1.json", function (error, us) {
if (error) throw error;
svg.append("g")
.attr("class", "states")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path);
svg.append("path")
.attr("class", "state-borders")
.attr("d", path(topojson.mesh(us, us.objects.states, function (a, b) { return a !== b; })));
});
svg.selectAll("myCircles")
.data(data)
.enter()
.append("circle")
.attr("cx", function (d) { return projection([d.lon, d.lat])[0]; })
.attr("cy", function (d) { return projection([d.lon, d.lat])[1]; })
.attr("r", 14) //first testing with fixed radius and then will scale acccording to count
.style("fill", "69b3a2")
.attr("stroke", "#69b3a2")
.attr("stroke-width", 3)
.attr("fill-opacity", 1);
}
我不知道这些气泡是否落在实际位置 - 我肯定在寻找。
至于查看功能是否正确对齐的测试方法,请尝试放置易于识别的地标,我在下面使用西雅图和迈阿密 - 它们位于感兴趣区域的两侧,而且应该很容易判断它们是否在错误的地方(在水中或内陆)。
我不确定它们应该落在哪里,因为我没有坐标,但我可以告诉你它们不在它们应该在的位置。
我之所以知道这一点,是因为您对数据使用了两种不同的投影。
墨卡托投影
您定义其中一个投影并使用它来定位点:
var projection = d3.geoMercator()
.scale(200)
.translate([margin.left + width / 2, margin.top + height / 2])
这是一个以 [0°,0°] 为中心的墨卡托投影(默认)。这是使用该投影投影的世界(具有边距和相同大小的 SVG):
D3 GᴇᴏMᴇʀᴄᴀᴛᴏʀ Wɪᴛʜ Cᴇɴᴛᴇʀ [0,0] ᴀɴᴅ Sᴄᴀʟᴇ 200
您正在根据此投影投影圆的坐标。
为了可重现性,这里有一个片段 - 您应该全屏查看:
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 1040 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/land-50m.json").then(function(json) {
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
var projection = d3.geoMercator()
.scale(200)
.translate([margin.left + width / 2, margin.top + height / 2])
var path = d3.geoPath().projection(projection);
svg.append("g")
.attr("class", "states")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(json, json.objects.land).features)
.enter().append("path")
.attr("d", path);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.js"></script>
一个神秘的投影
第二个投影不明显。如果您查看用于创建上述图像的代码片段,您会注意到它将投影分配给路径:
var path = d3.geoPath().projection(projection);
路径将每个地理坐标(球面 latitude/longitude 对)转换为屏幕上的正确坐标(笛卡尔像素 x,y 值):坐标 [-17°,85 °] 将转换为 [100px,50px].
在您的问题中,您只需使用:
var path = d3.geoPath();
您没有为路径分配投影 - 所以 d3.geoPath() 只是在 geojson/topojson 中绘制每个 vertice/point,就好像坐标包含像素坐标一样: geojson/topojson 中的 [100px,50px] 绘制在 SVG 上 x=100,y=50 处。
尽管没有使用投影,但您的美国各州绘图符合预期。为什么?因为 geojson/topojson 已经 投影了。由于它是预先投影的,所以我们在用 D3 绘制它时不需要使用投影。
预投影几何图形很有用,因为它需要较少的计算来绘制,从而加快渲染速度,但代价是灵活性较低(参见
如果我们用您使用 d3.geoProjection 投影的几何体叠加您的预投影几何体,我们得到:
当然,你可以看出两者之间没有相同的地方。因此,您没有投影点,以便它们正确覆盖预先投影的几何图形。
Cᴏᴍᴘᴀʀɪsᴏɴ ʙᴇᴛᴡᴇᴇɴ ᴛʜᴇ ᴛᴡᴏ ᴘʀᴏᴊᴇᴄᴛɪᴏɴs
要重现的片段:
var margin = { top: 20, right: 20, bottom: 30, left: 50 },
width = 1040 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svg = d3.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/land-50m.json").then(function(json) {
var projection = d3.geoMercator()
.scale(200)
.translate([margin.left + width / 2, margin.top + height / 2])
var path = d3.geoPath().projection(projection);
svg.append("g")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(json, json.objects.land).features)
.enter().append("path")
.attr("d", path);
})
d3.json("https://d3js.org/us-10m.v1.json").then(function(us) {
var path = d3.geoPath();
svg.append("g")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.js"></script>
不满意的解决方案
如果没有元数据解释 geojson/topojson 使用的投影和坐标系,我们通常无法复制该投影以覆盖其他特征。
然而,在这种情况下,如果我们仔细查看绘制的美国各州,我们可以看到使用了阿尔伯斯投影来预先投影各州轮廓。
有时,我们可以猜测投影参数。由于我对这个文件 () 相当熟悉,我可以告诉你它使用以下参数:
d3.geoAlbersUsa()
.scale(d3.geoAlbersUsa().scale()*6/5)
.translate([480,300]);
这是一个显示迈阿密和西雅图重叠的示例:
var width = 960,height = 600;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
d3.json("https://d3js.org/us-10m.v1.json").then(function(us) {
var path = d3.geoPath();
var projection = d3.geoAlbersUsa()
.scale(d3.geoAlbersUsa().scale()*6/5)
.translate([width/2,height/2]);
svg.append("g")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path);
var places = [
[-122.3367534,47.5996582],
[-80.1942949,25.7645783]
]
svg.selectAll(null)
.data(places)
.enter()
.append("circle")
.attr("r", 3)
.attr("transform", function(d) {
return "translate("+projection(d)+")";
})
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/3.0.2/topojson.js"></script>
但是,这有一个缺点,即在采用其他屏幕尺寸、平移、中心、比例等时非常迟钝。预投影几何与未投影几何结合使用时也会产生很多混乱。例如,此
更好的解决方案
更好的解决方案是对所有内容使用一个投影。要么先预先投影所有内容(这有点复杂),要么即时投影所有内容(对于浏览器来说真的不需要那么长时间)。这只是在修改可视化或地理数据时更清晰、更容易。
要以相同方式投影所有内容,您需要确保所有数据都未投影,也就是说它使用 lat/long 对作为其坐标/坐标 space。由于您的美国 json 是预先预测的,我们需要找到另一个,也许:
我们只是 运行 通过投影的一切:
Snippet 不会加载资源,而是here's一个bl.ock,代码如下:
var width =960,height = 600;
var svg = d3.select("body")
.append("svg")
.attr("width",width)
.attr("height",height);
d3.json("us.json").then(function(us) {
var projection = d3.geoAlbersUsa()
.scale(150)
.translate([width/2,height/2]);
var path = d3.geoPath().projection(projection);
svg.append("g")
.attr("fill-opacity", 0.4)
.selectAll("path")
.data(topojson.feature(us, us.objects.states).features)
.enter().append("path")
.attr("d", path);
var places = [
[-122.3367534,47.5996582],
[-80.1942949,25.7645783]
]
svg.selectAll(null)
.data(places)
.enter()
.append("circle")
.attr("r", 3)
.attr("transform", function(d) {
return "translate("+projection(d)+")";
})
})