← DEVLOG
Space Science2025.12.287 min read

Exoplanet Explorer — Browsing NASA's Database of Alien Worlds

Fetching real exoplanet data from the NASA Exoplanet Archive, classifying by habitability, and visualizing on Canvas

nasa-apidata-visualizationcanvasfilter

Starting Point — Over 5,000 Confirmed Exoplanets

In 2022 NASA confirmed the 5,000th exoplanet. These worlds are cataloged in a publicly accessible database with real observational data — radius, mass, orbital period, insolation flux, host star temperature.

Rather than displaying a flat list, the goal was to let users explore these planets visually: see their relative sizes, understand which ones might be habitable, and filter by different characteristics.


The API — TAP Service and ADQL Queries

NASA Exoplanet Archive provides a TAP (Table Access Protocol) service. SQL-like ADQL queries let you select specific columns from the catalog.

const QUERY = `
  SELECT pl_name, pl_rade, pl_bmasse, pl_insol, st_teff,
         pl_orbper, sy_dist, disc_year, discoverymethod
  FROM ps
  WHERE pl_controv_flag = 0
`;

const FETCH_URL =
  `https://exoplanetarchive.ipac.caltech.edu/TAP/sync` + `?query=${encodeURIComponent(QUERY)}&format=json`;

The pl_controv_flag = 0 filter excludes contested planet candidates, keeping only confirmed detections. The full response is roughly 5,500 rows — client-side caching is essential.


Habitability Classification

The primary habitability indicator is insolation flux (pl_insol) — the amount of stellar radiation the planet receives relative to Earth.

type Habitability = 'habitable' | 'hot' | 'cold' | 'unknown';

function getHabitability(p: Planet): Habitability {
  if (p.pl_insol == null) return 'unknown';
  if (p.pl_insol >= 0.25 && p.pl_insol <= 1.77) return 'habitable';
  if (p.pl_insol > 1.77) return 'hot';
  return 'cold';
}

The 0.25–1.77 range follows Kopparapu et al. (2013)'s optimistic habitable zone boundaries. This is a first-pass filter — insolation alone doesn't determine true habitability, but it narrows the field considerably.


Canvas Visualization — Size and Temperature

Each planet is drawn as a circle on Canvas. Two physical properties map to visual properties:

  • Circle radius: planet radius (pl_rade, Earth radii) — log scale
  • Circle color: host star effective temperature (st_teff, Kelvin)
function getTempColor(teff: number | null): string {
  if (teff == null) return 'rgba(150, 150, 150, 0.7)';
  // 2500K = cool red dwarf → 10000K = hot blue star
  const t = Math.max(0, Math.min(1, (teff - 2500) / 10000));
  const r = Math.round(255 * (1 - t * 0.6));
  const g = Math.round(180 * t);
  const b = Math.round(80 + 175 * t);
  return `rgba(${r}, ${g}, ${b}, 0.85)`;
}

Planets in the habitable zone receive a green shadowBlur glow to make them stand out.


Filter System

Four filter modes let users narrow the dataset:

  • All — full catalog
  • Habitable Zone — insolation 0.25–1.77 Earth flux
  • Earth-like — radius 0.5–1.5 Earth radii
  • Giant — radius > 6 Earth radii

Filter results update immediately. When habitable planets are present in the filtered set, they appear in a highlighted section at the top.


Client Cache and AbortController

Data is stored under exoplanet_data_v2 in localStorage after the first fetch. Subsequent visits skip the network request entirely.

const abortRef = useRef<AbortController | null>(null);

async function fetchExoplanets() {
  abortRef.current?.abort(); // cancel any in-flight request
  abortRef.current = new AbortController();

  const cached = storageGet<Planet[] | null>('exoplanet_data_v2', null);
  if (cached) {
    setData(cached);
    return;
  }

  const res = await fetch(FETCH_URL, { signal: abortRef.current.signal });
  // ...parse and cache
}

The AbortController prevents race conditions when components unmount or when the fetch is triggered multiple times in quick succession.


Canvas Performance — 4K Resolution Cap

The exoplanet visualization renders over 5,000 circles simultaneously, each with its own color computation and optional shadowBlur glow for habitable zone planets. On 4K displays, this workload can cause noticeable frame drops during pan and zoom interactions.

The canvas buffer area is capped at 1920 × 1080 pixels:

const MAX_AREA = 1920 * 1080;
const area = w * h;
const scale = area > MAX_AREA ? Math.sqrt(MAX_AREA / area) : 1;
canvas.width = Math.round(w * scale);
canvas.height = Math.round(h * scale);

The shadowBlur glow on habitable planets is the most expensive per-pixel operation — blur cost scales quadratically with blur radius and linearly with pixel count. The resolution cap keeps this tractable even when dozens of habitable planets are visible simultaneously.


Closing Thoughts

When the first batch of planets appeared on the Canvas — thousands of dots at once — I paused. Each of those points is a real world, detected by some telescope, logged by some astronomer. Scrolling through a list conveys the count; seeing them all rendered simultaneously conveys the scale.

That felt like the right way to present a dataset about alien worlds.

Content related to this post

Try it yourself