Meta-Analysis Funnel Plot for Publication Bias — Chart.js

A funnel plot used in meta-analysis to assess publication bias by plotting individual study effect sizes against their precision (typically standard error). Studies scatter around a summary effect line, with pseudo 95% confidence limits forming an inverted funnel shape. In the absence of bias, studies distribute symmetrically around the summary effect; asymmetry suggests publication bias or systematic heterogeneity. This is a standard tool in systematic reviews and Cochrane-style meta-analyses.

Meta-Analysis Funnel Plot for Publication Bias rendered with Chart.js

Renders

JavaScript source (Chart.js)

// anyplot.ai
// funnel-meta-analysis: Meta-Analysis Funnel Plot for Publication Bias
// Library: chartjs 4.4.7 | JavaScript 22.22.3
// Quality: 89/100 | Created: 2026-06-10
//# anyplot-orientation: landscape

const t = window.ANYPLOT_TOKENS;

// 15 RCTs comparing drug vs placebo — log odds ratios and standard errors
const studies = [
  { logOR: 0.52, se: 0.18 },
  { logOR: 0.41, se: 0.22 },
  { logOR: 0.28, se: 0.31 },
  { logOR: 0.35, se: 0.14 },
  { logOR: 0.18, se: 0.25 },
  { logOR: 0.60, se: 0.38 },
  { logOR: 0.45, se: 0.12 },
  { logOR: 0.22, se: 0.28 },
  { logOR: -0.05, se: 0.42 },
  { logOR: 0.38, se: 0.16 },
  { logOR: 0.55, se: 0.34 },
  { logOR: 0.30, se: 0.20 },
  { logOR: 0.48, se: 0.45 },
  { logOR: 0.15, se: 0.37 },
  { logOR: 0.62, se: 0.11 },
];

// Inverse-variance weighted pooled effect
const weights = studies.map(s => 1 / (s.se * s.se));
const totalWeight = weights.reduce((a, b) => a + b, 0);
const summaryEffect = studies.reduce((sum, s, i) => sum + weights[i] * s.logOR, 0) / totalWeight;

const maxSE = Math.max(...studies.map(s => s.se)) * 1.15;
const axisMax = Math.ceil(maxSE * 10) / 10;  // round up to avoid duplicate boundary tick at 0.5

// Funnel boundaries: apex at (summaryEffect, 0), base at SE=maxSE
const funnelLeft  = [{ x: summaryEffect, y: 0 }, { x: summaryEffect - 1.96 * maxSE, y: maxSE }];
const funnelRight = [{ x: summaryEffect, y: 0 }, { x: summaryEffect + 1.96 * maxSE, y: maxSE }];

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

new Chart(canvas, {
  type: "scatter",
  data: {
    datasets: [
      // Funnel left boundary — in legend as "95% CI", fills toward right boundary
      {
        type: "line",
        label: "95% Confidence Interval",
        data: funnelLeft,
        borderColor: t.palette[1],
        backgroundColor: t.palette[1] + "26",
        borderWidth: 2,
        borderDash: [8, 5],
        pointRadius: 0,
        tension: 0,
        fill: "+1",
      },
      // Funnel right boundary — hidden from legend, fill target for left boundary
      {
        type: "line",
        label: "_funnel_right",
        data: funnelRight,
        borderColor: t.palette[1],
        backgroundColor: "transparent",
        borderWidth: 2,
        borderDash: [8, 5],
        pointRadius: 0,
        tension: 0,
        fill: false,
      },
      // Pooled effect vertical line
      {
        type: "line",
        label: `Pooled effect (${summaryEffect.toFixed(2)})`,
        data: [{ x: summaryEffect, y: 0 }, { x: summaryEffect, y: maxSE }],
        borderColor: t.palette[0],
        backgroundColor: "transparent",
        borderWidth: 2.5,
        pointRadius: 0,
        tension: 0,
      },
      // Null effect reference line
      {
        type: "line",
        label: "Null effect (0)",
        data: [{ x: 0, y: 0 }, { x: 0, y: maxSE }],
        borderColor: t.inkSoft,
        backgroundColor: "transparent",
        borderWidth: 1.5,
        borderDash: [4, 4],
        pointRadius: 0,
        tension: 0,
      },
      // Individual study points — Imprint palette position 1 as first series
      {
        type: "scatter",
        label: "Studies",
        data: studies.map(s => ({ x: s.logOR, y: s.se })),
        backgroundColor: t.palette[0] + "CC",
        borderColor: t.pageBg,
        borderWidth: 2,
        pointRadius: 9,
        pointHoverRadius: 11,
      },
    ],
  },
  options: {
    responsive: true,
    maintainAspectRatio: false,
    animation: false,
    layout: { padding: { top: 10, right: 40, bottom: 10, left: 10 } },
    plugins: {
      title: {
        display: true,
        text: "funnel-meta-analysis · javascript · chartjs · anyplot.ai",
        color: t.ink,
        font: { size: 22, weight: "600" },
        padding: { top: 16, bottom: 20 },
      },
      legend: {
        display: true,
        position: "bottom",
        labels: {
          color: t.inkSoft,
          font: { size: 14 },
          padding: 24,
          usePointStyle: true,
          filter: item => !item.text.startsWith("_"),
        },
      },
    },
    scales: {
      x: {
        title: {
          display: true,
          text: "Log Odds Ratio",
          color: t.ink,
          font: { size: 16, weight: "500" },
          padding: { top: 10 },
        },
        ticks: { color: t.inkSoft, font: { size: 13 } },
        grid: { color: t.grid },
        border: { display: false },
      },
      y: {
        reverse: true,
        min: 0,
        max: axisMax,
        title: {
          display: true,
          text: "Standard Error",
          color: t.ink,
          font: { size: 16, weight: "500" },
        },
        ticks: { color: t.inkSoft, font: { size: 13 } },
        grid: { color: t.grid },
        border: { display: false },
      },
    },
  },
});

Retrieve this implementation

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

Part of Meta-Analysis Funnel Plot for Publication Bias on anyplot.ai.

Other implementations