Genregraphy is an interactive map of music genres from 1950 to 2025. It groups genres by similarity and renders them as continents. The size and borders of each "country" change year by year based on release data.
The system architecture runs on two ends. Python scripts scrape and normalize the historical release numbers offline. The browser then takes that dataset and uses D3.js to calculate the Voronoi polygon geometry on the fly, with Vue managing the timeline state.
Scraping
The underlying dataset comes from MusicBrainz and Last.fm, but clean timeline data for genre popularity was an absolute nightmare to extract.
There is no single API endpoint for "all albums by genre per year." The data pipeline required a massive multi-step process. First, the script queries MusicBrainz for baseline releases ( I drink tea ). Then hits Last.fm to cross-reference tags and pull detailed descriptions ( I drink tea ).
This forced me to deal with API rate limits, massive inconsistencies in tagging (sync "post-punk" vs "post punk"), and manual mappings of micro-genres to assign them to top-level families ( drink more tea ).
The backend is a labyrinth of Python scripts that fetch, merge, deduplicate, and calculate the exact album counts for every year since 1950 ( while I drink tea).
The final output consists of heavily optimized JSON shards grouped by decade.
Cartography
To keep the terrain stable as years pass and data wildly fluctuates, the layout uses a strict macro-geography model:
Anchored Continents: The logic groups micro-genres into 9 macro-families (Rock, Electronic, Jazz, etc.) and anchors them to precise cardinal directions. Rock & Metal live in the East and North. Electronic & Hip-Hop dominate the West. Reggae and Global Beats sit in the South.
Organic Borders: D3.js handles the geometry via nested hierarchies. The engine packs genres together based on volume, and these packed nodes serve as primary seeds for a Weighted Voronoi tessellation. The Voronoi algorithm draws contiguous, polygon-shaped borders around these seeds, mutating raw numbers into a continuous, geographical landmass.
To push hundreds of complex SVG paths efficiently while a user scrubs the timeline, the engine resolves the math in phases. It calculates the rigid macro-continents first to establish solid borders, then dynamically packs the mutating micro-genres inside them.
Code
Here is the logic for terrain calculation without layout thrashing:
// First, map the macro-continents based on global album totals
const macroHierarchy = d3.hierarchy({
name: 'root',
children: allGroups.map(name => {
const group = yearData.genre_group.find(g => g.name === name)
const trueRatio = group && computedGlobalTotal > 0 ? (group.total / computedGlobalTotal) : 0
return { name, value: Math.max(0.001, trueRatio) } // Prevent UI collapsing
})
}).sum(d => d.value)
.sort((a, b) => a.data.name.localeCompare(b.data.name))
const macroPack = d3.pack().size([width, height])
const macroNodes = macroPack(macroHierarchy).leaves()
// Then, dynamically pack the actual genres inside their assigned continent limits
macroNodes.forEach(macroNode => {
// ...
const root = d3.hierarchy(hierarchyData)
.sum(d => {
// Apply a logarithmic scale to the album counts so dominant
// genres don't completely swallow the map
if (!d.value || d.value <= 0) return 0
return 25 + (Math.log10(d.value) * 20)
});
// Determine the exact required diameter to fit our genres
const minNeededDiameter = 2 * minNodeR * Math.sqrt(totalSum / Math.max(minValue, 1));
const dynamicDiameter = Math.max(baseDiameter, minNeededDiameter);
const pack = d3.pack()
.size([dynamicDiameter, dynamicDiameter])
.padding(3);
// ...
});
Frontend
The D3 layer handles the map math, wrapped in an immersive shell powered by Vue 3 and Vite. This combination forces the DOM updates to remain smooth. The visual jump from the early 1950s rock-and-roll expansion directly to the 2010s EDM explosion remains completely seamless.
Live Demo - https://notbigmuzzy.github.io/genregraphy/
Source Code - https://github.com/notbigmuzzy/genregraphy
Data disclamer
To make this thing fun to use, some decisions had to be made:
- The 120 Limit: MusicBrainz and Last.fm return thousands of micro-genres. Plotting thousands of tiny nodes on a single map would not be realistic, so I chose a curated set of 120 foundational genres and umbrellas. Some will agree with the list, some will not, that is fine.
The "Round Year" Compensation: Early on I noticed heavy clustering of data. I'm guessing that when people log album releases but aren't sure of the exact year, they naturally default to round numbers (1980, 1990, 2000). The year 2000 was an absolute anomaly in the dataset—without mathematical compensation, that single year would appear as the defining peak for 50 different genres simultaneously. I had to algorithmically balance the numbers for these milestone years. Again, some will agree with this, some will not, that is also fine with me.
Logarithmic Scaling: If you size the map areas purely geographically (1-to-1 based on release numbers), majority of genres would simply disappear from the screen. Pop and Rock would take up 95% of the space, leaving everything else as a single unclickable pixel. A logarithmic scale guarantees the massive genres stay prominent without completely hiding smaller, culturally vital movements.
The Parent Tag Problem: Terms like "Blues", "Rock", or "Metal" function as both specific genres and wide colloquial umbrellas. There is rarely a "pure" Rock artist (they are Indie Rock, Hard Rock, etc.), but discarding tens of thousands of tags labeled just "Rock" ignores valid data. I kept these massive parent nodes in the ecosystem, accepting the tradeoff that they inherently capture a larger percentage of total territory on the map.
Powered by MusicBrainz and Last.fm.
Top comments (0)