{"spec_id":"swimmer-clinical-timeline","library":"makie","language":"julia","code":"# anyplot.ai\n# swimmer-clinical-timeline: Swimmer Plot for Clinical Trial Timelines\n# Library: makie 0.22.10 | Julia 1.11.9\n# Quality: 92/100 | Created: 2026-06-08\n\nusing CairoMakie\nusing Colors\nusing Random\n\nRandom.seed!(42)\n\n# Theme tokens\nconst THEME       = get(ENV, \"ANYPLOT_THEME\", \"light\")\nconst PAGE_BG     = THEME == \"light\" ? colorant\"#FAF8F1\" : colorant\"#1A1A17\"\nconst ELEVATED_BG = THEME == \"light\" ? colorant\"#FFFDF6\" : colorant\"#242420\"\nconst INK         = THEME == \"light\" ? colorant\"#1A1A17\" : colorant\"#F0EFE8\"\nconst INK_SOFT    = THEME == \"light\" ? colorant\"#4A4A44\" : colorant\"#B8B7B0\"\nconst INK_MUTED   = THEME == \"light\" ? colorant\"#6B6A63\" : colorant\"#A8A79F\"\n\n# Imprint categorical palette — hybrid-v3 canonical order\nconst IMPRINT_PALETTE = [\n    colorant\"#009E73\",  # 1 — Arm A / Experimental (brand green)\n    colorant\"#C475FD\",  # 2 — Arm B / Control (lavender)\n    colorant\"#4467A3\",  # 3 — Partial Response (blue)\n    colorant\"#BD8233\",  # 4 — Complete Response (ochre)\n    colorant\"#AE3030\",  # 5 — Progressive Disease (matte red)\n]\nconst ANYPLOT_AMBER = colorant\"#DDCC77\"  # Adverse Event — warning anchor (outside categorical pool)\n\n# Data: Phase II oncology trial — anti-PD-1 + chemo (Arm A) vs. chemo alone (Arm B)\nn_patients = 25\ntreatment_arms = vcat(fill(\"Arm A\", 13), fill(\"Arm B\", 12))\nRandom.shuffle!(treatment_arms)\n\ndurations = [\n    treatment_arms[i] == \"Arm A\" ? 10.0 + rand() * 42.0 : 4.0 + rand() * 28.0\n    for i in 1:n_patients\n]\n\nis_ongoing = [\n    treatment_arms[i] == \"Arm A\" ? rand() < 0.46 : rand() < 0.25\n    for i in 1:n_patients\n]\n\n# Sort ascending by duration so longest bar appears at top of chart\nperm           = sortperm(durations)\ndurations      = durations[perm]\ntreatment_arms = treatment_arms[perm]\nis_ongoing     = is_ongoing[perm]\npatient_labels = [\"PT-$(lpad(p, 3, '0'))\" for p in perm]\n\n# Clinical events: (patient_idx, time_weeks, event_type)\nevent_data = Tuple{Int, Float64, String}[]\n\nfor i in 1:n_patients\n    dur = durations[i]\n    arm = treatment_arms[i]\n\n    # Partial response — earlier in treatment, higher rate in Arm A\n    if dur > 6.0 && rand() < (arm == \"Arm A\" ? 0.65 : 0.40)\n        t = dur * (0.15 + rand() * 0.35)\n        push!(event_data, (i, t, \"partial_response\"))\n    end\n\n    # Complete response — sustained duration required, predominantly Arm A\n    if dur > 22.0 && rand() < (arm == \"Arm A\" ? 0.38 : 0.10)\n        t = dur * (0.42 + rand() * 0.28)\n        push!(event_data, (i, t, \"complete_response\"))\n    end\n\n    # Progressive disease — usually near end, more common in control arm\n    if !is_ongoing[i] && rand() < (arm == \"Arm B\" ? 0.58 : 0.22)\n        t = min(dur * (0.68 + rand() * 0.28), dur * 0.97)\n        push!(event_data, (i, t, \"progressive_disease\"))\n    end\n\n    # Adverse event — random, any patient\n    if rand() < 0.38\n        t = dur * (0.08 + rand() * 0.65)\n        push!(event_data, (i, t, \"adverse_event\"))\n    end\nend\n\n# Plot\nfig = Figure(\n    size            = (1600, 900),\n    fontsize        = 14,\n    backgroundcolor = PAGE_BG,\n)\n\ntitle_str    = \"swimmer-clinical-timeline · julia · makie · anyplot.ai\"\nn_chars      = length(title_str)\nratio        = n_chars > 67 ? 67 / n_chars : 1.0\ntitle_sz     = round(Int, 20 * ratio)\n\nax = Axis(\n    fig[1, 1];\n    title              = title_str,\n    titlesize          = title_sz,\n    titlecolor         = INK,\n    xlabel             = \"Time on Study (weeks)\",\n    xlabelsize         = 13,\n    xlabelcolor        = INK,\n    xticklabelsize     = 11,\n    yticklabelsize     = 10,\n    xticklabelcolor    = INK_SOFT,\n    yticklabelcolor    = INK_SOFT,\n    xtickcolor         = INK_SOFT,\n    ytickcolor         = INK_SOFT,\n    backgroundcolor    = PAGE_BG,\n    topspinevisible    = false,\n    rightspinevisible  = false,\n    leftspinecolor     = INK_SOFT,\n    bottomspinecolor   = INK_SOFT,\n    xgridvisible       = true,\n    ygridvisible       = false,\n    xgridcolor         = RGBAf(INK.r, INK.g, INK.b, 0.12),\n    yticks             = (1:n_patients, patient_labels),\n)\n\n# Horizontal bars — one per patient, colored by treatment arm\nbar_h = 0.52\nfor i in 1:n_patients\n    c = treatment_arms[i] == \"Arm A\" ? IMPRINT_PALETTE[1] : IMPRINT_PALETTE[2]\n    poly!(ax, Rect2f(0.0, i - bar_h / 2, durations[i], bar_h);\n        color       = (c, 0.78),\n        strokewidth = 0.5,\n        strokecolor = c,\n    )\nend\n\n# Rightward arrows for patients still on treatment at data cutoff\narrow_len = 2.8\nfor i in 1:n_patients\n    if is_ongoing[i]\n        c = treatment_arms[i] == \"Arm A\" ? IMPRINT_PALETTE[1] : IMPRINT_PALETTE[2]\n        arrows!(ax, [durations[i]], [Float64(i)], [arrow_len], [0.0];\n            color     = c,\n            arrowsize = 12,\n            linewidth = 2.5,\n        )\n    end\nend\n\n# Event markers — one scatter call per event type\nevt_styles = Dict(\n    \"partial_response\"    => (IMPRINT_PALETTE[3], :utriangle, 13),\n    \"complete_response\"   => (IMPRINT_PALETTE[4], :star5,     15),\n    \"progressive_disease\" => (IMPRINT_PALETTE[5], :dtriangle, 13),\n    \"adverse_event\"       => (ANYPLOT_AMBER,       :xcross,    12),\n)\n\nfor evt_type in (\"partial_response\", \"complete_response\", \"progressive_disease\", \"adverse_event\")\n    subset = filter(e -> e[3] == evt_type, event_data)\n    isempty(subset) && continue\n    xs = [e[2] for e in subset]\n    ys = [Float64(e[1]) for e in subset]\n    c, mk, sz = evt_styles[evt_type]\n    scatter!(ax, xs, ys;\n        color       = c,\n        marker      = mk,\n        markersize  = sz,\n        strokewidth = 1.0,\n        strokecolor = PAGE_BG,\n    )\nend\n\n# Vertical reference line at week 24 — common Phase II landmark assessment\nvlines!(ax, [24.0]; color = (INK_MUTED, 0.45), linestyle = :dash, linewidth = 1.5)\ntext!(ax, 24.4, n_patients + 0.55; text = \"Wk 24\", fontsize = 9, color = INK_MUTED, align = (:left, :top))\n\nmax_dur = maximum(durations)\nxlims!(ax, -0.8, max_dur + 7.0)\nylims!(ax, 0.25, n_patients + 0.75)\n\n# Legend\nelem_a  = PolyElement(polycolor = (IMPRINT_PALETTE[1], 0.78), polystrokecolor = IMPRINT_PALETTE[1], polystrokewidth = 0.5)\nelem_b  = PolyElement(polycolor = (IMPRINT_PALETTE[2], 0.78), polystrokecolor = IMPRINT_PALETTE[2], polystrokewidth = 0.5)\nelem_pr = MarkerElement(color = IMPRINT_PALETTE[3], marker = :utriangle, markersize = 12, strokecolor = PAGE_BG, strokewidth = 1)\nelem_cr = MarkerElement(color = IMPRINT_PALETTE[4], marker = :star5,     markersize = 12, strokecolor = PAGE_BG, strokewidth = 1)\nelem_pd = MarkerElement(color = IMPRINT_PALETTE[5], marker = :dtriangle, markersize = 12, strokecolor = PAGE_BG, strokewidth = 1)\nelem_ae = MarkerElement(color = ANYPLOT_AMBER,       marker = :xcross,    markersize = 12, strokecolor = PAGE_BG, strokewidth = 1)\n\nLegend(fig[1, 2],\n    [elem_a, elem_b, elem_pr, elem_cr, elem_pd, elem_ae],\n    [\"Arm A — Experimental\", \"Arm B — Control\", \"Partial Response\", \"Complete Response\", \"Progressive Disease\", \"Adverse Event\"],\n    \"Treatment & Events\";\n    framevisible      = true,\n    framecolor        = INK_SOFT,\n    backgroundcolor   = ELEVATED_BG,\n    labelcolor        = INK,\n    titlecolor        = INK,\n    labelfontsize   = 10,\n    titlefontsize   = 11,\n    patchsize       = (22, 14),\n    rowgap          = 4,\n)\n\ncolsize!(fig.layout, 1, Relative(0.78))\n\n# Save\nsave(\"plot-$(THEME).png\", fig; px_per_unit = 2)\n"}