A forest plot displays effect sizes with confidence intervals from multiple studies in a meta-analysis. Each study is represented as a point estimate with horizontal whiskers showing the confidence interval, and a diamond at the bottom shows the pooled estimate. The plot includes a vertical reference line at the null effect (typically 0 or 1), making it easy to assess statistical significance and heterogeneity across studies.

// anyplot.ai
// forest-basic: Meta-Analysis Forest Plot
// Library: highcharts 12.6.0 | JavaScript 22.23.2
// Quality: 91/100 | Created: 2026-09-05
const t = window.ANYPLOT_TOKENS;
// --- Data (in-memory, deterministic) ----------------------------------------
// Meta-analysis of randomized controlled trials: drug vs. placebo, risk ratio
// for the primary outcome. Null effect for a ratio measure is RR = 1.
const studies = [
{ label: "Chen 2015", effect: 0.72, ciLower: 0.55, ciUpper: 0.94, weight: 12 },
{ label: "Kumar 2016", effect: 0.85, ciLower: 0.68, ciUpper: 1.08, weight: 15 },
{ label: "Silva 2017", effect: 0.63, ciLower: 0.44, ciUpper: 0.9, weight: 9 },
{ label: "Fischer 2019", effect: 0.91, ciLower: 0.75, ciUpper: 1.12, weight: 18 },
{ label: "Tanaka 2020", effect: 0.77, ciLower: 0.6, ciUpper: 0.99, weight: 14 },
{ label: "Okafor 2021", effect: 0.68, ciLower: 0.5, ciUpper: 0.93, weight: 11 },
{ label: "Rossi 2022", effect: 0.8, ciLower: 0.64, ciUpper: 1.0, weight: 16 },
];
const pooledEstimate = { label: "Pooled Estimate", effect: 0.78, ciLower: 0.7, ciUpper: 0.87 };
const nullEffect = 1;
const weights = studies.map((s) => s.weight);
const minWeight = Math.min(...weights);
const maxWeight = Math.max(...weights);
const markerRadius = (weight) => 6 + ((weight - minWeight) / (maxWeight - minWeight)) * 10;
// CI whisker thickness echoes the same weight scale as the marker radius, so a
// heavy study (e.g. Fischer 2019) reads as "more precise" via both encodings.
const whiskerLineWidth = (weight) => 1.5 + ((weight - minWeight) / (maxWeight - minWeight)) * 2.5;
const rows = [...studies.map((s) => ({ ...s, pooled: false })), { ...pooledEstimate, pooled: true }];
const pooledIndex = rows.length - 1;
// --- Chart -------------------------------------------------------------------
// Each whisker series draws the CI line plus a short perpendicular end-cap at
// both bounds (classic forest-plot styling), using null points to break the
// path into three disconnected segments: low cap, main line, high cap.
const capHalfWidth = 0.12;
const whiskerSeries = rows.map((row, index) => ({
type: "line",
data: [
[index - capHalfWidth, row.ciLower],
[index + capHalfWidth, row.ciLower],
[index, null],
[index, row.ciLower],
[index, row.ciUpper],
[index, null],
[index - capHalfWidth, row.ciUpper],
[index + capHalfWidth, row.ciUpper],
],
color: Highcharts.color(row.pooled ? t.ink : t.palette[0])
.setOpacity(0.55)
.get(),
lineWidth: row.pooled ? 3 : whiskerLineWidth(row.weight),
marker: { enabled: false },
enableMouseTracking: false,
showInLegend: false,
animation: false,
}));
const ciLabelFormatter = function () {
return `${this.y.toFixed(2)} (${this.point.low.toFixed(2)}–${this.point.high.toFixed(2)})`;
};
const studySeries = {
name: "Study estimate",
type: "scatter",
color: t.palette[0],
data: studies.map((s, index) => ({
x: index,
y: s.effect,
low: s.ciLower,
high: s.ciUpper,
marker: { radius: markerRadius(s.weight) },
})),
marker: { symbol: "circle", lineWidth: 1, lineColor: t.pageBg },
dataLabels: {
enabled: true,
formatter: ciLabelFormatter,
style: { color: t.inkSoft, fontSize: "12px", fontWeight: "400", textOutline: "none" },
verticalAlign: "bottom",
y: -12,
},
zIndex: 5,
animation: false,
};
const pooledSeries = {
name: "Pooled estimate",
type: "scatter",
color: t.ink,
data: [
{
x: pooledIndex,
y: pooledEstimate.effect,
low: pooledEstimate.ciLower,
high: pooledEstimate.ciUpper,
marker: { radius: 14 },
},
],
marker: { symbol: "diamond", lineWidth: 1, lineColor: t.pageBg },
dataLabels: {
enabled: true,
formatter: ciLabelFormatter,
style: { color: t.ink, fontSize: "13px", fontWeight: "600", textOutline: "none" },
verticalAlign: "bottom",
y: -14,
},
zIndex: 6,
animation: false,
};
Highcharts.chart("container", {
chart: {
inverted: true,
backgroundColor: "transparent",
animation: false,
style: { fontFamily: "inherit" },
},
credits: { enabled: false },
title: {
text: "forest-basic · javascript · highcharts · anyplot.ai",
style: { color: t.ink, fontSize: "25px", fontWeight: "600" },
},
xAxis: {
categories: rows.map((row) => row.label),
lineColor: t.inkSoft,
tickColor: t.inkSoft,
gridLineWidth: 0,
labels: { style: { color: t.inkSoft, fontSize: "14px" } },
},
yAxis: {
min: 0.4,
max: 1.2,
tickInterval: 0.1,
title: { text: "Risk Ratio", style: { color: t.inkSoft, fontSize: "16px" } },
gridLineColor: t.grid,
labels: { style: { color: t.inkSoft, fontSize: "14px" } },
plotLines: [
{
value: nullEffect,
color: t.inkSoft,
dashStyle: "Dash",
width: 2,
zIndex: 4,
label: {
text: "No effect (RR = 1)",
rotation: 0,
align: "right",
verticalAlign: "top",
x: -8,
y: 20,
style: { color: t.inkSoft, fontSize: "13px" },
},
},
],
},
legend: {
enabled: true,
itemStyle: { color: t.inkSoft, fontSize: "14px" },
itemHoverStyle: { color: t.ink },
},
plotOptions: {
series: { animation: false },
},
series: [...whiskerSeries, studySeries, pooledSeries],
});
Runnable source as JSON, for any HTTP client: https://api.anyplot.ai/specs/forest-basic/highcharts/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": "forest-basic",
"language": "javascript",
"library": "highcharts",
"page": "https://anyplot.ai/forest-basic/javascript/highcharts",
"hub": "https://anyplot.ai/forest-basic",
"code_json": "https://api.anyplot.ai/specs/forest-basic/highcharts/code",
"spec_json": "https://api.anyplot.ai/specs/forest-basic",
"render_light_png": "https://storage.googleapis.com/anyplot-images/plots/forest-basic/javascript/highcharts/plot-light.png",
"render_dark_png": "https://storage.googleapis.com/anyplot-images/plots/forest-basic/javascript/highcharts/plot-dark.png",
"interactive_light_html": "https://storage.googleapis.com/anyplot-images/plots/forest-basic/javascript/highcharts/plot-light.html",
"interactive_dark_html": "https://storage.googleapis.com/anyplot-images/plots/forest-basic/javascript/highcharts/plot-dark.html",
"quality_score": 91.0,
"license": "MIT",
"guide": "https://anyplot.ai/llms.txt"
}Part of Meta-Analysis Forest Plot on anyplot.ai.