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