{"spec_id":"swimmer-clinical-timeline","library":"echarts","language":"javascript","code":"// anyplot.ai\n// swimmer-clinical-timeline: Swimmer Plot for Clinical Trial Timelines\n// Library: echarts 5.5.1 | JavaScript 22.22.3\n// Quality: 87/100 | Created: 2026-06-08\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Imprint palette — data colors stable across light/dark; only chrome tokens flip\nconst ARM_A_COLOR   = t.palette[0];  // #009E73 — Arm A (brand green, first series)\nconst ARM_B_COLOR   = t.palette[1];  // #C475FD — Arm B (lavender)\nconst PARTIAL_COLOR = t.palette[3];  // #BD8233 — ochre: partial response\nconst CR_COLOR      = t.palette[2];  // #4467A3 — blue: complete response\nconst PD_COLOR      = t.palette[4];  // #AE3030 — matte red: progressive disease (semantic bad/loss)\nconst ONGOING_COLOR = t.inkSoft;     // theme-adaptive neutral: ongoing patients\n\n// 25 simulated Phase II oncology patients, sorted longest → shortest duration\nconst patients = [\n  { id: \"PT-001\", arm: \"A\", duration: 52, ongoing: true,  events: [{time:  8, type: \"PR\"}, {time: 20, type: \"CR\"}] },\n  { id: \"PT-024\", arm: \"B\", duration: 50, ongoing: true,  events: [{time: 16, type: \"CR\"}] },\n  { id: \"PT-002\", arm: \"A\", duration: 48, ongoing: true,  events: [{time:  6, type: \"PR\"}] },\n  { id: \"PT-014\", arm: \"B\", duration: 46, ongoing: true,  events: [{time: 10, type: \"PR\"}, {time: 24, type: \"CR\"}] },\n  { id: \"PT-003\", arm: \"A\", duration: 44, ongoing: true,  events: [{time: 12, type: \"CR\"}] },\n  { id: \"PT-015\", arm: \"B\", duration: 42, ongoing: true,  events: [{time:  8, type: \"PR\"}] },\n  { id: \"PT-004\", arm: \"A\", duration: 40, ongoing: false, events: [{time:  8, type: \"PR\"}] },\n  { id: \"PT-005\", arm: \"A\", duration: 38, ongoing: true,  events: [{time: 10, type: \"CR\"}] },\n  { id: \"PT-006\", arm: \"A\", duration: 36, ongoing: false, events: [{time: 36, type: \"PD\"}] },\n  { id: \"PT-025\", arm: \"B\", duration: 35, ongoing: false, events: [{time: 12, type: \"PR\"}] },\n  { id: \"PT-016\", arm: \"B\", duration: 34, ongoing: false, events: [{time: 14, type: \"CR\"}] },\n  { id: \"PT-007\", arm: \"A\", duration: 32, ongoing: false, events: [{time:  6, type: \"PR\"}, {time: 32, type: \"PD\"}] },\n  { id: \"PT-017\", arm: \"B\", duration: 30, ongoing: false, events: [{time:  6, type: \"PR\"}, {time: 30, type: \"PD\"}] },\n  { id: \"PT-008\", arm: \"A\", duration: 28, ongoing: false, events: [{time: 28, type: \"PD\"}] },\n  { id: \"PT-018\", arm: \"B\", duration: 26, ongoing: false, events: [{time: 26, type: \"PD\"}] },\n  { id: \"PT-009\", arm: \"A\", duration: 24, ongoing: false, events: [{time:  8, type: \"PR\"}] },\n  { id: \"PT-019\", arm: \"B\", duration: 22, ongoing: false, events: [{time:  8, type: \"PR\"}, {time: 22, type: \"PD\"}] },\n  { id: \"PT-010\", arm: \"A\", duration: 20, ongoing: false, events: [{time: 20, type: \"PD\"}] },\n  { id: \"PT-020\", arm: \"B\", duration: 18, ongoing: false, events: [{time: 18, type: \"PD\"}] },\n  { id: \"PT-011\", arm: \"A\", duration: 16, ongoing: false, events: [{time: 16, type: \"PD\"}] },\n  { id: \"PT-021\", arm: \"B\", duration: 14, ongoing: false, events: [{time: 14, type: \"PD\"}] },\n  { id: \"PT-012\", arm: \"A\", duration: 12, ongoing: false, events: [{time: 12, type: \"PD\"}] },\n  { id: \"PT-022\", arm: \"B\", duration: 10, ongoing: false, events: [{time: 10, type: \"PD\"}] },\n  { id: \"PT-013\", arm: \"A\", duration:  8, ongoing: false, events: [{time:  8, type: \"PD\"}] },\n  { id: \"PT-023\", arm: \"B\", duration:  6, ongoing: false, events: [{time:  6, type: \"PD\"}] },\n];\n\nconst patientIds = patients.map(p => p.id);\n\n// Build scatter data arrays by event type (y = patient array index → category axis)\nconst prData = [], crData = [], pdData = [], ongoingData = [];\npatients.forEach((p, i) => {\n  p.events.forEach(ev => {\n    if (ev.type === \"PR\") prData.push([ev.time, i]);\n    if (ev.type === \"CR\") crData.push([ev.time, i]);\n    if (ev.type === \"PD\") pdData.push([ev.time, i]);\n  });\n  if (p.ongoing) ongoingData.push([p.duration + 1.5, i]);\n});\n\n// --- Init -------------------------------------------------------------------\nconst chart = echarts.init(document.getElementById(\"container\"));\n\n// --- Option -----------------------------------------------------------------\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"swimmer-clinical-timeline · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 22,\n    textStyle: { color: t.ink, fontSize: 27, fontWeight: \"bold\" }\n  },\n  legend: {\n    bottom: 16,\n    left: \"center\",\n    textStyle: { color: t.inkSoft, fontSize: 14 },\n    itemGap: 22,\n    itemWidth: 20,\n    itemHeight: 12,\n    data: [\n      { name: \"Arm A (n=13)\", icon: \"rect\" },\n      { name: \"Arm B (n=12)\", icon: \"rect\" },\n      { name: \"Partial Response\", icon: \"triangle\" },\n      { name: \"Complete Response\", icon: \"diamond\" },\n      { name: \"Progressive Disease\", icon: \"path://M0,8L-7,-4L7,-4Z\" },\n      { name: \"Ongoing\", icon: \"arrow\" },\n    ]\n  },\n  grid: {\n    left: 16,\n    right: 50,\n    top: 100,\n    bottom: 100,\n    containLabel: true\n  },\n  xAxis: {\n    type: \"value\",\n    min: 0,\n    max: 56,\n    interval: 8,\n    name: \"Time on Study (Weeks)\",\n    nameLocation: \"middle\",\n    nameGap: 44,\n    nameTextStyle: { color: t.inkSoft, fontSize: 19 },\n    axisLabel: { color: t.inkSoft, fontSize: 15 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { lineStyle: { color: t.inkSoft } },\n    splitLine: { lineStyle: { color: t.grid } }\n  },\n  yAxis: {\n    type: \"category\",\n    data: patientIds,\n    inverse: true,\n    name: \"Patient\",\n    nameLocation: \"end\",\n    nameGap: 10,\n    nameTextStyle: { color: t.inkSoft, fontSize: 15 },\n    axisLabel: { color: t.inkSoft, fontSize: 14 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { show: false }\n  },\n  series: [\n    // Legend proxy series for treatment arms (empty data — legend appearance only)\n    {\n      name: \"Arm A (n=13)\",\n      type: \"scatter\",\n      symbol: \"rect\",\n      symbolSize: [20, 10],\n      data: [],\n      itemStyle: { color: ARM_A_COLOR }\n    },\n    {\n      name: \"Arm B (n=12)\",\n      type: \"scatter\",\n      symbol: \"rect\",\n      symbolSize: [20, 10],\n      data: [],\n      itemStyle: { color: ARM_B_COLOR }\n    },\n    // Duration bars — per-item arm color via itemStyle\n    {\n      name: \"_bars\",\n      type: \"bar\",\n      barMaxWidth: 16,\n      data: patients.map(p => ({\n        value: p.duration,\n        itemStyle: {\n          color: p.arm === \"A\" ? ARM_A_COLOR : ARM_B_COLOR,\n          borderRadius: [0, 2, 2, 0]\n        }\n      }))\n    },\n    // Partial Response (PR) — upward triangle, ochre\n    {\n      name: \"Partial Response\",\n      type: \"scatter\",\n      symbol: \"triangle\",\n      symbolSize: 13,\n      data: prData,\n      itemStyle: { color: PARTIAL_COLOR },\n      z: 4\n    },\n    // Complete Response (CR) — diamond, blue\n    {\n      name: \"Complete Response\",\n      type: \"scatter\",\n      symbol: \"diamond\",\n      symbolSize: 15,\n      data: crData,\n      itemStyle: { color: CR_COLOR },\n      z: 4\n    },\n    // Progressive Disease (PD) — downward triangle, matte red (semantic bad/disease)\n    {\n      name: \"Progressive Disease\",\n      type: \"scatter\",\n      symbol: \"triangle\",\n      symbolRotate: 180,\n      symbolSize: 13,\n      data: pdData,\n      itemStyle: { color: PD_COLOR },\n      z: 4\n    },\n    // Ongoing — rightward arrow past bar end (symbolRotate 270 = down→right for ECharts arrow)\n    {\n      name: \"Ongoing\",\n      type: \"scatter\",\n      symbol: \"arrow\",\n      symbolRotate: 270,\n      symbolSize: 13,\n      data: ongoingData,\n      itemStyle: { color: ONGOING_COLOR },\n      z: 4\n    }\n  ]\n});\n"}