{"spec_id":"line-retention-cohort","library":"highcharts","language":"javascript","code":"// anyplot.ai\n// line-retention-cohort: User Retention Curve by Cohort\n// Library: highcharts 12.6.0 | JavaScript 22.22.3\n// Quality: 89/100 | Created: 2026-06-20\n//# anyplot-orientation: landscape\n\nconst t = window.ANYPLOT_TOKENS;\n\n// Monthly signup cohorts tracked weekly — 4 cohorts, 13 weeks (week 0 through 12)\n// Shows improving retention as product onboarding was refined over Q1 2025\nconst cohortData = [\n  {\n    label: \"Jan 2025\",\n    n: 1245,\n    color: t.palette[0],\n    opacity: 0.55,\n    lineWidth: 1.5,\n    data: [100, 62, 43, 32, 26, 22, 19, 17, 16, 15, 14, 14, 13],\n  },\n  {\n    label: \"Feb 2025\",\n    n: 1387,\n    color: t.palette[1],\n    opacity: 0.70,\n    lineWidth: 2.0,\n    data: [100, 65, 47, 36, 30, 25, 22, 20, 18, 17, 16, 16, 15],\n  },\n  {\n    label: \"Mar 2025\",\n    n: 1523,\n    color: t.palette[2],\n    opacity: 0.85,\n    lineWidth: 2.5,\n    data: [100, 70, 53, 42, 35, 31, 28, 26, 24, 23, 22, 22, 21],\n  },\n  {\n    label: \"Apr 2025\",\n    n: 1641,\n    color: t.palette[3],\n    opacity: 1.0,\n    lineWidth: 3.0,\n    data: [100, 74, 58, 48, 41, 37, 34, 32, 30, 29, 28, 28, 27],\n  },\n];\n\nHighcharts.chart(\"container\", {\n  chart: {\n    type: \"spline\",\n    backgroundColor: \"transparent\",\n    animation: false,\n    style: { fontFamily: \"inherit\" },\n    marginRight: 60,\n  },\n  credits: { enabled: false },\n  colors: t.palette,\n  title: {\n    text: \"line-retention-cohort · javascript · highcharts · anyplot.ai\",\n    style: { color: t.ink, fontSize: \"22px\", fontWeight: \"600\" },\n  },\n  xAxis: {\n    title: {\n      text: \"Weeks Since Signup\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    tickInterval: 1,\n    min: 0,\n    max: 12,\n    lineColor: t.inkSoft,\n    tickColor: t.inkSoft,\n    labels: { style: { color: t.inkSoft, fontSize: \"14px\" } },\n    gridLineColor: \"transparent\",\n  },\n  yAxis: {\n    title: {\n      text: \"Retention Rate (%)\",\n      style: { color: t.inkSoft, fontSize: \"16px\" },\n    },\n    min: 0,\n    max: 100,\n    tickInterval: 20,\n    gridLineColor: t.grid,\n    labels: {\n      style: { color: t.inkSoft, fontSize: \"14px\" },\n      format: \"{value}%\",\n    },\n    plotLines: [\n      {\n        value: 20,\n        color: t.inkSoft,\n        dashStyle: \"Dash\",\n        width: 1.5,\n        label: {\n          text: \"20% target\",\n          style: { color: t.inkSoft, fontSize: \"14px\" },\n          align: \"left\",\n          x: 8,\n        },\n        zIndex: 3,\n      },\n    ],\n  },\n  legend: {\n    enabled: true,\n    layout: \"horizontal\",\n    align: \"center\",\n    verticalAlign: \"bottom\",\n    itemStyle: { color: t.inkSoft, fontSize: \"14px\" },\n    itemHoverStyle: { color: t.ink },\n  },\n  responsive: {\n    rules: [\n      {\n        condition: { maxWidth: 900 },\n        chartOptions: {\n          legend: {\n            layout: \"vertical\",\n            align: \"right\",\n            verticalAlign: \"middle\",\n          },\n        },\n      },\n    ],\n  },\n  plotOptions: {\n    series: { animation: false },\n    spline: {\n      marker: {\n        enabled: true,\n        symbol: \"circle\",\n      },\n      dataLabels: {\n        enabled: true,\n        crop: false,\n        overflow: \"none\",\n        formatter: function () {\n          if (this.point.index === this.series.data.length - 1) {\n            return `${this.y}%`;\n          }\n          return null;\n        },\n        align: \"left\",\n        x: 8,\n        style: {\n          fontSize: \"12px\",\n          fontWeight: \"600\",\n          textOutline: \"none\",\n        },\n      },\n    },\n  },\n  series: cohortData.map((c) => ({\n    name: `${c.label} (n=${c.n.toLocaleString(\"en-US\")})`,\n    data: c.data,\n    color: c.color,\n    opacity: c.opacity,\n    lineWidth: c.lineWidth,\n    marker: {\n      fillColor: c.color,\n      radius: c.lineWidth >= 3.0 ? 6 : 5,\n    },\n  })),\n});\n"}