Skip to content

Instantly share code, notes, and snippets.

@fandu
Last active November 17, 2017 19:14
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 fandu/e2d1ecd99b1ca5c75edf2a6d81b447ab to your computer and use it in GitHub Desktop.
Save fandu/e2d1ecd99b1ca5c75edf2a6d81b447ab to your computer and use it in GitHub Desktop.
Barycentric Coordinates
license: mit

Clutter-free scatterplot in generalized Barycentric coordinates.

<!DOCTYPE html>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var radius = 200;
var width = 960;
var height = 500;
var margin = { "x": width / 2 - radius, "y": height / 2 - radius };
var speed = 1500;
var xMap = d3.scale.linear().domain([-1, 1]).range([0, radius * 2]);
var yMap = d3.scale.linear().domain([-1, 1]).range([0, radius * 2]);
var svg = d3.select("html").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + margin.x + "," + margin.y + ")");
var data = randData();
var visualItems = svg.selectAll(".item").data(data.items);
visualItems.exit().remove();
visualItems.enter()
.append("circle")
.attr("class", "item")
.attr("fill", "#4292c6")
.attr("stroke-width", 0.5)
.attr("stroke", "black")
.attr("cx", radius)
.attr("cy", radius)
.attr("r", function(d) {
return d.r * (radius / data.r0);
});
repelInit(data)
visualItems
.transition().duration(speed)
.attr("cx", function(d) {
return xMap(d.px + d.ox);
})
.attr("cy", function(d) {
return yMap(d.py + d.oy);
});
repelIterating(data);
visualItems
.transition().delay(speed * 0.6).duration(speed)
.attr("cx", function(d) {
return xMap(d.px + d.ox);
})
.attr("cy", function(d) {
return yMap(d.py + d.oy);
});
svg.append("circle")
.attr("r", radius)
.attr("fill", "none")
.attr("stroke-width", 1)
.attr("stroke", "black")
.style("stroke-dasharray", "5, 5")
.attr("cx", radius)
.attr("cy", radius);
svg.selectAll(".anchor")
.data(data.anchors)
.enter().append("circle")
.attr("class", "anchor")
.attr("fill", "#e6550d")
.attr("r", 4)
.attr("cx", function(d) {
return xMap(d.x0);
})
.attr("cy", function(d) {
return yMap(d.y0);
});
function randData() {
var data = {
"r0": 1,
"items": [],
"anchors": [{
"x0": 0,
"y0": -1,
}, {
"x0": 1,
"y0": 0,
}, {
"x0": 0,
"y0": 1,
}, {
"x0": -1,
"y0": 0,
}]
};
for (var i = 0; i < data.anchors.length; i++) {
for (var j = 0; j < 100; j++) {
var weights = [Math.random(), Math.random(), Math.random(), Math.random()];
weights[i] *= 10;
data.items.push({ "anchor_weights": weights, "r": 0.025 });
}
}
return data;
}
function repelInit(data) {
for (var i = 0; i < data.items.length; i++) {
var u = data.items[i];
u.ox = 0;
u.oy = 0;
var sum = 0;
for (var j = 0; j < u.anchor_weights.length; j++) {
sum = sum + u.anchor_weights[j];
}
u.px = 0;
u.py = 0;
for (var j = 0; j < u.anchor_weights.length; j++) {
u.px += data.anchors[j].x0 * u.anchor_weights[j] / sum;
u.py += data.anchors[j].y0 * u.anchor_weights[j] / sum;
}
}
return data;
}
function repelIterating(data, iterations) {
iterations = typeof iterations !== 'undefined' ? iterations : 100;
for (var iter = 0; iter < iterations; iter++) {
var changed = false;
for (var i = 0; i < data.items.length; i++) {
for (var j = 0; j < data.items.length; j++) {
if (i == j) continue;
var ui = data.items[i];
var uj = data.items[j];
var dx = (ui.px + ui.ox) - (uj.px + uj.ox);
var dy = (ui.py + ui.oy) - (uj.py + uj.oy);
var d2 = dx * dx + dy * dy;
var rij = ui.r + uj.r;
var rij2 = rij * rij;
if (d2 < rij2) {
var d = Math.sqrt(d2);
var f = (rij - d) / rij;
var fx = f * dx;
var fy = f * dy;
if (d == 0) {
dx = Math.random() - 0.5;
var dx2 = dx * dx;
dy = Math.random() - 0.5;
var dy2 = dy * dy;
fx = Math.sqrt(dx2 / (dx2 + dy2)) * rij;
if (dx < 0) fx = -fx;
fy = Math.sqrt(dy2 / (dx2 + dy2)) * rij;
if (dy < 0) fy = -fy;
}
ui.ox += fx;
ui.oy += fy;
uj.ox -= fx;
uj.oy -= fy;
changed = true;
}
}
}
if (!changed) {
break;
}
for (var i = 0; i < data.items.length; i++) {
var u = data.items[i];
u.ox = u.ox * 0.95;
u.oy = u.oy * 0.95;
var dx = u.px + u.ox;
var dy = u.py + u.oy;
var dx2 = dx * dx;
var dy2 = dy * dy;
var d2 = dx2 + dy2;
var r2 = data.r0 * data.r0;
if (d2 > r2) {
var d = Math.sqrt(d2);
var f = (d - data.r0) / d;
var fx = f * dx;
var fy = f * dy;
u.ox = u.ox - fx;
u.oy = u.oy - fy;
}
}
}
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment