Averages Past the Whole in React

Using ApexCharts with React

This Averages Past the Whole example wires ApexCharts into React through the react-apexcharts component.

Install it with npm install react-apexcharts apexcharts, then mount the chart with <Chart type="..." options={options} series={series} />.

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

import 'apexcharts/features/highlight-filter'

// Shared by the vanilla-js, React and Vue builds.

function money(v) {
  return (v < 0 ? '-$' : '$') + Math.abs(Math.round(v))
}
//
// Average profit per order by store, across all products (the wholes), and the
// same average over one product line (the parts). An average over a subset
// can be larger than the overall one, or fall on the other side of zero, so
// the parts here do both.
var STORES = ['Seattle', 'Denver', 'Austin', 'Chicago', 'Boston', 'Miami']
var ALL = [42, -18, 31, 24, 12, -9]
var LINES = {
  premium: [
    { x: 'Seattle', y: 42, highlight: 61 },
    { x: 'Denver', y: -18, highlight: -4 },
    { x: 'Austin', y: 31, highlight: 48 },
    { x: 'Chicago', y: 24, highlight: 19 },
    { x: 'Boston', y: 12, highlight: 27 },
    { x: 'Miami', y: -9, highlight: 6 },
  ],
  budget: [
    { x: 'Seattle', y: 42, highlight: 18 },
    { x: 'Denver', y: -18, highlight: -26 },
    { x: 'Austin', y: 31, highlight: 12 },
    { x: 'Chicago', y: 24, highlight: 28 },
    { x: 'Boston', y: 12, highlight: -3 },
    { x: 'Miami', y: -9, highlight: -21 },
  ],
}

function seriesFor(line) {
  return [
    {
      name: 'Avg profit per order',
      data: line
        ? LINES[line]
        : STORES.map(function (s, i) {
            return { x: s, y: ALL[i] }
          }),
    },
  ]
}

function pickLine(chart, line) {
  document.querySelectorAll('[data-line]').forEach(function (b) {
    b.classList.toggle('active', b.getAttribute('data-line') === line)
  })
  return chart.updateSeries(seriesFor(line))
}

function wireLines(chart) {
  document.querySelectorAll('[data-line]').forEach(function (b) {
    b.addEventListener('click', function () {
      pickLine(chart, b.getAttribute('data-line'))
    })
  })
}

const ApexChart = () => {
  const [state, setState] = React.useState({
    series: seriesFor('premium'),
    options: {
      chart: {
        id: 'profit',
        type: 'bar',
        height: 360,
        toolbar: { show: false },
      },
      colors: ['#0d9488'],
      plotOptions: {
        bar: { horizontal: true, barHeight: '62%', borderRadius: 4 },
      },
      dataLabels: {
        enabled: true,
        formatter: money,
      },
      xaxis: {
        labels: { formatter: money },
      },
      grid: {
        xaxis: { lines: { show: true } },
        yaxis: { lines: { show: false } },
      },
      tooltip: {
        y: { formatter: money },
      },
    },
  })

  React.useEffect(() => {
    // The react-apexcharts wrapper owns the render, so reach the live instance
    // by its chart.id. (seriesFor / pickLine live in the shared head script.)
    const timer = window.setInterval(() => {
      const chart = ApexCharts.getChartByID('profit')
      if (!chart) return
      window.clearInterval(timer)
      wireLines(chart)
    }, 50)
    return () => window.clearInterval(timer)
  }, [])

  return (
    <div>
      <div className="wrap">
        <h1>Averages can outgrow the whole</h1>
        <p>
          Each bar is a store's average profit per order across every product.
          Pick a product line to draw that line's own average solid in front.
          Where it beats the overall average, the axis stretches to fit it and a
          dashed edge marks where the overall average ends; where it loses
          money, it is drawn on its own side of zero.
        </p>
        <div className="actions">
          <span>Product line</span>
          <button data-line="premium" className="active">
            Premium
          </button>
          <button data-line="budget">Budget</button>
          <button data-line="">All (clear)</button>
        </div>
        <div className="chart-wrap">
          <div id="chart">
            <ReactApexChart
              options={state.options}
              series={state.series}
              type="bar"
              height={360}
            />
          </div>
        </div>
      </div>
    </div>
  )
}

export default ApexChart