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.

// Arc series. Each row is a `from`/`to` pair and the arc follows the real great
// circle between them, split cleanly where it crosses the antimeridian. `flow`
// animates travelling beads from -> to, so routes out of one hub read as
// direction rather than as a static web.
const hub = [103.99, 1.36] // Singapore Changi

// Great-circle distance (haversine, WGS84 mean radius). The width encodes it, so
// it is computed rather than invented.
const distanceKm = ([lon1, lat1], [lon2, lat2]) => {
  const R = 6371.0088
  const rad = Math.PI / 180
  const dLat = (lat2 - lat1) * rad
  const dLon = (lon2 - lon1) * rad
  const a =
    Math.sin(dLat / 2) ** 2 +
    Math.cos(lat1 * rad) * Math.cos(lat2 * rad) * Math.sin(dLon / 2) ** 2
  return Math.round(2 * R * Math.asin(Math.sqrt(a)))
}

const routes = [
  { name: 'Singapore to London Heathrow', to: [-0.45, 51.47] },
  { name: 'Singapore to New York JFK', to: [-73.78, 40.64] },
  { name: 'Singapore to Los Angeles', to: [-118.41, 33.94] },
  { name: 'Singapore to Tokyo Narita', to: [140.39, 35.77] },
  { name: 'Singapore to Sydney', to: [151.18, -33.95] },
  { name: 'Singapore to Johannesburg', to: [28.24, -26.13] },
  { name: 'Singapore to São Paulo', to: [-46.47, -23.43] },
  { name: 'Singapore to Frankfurt', to: [8.57, 50.04] },
  { name: 'Singapore to Dubai', to: [55.36, 25.25] },
  { name: 'Singapore to Auckland', to: [174.79, -37.01] },
].map((d) => ({ name: d.name, from: hub, to: d.to, value: distanceKm(hub, d.to) }))

// Cool-to-warm at constant lightness: on a dark map the pale end of a normal
// light-to-dark ramp would vanish into the sphere.
const night = ['#22d3ee', '#38bdf8', '#818cf8', '#c084fc', '#fbbf24']

const map = new ApexMaps(document.getElementById('map'), {
  chart: { height: 540 },
  theme: { mode: 'dark' },
  geo: {
    map: 'world/land@110m',
    fill: '#1d2735',
    view: { fit: [-180, -58, 180, 84], padding: 16 },
    sphere: { show: true, fill: '#0d121b', stroke: 'rgba(255,255,255,0.08)' },
    graticule: { show: true, step: 20, color: 'rgba(255,255,255,0.055)', width: 0.6 },
  },
  series: [
    {
      type: 'arc',
      name: 'Great-circle distance',
      data: routes,
      geodesic: true, // false = a naive chord through screen space
      curvature: 0, // > 0 bulges the chord; decorative, not the real path
      width: { range: [0.6, 3.4] }, // encodes distance
      colorScale: { palette: night, classes: 5 },
      opacity: 0.9,
      endpoints: { show: true, radius: 2, color: '#e8f4ff' },
      flow: true, // travelling beads, coloured and phased per route
    },
  ],
  legend: {
    title: 'Great-circle distance, km',
    align: 'start',
    formatter: (item) => `${Math.round(item.from)} to ${Math.round(item.to)}`,
  },
  tooltip: {
    formatter: ({ name, value }) =>
      `<div style="font-weight:640">${name}</div>` +
      `<div style="font-weight:600">${value.toLocaleString()} km</div>`,
  },
})

map.render()
Arcs - ApexMaps Demo | ApexCharts.js