Skip to content

Instantly share code, notes, and snippets.

@soxofaan
Last active December 29, 2018 17:06
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save soxofaan/17b13b4120c9dcb38fb0abccb2e4d200 to your computer and use it in GitHub Desktop.
Circle of Fifths with Diatonic Seventh Chords
license: mit
height: 800
border: no
(function (d3) {
var dims = {
width: 960,
height: 800
};
dims.outer = 0.35 * Math.min(dims.width, dims.height);
dims.inner = 0.6 * dims.outer * Math.atan(Math.PI / 12);
dims.note = dims.inner * Math.atan(Math.PI / 7);
dims.baseFontSize = 0.05 * dims.outer;
var animationDuration = 400;
var svg = d3.select("#content")
.append("svg")
.attr("width", dims.width).attr("height", dims.height)
.style("display", "block")
.style("margin", "0 auto")
.style("font", dims.baseFontSize + "pt sans-serif")
.style("text-anchor", "middle")
.append("g").attr("transform", "translate(" + (dims.width / 2) + "," + (dims.height / 2) + ")");
var data = [
["C", "F", "B", "E", "A", "D", "G"],
["F", "Bb", "E", "A", "D", "G", "C"],
["Bb", "Eb", "A", "D", "G", "C", "F"],
["Eb", "Ab", "D", "G", "C", "F", "Bb"],
["Ab", "Db", "G", "C", "F", "Bb", "Eb"],
["Db", "Gb", "C", "F", "Bb", "Eb", "Ab"],
["Gb", "Cb", "F", "Bb", "Eb", "Ab", "Db"],
["Cb", "Fb", "Bb", "Eb", "Ab", "Db", "Gb"],
["Fb", "Bbb", "Eb", "Ab", "Db", "Gb", "Cb"],
["G#", "C#", "F##", "B#", "E#", "A#", "D#"],
["C#", "F#", "B#", "E#", "A#", "D#", "G#"],
["F#", "B", "E#", "A#", "D#", "G#", "C#"],
["B", "E", "A#", "D#", "G#", "C#", "F#"],
["E", "A", "D#", "G#", "C#", "F#", "B"],
["A", "D", "G#", "C#", "F#", "B", "E"],
["D", "G", "C#", "F#", "B", "E", "A"],
["G", "C", "F#", "B", "E", "A", "D"],
];
var degrees = ["IΔ7", "IVΔ7", "VIIø7", "III-7", "VI-7", "II-7", "V7"];
// Order of scales in the circle of fifths
var scaleOrder = [
["C", "B#", "Dbb"],
["F", "E#", "Gbb"],
["Bb", "A#"],
["Eb", "D#"],
["Ab", "G#"],
["Db", "C#"],
["Gb", "F#"],
["B", "Cb", "A##"],
["E", "Fb", "D##"],
["A", "G##", "Bbb"],
["D", "C##", "Ebb"],
["G", "F##", "Abb"],
];
// Value between 0 and 1 for each scale
var scaleValues = {};
scaleOrder.forEach(function (notes, i) {
notes.forEach(function (note) {
scaleValues[note] = i / 12;
});
});
function noteColor(note) {
return d3.interpolateRainbow(scaleValues[note]);
}
function countSharps(scale) {
return (scale.join().match(/#/g) || []).length - (scale.join().match(/b/g) || []).length;
}
// Radial grid
svg.selectAll("line.grid")
.data(scaleOrder)
.enter()
.append("line")
.attr("class", "grid")
.attr("x1", 0).attr("y1", 0)
.attr("x2", function (d) {
return -2 * dims.outer * Math.sin(2 * Math.PI * scaleValues[d[0]]);
})
.attr("y2", function (d) {
return -2 * dims.outer * Math.cos(2 * Math.PI * scaleValues[d[0]]);
})
.attr("stroke", function (d) { return noteColor(d[0]);})
;
// svg.append("circle").attr("r", dims.outer).attr("fill", "transparent").attr("stroke", "#eee");
// Center text
svg.append('text').text('Circle of Fifths').attr("y", "-0.2em").style("font-size", "250%");
svg.append('text').text('Diatonic 7th Chords').attr("y", "1em").style("font-size", "200%");
// Big scale circles
var scales = svg.selectAll("g.scale")
.data(data)
.enter().append("g").attr("class", "scale");
scales.attr("transform", function (d, i) {
var a = 2 * Math.PI * scaleValues[d[0]];
var sharps = countSharps(d);
var r = dims.outer + sharps / 6 * (dims.inner + dims.note);
var s = Math.abs(sharps) <= 4 ? 1 : 1 - 0.02 * (Math.abs(sharps) - 4) * (Math.abs(sharps) - 4);
return "translate(" + (-r * Math.sin(a) + "," + (-r * Math.cos(a)) + ") scale(" + s + "," + s + ")");
})
;
scales.append("circle")
.attr("fill", function (d) { return noteColor(d[0]);})
.attr("r", 0)
.transition()
.delay(function (d) { return animationDuration * scaleValues[d[0]];})
.duration(animationDuration)
.attr("r", dims.inner)
;
scales.append("text")
.text(function (d) { return d[0];})
.style("font-size", "200%")
.attr("y", ".4em")
;
// Per scale notes.
// Hierarchical data binding https://bost.ocks.org/mike/nest/#data
var notes = scales.selectAll("g.note")
.data(function (d) {return d;})
.enter().append("g").attr("class", "note");
notes.attr("transform", function (d, i) {
var alpha = 2 * Math.PI * (-0.25 - i / 7);
return "translate(" + (dims.inner * Math.cos(alpha) + "," + (dims.inner * Math.sin(alpha)) + ")");
});
notes.append("circle")
.attr("fill", function (d) {
return noteColor(d);
})
.attr("stroke", "#fff")
.attr("r", 0)
.transition()
.delay(function (d) { return animationDuration * scaleValues[d];})
.duration(animationDuration)
.attr("r", function (d, i) { return i === 0 ? dims.note * 1.3 : dims.note; })
;
var noteTexts = notes.append("text");
noteTexts.append("tspan")
.text(function (d) {return d;})
.style("font-size", "100%")
.attr("x", "0")
.attr("y", "0.1em")
;
noteTexts.append("tspan")
.text(function (d, i) {return degrees[i];})
.style("font-size", "66%")
.attr("x", "0")
.attr("y", "0.9em")
;
})(d3);
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Circle of Fifths with Diatonic Seventh Chords</title>
</head>
<body>
<div id="content"></div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="app.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment