JavaScript How can I set up a d3 chart inside a custom directive in Angular?

I'v been following a few walkthroughs on how to implement d3 charts in an angular application. Basically, Im trying to implement the following d3 chart into my custom angular directive ('workHistory'). For the purpose of this question, I'm following a simple bar chart example where I have it set up like so :

index.html

<!doctype html>
    <html lang="en" ng-app="webApp">
    <head>
        <meta charset="utf-8">

        <title>My Portfolio</title>

        <!--Stylesheets -->
        <link rel="stylesheet" target='_blank' href="styles/main.css"/>
        <link rel="stylesheet" target='_blank' href="bower_components/bootstrap/dist/css/bootstrap.min.css"/>
        <!--Libraries -->
        <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
        <script src="bower_components/jquery/dist/jquery.min.js"></script>
        <script src="bower_components/angular-route/angular-route.min.js"></script>
        <script src="bower_components/angular-loader/angular-loader.min.js"></script>
        <script src="bower_components/bootstrap/dist/js/bootstrap.min.js"></script>
        <script src="bower_components/d3/d3.min.js"></script>
        <!--Module -->
        <script src="scripts/modules/module.js"></script>
        <script src="scripts/modules/d3.module.js"></script>
        <!--Controllers -->
        <script src="scripts/controllers/mainHeroController.js"></script>
        <script src="scripts/controllers/workHistoryController.js"></script>
        <!--Directives-->
        <script src="scripts/directives/mainHero.directive.js"></script>
        <script src="scripts/directives/mainNavbar.directive.js"></script>
        <script src="scripts/directives/workHistory.directive.js"></script>
    </head>

     <!--Main Landing Page-->    
    <body ng-app="webApp">
        <div id="container1"> 
            <work-history chart-data="myData"></work-history>
        </div>
        <div id="container2">
            Container 2
        </div>
    </body>
    </html>

workHistory.directive.js

(function()
{
    'use strict';

    angular
        .module('webApp')
        .directive('workHistory', workHistory);

    function workHistory()
    {
        var directive = 
            {
                restrict: 'EA',
                controller: 'WorkHistoryController',
                //controllerAs: 'workhistory',
                scope: {data: '=chartData'},
                template: "<svg width='850' height='200'></svg>",
                link: workHistoryLink,
            };

        return directive;
    }

    function workHistoryLink(scope, element/*, attrs, ctrl, tfn*/)
    {

       var chart = d3.select(element[0]);
        chart.append("div").attr("class", "chart")
         .selectAll('div')
         .data(scope.data).enter().append("div")
         .transition().ease("elastic")
         .style("width", function(d) { return d + "%"; })
         .text(function(d) { return d + "%"; });
         } 
})();

main.css

.axis path,
.axis line{
  fill: none;
  stroke:black;
  shape-rendering:crispEdge;
}

.axis text{
  font-family: sans-serif;
  font-size: 10px;
}

h1{
  font-family: sans-serif;
  font-weight: bold;
  font-size: 16px;
}
.tick
{
  stroke-dasharray: 1, 2;
}

The Problem: With this code, nothing displays. I get the following error:

angular.js:13920TypeError: chart.append(...).attr(...).selectAll(...).data(...).enter is not a function

Can someone help me understand how to properly set this up? (Bonus, if someone can explain how I can get the collapsible tree d3 chart configured into a custom directive.

Thanks.

Answer:1

There are couple of errors in your code:

  1. you template is not good: you make an svg, but you insert standard html tags inside. It can't possibly work. In this case you should make a template with a div tag as a root node.
  2. if you want to provide an initial template like you do, you should then make it as the root node by setting the replace property of your directive to true.
  3. then, if you want added divs by d3 visible, you should make them visible by setting a background or a border with a ".style('background', 'blue')" for example
  4. I don't understand what you can possibly do in the controller 'WorkHistoryController'. According to me, in such a directive, you should avoid provide both link and controller.
  5. finally, if you want the whole think to always work, you should put the D3 code into a $watch so that you are sure that the generation is triggered once the data is set on the scope.
  6. as a complement, now you graph is always observing the array, it should handle the removal of elements that are not necessary if the array is smaller than before: ".exit().remove();"

The directive should look something like this:

angular
    .module('app', [])
    .directive('workHistory', workHistory);

function workHistory()
{
    var directive = 
        {
          restrict: 'EA',
          scope: {data: '=chartData'},
          replace:true,
          template: "<div style='width:100%'></div>",
          link: workHistoryLink,
        };

    return directive;
}

function workHistoryLink(scope, element)
{
  scope.$watch('data', function(){
    var chart = d3.select(element[0]);
    chart.append("div").attr("class", "chart")
      .selectAll('div')
      .data(scope.data).enter().append("div")
      .style('background', 'blue')
      .transition().ease("elastic")
      .style("width", function(d) { return d + "%"; })
      .text(function(d) { return d + "%"; })
      .exit().remove();
  }, true);
} 

here is a jsbin to make my point: http://jsbin.com/vegesigevi/edit?html,js,output

Hope this helps

Answer:2

I am using D3 v4. I have a bar graph created with an x-axis using scaleBand(). Now, I have created a y-axis but my issue is that no matter how I position it, it is cutting into the actual bars of ...

I am using D3 v4. I have a bar graph created with an x-axis using scaleBand(). Now, I have created a y-axis but my issue is that no matter how I position it, it is cutting into the actual bars of ...

I'm using casperJS to try to get a screenshot of a DOM element using captureSelector, but the quality of the screenshot is really poor, but when capturing full screenshot using capture it works ...

I'm using casperJS to try to get a screenshot of a DOM element using captureSelector, but the quality of the screenshot is really poor, but when capturing full screenshot using capture it works ...

I am developing a windows desktop application using node.js and backbone.js. I want to perform an action when the user closes the app by clicking on the close button on the title bar or right clicking ...

I am developing a windows desktop application using node.js and backbone.js. I want to perform an action when the user closes the app by clicking on the close button on the title bar or right clicking ...

For some reaon, I cannot use nested div, and I m trying to toggle a tree of divs Div id contains any string except - <div class="tree"> <div id="a">id=a click to toggle child</div&...

For some reaon, I cannot use nested div, and I m trying to toggle a tree of divs Div id contains any string except - <div class="tree"> <div id="a">id=a click to toggle child</div&...