web-dev-qa-db-fra.com

Comment utiliser les directives angularjs dans d3 html prod?

J'essaie d'utiliser la directive angularjs tooltip sur ma visualisation en d3.

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

Cependant, les infobulles ne sont pas affichées. Dois-je $compile ou quelque chose? J'ai aussi essayé d'envelopper $timeout, mais cela n'a pas fonctionné.

24
zlog

J'ai eu un problème similaire et oui, je l'ai résolu avec $compile. Je suppose que votre code D3 est à l'intérieur d'une directive personnalisée. À partir de là, vous pouvez ajouter vos attributs d'infobulle, supprimer votre attribut de directive personnalisée pour que $ compile ne s'exécute qu'une seule fois, et appeler $ 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);
            }
        };
    }]);

Le service $ compile s'assure que votre élément est compilé avec les attributs ajoutés par votre directive.

Voici un violon en marche utilisant le code ci-dessus. J'espère que c'est ce que vous cherchez!

18
jbll

Une bonne réponse de @jbll - Mais il vaudra probablement mieux enchaîner la compilation des directives à la fin de la phase enter phase . Il est important de prévoir une phase de saisie et une phase de mise à jour pour que le graphique puisse répondre aux mises à jour des données sans recréer chaque élément. La réponse précédente aurait toutes les directives sur chaque nœud compilées chaque fois que le modèle a été modifié. C'est peut-être ce qui est voulu, mais probablement pas.

Le code suivant montre la mise à jour graphique d3 chaque fois que la variable $ scope.nodes change.

C’est également un peu plus ordonné, car il n’exige ni le retrait ni la reconstitution de la directive initiale, ce qui semble un peu comme un bidouillage.

Voici le Fiddle

Ajoutez le bouton au code HTML:

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

Et changez ensuite le fichier JavaScript en:

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

J'aime beaucoup mieux cette méthode car vous n'avez pas à appeler removeAttr (cela ressemble à un hack)

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

si le code HTML est généré par autre chose que angularjs et inséré dans le DOM, vous devez compiler le code HTML qui inclut vos attributs de directive avant de l'insérer dans le DOM afin qu'angular le sache.

2
btm1

@ david004 insiste sur le chaînage sur enter() , donc $compile n'est appelé qu'une fois sur chaque élément entrant. Mais au lieu d'appeler $compile sur la parentNode, voici comment j'utilise call to $compile pour chaque élément entrant:

// 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 );
  } );
}
0
Philip Bulley