Skip to content

Instantly share code, notes, and snippets.

@mwdchang
Created June 19, 2015 13:47
Show Gist options
  • Save mwdchang/9b7ece8375a12756bd6f to your computer and use it in GitHub Desktop.
Save mwdchang/9b7ece8375a12756bd6f to your computer and use it in GitHub Desktop.
Demoing the lens technique. The lens is used to focus, and reveal another dimension on the dataset.
<html>
<head>
<!--
<script src="d3.js" charset="utf-8"></script>
<script src="lodash.js"></script>
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.9.3/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js" charset="utf-8"></script>
<style>
body {
margin: 1rem;
font-family: Tahoma;
}
</style>
</head>
<body>
<pre>
Lens interaction technique:
Drag the lens over data points to see hidden attributes. Hover over the
the resulting histogram to isolate a specific data point.
</pre>
<svg id="canvas" width="800" height="800"></svg>
</body>
<script>
var svg = d3.select('#canvas');
var group = svg.append('g').attr('transform', 'translate(100, 100)');
var lensData = {
x: 100,
y: 100
};
var points = [];
var selected = [];
var lensRadius = 50;
// Draw a border
group.append('rect')
.attr('x', 1)
.attr('y', 1)
.attr('width', 298)
.attr('height', 298)
.style('stroke', '#CCC')
.style('fill', '#FFF');
// Randomize data points
for (var i=0; i < 80; i++) {
points.push({
id: 'p'+i,
x: Math.random()*280 + 10,
y: Math.random()*280 + 10,
size: Math.random()*5 + 1,
value: Math.floor(Math.random()*100) + 5
});
}
// Render
group.selectAll('.points')
.data(points)
.enter()
.append('circle')
.attr('class', function(d, i) { return 'p' + i; })
.classed('points', true)
.attr('cx', function(d) { return d.x;})
.attr('cy', function(d) { return d.y;})
.attr('r', function(d) { return d.size; })
.style('stroke', '#AAA')
.style('opacity', 0.75)
.style('fill', '#CCC');
// Len action
var lensDrag = d3.behavior.drag()
.origin(function(d) {
return {x: d.x, y: d.y};
})
.on('drag', function(d) {
d3.select(this).style('stroke-width', 5);
d3.select(this).style('stroke', '#7BF');
var coord = d3.mouse(this);
d3.select(this).attr('cx', coord[0]).attr('cy', coord[1]);
d.x = coord[0];
d.y = coord[1];
selected = [];
d3.selectAll('.points')
.style('fill', function(p) {
var dist = Math.sqrt( (p.x-d.x)*(p.x-d.x) + (p.y-d.y)*(p.y-d.y));
if (dist < lensRadius) {
selected.push(p);
return '#F20';
} else {
return '#CCC';
}
});
renderLabels();
})
.on('dragend', function(d) {
d3.select(this).style('stroke-width', 2);
d3.select(this).style('stroke', '#222');
});
function _line(x1, y1, x2, y2) {
return 'M' + x1 + ',' + y1 + 'L' + x2 + ',' + y2;
}
function renderLabels() {
if (_.isEmpty(selected)) return;
group.selectAll('.label').remove();
var startY = lensData.y - 50;
var h = 15;
// Y-sort
selected = _.sortBy(selected, function(d) { return d.y; });
selected.forEach(function(d, idx) {
group.append('svg:path')
.classed('label', true)
.classed(d.id, true)
.attr('d', _line(d.x, d.y, lensData.x+100, d.y))
.style('stroke', '#AAA');
group.append('svg:path')
.classed('label', true)
.classed(d.id, true)
.attr('d', _line(lensData.x+100, d.y, lensData.x+150, startY + idx*h))
.style('stroke', '#AAA');
group.append('svg:path')
.classed('label', true)
.classed(d.id, true)
.attr('d', _line(lensData.x+150, startY + idx*h, lensData.x+165, startY + idx*h))
.style('stroke', '#AAA');
group.append('svg:rect')
.classed('label', true)
.classed(d.id, true)
.attr('x', lensData.x+165)
.attr('y', startY + idx*h - 0.5*h +1)
.attr('width', d.value)
.attr('height', h-2)
.style('fill', '#58B')
.on('mouseover', function() {
var pid = d.id;
d3.selectAll('path, rect, .points')
.filter(function(d) {
return d3.select(this).classed(pid) === false;
})
.style('opacity', 0.12);
})
.on('mouseout', function() {
d3.selectAll('.points, path, rect').style('opacity', 1.0);
});
group.append('svg:text')
.classed('label', true)
.classed(d.id, true)
.attr('x', lensData.x+165 + d.value + 5)
.attr('y', startY + idx*h + 4)
.style('font-size', 10)
.style('fill', '#888')
.text(d.value);
});
}
// Render the lens
var lens = group.append('circle')
.datum(lensData)
.attr('cx', 100)
.attr('cy', 100)
.attr('r', lensRadius)
.style('stroke', '#222')
.style('stroke-width', 2)
.style('fill-opacity', 0.2)
.style('fill', '#9BC')
.style('cursor', 'move')
.on('dblclick', function(d) {
console.log('double clicked');
})
.call(lensDrag);
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment