User Retention Curve by Cohort — Highcharts

A line chart showing the percentage of retained users over time since signup, with separate curves for different cohorts. All curves start at 100% at time zero and typically exhibit exponential decay, revealing how well a product retains users over their lifecycle. By overlaying multiple cohorts, teams can visually compare whether retention is improving or degrading across signup periods.

User Retention Curve by Cohort rendered with Highcharts

JavaScript source (Highcharts)

// anyplot.ai
// line-retention-cohort: User Retention Curve by Cohort
// Library: highcharts 12.6.0 | JavaScript 22.22.3
// Quality: 89/100 | Created: 2026-06-20
//# anyplot-orientation: landscape

const t = window.ANYPLOT_TOKENS;

// Monthly signup cohorts tracked weekly — 4 cohorts, 13 weeks (week 0 through 12)
// Shows improving retention as product onboarding was refined over Q1 2025
const cohortData = [
  {
    label: "Jan 2025",
    n: 1245,
    color: t.palette[0],
    opacity: 0.55,
    lineWidth: 1.5,
    data: [100, 62, 43, 32, 26, 22, 19, 17, 16, 15, 14, 14, 13],
  },
  {
    label: "Feb 2025",
    n: 1387,
    color: t.palette[1],
    opacity: 0.70,
    lineWidth: 2.0,
    data: [100, 65, 47, 36, 30, 25, 22, 20, 18, 17, 16, 16, 15],
  },
  {
    label: "Mar 2025",
    n: 1523,
    color: t.palette[2],
    opacity: 0.85,
    lineWidth: 2.5,
    data: [100, 70, 53, 42, 35, 31, 28, 26, 24, 23, 22, 22, 21],
  },
  {
    label: "Apr 2025",
    n: 1641,
    color: t.palette[3],
    opacity: 1.0,
    lineWidth: 3.0,
    data: [100, 74, 58, 48, 41, 37, 34, 32, 30, 29, 28, 28, 27],
  },
];

Highcharts.chart("container", {
  chart: {
    type: "spline",
    backgroundColor: "transparent",
    animation: false,
    style: { fontFamily: "inherit" },
    marginRight: 60,
  },
  credits: { enabled: false },
  colors: t.palette,
  title: {
    text: "line-retention-cohort · javascript · highcharts · anyplot.ai",
    style: { color: t.ink, fontSize: "22px", fontWeight: "600" },
  },
  xAxis: {
    title: {
      text: "Weeks Since Signup",
      style: { color: t.inkSoft, fontSize: "16px" },
    },
    tickInterval: 1,
    min: 0,
    max: 12,
    lineColor: t.inkSoft,
    tickColor: t.inkSoft,
    labels: { style: { color: t.inkSoft, fontSize: "14px" } },
    gridLineColor: "transparent",
  },
  yAxis: {
    title: {
      text: "Retention Rate (%)",
      style: { color: t.inkSoft, fontSize: "16px" },
    },
    min: 0,
    max: 100,
    tickInterval: 20,
    gridLineColor: t.grid,
    labels: {
      style: { color: t.inkSoft, fontSize: "14px" },
      format: "{value}%",
    },
    plotLines: [
      {
        value: 20,
        color: t.inkSoft,
        dashStyle: "Dash",
        width: 1.5,
        label: {
          text: "20% target",
          style: { color: t.inkSoft, fontSize: "14px" },
          align: "left",
          x: 8,
        },
        zIndex: 3,
      },
    ],
  },
  legend: {
    enabled: true,
    layout: "horizontal",
    align: "center",
    verticalAlign: "bottom",
    itemStyle: { color: t.inkSoft, fontSize: "14px" },
    itemHoverStyle: { color: t.ink },
  },
  responsive: {
    rules: [
      {
        condition: { maxWidth: 900 },
        chartOptions: {
          legend: {
            layout: "vertical",
            align: "right",
            verticalAlign: "middle",
          },
        },
      },
    ],
  },
  plotOptions: {
    series: { animation: false },
    spline: {
      marker: {
        enabled: true,
        symbol: "circle",
      },
      dataLabels: {
        enabled: true,
        crop: false,
        overflow: "none",
        formatter: function () {
          if (this.point.index === this.series.data.length - 1) {
            return `${this.y}%`;
          }
          return null;
        },
        align: "left",
        x: 8,
        style: {
          fontSize: "12px",
          fontWeight: "600",
          textOutline: "none",
        },
      },
    },
  },
  series: cohortData.map((c) => ({
    name: `${c.label} (n=${c.n.toLocaleString("en-US")})`,
    data: c.data,
    color: c.color,
    opacity: c.opacity,
    lineWidth: c.lineWidth,
    marker: {
      fillColor: c.color,
      radius: c.lineWidth >= 3.0 ? 6 : 5,
    },
  })),
});

Part of User Retention Curve by Cohort on anyplot.ai.

Other implementations