Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Based on the response and example made by Andrew Reid, I produced this pen code here points_in_subdivisons: on clicking on areas(Germany) on the screen We want to offer a smooth animation from one close-up on the map to another by using ZOOM OUT, PAN, ZOOM IN.

  • I have many divisions(countries) on Country level and then many sub-divisions(regions) inside each country .
  • Many points scattered across all divisions (countries) on my example mainly above Germany.
  • when I have to click on a targeted division(country) I must get only the points which correspond to this targeted division(country) that I have just clicked on
  • That means when the zoom of the subdivision(regions) is triggered(when the click is made), the code should take all the points that exist already only inside the contours of the targeted divison(country) (that have just been clicked on) and points enclosed-in should scatter in their corresponding subdivisions(regions).

To achieve this functionality and based on Michael Rovinsky comment: in the function manipulate(), the code is able to filter and extract only points that are embedded inside the targeted and triggered subdivisions(regions) and exclude markers those that are outside. Inside function redraw() the enter exit pattern works well .

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);
    }
};

My Question is the following : How to make the Zooming (for points circle) to work adequately:
right now, on a map upon click the x y points not scale. They are rendered as circles in background and I would like them to move with the map. That means How to apply the same animation zoom (when subdivisions are triggered by click on a division) in order to those points inside the targeted subdivision follow in transition and move with the map and we could see circles points clearly distributed adequately in each correct corresponding subdivisions?

update

Andrew Reid described here How To accomplish a smooth zoom using d3.js

so following his hints.

I added the following instructions in redraw() function

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

AND then we should add the code to the The function that should actually do the moving recenter(featureCollection) function to

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

Thank You very much for your cooperation,participation and help !

like image 711
Schwertfisch Avatar asked Feb 10 '20 19:02

Schwertfisch


1 Answers

1- To generate first iteration click on Region equal country

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

2- To generate counties for example on click on region, we should first set startprojection and endprojection in zoom function and then trigger redraw of circles

  //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-the same thing should be applied to communities

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- Then reinitialise to go to first step 1 by

      // 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

like image 134
Schwertfisch Avatar answered Nov 15 '22 07:11

Schwertfisch