|
d3.triangleBin = function() { |
|
var size = [400, 300], |
|
sideLength = 30, |
|
x = function(d) { return d[0]; }, |
|
y = function(d) { return d[1]; }; |
|
|
|
function triangleBin(data) { |
|
var points = data.map(function(d) { return [x(d), y(d)]; }); |
|
|
|
var triangles = createTriangleGrid(size, sideLength) |
|
.map(function(d) { d.points = []; return d; }); |
|
|
|
// TODO: Optimize binning. This brute force search is slow. |
|
|
|
// Bin points in triangles |
|
points.forEach(function(point, i) { |
|
for (var i = 0; i < triangles.length; i++) { |
|
if (pointInTriangle(triangles[i], point)) triangles[i].points.push(point); |
|
} |
|
}); |
|
|
|
return triangles |
|
.map(function(d) { |
|
var center = d.center, |
|
orientation = d.orientation; |
|
d = d.points; |
|
d.x = center[0]; |
|
d.y = center[1]; |
|
d.orientation = orientation; |
|
return d.length > 0 ? d : null; |
|
}) |
|
.filter(function(d) { return d !== null; }); |
|
} |
|
|
|
triangleBin.size = function(_) { |
|
if (!arguments.length) return size; |
|
size = _; |
|
return triangleBin; |
|
}; |
|
|
|
triangleBin.sideLength = function(_) { |
|
if (!arguments.length) return sideLength; |
|
sideLength = _; |
|
return triangleBin; |
|
}; |
|
|
|
triangleBin.x = function(_) { |
|
if (!arguments.length) return x; |
|
x = _; |
|
return triangleBin; |
|
}; |
|
|
|
triangleBin.y = function(_) { |
|
if (!arguments.length) return y; |
|
y = _; |
|
return triangleBin; |
|
}; |
|
|
|
triangleBin.triangle = function(orientation, length) { |
|
length = length || sideLength; |
|
var points = createTriangle([0, 0], length, orientation); |
|
return "M" + points.join("L") + "Z"; |
|
}; |
|
|
|
return triangleBin; |
|
|
|
// Creates an array of triangles that covers the area of the canvas |
|
function createTriangleGrid(size, sideLength) { |
|
|
|
var triangles = [], |
|
rc = sideLength / Math.sqrt(3), // maximum radius of circumscribing circle |
|
ri = rc / 2; // maximum radius of inscribing circle |
|
|
|
|
|
// upward pointing triangle |
|
for (var x = sideLength/2; x <= size[0] + sideLength; x += sideLength) { |
|
for (var y = rc - ri; y <= size[1] + sideLength; y += rc + ri) { |
|
var triangle = createTriangle([x, y], sideLength, "up"); |
|
triangles.push(triangle); |
|
} |
|
} |
|
|
|
// downward pointing triangles |
|
for (var x = 0; x <= size[0] + sideLength; x += sideLength) { |
|
for (var y = 0; y <= size[1] + sideLength; y += rc + ri) { |
|
var triangle = createTriangle([x, y], sideLength, "down"); |
|
triangles.push(triangle); |
|
} |
|
} |
|
|
|
return triangles; |
|
} |
|
|
|
// Create equilateral triangle (with counterclockwise vertices) |
|
function createTriangle(center, sideLength, orientation) { |
|
|
|
var cx = center[0], |
|
cy = center[1], |
|
rc = sideLength / Math.sqrt(3), // maximum radius of circumscribing circle |
|
ri = rc / 2; // maximum radius of inscribing circle |
|
|
|
// Add vertices |
|
if (orientation === "up") { |
|
var triangle = [ |
|
[cx, cy - rc], |
|
[cx - sideLength/2, cy + ri], |
|
[cx + sideLength/2, cy + ri] |
|
]; |
|
} |
|
else if (orientation === "down") { |
|
var triangle = [ |
|
[cx, cy + rc], |
|
[cx + sideLength/2, cy - ri], |
|
[cx - sideLength/2, cy - ri] |
|
]; |
|
} |
|
|
|
triangle.center = center; |
|
triangle.orientation = orientation; |
|
|
|
return triangle; |
|
} |
|
|
|
// identify which side of a line and given point is |
|
function sideOfLine(line, point) { |
|
// TODO: clean up naming |
|
|
|
var x1 = line[0][0], |
|
y1 = line[0][1], |
|
x2 = line[1][0], |
|
y2 = line[1][1], |
|
x = point[0], |
|
y = point[1]; |
|
|
|
return (y2 - y1) * (x - x1) + (-x2 + x1) * (y - y1); |
|
} |
|
|
|
// identify if a point is in a triangle |
|
function pointInTriangle(triangle, point) { |
|
// triangle points must be counterclockwise |
|
|
|
// TODO: clean up naming |
|
var x1 = triangle[0][0], |
|
y1 = triangle[0][1], |
|
x2 = triangle[1][0], |
|
y2 = triangle[1][1], |
|
x3 = triangle[2][0], |
|
y3 = triangle[2][1], |
|
x = point[0], |
|
y = point[1]; |
|
|
|
var checkSide1 = sideOfLine([[x1, y1], [x2, y2]], [x, y]) >= 0, |
|
checkSide2 = sideOfLine([[x2, y2], [x3, y3]], [x, y]) >= 0, |
|
checkSide3 = sideOfLine([[x3, y3], [x1, y1]], [x, y]) >= 0; |
|
return checkSide1 && checkSide2 && checkSide3; |
|
} |
|
} |