Even Simpler Tooltip with D3.js and AngularJS

Using $compile to apply an angularjs tooltip directive to a d3 visualization.

HTML

<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.6.0/ui-bootstrap-tpls.js"></script>
<link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
<div ng-app="myApp" ng-controller="myCtrl">
    <div my-nodes></div>
    <button ng-click="moveDots()">Move the dots</button>
</div>

JavaScript

var myApp = angular.module('myApp', ['ui.bootstrap']);

myApp.controller('myCtrl', ['$scope', function($scope){
    $scope.nodes = [
        {"name": "foo", x: 50, y: 50},
        {"name": "bar", x: 100, y: 100}
    ];
    $scope.moveDots = function(){
        for(var n = 0; n < $scope.nodes.length; n++){
            var node = $scope.nodes[n];
            node.x = Math.random() * 200 + 20;
            node.y = Math.random() * 200 + 20;
        }
    }
}]);

myApp.directive('myNodes', ['$compile', function ($compile) {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {

            var mySvg = d3.select(element[0])
                .append("svg")
                .attr("width", 250)
                .attr("height", 250);

            renderDots();

            scope.$watch("nodes", renderDots, true);

            function renderDots(){
                // ENTER PHASE
                mySvg.selectAll("circle")
                    .data(scope.nodes)
                    .enter()
                    .append("circle")
                    .attr("tooltip-append-to-body", true)
                    .attr("tooltip", function(d){
                        return d.name;
                    })
                    .call(function(){
                        $compile(this[0].parentNode)(scope);
                    });

                // UPDATE PHASE - no call to enter(nodes) so all circles are selected
                mySvg.selectAll("circle")
                    .attr("cx", function(d,i){
                        return d.x;
                    })
                    .attr("cy", function(d,i){
                        return d.y;
                    })
                    .attr("r", 10);

                // todo: EXIT PHASE (remove any elements with deleted data)
            }
        }
    };
}]);