Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free.

I have an SVG map that is being rendered with D3 embedded within an HTML document. I know this is NOT the way to do this. Anyway, I'd like my Angular controller to be alerted when a country in the map is clicked. I have access to this data, and can console log all of the information related to a given country upon click, but I haven't figured out how to access the controller scope from within the D3 script. Any ideas?

HTML file, if it helps:

<html>
<head>
  <meta charset="utf-8" />
  <link rel="stylesheet" type="text/css" href="../styles/style.css">
  <script src="js/d3.min.js"></script>
  <script src="js/topojson.v1.min.js"></script>
</head>
<body ng-app='myApp'>

  <div class="main" ng-controller="mainController">

    <div countryClick="selectCountry()" id="container" ng-controller="mainController">  

    <script type='text/javascript'>


    var dom_el = document.querySelector('[ng-controller="mainController"]');
    var ng_el = angular.element(dom_el);
    var ng_el_scope = ng_el.scope();
    var things = ng_el_scope.things;

    // d3.select(window).on("resize", throttle);

    var zoom = d3.behavior.zoom()
        .scaleExtent([1, 9]);
        // .on("zoom", move); // Disables user move and zoom without breaking everything


    var width = document.getElementById('container').offsetWidth;
    var height = width / 2.5; //Originally 2

    var topo,projection,path,svg,g;

    var graticule = d3.geo.graticule();

    var tooltip = d3.select("#container").append("div").attr("class", "tooltip hidden");

    setup(width,height);
    // debugger;

    function setup(width,height){
      // debugger;
      projection = d3.geo.mercator()
        .translate([(width/2), (height/2)])
        .scale( width / 2 / Math.PI);

      path = d3.geo.path().projection(projection);

      svg = d3.select("#container").append("svg")
          .attr("width", width)
          .attr("height", height)
          .call(zoom)
          .on("click", click)
          .append("g");

      g = svg.append("g");

    }

    d3.json("data/world-topo-min.json", function(error, world) {

      var countries = topojson.feature(world, world.objects.countries).features;

      topo = countries;
      draw(topo);

    });

    function draw(topo) {

      // Draws equator
      g.append("path")
       .datum({type: "LineString", coordinates: [[-180, 0], [-90, 0], [0, 0], [90, 0], [180, 0]]})
       .attr("class", "equator")
       .attr("d", path);


      var country = g.selectAll(".country").data(topo);

      country.enter().insert("path")
          .attr("class", "country")
          .attr("d", path)
          .attr("id", function(d,i) { return d.id; })
          .attr("title", function(d,i) { return d.properties.name; })
          .style("fill", "#F8F8F8")
          .style("stroke", "gray")

      //offsets for tooltips
      var offsetL = document.getElementById('container').offsetLeft+20;
      var offsetT = document.getElementById('container').offsetTop+10;

      //tooltips
      country
        .on("mousemove", function(d,i) {
          console.log('mousemove',d);

          var mouse = d3.mouse(svg.node()).map( function(d) { return parseInt(d); } );

          tooltip.classed("hidden", false)
                 .attr("style", "left:"+(mouse[0]+offsetL)+"px;top:"+(mouse[1]+offsetT)+"px")
                 .html(d.properties.name);

          })
          .on("mouseout",  function(d,i) {
            tooltip.classed("hidden", true);
          })

        .on("click", function (d,i) {

        //THIS IS WHERE I WANT TO REGISTER THE CLICK WITH MY ANGULAR CONTROLLER
        console.log(d.properties.name);  //Logs country name  

        });  

    }


    function redraw() {
      width = document.getElementById('container').offsetWidth;
      height = width / 2;
      d3.select('svg').remove();
      setup(width,height);
      draw(topo);
    }


    function move() {

      var t = d3.event.translate;
      var s = d3.event.scale; 
      zscale = s;
      var h = height/4;

      console.log('moving with t: ' + t + ' ,s: ,' + s + ' and h: ' + h);

      t[0] = Math.min(
        (width/height)  * (s - 1), 
        Math.max( width * (1 - s), t[0] )
      );

      t[1] = Math.min(
        h * (s - 1) + h * s, 
        Math.max(height  * (1 - s) - h * s, t[1])
      );

      zoom.translate(t);
      g.attr("transform", "translate(" + t + ")scale(" + s + ")");


      //Removed this because it screws things up when there is an initial stroke set on countries:
      //adjust the country hover stroke width based on zoom level
      // d3.selectAll(".country").style("stroke-width", 1.5 / s);

    }



    var throttleTimer;
    function throttle() {
      window.clearTimeout(throttleTimer);
        throttleTimer = window.setTimeout(function() {
          redraw();
        }, 200);
    }


    //geo translation on mouse click in map
    function click() {
      var latlon = projection.invert(d3.mouse(this));
      console.log(latlon);
    }


    //function to add points and text to the map (used in plotting capitals)
    function addpoint(lat,lon,text) {

      var gpoint = g.append("g").attr("class", "gpoint");
      var x = projection([lat,lon])[0];
      var y = projection([lat,lon])[1];

      gpoint.append("svg:circle")
            .attr("cx", x)
            .attr("cy", y)
            .attr("class","point")
            .attr("r", 1.5);

      //conditional in case a point has no associated text
      if(text.length>0){

        gpoint.append("text")
              .attr("x", x+2)
              .attr("y", y+2)
              .attr("class","text")
              .text(text);
      }

    }

  </script>
  </div>
</div>

  <script src='../../bower_components/angular/angular.js'></script>
  <script src='../../bower_components/angular-ui-router/release/angular-ui-router.js'></script>
  <script src='../js/app.js'></script>

</body>

</html>
share|improve this question

1 Answer 1

up vote 0 down vote accepted

I followed this tutorial to integrate my d3 code into an angular directive: http://www.ng-newsletter.com/posts/d3-on-angular.html

It takes about 15 minutes and will make your d3/angular integration much easier in the long run.

If you don't have 15 minutes than you can try something like this: AngularJS. How to call controller function from outside of controller component

share|improve this answer
    
I put my D3 in a directive using the blog post as a guide, but ran into an issue injecting a GeoJSON dependency. In the end, I had to just drop my D3 directly into my controller. Not pretty, but it worked. Thanks. –  user3183170 May 21 '14 at 16:00

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.