Large Dataset Downsampling in JavaScript

Using ApexCharts with JavaScript

This Large Dataset Downsampling example uses ApexCharts.js directly in JavaScript, with no wrapper component.

Install it with npm install apexcharts, then mount the chart with new ApexCharts(element, options).render().

JavaScript installation guide
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> &nbsp;·&nbsp; Currently drawn: <b>' +
    drawn.toLocaleString() +
    '</b> &nbsp;·&nbsp; Window: <b>' +
    lastZoomLabel +
    '</b> &nbsp;·&nbsp; Render: <b>' +
    lastRenderMs +
    ' ms</b>'
}

var options = {
  series: [{ name: 'Sensor reading', data: seriesData }],
  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' } },
}

var chart = new ApexCharts(document.querySelector('#chart'), options)
chart.render()

// Initial render is started by the template above; wait one frame and update
// stats once the chart instance has finished its first paint.
requestAnimationFrame(function poll() {
  if (chart.w && chart.w.globals && chart.w.globals.series) {
    updateStats(chart)
  } else {
    requestAnimationFrame(poll)
  }
})

function rebuild() {
  var enabled = document.getElementById('toggleReducer').checked
  var 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'

  var t2 = performance.now()
  chart.destroy()
  chart = new ApexCharts(document.querySelector('#chart'), options)
  chart.render().then(function () {
    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)
})