// A market map: every company is a tile, sized by market value and coloured by
// how its day went. Three levels, sector > industry > company, so the sectors
// read as blocks before you look at any single name.
//
// The figures are synthetic. A seeded generator (no Math.random, so the chart
// is stable across re-renders and SSR) draws caps from a heavy-tailed
// distribution and daily moves from a per-sector drift, which is what gives a
// real market map its handful of giants and long tail of small names.
// Shared by the vanilla-js / React / Vue builds.
var TAXONOMY = [
  {
    sector: 'Information Technology',
    weight: 30,
    drift: 0.9,
    industries: [
      { name: 'Semiconductors', n: 14, weight: 12 },
      { name: 'Software', n: 16, weight: 11 },
      { name: 'Hardware & Devices', n: 9, weight: 7 },
    ],
  },
  {
    sector: 'Financials',
    weight: 15,
    drift: -0.3,
    industries: [
      { name: 'Banks', n: 15, weight: 6 },
      { name: 'Insurance', n: 11, weight: 4 },
      { name: 'Capital Markets', n: 10, weight: 5 },
    ],
  },
  {
    sector: 'Health Care',
    weight: 14,
    drift: 0.2,
    industries: [
      { name: 'Pharmaceuticals', n: 12, weight: 6 },
      { name: 'Biotechnology', n: 14, weight: 4 },
      { name: 'Medical Devices', n: 10, weight: 4 },
    ],
  },
  {
    sector: 'Consumer Discretionary',
    weight: 11,
    drift: -0.6,
    industries: [
      { name: 'Retail', n: 13, weight: 5 },
      { name: 'Automobiles', n: 8, weight: 4 },
      { name: 'Hotels & Leisure', n: 9, weight: 2 },
    ],
  },
  {
    sector: 'Communication Services',
    weight: 9,
    drift: 1.4,
    industries: [
      { name: 'Interactive Media', n: 9, weight: 5 },
      { name: 'Telecom', n: 8, weight: 2 },
      { name: 'Entertainment', n: 9, weight: 2 },
    ],
  },
  {
    sector: 'Industrials',
    weight: 8,
    drift: 0.1,
    industries: [
      { name: 'Aerospace & Defence', n: 8, weight: 3 },
      { name: 'Machinery', n: 10, weight: 3 },
      { name: 'Transport', n: 9, weight: 2 },
    ],
  },
  {
    sector: 'Consumer Staples',
    weight: 6,
    drift: 0.4,
    industries: [
      { name: 'Food & Beverage', n: 11, weight: 3 },
      { name: 'Household Products', n: 8, weight: 3 },
    ],
  },
  {
    sector: 'Energy',
    weight: 5,
    drift: -1.7,
    industries: [
      { name: 'Oil & Gas', n: 12, weight: 4 },
      { name: 'Renewables', n: 8, weight: 1 },
    ],
  },
  {
    sector: 'Utilities',
    weight: 4,
    drift: -0.2,
    industries: [
      { name: 'Electric Utilities', n: 9, weight: 3 },
      { name: 'Water & Gas', n: 6, weight: 1 },
    ],
  },
  {
    sector: 'Real Estate',
    weight: 4,
    drift: -0.9,
    industries: [
      { name: 'REITs', n: 11, weight: 3 },
      { name: 'Property Services', n: 6, weight: 1 },
    ],
  },
  {
    sector: 'Materials',
    weight: 4,
    drift: 0.6,
    industries: [
      { name: 'Chemicals', n: 9, weight: 2 },
      { name: 'Metals & Mining', n: 9, weight: 2 },
    ],
  },
]

// Deterministic uniform stream (LCG), so every render draws the same market.
function rng(seed) {
  var s = seed >>> 0
  return function () {
    s = (s * 1664525 + 1013904223) >>> 0
    return s / 4294967296
  }
}

// Sum of 6 uniforms, centred: a cheap normal-ish draw.
function gauss(u) {
  var g = 0
  for (var k = 0; k < 6; k++) g += u()
  return (g - 3) / Math.sqrt(0.5)
}

var LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
function ticker(u) {
  var out = ''
  var len = 3 + (u() > 0.65 ? 1 : 0)
  for (var i = 0; i < len; i++) {
    out += LETTERS.charAt(Math.floor(u() * 26))
  }
  return out
}

var seen = {}
var u = rng(20260812)

var marketSeries = [
  {
    name: 'Market',
    data: TAXONOMY.map(function (sec) {
      return {
        x: sec.sector,
        children: sec.industries.map(function (ind) {
          var companies = []
          for (var i = 0; i < ind.n; i++) {
            // Heavy tail: a few names carry most of the industry's value.
            var scale = Math.exp(gauss(u) * 0.85)
            var cap = Math.max(4, Math.round((ind.weight * 90 * scale) / ind.n))
            // The day's move: the sector drifts, the name adds its own noise.
            var change = Math.round((sec.drift + gauss(u) * 1.6) * 100) / 100
            var t = ticker(u)
            while (seen[t]) t = ticker(u)
            seen[t] = true
            companies.push({ x: t, y: cap, colorValue: change })
          }
          return { x: ind.name, children: companies }
        }),
      }
    }),
  },
]

function fmtCap(v) {
  return v >= 1000 ? '$' + (v / 1000).toFixed(2) + 'T' : '$' + v + 'B'
}
function fmtPct(v) {
  return (v > 0 ? '+' : '') + v.toFixed(2) + '%'
}

var options = {
  series: marketSeries,
  chart: {
    id: 'marketMap',
    type: 'treemap',
    height: 620,
    fontFamily: 'inherit',
    background: 'transparent',
    animations: {
      enabled: true,
      speed: 500,
    },
    toolbar: {
      show: false,
    },
  },
  theme: {
    mode: 'dark',
  },
  title: {
    // Kept to one line: the breadcrumb that appears once you zoom in sits in the
    // band between the title and the plot, and a subtitle would squeeze it out.
    text: "Market map by sector - area is market value, colour is the day's move",
    align: 'left',
    style: {
      fontSize: '14px',
      fontWeight: 600,
    },
  },
  legend: {
    show: true,
    position: 'bottom',
  },
  stroke: {
    width: 1,
    colors: ['#0f1115'],
  },
  dataLabels: {
    enabled: true,
    style: {
      fontSize: '11px',
      fontWeight: 600,
    },
  },
  tooltip: {
    enabled: true,
    custom: function ({ seriesIndex, dataPointIndex, w }) {
      var d = w.config.series[seriesIndex].data[dataPointIndex]
      var up = d.colorValue >= 0
      return (
        '<div class="apexcharts-tooltip-title" style="font-weight:600">' +
        d.x +
        '</div>' +
        '<div style="padding:6px 10px 8px">' +
        fmtCap(d.y) +
        ' &nbsp;<span style="color:' +
        (up ? '#3ddc84' : '#ff6b5e') +
        '">' +
        fmtPct(d.colorValue) +
        '</span></div>'
      )
    },
  },
  plotOptions: {
    treemap: {
      borderRadius: 2,
      dataLabels: {
        format: 'truncate',
      },
      colorScale: {
        // Colour by the day's move, not by market value.
        colorValue: 'colorValue',
        gradient: {
          colors: ['#c0392b', '#5b6570', '#1e9e57'],
          midpoint: 0,
        },
        gradientLegend: {
          enabled: true,
          width: '46%',
          thickness: 10,
          formatter: function (v) {
            return (v > 0 ? '+' : '') + v.toFixed(1) + '%'
          },
        },
      },
      // Click a sector or an industry to fill the canvas with it.
      zoom: {
        enabled: true,
      },
      parents: {
        padding: 3,
        tooltip: {
          formatter: function (o) {
            return (
              '<div class="apexcharts-tooltip-title" style="font-weight:600">' +
              o.name +
              '</div>' +
              '<div style="padding:6px 10px 8px">' +
              fmtCap(o.value) +
              ' &nbsp;&middot;&nbsp; ' +
              o.leafCount +
              ' companies &nbsp;&middot;&nbsp; ' +
              o.percentOfTotal.toFixed(1) +
              '% of market</div>'
            )
          },
        },
      },
      levels: [
        {
          // Sector.
          padding: 5,
          header: {
            height: 26,
            minWidth: 64,
            style: {
              fontSize: '13px',
              fontWeight: 700,
            },
            formatter: function (name, o) {
              return name + '   ' + fmtCap(o.value)
            },
          },
        },
        {
          // Industry.
          padding: 2,
          header: {
            height: 15,
            minWidth: 46,
            style: {
              fontSize: '10px',
              fontWeight: 500,
            },
          },
        },
      ],
    },
  },
}

var chart = new ApexCharts(document.querySelector('#chart'), options)
chart.render()
Nested Market Map - JavaScript Treemap Charts | ApexCharts.js | ApexCharts.js