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

// One field of 600 marathon finishers, shown three ways on a single unit chart:
// as five regional clusters, as one packed field, then regrouped into six
// finish-time bars. The design rule that makes it a story rather than three
// separate charts: every beat sets plotOptions.unit.transition:'flow', so the
// SAME dots are keyed by global order and GLIDE (and recolour) from one
// arrangement into the next instead of being rebuilt. These definitions are
// shared by the vanilla-js, React and Vue builds.
var REGION_LABELS = ['Africa', 'Europe', 'Americas', 'Asia', 'Oceania']
var REGION_COLORS = ['#F2994A', '#2D9CDB', '#27AE60', '#EB5757', '#9B51E0']
var REGION_COUNT = [100, 170, 150, 140, 40] // 600 finishers

var FINISH_LABELS = [
  'Sub-3:00',
  '3:00-3:30',
  '3:30-4:00',
  '4:00-4:30',
  '4:30-5:00',
  '5:00+',
]
var FINISH_COLORS = [
  '#1E8E5A',
  '#35B37E',
  '#86C82B',
  '#F2C037',
  '#F2994A',
  '#EB5757',
]
var FINISH_COUNT = [45, 120, 165, 140, 85, 45] // also 600

// A bare ViewState shared by every beat: the unit chart has no axes to window,
// so each beat only needs to describe its own data + layout in `options`.
var LIGHT = { theme: { mode: 'light' } }

var BEATS = [
  {
    selector: '#sb-step-1',
    view: LIGHT,
    options: {
      series: REGION_COUNT,
      labels: REGION_LABELS,
      colors: REGION_COLORS,
      plotOptions: { unit: { layout: 'grouped', transition: 'flow' } },
    },
    announce: 'The field by region: five clusters of finishers',
  },
  {
    selector: '#sb-step-2',
    view: LIGHT,
    options: {
      series: REGION_COUNT,
      labels: REGION_LABELS,
      colors: REGION_COLORS,
      plotOptions: { unit: { layout: 'packed', transition: 'flow' } },
    },
    announce:
      'One field: the same finishers packed together, smallest region centred',
  },
  {
    selector: '#sb-step-3',
    view: LIGHT,
    options: {
      series: FINISH_COUNT,
      labels: FINISH_LABELS,
      colors: FINISH_COLORS,
      plotOptions: { unit: { layout: 'columns', transition: 'flow' } },
    },
    announce: 'Regrouped by finish time: the finishers flow into six bars',
  },
]

const ApexChart = () => {
  const [state, setState] = React.useState({
    series: [100, 170, 150, 140, 40],
    options: {
      chart: {
        id: 'marathonStory',
        type: 'unit',
        height: 420,
        fontFamily: 'Helvetica, Arial, sans-serif',
        animations: {
          enabled: true,
          speed: 850,
        },
        toolbar: { show: false },
      },
      labels: ['Africa', 'Europe', 'Americas', 'Asia', 'Oceania'],
      colors: ['#F2994A', '#2D9CDB', '#27AE60', '#EB5757', '#9B51E0'],
      legend: {
        position: 'bottom',
      },
      plotOptions: {
        unit: {
          layout: 'grouped',
          transition: 'flow',
          // A fixed dot size keeps every dot the SAME size in all three layouts, so
          // dots only move and recolour across the story (they never resize). The
          // size is set by the tightest layout: 600 dots must fit the packed field
          // and the widest region cluster, so the dots are small on purpose.
          size: 2.3,
          spacing: 1.15,
        },
      },
    },
  })

  React.useEffect(() => {
    // The react-apexcharts wrapper owns the render, so reach the live instance by
    // its chart.id. Poll until it exists, then wire the same story the vanilla
    // build does. (The labels, colors, counts and BEATS live in the shared head
    // script.) The storyboard drives the chart instance directly, so no React
    // state changes and the <ReactApexChart> is never re-rendered under it.
    let chart
    const timer = window.setInterval(() => {
      chart = ApexCharts.getChartByID('marathonStory')
      if (!chart) return
      window.clearInterval(timer)

      const steps = document.querySelectorAll('.sb-step')
      const dots = document.querySelectorAll('.sb-dot')
      const chip = document.getElementById('sb-chip')
      const prevBtn = document.getElementById('sb-prev')
      const nextBtn = document.getElementById('sb-next')
      const scroller = document.getElementById('sb-scroller')
      let current = 0

      // Center a beat's step inside the story column WITHOUT scrolling the page:
      // adjust only the panel's own scrollTop. The IntersectionObserver then
      // activates that beat, so the controls and the scroll stay in sync.
      const scrollToBeat = (i) => {
        const step = document.getElementById('sb-step-' + (i + 1))
        if (!step || !scroller) return
        const sRect = step.getBoundingClientRect()
        const cRect = scroller.getBoundingClientRect()
        scroller.scrollTop +=
          sRect.top -
          cRect.top -
          (scroller.clientHeight - step.clientHeight) / 2
      }
      const goToBeat = (i) => {
        i = Math.max(0, Math.min(BEATS.length - 1, i))
        chart.storyboard.goTo(i)
        scrollToBeat(i)
      }

      chart.addEventListener('beatChange', (c, info) => {
        current = info.index
        steps.forEach((el, i) =>
          el.classList.toggle('is-active', i === info.index),
        )
        dots.forEach((dot, i) =>
          dot.classList.toggle('is-active', i === info.index),
        )
        chip.textContent = 'Beat ' + (info.index + 1) + ' of ' + BEATS.length
        prevBtn.disabled = info.index === 0
        nextBtn.disabled = info.index === BEATS.length - 1
      })

      dots.forEach((dot, i) => {
        dot.addEventListener('click', () => goToBeat(i))
      })
      prevBtn.addEventListener('click', () => goToBeat(current - 1))
      nextBtn.addEventListener('click', () => goToBeat(current + 1))

      // scroller binds the observer to the story column, so the story is driven
      // by that panel's own scroll (not the page/iframe viewport).
      chart.storyboard.bind({ beats: BEATS, scroller: '#sb-scroller' })
    }, 50)

    return () => {
      window.clearInterval(timer)
      if (chart && chart.storyboard) chart.storyboard.unbind()
    }
  }, [])

  return (
    <div>
      <div className="sb-wrap">
        <div className="sb-hero">
          <h1>600 finishers, one chart</h1>
          <p>
            A scroll-driven story on a single unit chart. The same dots start as
            five regional clusters, gather into one field, then flow apart into
            six finish-time bars. Scroll back up to rewind. Not one dot is
            destroyed or created; they only rearrange.
          </p>
        </div>

        <div className="sb-card">
          <div className="sb-layout">
            <div className="sb-scroller" id="sb-scroller">
              <div className="sb-steps">
                <section className="sb-step" id="sb-step-1">
                  <div className="card">
                    <h3>Five regions</h3>
                    <p>
                      The 600 finishers split into five clusters by home region:
                      Africa, Europe, the Americas, Asia and Oceania. One dot is
                      one runner.
                    </p>
                  </div>
                </section>
                <section className="sb-step" id="sb-step-2">
                  <div className="card">
                    <h3>One field</h3>
                    <p>
                      The five clusters gather into a single packed field. The
                      small Oceania group nests in the centre, the larger
                      regions wrap around it.
                    </p>
                  </div>
                </section>
                <section className="sb-step" id="sb-step-3">
                  <div className="card">
                    <h3>By finish time</h3>
                    <p>
                      The same finishers flow apart into six bars by finishing
                      time, each dot travelling from its old spot and
                      recolouring on the way. The four-hour pack towers over the
                      fast and the slow.
                    </p>
                  </div>
                </section>
              </div>
            </div>

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

              <div className="sb-head">
                <div>
                  <b>Marathon field</b>
                  <span className="sb-chip" id="sb-chip">
                    Beat 1 of 3
                  </span>
                </div>
                <div className="sb-nav">
                  <button
                    className="sb-btn"
                    id="sb-prev"
                    type="button"
                    disabled
                  >
                    Prev
                  </button>
                  <button
                    className="sb-dot is-active"
                    aria-label="Go to beat 1"
                  ></button>
                  <button className="sb-dot" aria-label="Go to beat 2"></button>
                  <button className="sb-dot" aria-label="Go to beat 3"></button>
                  <button className="sb-btn" id="sb-next" type="button">
                    Next
                  </button>
                </div>
              </div>
            </div>
          </div>
        </div>

        <p className="sb-hint">
          Scroll the story, or use the beat dots and Prev/Next. Each beat is a
          saved view; scrolling back up reverses it.
        </p>

        <div className="sb-note">
          <code>chart.storyboard.bind(&#123; beats, scroller &#125;)</code>{' '}
          pairs prose elements with views; here <code>scroller</code> points the
          observer at the story column, so a beat applies as its step crosses
          the middle of that panel (IntersectionObserver, no scroll listeners)
          and re-applies in reverse on the way up. Every beat's{' '}
          <code>options</code> payload sets
          <code>plotOptions.unit.transition: 'flow'</code>, which keys the dots
          by global order: the SAME dot migrates (and recolours) from a regional
          cluster to the packed field to a finish-time bar rather than fading
          out and back in. Transitions are cut instead of animated under
          prefers-reduced-motion. The unit chart is a premium type; without a
          license it renders with a trial watermark. Needs the{' '}
          <code>storyboard</code> feature (bundles
          <code>perspectives</code>).
        </div>
      </div>
    </div>
  )
}

export default ApexChart
Marathon Storyboard - React Unit Charts | ApexCharts.js | ApexCharts.js