Update Chart Data Dynamically
Four methods change an ApexCharts chart after it has rendered:
| Method | Use when |
|---|---|
updateSeries(newSeries) | Replacing all data (new API response, filter change) |
appendData(newData) | Pushing new points onto existing series (streaming, polling) |
updateOptions(options) | Changing config, or config and data together |
appendSeries(newSerie) | Adding a new series line or bar to an existing chart |
All four return a Promise<ApexCharts> that resolves after the DOM has updated.
updateSeries
Replaces the entire series array and re-renders.
chart.updateSeries([
{ name: 'Revenue', data: [42, 51, 38, 67, 80] }
])
Pass the complete series array even when only one series changes. There is no method to update a single series by index.
When the series count stays the same and the chart is not currently zoomed or has no hidden series, ApexCharts takes an internal fast path: it recomputes axis scales and redraws only the series paths, leaving the grid, axes, legend, and tooltip DOM in place. This happens automatically. For high-frequency updates where the data window is fixed, updateSeries is faster than appendData because appendData never takes this path.
updateSeries also resets any zoom-adjusted axis bounds, so the chart returns to its natural scale on each call.
appendData
Pushes new data points onto the end of each existing series. The existing data is preserved.
chart.appendData([
{ data: [{ x: Date.now(), y: 74 }] }, // appended to series[0]
{ data: [{ x: Date.now(), y: 51 }] } // appended to series[1]
])
The argument is an array indexed to match the existing series. Pass null at a position to skip that series:
// Chart has three series; skip series[1]
chart.appendData([
{ data: [{ x: Date.now(), y: 74 }] },
null,
{ data: [{ x: Date.now(), y: 31 }] }
])
For a real-time chart with a fixed visible window, keep the data array from growing unbounded. Once the window is full, updateSeries with a sliced array is more efficient:
async function push(chart, value) {
const data = chart.w.config.series[0].data
const maxPoints = 30
if (data.length >= maxPoints) {
await chart.updateSeries([
{ data: [...data.slice(1), { x: Date.now(), y: value }] }
])
} else {
await chart.appendData([{ data: [{ x: Date.now(), y: value }] }])
}
}
updateOptions
Merges a partial config object into the existing chart config and re-renders. Only the keys you provide are changed.
chart.updateOptions({
xaxis: { categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May'] },
title: { text: 'Monthly Revenue 2026' }
})
When you need to change both data and config at the same time, pass series inside updateOptions rather than making two separate calls:
chart.updateOptions({
series: [{ name: 'Revenue', data: newData }],
xaxis: { categories: newCategories },
colors: ['#00E396']
})
When series is included this way, each incoming series object is merged with the existing series at the same index. Properties you omit — name, color, type — are preserved from the existing series config.
Real-time streaming pattern
const chart = new ApexCharts(document.querySelector('#chart'), {
chart: {
type: 'line',
animations: {
enabled: true,
easing: 'linear',
dynamicAnimation: { speed: 1000 }
},
toolbar: { show: false }
},
series: [{ name: 'Sensor', data: [] }],
xaxis: { type: 'datetime', range: 30_000 },
yaxis: { min: 0, max: 100 }
})
await chart.render()
const id = setInterval(() => {
chart.appendData([{
data: [{ x: Date.now(), y: Math.random() * 100 }]
}])
}, 1000)
// On teardown:
// clearInterval(id)
// chart.destroy()
Setting xaxis.range to a fixed millisecond window keeps the visible area constant as new points arrive. ApexCharts trims points outside the range automatically.
For loading chart data from a REST API after initial render, see Update Charts from JSON API & AJAX.
Framework usage
Pass series and options as props. The wrapper compares them with deep equality and calls updateSeries or updateOptions as appropriate.
import ReactApexChart from 'react-apexcharts'
import { useState, useEffect } from 'react'
function SalesChart() {
const [series, setSeries] = useState([{ name: 'Sales', data: [] }])
useEffect(() => {
fetch('/api/sales')
.then(r => r.json())
.then(data => setSeries([{ name: 'Sales', data }]))
}, [])
return (
<ReactApexChart
type="bar"
height={350}
series={series}
options={{ chart: { id: 'sales' } }}
/>
)
}
For imperative updates from polling loops or WebSocket handlers, use the chartRef prop:
const chartRef = useRef(null)
// In an interval or event handler:
chartRef.current?.appendData([{ data: [{ x: Date.now(), y: value }] }])
// In JSX:
<ReactApexChart chartRef={chartRef} ... />
Always clear intervals in the useEffect cleanup to avoid calling update methods after the component unmounts and the chart is destroyed.
Bind series and options as reactive props. When both change in the same tick, the Vue 3 wrapper coalesces them into a single updateOptions call via nextTick. The Vue 2 wrapper does not coalesce — setting both in the same method fires two separate renders. In Vue 2, pass series inside a single updateOptions call when changing both at once.
Changing type, width, or height triggers a destroy() + init() cycle because these are constructor-level parameters.
<script setup>
import { ref } from 'vue'
import VueApexCharts from 'vue3-apexcharts'
const series = ref([{ name: 'Sales', data: [] }])
async function refresh() {
const data = await fetch('/api/sales').then(r => r.json())
series.value = [{ name: 'Sales', data }]
}
</script>
<template>
<VueApexCharts type="line" height="350" :series="series" :options="chartOptions" />
</template>
For imperative access, call exposed methods on a template ref: apexchartRef.value?.appendData(...).
The apx-chart component runs chart operations outside NgZone and guards all DOM access with isPlatformBrowser, so it is safe to include in SSR builds.
@Component({
template: `
<apx-chart [series]="series()" [chart]="chartConfig" [xaxis]="xaxis" />
`
})
export class ChartComponent {
series = signal([{ name: 'Revenue', data: [] as number[] }])
chartConfig = { type: 'bar' as const, height: 350 }
xaxis = { categories: ['Jan', 'Feb', 'Mar'] }
async load() {
const data = await fetch('/api/revenue').then(r => r.json())
this.series.set([{ name: 'Revenue', data }])
}
}
When only series changes (autoUpdateSeries: true, the default), the component calls updateSeries without recreating the chart. Any other input change alongside series triggers a full recreate.
For imperative calls, use viewChild to get the ChartComponent instance and call its methods directly.