Skip to content

Instantly share code, notes, and snippets.

@dbeach24
Last active September 22, 2017 05:15
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 dbeach24/bacaa17aa7e539dc3ffab8feed59daef to your computer and use it in GitHub Desktop.
Save dbeach24/bacaa17aa7e539dc3ffab8feed59daef to your computer and use it in GitHub Desktop.
Popout
license: mit

Popout

  • David J. C. Beach
  • CS 573 - Data Visualization
  • Prof. Curran Kelleher

This is a recreation of Figure 5.12 of "Visualization Analysis and Design" by Tamara Munzner, ch 5, p 110.

Many channels support visual popout, including (a) tilt, (b) size, (c) shape, (d) proximity, and (e) shadow direction. (f) However, parallel line pairs do not pop out from a sea of slightly tilted distractor object pairs and can only be detected through serial search. After http://www.csc.ncsu.edu/faculty/healey/PP by Christopher G. Healey.

Built with blockbuilder.org

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<script src="https://d3js.org/d3.v4.min.js"></script>
<title>Figure 5.12 - Popout</title>
<style>
.frame {
stroke: #A0A0A0;
stroke-width: 3;
fill: none;
}
.label {
text-anchor: middle;
}
.rectmark {
fill: #0070C0;
}
.circlemark {
fill: #0070C0;
}
</style>
</head>
<body>
<svg width="960" height="500">
<!--
Modified from example of SVG drop shadow at
https://www.w3schools.com/graphics/svg_feoffset.asp
-->
<defs>
<filter id="dropLR" x="0" y="0" width="200%" height="200%">
<feOffset result="offOut" in="SourceAlpha" dx="8" dy="8"/>
<feGaussianBlur result="blurOut" in="offOut" stdDeviation="4"/>
<feBlend in="SourceGraphic" in2="blurOut" mode="normal"/>
</filter>
<filter id="dropUL" x="-1" y="-1" width="200%" height="200%">
<feOffset result="offOut2" in="SourceAlpha" dx="-8" dy="-8"/>
<feGaussianBlur result="blurOut2" in="offOut2" stdDeviation="4"/>
<feBlend in="SourceGraphic" in2="blurOut2" mode="normal"/>
</filter>
</defs>
</svg>
<script>
const svg = d3.select('svg');
const margin = { left: 30, right: 30, top:10, bottom: 30 };
const width = svg.attr('width');
const height = svg.attr('height');
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;
const g = svg.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
const fPadX = 35;
const fPadY = 30;
const frameWidth = (innerWidth - 2*fPadX) / 3;
const frameHeight = (innerHeight - fPadY) / 2;
const frameInnerPad = 25;
const frameInnerWidth = frameWidth - 2*frameInnerPad;
const frameInnerHeight = frameHeight - 2*frameInnerPad;
function makeFrame(i, j, label) {
const left = (i-1) * (frameWidth + fPadX);
const top = (j-1) * (frameHeight + fPadY);
const frame = g.append('g')
.attr('transform', `translate(${left},${top})`);
frame.append('rect')
.attr('x', 0)
.attr('y', 0)
.attr('width', frameWidth)
.attr('height', frameHeight)
.attr('class', 'frame');
frame.append('text')
.attr('x', frameWidth/2)
.attr('y', frameHeight + 20)
.attr('class', 'label')
.text(label);
const innerFrame = frame.append('g')
.attr('transform', `translate(${frameInnerPad},${frameInnerPad})`);
return innerFrame;
}
function addRectMark(selection, cx, cy) {
const width = 40;
const height = 8;
const x = cx - width/2;
const y = cy - height/2;
return selection.append('rect')
.attr('x', x)
.attr('y', y)
.attr('width', width)
.attr('height', height)
.attr('class', 'rectmark');
}
function addCircleMark(selection, cx, cy) {
const r = 7;
return selection.append('circle')
.attr('cx', cx)
.attr('cy', cy)
.attr('r', r)
.attr('class', 'circlemark');
}
const aF = makeFrame(1, 1, "(a)");
const bF = makeFrame(2, 1, "(b)");
const cF = makeFrame(3, 1, "(c)");
const dF = makeFrame(1, 2, "(d)");
const eF = makeFrame(2, 2, "(e)");
const fF = makeFrame(3, 2, "(f)");
// (a) frame
const I = 4;
const J = 6;
const x0 = 25;
const y0 = 0;
const dx = ((frameInnerWidth - 200) / 3) + 50;
const dy = frameInnerHeight / (J-1);
for(var i = 0; i < I; i++) {
for(var j = 0; j < J; j++) {
if(i == 2 && j == 2) {
addRectMark(aF, x0 + i*dx + 15, y0 + j*dy - 15).attr('width', 8).attr('height', 40);
} else {
addRectMark(aF, x0 + i*dx, y0 + j*dy);
}
}
}
// (b) frame
for(var i = 0; i < I; i++) {
for(var j = 0; j < J; j++) {
if(i == 2 && j == 2) {
addRectMark(bF, x0 + i*dx, y0 + j*dy - 5).attr('height', 20);
} else {
addRectMark(bF, x0 + i*dx, y0 + j*dy);
}
}
}
// (c) frame
const xydots = [
{x:5, y:0},
{x:10.5, y:0.5},
{x:1.5, y:5},
{x:2.5, y:6},
{x:7, y:5.5},
{x:8, y:4.8},
{x:8, y:6},
{x:6, y:9.5}, // special mark, index=7
{x:3, y:12},
{x:10, y:12}
];
for(var i in xydots) {
var item = xydots[i];
var cx = item.x/12 * frameInnerWidth;
var cy = item.y/12 * frameInnerHeight;
if(i == 7) {
cF.append("rect").attr("width", 8).attr("height", 24).attr("x", cx-4).attr("y", cy-12).attr("class", "rectmark");
cF.append("rect").attr("width", 24).attr("height", 8).attr("x", cx-12).attr("y", cy-4).attr("class", "rectmark");
} else {
addCircleMark(cF, cx, cy);
}
}
// (d) frame
const xybars = [
{x: 5, y:0},
{x: 9, y:0.5},
{x: 2, y:1.0},
{x: 8, y:1.25},
{x: 10.25, y:1.5},
{x: 8.75, y:2.25},
{x: 7.5, y:3},
{x: 10.5, y:3},
{x: 0.5, y:2.25},
{x: 4.5, y:2.0},
{x: 1.5, y:3.5},
{x: 1.0, y:5},
{x: 1.0, y:7},
{x: 0.9, y:9},
{x: 3.75, y:5.75},
{x: 3.75, y:7.5},
{x: 3.75, y:10},
{x: 6.5, y:5.25},
{x: 6.75, y:7.5},
{x: 6.5, y:9.25},
{x: 9.5, y:6},
{x: 8, y:6.75},
{x: 9.5, y:8}
];
for(var i in xybars) {
var item = xybars[i];
addRectMark(dF, (item.x/11) * frameInnerWidth, (item.y/10) * frameInnerHeight);
}
// (e) frame
for(var i in xydots) {
var item = xydots[i];
var cx = item.x/12 * frameInnerWidth;
var cy = item.y/12 * frameInnerHeight;
if(i == 7) {
addCircleMark(eF, cx, cy).attr('filter', 'url(#dropUL)');
} else {
addCircleMark(eF, cx, cy).attr('filter', 'url(#dropLR)');
}
}
// (f) frame
const anglebars = [
{x: 5, y: 3, angle: 15, rot: -10},
{x: 8, y: 4, angle: 35, rot: -19},
{x: 1, y: 8, angle: -30, rot: 19},
{x: 5.5, y: 7, angle: -12, rot: 6},
{x: 11, y: 8, angle: -12, rot: 10},
{x: 8.5, y: 9.5, angle: 0, rot: -15},
{x: 6, y: 11, angle: -10, rot: 8}
];
for(var i in anglebars) {
var item = anglebars[i];
var cx = (item.x / 12) * frameInnerWidth;
var cy = (item.y / 12) * frameInnerHeight;
var offset = fF.append('g').attr('transform', `translate(${cx}, ${cy})`);
var rotate = offset.append('g').attr('transform', `rotate(${item.rot})`);
var off1 = rotate.append('g').attr('transform', 'translate(0, -10)');
var off1r = off1.append('g').attr('transform', `rotate(${-item.angle/2})`);
addRectMark(off1r, 0, 0);
var off2 = rotate.append('g').attr('transform', 'translate(0, 10)');
var off2r = off2.append('g').attr('transform', `rotate(${item.angle/2})`);
addRectMark(off2r, 0, 0);
}
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment