This block is a recreation based on times tables modulo (cf. https://www.youtube.com/watch?v=qhbuKbxJsk8).
- D3.js (v.4)
- blockbuilder.org
license: gpl-3.0 | |
border: no |
This block is a recreation based on times tables modulo (cf. https://www.youtube.com/watch?v=qhbuKbxJsk8).
<html lang="en"> | |
<head> | |
<meta charset="utf-8" /> | |
<title>Cardioïdes</title> | |
<meta name="description" content="Some fun at making visualization of times tables modulo, with D3js"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<style> | |
#layouter { | |
text-align: center; | |
position: relative; | |
} | |
#wip { | |
display: none; | |
position: absolute; | |
top: 220px; | |
left: 110px; | |
font-size: 40px; | |
text-align: center; | |
} | |
svg { | |
margin: 1px; | |
border-radius: 1000px; | |
box-shadow: 2px 2px 6px grey; | |
} | |
.pin-path { | |
fill: none; | |
stroke: lightgrey; | |
} | |
.pin { | |
fill: lightgrey; | |
} | |
.chord { | |
fill: none; | |
stroke-width: 1px; | |
stroke: hotpink; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="layouter"> | |
<svg></svg> | |
<div id="wip"> | |
Work in progress ... | |
</div> | |
</div> | |
<script> | |
var _PI = Math.PI, | |
_2PI = 2*Math.PI; | |
//begin: layout conf. | |
var totalWidth = 500, | |
totalHeight = 500, | |
controlsHeight = 0, | |
svgbw = 1, // canvas border width | |
svgbs = 8, // canvas box-shadow | |
radius = (totalHeight-controlsHeight-svgbs-2*svgbw)/2, | |
width = 2*radius, | |
height = 2*radius, | |
halfRadius = radius/2 | |
halfWidth = halfRadius, | |
halfHeight = halfRadius, | |
quarterRadius = radius/4; | |
quarterWidth = quarterRadius, | |
quarterHeight = quarterRadius; | |
//end: layout conf. | |
//begin: drawing conf. | |
var pinAmount = 121, | |
multiplier = 41, | |
pinRadius = radius-20, | |
drawPinPathes = true, | |
drawPins = true, | |
chordBaseDelay = 0; | |
//end: drawing conf. | |
//begin: noticeable pair between pinAmount and multiplier | |
//2 below: same figure by adding pA to m (due to modulo) | |
//pinAmount = 123, multiplier = 4; // | |
//pinAmount = 123, multiplier = 127; // pA = :4+:123 | |
//3 below: same figure <== one pattern (pA = 3m-4); higher m => higher definition | |
//pinAmount = 89, multiplier = 31; // pA = 3m-4 | |
//pinAmount = 119, multiplier = 41; // pA = 3m-4 | |
//pinAmount = 149, multiplier = 51; // pA = 3m-4 | |
//2 below: same figure <= one pattern (pA = 2m-3); higher m => higher definition | |
//pinAmount = 79, multiplier = 41; // pA = 2m-3 | |
//pinAmount = 117, multiplier = 60; // pA = 2m-3 | |
//4 below: same figure <= one pattern, last conf (see S. Plouffe) with distinct patern | |
//pinAmount = 121, multiplier = 31; // pA = 4m-3 | |
//pinAmount = 161, multiplier = 41; // pA = 4m-3 | |
//pinAmount = 345, multiplier = 87; // pA = 4m-3 | |
//pinAmount = 257, multiplier = 87; | |
//8 below: same figure with a pair of ms, pA = (m1*m2)-1 | |
//other pattern does not work (see 2 lasts, with pA = (m1*m2)-2) | |
//pinAmount = 29, multiplier = 2; // pA = (2*15)-1 | |
//pinAmount = 29, multiplier = 15; // pA = (15*2)-1 | |
//pinAmount = 50, multiplier = 3; // pA = (3*17)-1 | |
//pinAmount = 50, multiplier = 17; // pA = (17*3)-1 | |
//pinAmount = 123, multiplier = 4; // pA = (4*31)-1 | |
//pinAmount = 123, multiplier = 31; // pA = (31*4)-1 | |
//pinAmount = 121, multiplier = 41; // pA = 3*41-2; does not work | |
//pinAmount = 121, multiplier = 3; // pA = 41*3-2; does not work | |
//2 below: same as above, with pattern/2, pA = ((m1*m2)-1)/2 | |
//pinAmount = 25, multiplier = 3; // pA = ((3*17)-1)/2 | |
//pinAmount = 25, multiplier = 17; // pA = ((17*3)-1)/2 | |
//2 below: same as above, with pattern/3, pA = ((m1*m2)-1)/3 | |
//pinAmount = 41, multiplier = 4; // pA = ((4*31)-1)/3 | |
//pinAmount = 41, multiplier = 31; // pA = ((31*4)-1)/3 | |
//3 below: deriving petals | |
//pinAmount = 121, multiplier = 31; // base pattern | |
//pinAmount = 242, multiplier = 31; // 2*pA => 1 clone every 2PI/2 | |
//pinAmount = 363, multiplier = 31; // 3*pA => 2 clones every 2PI/3 | |
//pinAmount = 484, multiplier = 31; // 4*pA => 3 clones every 2PI/4 | |
//7 below: single petal at different nested levels, same pA | |
//pinAmount = 121, multiplier = 11; // pA = 12m-11 | |
//pinAmount = 121, multiplier = 13; // pA = 10m-9 | |
//pinAmount = 121, multiplier = 16; // pA = 8m-7 | |
//pinAmount = 121, multiplier = 21; // pA = 6m-5 | |
//pinAmount = 121, multiplier = 25; // pA = 5m-4 | |
//pinAmount = 121, multiplier = 31; // pA = 4m-3 | |
//pinAmount = 121, multiplier = 41; // pA = 3m-2 | |
//pinAmount = 121, multiplier = 61; // pA = 2m-1 ==> not a heart | |
//18 below: single petal at different nested levels, same m | |
//produces 2 chains, c1(x)=xm-(x+1) and c2(x)=xm-(x-1) | |
//same figure with c1(x) & c2(x+1) (eg, x=2, same figure with 2m-3 & 3m-2) | |
//pinAmount = 47, multiplier = 25; // pA = 2m-3 c1 | |
//pinAmount = 49, multiplier = 25; // pA = 2m-1 | c2 | |
//pinAmount = 71, multiplier = 25; // pA = 3m-4 c1 | | |
//pinAmount = 73, multiplier = 25; // pA = 3m-2 | c2 | |
//pinAmount = 95, multiplier = 25; // pA = 4m-5 c1 | | |
//pinAmount = 97, multiplier = 25; // pA = 4m-3 | c2 | |
//pinAmount = 119, multiplier = 25; // pA = 5m-6 c1 | | |
//pinAmount = 121, multiplier = 25; // pA = 5m-4 | c2 | |
//pinAmount = 143, multiplier = 25; // pA = 6m-7 c1 | | |
//pinAmount = 145, multiplier = 25; // pA = 6m-5 | c2 | |
//pinAmount = 167, multiplier = 25; // pA = 7m-8 c1 | | |
//pinAmount = 169, multiplier = 25; // pA = 7m-6 | c2 | |
//pinAmount = 191, multiplier = 25; // pA = 8m-9 c1 | | |
//pinAmount = 193, multiplier = 25; // pA = 8m-7 | c2 | |
//pinAmount = 215, multiplier = 25; // pA = 9m-10 c1 | | |
//pinAmount = 217, multiplier = 25; // pA = 9m-8 | c2 | |
//pinAmount = 215, multiplier = 25; // pA = 10m-11 c1 | | |
//pinAmount = 217, multiplier = 25; // pA = 10m-9 c2 | |
//2 below: same figure but changing pattern by adding (m+1) to pA | |
//comes from above 'same figure with c1(x) & c2(x+1)' | |
//c1(x)+(m+1) = c2(x+1) | |
//pinAmount = 79, multiplier = 41; // pA = 2m-3 | |
//pinAmount = 121, multiplier = 41; // pA = 3m-2 = 2m-3+(m+1) | |
//2 below: same figure with c1(x) & c2(x+1), higher m => higher definition | |
//pinAmount = 121, multiplier = 25; // pA = 5*:25-4; c2(5) | |
//pinAmount = 163, multiplier = 42; // pA = 4*:42-5; c1(4) | |
//end: noticeable pair | |
//begin: reusable d3 selection | |
var drawingArea, pathContainer, startPinContainer, endPinContainer, chordContainer; | |
//end: reusable d3 selection | |
// data | |
var pins, chords; | |
//begin: init layout | |
initLayout(); | |
//end: init layout | |
computeData(); | |
redraw(); | |
function computeData() { | |
var pinAngle = _2PI/pinAmount | |
newPins = [], | |
newChords = []; | |
var i, angle; | |
for (i=0; i<pinAmount; i++) { | |
angle = pinAngle*i; | |
newPins.push({ | |
x: Math.cos(angle)*pinRadius, | |
y: Math.sin(angle)*pinRadius | |
}); | |
newChords.push({ | |
startIndex: i, | |
endIndex: (i*multiplier)%pinAmount | |
}) | |
} | |
pins = newPins; | |
chords = newChords; | |
} | |
function redraw() { | |
if (drawPinPathes) { | |
redrawPinPathes(); | |
} | |
if (drawPins) { | |
redrawPins(); | |
} | |
redrawChords(); | |
} | |
function redrawPinPathes() { | |
var pathLiner = d3.line().x(function(d){ return d.x; }).y(function(d){ return d.y; }); | |
var pathes = pathContainer.selectAll("path") | |
.data([pins]); | |
pathes.enter() | |
.append("path") | |
.classed("pin-path", true) | |
.attr("d", function(d){ return pathLiner(d)+"z"; }) | |
.merge(pathes) | |
.transition() | |
.duration(3000) | |
.attr("d", function(d){ return pathLiner(d)+"z"; }); | |
} | |
function redrawPins() { | |
var drawnPins = startPinContainer.selectAll(".pin").data(pins); | |
drawnPins.enter() | |
.append("circle") | |
.classed("pin", true) | |
.attr("r", 1.5) | |
.attr("cx", function(d){ return d.x; }) | |
.attr("cy", function(d){ return d.y; }) | |
.merge(drawnPins) | |
.transition() | |
.duration(3000) | |
.attr("cx", function(d){ return d.x; }) | |
.attr("cy", function(d){ return d.y; }); | |
} | |
function redrawChords() { | |
var drawnChords = chordContainer.selectAll(".chord").data(chords); | |
drawnChords.enter() | |
.append("line") | |
.classed("chord", true) | |
.attr("x1", function (d){ return pins[d.startIndex].x; }) | |
.attr("y1", function (d){ return pins[d.startIndex].y; }) | |
.attr("x2", function (d){ return pins[d.endIndex].x; }) | |
.attr("y2", function (d){ return pins[d.endIndex].y; }) | |
.style("stroke-opacity", 0) | |
.merge(drawnChords) | |
.transition() | |
.duration(50) | |
.delay(function(d,i){ return i*chordBaseDelay; }) | |
.attr("x1", function (d){ return pins[d.startIndex].x; }) | |
.attr("y1", function (d){ return pins[d.startIndex].y; }) | |
.attr("x2", function (d){ return pins[d.endIndex].x; }) | |
.attr("y2", function (d){ return pins[d.endIndex].y; }) | |
.style("stroke-opacity", 1); | |
} | |
function initLayout() { | |
d3.select("#layouter").style("width", totalWidth+"px").style("height", totalHeight+"px"); | |
drawingArea = d3.select("svg").attr("width", width).attr("height", height) | |
.append("g") | |
.classed("drawing-area", true) | |
. attr("transform", "translate("+[radius, radius]+")rotate(-90)"); | |
pathContainer = drawingArea.append("g").attr("id", "path-container"); | |
startPinContainer = drawingArea.append("g").attr("id", "pin-container"); | |
endPinContainer = drawingArea.append("g").attr("id", "pin-container"); | |
chordContainer = drawingArea.append("g").attr("id", "chord-container"); | |
} | |
</script> | |
</body> | |
</html> |