Skip to content

Instantly share code, notes, and snippets.

@mattbrehmer
Last active August 29, 2015 14:05
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 mattbrehmer/82cf72481d4753eca1cf to your computer and use it in GitHub Desktop.
Save mattbrehmer/82cf72481d4753eca1cf to your computer and use it in GitHub Desktop.
Color Stock Chart

A color stock chart, faceted by stock name and drawn as small multiples. Each series contains 10 years of data, represented by the columns. Each column has a maximum (top tile), a median (middle tile), and a minimum (bottom tile).

Values are indexed to the median value in the first year in each series, which facilitates comparisons between stocks and years along a percentage scale. See Bertin's discussion of indexing for multiple time-series variables.

A recent study assessed the effectiveness of color stock charts, as well as a number of other time-series visualizations, for several analysis tasks, such as determining and comparing ranges. It is reported in this 2014 CHI paper by Albers et al.

//initialize dimensions
var margin = {top: 10, right: 10, bottom: 10, left: 10},
width = 900 - margin.left - margin.right,
height = 100 - margin.top - margin.bottom
padding = 10;
//specify a 9-class single-hue sequential color map (colorbrewer2.org)
var colourScale = d3.scale.quantize()
.range(d3.range(9).map(function(d) { return "q" + d + "-9"; }));
//specify linear scale for time in years
var xScale = d3.scale.linear();
//initialize a counter for the number of years
var numYears = 0;
//load data
d3.csv("indexedstocks.csv", type, function(error, data) {
//nest the data, faceting by stock name
var names = d3.nest()
.key(function(d) { return d.name; })
.entries(data);
//specify the domain of the time scale, from minimum to maximum year
xScale.domain([
d3.min(names, function(s) { return s.values[0].year; }),
d3.max(names, function(s) { return s.values[s.values.length - 1].year; })
]);
//initialize svg container object for each facet
var svg = d3.select("body")
.selectAll("svg")
.data(names)
.enter()
.append("svg")
.attr("width", width)
.attr("height", height);
//group each series into a text label and a series of panels
var series = svg.append("g");
//add a label for the stock name to the left of the chart
series.append("text")
.attr("x", padding)
.attr("y", height / 2)
.attr("class", "nameLabel")
.text(function(d) { return d.key});
//add a group of tiles containing max, median, and min values
var tileSeries = series.append("g")
.attr("transform", "translate(" + padding + ", " + padding + ")")
.attr("class", "RdSeq9")
.selectAll()
.data(function(d) {
numYears = d.values.length;
return d.values;
})
.enter();
//determine indiviudal panel dimensions based on the number of years
var panelHeight = (height - 2 * padding) / 2;
var panelWidth = (width - 6 * padding) / numYears;
//specify the range of the time scale to accomodate the width of panels
xScale.range([3 * padding, width - 3 * padding - panelWidth]);
//map the colour scale to the range of values in the data
colourScale.domain([
d3.min(data, function(d) { return d.iMinVal; }),
1,
d3.max(data, function(d) { return d.iMaxVal; })
]);
//add the tile containing the maximum value for each year
tileSeries.append("rect")
.attr("x", function(d) {
return xScale(d.year);
})
.attr("width", panelWidth - 1)
.attr("y", 0)
.attr("height", 0.5 * panelHeight - 1)
.attr("class", function(d) {
return colourScale(d.iMaxVal);
})
.append("title") // tooltip on mouseover
.text(function(d) {
return "Year: " + d.year + "; max: " + Math.round(d.iMaxVal * 100) + "%";
});
centerTile = tileSeries.append("g")
.attr("class", "centerTile");
//add the tile containing the median value for each year
centerTile.append("rect")
.attr("x", function(d) {
return xScale(d.year);
})
.attr("width", panelWidth - 1)
.attr("y", 0.5 * panelHeight)
.attr("height", panelHeight - 1)
.attr("class", function(d) {
return colourScale(d.iMedVal);
})
.append("title") // tooltip on mouseover
.text(function(d) {
return "Year: " + d.year + "; med: " + Math.round(d.iMedVal * 100) + "%";
});
//add a year label to the median tile
centerTile.append("text")
.attr("x", function(d){
return xScale(d.year) + 0.5 * panelWidth;
})
.attr("width", panelWidth)
.attr("height", panelHeight)
.attr("y", panelHeight)
.style("text-anchor", "middle")
.attr("class", "yearLabel")
.text(function(d) { return d.year})
.append("title") // tooltip on mouseover
.text(function(d) {
return "Year: " + d.year + "; min: " + Math.round(d.iMedVal * 100) + "%";
});
//add the tile and label containing the minimum value for each year
tileSeries.append("rect")
.attr("x", function(d) {
return xScale(d.year);
})
.attr("width", panelWidth - 1)
.attr("y", 1.5 * panelHeight)
.attr("height", 0.5 * panelHeight - 1)
.attr("class", function(d) {
return colourScale(d.iMinVal);
})
.append("title") // tooltip on mouseover
.text(function(d) {
return "Year: " + d.year + "; min: " + Math.round(d.iMinVal * 100) + "%";
});
});
//coerce data into a numerical format
function type(d) {
d.iMaxVal = +d.iMaxVal;
d.iMedVal = +d.iMedVal;
d.iMinVal = +d.iMinVal;
d.year = +d.year;
return d;
}
<!DOCTYPE html>
<link rel = "stylesheet" type="text/css" href="style.css" />
<script src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="facetTiles.js"></script>
name year value minVal medVal maxVal iMinVal iMedVal iMaxVal
MSFT 2000 39.81 17.65 28.39 43.22 0.6217 1.0000 1.5224
MSFT 2001 24.84 20.82 25.48 29.70 0.7334 0.8975 1.0461
MSFT 2002 25.92 17.79 21.51 25.92 0.6266 0.7577 0.9130
MSFT 2003 19.31 19.31 21.02 22.69 0.6802 0.7404 0.7992
MSFT 2004 22.69 20.46 22.73 24.60 0.7207 0.8006 0.8665
MSFT 2005 24.11 22.24 23.80 25.71 0.7834 0.8383 0.9056
MSFT 2006 26.14 21.19 25.20 28.13 0.7464 0.8876 0.9908
MSFT 2007 29.07 26.35 28.17 35.03 0.9281 0.9923 1.2339
MSFT 2008 31.13 18.91 26.22 31.13 0.6661 0.9236 1.0965
MSFT 2009 16.63 15.81 23.30 30.34 0.5569 0.8207 1.0687
AMZN 2000 64.56 15.56 39.97 68.87 0.3893 1.0000 1.7230
AMZN 2001 17.31 5.97 11.07 17.31 0.1494 0.2770 0.4331
AMZN 2002 14.19 14.10 16.09 23.35 0.3528 0.4026 0.5842
AMZN 2003 21.85 21.85 38.98 54.43 0.5467 0.9752 1.3618
AMZN 2004 50.4 34.13 43.15 54.40 0.8539 1.0796 1.3610
AMZN 2005 43.22 32.36 41.28 48.46 0.8096 1.0328 1.2124
AMZN 2006 44.82 26.89 36.99 44.82 0.6728 0.9254 1.1213
AMZN 2007 37.67 37.67 73.84 93.15 0.9425 1.8474 2.3305
AMZN 2008 77.7 42.70 73.05 81.62 1.0683 1.8276 2.0420
AMZN 2009 58.82 58.82 82.43 135.91 1.4716 2.0623 3.4003
IBM 2000 100.52 76.47 99.14 118.62 0.7713 1.0000 1.1965
IBM 2001 100.76 82.82 99.17 109.36 0.8354 1.0003 1.1031
IBM 2002 97.54 53.01 72.37 97.54 0.5347 0.7300 0.9839
IBM 2003 71.22 71.13 76.45 85.05 0.7175 0.7711 0.8579
IBM 2004 91.06 78.17 82.22 91.16 0.7885 0.8293 0.9195
IBM 2005 86.39 68.93 76.49 86.39 0.6953 0.7715 0.8714
IBM 2006 75.89 72.15 76.70 91.90 0.7278 0.7737 0.9270
IBM 2007 93.79 88.18 101.20 112.60 0.8894 1.0208 1.1358
IBM 2008 102.75 79.65 112.20 125.14 0.8034 1.1317 1.2623
IBM 2009 89.46 89.46 110.60 130.32 0.9024 1.1156 1.3145
body {
font: 10px sans-serif;
margin: 0;
}
rect {
fill: #fff;
stroke: #ccc;
}
.centerTile:hover rect,
rect:hover {
stroke: red;
stroke-width: 1px;
}
.centerTile:hover text {
fill: black;
}
.nameLabel {
font-family: sans-serif;
font-size: 9px;
text-align: left;
}
.yearLabel {
font-family: sans-serif;
font-size: 8px;
text-align: middle;
vertical-align: middle;
fill: #999;
-moz-user-select: -moz-none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: default;
}
.RdSeq9 .q0-9{fill:rgb(255,245,240)}
.RdSeq9 .q1-9{fill:rgb(254,224,210)}
.RdSeq9 .q2-9{fill:rgb(252,187,161)}
.RdSeq9 .q3-9{fill:rgb(252,146,114)}
.RdSeq9 .q4-9{fill:rgb(251,106,74)}
.RdSeq9 .q5-9{fill:rgb(239,59,44)}
.RdSeq9 .q6-9{fill:rgb(203,24,29)}
.RdSeq9 .q7-9{fill:rgb(165,15,21)}
.RdSeq9 .q8-9{fill:rgb(103,0,13)}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment