我试图想象出时间序列中出售的商品。我使用Nick Rabinowitz's alluvial chart作为基础,但对其进行了一些修改。其他一切看起来不错,但我想垂直居中堆叠的条形。
这就是我的图表目前的样子:
/*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;
如果你喜欢玩代码,这是一个JSFiddle。
解决方案可能在于计算条的整个高度并计算中心点的节点偏移量。
原始代码的结构化方式似乎是计算每个节点的偏移量,然后使用这些偏移量来计算节点位置。我可能需要能够在某个时刻修改这个计算出的偏移,但我无法弄清楚如何以及在何处。如果可能的话。
如果这不可能,d3中是否有另一种方法可以获得视觉上相似的结果?
答案 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进行了这些更改。