Skip to content

Instantly share code, notes, and snippets.

@tomshanley
Last active August 29, 2015 14:09
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 tomshanley/22f423df037fdd718306 to your computer and use it in GitHub Desktop.
Save tomshanley/22f423df037fdd718306 to your computer and use it in GitHub Desktop.
Cleveland and McGill Elementary Perceptual Tasks

Demonstration of some the guidelines proposed by William S. Cleveland and Robert McGill, and how colour and area are useful in displays of large amounts of datapoints.

William S. Cleveland and Robert McGill, Graphical Perception: Theory, Experimentation, and Application to the Development of Graphical Methods, 1984

Paper: http://www.cs.ubc.ca/~tmm/courses/cpsc533c-04-spr/readings/cleveland.pdf

A good brief overview is available on flowingdata: http://flowingdata.com/2010/03/20/graphical-perception-learn-the-fundamentals-first/

<!DOCTYPE html>
<html>
<head>
<title>Cleveland and McGill</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<style>
rect {
cursor: pointer ;
}
</style>
</head>
<body>
<h1>Elementary perceptual tasks</h1>
<br>
<div id='viz'></div>
<h2>Cleveland and McGill, 1984</h2>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var data = [ 10, 5, 8];
var width = 1000,
height = 400
noOfCharts = 4;
var chartWidth = width / noOfCharts,
barwidth = chartWidth/data.length - 20,
barPadding = 20;
var squareWidth = 10;
var svg = d3.select("#viz").append("svg:svg")
.attr("width", width)
.attr("height", height);
var y = d3.scale.linear()
.range([height, 0])
.domain([0, d3.max(data, function(d) { return d; })]);
/*var x = d3.scale.linear()
.range(0, chartWidth);*/
var clickBoard = svg.append("svg:rect")
.attr("width", width)
.attr("height", height)
.style("fill", "white")
.on("click", change);
var squares = svg.selectAll("squares")
.data(d3.range(250).map(function() {
return {
x: width * Math.random(),
y: height * Math.random(),
dx: Math.random() - .5,
dy: Math.random() - .5,
v: Math.random() * 10
};
}))
.enter().append("svg:rect")
.attr("width", squareWidth)
.attr("height", squareWidth)
.attr("fill-opacity", 0)
.attr("x", function(d) { d.x += d.dx; if (d.x > width) d.x -= width; else if (d.x < 0) d.x += width; return d.x; })
.attr("y", function(d) { d.y += d.dy; if (d.y > height) d.y -= height; else if (d.y < 0) d.y += height; return d.y; });
//////////////////////
shadingchartPosition = (chartWidth * 1) - chartWidth;
var color = d3.scale.ordinal()
.range(['#f7fbff','#deebf7','#c6dbef','#9ecae1','#6baed6','#4292c6','#2171b5','#08519c','#08306b'])
.domain([2,3,4,5,6,7,8,9,10]);
var shadingBars = svg.selectAll(".shadingBars")
.data(data)
.enter()
.append("svg:rect")
.attr("class", "shadingBars")
.attr("x", function (d, i) { return shadingchartPosition + (i * barwidth) ; } )
.attr("y", function(d) { return y(10); })
.style("fill", function (d) { return color(d); } )
.attr("width", barwidth - barPadding)
.attr("height", function(d) { return height - y(10); });
//////////////////////
areaChartPosition = (chartWidth * 2) - chartWidth;
var circleArea = d3.scale.sqrt().domain([2, 10]).range([5, 40])
var areaCircles = svg.selectAll(".areaCircles")
.data(data)
.enter()
.append("svg:circle")
.attr("class", "areaCircles")
.attr("cx", areaChartPosition + (chartWidth/2) )
.attr("cy", function (d, i) {return (i * height/4) + 60 ;} )
.attr("r", function (d) { return circleArea(d) ;} ) ;
//////////////////////
pathchartPosition = (chartWidth * 3) - chartWidth;
var paths = svg.selectAll(".paths")
.data(data)
.enter()
.append("svg:path")
.attr("d", function (d, i) {
var startX = pathchartPosition + 20;
var startY = (height - 20) - ((height/data.length) * i);
var endX = pathchartPosition + chartWidth - 40;
var endY = startY - 7*d;
var path = "";
return path = "M" + startX + "," + startY + "L" + endX + "," + endY;
})
.attr("stroke", "black")
.attr("stroke-width",5 )
.attr("fill", "none");;
//////////////////////
barchartPosition = (chartWidth * 4) - chartWidth;
var bars = svg.selectAll(".barchart")
.data(data)
.enter()
.append("svg:rect")
.attr("class", "barchart")
.attr("x", function (d, i) { return barchartPosition + (i * barwidth) ; } )
.attr("y", function(d) { return y(d); })
.attr("width", barwidth - barPadding)
.attr("height", function(d) { return height - y(d); });
//////////////////////
var changeID = 1;
var t = 1500;
var smallScale = d3.scale.linear()
.range([0, 30])
.domain([0, d3.max(data, function(d) { return d; })]);
function change() {
switch (true)
{
case ( changeID === 1):
paths
.transition().duration(t)
.style("stroke-opacity", 0);
areaCircles
.transition().duration(t)
.style("fill-opacity", 0);
break;
case ( changeID === 2):
shadingBars
.transition().duration(t)
.attr("width", squareWidth)
.attr("height", squareWidth)
.style("fill", "black")
.style("fill-opacity", 0.8);
bars
.transition().duration(t)
.attr("width", squareWidth)
.attr("height", squareWidth)
.style("fill-opacity", 0.8);
squares
.transition().duration(t)
.delay(t)
.style("fill-opacity", 0.8);
break;
case ( changeID === 3):
shadingBars
.transition().duration(t)
.attr("height", function (d) { return smallScale(d) ;} );
bars
.transition().duration(t)
.attr("height", function (d) { return smallScale(d) ;} );
squares
.transition().duration(t)
.attr("height", function (d) { return smallScale(d.v) ;} );
break;
case ( changeID === 4):
shadingBars
.transition().duration(t)
.attr("height", squareWidth)
.style("fill", function (d) { return color(Math.round(d)) ; } );
bars
.transition().duration(t)
.attr("height", squareWidth)
.style("fill", function (d) { return color(Math.round(d)) ; } );
squares
.transition().duration(t)
.attr("height", squareWidth)
.style("fill", function (d) { return color(Math.round(d.v)) ; } );
break;
case ( changeID === 5):
shadingBars
.transition().duration(t)
.attr("height", function (d) { return smallScale(d) ;} )
.attr("width", function (d) { return smallScale(d) ;} );
bars
.transition().duration(t)
.attr("height", function (d) { return smallScale(d) ;} )
.attr("width", function (d) { return smallScale(d) ;} );
squares
.transition().duration(t)
.attr("height", function (d) { return smallScale(d.v) ;} )
.attr("width", function (d) { return smallScale(d.v) ;} );
break;
}
changeID++;
};
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment