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

// A 2D bubble scatter: each bubble is a city, placed by average income (x) and
// cost of living (y), sized by a third field and coloured by region. This is
// the unit chart's `layout: 'scatter'` in `y: 'value'` mode (each datum's own x
// + y on two numeric axes) with `sizeRange` turning the dots into area-scaled
// bubbles.
//
// The size toggle is the story: by POPULATION the megacities (Tokyo, Delhi,
// Shanghai, Sao Paulo) swell; by GDP the money centres (Tokyo, New York, London,
// Paris) lead instead. Every bubble tweens its size. Figures are approximate
// (public-data style). Shared by the vanilla-js / React / Vue builds.
var REGIONS = [
  { name: 'Americas', color: '#22C55E' },
  { name: 'Europe', color: '#0EA5E9' },
  { name: 'Asia', color: '#F59E0B' },
  { name: 'Oceania', color: '#F43F5E' },
]

// [city, avg income (US$/yr), cost of living (index, NYC = 100),
//  metro population (millions), metro GDP (US$ billions)]
var DATA = {
  Americas: [
    ['New York', 55000, 100, 18.9, 2000],
    ['San Francisco', 62000, 98, 4.7, 650],
    ['Toronto', 40000, 72, 6.4, 400],
    ['Sao Paulo', 12000, 40, 22.6, 480],
    ['Mexico City', 11000, 38, 22.0, 480],
  ],
  Europe: [
    ['Zurich', 72000, 118, 1.4, 340],
    ['London', 45000, 85, 14.3, 1000],
    ['Paris', 38000, 80, 11.1, 850],
    ['Berlin', 36000, 70, 6.1, 180],
    ['Moscow', 18000, 45, 12.6, 550],
  ],
  Asia: [
    ['Tokyo', 40000, 83, 37.4, 1600],
    ['Singapore', 55000, 95, 5.9, 470],
    ['Dubai', 45000, 70, 3.5, 130],
    ['Hong Kong', 42000, 90, 7.5, 380],
    ['Seoul', 34000, 78, 26.0, 780],
    ['Shanghai', 20000, 60, 27.0, 680],
    ['Mumbai', 8000, 35, 20.7, 310],
    ['Delhi', 7000, 32, 32.0, 370],
  ],
  Oceania: [
    ['Sydney', 48000, 88, 5.3, 400],
    ['Melbourne', 44000, 82, 5.0, 250],
  ],
}

// z = the bubble size field: 'population' (people) or 'gdp' (metro output).
function seriesFor(sizeBy) {
  return REGIONS.map(function (r) {
    return {
      name: r.name,
      data: DATA[r.name].map(function (c) {
        return {
          name: c[0],
          x: c[1],
          y: c[2],
          z: sizeBy === 'gdp' ? c[4] : c[3],
        }
      }),
    }
  })
}

var VIEWS = {
  population: {
    caption:
      'Sized by population, the megacities swell: Tokyo, Delhi, Shanghai and Sao Paulo dominate, even where incomes are low and living is cheap.',
  },
  gdp: {
    caption:
      'Sized by economic output, the money centres take over: New York, Tokyo, London and Paris lead, while populous but poorer cities shrink.',
  },
}

const ApexChart = () => {
  const [state, setState] = React.useState({
    series: seriesFor('population'),
    options: {
      chart: {
        id: 'cityBubbles',
        type: 'unit',
        height: 520,
        animations: {
          enabled: true,
          speed: 700,
        },
      },
      colors: REGIONS.map(function (r) {
        return r.color
      }),
      // Translucent fills so overlapping bubbles read through each other.
      fill: {
        opacity: 0.7,
      },
      legend: {
        position: 'bottom',
      },
      plotOptions: {
        unit: {
          layout: 'scatter',
          scatter: {
            y: 'value',
            xMin: 0,
            yMin: 0,
            xTitle: 'Average income (US$/yr)',
            yTitle: 'Cost of living (NYC = 100)',
            xFormatter: function (v) {
              return '$' + Math.round(v / 1000) + 'k'
            },
            sizeRange: [7, 44],
          },
          tooltip: {
            formatter: function (o) {
              var d = (o && o.datum) || {}
              if (!d.name) return ''
              return (
                d.name +
                ': $' +
                Math.round(d.x / 1000) +
                'k income, cost of living ' +
                d.y
              )
            },
          },
        },
      },
    },
  })

  React.useEffect(() => {
    // Reach the live instance by its chart.id, then wire the buttons. (Data lives
    // in the shared head script.) updateSeries drives the instance directly, so no
    // React state changes under it.
    let chart
    const timer = window.setInterval(() => {
      chart = ApexCharts.getChartByID('cityBubbles')
      if (!chart) return
      window.clearInterval(timer)

      const buttons = document.querySelectorAll('#size-nav button')
      const caption = document.getElementById('caption')

      const showSize = (key) => {
        buttons.forEach((b) =>
          b.classList.toggle('active', b.getAttribute('data-size') === key),
        )
        caption.textContent = VIEWS[key].caption
        chart.updateSeries(seriesFor(key))
      }

      buttons.forEach((btn) => {
        btn.addEventListener('click', () => {
          showSize(btn.getAttribute('data-size'))
        })
      })

      caption.textContent = VIEWS.population.caption
    }, 50)

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

  return (
    <div>
      <div className="wrap">
        <h1>Earnings versus the cost of living</h1>
        <p className="lead">
          Each bubble is a city: average income across the bottom, cost of
          living up the side, sized by population.
        </p>

        <div className="chart-wrap">
          <div id="chart">
            <ReactApexChart
              options={state.options}
              series={state.series}
              type="unit"
              height={520}
            />
          </div>
        </div>

        <div className="actions" id="size-nav">
          <button data-size="population" className="active">
            Size by population
          </button>
          <button data-size="gdp">Size by GDP</button>
        </div>

        <p className="caption" id="caption"></p>

        <div className="note">
          <code>plotOptions.unit.scatter.y: 'value'</code> switches the scatter
          layout to a 2D value-value plot: each datum's own <code>x</code> and{' '}
          <code>y</code>
          on two drawn numeric axes, coloured by category.{' '}
          <code>scatter.sizeRange</code>
          turns the dots into bubbles scaled by area from each datum's
          <code>sizeField</code> (here <code>z</code>). Switching the size
          metric is a plain <code>chart.updateSeries()</code>, so every bubble
          tweens its radius; hiding a region from the legend fades its bubbles
          out in place. The unit chart is a premium type; without a license it
          renders with a trial watermark. Figures are approximate.
        </div>
      </div>
    </div>
  )
}

export default ApexChart
City Bubbles - React Unit Charts | ApexCharts.js | ApexCharts.js