{"spec_id":"line-retention-cohort","library":"echarts","language":"javascript","code":"// anyplot.ai\n// line-retention-cohort: User Retention Curve by Cohort\n// Library: echarts 5.5.1 | JavaScript 22.22.3\n// Quality: 87/100 | Created: 2026-06-20\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Monthly sign-up cohorts tracked weekly for 12 weeks (improving retention over time)\nconst cohorts = [\n  { label: \"Jan 2025\", size: 1245, decay: 0.28, floor: 12 },\n  { label: \"Feb 2025\", size: 1389, decay: 0.24, floor: 15 },\n  { label: \"Mar 2025\", size: 1502, decay: 0.21, floor: 18 },\n  { label: \"Apr 2025\", size: 1678, decay: 0.18, floor: 22 },\n  { label: \"May 2025\", size: 1834, decay: 0.15, floor: 26 },\n];\n\nconst weeks = Array.from({ length: 13 }, (_, i) => i); // weeks 0–12\n\nfunction retentionAt(week, decay, floor) {\n  return Math.round((floor + (100 - floor) * Math.exp(-decay * week)) * 10) / 10;\n}\n\n// Older cohorts: thinner + less opaque; newer cohorts: thicker + more prominent\nconst series = cohorts.map(function (c, i) {\n  const lineWidth = 2 + i * 0.5;              // 2 … 4 px\n  const opacity = Math.min(0.45 + i * 0.14, 1); // 0.45 … 1.0\n  const obj = {\n    name: c.label + \" (n=\" + c.size.toLocaleString() + \")\",\n    type: \"line\",\n    data: weeks.map(function (w) { return retentionAt(w, c.decay, c.floor); }),\n    lineStyle: { color: t.palette[i], width: lineWidth, opacity: opacity },\n    itemStyle: { color: t.palette[i] },\n    symbol: \"circle\",\n    symbolSize: 5 + i,\n    smooth: 0.3,\n    endLabel: {\n      show: true,\n      formatter: function () { return c.label; },\n      color: t.palette[i],\n      fontSize: 12,\n      fontWeight: \"bold\",\n      distance: 8,\n    },\n    emphasis: {\n      focus: \"series\",\n      lineStyle: { width: lineWidth + 2, opacity: 1 },\n    },\n  };\n  // Attach 20% benchmark reference line to the newest cohort only\n  if (i === cohorts.length - 1) {\n    obj.markLine = {\n      silent: true,\n      symbol: \"none\",\n      data: [{ yAxis: 20 }],\n      lineStyle: { type: \"dashed\", color: t.inkSoft, width: 1.5 },\n      label: {\n        position: \"insideEndBottom\",\n        formatter: \"20% benchmark\",\n        color: t.inkSoft,\n        fontSize: 14,\n      },\n    };\n  }\n  return obj;\n});\n\nconst chart = echarts.init(document.getElementById(\"container\"));\n\nchart.setOption({\n  animation: false,\n  color: t.palette,\n  backgroundColor: \"transparent\",\n  title: {\n    text: \"line-retention-cohort · javascript · echarts · anyplot.ai\",\n    left: \"center\",\n    top: 22,\n    textStyle: { color: t.ink, fontSize: 22, fontWeight: \"bold\" },\n  },\n  legend: {\n    bottom: 22,\n    orient: \"horizontal\",\n    textStyle: { color: t.inkSoft, fontSize: 14 },\n    icon: \"circle\",\n    itemWidth: 12,\n    itemHeight: 12,\n    itemGap: 28,\n  },\n  tooltip: {\n    trigger: \"axis\",\n    formatter: function (params) {\n      var html = \"<b>Week \" + params[0].axisValue + \"</b><br/>\";\n      params.forEach(function (p) {\n        html += p.marker + \" \" + p.seriesName + \": <b>\" + p.value + \"%</b><br/>\";\n      });\n      return html;\n    },\n  },\n  grid: {\n    left: 90,\n    right: 130,\n    top: 88,\n    bottom: 110,\n  },\n  xAxis: {\n    type: \"category\",\n    name: \"Week since signup\",\n    nameLocation: \"middle\",\n    nameGap: 44,\n    nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n    data: weeks.map(String),\n    axisLabel: { color: t.inkSoft, fontSize: 13 },\n    axisLine: { lineStyle: { color: t.inkSoft } },\n    axisTick: { show: false },\n    splitLine: { show: false },\n  },\n  yAxis: {\n    type: \"value\",\n    name: \"Retention (%)\",\n    nameLocation: \"middle\",\n    nameGap: 60,\n    nameTextStyle: { color: t.inkSoft, fontSize: 14 },\n    min: 0,\n    max: 100,\n    axisLabel: {\n      color: t.inkSoft,\n      fontSize: 13,\n      formatter: \"{value}%\",\n    },\n    axisLine: { show: false },\n    axisTick: { show: false },\n    splitLine: { lineStyle: { color: t.grid, width: 1 } },\n  },\n  series: series,\n});\n"}