Skip to main content

Data in

Real data arrives as a spreadsheet, not a GeoJSON FeatureCollection. fromCSV handles the gap with no geocoding service and no API key.

import { fromCSV } from "canvas-globe";

const markers = fromCSV(`city,count
Ahmedabad,12
London,8
Tokyo,4
Nairobi,2`);

globe.setMarkers(markers).fitToMarkers();
Loading globe…

How rows are resolved

For each row, in order:

  1. Explicit coordinates: a lat/latitude/y column plus lon/lng/long/longitude/x
  2. A city name: city, place, town or location
  3. A country: country, iso, country_code or nation

Anything that resolves to none of those is collected on markers.skipped so you can report it rather than silently dropping rows.

const markers = fromCSV(text);

markers.length; // placed
markers.skipped; // the original rows that failed
markers.skipped.length; // show this in your UI

Recognised columns

Column namesBecomes
lat, latitude, ymarker.lat
lon, lng, long, longitude, xmarker.lon
city, place, town, locationlooked up, and used as label
country, iso, country_code, nationlooked up
count, value, total, users, visits, amount, nmarker.count
label, name, titlemarker.label
emoji, image, color, datepassed through
anything elsecopied onto the marker

City coverage

City lookup uses the representative cities in the bundled time-zone table: roughly 300 major cities, at no extra payload. For anything beyond that, supply your own:

fromCSV(text, {
gazetteer: {
Ahmedabad: [72.58, 23.03],
Surat: [72.83, 21.17],
},
});

Keys are matched case-insensitively; values are [lon, lat] or { lat, lon }.

:::tip Ship coordinates when you can If your backend can emit lat/lon columns, do that. Name lookup is a convenience for pasted spreadsheets, not a substitute for real geocoding. :::

Parsing without placing

parseCSV handles quoted fields, embedded commas, escaped quotes and CRLF, and lowercases headers.

import { parseCSV, fromRows } from "canvas-globe";

const rows = parseCSV(text); // → [{ city: "London", count: "8" }, …]
const markers = fromRows(rows); // → markers

Use fromRows directly when your data comes from JSON or an API rather than CSV text:

const markers = fromRows(await res.json());

Resolving single places

import { geocode, countryPoint, placeLocation } from "canvas-globe";

geocode("Tokyo"); // city table, then countries
geocode("Ahmedabad", { gazetteer });
countryPoint("IN"); // ISO, numeric id or name
placeLocation("New York"); // time-zone city table only

From your own API

Nothing about the marker format is special: it is a plain object.

const res = await fetch("/api/signups");
const rows = await res.json();

globe.setMarkers(
rows.map((r) => ({
lat: r.latitude,
lon: r.longitude,
count: r.total,
label: r.city,
date: r.created_at,
plan: r.plan, // comes back in onClick untouched
})),
);

Live updates

setMarkers replaces the whole set and recomputes the weight ceiling. For a live feed, keep an array and re-set it, or use pings for transient events that should not persist as markers.

setInterval(async () => {
const markers = fromRows(await load());
globe.setMarkers(markers);
}, 30_000);