5

I'm trying to create a d3 chart using an Angular directive. I manage to create it, but the problem is that I want some ng-click events on the chart elements and I'm not really sure how this should be done.

Here is my directive:

.directive('circleChart', function($parse, $window, $compile) {
        return {
            restrict: 'A',
            scope: {
                datajson: '='
            },
            link: function(scope, elem, attrs) {

                var circleChart = new CircleChart(scope.datajson);
                circleChart.initialise(scope);
                var svg = circleChart.generateGraph();
                svg = angular.element(svg);

                console.log(svg);
                //scope.$apply(function() {
                  var content = $compile(svg)(scope);
                  elem.append(content);
                //});
            }
        }
    });

The CircleChart object creates my d3 chart and there is the place I attach an ng-click attribute to the chart elements (doesn't seem to be a proper Angular way of doing it):

var CircleChart = Class.create({
    initialise: function(scope) {
        this.datajson = scope.datajson;
    },

    generateGraph: function() {

    .............

    var chartContent = d3.select("div#chart-content");

    var svg = chartContent.append("svg")
        .attr("id", "circle")
        .attr("width", diameter)
        .attr("height", diameter)
        .style("border-radius", "50px")
        .append("g")
        .attr("transform", "translate(" + diameter / 2 + "," + diameter / 2 + ")");

      ...............

           var circle = svg.selectAll("circle")
                .data(nodes)
                .enter().append("circle")
                .attr("class", function(d) {
                    return d.parent ? d.children ? "node" : "node node--leaf" : "node node--root";
                })
                .attr("id", function(d) {
                    return d.id;
                })
                .attr("value", function(d) {
                    return d.name
                })
                .attr("parent", function(d) {
                    if (d.parent) return d.parent['id']
                })
                .attr("ng-click", function(d) {
                    return "getEgs('" + d.id + "')";
                })

    ...............

The d3 code is working fine because if I remove the compile code from the directive, the chart gets drawn, but the ng-clicks don't fire.

If I leave the compile code there, it goes into an infinite loop and the browser is building up memory until I have to kill it.

Obviously, my method is not working and it will be awesome if someone can help me out

Raz
  • 1,601
  • 2
  • 15
  • 23
  • What is the `svg` in the `generateGraph` function? Is that detached DOM? Also, in my opinion, it is not a good idea to generate angular templates from code. The way I would do it is by passing callbacks to `CircleChart` either during initialization or during invocation, capture the click event in d3 and invoke that callback via Javascript code. – musically_ut May 05 '15 at 14:10
  • @musically_ut I updated my question with the svg creation code. I'm trying to understand what you said about passing the callbacks. Are you referring to the actual function that I want to call with the ng-click? I can probably access that if I use the controller's scope in my directive. – Raz May 05 '15 at 14:36
  • Yes, I'd rather that you call the function `getEgs` inside `.on('click', ...)` instead of outputting an `ng-click` in the code. – musically_ut May 05 '15 at 14:39

1 Answers1

5

Since you are modifying the DOM from inside the CircleChart.generateGraph and re-appending the contents of the element in you directive: elem.append(content) after compilation, you (probably) end up in an infinite loop.

If you do want to compile the template thus produced, you can simple call $compile(elem.contents())(scope); without having to clear and append the content again. $compile will work Angular magic on the DOM in-place.


However, as I suggested in the comments, since you have access to the whole scope in CircleChart (which in my opinion is probably an overkill), you should capture the click event in d3 and invoke the getErgs function from code instead of $compile-ing the code and delegating the task to Angular.

musically_ut
  • 34,028
  • 8
  • 94
  • 106
  • 2
    Thanks for your answer, I managed to get it working now. What I did was to access my controller in the directive `controller: 'egCtrl'`, then instead of placing `getEgs()` on the controller's `$scope`, I add it as an attrribute like: `this.getEgs = function() {}`. Then as you said, I use d3's `on.('click', ...)`. – Raz May 05 '15 at 15:29