1
votes

Scenario

I want to see my dc.js horizontal row stacked bar chart (single row chart), but no chart appears, except for legend with names, axes with tick marks which do appear.

Screenshot

enter image description here

Data set below in code snippet

Question

How do I render the ensure chart rendering successfully using dc.js / d3.js?

Code Snippet

$scope.avgCycleTime = function(){


    var data = [ 
                    {"Project":"Expedition","Stage": "Created", "Days":12},
                    {"Project":"Expedition","Stage": "Active", "Days":14},
                    {"Project":"Expedition","Stage": "Closure", "Days":2}
    ];
    var ndx = crossfilter(data);

    data.forEach(function(x) {
      x.Days = 0;
    });

    var ndx = crossfilter(data)

    var xdim = ndx.dimension(function (d) {return d.Days;}); 

    function root_function(dim,stack_names) {
        return dim.group().reduce(
      function(p, v) {
        stack_names.forEach(stack_name => {
          if(v[stack_name] !== undefined)
              p[stack_name] = (p[v[stack_name]] || 0) + v[stack_name]
        });
        return p;}, 
      function(p, v) {
        stack_names.forEach(stack_name => {
          if(v[stack_name] !== undefined)
              p[stack_name] = (p[v[stack_name]] || 0) + v[stack_name]
        });
        return p;}, 
      function() {
        return {};
      });}

    var stages = ['Created', 'Active', 'Closure'];
    var ygroup = root_function(xdim,stages)

    function sel_stack(i) {
    return function(d) {
      return d.value[i];
    };}

    cycleChart = new dc.barChart("#risk-cycle-chart");


    var chart = document.getElementById('risk-cycle-chart'); 


    heightStatusChart =  200;
    widthStatusChart = Math.floor(parseFloat(window.getComputedStyle(chart, null).width))
     - 2*parseInt(window.getComputedStyle(chart, null).getPropertyValue('padding-top'));    


    cycleChart
      .x(d3.scaleLinear().domain([0,7,14,21,28]))
      .dimension(xdim)
      .group(ygroup, data[0].Project, sel_stack(data[0].Project))
      .xUnits(dc.units.ordinal)
      .margins({left:75, top: 0, right: 0, bottom: 20})
      .width(widthStatusChart) 
      .height(heightStatusChart)
      .legend(dc.legend());


    for(var i = 1; i<stages.length; ++i)
      cycleChart.stack(ygroup, stages[i], sel_stack(stages[i]));

}

2
Can you please create a working instance of your issue ? - Jasdeep Singh
Example was almost complete; it just needed the html for the risk-cycle-chart, and the chart constructor was using the wrong syntax. - Gordon

2 Answers

1
votes

Sigh, I guess dc.js and crossfilter have a steep learning curve. There isn't any "interesting" problem here, just a bunch of minor glitches:

  1. dc.barChart is not a constructor. Use either new dc.BarChart(...) or dc.barChart(...)
  2. You are zeroing out d.Days, which is the field your dimension uses, so there will only be one bin.
  3. Your x scale should match your xUnits, so d3.scaleOrdinal not d3.scaleLinear.
  4. You have changed your data from having separate fields for Created, Active, Closure to one field named Stage. With this format of data, your reducers could look like

    function root_function(dim, stack_field, stack_names) {
      return dim.group().reduce(
        function(p, v) {
          p[v[stack_field]] = p[v[stack_field]] + 1; // 2
          return p;},
        function(p, v) {
          p[v[stack_field]] = p[v[stack_field]] - 1; // 3
          return p;}, 
        function() {
          return Object.fromEntries(stack_names.map(sn => [sn,0])); // 1
        });}
    
    1. Initialize each bin with a field for each of the stack_names with value 0
    2. When adding a row to a bin, determine which field it affects using stack_field; increment that field
    3. When removing a row from a bin, decrement the same way
  5. You are requesting an x domain of [0,7,14,21,28] but none of your Days fall exactly on those values. If you want to round down, you could do

    var xdim = ndx.dimension(function (d) {return Math.floor(d.Days/7)*7;}); 
    
  6. .group(ygroup, data[0].Project, sel_stack(data[0].Project)) doesn't make sense since you are stacking by Stage, not by Project; this should be .group(ygroup, stages[0], sel_stack(stages[0]))

With the above changes, we get a chart with one stack in each of the first three bins:

screenshot of working chart

Demo fiddle.

0
votes

I resorted to not using a vector graphics generator like d3/dc for this grid portion and instead created a table like so.

Table

 $scope.avgCycleTime = function(){


    var data = [ 
                    {"Project":"Expedition","Stage": "Created", "Days":12, "Color": "rgb(166, 206, 227)"},
                    {"Project":"Expedition","Stage": "Active", "Days":14, "Color": "rgb(253, 180, 98)"},
                    {"Project":"Expedition","Stage": "Closure", "Days":2, "Color": "rgb(179, 222, 105)"}
    ];


    var tbl = document.createElement("table");
    var tblBody = document.createElement("tbody");

    // table row creation
    var row = document.createElement("tr");

    for (var i = 0; i <= 2; i++) {
      // create element <td> and text node 
      //Make text node the contents of <td> element
      // put <td> at end of the table row
      var cell = document.createElement("td");
      cell.setAttribute('style', 'width: ' + data[i].Days * 100 + "px; background-color: " + data[i].Color);
      var cellText = document.createTextNode(data[i].Stage + " " + data[i].Days);

      cell.appendChild(cellText);
      row.appendChild(cell);
    }

    //row added to end of table body
    tblBody.appendChild(row);

      // append the <tbody> inside the <table>
      tbl.appendChild(tblBody);
      // put <table> in the <body>
      document.querySelector("#risk-cycle-chart").appendChild(tbl);



}