Remote team map
"Where the team is" for a careers page, or "trusted in 40 countries" with real customer logos. Same build either way: image markers plus a CSV.
The code
import { createGlobe, fromCSV } from "canvas-globe";
const csv = `city,count,image,label
Ahmedabad,4,/team/ahmedabad.jpg,Ahmedabad
London,3,/team/london.jpg,London
New York,2,/team/nyc.jpg,New York
Tokyo,2,/team/tokyo.jpg,Tokyo`;
const markers = fromCSV(csv);
const globe = createGlobe(canvas, {
scene: "team",
markers,
tooltip: (m) => `${m.label}: ${m.count} people`,
});
globe.fitToMarkers();
fromCSV resolves the city column against the bundled city table, uses count as the weight, and
passes image and label straight through. No geocoding service involved.
Image markers
{
lat, lon,
image: "/team/priya.jpg", // URL, canvas, ImageBitmap…
imageSize: 20, // radius in px, default 19
count: 3, // shows as a badge when > 1
label: "Ahmedabad",
}
Images are cropped to a circle, ringed in the marker colour, and given a count badge. Cross-origin URLs need permissive CORS headers.
Cities the bundled table does not know
City lookup covers ~300 major cities. For anything else, supply coordinates in the CSV or pass a gazetteer:
fromCSV(csv, {
gazetteer: {
Surat: [72.83, 21.17],
Coimbatore: [76.96, 11.02],
},
});
Always check what did not resolve:
if (markers.skipped.length) {
console.warn("Could not place:", markers.skipped);
}
Grouping dense clusters
An office with forty people should not be forty overlapping avatars.
createGlobe(canvas, {
cluster: true,
clusterRadius: 54,
onClick: (target) => {
if (target.cluster) globe.fitTo(boundsOf(target.markers));
else openProfile(target);
},
});
Customer logos instead
Same shape, different framing: scene: "logos" uses the neutral mono preset so brand colours are
not fighting the map:
createGlobe(canvas, {
scene: "logos",
markers: customers.map((c) => ({
lat: c.lat,
lon: c.lon,
image: c.logoUrl,
imageSize: 22,
label: c.name,
})),
counter: { value: countryCount, label: "countries" },
});
Exporting it
Careers and customer maps end up in decks constantly:
globe.exportImage({ preset: "wide" }); // slides
globe.exportImage({ preset: "linkedin", transparent: true }); // posts