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> |