Real-time charts have two hard parts: making the line scroll smoothly, and keeping memory flat while it runs for hours. ApexCharts 6.0 solves the second one for you. Set chart.streaming.enabled: true, keep appending points, and the library trims the series automatically to the visible window, so the data array never grows without bound. The scroll is constant-velocity by default, with no easing pulse. This post shows the new API, explains why appending (not shifting) is what actually scrolls a chart, and compares it to the manual trim you used to hand-write.

Key takeaways

  • chart.streaming.enabled auto-trims. appendData trims each series to maxPoints, or to the visible xaxis.range window plus a runway, so memory stays flat.
  • Appending is what scrolls. Fixed points keep their x; the advancing axis moves their pixels left, so the line translates. Shifting a window each tick warps in place instead.
  • The scroll is constant-velocity. Streaming keeps the window moving at an even speed with no easing pulse.
  • It replaces manual trimming. Before 6.0 you trimmed the array yourself with a non-animated updateSeries; that still works, but is no longer required.
  • xaxis.range is a view setting, not a memory bound. It never removes data on its own.

See it live

The chart streams one point per second. Watch the two counters: appended climbs forever, while retained in memory stays bounded, because chart.streaming trimmed the off-screen points automatically. The window slides left at a steady pace.

Live signalPAUSED
Points appended: 30

chart.streaming.enabled trims the series to the visible window automatically, so “appended” climbs forever while “retained” stays bounded. The window slides left at constant velocity, no easing pulse and no in-place warp.

The streaming API

Enable streaming, set a window with xaxis.range, and match dynamicAnimation.speed to your tick interval. Then just append.

import ApexCharts from 'apexcharts'

const chart = new ApexCharts(document.querySelector('#chart'), {
  series: [{ name: 'Signal', data: seed }],
  chart: {
    type: 'line',
    streaming: { enabled: true },           // auto-trim + constant-velocity scroll
    // streaming: { enabled: true, maxPoints: 100000 }, // or cap explicitly
    animations: { dynamicAnimation: { speed: 1000 } }, // match the interval
    zoom: { enabled: false },
  },
  xaxis: { type: 'datetime', range: 29_000 }, // 29-second visible window
  yaxis: { min: 0, max: 100 },
})
await chart.render()

let lastX = Date.now()
setInterval(() => {
  lastX += 1000
  chart.appendData([{ data: [{ x: lastX, y: Math.round(Math.random() * 100) }] }])
}, 1000)

That is the whole thing. With streaming.enabled, you do not track the data yourself or trim it; the library bounds it to maxPoints when you set one, otherwise to the xaxis.range window plus a small runway.

Why appending scrolls (and shifting does not)

This is the mechanic worth understanding, because getting it wrong is the classic real-time bug.

appendData leaves every existing point where it is, at its index and its x, and adds the new point at the leading edge. As the axis max advances to that new point, every fixed point's pixel position moves left by exactly one step. The whole line translates. Keep the gap between points even (a steady cadence, or server timestamps at a fixed interval) and every scroll step is the same width.

The tempting alternative, dropping the oldest point and pushing a fixed-length window every tick, looks like it should scroll but does the opposite:

ApproachWhat happens on the axisWhat you see
Append (correct)New point extends max; old points keep their x, pixels shift leftThe line scrolls left smoothly
Shift a window each tick (wrong)Axis rescales; index i maps to a different datum at the same pixelOnly y changes per point: the line warps in place

So the rule is: append points, and let streaming (or an occasional trim) bound the array. Never rebuild a drop-oldest window every tick.

What changed from the pre-6 pattern

Before 6.0, xaxis.range set the visible window but never removed data, so a live chart's array grew forever unless you trimmed it yourself. The documented pattern was: append points, and periodically slice the array with a non-animated updateSeries (invisible, because the dropped points were off-screen).

// Pre-6 manual trim (still valid, still useful):
data.push(point)
if (data.length > 3 * VISIBLE) {
  data = data.slice(-2 * VISIBLE)
  chart.updateSeries([{ data }], false) // false => no animation, no flicker
}

