我正在尝试可视化时间序列中的已售商品。我使用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>
如果您喜欢使用代码,请使用JSFiddle。
解决方案可能在于计算钢筋的整个高度并计算节点距中心点的偏移量。
原始代码的结构方式看起来像是计算每个节点的偏移量,然后使用这些偏移量来计算节点位置。我可能需要在某个时候以某种方式修改此计算出的偏移量,但我只是想不出方法和位置。如果可能的话。
如果无法做到这一点,那么d3中还有另一种方法来获得视觉上相似的结果吗?
最佳答案
您可以尝试使用计算最大最大高度(我刚刚添加了更改的行,其余的相同):
//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进行了这些更改。