Skip to content

Instantly share code, notes, and snippets.

@msbarry
Last active August 29, 2015 13:58
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 msbarry/10430216 to your computer and use it in GitHub Desktop.
Save msbarry/10430216 to your computer and use it in GitHub Desktop.
Absolute vs. Relative Comparisons

This visualization demonstrates different techniques for facilitating relative vs. absolute comparisons as described in section 12.2.5 of Interactive Data Visualization by Ward, Grinstein, and Keim. For each variation, this graphic shows 10 bars representing values of 10 through 19. The top variation is the worst, requiring the user to make absolute comparisons between the bars - which humans are very bad at. The techniques below that demonstrate different ways to facilitate relative comparisons instead:

  1. Bounding Boxes: Bounding boxes give a consistent frame of reference to facilitate relative comparisons
  2. Grid/Tick Marks: Grid or tick marks give intermediate points of reference
  3. Residuals: By subtracting the average value from each bar, only the relative differences remain
  4. Reorientation: By simply re-orienting the bars to be stacked vertically instead of horizontally, relative comparisons become much easier
<!DOCTYPE html>
<title>Absolute -> Relative Judgment</title>
<style type="text/css">
body {
font-size: 12px;
font-family: Arial;
width: 940px;
margin: 0 auto;
text-align: center;
}
.header {
font-size: 14px;
font-weight: bold;
}
.description {
font-size: 10px;
fill: #888;
display: none;
}
.data {
shape-rendering: crispEdges;
stroke: none;
fill: gray;
}
.bounds,
.ticks {
shape-rendering: crispEdges;
stroke: black;
fill: none;
}
.grids {
shape-rendering: crispEdges;
stroke: gray;
}
</style>
<body>
<div id="chart">
</div>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script>
var margin = {top: 10, right: 10, bottom: 100, left: 60},
width = 940 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
rowHeight = 40;
var svg = d3.select("#chart").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 + ")");
// generate random array of 10 numbers from 10 to 20
function genData() {
return d3.shuffle(d3.range(10, 20));
}
var sectionTitles = [
'Absolute Judgment',
'Bounding Boxes',
'Grid/Tick Marks',
'Residuals',
'Reorientation',
];
var sectionScale = d3.scale.ordinal()
.domain(sectionTitles)
.rangeRoundBands([0, height], 0.1, 0.1);
var xScale = d3.scale.ordinal()
.domain(d3.range(0, 10))
.rangeRoundBands([0, width], 0.2, 0.1);
var yScale = d3.scale.ordinal()
.domain(d3.range(0, 10))
.rangeBands([10, 80], 0.1, 0.1);
// create the sections and headers
var sections = svg.selectAll('.section')
.data(sectionTitles)
.enter()
.append("g")
.attr('class', function (d) { return 'section ' + d.toLowerCase().replace('/', ' '); })
.attr('transform', function (d) { return 'translate(0,' + sectionScale(d) + ')'; });
sections
.append('text')
.attr('class', 'header')
.text(function (d) { return d; });
// draw the initial boxes
var boxContainers = sections.selectAll('.box')
.data(genData)
.enter()
.append('g')
.attr('class', 'box')
.attr('transform', function (d, i) { return 'translate(' + xScale(i) + ',20)'; });
boxContainers
.append('rect')
.attr('class', 'data')
.attr('width', function (d) { return d * xScale.rangeBand() / 20; })
.attr('height', 5);
// Add a bounding box to that section
svg.selectAll('.section.bounding .box')
.append('rect')
.attr('class', 'bounds')
.attr('x', -2)
.attr('y', -2)
.attr('width', xScale.rangeBand())
.attr('height', 8);
// Add tick marks to that section
svg.selectAll('.section.tick.marks .box').selectAll('.ticks')
.data([5, 10, 15])
.enter()
.append('line')
.attr('class', 'grids')
.attr('x1', function (d) { return xScale.rangeBand() * d / 20; })
.attr('y1', -2)
.attr('x2', function (d) { return xScale.rangeBand() * d / 20; })
.attr('y2', 7);
svg.selectAll('.section.tick.marks .box')
.append('line')
.attr('class', 'ticks')
.attr('x1', 0)
.attr('y1', 6)
.attr('x2', xScale.rangeBand())
.attr('y2', 6);
var average = d3.sum(genData()) / 10;
// Subtract average from residuals section
svg.selectAll('.section.residuals .data')
.attr('width', function (d) { return Math.abs(d - average) * xScale.rangeBand() / 5; })
svg.selectAll('.section.residuals .box')
.append('line')
.attr('class', 'ticks')
.attr('x1', function (d) { return d > average ? 0 : (d - average) * xScale.rangeBand() / -5; })
.attr('y1', -2)
.attr('x2', function (d) { return d > average ? 0 : (d - average) * xScale.rangeBand() / -5; })
.attr('y2', 7);
// Re-orient the orientation section
svg.selectAll('.section.reorientation .box')
.attr('transform', function (d, i) { return 'translate(' + xScale(0) + ',' + yScale(i) + ')'; })
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment