// Deterministic trade records (one row per trade). No randomness, but the
// distributions are intentionally uneven so the crossfilter is visible:
//   - quarters have different totals (the donut is 18 / 12 / 16 / 14, not 4x15)
//   - each quarter peaks on a different weekday
//   - outcome leans Gain early in the week and Loss later
// So clicking a quarter OR an outcome noticeably reshapes the day bar below.
function tradesData() {
  var days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']

  // quarter -> trades per weekday (Mon..Fri); column totals differ per quarter.
  var plan = {
    Q1: [8, 4, 3, 2, 1], // early-week heavy
    Q2: [1, 2, 5, 3, 1], // mid-week heavy
    Q3: [2, 2, 3, 4, 5], // late-week heavy
    Q4: [3, 6, 2, 2, 1], // Tuesday spike
  }
  // Share of Gains by weekday index (Mon..Fri): high early, low late.
  var gainByDay = [0.85, 0.7, 0.5, 0.3, 0.15]

  var out = []
  Object.keys(plan).forEach(function (q) {
    plan[q].forEach(function (count, di) {
      var gains = Math.round(count * gainByDay[di])
      for (var k = 0; k < count; k++) {
        out.push({
          q: q,
          day: days[di],
          gl: k < gains ? 'Gain' : 'Loss',
        })
      }
    })
  })
  return out
}

// Register the shared record set BEFORE the charts are constructed, so each
// chart's initial paint is already the aggregation (no empty flash).
ApexCharts.crossfilter({ id: 'trades', records: tradesData() })

// Readout formatter, shared by the vanilla-js, React and Vue builds.
function fmtFilters(state) {
  var keys = Object.keys(state.filters)
  if (!keys.length) return 'No filter (all ' + state.total + ' trades)'
  var parts = keys.map(function (id) {
    return id + ': ' + state.filters[id].join(', ')
  })
  return (
    parts.join('  |  ') +
    '   ->   ' +
    state.filteredCount +
    ' / ' +
    state.total +
    ' trades'
  )
}

var options = {
  series: [],
  chart: {
    id: 'byQuarter',
    type: 'donut',
    height: 300,
    fontFamily: 'Helvetica, Arial, sans-serif',
    animations: { speed: 500 },
    link: {
      id: 'trades',
      dimension: function (r) {
        return r.q
      },
      reduce: 'count',
      dimOpacity: 0.18,
    },
  },
  title: { text: 'By quarter', align: 'left' },
  legend: { position: 'bottom' },
  plotOptions: { pie: { expandOnClick: false } },
  dataLabels: {
    enabled: true,
    formatter: function (val, opts) {
      return opts.w.config.series[opts.seriesIndex]
    },
    style: { colors: ['#334155'], fontWeight: 600 },
    dropShadow: { enabled: false },
  },
  colors: ['#2563EB', '#38bdf8', '#4ade80', '#fbbf24'],
  stroke: { width: 2, colors: ['#fff'] },
}

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

var options1 = {
  series: [],
  chart: {
    id: 'byOutcome',
    type: 'donut',
    height: 300,
    fontFamily: 'Helvetica, Arial, sans-serif',
    animations: { speed: 500 },
    link: {
      id: 'trades',
      dimension: function (r) {
        return r.gl
      },
      reduce: 'count',
      order: 'asc', // Gain before Loss, so the colors below map semantically
      dimOpacity: 0.18,
    },
  },
  title: { text: 'By outcome', align: 'left' },
  legend: { position: 'bottom' },
  plotOptions: { pie: { expandOnClick: false } },
  dataLabels: {
    enabled: true,
    formatter: function (val, opts) {
      return opts.w.config.series[opts.seriesIndex]
    },
    style: { colors: ['#334155'], fontWeight: 600 },
    dropShadow: { enabled: false },
  },
  colors: ['#4ade80', '#f87171'],
  stroke: { width: 2, colors: ['#fff'] },
}

var chart1 = new ApexCharts(document.querySelector('#chart2'), options1)
chart1.render()

var options2 = {
  series: [],
  chart: {
    id: 'byDay',
    type: 'bar',
    height: 280,
    fontFamily: 'Helvetica, Arial, sans-serif',
    animations: { speed: 500 },
    link: {
      id: 'trades',
      dimension: function (r) {
        return r.day
      },
      reduce: 'count',
      seriesName: 'Trades',
      // `order` also takes a comparator: keep the weekdays in calendar order
      // instead of the order they first appear in the records.
      order: function (a, b) {
        var days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']
        return days.indexOf(a) - days.indexOf(b)
      },
      dimOpacity: 0.18,
    },
  },
  title: { text: 'By day of week (click a bar too)', align: 'left' },
  plotOptions: {
    bar: { columnWidth: '55%', borderRadius: 3, distributed: true },
  },
  legend: { show: false },
  dataLabels: { enabled: false },
  colors: ['#2563EB', '#38bdf8', '#4ade80', '#fbbf24', '#f472b6'],
}

var chart2 = new ApexCharts(document.querySelector('#chart3'), options2)
chart2.render()

// charts[0]=byQuarter, charts[1]=byOutcome, charts[2]=byDay.
// The generated file exposes them as chart, chart1, chart2. The wiring talks to
// the crossfilter engine (not the chart instances), and fmtFilters lives in the
// shared head script.
var cf = ApexCharts.getCrossfilter('trades')
var readout = document.getElementById('cf-readout')

if (cf) {
  cf.on('change', function (state) {
    readout.textContent = fmtFilters(state)
  })
}

document.getElementById('cf-reset').addEventListener('click', function () {
  if (cf) cf.reset()
})
Crossfilter (Categorical) - JavaScript Interactivity | ApexCharts.js | ApexCharts.js