Version 6 folds that into the library:

Pre-6 (manual)v6 (chart.streaming)
TrimmingYou slice the array + non-animated updateSeriesAutomatic on appendData
Track data yourselfYesNo
Constant-velocity scrollYou match cadence + speed carefullyGuaranteed by streaming
Memory bound2 * VISIBLE (your choice)maxPoints, or the range window + runway

The manual pattern is not deprecated; it is the right choice on older versions or when you want to decide exactly when and how much to trim. The real-time chart guide documents both, and our real-time dashboard tutorial builds a full multi-chart dashboard with the manual approach.

Wiring a WebSocket

A real feed replaces the interval with a message handler. The rules are the same, plus one: do not block the handler.

const socket = new WebSocket('wss://your-api.example.com/stream')

socket.addEventListener('message', (event) => {
  const { t, value } = JSON.parse(event.data)
  // fire-and-forget: do NOT await appendData in the handler
  chart.appendData([{ data: [{ x: t, y: value }] }])
})

Use the server's timestamp as x so the scroll steps stay even, and do not await inside the handler: messages can arrive faster than render cycles, and awaiting would queue them up. For very high-rate feeds, buffer incoming values and append the newest batch on a timer or requestAnimationFrame, so the render rate is decoupled from the message rate.

Common mistakes

  • Assuming xaxis.range bounds memory. It sets the view, not the data. Enable streaming, set maxPoints, or trim yourself.
  • Trimming a shifting window every tick. That warps the line in place instead of scrolling. Append; let streaming trim.
  • Uneven point spacing. A jittery timer or a throttled background tab makes the scroll speed stutter. Use a steady cadence or fixed-interval server timestamps.
  • Mismatched dynamicAnimation.speed. If it does not match the interval, the chart idles between ticks or falls behind. Set them equal.
  • Not cleaning up. Clear the interval and close the socket on unmount, then chart.destroy(), or updates target a dead instance.

Summary

ApexCharts 6.0 makes live charts a one-line concern: chart.streaming.enabled trims the series automatically as you appendData, so the window scrolls at constant velocity and memory stays flat for as long as the feed runs. The mechanic underneath is unchanged, appending points is what scrolls the line, and shifting a window is what warps it, but you no longer hand-write the trim. Set maxPoints or let it follow xaxis.range, match dynamicAnimation.speed to your cadence, and append. See the real-time chart guide and chart.streaming options for the reference, and the rest of what shipped in ApexCharts 6.0.

Frequently asked questions

What does chart.streaming do in ApexCharts 6.0?

chart.streaming.enabled turns on automatic memory management for live feeds. Once enabled, appendData() trims each series automatically, to maxPoints when set, otherwise to the visible xaxis.range window plus a small runway, so an always-on stream never grows the data array without limit. You no longer write your own periodic trim. The window also slides at constant velocity with no easing pulse.

Why does appending points scroll the chart instead of redrawing it?

appendData leaves every existing point at its index and x position and adds the new one at the leading edge. As the axis max advances to the new point, each fixed point's pixel moves left by one step, so the whole line translates smoothly. Dropping the oldest point and shifting a window every tick does the opposite: under the rescaled axis each point keeps its pixel and only its value changes, so the line warps in place instead of scrolling.

How is v6 streaming different from the old real-time pattern?

Before 6.0 you appended points and had to trim the series array yourself with an occasional non-animated updateSeries, because xaxis.range only sets the visible window and never removes data. In 6.0, chart.streaming.enabled does that trim for you and guarantees a constant-velocity scroll. The manual pattern still works and is the right choice on older versions or when you want full control over trimming.

Does xaxis.range bound memory on its own?

No. xaxis.range only computes the visible axis min and max; it never removes points from the series. Without chart.streaming.enabled or a manual trim, appendData grows the underlying array for as long as the stream runs. Enable streaming, set maxPoints, or trim yourself to keep memory flat.