Skip to content

Instantly share code, notes, and snippets.

@mccalluc
Forked from mbostock/.block
Created December 27, 2017 16:15
Show Gist options
  • Save mccalluc/d79b37d286212229555d39829e3678ee to your computer and use it in GitHub Desktop.
Save mccalluc/d79b37d286212229555d39829e3678ee to your computer and use it in GitHub Desktop.
Fisheye Grid
license: gpl-3.0

Based on the d3 fisheye distortion demo, but modified to work well with circos plots:

  • Zoom foci constrained to lie in a circle.
  • Click to add a new foci.
  • Shift-click to remove foci.
(function() {
d3.fisheye = function() {
var radius = 200,
power = 2,
k0,
k1,
foci = [[0, 0]];
function fisheye(d) {
var x = d[0];
var y = d[1];
for (focus of foci) {
var dx = x - focus[0],
dy = y - focus[1],
dd = Math.sqrt(dx * dx + dy * dy);
if (dd < radius) {
var k = k0 * (1 - Math.exp(-dd * k1)) / dd * .75 + .25;
x = focus[0] + dx * k;
y = focus[1] + dy * k;
}
}
return [x, y];
}
function rescale() {
k0 = Math.exp(power);
k0 = k0 / (k0 - 1) * radius;
k1 = power / radius;
return fisheye;
}
fisheye.radius = function(_) {
if (!arguments.length) return radius;
radius = +_;
return rescale();
};
fisheye.power = function(_) {
if (!arguments.length) return power;
power = +_;
return rescale();
};
fisheye.foci = function(_) {
if (!arguments.length) return foci;
foci = _;
return fisheye;
};
return rescale();
};
})();
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.background {
fill: none;
pointer-events: all;
}
path {
fill: none;
stroke: #333;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="fisheye.js"></script>
<script>
(function(){
var width = 960,
height = 500,
xStepsBig = d3.range(10, width, 20),
yStepsBig = d3.range(10, height, 20),
xStepsSmall = d3.range(0, width + 6, 6),
yStepsSmall = d3.range(0, height + 6, 6);
var fisheye = d3.fisheye();
var line = d3.svg.line();
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(-.5,-.5)");
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
svg.selectAll(".x")
.data(xStepsBig)
.enter().append("path")
.attr("class", "x")
.datum(function(x) { return yStepsSmall.map(function(y) { return [x, y]; }); });
svg.selectAll(".y")
.data(yStepsBig)
.enter().append("path")
.attr("class", "y")
.datum(function(y) { return xStepsSmall.map(function(x) { return [x, y]; }); });
var path = svg.selectAll("path")
.attr("d", line);
function find_target(mouse) {
var center = [width / 2, height / 2];
var r = Math.sqrt(Math.pow(mouse[0]-center[0], 2) + Math.pow(mouse[1]-center[1], 2));
var scale = Math.min(width, height)/2;
return [
scale*(mouse[0]-center[0])/r + center[0],
scale*(mouse[1]-center[1])/r + center[1]
]
}
function zoomer(do_pop) {
return function() {
var target = find_target(d3.mouse(this));
var foci = fisheye.foci();
if (do_pop) {
foci.pop();
} else {
if (d3.event.shiftKey) {
foci.pop();
remove_closest(foci, target);
}
}
foci.push(target);
fisheye.foci(foci);
path.attr("d", function(d) { return line(d.map(fisheye)); });
}
}
function remove_closest(foci, target) {
// Modifies foci in-place, removing the element
// which is nearest to target.
var dists = foci.map(function(p, i) {
return {
dist: Math.sqrt(Math.pow(p[0]-target[0], 2) + Math.pow(p[1]-target[1], 2)),
index: i }
});
var min_dist = Infinity;
var i = false;
for (dist of dists) {
if (dist.dist < min_dist) {
i = dist.index;
}
}
foci.splice(i, 1);
}
svg.on("mousemove", zoomer(true));
svg.on("click", zoomer(false));
})()
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment