简体   繁体   English

dc.js按月订购X轴

[英]dc.js Ordering X-Axis by Month

I am trying to create a Stacked Bar Chart using dc.js as following: 我正在尝试使用dc.js创建堆积条形图,如下所示:

Data: 数据:

<pre id="data">
Status,StatusT,Phase,PhaseT,CompletionDate,CalMonth,Sales,SalesCount
3,Won,Z04,Decide,20130101,201012,2203262.13,1
3,Won,Z04,Decide,20130101,201111,607933.8,1
3,Won,Z05,Deliver,20131001,201202,66.08,1
3,Won,Z04,Decide,20120501,201202,66.08,1
3,Won,Z01,Understand,20120409,201203,65.07,1
3,Won,Z04,Decide,20121231,201204,4645214.7,1
3,Won,Z05,Deliver,20130918,201204,66.52,1
3,Won,Z04,Decide,20130418,201204,2312130.92,1
3,Won,Z05,Deliver,20120504,201205,68.07,1
3,Won,Z05,Deliver,20120504,201205,515994.86,1
3,Won,Z04,Decide,20120504,201205,68.07,1
3,Won,Z04,Decide,20120504,201205,68.07,1
</pre>

Javascript: 使用Javascript:

var dateFormat = d3.time.format('%Y%m%d');
var monthNameFormat = d3.time.format("%b-%Y");
// Prepare Data
for (var z = 0; z < opportunities.length; z++) {
  opportunities[z].DD = dateFormat.parse(opportunities[z].CompletionDate);
}

opportunities.sort(function(a,b){
  return b.DD - a.DD;
});

var ndx = crossfilter(opportunities);

var dimMonthYear = ndx.dimension(function(d) {
  //return d.CompletionDate
  return monthNameFormat(new Date(d.DD));
});

var phaseUnderstand = dimMonthYear.group().reduceSum(function(d) {
  if (d.Phase === "Z01") {
    return d.Sales;
  } else {
    return 0;
  }
});
var phasePropose = dimMonthYear.group().reduceSum(function(d) {
  if (d.Phase === "Z02") {
    return d.Sales;
  } else {
    return 0;
  }
});
var phaseNegotiate = dimMonthYear.group().reduceSum(function(d) {
  if (d.Phase === "Z03") {
    return d.Sales;
  } else {
    return 0;
  }
});
var phaseDecide = dimMonthYear.group().reduceSum(function(d) {
  if (d.Phase === "Z04") {
    return d.Sales;
  } else {
    return 0;
  }
});
var phaseDeliver = dimMonthYear.group().reduceSum(function(d) {
  if (d.Phase === "Z05") {
    return d.Sales;
  } else {
    return 0;
  }
});

chart
  .width(768)
  .height(480)
  .margins({
    left: 80,
    top: 20,
    right: 10,
    bottom: 80
  })
  .x(d3.scale.ordinal())
  .round(d3.time.month.round)
  .alwaysUseRounding(true)
  .xUnits(dc.units.ordinal)
  .brushOn(false)
  .xAxisLabel('Months')
  .yAxisLabel("Expected Sales (in thousands)")
  .dimension(dimMonthYear)
  .renderHorizontalGridLines(true)
  .renderVerticalGridLines(true)
 // .barPadding(0.1)
  //.outerPadding(0.05)
  .legend(dc.legend().x(130).y(30).itemHeight(13).gap(5))
  .group(phaseUnderstand, "Understand")
  .stack(phasePropose, "Propose")
  .stack(phaseNegotiate, "Negotiate")
  .stack(phaseDecide, "Decide")
  .stack(phaseDeliver, "Deliver")
  .centerBar(true)
  .elasticY(true)
  .elasticX(true)
  .renderlet(function(chart) {
    chart.selectAll("g.x text").attr('dx', '-30').attr(
      'dy', '-7').attr('transform', "rotate(-60)");
  });
chart.render();

I am able to render the chart but I am not able to sort the X-axis by month in ascending order and make it elastic along the x-axis. 我可以渲染图表,但无法按月对X轴进行升序排序,也无法使其沿X轴具有弹性。 If I use just date then the chart becomes too detailed (although x-axis is still not elastic) which is not useful for the purpose I am creating it for. 如果我仅使用日期,则图表将变得过于详细(尽管x轴仍不具有弹性),这对于我创建图表的目的而言没有用。 Here is a sample fiddle: https://jsfiddle.net/NewMonk/1hbjwxzy/67/ . 这是一个样本提琴: https : //jsfiddle.net/NewMonk/1hbjwxzy/67/

I strongly recommend using time scales when the X axis is time-based. 我强烈建议在X轴基于时间时使用时间刻度。 It's just a little more work (and more risk of the dreaded Blank Chart), but it's way more flexible and accurate. 这只是更多的工作(还有更大的空白图表风险),但是它更加灵活和准确。

First off, we need to ask crossfilter to bin by month. 首先,我们需要让crossfilter按月进行分类。 d3.time.month will round each date down to the nearest month: d3.time.month会将每个日期四舍五入到最近的月份:

var dimMonthYear = ndx.dimension(function(d) {
  return d3.time.month(d.DD)
});

This has the same effect as the string-bins you were creating, but it keeps the keys as dates. 这与您创建的字符串箱具有相同的效果,但是将键保留为日期。

Then we can tell dc.js to use a time-based scale and elasticX: 然后,我们可以告诉dc.js使用基于时间的刻度和elasticX:

chart
  .x(d3.time.scale()).elasticX(true)
  .round(d3.time.month.round)
  .alwaysUseRounding(true)
  .xUnits(d3.time.months)

.xUnits is what gets the bar width right (so that dc.js can count the number of visible bars). .xUnits是使条形宽度正确的原因(以便dc.js可以计算可见条形的数量)。

We can restore the monthNameFormat tick format and show every tick like so: 我们可以恢复monthNameFormat刻度格式并显示每个刻度,如下所示:

chart.xAxis()
  .tickFormat(monthNameFormat)
  .ticks(d3.time.months,1);

Finally, you may need to remove empty bins if filtering with another chart, so that bars will be removed when they drop to zero: 最后,如果使用其他图表进行过滤,则可能需要删除空箱 ,以便在柱线降至零时将其删除:

function remove_empty_bins(source_group) {
    return {
        all:function () {
            return source_group.all().filter(function(d) {
                return d.value != 0;
            });
        }
    };
}

Afterword 后记

@Kush comments below that removing the empty bins from a stacked set of date-based groups is not easy. @Kush在下面的评论中说,要从堆叠的基于日期的组中删除空容器并不容易。 It is so. 是这样

Here's a custom version of combine_groups for combining date-based groups: 这是Combine_groups的自定义版本,用于合并基于日期的组:

function combine_date_groups() { // (groups...)
    var groups = Array.prototype.slice.call(arguments);
    return {
        all: function() {
            var alls = groups.map(function(g) { return g.all(); });
            var gm = {};
            alls.forEach(function(a, i) {
                a.forEach(function(b) {
                    var t = b.key.getTime();
                    if(!gm[t]) {
                        gm[t] = new Array(groups.length);
                        for(var j=0; j<groups.length; ++j)
                            gm[t][j] = 0;
                    }
                    gm[t][i] = b.value;
                });
            });
            var ret = [];
            for(var k in gm)
                ret.push({key: new Date(+k), value: gm[k]});
            return ret;
        }
    };
}

The nasty thing here is that we have to convert from dates to integers in order to index the objects correctly, and then convert the integers back to dates when building the fake group data. 这里令人讨厌的是,我们必须将日期转换为整数才能正确索引对象,然后在构建伪造的组数据时将整数转换回日期。

Apply it like this: 像这样应用它:

var combinedGroup = combine_date_groups(remove_empty_bins(phaseUnderstand), 
    remove_empty_bins(phasePropose), 
    remove_empty_bins(phaseNegotiate), 
    remove_empty_bins(phaseDecide), 
    remove_empty_bins(phaseDeliver));

Here's a helper function for pulling a stack by index: 这是一个通过索引拉栈的帮助函数:

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

Now we can specify the stacks like this: 现在我们可以像这样指定堆栈:

chart
  .group(combinedGroup, "Understand", sel_stack(0))
  .stack(combinedGroup, "Propose", sel_stack(1))
  .stack(combinedGroup, "Negotiate", sel_stack(2))
  .stack(combinedGroup, "Decide", sel_stack(3))
  .stack(combinedGroup, "Deliver", sel_stack(4))

Updated fiddle here: https://jsfiddle.net/gordonwoodhull/8pc0p1we/17/ 在这里更新小提琴: https : //jsfiddle.net/gordonwoodhull/8pc0p1we/17/

(Forgot to post the fiddle the first time, I think it was at about version 8 before this adventure.) (忘记第一次提琴,我认为这次冒险之前大约是第8版。)

We're so far beyond what crossfilter and dc.js were originally designed for, it's kind of humorous. 到目前为止,我们已经超出了crossfilter和dc.js最初设计的目的,这有点幽默。 Data is complicated. 数据很复杂。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM