Hex Tile MapOpen in new tab
View source
// CDN build: `ApexMaps` is a global (see index.html). With a bundler:
//   import ApexMaps from 'apexmaps'

// Premium feature: runs with a watermark until licensed. ApexMaps.setLicense(key) clears it.

// A hex tile map (honeycomb, tilegram) is a cartogram, not geography: every
// region becomes one equal cell, so the smallest unit is as legible as the
// largest and land area stops deciding how loud a value looks.
//
// `layout` is a property of the geometry, not of the series. The cells are keyed
// exactly the way the boundary pack is keyed, so ONE dataset and one `joinBy`
// serve both representations: the two maps below share `series` verbatim.

const data = [
  { key: 'CA', value: 88 }, { key: 'TX', value: 71 }, { key: 'FL', value: 64 },
  { key: 'NY', value: 79 }, { key: 'PA', value: 46 }, { key: 'IL', value: 52 },
  { key: 'OH', value: 41 }, { key: 'GA', value: 58 }, { key: 'NC', value: 55 },
  { key: 'MI', value: 38 }, { key: 'NJ', value: 74 }, { key: 'VA', value: 61 },
  { key: 'WA', value: 69 }, { key: 'AZ', value: 49 }, { key: 'MA', value: 82 },
  { key: 'TN', value: 44 }, { key: 'IN', value: 35 }, { key: 'MO', value: 33 },
  { key: 'MD', value: 76 }, { key: 'WI', value: 39 }, { key: 'CO', value: 63 },
  { key: 'MN', value: 51 }, { key: 'SC', value: 42 }, { key: 'AL', value: 28 },
  { key: 'LA', value: 31 }, { key: 'KY', value: 26 }, { key: 'OR', value: 57 },
  { key: 'OK', value: 24 }, { key: 'CT', value: 72 }, { key: 'UT', value: 47 },
  { key: 'IA', value: 34 }, { key: 'NV', value: 45 }, { key: 'AR', value: 22 },
  { key: 'MS', value: 19 }, { key: 'KS', value: 29 }, { key: 'NM', value: 27 },
  { key: 'NE', value: 32 }, { key: 'ID', value: 36 }, { key: 'WV', value: 17 },
  { key: 'HI', value: 66 }, { key: 'NH', value: 68 }, { key: 'ME', value: 43 },
  { key: 'RI', value: 59 }, { key: 'MT', value: 25 }, { key: 'DE', value: 62 },
  { key: 'SD', value: 21 }, { key: 'ND', value: 23 }, { key: 'AK', value: 37 },
  { key: 'VT', value: 54 }, { key: 'WY', value: 18 }, { key: 'DC', value: 91 },
]

// Identical for both maps. `abbr` is the join key the us/states boundary pack
// recommends, and the hex layout is keyed on it too, which is the point.
const series = [
  {
    name: 'Illustrative index',
    joinBy: ['abbr', 'key'],
    data,
    scale: { palette: 'blues', classes: 5 },
    stroke: { color: '#ffffff', width: 1.4 },
  },
]

/* ---- one cell per state -------------------------------------------------- */
const hex = new ApexMaps(document.getElementById('hex'), {
  chart: { height: 380 },
  geo: { map: 'us', layout: 'hex' },
  series,
  // Cells are small and uniform, so neither label guard earns its keep: an area
  // floor would cull every label, and collision resolution has nothing to
  // resolve on a lattice. The labels themselves are the postal abbreviations,
  // because that is what the layout pack recommends: a cell sized for Rhode
  // Island cannot hold "Rhode Island", and keys fit in all 51.
  dataLabels: { enabled: true, minFeatureArea: 0, collision: 'none' },
  legend: { position: 'bottom', title: 'Illustrative index' },
})

/* ---- the same series on real boundaries ---------------------------------- */
const geo = new ApexMaps(document.getElementById('geo'), {
  chart: { height: 380 },
  geo: { map: 'us' },
  series,
  legend: { position: 'bottom', title: 'Illustrative index' },
})

/* ---- a four-cell layout of our own --------------------------------------- */
// `cells` is `key -> [col, row]`, row 0 north and col 0 west, keyed by whatever
// field your boundary pack joins on. Registering is free; rendering a layout is
// the licensed part, your own table included.
ApexMaps.registerLayout('demo/quad@hex', {
  keyField: 'code',
  cells: { NW: [0, 0], NE: [1, 0], SW: [0, 1], SE: [1, 1] },
  names: {
    NW: 'North west',
    NE: 'North east',
    SW: 'South west',
    SE: 'South east',
  },
})

const custom = new ApexMaps(document.getElementById('custom'), {
  chart: { height: 240 },
  geo: { map: 'demo/quad@hex' },
  series: [
    {
      name: 'Utilisation',
      joinBy: ['code', 'key'],
      data: [
        { key: 'NW', value: 74 },
        { key: 'NE', value: 41 },
        { key: 'SW', value: 58 },
        { key: 'SE', value: 22 },
      ],
      scale: { palette: 'oranges', classes: 4 },
    },
  ],
  dataLabels: { enabled: true, minFeatureArea: 0, collision: 'none' },
})

Promise.all([hex.render(), geo.render(), custom.render()])

/* ---- the morph: geography and back, on one instance ---------------------- */
// Toggling `layout` through updateOptions walks each region between its outline
// and its cell rather than swapping them, which is the answer to the one real
// problem with a cartogram: a reader who cannot navigate a honeycomb can
// navigate the map it grew out of, and watching Texas walk to its cell is what
// tells them which cell is Texas. Automatic, with no option to enable; it runs
// off `chart.animations` and stands down above the motion budget.
let onHex = true
document.getElementById('toggle').addEventListener('click', async () => {
  onHex = !onHex
  await hex.updateOptions({ geo: { map: 'us', layout: onHex ? 'hex' : null } })
})