Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I use angularjs directives in generated d3 html?

Tags:

I'm trying to use the angularjs tooltip directive on my d3 visualisation, so I have something like

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

However, the tooltips are not showing. Do I need to $compile or something? I've tried wrapping it around $timeout too, but that didn't work.

like image 976
zlog Avatar asked Dec 05 '13 11:12

zlog


People also ask

How do I invoke a directive in AngularJS?

In addition to all the built-in AngularJS directives, you can create your own directives. New directives are created by using the . directive function. To invoke the new directive, make an HTML element with the same tag name as the new directive.

Which directive link AngularJS with HTML?

A - ng-bind directive binds the AngularJS Application data to HTML tags.

Where AngularJS directives are used?

The ng-app Directive in AngularJS is used to define the root element of an AngularJS application. This directive automatically initializes the AngularJS application on page load. It can be used to load various modules in AngularJS Application.


2 Answers

I had a similar problem and yes, solved it with $compile. I'm assuming your d3 code is inside a custom directive. From there you can add your tooltip attributes, remove your custom directive attribute so $compile only runs once, and call $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);             }         };     }]); 

The $compile service makes sure your element is compiled with the attributes added by your directive.

Here is a working fiddle using the above code. Hope it's what you're looking for!

like image 131
jbll Avatar answered Sep 19 '22 10:09

jbll


A pretty good answer from @jbll - But it will probably be best to chain the directive compilation onto the end of the enter phase. It is important to have an enter phase and an update phase so the graphic can respond to data updates without recreating every element. The previous answer would have every directive on every node compiled whenever the model was changed. This may be what is wanted, but probably not.

The following code shows the d3 graphic updating whenever the $scope.nodes variable changes.

This is also a little neater because it doesn't require the removal and recreation of the original directive, which seems like a bit of a hack.

Here is the Fiddle

Add the button to the html:

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

And then change the JavaScript fie to:

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)             }         }     }; }]); 
like image 45
dave walker Avatar answered Sep 22 '22 10:09

dave walker