<template>
  <div>
    <div class="panel">
      <div class="controls">
        <label>
          <input type="checkbox" id="toggleReducer" checked />
          Enable LTTB downsampling
        </label>
        <label>
          Target points:
          <input
            type="number"
            id="targetPoints"
            class="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 class="stats" id="stats"></div>
    </div>

    <div id="chart"></div>
  </div>
</template>

<script>
import VueApexCharts from 'vue-apexcharts'
import ApexCharts from 'apexcharts'

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>'
}

export default {
components: {
apexchart: VueApexCharts,
},
data: function () {
return {
series: [{ name: 'Sensor reading', data: seriesData }],
chartOptions: {
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' } },
},
reducerChart: null,
reducerDisposed: false,
}
},
mounted: function () {
  // This demo destroys and recreates the chart imperatively on every re-render,
  // so it skips the vue-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.
  var me = this
  var chart = null

  // The vanilla build gets a mutable `options` global from the template; rebuild
  // it here from the parsed chart state.
  var options = Object.assign({ series: me.series }, me.chartOptions)
  options.chart = Object.assign({}, me.chartOptions.chart)
  options.chart.dataReducer = Object.assign({}, me.chartOptions.chart.dataReducer)

  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()
    if (chart) chart.destroy()
    chart = new ApexCharts(document.querySelector('#chart'), options)
    me.reducerChart = chart
    chart.render().then(function () {
      if (me.reducerDisposed) 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)
  me.reducerChart = chart
  chart.render().then(function () {
    if (me.reducerDisposed) return
    updateStats(chart)
  })
},
beforeDestroy: function () {
  this.reducerDisposed = true
  if (this.reducerChart) this.reducerChart.destroy()
},,
}
</script>

<style>
#chart {
  max-width: 1000px;
  margin: 20px auto;
}
.panel {
  max-width: 1000px;
  margin: 16px auto;
  font-family: sans-serif;
}
.controls {
  display: flex;
  gap: 14px;
  align-items: center;
  flex-wrap: wrap;
  margin-bottom: 8px;
}
.controls label {
  font-size: 14px;
}
.controls button {
  padding: 6px 14px;
  cursor: pointer;
}
.target-input {
  width: 80px;
}
.stats {
  font-family: monospace;
  font-size: 13px;
  color: #555;
  line-height: 1.6;
}
.stats b {
  color: #111;
}
.note {
  background: #f4f8ff;
  border-left: 3px solid #5b8cff;
  padding: 10px 14px;
  font-size: 13px;
  color: #234;
  margin-top: 10px;
  border-radius: 2px;
}
</style>
Large Dataset Downsampling - Vue Line Charts | ApexCharts.js | ApexCharts.js