【问题标题】:d3 nested grouped bar chartd3嵌套分组条形图
【发布时间】:2016-06-07 21:51:34
【问题描述】:

首先抱歉,如果我的英语难以理解,我会尽力而为......

我对 D3.js 比较陌生,我正在尝试使用嵌套数据创建 D3 分组条形图。我查看了此处共享的一些解决方案,但它们仅显示一级分组。就我而言,数据将来自具有此数据结构的 csv 文件:

groups,categories,value 1,value 2,value 3
group 1,A,61.0158803,25.903359,13.08076071
group 1,B,71.27703826,21.0180133,7.70494844
group 1,C,82.70203982,13.52731445,3.770645737
group 2,A,58.85721523,28.25939061,12.88339417
group 2,B,71.39695487,20.66010982,7.942935308
group 2,C,82.22389321,13.68924542,4.08686137

该图表有两个 x 轴,一个用于组(0 级),一个用于类别(1 级)。值 1 到 3 将显示为每个类别的分组条形,并且将显示类别在相应的组内。

图表的结构应该是:

value 1 | value 2 | value 3 | value 1 | value 2 | value 3 | value 1 | value 2 | value 3 |
|        category A         |          category B         |          category C         |
|                                       group 1                                         |

与第 2 组相同,放置连续。

问题在于我正在处理的代码,我得到了正确的轴,但是在每个组区域中显示了对应于两个组的数据,一个在另一个之上。我无法将类别上的数据链接到它们对应的组,或者将它们绘制到对应的位置。

这是我目前得到的代码:

var x0 = d3.scale.ordinal()
  .rangeRoundBands([0,width], 0);

var x1 = d3.scale.ordinal()
.rangeRoundBands([0,width]);

var x2 = d3.scale.ordinal();

var y = d3.scale.linear()
  .range([height,0]);

var color = d3.scale.category10();

var x0Axis = d3.svg.axis()
  .scale(x0)
  .orient("bottom");

var x1Axis = d3.svg.axis()
  .scale(x1)
 .orient("bottom");

var yAxis = d3.svg.axis()
  .scale(y)
  .orient("left");

var svg = d3.select(".chart")
  .append("svg")
  .attr("class", "svg")
  .attr("viewBox", "" + margin* -1 + " " + margin* -1 + " " + (width + margin*2) + " " + (height + margin *2) + "")
  .attr ("preserveAspectRatio", "xMidYMid")
  .attr("width", "100%")
  .attr("height", "100%")



d3.csv("../data/EQ01.csv", function(error, data){
  if (error) throw error;

 var seriesNames = d3.keys(data[0]).filter(function(key) { return key !== "categories" && key !== "groups";});

  data.forEach(function(d) {
 d.values = seriesNames.map(function(name) { return {
     xValue: name,
     yValue: +d[name]
   };
 });
   });

   nested = d3.nest()
      .key(function(d) { return d.groups})
      .key(function(d) { return d.categories})
      .entries(data);

  y.domain([0, d3.max(data, function(d) { return d3.max(d.values, function(d) { return d.yValue; }); })]);
  x0.domain(nested.map(function(d) {return d.key;}));
  x1.domain(data.map(function(d) { return d.categories; })).rangeRoundBands([0, x0.rangeBand() ], 0.1);
  x2.domain(seriesNames).rangeRoundBands([0, x1.rangeBand()], 0);

  svg.append("g")
    .attr("class", "x0 axis")
    .attr("transform", "translate(0," + (height+30) + ")")
    .call(x0Axis);

  svg.append("g")
   .attr("class", "y axis")
   .call(yAxis)

  var group = svg.selectAll(".group")
   .data(nested)
   .enter().append("g")
   .attr("class", "group")
   .attr("transform", function(d) { return "translate(" + x0(d.key) + ",0)"; });

  group.append("g")
   .attr("class", "x1 axis")
   .attr("transform", "translate(0," + height + ")")
   .call(x1Axis);

  var category = group.selectAll(".category")
   .data(data)
   .enter().append("g")
   .attr("class", "category")
   .attr("transform", function(d) { return "translate(" + x1(d.categories) + ",0)"; });

  category.selectAll("rect")
  .data(function(d) { return d.values; })
  .enter().append("rect")
  .attr("width", x2.rangeBand())
  .attr("x", function(d) { return x2(d.xValue); })
  .attr("y", function(d) { return y(d.yValue); })
  .attr("height", function(d) { return height - y(d.yValue); })
  .style("fill", function(d){return color(d.xValue)})

非常感谢您的帮助!

【问题讨论】:

  • 我认为'.category' 组应该是.data(function(d) { return d.values; })。如果这不起作用,您可以设置一个 jsfiddle 或类似的吗?
  • 嗨@tarulen,感谢您的回答!我已经尝试过了,它似乎不起作用。这是一个 JSFiddle link

标签: javascript d3.js charts


【解决方案1】:

问题在于您没有将数据与元素正确连接。

为了得到正确的rangeBand值,我们需要构建不同的尺度。

var x_groups = d3.scale.ordinal()
  .rangeRoundBands([0, width], .1);

var x_categories = d3.scale.ordinal();

var x_values = d3.scale.ordinal();

我创建了一个嵌套数据结构,其中包含我们分组条形图方法所需的一切。

var nested = d3.nest()
    .key(function(d) {
      return d.groups;
    })
    .key(function(d) {
      return d.categories;
    })
    .rollup(function(leaves) {
      return [{
        key: 'v-a',
        value: leaves[0]['value 1']
      }, {
        key: 'v-b',
        value: leaves[0]['value 2']
      }, {
        key: 'v-c',
        value: leaves[0]['value 3']
      }];
    })
    .entries(data);

接下来让我们使用刚刚获得的信息来配置我们的体重秤。

  x_groups.domain(nested.map(function(d) {
    return d.key;
  }));
  //var categories = ['A', 'B', 'C']; 
  var categories = nested[0].values.map(function(d, i) {
    return d.key;
  });
  x_categories.domain(categories).rangeRoundBands([0, x_groups.rangeBand()]);
  //var values = ['value 1', 'value 2', 'value 3']; 
  var values = nested[0].values[0].values.map(function(d, i) {
    return d.key;
  });
  x_values.domain(values).rangeRoundBands([0, x_categories.rangeBand()]);

然后我们终于可以开始我们的数据连接了。可以看到,当我们输入一个新的信息级别时,我们需要正确设置data函数。

var groups_g = svg.selectAll(".group")
  .data(nested)
  .enter().append("g")
  .attr("class", function(d) {
    return 'group group-' + d.key;
  })
  .attr("transform", function(d) {
    return "translate(" + x_groups(d.key) + ",0)";
  });

var categories_g = groups_g.selectAll(".category")
  .data(function(d) {
    return d.values;
  })
  .enter().append("g")
  .attr("class", function(d) {
    return 'category category-' + d.key;
  })
  .attr("transform", function(d) {
    return "translate(" + x_categories(d.key) + ",0)";
  });

var categories_labels = categories_g.selectAll('.category-label')
  .data(function(d) {
    return [d.key];
  })
  .enter().append("text")
  .attr("class", function(d) {
    return 'category-label category-label-' + d;
  })
  .attr("x", function(d) {
    return x_categories.rangeBand() / 2;
  })
  .attr('y', function(d) {
    return height + 25;
  })
  .attr('text-anchor', 'middle')
  .text(function(d) {
    return d;
  })

var values_g = categories_g.selectAll(".value")
  .data(function(d) {
    return d.values;
  })
  .enter().append("g")
  .attr("class", function(d) {
    return 'value value-' + d.key;
  })
  .attr("transform", function(d) {
    return "translate(" + x_values(d.key) + ",0)";
  });

var values_labels = values_g.selectAll('.value-label')
  .data(function(d) {
    return [d.key];
  })
  .enter().append("text")
  .attr("class", function(d) {
    return 'value-label value-label-' + d;
  })
  .attr("x", function(d) {
    return x_values.rangeBand() / 2;
  })
  .attr('y', function(d) {
    return height + 10;
  })
  .attr('text-anchor', 'middle')
  .text(function(d) {
    return d;
  })

var rects = values_g.selectAll('.rect')
  .data(function(d) {
    return [d];
  })
  .enter().append("rect")
  .attr("class", "rect")
  .attr("width", x_values.rangeBand())
  .attr("x", function(d) {
    return 0;
  })
  .attr("y", function(d) {
    return y(d.value);
  })
  .attr("height", function(d) {
    return height - y(d.value);
  })
  .style("fill", function(d) {
    return color(d.key);
  });

工作 plnkr:https://plnkr.co/edit/qGZ1YuyFZnVtp04bqZki?p=preview

【讨论】:

  • 哇,非常感谢@torresomar!它工作得很好,最重要的是,我从你的回答中学到了很多!
猜你喜欢
  • 2016-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多