Latency Distribution in JavaScript

Using ApexCharts with JavaScript

This Latency Distribution 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
// Shared by the vanilla-js, React and Vue builds.
//
// The series carries RAW OBSERVATIONS, one number per request, and the chart
// bins them itself. That is the whole point of the type: you hand it a sample,
// not a pre-aggregated table.
//
// 1800 response times with the shape real latency has: a tight body around
// 120ms and a long right tail of slow requests. Generated from a seeded
// generator so the page is deterministic.
var LATENCY = (function () {
  var seed = 20260811
  function rand() {
    seed = (seed * 16807) % 2147483647
    return (seed - 1) / 2147483646
  }
  var out = []
  for (var i = 0; i < 1800; i++) {
    // Box-Muller into a log-normal: the standard shape for service latency.
    var u1 = Math.max(rand(), 1e-9)
    var u2 = rand()
    var z = Math.sqrt(-2 * Math.log(u1)) * Math.cos(2 * Math.PI * u2)
    out.push(Math.round(Math.exp(4.8 + z * 0.42) * 10) / 10)
  }
  return out
})()

function percentile(values, p) {
  var sorted = values.slice().sort(function (a, b) {
    return a - b
  })
  var pos = (sorted.length - 1) * p
  var lo = Math.floor(pos)
  var hi = Math.ceil(pos)
  if (lo === hi) return sorted[lo]
  return sorted[lo] + (sorted[hi] - sorted[lo]) * (pos - lo)
}

function renderStats() {
  var el = document.querySelector('#stats')
  if (!el) return
  el.innerHTML =
    '<b>' +
    LATENCY.length +
    '</b> requests &middot; ' +
    'median <b>' +
    percentile(LATENCY, 0.5).toFixed(0) +
    ' ms</b> &middot; ' +
    'p95 <b>' +
    percentile(LATENCY, 0.95).toFixed(0) +
    ' ms</b> &middot; ' +
    'slowest <b>' +
    Math.max.apply(null, LATENCY).toFixed(0) +
    ' ms</b>'
}

// Wires the bin-rule buttons and the cumulative toggle to a live chart. Shared
// by all three framework builds.
function wireHistogramControls(chart) {
  var buttons = [].slice.call(document.querySelectorAll('[data-bins]'))
  var cumulative = false
  var bins = 'auto'

  function apply(next) {
    buttons.forEach(function (b) {
      b.className = b.getAttribute('data-bins') === String(next) ? 'on' : ''
    })
    bins = next === 'auto' ? 'auto' : parseInt(next, 10)
    chart.updateOptions({
      plotOptions: { histogram: { bins: bins, cumulative: cumulative } },
      yaxis: {
        title: { text: cumulative ? 'Requests (cumulative)' : 'Requests' },
      },
    })
  }

  buttons.forEach(function (b) {
    b.addEventListener('click', function () {
      apply(b.getAttribute('data-bins'))
    })
  })

  var cb = document.querySelector('#cumulative')
  if (cb) {
    cb.addEventListener('change', function (e) {
      cumulative = e.target.checked
      apply(bins === 'auto' ? 'auto' : String(bins))
    })
  }

  renderStats()
}

var options = {
  series: [
    {
      name: 'Requests',
      data: LATENCY,
    },
  ],
  chart: {
    id: 'latency',
    type: 'histogram',
    // Fixed width, not responsive: 35 thin bars make every bar edge a hairline,
    // so a page-width nudge of a pixel or two (a scrollbar appearing as the
    // stats line renders) visibly moves the whole distribution.
    width: 700,
    height: 400,
    toolbar: {
      show: false,
    },
  },
  plotOptions: {
    histogram: {
      // 'auto' takes the narrower of Freedman-Diaconis and Sturges. Swap in a
      // number for a fixed bin count, or binWidth for fixed boundaries.
      bins: 'auto',
    },
  },
  colors: ['#008FFB'],
  fill: {
    type: 'gradient',
    gradient: {
      shadeIntensity: 0.25,
      opacityFrom: 0.95,
      opacityTo: 0.75,
      stops: [0, 100],
    },
  },
  legend: {
    show: false,
  },
  xaxis: {
    title: {
      text: 'Response time (ms)',
    },
    labels: {
      formatter: function (val) {
        return Math.round(val)
      },
    },
  },
  yaxis: {
    title: {
      text: 'Requests',
    },
  },
}

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

// LATENCY and wireHistogramControls live in the shared head script.
wireHistogramControls(chart)