Skip to content

Instantly share code, notes, and snippets.

@agware
Last active August 16, 2019 08:10
Show Gist options
  • Save agware/0ae791128a8c7d4eb31883dc384b5977 to your computer and use it in GitHub Desktop.
Save agware/0ae791128a8c7d4eb31883dc384b5977 to your computer and use it in GitHub Desktop.
Tile - SixFold 1
license: gpl-3.0

Credit

This animation is based on a pattern 'Sixfold Pattern 1' available in 'Islamic Geometric Design' by Eric Broug.

<!DOCTYPE html>
<head>
<meta charset="utf-8">
<style>
line, circle {
stroke: #000;
stroke-width: 1px;
}
circle {
fill-opacity: 0;
stroke-opacity: 1;
}
.hidden {
stroke-opacity: 0;
}
.construction {
stroke-dasharray: 2,5;
}
.final {
stroke-width: 3px;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="utils.js"></script>
<script>
// SET UP
const margin = {top: 50, right: 50, bottom: 50, left: 50}
const height = Math.max(500, window.innerHeight - margin.top - margin.bottom);
const width = Math.max(height, window.innerWidth - margin.left - margin.right);
let svg = d3.select('#container').append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom);
let g = svg.append('g')
.attr('transform', 'translate(' + (width/2 + margin.right) + ',' + (height/2 + margin.top) + ')');
// TILE DATA
const r = {outer: height/2, padding: height/10};
let circleData = {
main: {step: 0, category: 'construction', r: r.outer - r.padding},
outer: {step: 0, category: 'hidden', r: r.outer}
};
let lineData = {}
generateTile(circleData, lineData);
// CREATE
let duration = 1000;
g.selectAll('circle')
.data(d3.entries(circleData)).enter()
.append('circle')
.attr('class', d => d.value.category)
.transition().duration(duration).delay(d => d.value.step*duration)
.attr('r', d => d.value.r);
g.selectAll('line')
.data(d3.entries(lineData)).enter()
.append('line')
.attr('class', d => d.value.category)
.attr('x1', d => d.value.x1)
.attr('y1', d => d.value.y1)
.attr('x2', d => d.value.x1)
.attr('y2', d => d.value.y1)
.transition().duration(duration).delay(d => d.value.step*duration)
.attr('x2', d => d.value.x2)
.attr('y2', d => d.value.y2);
function generateTile(circleData, lineData) {
step1(circleData, lineData);
step2(circleData, lineData);
step3(circleData, lineData);
step4(circleData, lineData);
step5(lineData);
}
function step1(circleData, lineData) {
// Step 1 - split the circle into 12 parts
let lineNum = 12;
for(let i = 0; i < lineNum; i++) {
lineData['step1line' + i] = {step: 1, category: 'construction', x1: 0, y1: 0, x2: circleData['outer'].r*Math.sin(2*i*Math.PI/lineNum),
y2: circleData['outer'].r*Math.cos(2*i*Math.PI/lineNum)};
}
}
function step2(circleData, lineData) {
// Step 2 - draw a hexagon
// (i.e. draw lines between the intersection of even numbered lines from step 1 and the circle)
let lineNum = 6;
let factor = 2;
let hexagon = [];
for(let i = 0; i < lineNum; i++) {
hexagon.push(findCircleIntersection(lineData['step1line' + i*factor], circleData['main']));
}
for(let i = 0; i < lineNum; i++) {
lineData['step2line' + i] = {step: 2, category: 'construction', x1: hexagon[i].x, y1: hexagon[i].y,
x2: hexagon[(i+1)%lineNum].x, y2: hexagon[(i+1)%lineNum].y};
}
}
function step3(circleData, lineData) {
// Step 3 - draw two triangles
// (i.e draw lines between the intersection of every fourth line from step 1 and the circle)
let lineNum = 3;
let factor = 4;
// first triangle
let triangle = [];
for(let i = 0; i < lineNum; i++) {
triangle.push(findCircleIntersection(lineData['step1line' + i*factor], circleData['main']));
}
for(let i = 0; i < lineNum; i++) {
lineData['step3line' + i] = {step: 3, category: 'construction', x1: triangle[i].x, y1: triangle[i].y,
x2: triangle[(i+1)%lineNum].x, y2: triangle[(i+1)%lineNum].y};
}
// second triangle
triangle = [];
let offset = 2;
for(let i = 0; i < lineNum; i++) {
triangle.push(findCircleIntersection(lineData['step1line' + (i*factor + offset) % (lineNum*factor)], circleData['main']));
}
for(let i = 0; i < lineNum; i++) {
lineData['step3line' + (i + lineNum)] = {step: 3, category: 'construction', x1: triangle[i].x, y1: triangle[i].y,
x2: triangle[(i+1)%lineNum].x, y2: triangle[(i+1)%lineNum].y};
}
}
function step4(circleData, lineData) {
// Step 4 - Draw lines between triangle intercepts
let lineNum = 3;
// first triangle
let triangle = [];
for(let i = 0; i < lineNum; i++) {
triangle.push(findLineIntersection(lineData['step3line' + i], lineData['step3line' + (i + lineNum)]));
}
for(let i=0; i < lineNum; i++) {
let tempLine = {x1: triangle[i].x, y1: triangle[i].y, x2: triangle[(i+1)%lineNum].x, y2: triangle[(i+1)%lineNum].y};
lineData['step4line' + i] = {step: 4, category: 'construction', ...extendLineToCircle(tempLine, circleData['outer'])};
}
// second triangle
triangle = [];
for(let i = 0; i < lineNum; i++) {
triangle.push(findLineIntersection(lineData['step3line' + i], lineData['step3line' + (((i+2) % (lineNum)) + lineNum)]));
}
for(let i=0; i < lineNum; i++) {
let tempLine = {x1: triangle[i].x, y1: triangle[i].y, x2: triangle[(i+1)%lineNum].x, y2: triangle[(i+1)%lineNum].y};
tempLine = extendLineToCircle(tempLine, circleData['outer']);
lineData['step4line' + (i + lineNum)] = {step: 4, category: 'construction', x1: tempLine.x2, y1: tempLine.y2, x2: tempLine.x1, y2: tempLine.y1};
}
}
function step5(lineData) {
// Step 5 - Highlight final
// (i.e. Cut off each line from step 4 where it intersects with a line from step 2)
let lineNum = 6;
let factor = 2;
for(let i = 0; i < lineNum; i++) {
let point1;
let point2;
if(i < lineNum/2) {
point1 = findLineIntersection(lineData['step4line' + i], lineData['step2line' + (i*factor + 1) % (lineNum)]);
point2 = findLineIntersection(lineData['step4line' + i], lineData['step2line' + ((i+1)*factor + 1) % (lineNum)]);
} else {
point1 = findLineIntersection(lineData['step4line' + i], lineData['step2line' + (i*factor) % (lineNum)]);
point2 = findLineIntersection(lineData['step4line' + i], lineData['step2line' + ((i+1)*factor) % (lineNum)]);
}
lineData['step5line' + i] = {step: 5, category: 'final', x1: point1.x, y1: point1.y, x2: point2.x, y2: point2.y};
}
}
</script>
</body>
function findLineIntersection (line1, line2) {
let formula1 = getLineFormula(line1);
let formula2 = getLineFormula(line2);
let x = (formula2.c - formula1.c)/(formula1.m - formula2.m);
let y = formula1.m*x + formula1.c;
return {'x': x, 'y': y};
}
function findCircleIntersection(line, circle) {
let lineFormula = getLineFormula(line);
// 1 + m^2
let a = 1 + Math.pow(lineFormula.m, 2);
// 2mc - 2mk - 2h -> 2mc since k & h are 0
let b = 2*lineFormula.m*lineFormula.c;
// h^2 + c^2 + k^2 - r^2 - 2ck -> c^2 - r^2 since k & h are 0
let c = Math.pow(lineFormula.c, 2) - Math.pow(circle.r, 2);
let discriminant = Math.pow(b, 2) - 4*a*c;
let x1 = (-b + Math.sqrt(discriminant))/(2*a);
let x2 = (-b - Math.sqrt(discriminant))/(2*a);
let x = checkIfInBound(x1, x2, line);
let y = lineFormula.m*x + lineFormula.c;
return {'x': x, 'y': y};
}
function extendLineToCircle(line, circle) {
let lineFormula = getLineFormula(line);
// 1 + m^2
let a = 1 + Math.pow(lineFormula.m, 2);
// 2mc - 2mk - 2h -> 2mc since k & h are 0
let b = 2*lineFormula.m*lineFormula.c;
// h^2 + c^2 + k^2 - r^2 - 2ck -> c^2 - r^2 since k & h are 0
let c = Math.pow(lineFormula.c, 2) - Math.pow(circle.r, 2);
let discriminant = Math.pow(b, 2) - 4*a*c;
let x1 = (-b + Math.sqrt(discriminant))/(2*a);
let x2 = (-b - Math.sqrt(discriminant))/(2*a);
return {x1: x1, y1: lineFormula.m*x1 + lineFormula.c, x2: x2, y2: lineFormula.m*x2 + lineFormula.c};
}
function getLineFormula (line) {
let rise = line.y2-line.y1;
let run = line.x2-line.x1;
run = run ? run : -0.000001;
let m = Math.round(rise*1000/run)/1000;
let c = Math.round((line.y1 - m*line.x1)*1000)/1000;
return {'m': m, 'c': c};
}
function checkIfInBound(x1, x2, line) {
if (Math.min(line.x1, line.x2) <= x1 && Math.max(line.x1, line.x2) >= x1) {
return x1;
}
return x2;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment