The outer rings lie unless you scale them by a square root
Ringwood draws any public GitHub repo's history as a tree trunk, one ring per year. Getting the widths honest took a square root, and getting the dead years honest took scars.
Nobody has ever needed a legend to read a cut tree stump. Wide ring, good year. Thin ring, a year the rain didn't come. A run of hairline rings near the middle and then everything opening up again, and you know something bad happened to that tree in its twenties and it got over it. Dendrochronologists date timber to the exact year off that language, and the rest of us picked it up in a museum at age eight without anyone explaining it.
Commit history happens to fit the same shape, and every tool that visualizes it insists on drawing a bar chart instead. Ringwood takes a public repo and renders it as a trunk: one ring per calendar year, ring width from that year's commit count, color banding within the ring for the languages those commits actually touched. It runs entirely in your browser against the GitHub REST API. No login, no server, no key.
Why the naive version is a lie
My first pass scaled ring thickness linearly. Busiest year gets the full thickness budget, a year with half the commits gets half the thickness. Reasonable, and wrong in a way that took me an embarrassingly long time to name.
The problem is that a ring is an annulus, not a bar. Its ink on screen is roughly its circumference times its thickness, and circumference grows with radius. A ring 20 pixels thick sitting at radius 200 covers about four times the area of a 20-pixel ring at radius 50. So under linear scaling, two years with identical commit counts render at identical thickness but wildly different visual weight, and the eye reads area, not width. Every tree came out looking like the project had exploded in the last three years, no matter what the data said.
The fix is one line: thickness scales by sqrt(count / max) instead of count / max. Since
area goes as roughly the square of the radius, taking the square root of the share cancels the
inflation, and a quiet year at the bark reads about as quiet as the same year would have at the
core. It's the same correction behind sizing bubbles in a scatter plot by the square root of
their value, and it's just as easy to get wrong there.
The other honest-rendering decision: dormant years do not disappear. A repo that went silent for two years and came back keeps those years as thin, near-floor rings rather than having the timeline close up over them. That is exactly what a drought looks like on a real trunk, and it means the ring count always equals the repo's actual age.
The limit is 60 requests an hour
Anonymous GitHub API calls get 60 requests per hour per IP. Ringwood pages history at 100 commits a request, newest first, so a full hour of quota buys you about 6,000 commits. That is plenty for basically every side project ever pushed.
It is not plenty for torvalds/linux, which is past 1.4 million commits and would need somewhere
around 14,600 requests. I could have blocked big repos, or asked for a token, or quietly drawn a
wrong tree. Instead it renders whatever the quota covered and the status line tells you it's
showing a partial, most-recent slice. Linux comes out as a sapling made of its last few months,
which is a funny and completely truthful thing for a tool with no auth step to hand you. If the
rate limit bites, the message says how many minutes until it resets, because a spinner that never
resolves is worse than a number.
Try it
Open Ringwood and paste your oldest side project.
Shorthand, full URLs, branch links, and .git suffixes all resolve the same way. Press Grow,
watch the rings ease outward a year at a time, hover any ring for its commit count and dominant
language, then hit Export PNG once the last one lands. The shape of your own history is rarely
what you remember it being. The source is on GitHub.
This post is part of the build log: every app my automated factory ships gets written up here, honestly. Browse everything at apps.charliekrug.com. Comments are open below.
Loading comments…