Model Lift Chart — Chart.js

A lift curve visualizes how much better a predictive model performs compared to random selection, showing the cumulative lift ratio as you target increasing percentages of the population. It answers the question: "If I target the top X% of predictions, how many times more responders will I capture than random targeting?" This plot is essential for evaluating and comparing classification models in scenarios where targeting efficiency matters.

Model Lift Chart rendered with Chart.js

Renders

JavaScript source (Chart.js)

// anyplot.ai
// lift-curve: Model Lift Chart
// Library: chartjs 4.4.7 | JavaScript 22.23.2
// Quality: 92/100 | Created: 2026-09-05

const t = window.ANYPLOT_TOKENS;

// --- Data (in-memory, deterministic fraud-detection scenario) --------------
// Tiny fixed-seed LCG — the browser has no seeded Math.random().
function makeLcg(seed) {
  let state = seed >>> 0;
  return function lcg() {
    state = (Math.imul(state, 1664525) + 1013904223) >>> 0;
    return state / 4294967296;
  };
}
const rng = makeLcg(42);

const SAMPLE_COUNT = 3000;
const transactions = [];
for (let i = 0; i < SAMPLE_COUNT; i++) {
  // Latent risk, skewed toward zero — most transactions are low risk.
  const risk = Math.pow(rng(), 3);
  const fraudProbability = Math.min(0.95, risk * 0.9 + 0.02);
  const isFraud = rng() < fraudProbability ? 1 : 0;
  // Model score correlates with risk but includes noise (imperfect model).
  const noise = (rng() - 0.5) * 0.3;
  const score = Math.max(0, Math.min(1, risk + noise));
  transactions.push({ isFraud, score });
}

const rankedByScore = transactions.slice().sort((a, b) => b.score - a.score);
const cumulativeFraud = new Array(SAMPLE_COUNT + 1).fill(0);
for (let i = 0; i < SAMPLE_COUNT; i++) {
  cumulativeFraud[i + 1] = cumulativeFraud[i] + rankedByScore[i].isFraud;
}
const totalFraud = cumulativeFraud[SAMPLE_COUNT];
const baselineRate = totalFraud / SAMPLE_COUNT;

const liftPoints = [];
for (let percent = 1; percent <= 100; percent++) {
  const targeted = Math.max(1, Math.round((percent / 100) * SAMPLE_COUNT));
  const responseRate = cumulativeFraud[targeted] / targeted;
  liftPoints.push({ x: percent, y: responseRate / baselineRate });
}

const DECILE_STEP = 10;
// Deciles called out with an explicit numeric label (spec: "actual values at
// key percentiles"); FOCUS_DECILE also gets a larger marker as the chart's
// single focal point.
const CALLOUT_DECILES = [10, 30];
const FOCUS_DECILE = 10;

// --- Mount -------------------------------------------------------------
const canvas = document.createElement("canvas");
document.getElementById("container").appendChild(canvas);

// --- Plugins ---------------------------------------------------------------
// Draws "3.1x at 10%" callouts above the CALLOUT_DECILES markers.
const decileCalloutPlugin = {
  id: "decileCallout",
  afterDatasetsDraw(chart) {
    const meta = chart.getDatasetMeta(0);
    const { ctx, chartArea } = chart;
    ctx.save();
    ctx.font = "bold 15px sans-serif";
    ctx.fillStyle = t.ink;
    ctx.textAlign = "left";
    // Offset up-and-right of the marker: the falling curve approaches from
    // the upper-left and departs to the lower-right, so that quadrant stays clear.
    for (const percent of CALLOUT_DECILES) {
      const point = meta.data[percent - 1];
      if (!point) continue;
      const value = liftPoints[percent - 1].y;
      const labelY = Math.max(point.y - 22, chartArea.top + 14);
      ctx.fillText(`${value.toFixed(1)}x at ${percent}%`, point.x + 14, labelY);
    }
    ctx.restore();
  },
};

// --- Chart ---------------------------------------------------------------
new Chart(canvas, {
  type: "line",
  plugins: [decileCalloutPlugin],
  data: {
    datasets: [
      {
        label: "Model lift",
        data: liftPoints,
        borderColor: t.palette[0],
        backgroundColor: t.palette[0],
        borderWidth: 3,
        pointBackgroundColor: t.palette[0],
        pointBorderColor: t.pageBg,
        pointBorderWidth: 2,
        pointRadius: (ctx) => {
          const percent = ctx.dataIndex + 1;
          if (percent === FOCUS_DECILE) return 8;
          return percent % DECILE_STEP === 0 ? 6 : 0;
        },
        pointHoverRadius: 7,
        tension: 0.15,
        fill: false,
      },
      {
        label: "Random baseline (no lift)",
        data: [
          { x: 0, y: 1 },
          { x: 100, y: 1 },
        ],
        borderColor: t.ink,
        backgroundColor: t.pageBg,
        borderWidth: 2,
        borderDash: [8, 4],
        pointBackgroundColor: t.pageBg,
        pointBorderColor: t.ink,
        pointBorderWidth: 2,
        pointRadius: 0,
        fill: false,
      },
    ],
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    plugins: {
      title: {
        display: true,
        text: "lift-curve · javascript · chartjs · anyplot.ai",
        color: t.ink,
        font: { size: 22 },
        padding: { bottom: 20 },
      },
      legend: {
        position: "top",
        align: "end",
        labels: { color: t.ink, font: { size: 16 }, boxWidth: 24, usePointStyle: true },
      },
    },
    scales: {
      x: {
        type: "linear",
        min: 0,
        max: 100,
        title: { display: true, text: "Population Targeted (%)", color: t.ink, font: { size: 18 } },
        ticks: { color: t.inkSoft, font: { size: 14 }, callback: (v) => `${v}%` },
        grid: { display: false },
      },
      y: {
        beginAtZero: true,
        title: { display: true, text: "Cumulative Lift Ratio", color: t.ink, font: { size: 18 } },
        ticks: { color: t.inkSoft, font: { size: 14 }, callback: (v) => `${v}x` },
        grid: { color: t.grid },
      },
    },
  },
});

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/lift-curve/chartjs/code. Any spec id and library id listed in llms-full.txt fit the same URL shape; every URL below is complete and callable.

{
  "spec_id": "lift-curve",
  "language": "javascript",
  "library": "chartjs",
  "page": "https://anyplot.ai/lift-curve/javascript/chartjs",
  "hub": "https://anyplot.ai/lift-curve",
  "code_json": "https://api.anyplot.ai/specs/lift-curve/chartjs/code",
  "spec_json": "https://api.anyplot.ai/specs/lift-curve",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/lift-curve/javascript/chartjs/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/lift-curve/javascript/chartjs/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/lift-curve/javascript/chartjs/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/lift-curve/javascript/chartjs/plot-dark.html",
  "quality_score": 92.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Model Lift Chart on anyplot.ai.

Other implementations