Injecting AngularJS expressions (handle bars) into D3JS created SVG elements

There is the possibility to insert the AngularJS handle bar syntax directly into the d3 generated DOM elements:

Code for this post is here. (last updated 20140109)

To try the code, unzip and then start a web server in the folder where the files were unzipped: There are many ways to do so, I am listing some:

Run a python server (if you have python installed), open the file tester.htm:

python -m SimpleHTTPServer

Or you can run “npm install” (you need to have node installed) from the “testpage” folder, then run “node ex.js”. Now point your browser to “http://localhost:<portnumber>”. This server allows to play with socket IO too.

Or you can use visual studio (2012) and load the included solution file, then you can use the vs debugger and web server to run this.

Please note this example uses typescript but the JavaScript is included so you can just look at that, you don’t need to compile any typescript.

Here the significant parts in the java script:

20140109: I just updated the below code a bit with “ng-attr-” so that the browser doesn’t complain anymore about invalid attributes.

var containerDiv = d3.select(targetCSSSelectorForADiv);
var svgG = containerDiv
                                .append("svg")
                                .attr("width", width + margin.left + margin.right)
                                .attr("height", height + margin.top + margin.bottom)
                                .append("g")
                                .attr("transform", "translate(" + margin.left + "," + margin.top + ")")

 svgG.selectAll(".tempclass").data(scope.circles).enter()
                                .append("circle")
                                .attr("class", "tempclass")
                                .attr("ng-attr-cx", function (d, i) { return "{{circles[" + i + "].cx}}" })
                                .attr("ng-attr-cy", function (d, i) { return "{{circles[" + i + "].cy}}" })
                                .attr("ng-attr-r", function (d, i) { return "{{circles[" + i + "].radius}}" })
                                .attr("ng-style", function (d, i)
                                {
                                    return "{fill: circles[" + i + "].circolor"
                                        + ", opacity: circles[" + i + "].opa"
                                        + ", 'stroke-width': 4*circles[" + i + "].opa"
                                        + ", stroke: 'red' }";
                                });

Please note the following things: the scope is in-fact the angular scope object passed down from the directive to the rendering function. Setting the style of an element to an “{{…}}” expression will not work so I am using the “ng-style” attribute here.

However there is one more trick: You need to tell Angular to look at the dynamically generated DOM elements and wire up the data binding, I know now of two ways of doing this:

//the target div is the one with the angular ng-controller attribute 
//this you can call at the end of the d3 rendering call from within the render function
angular.bootstrap(document.getElementById("d3ContainerDivID"), ['d3App']);

the other way is this:

//and this could be called from the directive that triggered the rendering or
//some other place that could have the angular $compile service injected
$compile(document.getElementById("d3ContainerDivID"))(scope);

Now you can change your scope members and they will be directly updated to your d3 elements, in this case the svg circles. In the angular controller (which gets instantiated before the directive fires that draws the d3 objects).

    $scope.circles = [];
    for (var i = 0; i < 50; i++)
    {
        $scope.circles.push(new Circle());
    }
    setInterval(function ()
    {
        $scope.circles.forEach(function (d, i) { $scope.circles[i] = new Circle(); });
        $scope.$digest();
    }, 50);

Please note the $digest call, which tells angular to digest the changed scope; this will change the values on the svg circle elements. For anything like animations and such, d3 is now not responsible anymore and one would have to implement manually or use a different pattern.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s