Making Of: Ten Million Chess Openings

on: chess, data visualization, long form, making of, print

Whenever I share something I make, I get a lot of emails and comments, mostly asking: “how did you make this?” The honest answer is usually “slowly, and with a lot of fiddling”. This blog is open-source, the only common tool I use is d3, and mountains of custom code. But no, I can’t help you with tech support on this repo. Most are disheartened when they hear this; they want to hear there’s some magic software that can just do it (and nowadays there just might be, with the proliferation of AI tools, that’s another post).

So this is a “making of” post. It’s about a poster I just finished, Ten Million Chess Openings, and by extension it’s about the chess openings visualization it grew out of. I’ve wanted to make a print version of my dataviz work for a long time. I’ve had a few printed for myself over the years, but this is the first one I’m actually putting up for sale (the first of hopefully many). Instead of just rehashing an old viz, I wanted to bring you along for the ride as I took something built for a browser and turned it into ink on paper.

This isn’t a tutorial or a step-by-step. It’s more of a high-level tour through the things I tried, the knobs I turned, and the decisions I landed on. If I’m being honest, I’m mostly winging it the whole time, and I think that’s part of the fun.

Where it started

Back in 2016 I wrote A Visual Look at 2 Million Chess Games, digging through ~2M games from the MillionBase database. Out of everything in that post, the openings sunburst was the thing people kept coming back to. It got shared around a lot, and people spent a surprising amount of time just poking at it.

For me, it was an amazing way to explore chess openings. You hover over a slice and the board plays out the line, and you start to feel the shape of opening theory: how 1.e4 takes up half the world, how the Sicilian fans out into a hundred little branches, how some lines that sound popular are actually tiny slivers. It’s very tactile.

The data side of this has always been pgnstats, a PGN parser I wrote in Go for the original post. You point it at a PGN file and it chews through every game, building up the openings tree (and a bunch of other stats, like the heatmaps from the 2016 post) as it goes. It’s made specifically for enormous files like MillionBase or Lumbra’s Gigabase. That’s a big part of why I could keep coming back to this: getting a fresh tree out of a new database is one command, and then all the fun happens on the drawing side.

The first public version actually came a little before the post, as part of chess-dataviz, a D3 library I put together for these charts. It started life as a straight copy/paste of a d3 sunburst example, and over a couple of months it slowly turned into something chess-shaped.

2015 — chess-dataviz, three commits
The library's openings chart at three points in its history, each drawn with that commit's settings. The first one still had the example's sample data; the other two draw the same chess dataset.
june11attsjosus13sus9sus1stillonallsusjcpstillon
Oct 17, 2015. first commit: copy-pasted d3 "sequences sunburst" example. It still drew the example's sample data, so that's what you see here. A color per move (d3.category20), no shading, biggest arcs first, 1px white lines. Ugly as sin.
34 nodes · 26 leaves · depth 5

The 2016 post used that same library, with a chessboard sitting in the middle of the circle that would play out whatever line you were hovering. Hover over an arc and you’d also get the opening name.

A quick note on the figures in this post: they’re all static illustrations. No pieces on the board, no hovering, no tooltips. Some have a slider or a toggle so you can turn a knob yourself, but what I’m really trying to show here is how this turned into a print.

2016 post as published / 2021 rewrite
The same quarter of the circle, top right, from the same 2m tree.
2016, as published
e4d4c4Nf3e6e5c5Nf6d5d4Nf3Nf3c4Nf3c4d5Nc6e6Nc6d6e6g6Bb5d4d4Nc3a6cxd4cxd4Nxd4Nxd4Nf6Nc3a6
chess-dataviz 1.0.0: 0.5px white lines, moves in tree order, a chessboard over the hole.
2021 rewrite
e4d4Nf3c4c5e5e6c6Nf6d5e6Nf6d5Nf6Nf3Nc3Nf3d4d4c4Nf3c4Nf3c4d6Nc6e6Nc6d5d5e6g6c5g6e6e6c6d4d4d4Bb5Nc3Nd2Nc3Nf3Nc3Nc3cxd4cxd4cxd4a6Bb4Bg7Nxd4Nxd4Nxd4Ba4e4Nf6Nf6Nf6d6Nc3Nc3O-Oa6Be7
Small gaps instead of lines, deepest lines first, a softer fade, slightly better colors.
968 nodes · 358 leaves · depth 10 · 2,197,113-game tree

