Callbacks
All callbacks are plain options. Patch them at runtime with setOptions like anything else.
onHover
onHover(marker: Marker | ClusterMarker | null, position: { x, y } | null): void
Fires when the hovered marker changes, not on every pointer move. marker is null when the
pointer leaves a marker or the canvas.
createGlobe(canvas, {
onHover: (marker, position) => {
if (!marker) return hideCard();
showCard(marker, position.x, position.y);
},
});
Positions are in CSS pixels relative to the canvas, and are the marker's centre rather than the pointer's, so a card anchored to them stays put while the pointer wobbles.
onClick
onClick(marker: Marker | ClusterMarker, position: { x, y }): void
Only fires when a marker was actually hit, and never after a drag.
onClick: (target) => {
if (target.cluster) globe.fitTo(boundsOf(target.markers));
else openProfile(target);
};
Hit testing walks front-to-back, so overlapping markers resolve to the topmost.
onCountryHover
onCountryHover(country: CountryShape | null, position: { x, y } | null): void
Adding this enables country hit testing and the hover highlight. Without it, no country testing happens at all. This is deliberate because it costs a point-in-polygon pass.
onCountryHover: (shape) => setHighlighted(shape?.iso ?? null),
A marker takes precedence: when the pointer is over a marker, no country is reported.
onCountryClick
onCountryClick(country: CountryShape, position: { x, y }): void
onCountryClick: (shape) => {
console.log(shape.iso, shape.id, shape.name);
globe.focusOn(shape.iso, { isolate: true });
};
The country shape
{
id?: string | number; // numeric ISO 3166-1
name?: string; // "India"
iso?: string; // "IN"
geometry: GeoJSON; // Polygon or MultiPolygon
}
onRender
onRender(instance: GeoGlobe): void
Runs after every painted frame. Because the loop idles when nothing moves, this is not a steady 30 Hz tick: it fires only when something was actually drawn.
Use it to draw your own overlay on top, or to sync external UI to the view:
createGlobe(canvas, {
onRender: (g) => {
const p = g.project(72.58, 23.03);
badge.style.display = p ? "block" : "none";
if (p) {
badge.style.transform = `translate(${p.x}px, ${p.y}px)`;
}
},
});
:::caution Keep it cheap
onRender is on the frame path. Do not allocate, query layout, or touch the DOM in ways that force
a reflow, which can make the globe stutter.
:::
tooltip as a callback
tooltip(target: Marker | ClusterMarker | CountryShape, kind: "marker" | "cluster" | "country"): string
tooltip: (target, kind) => {
if (kind === "country") return target.name;
if (kind === "cluster") return `${target.count} people nearby`;
return `${target.city}: ${target.count}`;
};
Return an empty string to suppress the tooltip for that target.
:::info Always text
The result is applied with textContent, never innerHTML, so a label coming from user data cannot
inject markup.
:::
renderMarker
renderMarker(ctx, marker, info): number | void
Takes over marker drawing entirely. Return the hit radius in px.
See Markers → drawing markers yourself.
Custom element events
The <geo-globe> element forwards all of these as DOM events:
geo-hover, geo-click, geo-country-hover, geo-country-click, geo-render.
React
In the React component, callbacks are read through a ref, so they are always fresh and never need memoising.