当我的群组大小不一时制作分组条形图?

时间:2016-05-01 03:55:34

标签: d3.js

我正试图从一些分组数据中制作一个条形图。这是虚拟数据,但结构基本相同。数据:election results包括一群候选人,组织到他们所在的地区,以及总投票数:

district,candidate,votes
Dist 1,Leticia Putte,3580
Dist 2,David Barron,1620
Dist 2,John Higginson,339
Dist 2,Walter Bannister,2866
[...]

我想创建一个条形图或柱形图(老实说,虽然我的最终目标是水平的),按地区划分候选人。

迈克博斯托克有一个excellent demo,但我在为我的目的而智能地翻译它时遇到了麻烦。我开始在https://jsfiddle.net/97ur6cwt/6/取笑它,但我的数据组织有所不同 - 而不是行,按组,我有一个列设置类别。可能只有一名候选人或可能会有一些候选人。

如果组的大小不同,我可以对项目进行分组吗?

2 个答案:

答案 0 :(得分:5)

我的回答类似于@GerardoFurtado,但我使用d3.nest来构建每个区域的域名。这消除了对硬编码值的需要并稍微清理了一下:

y0.domain(data.map(function(d) { return d.district; }));

var districtD = d3.nest()
  .key(function(d) { return d.district; })
  .rollup(function(d){
    return d3.scale.ordinal()
        .domain(d.map(function(c){return c.candidate}))
      .rangeRoundBands([0, y0.rangeBand()], pad);
  }).map(data);

districtD成为您放置rects时使用的y轴域的地图:

  svg.selectAll("bar")
      .data(data)
      .enter().append("rect")
      .style("fill", function(d,i) {
          return color(d.district);
      })
      .attr("x", 0)
      .attr("y", function(d) { return y0(d.district) + districtD[d.district](d.candidate); })
      .attr("height", function(d){
        return districtD[d.district].rangeBand();
      })
      .attr("width", function(d) {
        return x(d.votes);
      });

我参加了会议,但下一步是清理轴并在那里获取候选人姓名。

完整运行代码:



var url = "https://gist.githubusercontent.com/amandabee/edf73bc0bbe131435c952f5ed47524a6/raw/99febb9971f76e36af06f1b99913fcaa645ecb3e/election.csv"
var m = {top: 10, right: 10, bottom: 50, left: 110},
  w = 800 - m.left - m.right,
  h = 500 - m.top - m.bottom,
  pad = .1;

var x = d3.scale.linear().range([0, w]);
y0 = d3.scale.ordinal().rangeRoundBands([0, h], pad);

var color = d3.scale.category20c();

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

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom")
    .ticks(5)
    .tickFormat(d3.format("$,.0f"));


var svg = d3.select("#chart").append("svg")
  .attr("width", w + m.right + m.left + 100)
  .attr("height", h + m.top + m.bottom)
  .append("g")
  .attr("transform",
        "translate(" + m.left + "," + m.top + ")");

        // This moves the SVG over by m.left(110)
        // and down by m.top (10)


  d3.csv(url, function(error, data) {

    data.forEach(function(d) {
      d.votes = +d.votes;
    });
    
    y0.domain(data.map(function(d) { return d.district; }));
    districtD = d3.nest()
    	.key(function(d) { return d.district; })
      .rollup(function(d){
      	console.log(d);
        return d3.scale.ordinal()
        	.domain(d.map(function(c){return c.candidate}))
          .rangeRoundBands([0, y0.rangeBand()], pad);
      })
      .map(data);    
		
    x.domain([0, d3.max(data, function(d) {
        return d.votes;
      })]);

      svg.append("g")
          .attr("class", "x axis")
          .attr("transform", "translate(0," + h + ")")
          .call(xAxis)
          .selectAll("text")
          .style("text-anchor", "middle");

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

      svg.selectAll("bar")
          .data(data)
          .enter().append("rect")
          .style("fill", function(d,i) {
              return color(d.district);
          })
          .attr("x", 0)
          .attr("y", function(d) { return y0(d.district) + districtD[d.district](d.candidate); })
          .attr("height", function(d){
          	return districtD[d.district].rangeBand();
          })
          .attr("width", function(d) {
            return x(d.votes);
            });

      svg.selectAll(".label")
  			   .data(data)
  			   .enter().append("text")
  			   .text(function(d) {
             return (d.votes);
             })
  			   .attr("text-anchor", "start")
           .attr("x", function(d) { return x(d.votes)})
           .attr("y", function(d) { return y0(d.district) +  districtD[d.district](d.candidate) + districtD[d.district].rangeBand()/2;})
  			   .attr("class", "axis");

  });

    .axis {
      font: 10px sans-serif;
    }
    .axis path, .axis line {
      fill: none;
      stroke: black;
      shape-rendering: crispEdges;
    }

<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<div id="chart"></div>
&#13;
&#13;
&#13;

另一个版本,它调整条形图的大小并适当地缩放外部域:

&#13;
&#13;
<!DOCTYPE html>
<html>

<head>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
  <style>
    .label {
      font: 10px sans-serif;
    }
    
    .axis {
      font: 11px sans-serif;
      font-weight: bold;
    }
    
    .axis path,
    .axis line {
      fill: none;
      stroke: black;
      shape-rendering: crispEdges;
    }
  </style>
</head>

<body>
  <div id="chart"></div>
  <script>
    var url = "https://gist.githubusercontent.com/amandabee/edf73bc0bbe131435c952f5ed47524a6/raw/99febb9971f76e36af06f1b99913fcaa645ecb3e/election.csv"
    var m = {
        top: 10,
        right: 10,
        bottom: 50,
        left: 110
      },
      w = 800 - m.left - m.right,
      h = 500 - m.top - m.bottom,
      pad = .1, padPixel = 5;

    var x = d3.scale.linear().range([0, w]);
    var y0 = d3.scale.ordinal();

    var color = d3.scale.category20c();

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

    var xAxis = d3.svg.axis()
      .scale(x)
      .orient("bottom")
      .ticks(5)
      .tickFormat(d3.format("$,.0f"));


    var svg = d3.select("#chart").append("svg")
      .attr("width", w + m.right + m.left + 100)
      .attr("height", h + m.top + m.bottom)
      .append("g")
      .attr("transform",
        "translate(" + m.left + "," + m.top + ")");

    // This moves the SVG over by m.left(110)
    // and down by m.top (10)


    d3.csv(url, function(error, data) {

      data.forEach(function(d) {
        d.votes = +d.votes;
      });

      var barHeight = h / data.length;

      y0.domain(data.map(function(d) {
        return d.district;
      }));
      
      var y0Range = [0];
      districtD = d3.nest()
        .key(function(d) {
          return d.district;
        })
        .rollup(function(d) {
          var barSpace = (barHeight * d.length);
          y0Range.push(y0Range[y0Range.length - 1] + barSpace);
          return d3.scale.ordinal()
            .domain(d.map(function(c) {
              return c.candidate
            }))
            .rangeRoundBands([0, barSpace], pad);
        })
        .map(data);
      
      y0.range(y0Range);
      
      x.domain([0, d3.max(data, function(d) {
        return d.votes;
      })]);

      svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + h + ")")
        .call(xAxis)
        .selectAll("text")
        .style("text-anchor", "middle");

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

      svg.selectAll("bar")
        .data(data)
        .enter().append("rect")
        .style("fill", function(d, i) {
          return color(d.district);
        })
        .attr("x", 0)
        .attr("y", function(d) {
          return y0(d.district) + districtD[d.district](d.candidate);
        })
        .attr("height", function(d) {
          return districtD[d.district].rangeBand();
        })
        .attr("width", function(d) {
          return x(d.votes);
        });

      var ls = svg.selectAll(".labels")
        .data(data)
        .enter().append("g");
        
      ls.append("text")
        .text(function(d) {
          return (d.votes);
        })
        .attr("text-anchor", "start")
        .attr("x", function(d) {
          return x(d.votes)
        })
        .attr("y", function(d) {
          return y0(d.district) + districtD[d.district](d.candidate) + districtD[d.district].rangeBand() / 2;
        })
        .attr("class", "label");

      ls.append("text")
        .text(function(d) {
          return (d.candidate);
        })
        .attr("text-anchor", "end")
        .attr("x", -2)
        .attr("y", function(d) {
          return y0(d.district) + districtD[d.district](d.candidate) + districtD[d.district].rangeBand() / 2;
        })
        .style("alignment-baseline", "middle")
        .attr("class", "label");

    });
  </script>
</body>

</html>
&#13;
&#13;
&#13;

答案 1 :(得分:2)

这是部分解决方案:https://jsfiddle.net/hb13oe4v/

这里的主要问题是为每个具有可变域的组创建一个比例。与博斯托克的例子不同,你不会为每个团体(地区)提供相同数量的酒吧(候选人)。

所以,我必须做一个解决方法。首先,我以最微不足道的方式嵌套数据:

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

然后相应地创建了组:

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

由于我无法创建y1(在Bostock的示例中为x1)比例,我不得不硬编码条的高度(这本身就是坏的)。另外,为了使每组中的条形图居中,我创建了这个疯狂的数学,它将一个条形放在中间,下一个条形图,下一个条形图,下一个条形图等等:

.attr("y", function(d, i) {
      if( i % 2 == 0){ return (y.rangeBand()/2 - 10) + (i/2 + 0.5) * 10}
      else { return (y.rangeBand()/2 - 10) - (i/2) * 10}
      })

当然,如果我们可以为每个组设置一个变量比例,所有这些都可以避免和编码更优雅。