And then I just… kept coming back to it. I rewrote it in 2021, used a version of it for the 2022 Candidates recap, and eventually ended up building a custom app just to help me with the layout and all the knob turning while iterating on it. Every time I touched it I’d change something small, and over the years those small things added up.

Knobs

This is the part I think is the most interesting, and also the part nobody sees. A sunburst looks like one decision, but it’s really a pile of little ones, and every one of them changes the picture. The figures below are all drawn from the original 2 million game tree, with one thing changed at a time so you can see what each knob does.

How deep to go

The first question is how many half-moves to show. Go too shallow and it’s basically a pie chart: e4, d4, done. Go too deep and the outer rings turn into dust that you can’t read or even see.

Ply depth
Same drawing as 2016. 4 half-moves is a pie. 10 is a city.
e4d4Nf3c4c5e5e6c6d6Nf6d5Nf6d5Nf6Nf3Nc3c3Nf3d4d4d4c4Nf3c4Nf3c4d6Nc6e6Nc6d5d5Nf6e6g6c5g6e6c6e6
269 nodes · 156 leaves · depth 4 · 2,197,113-game tree

The 2016 version showed 10 plies. At some point in 2018 I refactored the parser and, without really noticing, it started producing 9. The poster also has 9 rings. I’d love to tell you that was a carefully considered artistic choice, but I honestly don’t remember! It does look good though. Nine rings is just enough to walk you right up to the doorstep of the real named openings (the last ring of the Najdorf is 5.Nc3, one move short of 5…a6) without the outer ring falling apart.

What to throw away

With millions of games, there’s a very long tail of lines that were played once or twice. Some of those are real gems, and some are… questionable. Do you keep them?

Prune threshold
Further prune on the already-cut 2m tree. 0.5% is the later default.
e4d4Nf3c4c5e5e6c6d6d5Nf6d5e6f5g6d6Nf6d5Nf6e6Nf3c3Nf3d4d4d4exd5c4Nf3c4c4c4Nc3d6Nc6e6Nc6d5d5Nf6g6e6c6e6g6d4d4Bb5d4Bb5Bc4Nc3Nc3Nc3Nc3Nf3Nf3cxd4cxd4cxd4a6g6Bg7Bb4Nf6Nxd4Nxd4Nxd4Ba4e4e3Nc3Nf6Nf6Nf6d6Nc3Nc3O-Oa6g6Be7
197 nodes · 70 leaves · depth 10 · 2,197,113-game tree

In 2016 I cut anything played in less than 0.1% of games while parsing, so the smallest slice on that chart is still 2,199 games. That keeps things tidy, but it also hides the texture at the edges, which turns out to be one of the prettiest parts.

For the poster I kept it lighter, but not completely. I don’t actually remember the exact threshold I used, but it feels right: on paper you can see far finer detail than on a screen.

Squeezing the rings

Most of the data lives near the center. Every single game passes through the first ring, and the further out you go, the more the games spread out into thinner and thinner lines. So that’s where the visual weight should be too. If every ring had the same thickness, the outer rings would end up with way more area (a ring’s area grows with its distance from the center), and all that ink would go to the sparse fringe instead of the main lines. So the radius uses a square root scale, which gives every ring the same area instead of the same thickness. The inner rings get thicker, the outer ones thinner, and the center gets the weight it deserves.

Radius scale
Same tree, same angles. Only the mapping from ring to radius changes.
linear
Every ring equally thick, so the outer rings take most of the ink.
square root
Every ring the same area; rings thin out toward the edge.
square
The mirror of square root: every ring thicker than the one inside it. If we wanted to highlight the leaves, this could be a better option.
968 nodes · 358 leaves · depth 10 · 2,197,113-game tree

A side effect I like a lot: the hole in the middle isn’t a design decision, it’s the “starting position” ring, the root that every game passes through, which just doesn’t get drawn. With 9 rings, it ends up being about a third of the radius. For the 2016 post I put a chessboard in it, and for the poster I left it empty. More on that below.

Gaps and lines between slices

How you separate slices sounds like a nitpick, but it changes the whole feel. The original library drew thin white lines, the easiest and simplest way to achieve the separation. The 2021 rewrite added small gaps between slices instead. Later on I switched to a very thin dark line.

In all cases, the issue is that you want the colored “ink” to correlate to the actual distribution of the data. This isn’t a big deal for e4 which takes up a big portion of the space, but if you’re adding 1-2 pixel borders around tiny slivers, it actually takes away from the accuracy of the data for the farther moves. This is why sub-pixel lines are useful in the poster - it can delineate the different moves without affecting the actual size and scale of the inked area.

Gaps and lines between slices
Same 2m tree and 2016 knobs. Only the separation changes. The inset is the outer edge, magnified.
outer edge, ×9
chess-dataviz v3 as published: 0.5px white stroke, no padAngle, rings touch.
968 nodes · 358 leaves · depth 10 · 2,197,113-game tree

Color

The very first version used a stock color palette. Later I switched to Tableau colors, which are nicer but still feel like “a chart”. The color carries the first move: everything that starts with 1.e4 is one family, 1.d4 is another, and so on, with each ring getting slightly lighter or darker so you can still see the steps.

Color
Same 2m tree. Color carries the first move; rings alternate light and dark so the steps stay visible.
d3 category10 by first move, as in chess-dataviz.
968 nodes · 358 leaves · depth 10 · 2,197,113-game tree

Picking the colors was only half of it. How the color changes from ring to ring was a pretty conscious decision, and it took a lot of iterating to get there. The first version was one flat color per first move, which makes each family read as a blob. Then I made the rings alternate darker and lighter, which has a nice side effect: every other ring is a White move, and the ones in between are Black’s, so you can kind of see the back and forth of the game. Finally, everything gets a little darker as you go outward, so the eye is pulled towards the center, where most of the games are.

Shading the rings
Poster palette on the 2m tree. Odd rings are White's moves, even rings are Black's.
flat
One color per first move. Rings only show at the hairlines.
alternating
Odd rings (White's moves) lighter, even rings (Black's) darker.
alternating, darker outward
Same, and each full move a step darker: the poster.
968 nodes · 358 leaves · depth 10 · 2,197,113-game tree

Move names vs opening names

On the interactive version, the arcs show the moves (e4, c5, Nf3…) and the opening names live in a tooltip. That’s great when you have a mouse. On paper, there’s no hover, so the names have to be on the sheet somewhere, and that was a whole thing (see below).

Labels, poster style
Labels in the style of the poster, drawn on the 2m tree. The poster's callouts were placed by hand; these are placed by a simple rule.
Names, callouts and moves together, as on the poster.
968 nodes · 358 leaves · depth 10 · 2,197,113-game tree

Board vs. no board

The board in the middle was one of my favorite parts of the original. You hover, it plays the line. But a board on a poster is just a static picture of a board, and it fights with everything around it for attention.

From browser to paper

Here’s where it gets interesting. I’d made some version of this chart a dozen times by now, but a screen and a sheet of paper are very different beasts. On a screen you can hover, zoom, and read a tooltip. On a wall it has to explain itself from across the room, and from two inches away. Almost every interactive feature had to be replaced with something else, so here are the decisions I landed on:

Data. First I needed more games. Back in 2016, MillionBase was the biggest free database around, so naturally I tried that first, but the link was down. Then I remembered that the owner of Caissabase had actually reached out to me at some point… about four years earlier. Oops. In the end I went with Lumbra’s Gigabase, which I found through the En Croissant database list. That’s 10,388,956 games, almost five times what I had in 2016, so a big improvement. The earliest game in there is from 1515, which is where the subtitle comes from: “A visual record of 510 years of human play.” I didn’t do any fancy filtering either, it all went through pgnstats with the default settings.

2,197,113games · MillionBase · 201610,388,956games · Lumbra's Gigabase · poster
Circles sized by game count: about 4.7 times as many games.

An empty center. In the end I left the hole empty. It gives your eye somewhere to rest, and honestly the circle reads better as one object without a board in the middle. Every game starts there anyway.

Background and color. A warm near-black instead of a pure black, and a custom palette instead of a stock one. 1.e4 is violet, 1.d4 is sky blue, 1.Nf3 is pink, 1.c4 is mint. I wanted it to look good on a wall first, and like a chart second. Fun to see that e4 takes up about half the circle in this dataset, d4 about a third, and the rest share what’s left.

Web (the original post)
1.e4
1.d4
1.Nf3
1.c4
d3's stock category10, on the blog's background.
Poster
1.e4
1.d4
1.Nf3
1.c4
Custom palette, on the poster's warm near-black.
Each chip is a first move: the first ring's color on top, the second ring's below.

Lines. Always-on hairlines between slices. With no mouse, the slices need to separate themselves.

Move names along the rings. On screen, the move labels stay upright so they’re easy to read. On the poster they follow the curve of each ring, which looks a lot more natural when you’re standing in front of it.

Opening names. This was the most work by far. The bigger openings get their names written inside the arcs. For the smaller ones out at the edges, there are about fifty little callouts with leader lines pointing to their slice. My little layout app handled the rendering and let me toggle each label on and off and drag it around, but the actual placement was all by hand. Lots of nudging something two pixels to the left, staring at it, and nudging it back.

Close-up of the poster's outer rings, with hundreds of thin slices separated by dark hairlines
Hairlines between slices, out at the fringe.
Close-up of the poster where move names like Nc6 and cxd4 follow the curve of each ring
Moves follow the rings; opening names sit underneath.
Close-up of hand-placed callouts on the poster: Chekhover, Moscow, Accelerated Dragon, with leader lines into their slices
Callouts, placed by hand.

Type. Fanwood and Junction, both from The League of Moveable Type.

Web (the original post)
A Visual Look at 2 Million Chess Games
e4 c5 Nf3 d6 d4 cxd4
The blog's heading font, and plain sans-serif for the move labels.
Poster
Ten Million Openings
A visual record of 510 years of human play.
SICILIAN DEFENSE · e4 c5 Nf3 d6
Fanwood for the title, Junction for the labels.

Getting it printed. This part was way less glamorous than I expected. I print through Prodigi, and my first few attempts at fancy press-ready exports (PDF, TIFF, a giant 4x render) all got rejected. What worked in the end was one 24×36" PNG master, resampled carefully. Then I ordered test prints and held my breath.

Four of them: two big, two small, each size on two different papers. Up on the wall with painter’s tape, so I could see them in real life, from across the room and up close. My dog supervised the whole process.

Four test prints of the poster taped to a wall with green painter's tape, two large on top and two small below, with a dog lying underneath
The test prints: two sizes, two papers. Supervisor bottom left.

Picking the paper. The two contenders were Prodigi’s “Smooth Art Paper” (240gsm) and their “Enhanced Matte Art Paper” (200gsm). Most of this poster is one big field of near-black ink, and that shows every mark, so I did the scientific thing and scratched them with my fingernail (no, it wasn’t my dog’s fingernail, you cannot prove anything).

Close-up of a test print's dark background with fingernail scratch marks under the purple e4 arcs
The fingernail test.
Angled close-up of the poster's title and description block, with scratches across the dark background
Dark ink shows everything.

The Enhanced Matter Art paper came out ahead on both print quality and durability, so that’s what the poster is printed on, even though it costs a bit more.

Ten Million Openings

And here it is!

Ten Million Openings poster

It’s a little surreal to see something that started as a hover effect in a 2016 blog post hanging on a wall. Every slice is real games, every thin line out at the edge is someone, somewhere, trying something a bit different.

If you’d like one for yourself, or you know a chess player who’d love it, it’s available on Etsy. Buying one genuinely helps me keep making these (and keep coming back to this sunburst for another ten years). If not, I hope you enjoyed the peek behind the curtain anyway. Let me know what you think, and if you have questions about any of the knobs, ask away!

Share this post

I'm pretty sure you know how to share things on social media, so I'll spare you the 50 different obnoxious buttons. If you liked this, feel free to share anywhere, it helps me out.

Have something to say? Discuss on Twitter, or tell me @ebemunk. Consume my work on Instagram or stalk me on Reddit.

Join my newsletter to get updates when I add new stuff here. I won't sell you anything or spam you with bs.

See Also

Like this stuff and want custom visualizations for your own data?
Let's work together. Get in touch!