【问题标题】:dc.js Ordering X-Axis by Monthdc.js 按月排序 X 轴
【发布时间】:2017-02-28 11:12:48
【问题描述】:

我正在尝试使用 dc.js 创建堆叠条形图,如下所示:

数据:

<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:

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

我能够渲染图表,但我无法按月按升序对 X 轴进行排序并使其沿 X 轴具有弹性。如果我只使用日期,那么图表会变得过于详细(尽管 x 轴仍然没有弹性),这对于我创建它的目的没有用。这是一个示例小提琴:https://jsfiddle.net/NewMonk/1hbjwxzy/67/

【问题讨论】:

    标签: javascript d3.js dc.js crossfilter stacked-chart


    【解决方案1】:

    我强烈建议在 X 轴基于时间时使用时间刻度。这只是多一点工作(而且可怕的空白图表的风险更大),但它更加灵活和准确。

    首先,我们需要让 crossfilter 按月分类。 d3.time.month 会将每个日期向下舍入到最接近的月份:

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

    这与您创建的字符串箱具有相同的效果,但它将键保留为日期。

    然后我们可以告诉 dc.js 使用基于时间的比例和 elasticX:

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

    .xUnits 是使条形宽度正确的原因(以便 dc.js 可以计算可见条形的数量)。

    我们可以恢复 monthNameFormat 刻度格式并像这样显示每个刻度:

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

    最后,如果使用另一个图表进行过滤,您可能需要remove empty bins,以便当它们下降到零时将删除条形:

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

    后记

    @Kush cmets 指出,从一组堆叠的基于日期的组中删除空箱并不容易。原来如此。

    这是用于组合基于日期的组的 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;
            }
        };
    }
    

    这里令人讨厌的是,我们必须将日期转换为整数才能正确索引对象,然后在构建假组数据时将整数转换回日期。

    像这样应用它:

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

    这是一个按索引拉取堆栈的辅助函数:

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

    现在我们可以像这样指定堆栈:

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

    在此处更新小提琴:https://jsfiddle.net/gordonwoodhull/8pc0p1we/17/

    (第一次忘记发布小提琴了,我想在这次冒险之前大约是第 8 版。)

    我们远远超出了 crossfilter 和 dc.js 最初的设计目的,它有点幽默。数据很复杂。

    【讨论】:

    • 非常感谢戈登。它就像一个魅力,但现在我坚持删除空垃圾箱并将所有组组合起来,就像你建议的那样与另一个图表进行过滤。我真的需要合并所有组吗?有没有我可以参考的例子?我关注了this link。但遗憾的是,它导致了一张空白的白色图表。再次感谢。
    • 天啊。好点子,这根本不是件容易的事。我已经更新了上面的答案。我们真的需要一个数据预处理库来介于 crossfilter 和 dc.js 之间。
    • 不用说,现在组合组是可行的。但是 x 轴上仍然没有弹性 (this is my updated fiddle)。有什么我仍然做错或错过的事情吗?
    • 呃,我没有要测试的东西,所以我实际上忘记了remove_empty_bins(...)。以上已更正。
    • 很遗憾地告诉您,但我仍然无法正常工作。这似乎是由负/浮点值引起的。例如考虑这个样本箱:code key: Mon Oct 01 2012 00:00:00 0:0 1:0 2:0 3:-9.313225746154785e-10 4:0 Updated Fiddle
    猜你喜欢
    • 2014-07-09
    • 1970-01-01
    • 1970-01-01
    • 2022-01-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-16
    相关资源
    最近更新 更多