2016-01-13 188 views
2

我試圖從時間序列中可視化出售物品。我使用Nick Rabinowitz's alluvial chart作爲基礎,但對其進行了少量修改。其他一切看起來不錯,但我想垂直居中堆疊的酒吧。在d3中垂直居中堆疊的酒吧

這是我的圖表看起來像此刻:

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>

這裏是一個JSFiddle如果你喜歡玩的代碼。

該解決方案可能位於計算杆的全高並計算中心點的節點偏移量的某處。

原始代碼的結構看起來像是計算每個節點的偏移量,然後使用這些偏移量來計算節點位置。我可能需要能夠在某個點修改這個計算出的偏移量,但我無法弄清楚如何以及在哪裏。如果這甚至是可能的。

如果這是不可能的,d3中還有另外一種方法實現視覺上相似的結果嗎?

回答

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這些變化。

+0

那麼,這是一個簡單的解決方案。它的工作。謝謝! – locationunknown