import React from 'react'
import ReactApexChart from 'react-apexcharts'
import ApexCharts from 'apexcharts'
import './styles.css'
var TOTAL_POINTS = 500000
function generateData(count) {
var data = new Array(count)
var startTime = new Date('2010-01-01T00:00:00Z').getTime()
var step = 60 * 1000 // 1 minute
var y = 100
for (var i = 0; i < count; i++) {
// Random walk with periodic components — gives visible structure at
// multiple zoom levels (slow trend + faster cycle + noise).
y +=
(Math.random() - 0.5) * 2 +
Math.sin(i / 5000) * 0.05 +
Math.cos(i / 1200) * 0.02
data[i] = [startTime + i * step, +y.toFixed(3)]
}
return data
}
var seriesData = generateData(TOTAL_POINTS)
var lastRenderMs = 0
var lastZoomLabel = 'full range'
function fmt(ts) {
var d = new Date(ts)
return (
d.getFullYear() +
'-' +
String(d.getMonth() + 1).padStart(2, '0') +
'-' +
String(d.getDate()).padStart(2, '0')
)
}
// Accepts the live chart instance as an argument so the React and Vue builds
// (which have no global `chart` var) can share this stateless helper.
function updateStats(chart) {
var statsEl = document.getElementById('stats')
if (!statsEl || !chart || !chart.w) return
var drawn = (chart.w.globals.series[0] || []).length
statsEl.innerHTML =
'Raw points: <b>' +
TOTAL_POINTS.toLocaleString() +
'</b> · Currently drawn: <b>' +
drawn.toLocaleString() +
'</b> · Window: <b>' +
lastZoomLabel +
'</b> · Render: <b>' +
lastRenderMs +
' ms</b>'
}
const ApexChart = () => {
const [state, setState] = React.useState({
series: [{ name: 'Sensor reading', data: seriesData }],
options: {
chart: {
height: 460,
type: 'line',
animations: { enabled: false },
zoom: { enabled: true, type: 'x', autoScaleYaxis: true },
toolbar: { autoSelected: 'zoom' },
dataReducer: {
enabled: true,
algorithm: 'lttb',
targetPoints: 500,
threshold: 1000,
},
events: {
zoomed: function (chartCtx, args) {
var xaxis = args && args.xaxis
lastZoomLabel =
xaxis && xaxis.min && xaxis.max
? fmt(xaxis.min) + ' → ' + fmt(xaxis.max)
: 'full range'
requestAnimationFrame(function () {
updateStats(chartCtx)
})
},
beforeResetZoom: function () {
lastZoomLabel = 'full range'
},
},
},
stroke: { curve: 'straight', width: 1 },
dataLabels: { enabled: false },
markers: { size: 0 },
title: {
text: 'Line chart — 500,000 raw datapoints',
align: 'left',
},
subtitle: {
text: 'LTTB downsampling enabled — zoom in to re-sample the raw series at higher resolution within the visible window',
align: 'left',
},
xaxis: {
type: 'datetime',
labels: { datetimeUTC: false },
},
yaxis: {
labels: {
formatter: function (val) {
return val.toFixed(2)
},
},
},
tooltip: { x: { format: 'yyyy-MM-dd HH:mm' } },
},
})
React.useEffect(() => {
// This demo destroys and recreates the chart imperatively on every re-render,
// so it skips the react-apexcharts wrapper and creates the instance in #chart
// directly, exactly like the vanilla build. TOTAL_POINTS, seriesData, fmt,
// updateStats and the lastRenderMs / lastZoomLabel state live in the shared
// head script.
let chart = null
let disposed = false
// The vanilla build gets a mutable `options` global from the template; rebuild
// it here from the parsed chart state.
const options = Object.assign({ series: state.series }, state.options)
options.chart = Object.assign({}, state.options.chart)
options.chart.dataReducer = Object.assign(
{},
state.options.chart.dataReducer,
)
function rebuild() {
const enabled = document.getElementById('toggleReducer').checked
const targetPoints = parseInt(
document.getElementById('targetPoints').value,
10,
)
options.chart.dataReducer.enabled = enabled
options.chart.dataReducer.targetPoints = targetPoints
options.subtitle.text = enabled
? 'LTTB downsampling enabled — drawing ~' +
targetPoints.toLocaleString() +
' points'
: 'Downsampling disabled — drawing every raw point'
const t2 = performance.now()
if (chart) chart.destroy()
chart = new ApexCharts(document.querySelector('#chart'), options)
chart.render().then(function () {
if (disposed) return
lastRenderMs = Math.round(performance.now() - t2)
updateStats(chart)
})
}
document.getElementById('rerender').addEventListener('click', rebuild)
document.getElementById('toggleReducer').addEventListener('change', rebuild)
document.getElementById('resetZoom').addEventListener('click', function () {
if (chart) chart.resetSeries(true, true)
})
// Initial render into #chart, then seed the stats once the first paint settles.
chart = new ApexCharts(document.querySelector('#chart'), options)
chart.render().then(function () {
if (disposed) return
updateStats(chart)
})
return () => {
disposed = true
if (chart) chart.destroy()
}
}, [])
return (
<div>
<div className="panel">
<div className="controls">
<label>
<input type="checkbox" id="toggleReducer" checked />
Enable LTTB downsampling
</label>
<label>
Target points:
<input
type="number"
id="targetPoints"
className="target-input"
value="500"
min="50"
max="5000"
step="50"
/>
</label>
<button id="rerender">Re-render</button>
<button id="resetZoom">Reset zoom</button>
</div>
<div className="stats" id="stats"></div>
</div>
<div id="chart"></div>
</div>
)
}
export default ApexChart