var pointCount = 50000

// Box-Muller gaussian: gives two legible clusters at any density instead of
// uniform noise, so the plotted shape stays readable whether it is 2,000 or
// 100,000 points.
function gauss(mean, sd) {
  var u = 1 - Math.random()
  var v = Math.random()
  return mean + sd * Math.sqrt(-2 * Math.log(u)) * Math.cos(2 * Math.PI * v)
}

function generateClusters(count) {
  var half = Math.floor(count / 2)
  var a = new Array(half)
  var b = new Array(count - half)
  for (var i = 0; i < a.length; i++) {
    a[i] = [+gauss(32, 9).toFixed(2), +gauss(62, 15).toFixed(2)]
  }
  for (var j = 0; j < b.length; j++) {
    b[j] = [+gauss(70, 10).toFixed(2), +gauss(34, 13).toFixed(2)]
  }
  return [a, b]
}

var clusters = generateClusters(pointCount)
var lastRenderMs = 0

// Every SVG mark is a live DOM node, so counting nodes inside the chart makes
// the hybrid renderer visible: canvas keeps this in the dozens no matter how
// many points are plotted.
function domNodeCount() {
  var el = document.querySelector('#chart')
  return el ? el.getElementsByTagName('*').length : 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 el = document.getElementById('stats')
  if (!el || !chart) return
  var active = chart.getActiveRenderer ? chart.getActiveRenderer() : 'svg'
  el.innerHTML =
    'Points: <b>' +
    pointCount.toLocaleString() +
    '</b> &nbsp;·&nbsp; Active renderer: <b>' +
    active.toUpperCase() +
    '</b> &nbsp;·&nbsp; DOM nodes in chart: <b>' +
    domNodeCount().toLocaleString() +
    '</b> &nbsp;·&nbsp; Render: <b>' +
    (lastRenderMs > 0 ? lastRenderMs + ' ms' : 'change a control to time it') +
    '</b>'
}

var options = {
  series: [
    { name: 'Cluster A', data: clusters[0] },
    { name: 'Cluster B', data: clusters[1] },
  ],
  chart: {
    height: 480,
    type: 'scatter',
    renderer: 'auto',
    rendererThreshold: 8000,
    animations: { enabled: false },
    zoom: { enabled: true, type: 'xy' },
    toolbar: { autoSelected: 'zoom' },
  },
  markers: { size: 3, strokeWidth: 0 },
  colors: ['#008FFB', '#FF4560'],
  title: {
    text: 'Scatter: 50,000 points on the auto-selected renderer',
    align: 'left',
  },
  subtitle: {
    text: 'Dense series marks paint to a canvas layer; axes, grid, zoom and tooltip stay SVG',
    align: 'left',
  },
  xaxis: {
    type: 'numeric',
    tickAmount: 10,
    labels: {
      formatter: function (val) {
        return parseFloat(val).toFixed(0)
      },
    },
  },
  yaxis: {
    tickAmount: 8,
    labels: {
      formatter: function (val) {
        return val.toFixed(0)
      },
    },
  },
  legend: { position: 'top' },
  tooltip: { shared: true, intersect: false },
}

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

// The template starts the first render; poll one frame for the instance, then
// fill in the live metrics.
requestAnimationFrame(function poll() {
  if (chart && chart.w && chart.w.globals && chart.w.globals.series) {
    updateStats(chart)
  } else {
    requestAnimationFrame(poll)
  }
})

function rebuild() {
  pointCount = parseInt(document.getElementById('pointCount').value, 10)
  var mode = document.getElementById('rendererMode').value

  clusters = generateClusters(pointCount)
  options.chart.renderer = mode
  options.series = [
    { name: 'Cluster A', data: clusters[0] },
    { name: 'Cluster B', data: clusters[1] },
  ]
  options.title.text =
    'Scatter: ' +
    pointCount.toLocaleString() +
    ' points on the ' +
    (mode === 'auto' ? 'auto-selected' : mode) +
    ' renderer'

  var t = performance.now()
  chart.destroy()
  chart = new ApexCharts(document.querySelector('#chart'), options)
  chart.render().then(function () {
    lastRenderMs = Math.round(performance.now() - t)
    updateStats(chart)
  })
}

document.getElementById('rerender').addEventListener('click', rebuild)
document.getElementById('rendererMode').addEventListener('change', rebuild)
document.getElementById('pointCount').addEventListener('change', rebuild)
document.getElementById('resetZoom').addEventListener('click', function () {
  if (chart) chart.resetSeries(true, true)
})
Canvas Renderer (High-Density) - JavaScript Scatter Charts | ApexCharts.js | ApexCharts.js