ApexCharts Line Chart with Annotations and Target Lines — Free Snippet
ApexCharts Line Chart with Annotations and Target Lines · Charts · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<div class="aan-wrap">
<div class="aan-card">
<div class="aan-head">
<div>
<div class="aan-title">API p95 Latency</div>
<div class="aan-sub">SLO target, a tolerance band, deploy markers and the worst spike are all annotations</div>
</div>
<label class="aan-slo">SLO
<input type="range" id="aanSlo" min="180" max="320" step="10" value="250">
<output id="aanSloOut">250 ms</output>
</label>
</div>
<div id="aanChart"></div>
<div class="aan-foot" id="aanFoot"></div>
</div>
</div>*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,sans-serif;background:#f8fafc;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
.aan-wrap{width:100%;max-width:860px}
.aan-card{background:#fff;border:1px solid #e2e8f0;border-radius:16px;padding:20px 18px 14px;box-shadow:0 1px 8px rgba(15,23,42,.06)}
.aan-head{display:flex;justify-content:space-between;align-items:flex-start;gap:14px;flex-wrap:wrap}
.aan-title{font-size:15px;font-weight:700;color:#0f172a}
.aan-sub{font-size:12px;color:#64748b;margin-top:3px;max-width:460px}
.aan-slo{display:flex;align-items:center;gap:8px;font-size:12px;font-weight:600;color:#334155}
.aan-slo input{accent-color:#ef4444;width:130px}
.aan-slo output{font-variant-numeric:tabular-nums;min-width:52px}
.aan-foot{font-size:12.5px;color:#334155;padding:2px 6px}
.aan-foot b{color:#b91c1c}var DAY = 86400000;
var start = Date.UTC(2026, 7, 1);
var seed = 3;
function rand() { seed = (seed * 16807) % 2147483647; return seed / 2147483647; }
var data = [];
for (var i = 0; i < 42; i++) {
var v = 196 + Math.sin(i / 4) * 18 + rand() * 30;
if (i === 17) v += 150; // incident spike
if (i >= 29) v -= 28; // performance fix after a deploy
data.push([start + i * DAY, Math.round(v)]);
}
var DEPLOYS = [
{ day: 9, text: 'v4.2 deploy' },
{ day: 17, text: 'Cache outage' },
{ day: 29, text: 'v4.3 query fix' },
];
var peak = data.reduce(function (a, b) { return b[1] > a[1] ? b : a; });
// Everything except the SLO line is fixed; the SLO line and band depend on
// the slider, so annotations are rebuilt from one function.
function annotations(slo) {
return {
yaxis: [
// A band (y to y2) shows the "warning" zone just under the target.
{ y: slo - 30, y2: slo, fillColor: '#fef3c7', opacity: 0.55, borderColor: 'transparent',
label: { text: 'Warning band', position: 'left', textAnchor: 'start', offsetX: 6, style: { background: 'transparent', color: '#92400e', fontSize: '10px' }, borderColor: 'transparent' } },
{ y: slo, borderColor: '#ef4444', strokeDashArray: 5,
label: { text: 'SLO ' + slo + ' ms', style: { background: '#ef4444', color: '#fff', fontSize: '11px' }, borderColor: '#ef4444' } },
],
xaxis: DEPLOYS.map(function (d) {
return { x: start + d.day * DAY, borderColor: '#94a3b8', strokeDashArray: 3,
label: { text: d.text, orientation: 'vertical', style: { background: '#f1f5f9', color: '#334155', fontSize: '10px' }, borderColor: '#cbd5e1' } };
}),
points: [{
x: peak[0], y: peak[1],
marker: { size: 6, fillColor: '#ef4444', strokeColor: '#fff', strokeWidth: 2 },
// Anchored to the right of the point so it clears the vertical event
// label drawn on the same day.
label: { text: 'Peak ' + peak[1] + ' ms', textAnchor: 'start', offsetX: 12, offsetY: 18, style: { background: '#0f172a', color: '#fff', fontSize: '11px' }, borderColor: '#0f172a' },
}],
};
}
var chart = new ApexCharts(document.getElementById('aanChart'), {
chart: { type: 'line', height: 330, toolbar: { show: false }, zoom: { enabled: false }, fontFamily: 'system-ui, sans-serif' },
series: [{ name: 'p95 latency', data: data }],
colors: ['#4f46e5'],
stroke: { width: 2.5, curve: 'smooth' },
xaxis: { type: 'datetime', labels: { datetimeUTC: true } },
yaxis: { min: 120, max: 400, tickAmount: 7, labels: { formatter: function (v) { return v + ' ms'; } } },
grid: { borderColor: '#eef2f7', strokeDashArray: 4 },
tooltip: { x: { format: 'dd MMM' }, y: { formatter: function (v) { return v + ' ms'; } } },
annotations: annotations(250),
});
chart.render();
var sloInput = document.getElementById('aanSlo');
var sloOut = document.getElementById('aanSloOut');
var foot = document.getElementById('aanFoot');
function summarise(slo) {
var breaches = data.filter(function (p) { return p[1] > slo; }).length;
var pct = ((data.length - breaches) / data.length) * 100;
foot.innerHTML = '<b>' + breaches + '</b> of ' + data.length + ' days breached ' + slo + ' ms · ' + pct.toFixed(1) + '% of days within SLO.';
}
sloInput.addEventListener('input', function () {
var slo = Number(sloInput.value);
sloOut.textContent = slo + ' ms';
// updateOptions with a full annotations object replaces the old set,
// rather than stacking a new line on top of the previous one.
chart.updateOptions({ annotations: annotations(slo) }, false, false);
summarise(slo);
});
summarise(250);Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month, 7 days free), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
ApexCharts Annotations — Turning a Line Chart Into an Explanation

A line chart shows what happened. Annotations explain why. A target line tells you whether a value is good, a band shows the zone to worry about, vertical markers line changes up with events like deploys, and a point label calls out the outlier that everyone will ask about. This snippet uses all four on one latency chart.
Three annotation groups
ApexCharts accepts annotations.yaxis, annotations.xaxis and annotations.points. A y-axis annotation with only y draws a horizontal line; adding y2 turns it into a shaded band between two values. X-axis annotations draw vertical lines at timestamps, with labels that can be rotated with orientation: 'vertical' so several fit without overlapping. Point annotations place a marker and label on a specific x, y coordinate.
Annotations built from one function
Because the SLO line and the warning band depend on the slider, all annotations come from annotations(slo). On every slider change, updateOptions({ annotations: ... }) replaces the whole set. Using addYaxisAnnotation instead would stack a new line on each input event unless the previous one were removed by id.
The numbers under the chart
The footer counts days above the current SLO and the share within it. The chart shows where breaches happened; the text states how many, so the result is available without reading the graphic.
A fixed y-range
yaxis.min and max are fixed so moving the SLO doesn't rescale the chart under the slider, which would make the line appear to jump.
Deterministic data with a story
Seeded data includes a cache-outage spike and a lasting improvement after a query fix, so the deploy markers line up with visible changes.
Step by step
How to Use
- 1Load ApexChartsInclude apexcharts.min.js from the CDN.
- 2Paste the snippetA latency line renders with target, band, deploy and peak annotations.
- 3Move the SLO sliderThe line and band move together and the breach count updates.
- 4Hover the lineThe tooltip shows the day's p95 latency.
- 5Add your eventsEdit DEPLOYS with day offsets and labels.
- 6Plot your metricReplace the generated data with [timestamp, value] pairs.
Real-world uses
Common Use Cases
y and y2 and let readings drift outside it, making out-of-range values obvious without reading axis numbers.Got questions?
Frequently Asked Questions
Add an object to annotations.yaxis with y set to the target value, a borderColor, optionally strokeDashArray for a dashed line, and a label.
Give a y-axis annotation both y and y2. ApexCharts fills the area between them using fillColor and opacity. The same works on the x-axis with x and x2 for time windows.
Add objects to annotations.xaxis with x set to the event timestamp. Set label.orientation to 'vertical' so several nearby labels don't overlap.
Rebuild the full annotations object and pass it to updateOptions, which replaces the previous set. If you use addYaxisAnnotation, give each annotation an id and remove it before adding a new one.
Yes, use annotations.points with x and y coordinates, a marker style and a label. It is useful for peaks, records or anomalies.