Skip to content

Instantly share code, notes, and snippets.

@msbarry
Last active August 29, 2015 13:55
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/8759376 to your computer and use it in GitHub Desktop.
Save msbarry/8759376 to your computer and use it in GitHub Desktop.
Using motion to aid in target detection

Try to find the red circle in a sea of red squares and blue circles.

This visualization demonstrates the use of motion to extend a viewer's ability to perform exploration tasks as explained in page 114 in Interactive Data Visualization by Ward, Grinstein, and Keim.

Three options are demonstrated:

  1. Static: All circles/squares remain stationary. This requires a serial search through all elements
  2. Vertical Motion: The red items move up while blue move down. This makes the red circle immediately visible
  3. Oscillation: The red items oscillate up/down while the blue items oscillate left/right. This also makes the red circle immediately apparent.

Click the red circle when you find it and see the difference for yourself.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.controls {
text-align: center;
}
.countdown {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 48px;
font-weight: bold;
}
.red {
fill: red;
}
.blue {
fill: blue;
}
</style>
<body>
<div class="controls">
</div>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
var controls = [
{ name: 'Static', action: staticItems },
{ name: 'Vertical Motion', action: movingItems },
{ name: 'Oscillation', action: oscillatingItems }
];
var RED_SQUARES = 30;
var BLUE_CIRCLES = 30;
var CIRCLE_RADIUS = 10;
var SQUARE_SIZE = Math.sqrt(Math.PI * CIRCLE_RADIUS * CIRCLE_RADIUS)
var margin = {top: 40, right: 40, bottom: 40, left: 40},
width = 960 - margin.left - margin.right,
height = 480 - margin.top - margin.bottom;
var xScale = d3.scale.linear()
.range([0, width]);
var yScale = d3.scale.linear()
.range([0, height]);
var svg = d3.select("body").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 + ")");
var shapes = svg.append('g');
var reds = shapes.append('g').attr('class', 'red');
var blues = shapes.append('g').attr('class', 'blue');
var countdown = svg.append("g")
.attr("transform", "translate(" + width / 2 + ", " + height / 2 + ")")
.append("text")
.attr("text-anchor", "middle")
.attr("class", "countdown");
function display(text) {
reds.html("").interrupt();
blues.html("").interrupt();
countdown.text(text);
}
function countDown(start) {
display("Find the red circle");
setTimeout(display, 2000, "3");
setTimeout(display, 3000, "2");
setTimeout(display, 4000, "1");
setTimeout(display, 5000, "");
setTimeout(start, 5000);
}
// Build the controls
d3.select('.controls').selectAll('button')
.data(controls)
.enter()
.append('button')
.text(function (d) { return d.name; })
.on('click', function (d) {
countDown(d.action);
});
// Render the circles/squares randomly
function draw() {
reds.attr('transform', 'translate(0,0)');
blues.attr('transform', 'translate(0,0)');
var start = new Date().getTime();
var redSquares = d3.range(0, RED_SQUARES).map(function () {
return {
x: xScale(Math.random()),
y: yScale(Math.random())
};
});
reds.selectAll('rect')
.data(redSquares)
.enter()
.append('rect')
.attr('x', function (d) { return d.x; })
.attr('y', function (d) { return d.y; })
.attr('width', SQUARE_SIZE)
.attr('height', SQUARE_SIZE);
var blueCircles = d3.range(0, BLUE_CIRCLES).map(function () {
return {
x: xScale(Math.random()),
y: yScale(Math.random())
};
});
blues.selectAll('circle')
.data(blueCircles)
.enter()
.append('circle')
.attr('cx', function (d) { return d.x; })
.attr('cy', function (d) { return d.y; })
.attr('r', CIRCLE_RADIUS);
// The magic red circle
reds.append('circle')
.datum(function () { return { x: xScale(Math.random()), y: yScale(Math.random())}; })
.attr('cx', function (d) { return d.x; })
.attr('cy', function (d) { return d.y; })
.attr('r', CIRCLE_RADIUS)
.on('click', function () {
var end = new Date().getTime();
var took = end - start;
display("Took " + took + "ms");
});
}
// Simple case, all items stationary
function staticItems() {
draw();
}
// Red items move up, blue move down
function movingItems() {
draw();
reds.transition().duration(3000).ease("linear")
.attr('transform', 'translate(0,-40)');
blues.transition().duration(3000).ease("linear")
.attr('transform', 'translate(0,40)');
}
// Red oscillate up/down, blue oscillate left/right
function oscillatingItems() {
draw();
function stateA() {
reds.transition().duration(500)
.attr('transform', 'translate(0,20)')
.each('end', stateB);
blues.transition().duration(500).delay(500)
.attr('transform', 'translate(20,0)');
}
function stateB() {
reds.transition().duration(500)
.attr('transform', 'translate(0,0)')
.each('end', stateA);
blues.transition().duration(500).delay(500)
.attr('transform', 'translate(0,0)');
}
stateA();
}
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment