This animation is based on a pattern 'Sixfold Pattern 1' available in 'Islamic Geometric Design' by Eric Broug.
Last active
August 16, 2019 08:10
-
-
Save agware/0ae791128a8c7d4eb31883dc384b5977 to your computer and use it in GitHub Desktop.
Tile - SixFold 1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: gpl-3.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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