d3 v4嵌套数据和堆积条形图

时间:2017-06-02 02:24:43

标签: javascript d3.js stacked-chart

我正在尝试制作一个如下所示的图表:Example Bar Chart

我有一个D3.nest数据结构,如下所示:

{"key":"Area 1","values":[
    {"key":"5. Validation Complete","value":12.5},
    {"key":"Deferred","value":1},
    {"key":"3. Identify & Validate Proposed Solutions","value":5},
    {"key":"1. Define & Describe the Problem or Opportunity","value":0}]},
{"key":"Area 2","values":[
    {"key":"5. Validation Complete","value":41.2},
    {"key":"4. Implement the Solutions","value":86.6},
    {"key":"3. Identify & Validate Proposed Solutions","value":6},
    {"key":"2. Identify Root Causes","value":4},
    {"key":"1. Define & Describe the Problem or Opportunity","value":9}]},
{"key":"Area 3","values":[
    {"key":"5. Validation Complete","value":40},
    {"key":"4. Implement the Solutions","value":49.2},
    {"key":"3. Identify & Validate Proposed Solutions","value":10.4}]},
{"key":"Area 4","values":[
    {"key":"Deferred","value":0.25},
    {"key":"4. Implement the Solutions","value":28},
    {"key":"3. Identify & Validate Proposed Solutions","value":84.9},
    {"key":"2. Identify Root Causes","value":0}]}

我的zKeys结构为:

Array Image

我未能成功Bostock's Stacked Bar Chart Example和SO post

这是我的代码:

    var svg = d3.select("svg"),
    margin = {top: 20, right: 20, bottom: 30, left: 40},
    width = +svg.attr("width") - margin.left - margin.right,
    height = +svg.attr("height") - margin.top - margin.bottom;

var x = d3.scaleBand().rangeRound([0, width]).padding(0.1);
var y = d3.scaleLinear().rangeRound([height, 0]);
var z = d3.scaleOrdinal().range(["#F8A11E", "#E51F36", "#582C85", "#1C92D0", "#017165", "#7F7F7F"]);    

var g = svg.append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

var sharepointStatusArray = getListData("Points List","ID,Title,Color_Code");
var data=getListData("Points%20List","$select=Area,StatusID,Points,Status/Title&$expand=Status");   

var zKeys = [];

sharepointStatusArray.forEach(function(d)
 {
    zKeys.push(d.Title);
 });

var nestData = d3.nest()
    .key(function(d) { return d.Area; })
    .key(function(d) { return d.Status.Title; })
    .rollup(function(v) { return d3.sum(v, function(d) { return d.Points; }); })
    .entries(data);

    nestData.sort(function(a,b) {return b.total - a.total;});
    x.domain(nestData.map(function(d) { return d.key; }));
    y.domain([0, d3.max(nestData, function(d){return d3.sum(d.values, function(d){return d.value})})+20]).nice();
    z.domain(zKeys) 

  g.append("g")
    .selectAll(".serie")
    .data(d3.stack().keys(zKeys)(nestData))
    .enter().append("g")
        .attr("class","serie")
        .selectAll("rect")
        .data(function(d) { return d; })
        .enter().append("rect")
            .attr("class", "bar")
            .attr("fill", function(d) { return z(d.key);})
            .attr("x", function(d) {return x(d.data.key);})         
            .attr("y", function(d) {return y(d[1]);})
            .attr("height", function(d) { return y(d[0]) - y(d[1]); })
            .attr("width", x.bandwidth());

  g.append("g")
    .attr("class", "axis axis--x")
    .attr("transform", "translate(0," + height + ")")
    .call(d3.axisBottom(x));

  g.append("g")
      .attr("class", "axis")
      .call(d3.axisLeft(y).ticks(null, "s"))
    .append("text")
      .attr("x", 2)
      .attr("y", y(y.ticks().pop()) + 0.5)
      .attr("dy", "0.32em")
      .attr("fill", "#000")
      .attr("font-weight", "bold")
      .attr("text-anchor", "start")
      .text("Hours");


    //Creating legend for colors
      var legend = g.append("g")
      .attr("font-family", "sans-serif")
      .attr("font-size", 10)
      .attr("text-anchor", "end")
    .selectAll("g")
    .data(zKeys.slice())
    .enter().append("g")
      .attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });

  legend.append("rect")
      .attr("x", width - 19)
      .attr("width", 19)
      .attr("height", 19)
      .attr("fill", z);

  legend.append("text")
      .attr("x", width - 24)
      .attr("y", 9.5)
      .attr("dy", "0.32em")
      .text(function(d) { return d; }); 

根据示例,我无法绘制堆积条形图。几乎所有的例子都使用d3.csv而不是d3.nest所以我不知道这些例子如何转化为带有输出数组的d3.nest场景。

任何人都可以帮帮我吗?感谢。

1 个答案:

答案 0 :(得分:4)

我最终找到了自己问题的答案。 我发现的一件事是,我发现处理d3.stack()的所有信息都表明发送给函数的数据需要是2D(2维)。这是d3.nest()很好地输出的东西。事实证明这不准确。 我应该从一开始就这样做了,但我调试了示例Mike Bostock’s Stacked Bar Chart并发现每个人在其示例中使用的d3.csv()的输出实际上输出了一个1D数组,每个元素包含键/值对每个矩形中显示的数据。 Screenshot of the data output from Mike's example

我可能已经做了一个非常迂回的方式,但是我这样做是为了解决我没有d3.stack()的正确数据结构的问题。

1)我保留了对d3.nest()输出的使用,因为它允许我将单个值汇总为每个元素的单个键/值对,如下所示:

Nested Data

2)然后,我使用以下代码清理了该数据输出,使其看起来像所有示例中的d3.csv()的输出(为缺少的数据添加键/值默认值并展平结构:

//BEGIN data cleanup for d3.stack
//Add default values for missing data points to make each array formatted the same
    nestData = nestData.map(function(keyObj) {
        return {
            key: keyObj.key,
            values: zKeys.map(function(k) { 
                    value = keyObj.values.filter(function(v) { return v.key == k; })[0];
                    return value || ({key: k, value: 0});
                })
        };
    });

//Loop through the nested array and create a new array element that converts each individual nested element into a key/value pair in a single object.
var flatData = [];
nestData.forEach(function(d) {
    var obj = { Area: d.key }
        d.values.forEach(function(f) {
            obj[f.key] = f.value;
        });
    flatData.push(obj);
  });
//END data cleanup for d3.stack

数据现在看起来像这样: Flattened Structure

3)数据清理之后,我就可以使用Mike开箱即用的代码,如下所示:

    x.domain(flatData.map(function(d) { return d.Area; }));
    y.domain([0, d3.max(nestData, function(d){return d3.sum(d.values, function(d){return d.value})})+20]);
    z.domain(zKeys) 

var g = svg.append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  g.append("g")
    .selectAll("g")
    .data(d3.stack().keys(zKeys)(flatData))
    .enter().append("g")
      .attr("fill", function(d) { return z(d.key); })
    .selectAll("rect")
    .data(function(d) { return d; })
    .enter().append("rect")
      .attr("x", function(d) { return x(d.data.Area); })
      .attr("y", function(d) { return y(d[1]); })
      .attr("height", function(d) { return y(d[0]) - y(d[1]); })
      .attr("width", x.bandwidth());