D3.JS 地球上的点不随地球旋转
Points on D3.JS globe not rotating with globe
我正在使用 latitute/longitude 坐标数据集。我在 d3 中创建了一个地球仪来为每个 long/lat 坐标创建标记点。我已经成功地旋转了地球,但标记点没有旋转,我不确定如何将它们附加到旋转中?
谢谢
下面是我的代码:
<!DOCTYPE html>
<html>
<body>
<svg></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script>
var radius = 250;
let locations = [];
var projection = d3.geoOrthographic().scale(radius) .translate([radius, radius]).clipAngle(90);
var svg = d3.select("body").append("svg").attr("width", radius * 2).attr("height", radius * 2).on("mousedown", mousedown).on("mousemove", mousemove).on("mouseup", mouseup);
svg.append("circle") .attr("cx", radius).attr("cy", radius).attr("r", radius).style("fill", "none").style("stroke", "black");
var path = d3.geoPath().projection(projection);
const markerGroup = svg.append('g');
const center = [radius,radius];
d3.queue()
.defer(d3.json, 'https://gist.githubusercontent.com/mbostock/4090846/raw/d534aba169207548a8a3d670c9c2cc719ff05c47/world-110m.json')
.defer(d3.json, 'data/disdata.json')
.await((error, worldData, locationData) => {
svg.selectAll(".segment")
.data(topojson.feature(worldData, worldData.objects.countries).features)
.enter().append("path")
.attr("class", "segment")
.attr("d", path)
.style("stroke", "#888")
.style("stroke-width", "1px")
.style("fill", (d, i) => '#e5e5e5')
.style("opacity", ".6")
locations = locationData;
drawMarkers();
});
function trackballAngles(pt) {
var r = projection.scale();
var c = projection.translate();
var x = pt[0] - c[0], y = - (pt[1] - c[1]), ss = x*x + y*y;
var z = r*r > 2 * ss ? Math.sqrt(r*r - ss) : r*r / 2 / Math.sqrt(ss);
var lambda = Math.atan2(x, z) * 180 / Math.PI;
var phi = Math.atan2(y, z) * 180 / Math.PI
return [lambda, phi];
}
function composedRotation(λ, ϕ, γ, δλ, δϕ) {
λ = Math.PI / 180 * λ;
ϕ = Math.PI / 180 * ϕ;
γ = Math.PI / 180 * γ;
δλ = Math.PI / 180 * δλ;
δϕ = Math.PI / 180 * δϕ;
var sλ = Math.sin(λ), sϕ = Math.sin(ϕ), sγ = Math.sin(γ),
sδλ = Math.sin(δλ), sδϕ = Math.sin(δϕ),
cλ = Math.cos(λ), cϕ = Math.cos(ϕ), cγ = Math.cos(γ),
cδλ = Math.cos(δλ), cδϕ = Math.cos(δϕ);
var m00 = -sδλ * sλ * cϕ + (sγ * sλ * sϕ + cγ * cλ) * cδλ,
m01 = -sγ * cδλ * cϕ - sδλ * sϕ,
m02 = sδλ * cλ * cϕ - (sγ * sϕ * cλ - sλ * cγ) * cδλ,
m10 = - sδϕ * sλ * cδλ * cϕ - (sγ * sλ * sϕ + cγ * cλ) * sδλ * sδϕ - (sλ * sϕ * cγ - sγ * cλ) * cδϕ,
m11 = sδλ * sδϕ * sγ * cϕ - sδϕ * sϕ * cδλ + cδϕ * cγ * cϕ,
m12 = sδϕ * cδλ * cλ * cϕ + (sγ * sϕ * cλ - sλ * cγ) * sδλ * sδϕ + (sϕ * cγ * cλ + sγ * sλ) * cδϕ,
m20 = - sλ * cδλ * cδϕ * cϕ - (sγ * sλ * sϕ + cγ * cλ) * sδλ * cδϕ + (sλ * sϕ * cγ - sγ * cλ) * sδϕ,
m21 = sδλ * sγ * cδϕ * cϕ - sδϕ * cγ * cϕ - sϕ * cδλ * cδϕ,
m22 = cδλ * cδϕ * cλ * cϕ + (sγ * sϕ * cλ - sλ * cγ) * sδλ * cδϕ - (sϕ * cγ * cλ + sγ * sλ) * sδϕ;
if (m01 != 0 || m11 != 0) {
γ_ = Math.atan2(-m01, m11);
ϕ_ = Math.atan2(-m21, Math.sin(γ_) == 0 ? m11 / Math.cos(γ_) : - m01 / Math.sin(γ_));
λ_ = Math.atan2(-m20, m22);
} else {
γ_ = Math.atan2(m10, m00) - m21 * λ;
ϕ_ = - m21 * Math.PI / 2;
λ_ = λ;
}
return([λ_ * 180 / Math.PI, ϕ_ * 180 / Math.PI, γ_ * 180 / Math.PI]);
}
var m0 = null,
o0;
function mousedown() { // remember where the mouse was pressed, in canvas coords
m0 = trackballAngles(d3.mouse(svg.node()));
o0 = projection.rotate();
d3.event.preventDefault();
}
function mousemove() {
if (m0) { // if mousedown
var m1 = trackballAngles(d3.mouse(svg.node()));
o1 = composedRotation(o0[0], o0[1], o0[2], m1[0] - m0[0], m1[1] - m0[1])
projection.rotate(o1);
svg.selectAll("path").attr("d", path);
}
}
function mouseup() {
if (m0) {
mousemove();
m0 = null;
}
}
function drawMarkers() {
const markers = markerGroup.selectAll('circle')
.data(locations);
markers
.enter()
.append('circle')
.merge(markers)
.attr('cx', d => projection([d.Longitude, d.Latitude])[0])
.attr('cy', d => projection([d.Longitude, d.Latitude])[1])
.attr('fill', d => {
const coordinate = [d.Longitude, d.Latitude];
gdistance = d3.geoDistance(coordinate, projection.invert(center));
return gdistance > 1.57 ? 'none' : 'steelblue';
})
.attr('r', 7);
markerGroup.each(function () {
this.parentNode.appendChild(this);
});
}
</script>
</body>
</html>
在鼠标移动时更新路径:
svg.selectAll("path").attr("d", path);
但是你不更新你的圈子,你可以通过重新计算 cx 和 cy 来更新:
markerGroup.selectAll('circle')
.attr('cx', d => projection([d.Longitude, d.Latitude])[0])
.attr('cy', d => projection([d.Longitude, d.Latitude])[1])
因此路径随鼠标移动,但圆圈保持不动。
我正在使用 latitute/longitude 坐标数据集。我在 d3 中创建了一个地球仪来为每个 long/lat 坐标创建标记点。我已经成功地旋转了地球,但标记点没有旋转,我不确定如何将它们附加到旋转中?
谢谢
下面是我的代码:
<!DOCTYPE html>
<html>
<body>
<svg></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script>
var radius = 250;
let locations = [];
var projection = d3.geoOrthographic().scale(radius) .translate([radius, radius]).clipAngle(90);
var svg = d3.select("body").append("svg").attr("width", radius * 2).attr("height", radius * 2).on("mousedown", mousedown).on("mousemove", mousemove).on("mouseup", mouseup);
svg.append("circle") .attr("cx", radius).attr("cy", radius).attr("r", radius).style("fill", "none").style("stroke", "black");
var path = d3.geoPath().projection(projection);
const markerGroup = svg.append('g');
const center = [radius,radius];
d3.queue()
.defer(d3.json, 'https://gist.githubusercontent.com/mbostock/4090846/raw/d534aba169207548a8a3d670c9c2cc719ff05c47/world-110m.json')
.defer(d3.json, 'data/disdata.json')
.await((error, worldData, locationData) => {
svg.selectAll(".segment")
.data(topojson.feature(worldData, worldData.objects.countries).features)
.enter().append("path")
.attr("class", "segment")
.attr("d", path)
.style("stroke", "#888")
.style("stroke-width", "1px")
.style("fill", (d, i) => '#e5e5e5')
.style("opacity", ".6")
locations = locationData;
drawMarkers();
});
function trackballAngles(pt) {
var r = projection.scale();
var c = projection.translate();
var x = pt[0] - c[0], y = - (pt[1] - c[1]), ss = x*x + y*y;
var z = r*r > 2 * ss ? Math.sqrt(r*r - ss) : r*r / 2 / Math.sqrt(ss);
var lambda = Math.atan2(x, z) * 180 / Math.PI;
var phi = Math.atan2(y, z) * 180 / Math.PI
return [lambda, phi];
}
function composedRotation(λ, ϕ, γ, δλ, δϕ) {
λ = Math.PI / 180 * λ;
ϕ = Math.PI / 180 * ϕ;
γ = Math.PI / 180 * γ;
δλ = Math.PI / 180 * δλ;
δϕ = Math.PI / 180 * δϕ;
var sλ = Math.sin(λ), sϕ = Math.sin(ϕ), sγ = Math.sin(γ),
sδλ = Math.sin(δλ), sδϕ = Math.sin(δϕ),
cλ = Math.cos(λ), cϕ = Math.cos(ϕ), cγ = Math.cos(γ),
cδλ = Math.cos(δλ), cδϕ = Math.cos(δϕ);
var m00 = -sδλ * sλ * cϕ + (sγ * sλ * sϕ + cγ * cλ) * cδλ,
m01 = -sγ * cδλ * cϕ - sδλ * sϕ,
m02 = sδλ * cλ * cϕ - (sγ * sϕ * cλ - sλ * cγ) * cδλ,
m10 = - sδϕ * sλ * cδλ * cϕ - (sγ * sλ * sϕ + cγ * cλ) * sδλ * sδϕ - (sλ * sϕ * cγ - sγ * cλ) * cδϕ,
m11 = sδλ * sδϕ * sγ * cϕ - sδϕ * sϕ * cδλ + cδϕ * cγ * cϕ,
m12 = sδϕ * cδλ * cλ * cϕ + (sγ * sϕ * cλ - sλ * cγ) * sδλ * sδϕ + (sϕ * cγ * cλ + sγ * sλ) * cδϕ,
m20 = - sλ * cδλ * cδϕ * cϕ - (sγ * sλ * sϕ + cγ * cλ) * sδλ * cδϕ + (sλ * sϕ * cγ - sγ * cλ) * sδϕ,
m21 = sδλ * sγ * cδϕ * cϕ - sδϕ * cγ * cϕ - sϕ * cδλ * cδϕ,
m22 = cδλ * cδϕ * cλ * cϕ + (sγ * sϕ * cλ - sλ * cγ) * sδλ * cδϕ - (sϕ * cγ * cλ + sγ * sλ) * sδϕ;
if (m01 != 0 || m11 != 0) {
γ_ = Math.atan2(-m01, m11);
ϕ_ = Math.atan2(-m21, Math.sin(γ_) == 0 ? m11 / Math.cos(γ_) : - m01 / Math.sin(γ_));
λ_ = Math.atan2(-m20, m22);
} else {
γ_ = Math.atan2(m10, m00) - m21 * λ;
ϕ_ = - m21 * Math.PI / 2;
λ_ = λ;
}
return([λ_ * 180 / Math.PI, ϕ_ * 180 / Math.PI, γ_ * 180 / Math.PI]);
}
var m0 = null,
o0;
function mousedown() { // remember where the mouse was pressed, in canvas coords
m0 = trackballAngles(d3.mouse(svg.node()));
o0 = projection.rotate();
d3.event.preventDefault();
}
function mousemove() {
if (m0) { // if mousedown
var m1 = trackballAngles(d3.mouse(svg.node()));
o1 = composedRotation(o0[0], o0[1], o0[2], m1[0] - m0[0], m1[1] - m0[1])
projection.rotate(o1);
svg.selectAll("path").attr("d", path);
}
}
function mouseup() {
if (m0) {
mousemove();
m0 = null;
}
}
function drawMarkers() {
const markers = markerGroup.selectAll('circle')
.data(locations);
markers
.enter()
.append('circle')
.merge(markers)
.attr('cx', d => projection([d.Longitude, d.Latitude])[0])
.attr('cy', d => projection([d.Longitude, d.Latitude])[1])
.attr('fill', d => {
const coordinate = [d.Longitude, d.Latitude];
gdistance = d3.geoDistance(coordinate, projection.invert(center));
return gdistance > 1.57 ? 'none' : 'steelblue';
})
.attr('r', 7);
markerGroup.each(function () {
this.parentNode.appendChild(this);
});
}
</script>
</body>
</html>
在鼠标移动时更新路径:
svg.selectAll("path").attr("d", path);
但是你不更新你的圈子,你可以通过重新计算 cx 和 cy 来更新:
markerGroup.selectAll('circle')
.attr('cx', d => projection([d.Longitude, d.Latitude])[0])
.attr('cy', d => projection([d.Longitude, d.Latitude])[1])
因此路径随鼠标移动,但圆圈保持不动。