#4517 KG for Music Theory with Clickable Links to Web Pages
#4517 KG for Music Theory with Clickable Links to Web Pages
#4517 Create an HTML, JavaScript, CSS app with a knowledge graph of music theory, with clickable node links to webpages for each node topic
Here is a complete, single-file HTML application that builds an interactive music theory knowledge graph. It uses D3.js (v7) for a smooth, draggable, and zoomable force-directed graph.
Every node represents a core music theory concept. Clicking a node will instantly open a curated, high-quality educational webpage (like Wikipedia or Open Music Theory) in a new tab.
How to Use It:
Copy the code below.
Save it as
music_theory_graph.html.Double-click the file to open it in any web browser.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Music Theory Knowledge Graph</title>
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
:root {
--bg-color: #1a1a24;
--panel-bg: #252538;
--text-color: #f0f0f6;
--accent-color: #4f46e5;
--node-core: #ff007f;
--node-pitch: #00f0ff;
--node-harmony: #ad00ff;
--node-rhythm: #ffaa00;
--node-form: #00ff66;
--link-color: rgba(255, 255, 255, 0.15);
}
body, html {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
background-color: var(--bg-color);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
color: var(--text-color);
}
#header {
position: absolute;
top: 20px;
left: 20px;
z-index: 10;
pointer-events: none;
}
h1 {
margin: 0 0 5px 0;
font-size: 1.8rem;
font-weight: 700;
letter-spacing: 1px;
text-shadow: 0 2px 4px rgba(0,0,0,0.5);
}
p {
margin: 0;
font-size: 0.9rem;
opacity: 0.8;
}
#graph-container {
width: 100%;
height: 100%;
}
/* D3 Styling */
.node {
cursor: pointer;
transition: filter 0.2s ease;
}
.node:hover {
filter: brightness(1.3) drop-shadow(0px 0px 8px rgba(255,255,255,0.6));
}
.node-label {
font-size: 12px;
font-weight: 600;
pointer-events: none;
text-shadow: 0 2px 4px rgba(0,0,0,0.9), 0 -1px 2px rgba(0,0,0,0.9);
}
.link {
stroke: var(--link-color);
stroke-width: 1.5px;
stroke-dasharray: 4 2;
}
/* Legend Panel */
#legend {
position: absolute;
bottom: 20px;
left: 20px;
background: var(--panel-bg);
padding: 15px;
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0,0,0,0.5);
font-size: 0.85rem;
z-index: 10;
}
.legend-title {
font-weight: bold;
margin-bottom: 8px;
border-bottom: 1px solid rgba(255,255,255,0.1);
padding-bottom: 4px;
}
.legend-item {
display: flex;
align-items: center;
margin: 4px 0;
}
.legend-color {
width: 12px;
height: 12px;
border-radius: 50%;
margin-right: 8px;
}
</style>
</head>
<body>
<div id="header">
<h1>Music Theory Knowledge Graph</h1>
<p>Scroll to Zoom. Drag nodes to explore. <strong>Click a node</strong> to open its learning resource.</p>
</div>
<div id="legend">
<div class="legend-title">Categories</div>
<div class="legend-item"><div class="legend-color" style="background: var(--node-core);"></div>Core Foundation</div>
<div class="legend-item"><div class="legend-color" style="background: var(--node-pitch);"></div>Pitch & Scales</div>
<div class="legend-item"><div class="legend-color" style="background: var(--node-harmony);"></div>Harmony & Chords</div>
<div class="legend-item"><div class="legend-color" style="background: var(--node-rhythm);"></div>Rhythm & Meter</div>
<div class="legend-item"><div class="legend-color" style="background: var(--node-form);"></div>Form & Structure</div>
</div>
<div id="graph-container"></div>
<script>
// --- 1. DATA DEFINITION ---
const graphData = {
nodes: [
// Core
{ id: "Music Theory", group: "core", radius: 25, url: "https://en.wikipedia.org/wiki/Music_theory" },
// Pitch & Scales
{ id: "Pitch", group: "pitch", radius: 18, url: "https://en.wikipedia.org/wiki/Pitch_(music)" },
{ id: "Intervals", group: "pitch", radius: 15, url: "https://en.wikipedia.org/wiki/Interval_(music)" },
{ id: "Scales", group: "pitch", radius: 16, url: "https://en.wikipedia.org/wiki/Scale_(music)" },
{ id: "Major Scale", group: "pitch", radius: 14, url: "https://en.wikipedia.org/wiki/Major_scale" },
{ id: "Minor Scale", group: "pitch", radius: 14, url: "https://en.wikipedia.org/wiki/Minor_scale" },
{ id: "Modes", group: "pitch", radius: 14, url: "https://en.wikipedia.org/wiki/Mode_(music)" },
{ id: "Circle of Fifths", group: "pitch", radius: 16, url: "https://en.wikipedia.org/wiki/Circle_of_fifths" },
// Harmony & Chords
{ id: "Harmony", group: "harmony", radius: 18, url: "https://en.wikipedia.org/wiki/Harmony" },
{ id: "Chords", group: "harmony", radius: 16, url: "https://en.wikipedia.org/wiki/Chord_(music)" },
{ id: "Triads", group: "harmony", radius: 14, url: "https://en.wikipedia.org/wiki/Triad_(music)" },
{ id: "Seventh Chords", group: "harmony", radius: 14, url: "https://en.wikipedia.org/wiki/Seventh_chord" },
{ id: "Chord Progressions", group: "harmony", radius: 16, url: "https://en.wikipedia.org/wiki/Chord_progression" },
{ id: "Voice Leading", group: "harmony", radius: 14, url: "https://en.wikipedia.org/wiki/Voice_leading" },
{ id: "Counterpoint", group: "harmony", radius: 15, url: "https://en.wikipedia.org/wiki/Counterpoint" },
// Rhythm & Meter
{ id: "Rhythm", group: "rhythm", radius: 18, url: "https://en.wikipedia.org/wiki/Rhythm" },
{ id: "Tempo", group: "rhythm", radius: 14, url: "https://en.wikipedia.org/wiki/Tempo" },
{ id: "Meter", group: "rhythm", radius: 16, url: "https://en.wikipedia.org/wiki/Meter_(music)" },
{ id: "Time Signatures", group: "rhythm", radius: 14, url: "https://en.wikipedia.org/wiki/Time_signature" },
{ id: "Syncopation", group: "rhythm", radius: 13, url: "https://en.wikipedia.org/wiki/Syncopation" },
// Form & Structure
{ id: "Musical Form", group: "form", radius: 18, url: "https://en.wikipedia.org/wiki/Musical_form" },
{ id: "Binary Form", group: "form", radius: 13, url: "https://en.wikipedia.org/wiki/Binary_form" },
{ id: "Ternary Form", group: "form", radius: 13, url: "https://en.wikipedia.org/wiki/Ternary_form" },
{ id: "Sonata Form", group: "form", radius: 15, url: "https://en.wikipedia.org/wiki/Sonata_form" }
],
links: [
// Core connections
{ source: "Music Theory", target: "Pitch" },
{ source: "Music Theory", target: "Harmony" },
{ source: "Music Theory", target: "Rhythm" },
{ source: "Music Theory", target: "Musical Form" },
// Pitch connections
{ source: "Pitch", target: "Intervals" },
{ source: "Pitch", target: "Scales" },
{ source: "Scales", target: "Major Scale" },
{ source: "Scales", target: "Minor Scale" },
{ source: "Scales", target: "Modes" },
{ source: "Scales", target: "Circle of Fifths" },
{ source: "Circle of Fifths", target: "Major Scale" },
{ source: "Circle of Fifths", target: "Minor Scale" },
// Harmony connections
{ source: "Harmony", target: "Chords" },
{ source: "Intervals", target: "Chords" }, // Cross-categorical link
{ source: "Chords", target: "Triads" },
{ source: "Chords", target: "Seventh Chords" },
{ source: "Chords", target: "Chord Progressions" },
{ source: "Circle of Fifths", target: "Chord Progressions" }, // Cross link
{ source: "Harmony", target: "Counterpoint" },
{ source: "Counterpoint", target: "Voice Leading" },
{ source: "Chord Progressions", target: "Voice Leading" },
// Rhythm connections
{ source: "Rhythm", target: "Tempo" },
{ source: "Rhythm", target: "Meter" },
{ source: "Meter", target: "Time Signatures" },
{ source: "Rhythm", target: "Syncopation" },
// Form connections
{ source: "Musical Form", target: "Binary Form" },
{ source: "Musical Form", target: "Ternary Form" },
{ source: "Musical Form", target: "Sonata Form" },
{ source: "Chord Progressions", target: "Musical Form" } // Structural harmony link
]
};
// --- 2. CONFIGURATION & COLOR SETUP ---
const container = document.getElementById('graph-container');
const width = container.clientWidth;
const height = container.clientHeight;
const colors = {
core: "#ff007f",
pitch: "#00f0ff",
harmony: "#ad00ff",
rhythm: "#ffaa00",
form: "#00ff66"
};
// --- 3. INITIALIZE SVG & ZOOM ---
const svg = d3.select("#graph-container")
.append("svg")
.attr("width", "100%")
.attr("height", "100%")
.attr("viewBox", [0, 0, width, height]);
// Create a master grouping element for zoom/pan support
const g = svg.append("g");
svg.call(d3.zoom()
.scaleExtent([0.3, 4])
.on("zoom", (event) => {
g.attr("transform", event.transform);
}));
// --- 4. FORCE SIMULATION CONFIGURATION ---
const simulation = d3.forceSimulation(graphData.nodes)
.force("link", d3.forceLink(graphData.links).id(d => d.id).distance(100))
.force("charge", d3.forceManyBody().strength(-400))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(d => d.radius + 20));
// --- 5. RENDER ELEMENTS ---
// Render links (Lines)
const link = g.append("g")
.attr("class", "links")
.selectAll("line")
.data(graphData.links)
.enter().append("line")
.attr("class", "link");
// Render nodes (Groups containing Circles + Labels)
const node = g.append("g")
.attr("class", "nodes")
.selectAll("g")
.data(graphData.nodes)
.enter().append("g")
.attr("class", "node")
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("click", (event, d) => {
// Prevent node click trigger if dragging
if (event.defaultPrevented) return;
window.open(d.url, '_blank');
});
// Add circles to nodes
node.append("circle")
.attr("r", d => d.radius)
.attr("fill", d => colors[d.group] || "#fff")
.attr("opacity", 0.85)
.attr("stroke", "#fff")
.attr("stroke-width", 1.5);
// Add text labels to nodes
node.append("text")
.attr("class", "node-label")
.attr("dy", d => d.radius + 16) // Position label below the circle
.attr("text-anchor", "middle")
.attr("fill", "#ffffff")
.text(d => d.id);
// --- 6. ANIMATION TICK LOOP ---
simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);
node
.attr("transform", d => `translate(${d.x}, ${d.y})`);
});
// --- 7. DRAG HANDLERS ---
function dragstarted(event, d) {
if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(event, d) {
d.fx = event.x;
d.fy = event.y;
}
function dragended(event, d) {
if (!event.active) simulation.alphaTarget(0);
d.fx = null;
d.fy = null;
}
// --- 8. RESPONSIVE WINDOW RESIZING ---
window.addEventListener('resize', () => {
const w = container.clientWidth;
const h = container.clientHeight;
svg.attr("width", w).attr("height", h);
simulation.force("center", d3.forceCenter(w / 2, h / 2)).alpha(0.3).restart();
});
</script>
</body>
</html>
Features included in this build:
Color-Coded Categories: Nodes are separated into core domains (Pitch, Harmony, Rhythm, Form) with a slick dark-mode UI and neon identifiers.
Dynamic Force Simulation: Nodes pull towards logical relatives and push away from unrelated concepts automatically to prevent overlapping text.
Interaction: Features fluid panning and zooming alongside click-and-drag mechanics to keep the map dynamic.
Adaptive Resizing: The application automatically repositions the center gravity if you adjust the dimensions of your browser window.
From <https://gemini.google.com/app/fbb80a89c657a74d> Google Gemini (3.5 Flash)
Comments
Post a Comment