d3.js 单击并应用缩放和平移以将位于目标分区内的点分布到触发的细分

d3.js click and apply zoom and pan to distribute points located inside a targeted division to the triggered subdivisions

基于,我制作了这个 此处的笔代码 points_in_subdivisons:点击屏幕上的区域(德国) 我们想提供从地图上的一个特写镜头到另一个特写镜头的流畅动画 通过使用缩小、平移、放大。

为了实现这个功能和 基于 Michael Rovinsky 的评论: 在函数 manipulate() 中,代码能够过滤和提取仅嵌入在目标和触发的细分(区域)内部的点,并排除外部的标记。 在函数 redraw() 中,输入退出模式运行良好。

var svg = d3.select("svg");

width = 960;
height = 500;
var dataArray  = [];
var mydataArray= []; 
var projection = d3.geoMercator();
var baseProjection = d3.geoMercator();

var path = d3.geoPath().projection(projection);
var gBackground = svg.append("g"); // appended first
var  gProvince = svg.append("g");  
var gDataPoints = svg.append("g"); // appended second
var ttooltip = d3.select("body").append("div")
      .attr("class", "ttooltip");
var csvPath="https://dl.dropbox.com/s/rb9trt4zy87ezi3/lonlat.csv?dl=0";

d3.csv(csvPath, function(error, data) {

  if (error) throw error;

  d3.json("https://gist.githubusercontent.com/rveciana/5919944/raw/2fef6be25d39ebeb3bead3933b2c9380497ddff4/nuts0.json", function(error, nuts0) {

  if (error) throw error;

  d3.json("https://gist.githubusercontent.com/rveciana/5919944/raw/2fef6be25d39ebeb3bead3933b2c9380497ddff4/nuts2.json", function(error, nuts2) {
    if (error) throw error;

  // convert topojson back to geojson
  var countries = topojson.feature(nuts0, nuts0.objects.nuts0);
  var regions = topojson.feature(nuts2, nuts2.objects.nuts2);
  baseProjection.fitSize([width,height],regions);
  projection.fitSize([width,height],regions);
  var color = d3.scaleLinear().range(["steelblue","darkblue"]).domain([0,countries.features.length]);
  var regionColor = d3.scaleLinear().range(["orange","red"]);

  baseProjection.fitSize([width,height],countries);
  projection.fitSize([width,height],countries);
  var featureCollectionCountries = { "type":"FeatureCollection", "features": countries.features };
  gBackground
    .attr("class", "country")
    .selectAll("path")
    .data(countries.features)
    .enter()
    .append("path")
    .attr("fill",function(d,i) { return color(i); })
    .attr("opacity",0.7)
    .attr("d", path)
    .style("stroke","black")
    .style("stroke-width",0)
    .on("mouseover", function() {
        d3.select(this)
          .style("stroke-width",1)
          .raise();
    })
    .on("mouseout", function(d,i) {
        d3.select(this)
          .style("stroke-width", 0 );
    })
    ///// now zoom in when clicked and show subdivisions:
    .on("click", function(d) {
        // remove all other subdivisions:
        d3.selectAll(".region")
          .remove();

        // add new features:


        var features = regions.features.filter(function(feature) { return feature.properties.nuts_id.substring(0,2) == d.properties.nuts_id; });



        regionColor.domain([0,features.length])

        gProvince.selectAll(null)
          .data(features)
          .enter()
          .append("path")
          .attr("class","region")
          .attr("fill", function(d,i) { return regionColor(i) })
          .attr("d", path)
          .style("stroke","black")
          .style("stroke-width",0)
          .on("click", function() {
            zoom(projection,baseProjection);
            d3.selectAll(".subdivision")
              .remove();          
          })
          .on("mouseover", function() {
                d3.select(this)
                  .style("stroke-width",1)
                  .raise();
          })
          .on("mouseout", function(d,i) {
                d3.select(this)
                  .style("stroke-width", 0 );
          })
          .raise()

        // zoom to selected features:
        var featureCollection = { "type":"FeatureCollection", "features": features }

        manipulate(data,features);
        redraw(featureCollection);

        var endProjection = d3.geoMercator();

        zoom(projection,endProjection.fitExtent([[50,50],[width-50,height-50]],featureCollection));

    }); 

    dataArray  = data;
    redraw(featureCollectionCountries);
  });
});
});

function zoom(startProjection,endProjection,middleProjection) {

  if(!middleProjection) {
      d3.selectAll("path")
        .transition()
        .attrTween("d", function(d) {
          var s = d3.interpolate(startProjection.scale(), endProjection.scale());
          var x = d3.interpolate(startProjection.translate()[0], endProjection.translate()[0]);
          var y = d3.interpolate(startProjection.translate()[1], endProjection.translate()[1]);
            return function(t) {
              projection
                .scale(s(t))
                .translate([x(t),y(t)])

              path.projection(projection);
              return path(d);
            }
         })
        .duration(1000);
    }
    else {
      d3.selectAll("path")
        .transition()
        .attrTween("d", function(d) {

          var s1 = d3.interpolate(startProjection.scale(),middleProjection.scale());
          var s2 = d3.interpolate(middleProjection.scale(),endProjection.scale()); 
          var x = d3.interpolate(startProjection.translate()[0], endProjection.translate()[0]);
          var y = d3.interpolate(startProjection.translate()[1], endProjection.translate()[1]);

          function s(t) {
            if (t < 0.5) return s1; return s2; 
          }

           return function(t) {                 
               projection
                .translate([x(t),y(t)])
                .scale(s(t)(t))

              path.projection(projection);
              return path(d);
            }
         })
        .duration(1500);    
    }

}


function redraw(featureCollection,type) {
        var mapG = d3.select('svg g.country');

        d3.selectAll('circle')
          .remove();

        let grp = gDataPoints
                  .attr("class", "circle")
                  .selectAll("circle")
                  .data(dataArray,function(d) { return d.NOM; })
        let grpEnter = grp.enter()
        let group = grpEnter
        group.append("circle")
             .attr('fill', 'rgba(135, 5, 151, 125)')
             .attr('stroke', 'black')
             .each(function(d) {
                                 if (d.lon === null ) return;
                                 if (isNaN(d.lon ))return;
                                 if (d.lat === null) return;
                                 if (isNaN(d.lat ))return;
                                 var pos = projection([parseFloat(d.lon), parseFloat(d.lat)]);
                                 d.cx = pos[0];
                                 d.cy = pos[1];
             })
             .attr("cx", function(d) {
                        return d.cx;
             })
            .attr("cy", function(d) {
                    return d.cy;
            })
            .attr("r",0.5)
            .on("mouseover", showTooltip)
            .on("mouseout", hideTooltip)          
            .on('mousemove', function(d) {
                                var xPos = d3.mouse(this)[0] - 15;
                                var yPos = d3.mouse(this)[1] - 55;
                                ttooltip.attr('transform', 'translate(' + xPos + ',' + yPos + ')');
                                ttooltip.style('opacity', 1);
                                var html = "<span>" + d.lon+ "</span>, <span>" + d.lat + "</span>";
                                 ttooltip.html(html);

            });
            // Setup each circle with a transition, each transition working on transform attribute,
            // and using the translateFn
            group
                .transition()
                .duration(2000)
                .attrTween("transform",function(d) {
                             return  mapG._groups[0][0] != null ? recenter(featureCollection): null;
                 });
            group.exit().remove() // exit > remove >  g

    }



    function recenter(featureCollection) {
        console.log('recentering');     

    };


function manipulate(data,features){      

                    dataArray= [];
                    mydataArray =[];        

                    data.forEach(function(ddd) 
                    {
                        features.forEach(function(feature) 
                        {
                            var polygoneOriginal =feature;

                            var points = [parseFloat(ddd.lon), parseFloat(ddd.lat)];

                            var isIn = d3.geoContains(polygoneOriginal, points);
                            if(isIn)
                            {

                               var element = ddd;
                                mydataArray.pushIfNotExist(element, function(e) { 
                                    return e.lat === element.lat && e.lon === element.lon   ; 
                                });

                            }

                        });


                    });

                    if(mydataArray.length>0)
                    {

                       var columnsArray= ["lon","lat"];
                       dataArray=mydataArray;
                       dataArray.columns = columnsArray;


                    }      
}


    function showTooltip(d) {
      var html = "<span>" + d.lon+ "</span>, <span>" + d.lat + "</span>";
      ttooltip.html(html);
      ttooltip
        .style("left", window.pageXOffset + d3.event.x + 12 + "px")
        .style("top", window.pageYOffset + d3.event.y + 12 + "px")
        .transition()
        .style("opacity", 1);

        return d3.select(this).attr('fill', 'rgba(103, 65, 114, 0.8)');

    }

