1

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]));

}

Vahe
  • 1,699
  • 3
  • 25
  • 76

2 Answers2

1

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.

Gordon
  • 19,811
  • 4
  • 36
  • 74
  • Wow!! Thank you once more Gordon, Can you suggest how I control animation of the bars to width horizontally, not diagonally up on load, if this can by modified some way or via css? How do I shift the horizontal tick marks to have the "0" appear centered on the y axis line not 3.5 units to the right of the y axis. – Vahe Feb 17 '20 at 13:28
  • Also, day's (12,14, 2) determine width of each of the stacked rectangles corresponding to the horizontal units. Example I am trying to piece together. https://bl.ocks.org/cool-Blue/418d3f6d91915602e4fd – Vahe Feb 17 '20 at 13:43
  • 1
    Oh I guess you should have specified that in your question. Variable width bars are not supported, sorry. – Gordon Feb 17 '20 at 13:48
  • 1
    It’s hard to control the animation. Not sure if it’s possible to uncenter the bars for an ordinal scale, might have to use a linear scale and appropriate xUnits. – Gordon Feb 17 '20 at 13:50
  • To the point about, variable widths, am I able to use d3 to modify at the end of the function? – Vahe Feb 17 '20 at 13:51
  • Maybe, but it sounds too difficult. You're probably better off [adapting the D3 chart to dc.js](https://stackoverflow.com/questions/25336528/dc-js-listening-for-chart-group-render) than changing both the position and size of the bars in dc.js - I mean, there's hardly anything left of dc.js once you do that. – Gordon Feb 17 '20 at 14:31
  • Placing a single row variable cell (3 cells) width table solved my issue. See answer below. – Vahe Feb 18 '20 at 04:01
  • 1
    Oh I see. Sure, if you only need one dimension, that’s a lot easier. Thanks for posting your solution! (I don’t understand how it relates to your question, but that’s cool. Asking the right question is a tough problem in itself!) – Gordon Feb 18 '20 at 04:09
0

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);



}
Vahe
  • 1,699
  • 3
  • 25
  • 76