Line Graph
Real-time scrolling SVG: the line and its gradient fill update frame-by-frame via rAF, combining a sine wave and random noise over a 60-point rolling buffer.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Atmosphere has 57 effects, including 10 free. Explore the category →
3 usage examples



How it works
The component uses a <svg viewBox="0 0 300 100" preserveAspectRatio="none"> with two overlapping paths: <path class="area"> for the filled region below the curve and <path class="line"> for the visible stroke. preserveAspectRatio="none" forces the SVG to stretch to fill its CSS container exactly — no aspect ratio is preserved, making the chart fully responsive to any width without JavaScript.
The animation relies on a rolling buffer of 60 values, all initialized at 50 (the viewBox midpoint). Each requestAnimationFrame call invokes animateLineGraph, which runs lineData.shift() to drop the oldest point, then pushes a new point computed as 50 + Math.sin(time × 0.005) × 30 + Math.random() × 20 − 10: a sine wave of ±30 amplitude sets the overall curve shape, while random noise of ±10 simulates real-world data volatility. There is no smoothing or bézier interpolation — each point generates a raw SVG L segment.
Both path d attributes are fully rebuilt each frame via setAttribute. The line path starts at M0,y[0] then chains L xi,yi segments for the remaining 59 points. The area path follows the same trace but closes the shape with L300,100 Z — the viewBox bottom — to form the filled polygon.
The CSS rule fill: url(#areaGradient) expects a <linearGradient id="areaGradient"> declared in an SVG <defs> block. This gradient is absent from the base HTML: without it, the area renders black or transparent depending on the browser. The animation runs inside a shared requestAnimationFrame loop alongside other effects from the same IIFE — only animateLineGraph is active when the id="linePath" and id="lineArea" elements are present in the DOM.
Accessibility
- prefers-reduced-motion not checked: the animation runs unconditionally regardless of the OS preference. For WCAG 2.3.3 compliance, add a guard
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;before the firstrequestAnimationFramecall. - The
<svg>carries noaria-hidden,role, oraria-label— it is treated as generic content by screen readers. Addaria-hidden="true"for purely decorative use, orrole="img" aria-label="…"if the chart conveys information. - No
<title>element inside the SVG: assistive technologies receive no description of the curve. - The stroke color
#6366f1on background#0a0a0fyields a contrast ratio of approximately 4.6:1 — meeting WCAG AA for normal text, and well above the threshold for a decorative graphical element. - No keyboard or pointer interaction is implemented — the effect is purely decorative, which limits interactive accessibility requirements.
Browser compatibility
Requires inline SVG and requestAnimationFrame — supported in all modern browsers since 2013.
If SVG is not rendered (very old browsers or screen readers in text mode), the <code><path></code> elements do not appear — no JS errors, the page remains functional with any text overlay elements.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="demo-preview">
<div class="line-graph" id="lineGraph">
<svg viewBox="0 0 300 100" preserveAspectRatio="none">
<!-- Declare the gradient for the fill (missing from the base HTML) -->
<defs>
<linearGradient id="areaGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#6366f1" stop-opacity="0.35"/>
<stop offset="100%" stop-color="#6366f1" stop-opacity="0.03"/>
</linearGradient>
</defs>
<path class="area" id="lineArea"></path>
<path class="line" id="linePath"></path>
</svg>
</div>
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| .line { stroke } | #6366f1 | Line color — swap for your brand color. Any CSS color value is accepted. |
| .line { stroke-width } | 2 | Line thickness in px. 1 = thin and subtle, 3–4 = bold and prominent. |
| url(#areaGradient) in |
undefined in base HTML | Gradient below the curve: declare a <linearGradient id="areaGradient"> manually inside the SVG <defs> (see structure_html) with stop-color and stop-opacity matching your palette. |
| .line-graph { height } | 120px | Container height. The curve stretches proportionally (preserveAspectRatio="none") — adjust freely without touching the viewBox. |
| Array(60) — buffer size | 60 | Number of data points in the rolling history. More points mean slower scroll (60 ≈ 1 second at 60 fps). Drop to 30 for a more reactive chart. |
| time * 0.005 — sine frequency | 0.005 | Sine wave frequency. 0.002 = slow and sweeping, 0.012 = fast and nervous. |
| Math.random() * 20 - 10 — noise | ±10 amplitude | Random noise added to each new point. Reduce to ±3 for a smooth curve (financial dashboard), increase to ±20 for high volatility (network monitoring). |
FAQ
.area { fill: url(#areaGradient) } references an identifier that is not defined in the base HTML. Add a <defs> block to your <svg> (see structure_html) containing a <linearGradient id="areaGradient"> with two color stops — the fill appears immediately.const newValue = 50 + Math.sin(…) line inside animateLineGraph with your actual value, normalized to [0, 100] to stay within the SVG viewBox. Push values from a WebSocket callback or fetch-polling loop: lineData.push(normalizedValue); lineData.shift(); is all that's needed — the current frame will read the new value on the next requestAnimationFrame tick.getElementById('linePath') and getElementById('lineArea'), which must be unique per page. For multiple instances, extract the animateLineGraph logic into a function that accepts the target elements as parameters, then launch a separate requestAnimationFrame loop per instance — as shown in scenes 2 and 3 of this effect's example file.