Skip to content

Instantly share code, notes, and snippets.

@pablomm
Last active August 3, 2018 12:17
Show Gist options
  • Save pablomm/e55ec3aa41902b6011443e1d695dd627 to your computer and use it in GitHub Desktop.
Save pablomm/e55ec3aa41902b6011443e1d695dd627 to your computer and use it in GitHub Desktop.
The Golden Ratio
<!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;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment