Partial Dependence Plot — Chart.js

A partial dependence plot (PDP) showing the marginal effect of one feature on the predicted outcome of a machine learning model. The plot displays how predictions change as a feature varies across its range, while averaging over the effects of all other features. This visualization is essential for understanding the relationship between individual features and model predictions in interpretable machine learning.

Partial Dependence Plot rendered with Chart.js

Renders

JavaScript source (Chart.js)

// anyplot.ai
// pdp-basic: Partial Dependence Plot
// Library: chartjs 4.4.7 | JavaScript 22.23.2
// Quality: 94/100 | Created: 2026-09-05

const t = window.ANYPLOT_TOKENS;

// --- Data (in-memory, deterministic) ----------------------------------------
// Partial dependence of predicted house price on living-area square footage,
// as if extracted from a GradientBoostingRegressor. The effect is centered at
// zero at the median square footage so the curve reads as a relative lift.
const GRID_POINTS = 60;
const SQFT_MIN = 500;
const SQFT_MAX = 4000;
const SQFT_MEDIAN = 1500;

const featureValues = Array.from(
  { length: GRID_POINTS },
  (_, i) => SQFT_MIN + (i * (SQFT_MAX - SQFT_MIN)) / (GRID_POINTS - 1),
);

// Diminishing-returns effect (log-shaped), in thousands of dollars, zeroed at
// the median so the plot shows relative lift rather than absolute price.
const partialDependence = featureValues.map(
  (sqft) => 62 * Math.log(sqft / SQFT_MEDIAN),
);

// Uncertainty widens away from the bulk of the training data (fewer nearby
// samples at the tails), a standard PDP confidence-band shape.
const bandHalfWidth = featureValues.map((sqft) => {
  const distance = Math.abs(sqft - SQFT_MEDIAN) / (SQFT_MAX - SQFT_MIN);
  return 4 + 34 * distance * distance;
});
const upperBound = partialDependence.map((pd, i) => pd + bandHalfWidth[i]);
const lowerBound = partialDependence.map((pd, i) => pd - bandHalfWidth[i]);

// Rug: a small fixed-seed LCG stands in for the square-footage distribution
// of the training sample, clustered around the median with a long right tail.
let seed = 42;
const lcg = () => {
  seed = (seed * 1103515245 + 12345) % 2147483648;
  return seed / 2147483648;
};
const trainingSqft = Array.from({ length: 90 }, () => {
  const u1 = lcg() || 1e-9;
  const u2 = lcg();
  const gaussian = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2);
  const sample = SQFT_MEDIAN + gaussian * 420 + 260;
  return Math.min(SQFT_MAX - 20, Math.max(SQFT_MIN + 20, sample));
}).sort((a, b) => a - b);
const rugPoints = trainingSqft.map((sqft) => ({ x: sqft, y: 0.08 }));

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

// --- Chart --------------------------------------------------------------------
const title = "House Price vs. Square Footage · pdp-basic · javascript · chartjs · anyplot.ai";

// Custom plugin: callout the zero-crossing (median sq ft, where partial
// dependence is centered at $0) so the "centered" framing is explicit rather
// than only inferable from the curve shape.
const zeroCrossingCallout = {
  id: "zeroCrossingCallout",
  afterDatasetsDraw(chart) {
    const { ctx, chartArea, scales } = chart;
    const px = scales.x.getPixelForValue(SQFT_MEDIAN);
    const py = scales.y.getPixelForValue(0);
    ctx.save();
    ctx.setLineDash([4, 4]);
    ctx.strokeStyle = t.inkSoft;
    ctx.lineWidth = 1.5;
    ctx.beginPath();
    ctx.moveTo(px, chartArea.top);
    ctx.lineTo(px, chartArea.bottom);
    ctx.stroke();
    ctx.setLineDash([]);
    ctx.beginPath();
    ctx.arc(px, py, 6, 0, Math.PI * 2);
    ctx.fillStyle = t.palette[0];
    ctx.fill();
    ctx.lineWidth = 2;
    ctx.strokeStyle = t.pageBg;
    ctx.stroke();
    ctx.fillStyle = t.ink;
    ctx.font = "600 15px sans-serif";
    ctx.textAlign = "left";
    ctx.textBaseline = "bottom";
    ctx.fillText(`Median: ${SQFT_MEDIAN.toLocaleString()} sq ft → $0`, px + 12, py - 10);
    ctx.restore();
  },
};

new Chart(canvas, {
  type: "line",
  data: {
    datasets: [
      {
        label: "Upper bound",
        data: featureValues.map((x, i) => ({ x, y: upperBound[i] })),
        borderWidth: 0,
        pointRadius: 0,
        fill: false,
        tension: 0.3,
      },
      {
        label: "95% confidence band",
        data: featureValues.map((x, i) => ({ x, y: lowerBound[i] })),
        borderWidth: 0,
        pointRadius: 0,
        backgroundColor: `${t.palette[0]}26`,
        fill: "-1",
        tension: 0.3,
      },
      {
        label: "Partial dependence",
        data: featureValues.map((x, i) => ({ x, y: partialDependence[i] })),
        borderColor: t.palette[0],
        backgroundColor: t.palette[0],
        borderWidth: 4,
        pointRadius: 0,
        fill: false,
        tension: 0.3,
      },
      {
        label: "Training data (rug)",
        type: "scatter",
        data: rugPoints,
        yAxisID: "rug",
        pointStyle: "line",
        rotation: 90,
        radius: 9,
        borderColor: t.inkSoft,
        borderWidth: 1.5,
        showLine: false,
      },
    ],
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    layout: { padding: { top: 14, right: 30, bottom: 14, left: 10 } },
    plugins: {
      title: { display: true, text: title, color: t.ink, font: { size: 21, weight: "700" } },
      legend: {
        labels: {
          color: t.inkSoft,
          font: { size: 14 },
          padding: 20,
          filter: (item) => item.text === "Partial dependence" || item.text === "95% confidence band",
        },
      },
    },
    scales: {
      x: {
        type: "linear",
        min: SQFT_MIN,
        max: SQFT_MAX,
        title: { display: true, text: "Living Area (sq ft)", color: t.ink, font: { size: 18 } },
        ticks: { color: t.inkSoft, font: { size: 14 } },
        grid: { display: false },
      },
      y: {
        title: { display: true, text: "Partial Dependence ($k, centered)", color: t.ink, font: { size: 18 } },
        ticks: { color: t.inkSoft, font: { size: 14 } },
        grid: { color: t.grid },
      },
      rug: {
        type: "linear",
        position: "left",
        display: false,
        min: 0,
        max: 1,
      },
    },
  },
  plugins: [zeroCrossingCallout],
});

Retrieve this implementation

Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/pdp-basic/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": "pdp-basic",
  "language": "javascript",
  "library": "chartjs",
  "page": "https://anyplot.ai/pdp-basic/javascript/chartjs",
  "hub": "https://anyplot.ai/pdp-basic",
  "code_json": "https://api.anyplot.ai/specs/pdp-basic/chartjs/code",
  "spec_json": "https://api.anyplot.ai/specs/pdp-basic",
  "render_light_png": "https://storage.googleapis.com/anyplot-images/plots/pdp-basic/javascript/chartjs/plot-light.png",
  "render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/pdp-basic/javascript/chartjs/plot-dark.png",
  "interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/pdp-basic/javascript/chartjs/plot-light.html",
  "interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/pdp-basic/javascript/chartjs/plot-dark.html",
  "quality_score": 94.0,
  "license": "MIT",
  "guide": "https://anyplot.ai/llms.txt"
}

Part of Partial Dependence Plot on anyplot.ai.

Other implementations