function hideTooltip() {
      ttooltip
        .transition()
        .style("opacity", 0);
         return d3.select(this).attr('fill', 'rgba(103, 65, 114, 0.5)');
}


// check if an element exists in array using a comparer function
// comparer : function(currentElement)
Array.prototype.inArray = function(comparer) { 
    for(var i=0; i < this.length; i++) { 
        if(comparer(this[i])) return true; 
    }
    return false; 
}; 

// adds an element to the array if it does not already exist using a comparer 
// function
Array.prototype.pushIfNotExist = function(element, comparer) { 
    if (!this.inArray(comparer)) {
        this.push(element);
    }
};

我的问题如下:如何使缩放(点圆)充分发挥作用:
现在,在地图上点击 x y 点不按比例。 它们在背景中呈现为圆圈,我希望它们随地图移动。 这意味着如何应用相同的动画缩放(当通过单击分区触发细分时)以便目标细分内的那些点跟随过渡并随地图移动,我们可以看到圆圈点清晰分布 在每个正确的相应细分中充分

更新

Andrew Reid 描述了 如何使用 d3.js

实现平滑缩放

所以听从他的提示。

我在redraw()函数中添加了如下指令

            var mapG = d3.select('svg g.country');
             group
            .transition()
            .duration(2000)
            .attrTween("transform",function(d) {
                         return  mapG._groups[0][0] != null ? recenter(): null;
             });

然后我们应该将代码添加到实际上应该执行移动 recenter(featureCollection) 函数的函数到

           function recenter(featureCollection) {
           // TO ADD CODE TO BE IMPLEMENTED HERE 
           };

非常感谢您的配合、参与和帮助!

1- 要生成第一次迭代,请点击地区等于国家

//GENERATE FIRST MAP 
        dataArray  = data;
        redraw(); 

2-点击区域生成县,我们应该先在zoom函数中设置startprojection和endprojection,然后触发圆的重绘

  //zoom to selected provinces features:
   var countiesFeatureCollection = { "type":"FeatureCollection", "features": countiesFeatures }
  //manipulate counties And Redraw
   manipulateCounties(data,countiesFeatures);
   baseProjection.fitExtent([[50,50],[width-50,height-50]],countiesFeatureCollection);
   projection.fitExtent([[50,50],[width-50,height-50]],countiesFeatureCollection);
   redraw(countiesFeatureCollection,"counties");                                 
   if ( projection.translate().toString() === baseProjection.translate().toString() && projection.scale() === baseProjection.scale() ) 
   {
    zoom(baseProjection,projection.fitExtent([[50,50],[width-50,height-50]],countiesFeatureCollection));
    }
    else 
    {
       var endProjection = d3.geoMercator();
       zoom(projection,endProjection.fitExtent([[50,50],[width-50,height-50]],countiesFeatureCollection));
    }

3-同样的事情应该应用于社区

var endProjection = d3.geoMercator();
endProjection.fitExtent([[50,50],[width-50,height-50]],communesfeatureCollection);
projection.fitExtent([[50,50],[width-50,height-50]],communesfeatureCollection);
redraw(communesfeatureCollection,"communes");
if ( projection.translate().toString() === projectioncommune.translate().toString() && projection.scale() === projectioncommune.scale()){

  zoom(projectioncommune,projection.fitExtent([[50,50],[width-50,height-50]],communesfeatureCollection));
}

else {
      var endProjection = d3.geoMercator();
      zoom(projection,endProjection.fitExtent([[50,50],[width-50,height-50]],communesfeatureCollection));
}

4- 然后通过

重新初始化以转到第 1 步
      // start rendering points again
      baseProjection.fitSize([width,height],regions);
      projection.fitSize([width,height],regions);
      //GENERATE AGAIN THE FIRST MAP 
      dataArray  = data;
      redraw();                                                                 
      zoom(projection,baseProjection);

ATTACHED WORKING PEN