3

I have interesting question regarding an application making use of AngularJS and D3. Essentially, I have an ng-repeat which repeats based on the contents of an array. In this array are objects which contain a REST url they will periodically reach out to and pull data from. Once they get this data, a graph is drawn using D3 (one graph for each object in the array).

The issue I am having is dynamically adding an object to this array via user input, and having the drawing work correctly. It appears that the d3 code jumps the gun and starts trying to select SVG elements that haven't been rendered yet as Angular is still finishing adding stuff to the DOM via ng-repeat.

I have tried using $timeout, and also created a custom directive similar to this: ng-repeat finish event.

It works well, but only seems to fire the event the first time that the ng-repeat is run, not when I add another object to the array. Any comments on the best way to handle this situation? If I could fire the event based on $last everytime a new object is added/removed and then iterate through the array and invoke the drawing function, that seems like it would work.

Cheers!

Edit: The D3 code currently is similar to this http://bl.ocks.org/mbostock/1346410 which is just a simple donut chart update, with the caveat that the redraw is triggered by a REST call. In the future however there may be more complicated graphs and such which is why I was hoping to use D3.

Edit 2: I have moved all my D3 code into a custom directive and after a lot of messing around it seems to work almost as it should. By not isolating the scope, the custom element has the same scope as the ng-repeat and can access the object and its properties, as well as the relevant DOM elements (currently I am creating a DIV with an ID that is unique to each object).

The problem now comes when using d3.select...when I try and select the DIV where I want to place the drawing, it will not allow me to select by ID, as it returns null..what is very strange about this is if I selectAll("div"), it can find the div I'm looking for, but won't let me select just that one.

Here is a JsFiddle showing what I mean...check the console and see the first test returns the div "obj1", but the second select returns null.

If I could get this working and then append my SVG canvas and drawing within that div, I think it would work!

Relevant selects:

    var test = d3.selectAll("div");
       console.log(test);
       var chart = d3.select("#obj" +obj.id);
       console.log(chart);

HTML that should create the divs:

    <div ng-repeat="object in myData">
        <div id="obj{{object.id}}">
            Hi 
           <bars-chart></bars-chart>
       </div>
   </div> 

http://jsfiddle.net/SleepyProgrammer/eN6p7/3/

Community
  • 1
  • 1
SleepyProgrammer
  • 443
  • 1
  • 5
  • 15
  • Would be nice to see some code as in some cases it is possible to ditch D3 in favor of AngularJS directives. – Im0rtality Apr 21 '14 at 14:59
  • Made an edit! Basically just following the D3 example for right now, in terms of what D3 is doing. – SleepyProgrammer Apr 21 '14 at 15:04
  • 1
    still, this sounds like the crucial aspects could be summarized by a few lines of code. this will greatly improve the chances of people being able to help! – Nicolas78 Apr 21 '14 at 15:15
  • 1
    You could write AngularJS directive which wraps D3 code inside it. It should get called when you update data (and I think after parent ngRepeat iteration is done). – Im0rtality Apr 21 '14 at 15:27
  • Added a JsFiddle the outlines my current problem. I tried wrapping my D3 code in a directive as suggested, which seems to make the most sense, but it seems to be messing up the d3.select now – SleepyProgrammer Apr 22 '14 at 13:05
  • Thanks @Im0rtality for getting me started! From now on, anything SVG/D3 stays inside the directive, at least during creation. – SleepyProgrammer Apr 22 '14 at 18:51

1 Answers1

3

There's no need to use the ID for selection at all. You can simply select the element[0] passed in to the Angular directive's link function:

var chart = d3.select(element[0]).append('svg')

For example:

angular.module('myApp', []).
   directive('barsChart', function () {
     return {
         restrict: 'E',
         link: function (scope, element, attrs) {
             var chart = d3.select(element[0]).append('svg').attr({height: 20, width: 20});             
             chart.append('rect').attr({height: 10, width: 10});             
         } 
      };
   });

function Ctrl($scope, $timeout) {
    $scope.myData = [ {"id":"1"}, {"id":"2"}];

    // just to show that it reacts to changes in the data and will render more SVGs
    $timeout(function() {
        $scope.myData.push({"id":"3"});
    }, 3000);    
}

See JSFiddle here:

http://jsfiddle.net/4vdvq/2/

explunit
  • 18,967
  • 6
  • 69
  • 94
  • Thanks! This was awesome. I actually got around it another way, but this is much nicer. I had to do some other fun stuff to get the svg canvas to update with new values on a timer, but it should work this way too! Cheers. – SleepyProgrammer Apr 22 '14 at 17:52