Skip to content

Instantly share code, notes, and snippets.

@EE2dev
Last active September 17, 2017 22:07
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save EE2dev/fc880e1cfbb80f649878f3d5b9e8ed93 to your computer and use it in GitHub Desktop.
Save EE2dev/fc880e1cfbb80f649878f3d5b9e8ed93 to your computer and use it in GitHub Desktop.
Vertical alignment of text labels
license: mit

This is an example how to compute the position of text labels for stacked bar charts. The challenge is to align the labels when the stacked areas are very small and next to each other leading to overlapping text labels. Or when a stacked area is very close to the border.

In order to simulate the algorithm, rectangles are drawn, corresponding to the bounding box of a text label. On the left, the rectangles are randomly initialized. In one or two passes the final position is computed. Reload page to see different initial seeting.

Built with blockbuilder.org

<!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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment