如何在生成的d3 html中使用angularjs指令?

时间:2013-12-05 11:53:48

标签: angularjs d3.js angularjs-directive angular-ui

我正在尝试在我的d3可视化中使用angularjs tooltip directive,所以我有类似的东西

var node = svg.selectAll(".node")
    .data(nodes)
    .enter().append("circle")
        .attr("tooltip-append-to-body", true)
        .attr("tooltip", function(d) {
            return d.name;
        })
// ... attributes

但是,工具提示未显示。我需要$compile还是其他什么?我已经尝试将它包裹在$timeout左右,但这不起作用。

5 个答案:

答案 0 :(得分:19)

我遇到了类似的问题,是的,用$compile解决了这个问题。我假设您的d3代码在自定义指令中。从那里你可以添加你的工具提示属性,删除你的自定义指令属性,所以$ compile只运行一次,并调用$ compile:

    myApp.directive('myNodes', ['$compile', function ($compile) {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            var nodes = [{"name": "foo"}, {"name": "bar"}] 
            var mySvg = d3.select(element[0])
                  .append("svg")
                  .attr("width", 100)
                  .attr("height", 100);

            var node = mySvg.selectAll(".node")
             .data(nodes)
             .enter()
             .append("circle")
             .attr("cx", function(d,i){
                return 20+i*50;
             })
             .attr("cy", 50)
             .attr("r", 10)
             .attr("tooltip-append-to-body", true)
             .attr("tooltip", function(d){
                 return d.name;
             });

            element.removeAttr("my-nodes");
            $compile(element)(scope);
            }
        };
    }]);

$ compile服务确保使用指令添加的属性编译元素。

Here is a working fiddle使用上面的代码。希望这就是你要找的东西!

答案 1 :(得分:17)

来自@jbll的一个非常好的答案 - 但最好将指令编译链接到输入阶段的末尾。具有输入阶段和更新阶段非常重要,因此图形可以响应数据更新而无需重新创建每个元素。无论何时更改模型,上一个答案都会在每个节点上编译每个指令。这可能是想要的,但可能不是。

以下代码显示$ scope.nodes变量更改时的d3图形更新。

这也有点整洁,因为它不需要删除和重新创建原始指令,这看起来有点像黑客。

Here is the Fiddle

将按钮添加到html:

<button ng-click="moveDots()">Move the dots</button>

然后将JavaScript fie更改为:

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

答案 2 :(得分:2)

如果html是由angularjs以外的东西生成并插入到DOM中,那么在将其插入DOM之前需要编译包含指令属性的html,以便angular知道它。

答案 3 :(得分:2)

我更喜欢这种方法,因为你不必调用removeAttr(看起来像是黑客)

myApp.directive('myNodes', ['$compile', function ($compile) {
return {
    restrict: 'A',
    link: function(scope, element, attrs) {
        var nodes = [{"name": "foo"}, {"name": "bar"}] 
        var mySvg = d3.select(element[0])
              .append("svg")
              .attr("width", 100)
              .attr("height", 100);

        var node = mySvg.selectAll(".node")
         .data(nodes)
         .enter()
         .append("circle")
         .attr("cx", function(d,i){
            return 20+i*50;
         })
         .attr("cy", 50)
         .attr("r", 10)
         .attr("tooltip-append-to-body", true)
         .attr("tooltip", function(d){
             return d.name;
         });

        $compile(svg[0])(scope);
        }
    };
}]);

答案 4 :(得分:0)

@ david004对链接到enter()提出了一个很好的观点,因此$compile仅在每个输入元素上调用一次。但是,不是在$compile上调用parentNode,而是我使用call$compile每个人进入元素的方式:

// Entering
myD3Selection.enter()
  .append( 'rect' )
  .attr( {foo: 'bar'} )
  .call( compile );

// Compile encapsulated in reusable function, so can be used on multiple enter() chains
function compile( d3Selection )
{
  d3Selection.each( function( d, i )
  {
    // this is the actual DOM element
    $compile( this )( scope );
  } );
}