预览 d3 条形图,使其按每个 x 值的值排序

Preview d3 bar chart such that it sorted by values for each x value

我用以下数据创建了一个 nvd3 组条形图。

var data = [{
  key: "JUN",
  values: [
    { x: 2013, y: 200 },
    { x: 2014, y: 352 },
    { x: 2015, y: 1100 },
    { x: 2016, y: 120 }
  ]
}, {
  key: "JUL",
  values: [
    { x: 2013, y: 200 },
    { x: 2014, y: 862 },
    { x: 2015, y: 124 },
    { x: 2016, y: 500 }
  ]
}, {
  key: "MAR",
  values: [
    { x: 2013, y: 578 },
    { x: 2014, y: 964 },
    { x: 2015, y: 788 },
    { x: 2016, y: 152 }
  ]
}, {
  key: "JAN",
  values: [
    { x: 2013, y: 20 },
    { x: 2014, y: 485 },
    { x: 2015, y: 258 },
    { x: 2016, y: 900 }
  ]
}];

nv.addGraph(function() {
  var chart = nv.models.multiBarChart();

  chart.xAxis.tickFormat(d3.format(',f'));
  chart.yAxis.tickFormat(d3.format(',.1f'));

  d3.select('#chart svg')
      .datum(data)
      .transition().duration(500)
      .call(chart);

  nv.utils.windowResize(chart.update);

  return chart;
});
#chart svg {
  height: 400px;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.6/nv.d3.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/nvd3/1.8.6/nv.d3.min.js"></script>

<div id="chart"><svg></svg></div>


我得到的结果如下。

我的问题是,我能否重新排列 d3 图表,使其重新排列系列轴,以便按如下方式查看。通过这种方式,每个系列的条形图都会交换每个 x 值。我们可以在 d3 中实现吗?

您可能想要覆盖 nv.models.multiBar class。这就是绘制条形图的地方。

看起来似乎每个月都有自己的组,并且它们彼此堆叠在一起。每个分组彼此偏移一定量。

您需要按 y-value 对每个组进行排序并更改 x-position(水平边距)。

组偏移由以下因素决定:x(getX(d, i))

bars
  .attr('class', function(d, i) {
    return getY(d, i) < 0 ? 'nv-bar negative' : 'nv-bar positive'
  })
  .attr('transform',
    function(d, i) { return 'translate(' + x(getX(d, i)) + ',0)';
  })

组内的条形位置由以下因素决定:

barSelection
  .attr('x', function(d, i) {
    return d.series * x.rangeBand() / data.length;
  })

如果可以对数据进行排序,则可以更改这些值。


您可以使用以下方法转换数据。这可以在条形图布局函数中使用,以引用索引位置。

function sortGroupedData(data) {
  var groups = {};
  for (var m = 0; m < data.length; m++) {
    var subset = data[m];
    for (var y = 0; y < subset.values.length; y++) {
      var point = subset.values[y],
          tuples = groups[point.x] || [];
      tuples.push([subset.key, point.y, m]);
      groups[point.x] = tuples;
    }
  }
  Object.keys(groups).forEach(function(key) {
    groups[key].sort(function(a, b) {
      return a[1] - b[1];
    });
  });
  return groups;
}

生成的数据是一个地图(按年份),每个月按 y-value 升序排序。我将条形图的原始索引(位置)包含在其分组中。

{
  "2013" : [
    ["JAN", 20,  3],
    ["JUN", 200, 0],
    ["JUL", 200, 1],
    ["MAR", 578, 2]
  ],
  "2014" : [
    ["JUN", 352, 0],
    ["JAN", 485, 3],
    ["JUL", 862, 1],
    ["MAR", 964, 2]
  ],
  "2015" : [
    ["JUL", 124,  1],
    ["JAN", 258,  3],
    ["MAR", 788,  2],
    ["JUN", 1100, 0]
  ],
  "2016" : [
    ["JUN", 120, 0],
    ["MAR", 152, 2],
    ["JUL", 500, 1],
    ["JAN", 900, 3]
  ]
}

所需的代码更改

nv.models.multiBar = function() {
  // ...
  function chart(selection) {
    // ...
    selection.each(function(data) {
      var dataMap = sortGroupedData(data);
      // ...
      else {
        barSelection
          .attr('x', function(d,i) {
            var pos = dataMap[d.x].map((val, idx) => val[0] === d.key ? idx : -1).filter(x => x > -1)[0];
            return pos * x.rangeBand() / data.length;
          })
        // ...

最终工作演示

免责声明: 这不适用于堆叠,我会在有机会时修复它。这只会解决用户的直接问题。

https://jsfiddle.net/MrPolywhirl/xrj4eyph/


堆叠

在为 SVG 写出 y-position 之前,d.y1 值是 pre-determined。我们需要 re-calculate 数据中每个项目的 y1(底部)位置。

if (stacked) {
  barSelection
    .attr('y', function(d, i, j) {
      var yVal = 0;
      if (!data[j].nonStackable) {
        yVal = y(d.y1); // Already determined...