Skip to main content

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.

Loading globe…

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