Skip to content

Instantly share code, notes, and snippets.

@cool-Blue
Forked from mbostock/.block
Last active July 30, 2023 20:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save cool-Blue/418d3f6d91915602e4fd to your computer and use it in GitHub Desktop.
Save cool-Blue/418d3f6d91915602e4fd to your computer and use it in GitHub Desktop.
Variable width bar chart with matching, data driven, variable width wordwrap

This example, using satirical data from The Onion, demonstrates how to wrap long axis labels to fit on multiple lines.

  1. Modified wrap to acept text wrap width as a function of data and index.
  2. Made bar widths proportional to values.

Note: The second point is not recommended practice but serves to ilustrate the variable wrap feature!

name value
Family in feud with Zuckerbergs .17
Committed 671 birthdays to memory .19
Ex is doing too well .10
High school friends all dead now .15
Discovered how to “like” things mentally .27
Not enough politics .12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Wrapping Long Labels</title>
<style>
.bar {
fill: steelblue;
}
.bar:hover {
fill: brown;
}
.title {
font: bold 14px "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
var margin = {top: 80, right: 180, bottom: 80, left: 180},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var padding = 0.1, outerPadding = 0.3,
x = d3.scale.ordinal()
.rangeRoundBands([0, width], padding, outerPadding);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(8, "%");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.tsv("data.tsv", type, function (error, data) {
var a = alpha(data, v), //scale factor between value and bar width
mid = Midi(data, v, a), //mid-point displacement of bar i
w = Wi(data, v, a); //width of bar i
x.range(data.map(mid)); //force irregular intervals based on value
x.domain(data.map(function (d) {
return d.name;
}));
y.domain([0, d3.max(data, v)]);
svg.append("text")
.attr("class", "title")
.attr("x", x(data[0].name))
.attr("y", -26)
.attr("shape-rendering", "crispEdges")
.text("Why Are We Leaving Facebook?");
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll(".tick text")
.call(wrap, function (d, i) {
return w(i);
});
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function (d, i) {
return x(d.name) - a * d.value/2; //center the bar on the tick
})
//using x.range sets x.rangeBands to zero, compute width as a function of value
.attr("width", x.rangeBand() || function (d) {
return a * d.value; //`a` already accounts for both types of padding
})
.attr("y", function (d) {
return y(d.value);
})
.attr("height", function (d) {
return height - y(d.value);
});
});
function wrap(text, width) {
text.each(function (d, i) {
var text = d3.select(this),
words = text.html(d).text().split(/\s+/).reverse(), //reset html to clear any tspans added before
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em"),
//[edit]add dWidth
dWidth = typeof width === "function" ? width(d, i) : width;
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > dWidth/*[edit]*/) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
function type(d) {
d.value = +d.value;
return d;
}
function v(d) {
return +d.value;
}
function alpha(values, value) {
var n = values.length, total = d3.sum(values, value);
return (width - (n - 1) * padding * width / n - 2 * outerPadding * width / n) / total
}
function Wi(values, value, alpha) {
return function (i) {
return value(values[i]) * alpha
}
}
function Midi(values, value, alpha) {
var w = Wi(values, value, alpha), n = values.length;
return function (_, i) {
var op = outerPadding * width / n, p = padding * width / n;
return op + d3.sum(values.slice(0, i), value) * alpha + i * p + w(i) / 2;
}
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment