{"spec_id":"chord-basic","library":"makie","language":"julia","code":"# anyplot.ai\n# chord-basic: Basic Chord Diagram\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 90/100 | Created: 2026-06-17\n\nusing CairoMakie\nusing Colors\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 distinct hue per region; first series is brand green.\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",  # 1 — brand green\n    colorant\"#C475FD\",  # 2 — lavender\n    colorant\"#4467A3\",  # 3 — blue\n    colorant\"#BD8233\",  # 4 — ochre\n    colorant\"#2ABCCD\",  # 6 — cyan\n    colorant\"#954477\",  # 7 — rose\n]\n\n# --- Data: annual migration flows between 6 world regions (thousands of people) ---\nregions = [\"Africa\", \"Asia\", \"Europe\", \"N. America\", \"S. America\", \"Oceania\"]\n\n# flow[i, j] = migrants moving from region i to region j (diagonal is unused)\nflow = Float64[\n    0  320  410  180   60   40    # from Africa\n  280    0  520  640  110  240    # from Asia\n  150  300    0  430  120  160    # from Europe\n   90  210  260    0  300   80    # from N. America\n   70   90  140  350    0   30    # from S. America\n   20   60   90   70   20    0    # from Oceania\n]\n\n# --- Geometry: one arc per region, sized by total throughput (outflow + inflow) ---\n# Each arc reserves a separate foot for every directed flow, so i→j and j→i render\n# as two distinct, source-tinted chords rather than a single merged ribbon.\nn          = length(regions)\ngap        = deg2rad(4.0)                          # angular gap between adjacent arcs\nthroughput = vec(sum(flow, dims = 2)) .+ vec(sum(flow, dims = 1))  # row + column totals\nspan       = (2π - n * gap) .* throughput ./ sum(throughput)\n\n# Each arc starts at the top (π/2) offset by the spans and gaps that precede it.\narc_start = [π / 2 + sum(span[1:i-1]) + (i - 1) * gap for i in 1:n]\narc_stop  = arc_start .+ span\n\n# Walk each arc once, laying down an outgoing foot then an incoming foot per partner.\nout_foot = fill((0.0, 0.0), n, n)   # out_foot[i, j] = sub-arc on i carrying flow i→j\nin_foot  = fill((0.0, 0.0), n, n)   # in_foot[i, j]  = sub-arc on i receiving flow j→i\nfor i in 1:n\n    a = arc_start[i]\n    for j in 1:n\n        w_out = flow[i, j] / throughput[i] * span[i]\n        out_foot[i, j] = (a, a + w_out)\n        a += w_out\n        w_in = flow[j, i] / throughput[i] * span[i]\n        in_foot[i, j] = (a, a + w_in)\n        a += w_in\n    end\nend\n\n# Radii: colored ring sits between r_in and r_out; ribbons attach at r_in.\nr_in  = 0.90\nr_out = 1.00\n\n# Helpers: a point on a circle, and a quadratic Bézier bowed through the centre.\non_ring = (t, rad) -> Point2f(rad * cos(t), rad * sin(t))\nbezier  = (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 = 48)]\n\n# --- Plot ---\nfig = Figure(size = (1200, 1200), backgroundcolor = PAGE_BG)\n\nax = Axis(\n    fig[1, 1];\n    title           = \"chord-basic · julia · makie · anyplot.ai\",\n    titlesize       = 30,\n    titlecolor      = INK,\n    titlegap        = 16,\n    backgroundcolor = PAGE_BG,\n    aspect          = DataAspect(),\n)\nhidespines!(ax)\nhidedecorations!(ax)\nlimits!(ax, -1.5, 1.5, -1.5, 1.5)\n\n# One ribbon per directed flow, tinted by its source. Largest flows are drawn\n# first so the thin tail flows stay readable on top, keeping the core legible.\norder = sort([(i, j) for i in 1:n, j in 1:n if flow[i, j] > 0];\n    by = ij -> flow[ij[1], ij[2]], rev = true)\nfor (i, j) in order\n    a0, a1 = out_foot[i, j]                  # source foot on region i\n    b0, b1 = in_foot[j, i]                   # target foot on region j\n    foot_i = [on_ring(t, r_in) for t in range(a0, a1; length = 12)]\n    foot_j = [on_ring(t, r_in) for t in range(b0, b1; length = 12)]\n    ribbon = vcat(\n        foot_i,\n        bezier(foot_i[end], foot_j[1]),\n        foot_j,\n        bezier(foot_j[end], foot_i[1]),\n    )\n    poly!(ax, ribbon; color = (IMPRINT_PALETTE[i], 0.5), strokewidth = 0)\nend\n\n# Colored arc segment per region (outer ring forward, inner ring back).\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_in) for t in reverse(ang)]\n    poly!(ax, vcat(outer, inner);\n        color = IMPRINT_PALETTE[i], strokecolor = PAGE_BG, strokewidth = 2)\nend\n\n# Region 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.05 * cx, 1.05 * cy;\n        text = regions[i], align = (ha, va), color = INK,\n        fontsize = 24, font = :bold)\nend\n\n# Footnote: clarify the encoding without faking interactivity.\ntext!(ax, 0.0, -1.28;\n    text = \"Arc width ∝ total flow · each ribbon is one direction, tinted by its source region\",\n    align = (:center, :bottom), color = INK_SOFT, fontsize = 17)\n\n# --- Save ---\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}