{"spec_id":"circos-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# circos-basic: Circos Plot\n# Library: makie 0.21.9 | Julia 1.11.9\n# Quality: 94/100 | Created: 2026-09-04\n\nusing CairoMakie\nusing Makie\nusing Colors\nusing Random\n\n# --- Theme tokens (see prompts/default-style-guide.md \"Theme-adaptive Chrome\") ---\nconst THEME    = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG  = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst INK      = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\n\n# Imprint palette — one hue per module, first series is brand green.\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\", colorant\"#C475FD\", colorant\"#4467A3\", colorant\"#BD8233\",\n    colorant\"#AE3030\", colorant\"#2ABCCD\", colorant\"#954477\", colorant\"#99B314\",\n]\n\n# --- Data: request volume between backend service modules over one day ---\nRandom.seed!(42)\nmodules = [\"Frontend\", \"API\", \"Auth\", \"Cache\", \"Queue\", \"Database\", \"Logging\", \"Analytics\"]\n\nedges = [\n    (\"Frontend\", \"API\"), (\"Frontend\", \"Auth\"), (\"API\", \"Auth\"), (\"API\", \"Database\"),\n    (\"API\", \"Cache\"), (\"API\", \"Queue\"), (\"Auth\", \"Database\"), (\"Auth\", \"Cache\"),\n    (\"Cache\", \"Database\"), (\"Queue\", \"Database\"), (\"Queue\", \"Logging\"),\n    (\"Database\", \"Logging\"), (\"Analytics\", \"Database\"), (\"Analytics\", \"API\"),\n    (\"Analytics\", \"Logging\"), (\"Cache\", \"Logging\"),\n]\nweight = rand(8:60, length(edges))  # thousands of calls / day\n\n# --- Geometry: one arc per module, sized by total call volume touching it ---\nn            = length(modules)\nidx          = Dict(m => i for (i, m) in enumerate(modules))\ngap          = deg2rad(3.0)\ndegree       = zeros(Int, n)                 # number of distinct links per module (track data)\nmod_total    = zeros(Float64, n)              # sum of call volume touching each module\nfor (k, (s, t)) in enumerate(edges)\n    mod_total[idx[s]] += weight[k]\n    mod_total[idx[t]] += weight[k]\n    degree[idx[s]]    += 1\n    degree[idx[t]]    += 1\nend\n\navailable = 2π - n * gap\nper_unit  = available / sum(mod_total)\nspan      = mod_total .* per_unit\narc_start = [π / 2 + sum(span[1:i-1]) + (i - 1) * gap for i in 1:n]\narc_stop  = arc_start .+ span\n\n# Sub-divide each module's arc into one foot per edge touching it (in edge order).\nfoot = Dict{Tuple{Int, Int}, Tuple{Float64, Float64}}()  # (edge_index, module_index) -> (a0, a1)\nsub_cursor = copy(arc_start)\nfor (k, (s, t)) in enumerate(edges)\n    si, ti = idx[s], idx[t]\n    w = weight[k] * per_unit\n    foot[(k, si)] = (sub_cursor[si], sub_cursor[si] + w)\n    sub_cursor[si] += w\n    foot[(k, ti)] = (sub_cursor[ti], sub_cursor[ti] + w)\n    sub_cursor[ti] += w\nend\n\n# Radii, outer to inner: module ring, degree track, ribbon attachment point.\nr_out         = 1.00\nr_band_inner  = 0.93\nr_track_outer = 0.88\nr_track_inner = 0.62\nr_ribbon      = 0.60\n\non_ring = (t, rad) -> Point2f(rad * cos(t), rad * sin(t))\n\n# Custom Makie recipe (Makie.@recipe): a circos chord is its own plot type,\n# not a bare poly! call — it owns its point-generation pipeline behind\n# `circoschord!`, in the same spirit as Makie's built-in recipes (e.g. band!).\n@recipe(CircosChord) do scene\n    Theme(a0 = 0.0, a1 = 0.0, b0 = 0.0, b1 = 0.0, radius = 0.6, color = :gray)\nend\n\nfunction Makie.plot!(chord::CircosChord)\n    @extract(chord, (a0, a1, b0, b1, radius, color))\n    points = lift(chord, a0, a1, b0, b1, radius) do a0, a1, b0, b1, r\n        curve(p0, p2) = [Point2f((1 - t)^2 * p0[1] + t^2 * p2[1],\n                                  (1 - t)^2 * p0[2] + t^2 * p2[2]) for t in range(0, 1; length = 40)]\n        edge_i = [on_ring(t, r) for t in range(a0, a1; length = 10)]\n        edge_j = [on_ring(t, r) for t in range(b0, b1; length = 10)]\n        vcat(edge_i, curve(edge_i[end], edge_j[1]), edge_j, curve(edge_j[end], edge_i[1]))\n    end\n    poly!(chord, points; color = color, strokewidth = 0)\n    chord\nend\n\ntitle       = \"Service Call Volume · circos-basic · julia · makie · anyplot.ai\"\ntitle_ratio = length(title) > 67 ? 67 / length(title) : 1.0\ntitlesize   = max(14, round(Int, 20 * title_ratio))\n\n# --- Plot ---\nfig = Figure(size = (1200, 1200), backgroundcolor = PAGE_BG)\n\nax = Axis(\n    fig[1, 1];\n    title           = title,\n    titlesize       = titlesize,\n    titlecolor      = INK,\n    titlegap        = 16,\n    backgroundcolor = PAGE_BG,\n    aspect          = DataAspect(),\n)\nhidespines!(ax)\nhidedecorations!(ax)\nlimits!(ax, -1.55, 1.55, -1.5, 1.2)\n\n# Ribbons: one per edge, tinted by its source module. Drawn grouped by source\n# module (hue family) so overlapping regions blend within a family rather\n# than criss-crossing many unrelated hues; heavier flows within a family\n# drawn first so thin flows stay visible on top.\norder = sort(1:length(edges); by = k -> (idx[edges[k][1]], -weight[k]))\nfor k in order\n    s, t   = edges[k]\n    si, ti = idx[s], idx[t]\n    a0, a1 = foot[(k, si)]\n    b0, b1 = foot[(k, ti)]\n    circoschord!(ax; a0 = a0, a1 = a1, b0 = b0, b1 = b1, radius = r_ribbon,\n        color = (IMPRINT_PALETTE[si], 0.4))\nend\n\n# Degree track: inner bar per module, height proportional to number of links.\nmax_degree = maximum(degree)\nfor i in 1:n\n    ang    = range(arc_start[i], arc_stop[i]; length = 24)\n    r_bar  = r_track_inner + (r_track_outer - r_track_inner) * degree[i] / max_degree\n    outer  = [on_ring(t, r_bar) for t in ang]\n    inner  = [on_ring(t, r_track_inner) for t in reverse(ang)]\n    poly!(ax, vcat(outer, inner); color = (IMPRINT_PALETTE[i], 0.65), strokewidth = 0)\nend\n\n# Module ring: colored arc segment per module.\nfor i in 1:n\n    ang   = range(arc_start[i], arc_stop[i]; length = 64)\n    outer = [on_ring(t, r_out) for t in ang]\n    inner = [on_ring(t, r_band_inner) for t in reverse(ang)]\n    poly!(ax, vcat(outer, inner);\n        color = IMPRINT_PALETTE[i], strokecolor = PAGE_BG, strokewidth = 2)\nend\n\n# Module labels, placed just outside the ring and anchored by quadrant.\nfor i in 1:n\n    mid    = (arc_start[i] + arc_stop[i]) / 2\n    cx, cy = cos(mid), sin(mid)\n    ha = abs(cx) < 0.35 ? :center : (cx > 0 ? :left : :right)\n    va = abs(cy) < 0.35 ? :center : (cy > 0 ? :bottom : :top)\n    text!(ax, 1.06 * cx, 1.06 * cy;\n        text = modules[i], align = (ha, va), color = INK, fontsize = 22, font = :bold)\nend\n\n# Footnote: clarify the encoding without faking interactivity.\ntext!(ax, 0.0, -1.42;\n    text = \"Ribbon width ∝ call volume · inner bar ∝ number of linked modules\",\n    align = (:center, :bottom), color = INK_SOFT, fontsize = 16)\n\n# --- Save ---\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}