Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

D3 within an AngularJS app

I'm trying to create my first app with AngularJS. It looks neat, but there's a lot of abstraction, and I'm just curious if anyone has advice on the most idiomatic way to use the angular methodology to update visuals created with d3js.

Thanks, bp

like image 359
BHP Avatar asked Jun 21 '12 15:06

BHP


People also ask

Can D3 be used with angular?

D3 can load CSVs from your Angular application or a third-party URL and makes the data available as an array of objects in the resulting promise.

Do people still use D3 JS?

The JavaScript ecosystem has completely changed during this time, in terms of libraries, best practices and even language features. Nevertheless, D3 is still here. And it's more popular than ever.

What is the way to include D3 js library into HTML site?

Include D3 Library from CDN js library by linking it directly into our HTML page from the Content Delivery Network (CDN). CDN is a network of servers where files are hosted and are delivered to a user based on their geographic location. If we use the CDN, we do not need to download the source code.


2 Answers

In order to make angular and other frameworks play nice is to wrap the "other" frameworks using directives.

http://docs.angularjs.org/guide/directive

The thing that you want to do is to tell angular when data has been updated by the "other" frameworks. If angular doesn't need to know, then your task is simpler.

Here is an example that works with SVG, its awesome

http://sullerandras.github.com/SVG-Sequence-Diagram/

Here is an example that wraps TinyMCE

http://jsfiddle.net/programmieraffe/kjsEV/

like image 157
Dan Doyon Avatar answered Oct 03 '22 21:10

Dan Doyon


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

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("cx", function (d, i) { return "{{circles[" + i + "].cx}}" })                                 .attr("cy", function (d, i) { return "{{circles[" + i + "].cy}}" })                                 .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();     }, 2000); 

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.

like image 44
hans Avatar answered Oct 03 '22 21:10

hans