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 pixel map is a choropleth with a different mark: the same rows, the same
// join and the same scale, drawn as a lattice of dots inside each region
// instead of a flat fill. Change `type: 'pixel'` back to a choropleth and the
// rest of the config still works.
//
// Your data. `joinBy: 'name'` matches each row to a country by its name, and a
// country without a row is drawn in the null colour. Illustrative values.
const data = [
  { name: 'United States of America', value: 63 },
  { name: 'Canada', value: 41 },
  { name: 'Mexico', value: 52 },
  { name: 'Brazil', value: 72 },
  { name: 'Argentina', value: 38 },
  { name: 'Chile', value: 61 },
  { name: 'Colombia', value: 47 },
  { name: 'Peru', value: 35 },
  { name: 'United Kingdom', value: 84 },
  { name: 'France', value: 77 },
  { name: 'Germany', value: 69 },
  { name: 'Spain', value: 58 },
  { name: 'Italy', value: 54 },
  { name: 'Poland', value: 49 },
  { name: 'Sweden', value: 81 },
  { name: 'Norway', value: 88 },
  { name: 'Finland', value: 79 },
  { name: 'Ukraine', value: 33 },
  { name: 'Turkey', value: 45 },
  { name: 'Nigeria', value: 55 },
  { name: 'Egypt', value: 48 },
  { name: 'South Africa', value: 51 },
  { name: 'Kenya', value: 43 },
  { name: 'Ethiopia', value: 29 },
  { name: 'Algeria', value: 37 },
  { name: 'Saudi Arabia', value: 62 },
  { name: 'Iran', value: 40 },
  { name: 'India', value: 66 },
  { name: 'Pakistan', value: 31 },
  { name: 'China', value: 71 },
  { name: 'Mongolia', value: 27 },
  { name: 'Japan', value: 80 },
  { name: 'South Korea', value: 76 },
  { name: 'Indonesia', value: 57 },
  { name: 'Thailand', value: 53 },
  { name: 'Vietnam', value: 50 },
  { name: 'Australia', value: 59 },
  { name: 'New Zealand', value: 74 },
  { name: 'Russia', value: 44 },
  { name: 'Kazakhstan', value: 39 },
  { name: 'Dem. Rep. Congo', value: 22 },
  { name: 'Angola', value: 30 },
  { name: 'Greenland', value: 46 },
  // Small enough that the lattice steps right over them: each still gets a dot.
  { name: 'Luxembourg', value: 86 },
  { name: 'Brunei', value: 64 },
]

// Slate to gold, so the lowest class still reads as land on a dark ground.
const RAMP = ['#34425a', '#56627a', '#8a8467', '#c09a4f', '#f0b43c']

const series = (shape) => ({
  type: 'pixel',
  name: 'Index',
  joinBy: 'name',
  data,
  shape,               // 'circle', 'square', 'hexagon' or 'diamond'
  grid: 'geo',         // fixed to the ground: zooming in magnifies each dot
  spacing: 3,          // degrees between dots on a geo grid
  size: '80%',         // of the spacing; '100%' makes the shapes tile
  scale: { palette: RAMP },
})

const map = new ApexMaps(document.getElementById('map'), {
  chart: { height: 520 },
  theme: { mode: 'dark' },
  geo: {
    map: 'world/countries@110m',
    projection: 'equalEarth',
    view: { fit: [-180, -58, 180, 84], padding: 10 },
  },
  series: [series('circle')],
  legend: { title: 'Composite index (illustrative)', align: 'center' },
})

map.render()

// Every shape sits on a lattice made for it, so the same data redraws cleanly.
const show = (shape) => map.updateSeries([series(shape)])
document.getElementById('circle').addEventListener('click', () => show('circle'))
document.getElementById('square').addEventListener('click', () => show('square'))
document.getElementById('hexagon').addEventListener('click', () => show('hexagon'))
document.getElementById('diamond').addEventListener('click', () => show('diamond'))

// On an orthographic globe a geo grid is laid on the sphere itself, so the dots
// turn with the world when it is dragged.
const globe = new ApexMaps(document.getElementById('globe'), {
  chart: { height: 480 },
  theme: { mode: 'dark' },
  geo: {
    map: 'world/countries@110m',
    projection: { name: 'orthographic', rotate: [-20, -25, 0] },
    view: { fit: 'world', padding: 12 },
    sphere: { show: true, fill: '#0d121b', stroke: 'rgba(255,255,255,0.08)' },
  },
  series: [{ ...series('hexagon'), spacing: 2.5, size: '88%' }],
  legend: { show: false },
})

globe.render()