{"spec_id":"swimmer-clinical-timeline","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nswimmer-clinical-timeline: Swimmer Plot for Clinical Trial Timelines\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-06-08\n\"\"\"\n\nimport os\n\nimport matplotlib.patches as mpatches\nimport matplotlib.patheffects as pe\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n\n# Theme tokens — Imprint palette chrome mapping\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint categorical palette — 8 hues, canonical order\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nANYPLOT_AMBER = \"#DDCC77\"  # semantic anchor: warning / caution\n\n# Data — Phase II oncology trial: 25 patients across Arm A (n=13) and Arm B (n=12)\nnp.random.seed(42)\n\nn_patients = 25\npatient_ids = [f\"PT-{i + 1:03d}\" for i in range(n_patients)]\narms = np.array([\"Arm A\"] * 13 + [\"Arm B\"] * 12)\ndurations = np.concatenate([np.random.uniform(4, 48, 13), np.random.uniform(6, 44, 12)])\ndurations = np.round(durations, 1)\n\nongoing = np.array([False] * n_patients)\nfor idx in [0, 3, 7, 14, 18, 22]:\n    ongoing[idx] = True\n\nevent_markers = {\n    \"partial_response\": (\"^\", IMPRINT_PALETTE[3], \"Partial Response\", 120),\n    \"complete_response\": (\"*\", IMPRINT_PALETTE[2], \"Complete Response\", 220),\n    \"progressive_disease\": (\"D\", IMPRINT_PALETTE[4], \"Progressive Disease\", 110),\n    \"adverse_event\": (\"X\", ANYPLOT_AMBER, \"Adverse Event\", 100),\n}\n\nevents = []\nfor i in range(n_patients):\n    patient_events = []\n    dur = durations[i]\n    if dur > 8:\n        pr_time = np.random.uniform(4, min(dur * 0.5, 12))\n        patient_events.append((\"partial_response\", round(pr_time, 1)))\n        if dur > 20 and np.random.random() > 0.5:\n            cr_time = np.random.uniform(pr_time + 4, min(dur * 0.8, dur - 2))\n            patient_events.append((\"complete_response\", round(cr_time, 1)))\n    if not ongoing[i] and dur > 12 and np.random.random() > 0.4:\n        pd_time = np.random.uniform(dur * 0.6, dur - 1)\n        patient_events.append((\"progressive_disease\", round(pd_time, 1)))\n    if dur > 10 and np.random.random() > 0.75:\n        ae_time = np.random.uniform(2, min(dur * 0.7, dur - 1))\n        patient_events.append((\"adverse_event\", round(ae_time, 1)))\n    events.append(patient_events)\n\nsort_idx = np.argsort(durations)\npatient_ids = [patient_ids[i] for i in sort_idx]\ndurations = durations[sort_idx]\narms = arms[sort_idx]\nongoing = ongoing[sort_idx]\nevents = [events[i] for i in sort_idx]\n\n# Arm colors — Imprint positions 1 (brand green) and 2 (lavender)\narm_colors = {\"Arm A\": IMPRINT_PALETTE[0], \"Arm B\": IMPRINT_PALETTE[1]}\n\n# Plot — landscape 3200×1800 px (figsize=(8,4.5) × dpi=400)\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nfor i in range(n_patients):\n    color = arm_colors[arms[i]]\n    rect = mpatches.FancyBboxPatch(\n        (0, i - 0.3),\n        durations[i],\n        0.6,\n        boxstyle=mpatches.BoxStyle.Round(pad=0, rounding_size=0.1),\n        facecolor=color,\n        alpha=0.82,\n        edgecolor=PAGE_BG,\n        linewidth=0.4,\n    )\n    ax.add_patch(rect)\n\n    if ongoing[i]:\n        ax.annotate(\n            \"\",\n            xy=(durations[i] + 1.2, i),\n            xytext=(durations[i], i),\n            arrowprops={\"arrowstyle\": \"-|>\", \"color\": color, \"lw\": 2.0, \"mutation_scale\": 12},\n        )\n\n    for event_type, event_time in events[i]:\n        marker, mcolor, _, msize = event_markers[event_type]\n        ax.scatter(\n            event_time,\n            i,\n            marker=marker,\n            color=mcolor,\n            s=msize,\n            zorder=5,\n            edgecolors=PAGE_BG,\n            linewidth=0.7,\n            path_effects=[pe.withStroke(linewidth=1.8, foreground=PAGE_BG)],\n        )\n\n# Data cutoff line\nmax_dur = durations.max()\nax.axvline(x=max_dur + 0.5, color=INK_MUTED, linestyle=\"--\", linewidth=0.8, alpha=0.5)\nax.text(\n    max_dur + 0.3,\n    n_patients - 0.8,\n    \"Data cutoff\",\n    fontsize=7,\n    color=INK_MUTED,\n    ha=\"right\",\n    va=\"top\",\n    fontstyle=\"italic\",\n    rotation=90,\n)\n\n# Style\ntitle = \"swimmer-clinical-timeline · python · matplotlib · anyplot.ai\"\nn_chars = len(title)\ntitle_fontsize = max(8, round(12 * 67 / n_chars)) if n_chars > 67 else 12\n\nax.set_yticks(range(n_patients))\nax.set_yticklabels(patient_ids, fontsize=7, fontfamily=\"monospace\")\nax.set_xlabel(\"Time on Study (weeks)\", fontsize=10, color=INK)\nax.set_ylabel(\"Patient\", fontsize=10, color=INK)\nax.set_title(title, fontsize=title_fontsize, fontweight=\"medium\", color=INK, pad=8)\nax.tick_params(axis=\"x\", labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT)\nax.tick_params(axis=\"y\", labelsize=7, colors=INK_SOFT, labelcolor=INK_SOFT)\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nfor s in (\"left\", \"bottom\"):\n    ax.spines[s].set_color(INK_SOFT)\n    ax.spines[s].set_linewidth(0.5)\nax.xaxis.grid(True, alpha=0.12, linewidth=0.6, color=INK)\nax.set_xlim(0, None)\nax.set_ylim(-0.8, n_patients - 0.2)\n\n# Legend\narm_a_patch = mpatches.Patch(color=arm_colors[\"Arm A\"], alpha=0.85, label=\"Arm A\")\narm_b_patch = mpatches.Patch(color=arm_colors[\"Arm B\"], alpha=0.85, label=\"Arm B\")\nlegend_handles = [arm_a_patch, arm_b_patch]\nfor _etype, (marker, mcolor, label, _) in event_markers.items():\n    legend_handles.append(\n        plt.Line2D(\n            [0],\n            [0],\n            marker=marker,\n            color=\"w\",\n            markerfacecolor=mcolor,\n            markersize=7,\n            label=label,\n            markeredgecolor=PAGE_BG,\n            markeredgewidth=0.5,\n            linestyle=\"None\",\n        )\n    )\nlegend_handles.append(\n    plt.Line2D(\n        [0], [0], marker=\">\", color=\"w\", markerfacecolor=INK_MUTED, markersize=6, label=\"Ongoing\", linestyle=\"None\"\n    )\n)\n\nleg = ax.legend(handles=legend_handles, fontsize=8, loc=\"lower right\", framealpha=0.9, borderpad=0.8)\nleg.get_frame().set_facecolor(ELEVATED_BG)\nleg.get_frame().set_edgecolor(INK_SOFT)\nleg.get_frame().set_linewidth(0.6)\nplt.setp(leg.get_texts(), color=INK_SOFT)\n\nfig.subplots_adjust(left=0.13, right=0.97, top=0.93, bottom=0.11)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}