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();
How rows are resolved
For each row, in order:
- Explicit coordinates: a
lat/latitude/ycolumn pluslon/lng/long/longitude/x - A city name:
city,place,townorlocation - A country:
country,iso,country_codeornation
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 names | Becomes |
|---|---|
lat, latitude, y | marker.lat |
lon, lng, long, longitude, x | marker.lon |
city, place, town, location | looked up, and used as label |
country, iso, country_code, nation | looked up |
count, value, total, users, visits, amount, n | marker.count |
label, name, title | marker.label |
emoji, image, color, date | passed through |
| anything else | copied 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);