With some time series, raw values are less important than relative change. Consider investors, who may be more interested in a stock’s growth than its price. Multiple stocks may have dramatically different baseline prices, but be meaningfully compared when normalized. An index chart is an interactive line chart that shows percentage changes for a collection of time-series based on a selected index point.
In this example, we see the percentage change of selected stock prices according to the day of purchase. For January 2005, one can see the rocky rise enjoyed by those who invested in Amazon, Apple, or Google at that time. Mouseover the chart to change the reference month.
Next: Parallel Coordinates
<html>
<head>
<title>Index Chart</title>
<link type="text/css" rel="stylesheet" href="ex.css?3.2"/>
<script type="text/javascript" src="../protovis-r3.2.js"></script>
<script type="text/javascript" src="stocks.js"></script>
<style type="text/css">
#fig {
width: 860px;
height: 400px;
}
</style>
</head>
<body><div id="center"><div id="fig">
<script type="text/javascript+protovis">
var data = [],
fy = function(d) d.price, // y-axis value
fx = function(d) d.index, // x-axis value
ft = function() data[this.parent.index].ticker, // label
w = 730,
h = 360,
S = pv.max(pv.values(stocks), function(s) s.values.length),
idx = Math.floor(S/2) - 1,
x = pv.Scale.linear(0,S-1).range(0,w),
y = pv.Scale.linear(-1,5).range(0,h),
rescale = true;
/* Normalize the data according to an index point. */
var indexify = function(data, cols, idx) {
return cols.map(function(c) {
var v = data[c].values[idx];
return { ticker: c,
values: data[c].values.map(function(d,i) {
return {index:i, price:((d-v)/v)}; })
}
});
};
/* Compute new index values, rescale if needed, and render. */
var update = function() {
data = indexify(stocks, names, idx);
if (rescale) {
var min = pv.min(data.map(function(d) pv.min(d.values, fy)));
var max = pv.max(data.map(function(d) pv.max(d.values, fy)));
}
y.domain(min, max).nice();
vis.render();
}
/* The visualization panel. Stores the active index. */
var vis = new pv.Panel()
.def("i", -1)
.left(60)
.right(70)
.top(20.5)
.bottom(18)
.width(w)
.height(h);
/* Horizontal gridlines showing %-change. */
vis.add(pv.Rule)
.data(function() y.ticks(8))
.bottom(y)
.strokeStyle(function(d) d==0 ? "black" : "#cccccc")
.anchor("left").add(pv.Label)
.text(function(d) (d * 100).toFixed(0) + "%");
/* Y-axis label */
vis.add(pv.Label)
.data(["Gain / Loss Factor"])
.left(-45)
.bottom(h/2)
.font("10pt Arial")
.textAlign("center")
.textAngle(-Math.PI/2);
/* Stock lines. */
vis.add(pv.Panel)
.data(function() data)
.add(pv.Line)
.data(function(d) d.values)
.left(x.by(fx))
.bottom(y.by(fy))
.lineWidth(2)
.add(pv.Label)
.visible(function() this.index == S-1)
.textBaseline("middle")
.textMargin(6)
.text(ft);
/* Current index line. */
vis.add(pv.Rule)
.visible(function() idx >= 0 && idx != vis.i())
.left(function() x(idx))
.top(-4)
.bottom(-4)
.strokeStyle("red")
.anchor("bottom").add(pv.Label)
.text(function() stocks.Date.values[idx]);
/* An invisible bar to capture events (without flickering). */
vis.add(pv.Panel)
.events("all")
.event("mousemove", function() { idx = x.invert(vis.mouse().x) >> 0; update(); });
update();
</script>
</div></div></body>
</html>