Created
June 24, 2026 10:44
-
-
Save jkrumbiegel/b6b7628a51b025dbfa74dbff9b293a7b to your computer and use it in GitHub Desktop.
Count new Julia package registrations per month from the General registry and plot the trend (RegistryActivity + AlgebraOfGraphics/CairoMakie)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using Pkg | |
| Pkg.activate(; temp = true) | |
| Pkg.add(["DataFrames", "CairoMakie", "AlgebraOfGraphics"]) | |
| Pkg.add(url = "https://github.com/JuliaEcosystem/RegistryActivity.jl") | |
| using RegistryActivity, DataFrames, Dates, CairoMakie, AlgebraOfGraphics | |
| const AoG = AlgebraOfGraphics | |
| # RegistryActivity.clone_registry() clones via LibGit2, which fails on macOS with a | |
| # SecureTransport error; clone with the system git and pass the path instead. | |
| function local_general() | |
| dir = joinpath(@__DIR__, "General") | |
| isdir(dir) || run(`git clone https://github.com/JuliaRegistries/General.git $dir`) | |
| dir | |
| end | |
| months, packages, _versions = RegistryActivity.registry_activity(local_general()) | |
| reg = DataFrame(month = collect(months), packages = collect(Int, packages)) | |
| reg.year = year.(reg.month) | |
| # registry_activity reports the cumulative distinct-package count per monthly snapshot, so | |
| # new registrations are the month-to-month increase. The Sep->Oct 2019 General cleanup | |
| # removed packages, producing a one-off negative diff that is a removal, not a registration. | |
| reg.new = [missing; diff(reg.packages)] | |
| reg.new = [(!ismissing(x) && x < 0) ? missing : x for x in reg.new] | |
| monthly = select(dropmissing(reg, :new), :month => :date, :new => :n) | |
| yearly = combine(groupby(reg, :year), :new => (v -> sum(skipmissing(v))) => :n) | |
| sort!(yearly, :year) | |
| current_year = year(maximum(reg.month)) | |
| months_elapsed = count(==(current_year), reg.year) | |
| yearly.annualized = [r.year == current_year ? round(Int, r.n * 12 / months_elapsed) : r.n | |
| for r in eachrow(yearly)] | |
| yearly.partial = yearly.year .== current_year | |
| era_start = Date(2025, 2) | |
| era = DataFrame(lo = [era_start], hi = [maximum(monthly.date)], | |
| x = [era_start - Month(1)], y = [maximum(monthly.n) + 5.0]) | |
| fig = Figure(size = (1000, 760)) | |
| era_layers = AoG.data(era) * ( | |
| AoG.mapping(:lo, :hi) * AoG.visual(VSpan, color = (:orange, 0.1)) + | |
| AoG.mapping(:lo) * AoG.visual(VLines, color = (:orange, 0.6), linestyle = :dash) + | |
| AoG.mapping(:x, :y) * AoG.visual(Makie.Text, text = "Claude Code / coding-agent era", | |
| color = :darkorange, align = (:right, :top), fontsize = 12)) | |
| # degree = 1: AoG's default degree-2 loess collapses on Date-typed x, because dates convert | |
| # to a ~1e11 numeric scale that ill-conditions the quadratic term; a local-linear fit is robust. | |
| monthly_layers = AoG.data(monthly) * AoG.mapping(:date => "", :n) * ( | |
| AoG.visual(Scatter, color = (:steelblue, 0.4), markersize = 7) + | |
| AoG.smooth(span = 0.4, degree = 1) * AoG.visual(color = :firebrick)) | |
| AoG.draw!(fig[1, 1], era_layers + monthly_layers; | |
| axis = (; title = "General registry: new packages registered per month", | |
| ylabel = "new packages / month")) | |
| bars = AoG.data(filter(:partial => identity, yearly)) * AoG.mapping(:year, :annualized) * | |
| AoG.visual(BarPlot, color = (:firebrick, 0.2)) + | |
| AoG.data(yearly) * AoG.mapping(:year, :n, color = :partial) * AoG.visual(BarPlot) | |
| grid = AoG.draw!(fig[2, 1], bars, | |
| AoG.scales(Color = (; palette = [:steelblue, :firebrick], legend = false)); | |
| axis = (; title = "New packages per year ($(current_year) annualized; 2018 & 2019 partial/affected by removal)", | |
| xlabel = "year", ylabel = "packages / year", | |
| xticks = minimum(yearly.year):maximum(yearly.year))) | |
| for r in eachrow(yearly) | |
| label = r.partial ? "$(r.n)\n(→$(r.annualized))" : string(r.n) | |
| text!(grid[1].axis, r.year, r.partial ? r.annualized : r.n; | |
| text = label, align = (:center, :bottom), fontsize = 10) | |
| end | |
| save("julia_new_packages.png", fig) | |
| fig |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment