Skip to content

Instantly share code, notes, and snippets.

@agware
Last active February 7, 2017 22:57
Show Gist options
  • Save agware/c140498073891507d875f1ece70628bb to your computer and use it in GitHub Desktop.
Save agware/c140498073891507d875f1ece70628bb to your computer and use it in GitHub Desktop.
Tile - Sixfold 2
license: gpl-3.0

Credit

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

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Sixfold Pattern 2</title>
<style>
line {
stroke: #000;
stroke-width: 1px;
}
circle, rect {
fill-opacity: 0;
stroke: #000;
stroke-width: 1px;
stroke-opacity: 1;
}
.construction {
stroke-dasharray: 2,5;
}
.final {
stroke-width: 3px;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="utils.js" type="text/javascript"></script>
<script>
const height = 500;
const width = 960;
const r = 200;
const duration = 1500;
let svg = d3.select('body').append('svg')
.attr('height', height)
.attr('width', width)
.attr('overflow', 'hidden');
let g = svg.append('g')
.attr('transform', 'translate(' + (width/2) + ',' + (height/2) +')');
let start = Date.now();
let count = 0;
d3.timer(animate);
function animate () {
let t = (Date.now() - start)/(duration+200);
if (t > count) {
switch(count) {
case 0:
step0();
break;
case 1:
step1();
break;
case 2:
step2();
break;
case 3:
step3();
break;
case 4:
step4();
break;
case 5:
step5();
break;
case 8:
g.selectAll('*').remove();
count = -1;
start = Date.now();
break;
}
count += 1;
}
}
function step0 () {
g.append('circle')
.attr('id', 'step1Circle')
.classed('construction', true)
.transition()
.duration(duration)
.attr('r', r);
}
function step1 () {
const lineNum = 12;
const lineOffset = 15;
for (let i = 0; i < lineNum; i++) {
g.append('line')
.attr('id', 'step1Line' + i)
.classed('construction', true)
.transition()
.duration(duration)
.attr('x2', (r+lineOffset) * Math.sin((2*Math.PI/lineNum)*i))
.attr('y2', (r+lineOffset) * Math.cos((2*Math.PI/lineNum)*i));
}
}
function step2 () {
const lineNum = 6;
for (let i = 0; i < lineNum; i++) {
let intersect = findCircleIntersection(d3.select('#step1Line' + i*2), d3.select('#step1Circle'));
let intersect2 = findCircleIntersection(d3.select('#step1Line' + ((i+1)*2) % (lineNum*2)), d3.select('#step1Circle'));
let points = {'x1': intersect.x, 'y1': intersect.y, 'x2': intersect2.x, 'y2': intersect2.y};
addLine(points, 2, i);
}
}
function step3 () {
const lineNum = 6;
for (let i = 0; i < lineNum; i++) {
let intersect = findLineIntersection(d3.select('#step1Line' + (i*2 + 1) % (lineNum*2)), d3.select('#step2Line' + i));
let intersect2 = findLineIntersection(d3.select('#step1Line' + ((i+1)*2 + 1) % (lineNum*2)), d3.select('#step2Line' + (i+1) % lineNum));
let points = {'x1': intersect.x, 'y1': intersect.y, 'x2': intersect2.x, 'y2': intersect2.y};
addLine(points, 3, i);
}
}
function step4 () {
const lineNum = 6;
for (let i = 0; i < lineNum; i++) {
let intersect = findLineIntersection(d3.select('#step1Line' + ((i+1)*2) % (lineNum*2)), d3.select('#step3Line' + i));
let intersect2 = findLineIntersection(d3.select('#step1Line' + ((i+3)*2) % (lineNum*2)), d3.select('#step3Line' + (i+2) % lineNum));
let points = extendLine(intersect, intersect2, 70);
addLine(points, 4, i);
}
}
function step5 () {
const lineNum = 6;
for (let i = 0; i < lineNum; i++) {
let intersect = findLineIntersection(d3.select('#step2Line' + i), d3.select('#step4Line' + i));
let intersect2 = findLineIntersection(d3.select('#step1Line' + ((i+1)*2 + 1) % (lineNum*2)), d3.select('#step4Line' + i));
let points = {'x1': intersect.x, 'y1': intersect.y, 'x2': intersect2.x, 'y2': intersect2.y};
addLine(points, 5, i);
d3.select('#step5Line' + i)
.classed('construction', false)
.classed('final', true);
}
for (let j = 0; j < lineNum; j++) {
let intersect = findLineIntersection(d3.select('#step2Line' + j), d3.select('#step4Line' + (j+3) % lineNum));
let intersect2 = findLineIntersection(d3.select('#step1Line' + ((j+5)*2 + 1) % (lineNum*2)), d3.select('#step4Line' + (j+3) % lineNum));
let points = {'x1': intersect.x, 'y1': intersect.y, 'x2': intersect2.x, 'y2': intersect2.y};
addLine(points, 5, j+lineNum);
d3.select('#step5Line' + (j+lineNum))
.classed('construction', false)
.classed('final', true);
}
}
</script>
</body>
</html>
function addLine(points, step, i) {
g.append('line')
.attr('x1', points.x1)
.attr('y1', points.y1)
.attr('x2', points.x1)
.attr('y2', points.y1)
.attr('id', 'step' + step + 'Line' + i)
.classed('construction', true)
.transition()
.duration(duration)
.attr('x2', points.x2)
.attr('y2', points.y2);
}
function addStaticLine(points, step, i) {
g.append('line')
.attr('x1', points.x1)
.attr('y1', points.y1)
.attr('x2', points.x2)
.attr('y2', points.y2)
.attr('id', 'step' + step + 'Line' + i)
.classed('construction', true);
}
function findCircleIntersection(line, circle) {
let circleFormula = getCircleFormula(circle);
let lineFormula = getLineFormula(line);
// 1 + m^2
let a = 1 + Math.pow(lineFormula.m, 2);
// 2mc - 2mk - 2h
let b = 2*lineFormula.m*lineFormula.c - 2*lineFormula.m*circleFormula.k - 2*circleFormula.h;
// h^2 + c^2 + k^2 - r^2 - 2ck
let c = Math.pow(circleFormula.h, 2) + Math.pow(lineFormula.c, 2) + Math.pow(circleFormula.k, 2) - Math.pow(circleFormula.r, 2) - 2*lineFormula.c*circleFormula.k;
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 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 extendLine(point1, point2, extension) {
let x1 = point1.x;
let x2 = point2.x;
let y1 = point1.y;
let y2 = point2.y;
let rise = y2-y1;
let run = x2-x1;
run = run ? run : -0.000001;
let m = Math.round(rise*1000/run)/1000;
let angle = Math.round(Math.atan(m)*1000)/1000;
let xShift = Math.round(extension*Math.cos(angle)*1000)/1000;
let yShift = Math.round(extension*Math.sin(angle)*1000)/1000;
xShift = xShift*(run > 0 ? -1 : 1);
yShift = yShift*(run > 0 ? -1 : 1);
return {'x1': x1 + xShift, 'y1': y1 + yShift, 'x2': x2 - xShift, 'y2': y2 - yShift};
}
function ripLineData (g) {
let lineNum = g.selectAll('line').size();
let ret = [];
for(let i = 0; i < lineNum; i++) {
let line = g.select('line:nth-child(' + (i+1) + ')');
let data = {'x1': line.attr('x1'), 'y1': line.attr('y1'), 'x2': line.attr('x2'), 'y2': line.attr('y2')}
ret.push(data);
}
return ret;
}
function ripMultiGLineData (container) {
let layer = container.select('g:nth-child(1)');
let linesPerLayer = layer.selectAll('line').size();
let lineNum = container.selectAll('line').size();
let lineData = [];
for (let i = 0; i < lineNum; i++) {
let layer = Math.floor(i/linesPerLayer);
let layerG = container.select('g:nth-child(' + (layer+1) + ')');
let line = layerG.select('line:nth-child(' + ((i%linesPerLayer)+1) + ')');
let x1 = line.attr('x1');
let y1 = line.attr('y1');
let x2 = line.attr('x2');
let y2 = line.attr('y2');
lineData.push({'x1': x1, 'y1': y1, 'x2': x2, 'y2': y2});
}
return lineData;
}
function generateUsingLineData (container, lineData) {
let layer = container.append('g');
layer.selectAll('line')
.data(lineData)
.enter().append('line')
.attr('x1', function(d) {return d.x1})
.attr('y1', function(d) {return d.y1})
.attr('x2', function(d) {return d.x2})
.attr('y2', function(d) {return d.y2})
.classed('final', true);
}
function transitionUsingLineData (g, lineData) {
// ToDo: Hide/generate lines based on need
let lineNum = g.selectAll('line').size();
let lineNumNeeded = lineData.length;
for (let i = 0; i < lineNumNeeded; i++) {
let line = g.select('line:nth-child(' + (i+1) + ')');
let data = lineData[i];
line
.transition()
.duration(2000)
.attr('x1', data.x1)
.attr('y1', data.y1)
.attr('x2', data.x2)
.attr('y2', data.y2);
}
}
function getLineFormula (line) {
let x1 = line.attr('x1');
let x2 = line.attr('x2');
let y1 = line.attr('y1');
let y2 = line.attr('y2');
let rise = y2-y1;
let run = x2-x1;
run = run ? run : -0.000001;
let m = Math.round(rise*1000/run)/1000;
let c = Math.round((y1 - m*x1)*1000)/1000;
return {'m': m, 'c': c};
}
function getCircleFormula(circle) {
let h = circle.attr('cx');
h = (h == null) ? 0 : h;
let k = circle.attr('cy');
k = (k == null) ? 0 : k;
let r = circle.attr('r');
return {'h': h, 'k': k, 'r': r};
}
function checkIfInBound(x1, x2, line) {
let xBound1 = line.attr('x1');
let xBound2 = line.attr('x2');
let x;
if (Math.min(xBound1, xBound2) <= x1 && Math.max(xBound1, xBound2) >= x1) {
x = x1;
} else {
x = x2;
}
return x;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment