Horizon graphs increase data density while preserving resolution. Here we show national unemployment rates as a percentage of the labor force from 2000 to 2010, courtesy of the U.S. Bureau of Labor Statistics. Negative values represent below-average employment and are either "mirrored" or "offset" to share the same space as positive values, which represent above-average employment.
While horizon graphs may require learning, they have been found to be more effective than standard line and area plots when chart sizes are small. For more, see "Sizing the Horizon: The Effects of Chart Size and Layering on the Graphical Perception of Time Series Visualizations" by Heer et al., CHI 2009.
Next: Candlestick Charts
<html>
<head>
<title>Horizon Graph</title>
<script type="text/javascript" src="../protovis-r3.2.js"></script>
<script type="text/javascript" src="../jquery-1.4.2.min.js"></script>
<script type="text/javascript" src="../jquery-ui-1.8rc3.custom.min.js"></script>
<script type="text/javascript" src="unemployment.js"></script>
<link type="text/css" rel="stylesheet" href="ex.css"/>
<link type="text/css" href="../ui-lightness/jquery-ui-1.8rc3.custom.css" rel="stylesheet"/>
<style type="text/css">
#fig {
width: 500px;
height: 115px;
}
#slider {
width: 120px;
display: inline-block;
margin-left: 10px;
margin-right: 10px;
}
#bands {
display: inline-block;
width: 10px;
text-align: right;
}
.ui-slider {
font-size: 10px;
width: 300px;
margin-top: 5px;
}
.ui-state-focus {
outline: none;
}
</style>
</head>
<body><div id="center"><div id="fig">
<div style="width:500px;">
<b>Bands:</b><span id="slider"></span><span id="bands">2</span>
<span style="float:right;"><b>Negative:</b>
<input type="radio" id="mirror" name="mode" value="mirror" checked/><label
for="mirror">mirror</label>
<input type="radio" id="offset" name="mode" value="offset"/><label
for="offset">offset</label></span>
</div>
<script type="text/javascript+protovis">
var data = pv.nest(all)
.key(function(d) d.series)
.entries()
.filter(function(d) d.key == 0)[0].values;
/* Offset so that positive is above-average and negative is below-average. */
var avg = pv.mean(data, function(d) d.rate);
data.map(function(d) d.rate -= avg);
var w = 500,
h = 84,
bands = 2,
mode = "mirror",
fx = function(d) d.date,
fy = function(d) d.rate,
m = Math.max(pv.max(data, fy), -pv.min(data, fy)),
x = pv.Scale.linear(data, fx).range(0, w).by(fx),
y = pv.Scale.linear(-m, m).range(-h, h).by(fy);
var vis = new pv.Panel()
.top(10)
.width(w)
.height(h);
vis.add(pv.Layout.Horizon)
.bands(function() bands)
.mode(function() mode)
.height(function() h / bands)
.bottom(0)
.band.add(pv.Area)
.data(data)
.left(x)
.height(y);
vis.render();
$(slider).slider({
min: 1, max: 5, value: 2, slide: function(e, ui) {
$("#bands").html(bands = ui.value);
vis.render();
}
});
$([mirror, offset]).change(function() {
mode = this.value;
vis.render();
});
</script>
</div></div></body>
</html>