Site updates: chapters, maps, and menus
Sunday, 8 November, 2026
⨳ 51 minute read ⨳ 12577 words ⨳ concerts ⨳ dataviz ⨳ maps ⨳ music ⨳ site ⨳TLDR; the site got a round of upgrades: the concert chart on my events page is split into chapters of my life (which also mark the shows and posts listings), the events page has stat tiles, the home page has an activity grid, venue, show, and artist pages have maps, and the menus are smarter.
For a while now, the events page has had a simple d3 chart: one line, the running total of every performance I've attended since 1994. Hollow dots on the line mark festivals, which explain most of the big jumps. It answered "how many?" just fine. It didn't answer "when, and why then?" That's where this started; it didn't end there.
The concert chart
Filling the page
The original chart was a fixed 500x200. Now it redraws to the width of the page and keeps a 5:2 shape (never shorter than 200px or taller than 450px). It redraws rather than stretches, so the axis text stays a normal size. The year ticks thin out on their own: every year when there's room, then every 2, 5, 10, or 20 years as the chart narrows.
Livestreams don't count
COVID turned a handful of shows into livestreams: Bad Religion's Decades run from the Roxy, a few Clutch streams, NOFX, and so on. Watching from the couch is not attending, so those come out of the chart and the counts.
setlist.fm sometimes notes a livestream, but those notes can be edited at any time, so I don't rely on them. Instead there's a short hand-kept file of setlist IDs (the ID is the tail end of every setlist.fm URL):
- "53c127b5" # 1977-03-21 Eagles @ Capital Centre
- "2babe41e" # 2024-10-04 NOFX
Anything in that list is left out of the stats by default. The events page, and the artist, show, and venue listings, each have an "Ignore Livestreams" checkbox (on by default); unchecking it on the events page puts livestreams back into the stat tiles and the chart. Livestreams never count toward miles, though: there's no trip to a couch.
Chapters
The interesting part of the line isn't its height; it's where the slope changes. So the chart is now shaded into periods:
I first wrote these as date ranges, but a list of cutpoints turned out to be easier to maintain. Each cutpoint starts a chapter that runs until the next one, so there are no gaps or overlaps to keep straight. They live in a small data file, along with the COVID shutdown (the gold band):
periods:
- start: "1993-09-01"
label: HS Years
- start: "1997-01-01"
label: College Years
- start: "2001-09-01"
label: Wisconsin Years
- start: "2005-02-12"
label: Decreased Free Time
- start: "2015-04-06"
label: Increasing Free Time
covid:
label: COVID
start: "2020-03-15"
end: "2021-05-28"
A cutpoint without a label just ends the chapter before it, leaving an unshaded gap.
The same file marks the chapters in the margin of the shows and posts listings: a banner runs alongside each chapter's entries, so you can tell at a glance which part of my life a show or a post came from.
A line per chapter
Inside each band, a second, fainter line starts back at zero and counts only that chapter's shows. On the main line, a slow chapter late in life gets visually flattened by everything that came before it. Restarting at zero puts every chapter on equal footing.
Rates, not totals
Chapters aren't the same length, so raw counts are misleading: ten years with kids at home will out-count two years of high school even if I barely left the house. Each band now shows rates instead: shows per year and performances per year.
| chapter | shows/yr | performances/yr |
|---|---|---|
| HS Years | 5.4 | 24.6 |
| College Years | 2.1 | 11.1 |
| Wisconsin Years | 1.2 | 2.3 |
| Decreased Free Time | 0.8 | 1.2 |
| Increasing Free Time | 5.2 | 8.3 |
The last chapter overlaps the COVID shutdown, when there were no in-person shows to go to, so its band also gives each rate with those 14 months left out (5.8 shows and 9.2 performances a year).
High school me went to a lot of shows (festivals helped: one day of Lollapalooza is seven performances). Then it falls off a cliff, bottoms out when the kids were small, and climbs back once they could fend for themselves. The gap between the two columns says something too: in high school a show averaged four or five bands, and lately it's closer to one or two.
Miles
The last addition to the chart is distance. Given where I lived and when, each show's distance from home is a straight-line, as-the-crow-flies calculation. setlist.fm only has city coordinates, so this is rough, but that's fine for a ballpark. (For the same reason, my home coordinates are rounded to one decimal, which is about 10 km.)
The one wrinkle: a day counts once. Driving 100 miles to a show with three bands is a 100-mile trip, not 300. So each day takes the distance to its farthest venue, and those are summed and divided by the chapter's length, just like the shows. The result is a third number on each band: miles per year. Elsewhere the miles are counted there and back: the overview's miles per show, and each show's page, which notes the round trip (or 0 miles for a livestream).
Stat tiles
The events page used to open with a couple of sentences of totals. Now it's a row of tiles: years, artists, shows, artists per show, songs (from the setlists), venues, cities, states, and miles per show. They follow the "Ignore Livestreams" checkbox along with the chart.
Maps
The setlist data comes with coordinates, so it seemed a shame not to use them. Every venue, show, and artist page now has a small map: the outline of the venue's state with a dot where it is, or, for an artist, every state I've seen them in. On those pages the details sit on the left half and the map on the right, where it stays put as you scroll down a long list (on a phone, the map moves up top).
The venues page gets a map of the whole country, each state shaded by how many shows I've seen there, with a dot at every venue:
Same caveat as the miles: setlist.fm only knows the city, so every Minneapolis venue lands on one downtown dot. Venues at the same spot share a dot; hover it to see them all. When I care enough to pin one exactly, I add its coordinates to the venue's entry in a data file and it gets its own dot.
Activity, by month
The home page got a GitHub-style activity grid: a row per month, a column per year, and darker squares for busier months. It counts posts, app releases, and shows (in person; livestreams don't count here either). Hover a square for the breakdown; click it to jump to that year.
It's a pretty honest picture of when I was out doing things or had something to say: shows packed into the mid-90s, a burst of (mostly SAS) posts from 2007 through 2014, a long quiet stretch, and shows picking back up lately. It took a few rounds to get right: years down the side at first, then swapped, then the squares shrunk to about a quarter of their size, and finally two-digit years that read left to right instead of rotated labels.
Menus
The navigation used to be built by sorting every page on the site, every time any page was built. That turned out to be what made a full build take about eight minutes. Now the menus live in a single data file, and a build takes about ten seconds.
That same file holds the sub-menus (posts, apps, and events each have one), and the highlighting works itself out:
- the top-level item stays highlighted while you're anywhere in its section,
- the sub-menu highlights the page you're on (or the closest one above it), and
- a highlighted item is still a link when you're deeper than its page, so you can click it to get back.
Odds and ends
- The artist, venue, and tag listings flow into as many columns as fit.
- Year and letter headings are the same size everywhere.
- Every date-ordered listing is newest first, all the way down.
- The tags got redone from scratch: 20 short tags instead of 60-odd, and each tag page lists related tags.
- The shows listing has a calendar file (.ics) with every show, built by Jekyll from the same data instead of by a separate script.
- The app pages share one header: the app's name, then About, Features, Pricing, Privacy, and Support (whichever the page has), and a Get button.
The data
As before, the concert data comes from my setlist.fm page, pulled down with my setlist.fm.go script.