Javascript 缩放时如何保持d3正交投影居中

Javascript 缩放时如何保持d3正交投影居中,javascript,d3.js,topojson,Javascript,D3.js,Topojson,我正在尝试复制Jason Davies“旋转世界”可视化中显示的缩放功能() 我可以旋转和缩放,但是,如果旋转后进行缩放,则投影将以一定角度缩放(也就是说,如果将球体向左旋转15度,然后进行缩放,球体将不再保持在画布的中心位置)。下面是我的代码,任何帮助都将不胜感激 d3.select(window) .on("mousemove", mousemove) .on("mouseup", mouseup); var width = 960, height = 500; var

我正在尝试复制Jason Davies“旋转世界”可视化中显示的缩放功能()

我可以旋转和缩放,但是,如果旋转后进行缩放,则投影将以一定角度缩放(也就是说,如果将球体向左旋转15度,然后进行缩放,球体将不再保持在画布的中心位置)。下面是我的代码,任何帮助都将不胜感激

d3.select(window)
    .on("mousemove", mousemove)
    .on("mouseup", mouseup);

var width = 960,
height = 500;

var proj = d3.geo.orthographic()
    .scale(220)
    .translate([width / 2, height / 2])
    .clipAngle(90);


var path = d3.geo.path().projection(proj).pointRadius(1.5);

var graticule = d3.geo.graticule();

var svg = d3.select("#content").append("svg")
            .attr("width", width)
            .attr("height", height)
            .on("mousedown", mousedown);


var zoom = d3.behavior.zoom()
    .center([width / 2, height / 2])
    //.scaleExtent([.5, 10])
    .on("zoom", zoomed);

svg.call(zoom);


queue()
    .defer(d3.json, "/static/json/world.json")
    .await(ready);

function ready(error, world) {
    /*
        Define gradients
     */

    // ocean
    var ocean_fill = svg.append("defs").append("radialGradient")
        .attr("id", "ocean_fill")
        .attr("cx", "75%")
        .attr("cy", "25%");
    ocean_fill.append("stop").attr("offset", "5%").attr("stop-color", "#777");
    ocean_fill.append("stop").attr("offset", "100%").attr("stop-color", "#555");

    // globe highlight
    var globe_highlight = svg.append("defs").append("radialGradient")
        .attr("id", "globe_highlight")
        .attr("cx", "75%")
        .attr("cy", "25%");
    globe_highlight.append("stop")
        .attr("offset", "5%").attr("stop-color", "#bbb")
        .attr("stop-opacity","0.6");
    globe_highlight.append("stop")
        .attr("offset", "100%").attr("stop-color", "#999")
        .attr("stop-opacity","0.2");

    // globe shadow
    var globe_shading = svg.append("defs").append("radialGradient")
        .attr("id", "globe_shading")
        .attr("cx", "50%")
        .attr("cy", "40%");
    globe_shading.append("stop")
        .attr("offset","50%").attr("stop-color", "#333")
        .attr("stop-opacity","0");
    globe_shading.append("stop")
        .attr("offset","100%").attr("stop-color", "#111")
        .attr("stop-opacity","0.3");

    // drop shadow
    var drop_shadow = svg.append("defs").append("radialGradient")
        .attr("id", "drop_shadow")
        .attr("cx", "50%")
        .attr("cy", "50%");
    drop_shadow.append("stop")
        .attr("offset","20%").attr("stop-color", "#000")
        .attr("stop-opacity",".5");
    drop_shadow.append("stop")
        .attr("offset","100%").attr("stop-color", "#000")
        .attr("stop-opacity","0");

    /*
        Draw globe objects
     */

    // drop shadow
    svg.append("ellipse")
        .attr("cx", 440).attr("cy", 450)
        .attr("rx", proj.scale()*.90)
        .attr("ry", proj.scale()*.25)
        .attr("class", "noclicks")
        .style("fill", "url(#drop_shadow)");

    // globe
    svg.append("circle")
        .attr("cx", width / 2).attr("cy", height / 2)
        .attr("r", proj.scale())
        .attr("class", "noclicks")
        .style("fill", "url(#ocean_fill)");

    // land
    svg.append("path")
        .datum(topojson.feature(world, world.objects.land))
        .attr("class", "land")
        .attr("d", path);

    svg.append("path")
        .datum(graticule)
        .attr("class", "graticule noclicks")
        .attr("d", path);

    svg.append("circle")
        .attr("cx", width / 2).attr("cy", height / 2)
        .attr("r", proj.scale())
        .attr("class","noclicks")
        .style("fill", "url(#globe_highlight)");

    svg.append("circle")
        .attr("cx", width / 2).attr("cy", height / 2)
        .attr("r", proj.scale())
        .attr("class","noclicks")
        .style("fill", "url(#globe_shading)");

/*    svg.append("g").attr("class","points")
        .selectAll("text").data(places.features)
        .enter().append("path")
        .attr("class", "point")
        .attr("d", path);

    svg.append("g").attr("class","labels")
        .selectAll("text").data(places.features)
        .enter().append("text")
        .attr("class", "label")
        .text(function(d) { return d.properties.name })*/

    svg.append("g").attr("class","countries")
      .selectAll("path")
        .data(topojson.feature(world2, world2.objects.countries).features)
      .enter().append("path")
        .attr("d", path);
}

// modified from http://bl.ocks.org/1392560
var m0, o0;

function mousedown() {
    m0 = [d3.event.pageX, d3.event.pageY];
    o0 = proj.rotate();
    d3.event.preventDefault();
}
function mousemove() {
    if (m0) {
        var m1 = [d3.event.pageX, d3.event.pageY]
            , o1 = [o0[0] + (m1[0] - m0[0]) / 6, o0[1] + (m0[1] - m1[1]) / 6];
        o1[1] = o1[1] > 30  ? 30  :
            o1[1] < -30 ? -30 :
            o1[1];
        proj.rotate(o1);
        refresh();
    }
}
function mouseup() {
    if (m0) {
        mousemove();
        m0 = null;
    }
}

function refresh() {
    svg.selectAll(".land").attr("d", path);
    svg.selectAll(".countries path").attr("d", path);
    svg.selectAll(".graticule").attr("d", path);
    svg.selectAll(".point").attr("d", path);
    //position_labels();
}

var slast = 1;

function zoomed() {
    if (slast != d3.event.scale) {
        svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
        slast = d3.event.scale;
    };
}
d3.选择(窗口)
.on(“mousemove”,mousemove)
.on(“mouseup”,mouseup);
可变宽度=960,
高度=500;
var proj=d3.geo.orthographic()
.比例尺(220)
.translate([宽度/2,高度/2])
.clipAngle(90);
var path=d3.geo.path().proj.投影点半径(1.5);
var graticule=d3.geo.graticule();
var svg=d3.选择(“内容”).追加(“svg”)
.attr(“宽度”,宽度)
.attr(“高度”,高度)
.on(“mousedown”,mousedown);
var zoom=d3.behavior.zoom()
.中心([宽度/2,高度/2])
//.scaleExtent([5,10])
。打开(“缩放”,缩放);
调用(缩放);
队列()
.defer(d3.json,“/static/json/world.json”)
.等待(准备好);
功能就绪(错误,世界){
/*
定义渐变
*/
//海洋
var ocean_fill=svg.append(“defs”).append(“radialGradient”)
.attr(“id”、“海洋填充”)
.attr(“cx”,“75%”)
.属性(“cy”,“25%”);
ocean_fill.append(“stop”).attr(“offset”,“5%”).attr(“stop color”,“777”);
ocean_fill.append(“停止”).attr(“偏移”,“100%”).attr(“停止颜色”,“555”);
//全球亮点
var globe_highlight=svg.append(“defs”).append(“radialGradient”)
.attr(“id”、“全球突出显示”)
.attr(“cx”,“75%”)
.属性(“cy”,“25%”);
全局突出显示。附加(“停止”)
.attr(“偏移量”、“5%”)。attr(“停止颜色”、“bbb”)
.attr(“停止不透明度”,“0.6”);
全局突出显示。附加(“停止”)
.attr(“偏移量”,“100%”)。attr(“停止颜色”,“999”)
.attr(“停止不透明度”,“0.2”);
//球形阴影
var globe_shading=svg.append(“defs”).append(“radialGradient”)
.attr(“id”、“全球阴影”)
.attr(“cx”,“50%”)
.属性(“cy”,“40%”);
globe_shading.append(“停止”)
.attr(“偏移量”,“50%”)。attr(“停止颜色”,“#333”)
.attr(“停止不透明度”,“0”);
globe_shading.append(“停止”)
.attr(“偏移量”、“100%”)。attr(“停止颜色”、“111”)
.attr(“停止不透明度”,“0.3”);
//阴影
var drop_shadow=svg.append(“defs”).append(“radialGradient”)
.attr(“id”,“drop\u shadow”)
.attr(“cx”,“50%”)
.属性(“cy”,“50%”);
删除阴影。追加(“停止”)
.attr(“偏移量”,“20%”)。attr(“停止颜色”,“#000”)
.attr(“停止不透明度”和“.5”);
删除阴影。追加(“停止”)
.attr(“偏移量”,“100%”)。attr(“停止颜色”,“000”)
.attr(“停止不透明度”,“0”);
/*
绘制球体对象
*/
//阴影
追加(“椭圆”)
.attr(“cx”,440).attr(“cy”,450)
.attr(“rx”,项目比例()*.90)
.attr(“ry”,项目比例()*.25)
.attr(“类”、“noclicks”)
.style(“填充”、“url(#drop_shadow)”);
//地球仪
svg.append(“圆”)
.attr(“cx”,宽度/2)。attr(“cy”,高度/2)
.attr(“r”,项目比例()
.attr(“类”、“noclicks”)
.style(“填充”、“url(#海洋填充)”);
//土地
追加(“路径”)
.datum(topojson.feature(world,world.objects.land))
.attr(“类别”、“土地”)
.attr(“d”,路径);
追加(“路径”)
.基准面(分划)
.attr(“类”、“分划noclicks”)
.attr(“d”,路径);
svg.append(“圆”)
.attr(“cx”,宽度/2)。attr(“cy”,高度/2)
.attr(“r”,项目比例()
.attr(“类”、“noclicks”)
.style(“填充”、“url(#globe#u highlight)”);
svg.append(“圆”)
.attr(“cx”,宽度/2)。attr(“cy”,高度/2)
.attr(“r”,项目比例()
.attr(“类”、“noclicks”)
.style(“填充”、“url(#globe_shading)”);
/*svg.append(“g”).attr(“类”、“点”)
.选择所有(“文本”).数据(位置.特征)
.enter().append(“路径”)
.attr(“类”、“点”)
.attr(“d”,路径);
svg.append(“g”).attr(“类”、“标签”)
.选择所有(“文本”).数据(位置.特征)
.enter().append(“文本”)
.attr(“类别”、“标签”)
.text(函数(d){return d.properties.name})*/
svg.append(“g”).attr(“类”、“国家”)
.selectAll(“路径”)
.data(topojson.feature(world2,world2.objects.countries).features)
.enter().append(“路径”)
.attr(“d”,路径);
}
//修改自http://bl.ocks.org/1392560
var m0,o0;
函数mousedown(){
m0=[d3.event.pageX,d3.event.pageY];
o0=项目旋转();
d3.event.preventDefault();
}
函数mousemove(){
if(m0){
变量m1=[d3.event.pageX,d3.event.pageY]
,o1=[o0[0]+(m1[0]-m0[0])/6,o0[1]+(m0[1]-m1[1])/6];
o1[1]=o1[1]>30?30:
o1[1]<-30?-30:
o1[1];
项目旋转(o1);
刷新();
}
}
函数mouseup(){
if(m0){
mousemove();
m0=零;
}
}
函数刷新(){
svg.selectAll(“.land”).attr(“d”,路径);
svg.selectAll(“.countries path”).attr(“d”,path);
svg.selectAll(“.gracile”).attr(“d”,路径);
svg.selectAll(“.point”).attr(“d”,路径);
//定位标签();
}
var-slast=1;
函数缩放(){
if(slast!=d3.event.scale){
attr(“transform”、“translate”(+d3.event.translate+))比例(+d3.event.scale+);
slast=d3.event.scale;
};
}

居中和旋转正交投影,另请参见: