在d3中垂直居中堆叠的条形

时间:2016-01-13 14:20:00

标签: javascript d3.js

我试图想象出时间序列中出售的商品。我使用Nick Rabinowitz's alluvial chart作为基础,但对其进行了一些修改。其他一切看起来不错,但我想垂直居中堆叠的条形。

这就是我的图表目前的样子:

enter image description here



/*Original code obtained from http://nickrabinowitz.com/projects/d3/alluvial/alluvial.html*/

var data = {
  "times": [
    [{
      "id": "item1",
      "nodeName": "Item 1 50/2015",
      "nodeValue": 9,
      "incoming": []
    }, {
      "id": 1,
      "nodeName": "Item 2 50/2015",
      "nodeValue": 6,
      "incoming": []
    }, {
      "id": 2,
      "nodeName": "Item 3 50/2015",
      "nodeValue": 3,
      "incoming": []
    }],
    [{
      "id": "item12",
      "nodeName": "Item 1 51/2015",
      "nodeValue": 8,
      "incoming": []
    }, {
      "id": 4,
      "nodeName": "Item 2 51/2015",
      "nodeValue": 2,
      "incoming": []
    }, {
      "id": 5,
      "nodeName": "Item 3 51/2015",
      "nodeValue": 5,
      "incoming": []
    }],
    [{
      "id": 6,
      "nodeName": "Item 1 52/2015",
      "nodeValue": 1,
      "incoming": []
    }, {
      "id": 7,
      "nodeName": "Item 2 52/2015",
      "nodeValue": 7,
      "incoming": []
    }, {
      "id": 8,
      "nodeName": "Item 3 50/2015",
      "nodeValue": 4,
      "incoming": []
    }]
  ],
  "links": [{
      "source": "item1",
      "target": "item12",
      "outValue": 9,
      "inValue": 8
    }, {
      "source": "item12",
      "target": 6,
      "outValue": 8,
      "inValue": 1
    }, {
      "source": 1,
      "target": 4,
      "outValue": 6,
      "inValue": 2
    }, {
      "source": 4,
      "target": 7,
      "outValue": 2,
      "inValue": 7
    }, {
      "source": 2,
      "target": 5,
      "outValue": 3,
      "inValue": 5
    }
    /*,
                {
                    "source": 5,
                    "target": 8,
                    "outValue": 5,
                    "inValue": 4
                }*/
  ]
};

/* Process Data */

// make a node lookup map
var nodeMap = (function() {
  var nm = {};
  data.times.forEach(function(nodes) {
    nodes.forEach(function(n) {
      nm[n.id] = n;
      // add links and assure node value
      n.links = [];
      n.incoming = [];
      n.nodeValue = n.nodeValue || 0;
    })
  });
  console.log(nm);
  return nm;
})();

// attach links to nodes
data.links.forEach(function(link) {
  console.log(link);
  nodeMap[link.source].links.push(link);
  nodeMap[link.target].incoming.push(link);
});

// sort by value and calculate offsets
data.times.forEach(function(nodes) {
  var nCumValue = 0;
  nodes.sort(function(a, b) {
    return d3.descending(a.nodeValue, b.nodeValue)
  });
  nodes.forEach(function(n, i) {
    n.order = i;
    n.offsetValue = nCumValue;
    nCumValue += n.nodeValue;
    // same for links
    var lInCumValue;
    var lOutCumValue;
    // outgoing
    if (n.links) {
      lOutCumValue = 0;
      n.links.sort(function(a, b) {
        return d3.descending(a.outValue, b.outValue)
      });
      n.links.forEach(function(l) {
        l.outOffset = lOutCumValue;
        lOutCumValue += l.outValue;
      });
    }
    // incoming
    if (n.incoming) {
      lInCumValue = 0;
      n.incoming.sort(function(a, b) {
        return d3.descending(a.inValue, b.inValue)
      });
      n.incoming.forEach(function(l) {
        l.inOffset = lInCumValue;
        lInCumValue += l.inValue;
      });
    }
  })
});
data = data.times;

// calculate maxes
var maxn = d3.max(data, function(t) {
    return t.length
  }),
  maxv = d3.max(data, function(t) {
    return d3.sum(t, function(n) {
      return n.nodeValue
    })
  });

/* Make Vis */

// settings and scales
var w = 960,
  h = 500,
  gapratio = .5,
  padding = 7,
  x = d3.scale.ordinal()
  .domain(d3.range(data.length))
  .rangeBands([0, w], gapratio),
  y = d3.scale.linear()
  .domain([0, maxv])
  .range([0, h - padding * maxn]),
  area = d3.svg.area()
  .interpolate('monotone');

// root
var vis = d3.select("#alluvial")
  .append("svg:svg")
  .attr("width", w)
  .attr("height", h);

// time slots
var times = vis.selectAll('g.time')
  .data(data)
  .enter().append('svg:g')
  .attr('class', 'time')
  .attr("transform", function(d, i) {
    return "translate(" + x(i) + ",0)"
  });

// node bars
var nodes = times.selectAll('g.node')
  .data(function(d) {
    return d
  })
  .enter().append('svg:g')
  .attr('class', 'node');

nodes.append('svg:rect')
  .attr('fill', 'steelblue')
  .attr('y', function(n, i) {
    return y(n.offsetValue) + i * padding;
  })
  .attr('width', x.rangeBand())
  .attr('height', function(n) {
    return y(n.nodeValue)
  })
  .append('svg:title')
  .text(function(n) {
    return n.nodeName
  });

// links
var links = nodes.selectAll('path.link')
  .data(function(n) {
    return n.links || []
  })
  .enter().append('svg:path')
  .attr('class', 'link')
  .attr('d', function(l, i) {
    var source = nodeMap[l.source];
    var target = nodeMap[l.target];
    var gapWidth = x(0);
    var bandWidth = x.rangeBand() + gapWidth;

    var sourceybtm = y(source.offsetValue) +
      source.order * padding +
      y(l.outOffset) +
      y(l.outValue);
    var targetybtm = y(target.offsetValue) +
      target.order * padding +
      y(l.inOffset) +
      y(l.inValue);
    var sourceytop = y(source.offsetValue) +
      source.order * padding +
      y(l.outOffset);
    var targetytop = y(target.offsetValue) +
      target.order * padding +
      y(l.inOffset);

    var points = [
      [x.rangeBand(), sourceytop],
      [x.rangeBand() + gapWidth / 5, sourceytop],
      [bandWidth - gapWidth / 5, targetytop],
      [bandWidth, targetytop],
      [bandWidth, targetybtm],
      [bandWidth - gapWidth / 5, targetybtm],
      [x.rangeBand() + gapWidth / 5, sourceybtm],
      [x.rangeBand(), sourceybtm]
    ];

    return area(points);
  });

body {
  margin: 3em;
}
.node {
  stroke: #fff;
  stroke-width: 2px;
}
.link {
  fill: #000;
  stroke: none;
  opacity: .3;
}
.node {
  stroke: none;
}

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

如果你喜欢玩代码,这是一个JSFiddle

解决方案可能在于计算条的整个高度并计算中心点的节点偏移量。

原始代码的结构化方式似乎是计算每个节点的偏移量,然后使用这些偏移量来计算节点位置。我可能需要能够在某个时刻修改这个计算出的偏移,但我无法弄清楚如何以及在何处。如果可能的话。

如果这不可能,d3中是否有另一种方法可以获得视觉上相似的结果?

1 个答案:

答案 0 :(得分:1)

您可以尝试使用(我刚刚添加了更改的行,计算了最大全高),其余部分相同):

//calculate the max full height
var maxHeight=0;
data.times.forEach(function(nodes,p) {
    var curHeight=0;
    nodes.forEach(function(n) {
    curHeight+=n.nodeValue;
    });
  if(curHeight > maxHeight) maxHeight=curHeight
});

然后将(maxHeight/2 - curHeight/2)添加到偏移量中,curHeight是每个频段的节点总高度。

为此,您可以在计算偏移的循环中添加几行:

// sort by value and calculate offsets
data.times.forEach(function(nodes,p) {
  var nCumValue = 0;
  nodes.sort(function(a, b) {
    return d3.descending(a.nodeValue, b.nodeValue)
  });  

  var bandHeight = 0;
  nodes.forEach(function(n) {
    bandHeight+=n.nodeValue;
  });

  nodes.forEach(function(n, i) {
    n.order = i;
    n.offsetValue = nCumValue + (maxHeight/2-bandHeight/2);

Here's a JSFiddle进行了这些更改。