|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
rect { |
|
fill: none; |
|
stroke: #000; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
rect.before{ |
|
stroke: red; |
|
} |
|
|
|
rect.after{ |
|
stroke: orange; |
|
} |
|
|
|
rect.final{ |
|
stroke: green; |
|
} |
|
</style> |
|
|
|
<svg width="960" height="600"></svg> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script> |
|
|
|
var svg = d3.select("svg"), |
|
margin = {top: 40, right: 40, bottom: 40, left: 40}, |
|
width = svg.attr("width") - margin.left - margin.right, |
|
height = svg.attr("height") - margin.top - margin.bottom; |
|
|
|
var g = svg.append("g") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|
|
|
g.append("rect") |
|
.attr("class", "frame") |
|
.attr("width", width) |
|
.attr("height", height); |
|
|
|
var rectHeight = 35; |
|
var data = []; |
|
var trans = 0; |
|
|
|
for (var i=0; i<8; i++) { |
|
data.push(Math.round(Math.random() * (height) - rectHeight/2)); |
|
} |
|
data.sort(function(a, b){return b - a}); |
|
|
|
drawRectangles(g, data, "before"); |
|
|
|
var dataObject = createObject(data); |
|
dataObject = adjustBottoms(dataObject); |
|
var data2 = trimObject(dataObject); |
|
drawRectangles(g, data2, "after"); |
|
|
|
if (data2[data2.length-1] < 0) { |
|
dataObject = adjustTops(dataObject); |
|
var data3 = trimObject(dataObject); |
|
drawRectangles(g, data3, "final"); |
|
} |
|
|
|
function drawRectangles(sel, data, className){ |
|
var gNew = sel.append("g") |
|
.attr("transform", "translate(" + trans + ", 0)"); |
|
|
|
gNew.selectAll("g") |
|
.data(data) |
|
.enter() |
|
.append("g") |
|
.attr("transform", function(d) {return "translate(100, " + d + ")";}) |
|
.append("rect") |
|
.attr("class", className) |
|
.attr("width", "50px") |
|
.attr("height", rectHeight); |
|
|
|
trans += 200; |
|
} |
|
|
|
function createObject(data) { |
|
// setup data structure with rectangles from bottom to the top |
|
var dataObject = []; |
|
var obj = {top: height, bottom: height + rectHeight}; // add dummy rect for lower bound |
|
|
|
dataObject.push(obj); |
|
data.forEach(function(d,i){ |
|
obj = {top: d, bottom: d + rectHeight} |
|
dataObject.push(obj); |
|
}); |
|
obj = {top: 0 - rectHeight, bottom: 0}; // add dummy rect for upper bound |
|
dataObject.push(obj); |
|
|
|
return dataObject; |
|
} |
|
|
|
function trimObject(dataObject) { // convert back to original array of values, also remove dummies |
|
var data3 = []; |
|
dataObject.forEach(function(d,i){ |
|
if (!(i === 0 || i === dataObject.length-1)) { |
|
data3.push(d.top); |
|
} |
|
}); |
|
return data3; |
|
} |
|
|
|
function adjustBottoms(dataObject){ |
|
dataObject.forEach(function(d,i){ |
|
if (!(i === 0 || i === dataObject.length-1)) { |
|
var diff = dataObject[i-1].top - d.bottom; |
|
if (diff < 0) { // move rect up |
|
d.top += diff; |
|
d.bottom += diff; |
|
} |
|
} |
|
}); |
|
return dataObject; |
|
} |
|
|
|
function adjustTops(dataObject){ |
|
for (var i = dataObject.length; i-- > 0; ){ |
|
if (!(i === 0 || i === dataObject.length-1)) { |
|
var diff = dataObject[i+1].bottom - dataObject[i].top; |
|
if (diff > 0) { // move rect down |
|
dataObject[i].top += diff; |
|
dataObject[i].bottom += diff; |
|
} |
|
} |
|
}; |
|
return dataObject; |
|
} |
|
</script> |