D3 interactive plot based in the Numberphile video 'The Golden Ratio(why it is so irrational)'
The result is on bl.ocks. Enjoy it!
3 Aug 2018, Pablo Marcos
D3 interactive plot based in the Numberphile video 'The Golden Ratio(why it is so irrational)'
The result is on bl.ocks. Enjoy it!
3 Aug 2018, Pablo Marcos
| <!DOCTYPE html> | |
| <!-- D3 interactive plot based in the Numberphile video 'The Golden Ratio | |
| (why it is so irrational)' | |
| Uses parts writed by hey-nick https://www.codeseek.co/hey-nick/d3-polar-scatter-NxqpVr | |
| (the d3 polar plot in phi.js), from w3schools | |
| https://www.w3schools.com/howto/howto_js_rangeslider.asp (the sliders) | |
| and from Jérome Freyre (the color) http://bl.ocks.org/jfreyre/b1882159636cc9e1283a | |
| Enjoy it! | |
| 3 Aug 2018, Pablo Marcos https://github.com/pablomm --> | |
| <html lang="en" > | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta author='Pablo Marcos'> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>The golden ratio</title> | |
| <link rel="stylesheet" href="style.css"> | |
| <script src='https://d3js.org/d3.v3.min.js'></script> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <div class="phi"></div> | |
| <div class="settings"> | |
| <div class="slidecontainer"> | |
| <label for="ratio">Ratio:</label> | |
| <input class='field' type='number' min="0" step='0.01' value='0.6180339887498949' id="ratiovalue" onchange="updateLabel('ratio', (this.value%1)*100000);"> | |
| <input type="range" min="0" max="100000" value="61803" class="slider" id="ratio" onchange="updateLabel('ratiovalue', this.value/100000);"> | |
| </div> | |
| <div class="slidecontainer"> | |
| <label for="samples">Points:</label> | |
| <input class='field' type='number' min="1" step='20' id="samplesvalue" value="600" onchange="updateLabel('samples', this.value);"> | |
| <input type="range" min="1" max="3000" value="600" class="slider" id="samples" onchange="updateLabel('samplesvalue', this.value);"> | |
| </div> | |
| <div class="slidecontainer"> | |
| <label for="size">Size:</label> | |
| <input class='field' type='number' min="1" max="10" value='5' id="sizevalue" onchange="updateLabel('size', this.value);"> | |
| <input type="range" min="1" max="10" value="5" class="slider" id="size" onchange="updateLabel('sizevalue', this.value);"> | |
| </div> | |
| <div class="slidecontainer"> | |
| <input class='bottom-button' type="button" id='animation' value="animation" onclick="startAnimation();"> | |
| <input class='bottom-button' type="button" id='reset' value="reset" onclick="reset();"> | |
| </div> | |
| </div> | |
| <script src="phi.js"></script> | |
| <script> | |
| // Initial settings of the plot | |
| var ratio = (Math.sqrt(5)-1)/2; // Golden ratio conjugate | |
| var N = 600; | |
| var size = 5; | |
| var playing = false; | |
| var fps = 25; | |
| var initial = true; | |
| function updateLabel(id,val) { | |
| document.getElementById(id).value=val; | |
| } | |
| document.getElementById("ratio").oninput = function() { | |
| var ratioaux = parseInt(this.value)/100000; | |
| if(!isNaN(ratioaux)) { | |
| ratio = ratioaux; | |
| plot(ratio, N, size); | |
| } | |
| }; | |
| document.getElementById("ratiovalue").oninput =function() { | |
| var ratioaux = parseFloat(this.value); | |
| if(!isNaN(ratioaux)) { | |
| ratio = ratioaux % 1; | |
| plot(ratio, N, size); | |
| } | |
| }; | |
| document.getElementById("samples").oninput = function() { | |
| var Naux = parseInt(this.value); | |
| if(!isNaN(Naux)) { | |
| N=Naux; | |
| plot(ratio, N, size); | |
| } | |
| }; | |
| document.getElementById("samplesvalue").oninput = | |
| document.getElementById("samples").oninput; | |
| document.getElementById("size").oninput = function() { | |
| var sizeaux = parseInt(this.value); | |
| if(!isNaN(sizeaux)) { | |
| size = sizeaux; | |
| plot(ratio, N, size); | |
| } | |
| }; | |
| document.getElementById("sizevalue").oninput = | |
| document.getElementById("size").oninput; | |
| // First plot | |
| function reset() { | |
| playing=false; | |
| initial = true; | |
| ratio = (Math.sqrt(5)-1)/2; | |
| N = 600; | |
| size = 5; | |
| document.getElementById("animation").value = "animation"; | |
| updateSliders(); | |
| plot(ratio, N, size); | |
| resetZoom(); | |
| } | |
| function updateSliders() { | |
| document.getElementById("sizevalue").value = size; | |
| document.getElementById("size").value = size; | |
| document.getElementById("samples").value = N; | |
| document.getElementById("samplesvalue").value = N; | |
| document.getElementById("ratio").value = ratio*100000; | |
| document.getElementById("ratiovalue").value = ratio; | |
| } | |
| function startAnimation() { | |
| if(playing) { | |
| playing = false; | |
| document.getElementById("animation").value = "continue"; | |
| } else { | |
| playing = true; | |
| if(initial) { | |
| ratio = 0; | |
| N = 500; | |
| size = 5; | |
| initial = false; | |
| } | |
| document.getElementById("animation").value = "stop"; | |
| updateSliders(); | |
| plot(ratio, N, size); | |
| animationFrame(); | |
| } | |
| } | |
| (function() { | |
| var requestAnimationFrame = window.requestAnimationFrame || | |
| window.mozRequestAnimationFrame || | |
| window.webkitRequestAnimationFrame || | |
| window.msRequestAnimationFrame; | |
| window.requestAnimationFrame = requestAnimationFrame; | |
| })(); | |
| function animationFrame() { | |
| if (ratio < 1 && playing) { | |
| ratio += 0.00008; | |
| updateSliders(); | |
| plot(ratio, N, size); | |
| setTimeout(function(){ //throttle requestAnimationFrame to 20fps | |
| requestAnimationFrame(animationFrame) | |
| }, 1000/fps); | |
| } else { | |
| playin = false; | |
| } | |
| } | |
| plot(ratio, N, size) | |
| </script> | |
| </body> | |
| </html> |
| // 3 Aug 2018, Pablo Marcos https://github.com/pablomm | |
| // Downloaded from https://www.codeseek.co/hey-nick/d3-polar-scatter-NxqpVr | |
| // http://stackoverflow.com/questions/33695073/javascript-polar-scatter-plot-using-d3-js/33710021#33710021 | |
| var target = '.phi', | |
| color1 = '#ffcc00', | |
| color2 = '#ff99cc', | |
| width = 600, | |
| height = 600, | |
| radius = Math.min(width, height) / 2 - 30; // radius of the whole chart | |
| var data = []; | |
| var zoom = d3.behavior.zoom() | |
| .scaleExtent([1, 10]) | |
| .on("zoom", zoomed); | |
| function zoomed() { | |
| var e = d3.event, | |
| tx = Math.min(Math.max(e.translate[0], width - (radius+30) * e.scale),width - (radius+30) * e.scale), | |
| ty = Math.min(Math.max(e.translate[1], height - (radius+30) * e.scale),height - (radius+30) * e.scale); | |
| zoom.translate([tx, ty]); | |
| svg.attr("transform", [ | |
| "translate(" + [tx, ty] + ")", | |
| "scale(" + e.scale + ")" | |
| ].join(" ")); | |
| //svg.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); | |
| } | |
| function dragstarted(d) { | |
| d3.event.sourceEvent.stopPropagation(); | |
| d3.select(this).classed("dragging", true); | |
| } | |
| function dragged(d) { | |
| d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y); | |
| } | |
| function dragended(d) { | |
| d3.select(this).classed("dragging", false); | |
| } | |
| var r = d3.scale.linear() | |
| .domain([0, 1]) | |
| .range([0, radius]); | |
| var svg = d3.select(target).append('svg') | |
| .call(zoom) | |
| .attr('width', width) | |
| .attr('height', height) | |
| .append('g') | |
| .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); | |
| var gr = svg.append('g') | |
| .attr('class', 'r axis') | |
| .selectAll('g') | |
| .data(r.ticks(5).slice(1)) | |
| .enter().append('g'); | |
| gr.append('circle') | |
| .attr('r', r); | |
| var ga = svg.append('g') | |
| .attr('class', 'a axis') | |
| .selectAll('g') | |
| .data(d3.range(0, 360, 30)) // line density | |
| .enter().append('g') | |
| .attr('transform', function(d) { | |
| return 'rotate(' + -d + ')'; | |
| }); | |
| ga.append('line') | |
| .attr('x2', radius); | |
| //var color = d3.scale.category20(); | |
| var color = d3.scale.linear().domain([1,1000]) | |
| .interpolate(d3.interpolateHcl) | |
| .range([d3.rgb(color1), d3.rgb(color2)]); | |
| var line = d3.svg.line.radial() | |
| .radius(function(d) { | |
| return r(d[1]); | |
| }) | |
| .angle(function(d) { | |
| return -d[0] + Math.PI / 2; | |
| }); | |
| var tooltip = d3.select("body") | |
| .append("div") | |
| .style("position", "absolute") | |
| .style("z-index", "10") | |
| .style("visibility", "hidden") | |
| .text("a simple tooltip"); | |
| function generate_points(ratio, N, size) { | |
| data = [] | |
| for(var i=0; i<N; i++){ | |
| data.push([Math.PI * 2 *((ratio*i)%1), i/N, size]); | |
| } | |
| } | |
| function plot(ratio, N, size) { | |
| generate_points(ratio, N, size); | |
| svg.selectAll('.point').remove(); | |
| /* Plots the data */ | |
| svg.selectAll('point') | |
| .data(data) | |
| .enter() | |
| .append('circle') | |
| .attr('class', 'point') | |
| .attr('transform', function(d) { | |
| var coors = line([d]).slice(1).slice(0, -1); | |
| return 'translate(' + coors + ')' | |
| }) | |
| .attr('r', function(d) { | |
| return d[2]; | |
| }) | |
| .attr('fill',function(d,i){ | |
| return color(Math.floor(1000*d[1])); | |
| }); | |
| } | |
| function resetZoom() { | |
| var r = radius + 30; | |
| svg.attr("transform", "translate("+r+","+r+")scale(1,1)"); | |
| zoom.scale(scale) | |
| .translate([r,r]); | |
| } |
| /*Downloaded from https://www.codeseek.co/hey-nick/d3-polar-scatter-NxqpVr */ | |
| body { | |
| font-family: sans-serif; | |
| } | |
| .point { | |
| mix-blend-mode: multiply; | |
| } | |
| .frame { | |
| fill: none; | |
| stroke: #000; | |
| } | |
| .axis text { | |
| font: 10px sans-serif; | |
| } | |
| .axis line { | |
| fill: none; | |
| stroke: #ebebeb; | |
| } | |
| .axis circle { | |
| fill: none; | |
| stroke: #aaa; | |
| } | |
| .axis:last-of-type circle { | |
| stroke: #333; | |
| } | |
| .line { | |
| fill: none; | |
| stroke: orange; | |
| stroke-width: 3px; | |
| } | |
| /* https://www.w3schools.com/howto/howto_js_rangeslider.asp */ | |
| .slidecontainer { | |
| width: 100%; /* Width of the outside container */ | |
| } | |
| /* The slider itself */ | |
| .slider { | |
| -webkit-appearance: none; /* Override default CSS styles */ | |
| appearance: none; | |
| width: 100%; /* Full-width */ | |
| height: 25px; /* Specified height */ | |
| background: #d3d3d3; /* Grey background */ | |
| outline: none; /* Remove outline */ | |
| opacity: 0.7; /* Set transparency (for mouse-over effects on hover) */ | |
| -webkit-transition: .2s; /* 0.2 seconds transition on hover */ | |
| transition: opacity .2s; | |
| } | |
| /* Mouse-over effects */ | |
| .slider:hover { | |
| opacity: 1; /* Fully shown on mouse-over */ | |
| } | |
| /* The slider handle (use -webkit- (Chrome, Opera, Safari, Edge) and -moz- (Firefox) to override default look) */ | |
| .slider::-webkit-slider-thumb { | |
| -webkit-appearance: none; /* Override default look */ | |
| appearance: none; | |
| width: 25px; /* Set a specific slider handle width */ | |
| height: 25px; /* Slider handle height */ | |
| background: #4CAF50; /* Green background */ | |
| cursor: pointer; /* Cursor on hover */ | |
| } | |
| .slider::-moz-range-thumb { | |
| width: 25px; /* Set a specific slider handle width */ | |
| height: 25px; /* Slider handle height */ | |
| background: #4CAF50; /* Green background */ | |
| cursor: pointer; /* Cursor on hover */ | |
| } | |
| .field { | |
| width: 150px; | |
| } | |
| .bottom-button { | |
| width: 100px; | |
| } | |
| .settings { | |
| width: 600px; | |
| } | |
| @media screen and (orientation: landscape) { | |
| .container { | |
| width: 100%; | |
| text-align: center; | |
| } | |
| .settings { | |
| width: 600px; | |
| margin: auto; | |
| display: inline-block; | |
| } | |
| } |