D3 滚动地图更新 d3.geo.circle,但不更新弧线
D3 Rolling map updates d3.geo.circle, but not arcs
我在 http://bl.ocks.org/patricksurry/6621971 的顶部 构建了一个可视化 。
基本上我在地图上添加了d3.geo.circle和d3.svg.arc。
我观察到的是,当我pan/zoom地图时,圆圈仍然完好无损,但圆弧消失了。
当我检查 chrome 中的元素时,我发现圆弧路径的属性 'd' 消失了,但是对于圆形路径,它得到了适当的更新。
任何人都可以帮助我理解为什么更新的投影应用于圆形路径元素而不是弧形。有没有办法强制重新投影弧线而不必删除并重新创建它们?
更新 1:因为这个问题似乎很难重现,而且 jsfiddle 不允许上传地理数据文件,我在这里发布源代码:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
background-color: lavender;
border: 1px solid black;
}
path {
fill: oldlace;
stroke: #666;
stroke-width: .5px;
}
path.circle {
fill: red;
stroke: #666;
stroke-width: .5px;
}
path.arc1 {
fill: green;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 1600,
height = 400,
rotate = 60, // so that [-60, 0] becomes initial center of projection
maxlat = 83; // clip northern and southern poles (infinite in mercator)
var projection = d3.geo.mercator()
.rotate([rotate,0])
.scale(1) // we'll scale up to match viewport shortly.
.translate([width/2, height/2]);
// find the top left and bottom right of current projection
function mercatorBounds(projection, maxlat) {
var yaw = projection.rotate()[0],
xymax = projection([-yaw+180-1e-6,-maxlat]),
xymin = projection([-yaw-180+1e-6, maxlat]);
return [xymin,xymax];
}
// set up the scale extent and initial scale for the projection
var b = mercatorBounds(projection, maxlat),
s = width/(b[1][0]-b[0][0]),
scaleExtent = [s, 10*s];
projection.scale(scaleExtent[0]);
var zoom = d3.behavior.zoom()
.scaleExtent(scaleExtent)
.scale(projection.scale())
.translate([0,0]) // not linked directly to projection
.on("zoom", redraw);
var path = d3.geo.path()
.projection(projection);
var svg = d3.selectAll('body')
.append('svg')
.attr('width',width)
.attr('height',height)
.attr('id', 'svg')
.call(zoom);
d3.json("js/data/world-110m2.json", function ready(error, world) {
// adding geo paths
svg.selectAll('path')
.data(topojson.feature(world, world.objects.countries).features)
.enter().append('path')
// adding a circle
svg.append("path")
.datum(d3.geo.circle().angle(2).origin([-10, 0]))
.attr("d", path)
.attr("class", "circle");
redraw();
// adding a pie arc
var r = 10;
var p = Math.PI * 2;
var arc1 = d3.svg.arc()
.innerRadius(r - 5)
.outerRadius(r)
.startAngle(0);
var arcData = JSON.parse('[{ "lon" : "0", "lat":"0", "endAngle":"6.4" }]');
var arcs1 = svg.selectAll("path.arc1");
arcs1 = arcs1.data(arcData)
.enter()
.append("path")
.attr("class", "arc1")
.attr("fill", "green")
.attr("transform", function(d, i) { return "translate(" + projection([d.lon, d.lat])[0] + ", " + projection([d.lon, d.lat])[1] + ")"; })
.attr("d", arc1);
});
// track last translation and scale event we processed
var tlast = [0,0],
slast = null;
function redraw() {
if (d3.event) {
var scale = d3.event.scale,
t = d3.event.translate;
console.log(d3.event.scale + " [" +d3.event.translate + "]");
// if scaling changes, ignore translation (otherwise touch zooms are weird)
if (scale != slast) {
projection.scale(scale);
} else {
var dx = t[0]-tlast[0],
dy = t[1]-tlast[1],
yaw = projection.rotate()[0],
tp = projection.translate();
// use x translation to rotate based on current scale
projection.rotate([yaw+360.*dx/width*scaleExtent[0]/scale, 0, 0]);
// use y translation to translate projection, clamped by min/max
var b = mercatorBounds(projection, maxlat);
if (b[0][1] + dy > 0) dy = -b[0][1];
else if (b[1][1] + dy < height) dy = height-b[1][1];
projection.translate([tp[0],tp[1]+dy]);
}
// save last values. resetting zoom.translate() and scale() would
// seem equivalent but doesn't seem to work reliably?
slast = scale;
tlast = t;
}
svg.selectAll('path').attr('d', path);
}
</script>
我终于弄清楚出了什么问题。我应该对 arc 元素应用转换。所以基本上,在我做的 redraw() 方法中:
var scaleRatio = 1;
function redraw() {
if (d3.event) {
var scale = d3.event.scale,
t = d3.event.translate;
//console.log(d3.event.scale + " [" +d3.event.translate + "]");
// if scaling changes, ignore translation (otherwise touch zooms are weird)
if (scale != slast) {
projection.scale(scale);
} else {
var dx = t[0]-tlast[0],
dy = t[1]-tlast[1],
yaw = projection.rotate()[0],
tp = projection.translate();
// use x translation to rotate based on current scale
projection.rotate([yaw+360.*dx/width*scaleExtent[0]/scale, 0, 0]);
// use y translation to translate projection, clamped by min/max
var b = mercatorBounds(projection, maxlat);
if (b[0][1] + dy > 0) dy = -b[0][1];
else if (b[1][1] + dy < height) dy = height-b[1][1];
projection.translate([tp[0],tp[1]+dy]);
}
// save last values. resetting zoom.translate() and scale() would
// seem equivalent but doesn't seem to work reliably?
if(slast==null)
scaleRatio=1;
else
scaleRatio = scaleRatio * (scale/slast);
console.log(slast+'-' + scaleRatio);
slast = scale;
tlast = t;
}
svg.selectAll('path').attr('d', path);
svg.selectAll("path.arc1")
.attr("transform", function(d, i) { return "translate(" + projection([d.lon, d.lat])[0] + ", " + projection([d.lon, d.lat])[1] + ")scale(" + scaleRatio + ")" })
.attr("d", arc1);
}
但是,关于为什么 svg.path 元素需要显式重新投影,这与 d3.geo.path 元素不同,我仍然存在知识空白。希望有人能帮助我。
我在 http://bl.ocks.org/patricksurry/6621971 的顶部 构建了一个可视化 。
基本上我在地图上添加了d3.geo.circle和d3.svg.arc。
我观察到的是,当我pan/zoom地图时,圆圈仍然完好无损,但圆弧消失了。
当我检查 chrome 中的元素时,我发现圆弧路径的属性 'd' 消失了,但是对于圆形路径,它得到了适当的更新。
任何人都可以帮助我理解为什么更新的投影应用于圆形路径元素而不是弧形。有没有办法强制重新投影弧线而不必删除并重新创建它们?
更新 1:因为这个问题似乎很难重现,而且 jsfiddle 不允许上传地理数据文件,我在这里发布源代码:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
background-color: lavender;
border: 1px solid black;
}
path {
fill: oldlace;
stroke: #666;
stroke-width: .5px;
}
path.circle {
fill: red;
stroke: #666;
stroke-width: .5px;
}
path.arc1 {
fill: green;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script>
var width = 1600,
height = 400,
rotate = 60, // so that [-60, 0] becomes initial center of projection
maxlat = 83; // clip northern and southern poles (infinite in mercator)
var projection = d3.geo.mercator()
.rotate([rotate,0])
.scale(1) // we'll scale up to match viewport shortly.
.translate([width/2, height/2]);
// find the top left and bottom right of current projection
function mercatorBounds(projection, maxlat) {
var yaw = projection.rotate()[0],
xymax = projection([-yaw+180-1e-6,-maxlat]),
xymin = projection([-yaw-180+1e-6, maxlat]);
return [xymin,xymax];
}
// set up the scale extent and initial scale for the projection
var b = mercatorBounds(projection, maxlat),
s = width/(b[1][0]-b[0][0]),
scaleExtent = [s, 10*s];
projection.scale(scaleExtent[0]);
var zoom = d3.behavior.zoom()
.scaleExtent(scaleExtent)
.scale(projection.scale())
.translate([0,0]) // not linked directly to projection
.on("zoom", redraw);
var path = d3.geo.path()
.projection(projection);
var svg = d3.selectAll('body')
.append('svg')
.attr('width',width)
.attr('height',height)
.attr('id', 'svg')
.call(zoom);
d3.json("js/data/world-110m2.json", function ready(error, world) {
// adding geo paths
svg.selectAll('path')
.data(topojson.feature(world, world.objects.countries).features)
.enter().append('path')
// adding a circle
svg.append("path")
.datum(d3.geo.circle().angle(2).origin([-10, 0]))
.attr("d", path)
.attr("class", "circle");
redraw();
// adding a pie arc
var r = 10;
var p = Math.PI * 2;
var arc1 = d3.svg.arc()
.innerRadius(r - 5)
.outerRadius(r)
.startAngle(0);
var arcData = JSON.parse('[{ "lon" : "0", "lat":"0", "endAngle":"6.4" }]');
var arcs1 = svg.selectAll("path.arc1");
arcs1 = arcs1.data(arcData)
.enter()
.append("path")
.attr("class", "arc1")
.attr("fill", "green")
.attr("transform", function(d, i) { return "translate(" + projection([d.lon, d.lat])[0] + ", " + projection([d.lon, d.lat])[1] + ")"; })
.attr("d", arc1);
});
// track last translation and scale event we processed
var tlast = [0,0],
slast = null;
function redraw() {
if (d3.event) {
var scale = d3.event.scale,
t = d3.event.translate;
console.log(d3.event.scale + " [" +d3.event.translate + "]");
// if scaling changes, ignore translation (otherwise touch zooms are weird)
if (scale != slast) {
projection.scale(scale);
} else {
var dx = t[0]-tlast[0],
dy = t[1]-tlast[1],
yaw = projection.rotate()[0],
tp = projection.translate();
// use x translation to rotate based on current scale
projection.rotate([yaw+360.*dx/width*scaleExtent[0]/scale, 0, 0]);
// use y translation to translate projection, clamped by min/max
var b = mercatorBounds(projection, maxlat);
if (b[0][1] + dy > 0) dy = -b[0][1];
else if (b[1][1] + dy < height) dy = height-b[1][1];
projection.translate([tp[0],tp[1]+dy]);
}
// save last values. resetting zoom.translate() and scale() would
// seem equivalent but doesn't seem to work reliably?
slast = scale;
tlast = t;
}
svg.selectAll('path').attr('d', path);
}
</script>
我终于弄清楚出了什么问题。我应该对 arc 元素应用转换。所以基本上,在我做的 redraw() 方法中:
var scaleRatio = 1;
function redraw() {
if (d3.event) {
var scale = d3.event.scale,
t = d3.event.translate;
//console.log(d3.event.scale + " [" +d3.event.translate + "]");
// if scaling changes, ignore translation (otherwise touch zooms are weird)
if (scale != slast) {
projection.scale(scale);
} else {
var dx = t[0]-tlast[0],
dy = t[1]-tlast[1],
yaw = projection.rotate()[0],
tp = projection.translate();
// use x translation to rotate based on current scale
projection.rotate([yaw+360.*dx/width*scaleExtent[0]/scale, 0, 0]);
// use y translation to translate projection, clamped by min/max
var b = mercatorBounds(projection, maxlat);
if (b[0][1] + dy > 0) dy = -b[0][1];
else if (b[1][1] + dy < height) dy = height-b[1][1];
projection.translate([tp[0],tp[1]+dy]);
}
// save last values. resetting zoom.translate() and scale() would
// seem equivalent but doesn't seem to work reliably?
if(slast==null)
scaleRatio=1;
else
scaleRatio = scaleRatio * (scale/slast);
console.log(slast+'-' + scaleRatio);
slast = scale;
tlast = t;
}
svg.selectAll('path').attr('d', path);
svg.selectAll("path.arc1")
.attr("transform", function(d, i) { return "translate(" + projection([d.lon, d.lat])[0] + ", " + projection([d.lon, d.lat])[1] + ")scale(" + scaleRatio + ")" })
.attr("d", arc1);
}
但是,关于为什么 svg.path 元素需要显式重新投影,这与 d3.geo.path 元素不同,我仍然存在知识空白。希望有人能帮助我。