import React from 'react'
import ReactApexChart from 'react-apexcharts'
import ApexCharts from 'apexcharts'
import './styles.css'

// 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()
}

const ApexChart = () => {
  const [state, setState] = React.useState({
    series: [
      {
        name: 'Requests',
        data: LATENCY,
      },
    ],
    options: {
      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',
        },
      },
    },
  })

  React.useEffect(() => {
    // The react-apexcharts wrapper owns the render, so reach the live instance
    // by its chart.id before wiring the controls.
    const timer = window.setInterval(() => {
      const chart = ApexCharts.getChartByID('latency')
      if (!chart) return
      window.clearInterval(timer)
      wireHistogramControls(chart)
    }, 50)

    return () => window.clearInterval(timer)
  }, [])

  return (
    <div>
      <div className="wrap">
        <h1>Where does the time actually go?</h1>
        <p>
          Every one of these 1800 requests is a single number in the series. The
          chart does the binning, so the shape of the distribution is the data
          rather than something you had to compute first. Change the bin rule to
          see how much the story depends on it: too few bins hide the slow tail,
          too many turn it into noise. "Auto" picks a width from the sample
          itself.
        </p>

        <div className="actions">
          <button data-bins="auto" className="on">
            Auto
          </button>
          <button data-bins="12">12 bins</button>
          <button data-bins="30">30 bins</button>
          <button data-bins="80">80 bins</button>
          <label>
            <input type="checkbox" id="cumulative" />
            Cumulative
          </label>
        </div>

        <div className="chart-wrap">
          <div id="chart">
            <ReactApexChart
              options={state.options}
              series={state.series}
              type="histogram"
              height={400}
              width={700}
            />
          </div>
        </div>

        <div className="stats" id="stats"></div>
      </div>
    </div>
  )
}

export default ApexChart
Latency Distribution - React Histogram Charts | ApexCharts.js | ApexCharts.js