Appearance
Migration guides
Ogma@6.1 migration guide
Ogma@6.1 introduces the new Geo module, which allows to easily bridge Ogma to modern maps providers (maplibre, mapbox, deckgl, google maps).
Import the maps module
Geo lives in a dedicated subpath so it is tree-shaken away from apps that don't use it. The provider SDKs (mapbox-gl, maplibre-gl, @deck.gl/core) are declared as optional peer dependencies — install only the one you actually use.
ts
import { Geo } from '@linkurious/ogma/geo';Link your maps provider with Ogma
You bring your own map/renderer instance and hand it to Geo wrapped in an explicit adapter — you state the provider type yourself with createMapboxAdapter, createMaplibreAdapter, createDeckAdapter or createGoogleAdapter, there is no auto-detection from the instance shape.
ts
import { Geo, createMaplibreAdapter } from '@linkurious/ogma/geo';
const provider = new maplibregl.Map({ container: 'map', style }); // your Maplibre/Mapbox/Deck.gl/Google Maps instance
const geo = new Geo<NodeData, EdgeData>(ogma, createMaplibreAdapter(provider), {
animation: { duration: 800, easing: 'quadraticInOut' }
});
await geo.enable();enable() projects the nodes into the map's coordinate system, applies a scalingMethod: 'fixed' style rule and hides nodes without coordinates. disable() reverses the transition and restores the original graph layout.
Configuration
Geo accepts a GeoOptions object. Common fields:
| Option | Default | Description |
|---|---|---|
latitudePath | 'latitude' | Node data path for the latitude. |
longitudePath | 'longitude' | Node data path for the longitude. |
getCoordinates | — | Custom extractor. Takes precedence over the *Path options. Return null to hide a node. |
projection | web-mercator | Custom geo ↔ graph projection. Must be flat and zoom-independent. |
syncCamera | true | Keep the Ogma camera in sync with the provider. |
syncOnDrag | true | Write geo coordinates back to node data when nodes are dragged. |
hideNodesWithoutCoordinates | true | Hide nodes for which no coordinate is resolved. |
reprojectOnViewChange | false | Reproject every node on each provider view change (needed for non-mercator basemaps). |
sizeRatio | 1 | Uniform multiplier applied to node radii and edge widths. |
minNodeSize | 0 | Minimum on-screen node radius in pixels (0 disables the clamp). |
animation | 0 | Enable/disable transition animation. |
Options are validated eagerly: wrong types or out-of-range values throw a TypeError from new Geo(...) / setOptions(...).
Coordinate helpers
While the binding is enabled you can convert back and forth between graph and geo space:
ts
const { latitude, longitude } = geo.toGeoCoordinates({ x, y });
const { x, y } = geo.fromGeoCoordinates({ latitude, longitude });Events
Geo fires its own lifecycle events on itself:
ts
geo.on('enabled', () => { /* projection & camera sync are live */ });
geo.on('disabled', () => { /* nodes restored to their original layout */ });
geo.on('cameraSync', () => { /* Ogma camera has just been re-synced */ });For compatibility with the legacy geo module the core geoEnabled / geoDisabled / geoLoaded events are still fired on the Ogma instance, so listeners written for the legacy mode keep working:
ts
ogma.events.on('geoEnabled', () => { ... });
ogma.events.on('geoDisabled', () => { ... });ogma.view.zoomIn() / ogma.view.zoomOut() and the keyboard pan/zoom controls also defer to the bound provider automatically while Geo is enabled — no manual wiring required.
Migrating from ogma.geo
The legacy ogma.geo module keeps working during the deprecation window. Side-by-side comparison of the equivalent APIs:
Legacy ogma.geo | New Geo |
|---|---|
ogma.geo.enable({ tiles: '…' }) | new Geo(ogma, createMaplibreAdapter(provider)); await geo.enable(); |
ogma.geo.disable() | await geo.disable(); |
ogma.geo.enabled() | geo.enabled() |
ogma.geo.getMap() | geo.getProvider() |
ogma.geo.setOptions({...}) | await geo.setOptions({...}) |
ogma.geo.getUnprojectedCoordinates(node) | geo.toGeoCoordinates(node.getPosition()) |
The main behavioural change is that Ogma no longer creates the map: you instantiate the provider yourself with its full native API, wrap it with the matching create*Adapter, then pass the adapter in. This makes multi-graph, custom controls and existing map-based apps work naturally.
Provider-specific notes
- Mapbox GL / MapLibre GL: the two SDKs are structurally identical —
createMapboxAdapter/createMaplibreAdapterbind to the same code path, so use whichever matches the SDK you installed. - deck.gl:
Deckis a renderer, not a basemap.createDeckAdapterhooks intoonViewStateChangefor camera sync and requires no external tile layer to work, but you typically stack it over a basemap of your own. - Google Maps:
createGoogleAdapterusesgetCenter()/setCenter()/getZoom()/setZoom()— nomapbox-gl-leafletshim needed.
Ogma@6.0 migration guide
Ogma@6.0 introduces some changes to the transformation API that may require adjustments in your codebase. This guide outlines the key modifications and how to adapt your code accordingly.
Import syntax
Ogma no longer provides a default export. You must now use a named import:
Old code:
ts
import Ogma from '@linkurious/ogma';New code:
ts
import { Ogma } from '@linkurious/ogma';This change aligns with modern JavaScript/TypeScript best practices:
- Better tree-shaking: Named exports allow bundlers to statically analyze which exports are used and eliminate dead code more effectively.
- Explicit imports: Named exports make it clear exactly what is being imported, improving code readability and reducing ambiguity.
- TypeScript consistency: The TypeScript community recommends named exports as they work more predictably with type inference and refactoring tools.
- ESM compatibility: Named exports are the standard pattern in ES modules and avoid interoperability issues between CommonJS and ESM.
Animations
Animations control changed quite a bit:
- Individual transformations do not have a
durationandeasingparam anymore - Transformations have a new param:
animates, if set to false, it acts as theduration:0from previous versions. - The global animations duration and easing are controlled through the
ogma.transformations.setAnimationfunction.
Why those changes?
- In Ogma 4, the animations of the transformations applied sequentially, leading to confusing transitions.
- In Ogma 5, we introduced the merged animations for the transformations: Ogma computes the final state of the visualisation and animates once for all transformations.
- In Ogma we reflect this change of behaviour in the API.
So code like this:
ts
ogma.transformations.addNodeFilter({
...
duration: 250,
easing: 'quadraticInOut'
});Becomes:
ts
ogma.transformations.setAnimation({
duration: 250,
easing: 'quadraticInOut'
});
ogma.transformations.addNodeFilter({
...
});Easy Grouping
This new feature aims to make it easier for developers to use the Grouping feature: instead of declaring NodeGrouping transformations and think in terms of group Sets, groups hierarchy, you can build your grouping by hand using two functions:
nodeList.group()which group together a list of nodesnode.addSubGraph()which replaces the node with a group containing the subGraph passed in param
Discontinuation of deprecated features
Events API
The deprecated events API has been discontinued in favor of a simplified event handling system. Instead of using method-based event subscriptions, use the standard on API with JavaScript event names.
Old code:
ts
ogma.events.onClick(someFunction);
ogma.events.onDoubleClick(someFunction);
ogma.events.onHover(someFunction);New code:
ts
ogma.on('click', someFunction);
ogma.on('doubleclick', someFunction);
ogma.on('mouseover', someFunction);This change provides a more consistent and flexible API that aligns with standard JavaScript event patterns.
Complete list of removed methods
| Deprecated method | Replacement |
|---|---|
| General | |
removeListener(fn) | off(fn) |
| Graph events | |
onNodesAdded(fn) | on('addNodes', fn) |
onBeforeNodesRemoved(fn) | on('beforeRemoveNodes', fn) |
onNodesRemoved(fn) | on('removeNodes', fn) |
onEdgesAdded(fn) | on('addEdges', fn) |
onBeforeEdgesRemoved(fn) | on('beforeRemoveEdges', fn) |
onEdgesRemoved(fn) | on('removeEdges', fn) |
onGraphCleared(fn) | on('clearGraph', fn) |
| Node drag events | |
onNodeDragStart(fn) | on('nodesDragStart', fn) |
onNodeDragProgress(fn) | on('nodesDragProgress', fn) |
onNodeDragEnd(fn) | on('nodesDragEnd', fn) |
onDrop(fn) | on('drop', fn) |
| Selection events | |
onNodesSelected(fn) | on('nodesSelected', fn) |
onNodesUnselected(fn) | on('nodesUnselected', fn) |
onEdgesSelected(fn) | on('edgesSelected', fn) |
onEdgesUnselected(fn) | on('edgesUnselected', fn) |
| Data change events | |
onNodeDataChange(fn) | on('updateNodeData', fn) |
onEdgeDataChange(fn) | on('updateEdgeData', fn) |
onAnimate(fn) | on('animate', fn) |
| Mouse events | |
onMouseMove(fn) | on('mousemove', fn) |
onMouseButtonDown(fn) | on('mousedown', fn) |
onMouseButtonUp(fn) | on('mouseup', fn) |
onClick(fn) | on('click', fn) |
onDoubleClick(fn) | on('doubleclick', fn) |
onMouseWheel(fn) | on('mousewheel', fn) |
onHover(fn) | on('mouseover', fn) |
onUnhover(fn) | on('mouseout', fn) |
| Drag/gesture events | |
onDragStart(fn) | on('dragStart', fn) |
onDragProgress(fn) | on('dragProgress', fn) |
onDragEnd(fn) | on('dragEnd', fn) |
onGestureStart(fn) | on('gestureStart', fn) |
onGestureProgress(fn) | on('gestureProgress', fn) |
onGestureEnd(fn) | on('gestureEnd', fn) |
| View/render events | |
onRendererStateChange(fn) | on('rendererStateChange', fn) |
onZoomStart(fn) | on('zoomStart', fn) |
onZoomProgress(fn) | on('zoom', fn) |
onViewChanged(fn) | on('viewChanged', fn) |
| Geo events | |
onNodesConnected(fn) | on('connectNodes', fn) |
onGeoModeEnabled(fn) | on('geoEnabled', fn) |
onGeoModeDisabled(fn) | on('geoDisabled', fn) |
onGeoModeLoaded(fn) | on('geoLoaded', fn) |
| Layout events | |
onLayoutStart(fn) | on('layoutStart', fn) |
onLayoutComplete(fn) | on('layoutEnd', fn) |
onLayoutComputed(fn) | on('layoutComputed', fn) |
| Tooltip events | |
onTooltipShown(fn) | on('tooltipShow', fn) |
onTooltipHidden(fn) | on('tooltipHide', fn) |
| Transformation events | |
onTransformationEnabled(fn) | on('transformationEnabled', fn) |
onTransformationDisabled(fn) | on('transformationDisabled', fn) |
onTransformationSetIndex(fn) | on('transformationSetIndex', fn) |
onTransformationDestroyed(fn) | on('transformationDestroyed', fn) |
onTransformationRefreshed(fn) | on('transformationRefresh', fn) |
Parse API
The instance-based parse API has been replaced with static methods on the Ogma class. This change allows you to parse graph data before creating an Ogma instance, improving performance and flexibility.
Old code:
ts
const ogma = new Ogma();
Ogma.parse.gexf(gexfString).then((graph) => ogma.setGraph(graph));New code:
ts
const graph = await Ogma.parse.gexf(gexfString);
const ogma = new Ogma();
ogma.setGraph(graph);Ogma@5.3 migration guide
Ogma@5.3 introduces some changes to the transformation API that may require adjustments in your codebase. This guide outlines the key modifications and how to adapt your code accordingly.
Node grouping
Ogma@5.3 introduces a new way to increase the performance of the layout by merging running it once for all the nodes inside of the groups. This is done by returning the layout options in the onGroupUpdated callback.
So if you had a code like this:
js
ogma.group({
groupIdFunction: (node) => ...,
showContents: true,
onGroupUpdated: (group) => {
return ogma.layouts.force({ nodes: group });
}
});You should now return the layout options instead of the layout itself:
js
ogma.group({
groupIdFunction: (node) => ...,
showContents: true,
onGroupUpdated: (group) => {
return { layout: 'force', params: {...} };
}
});Same goes for the onGroupsUpdated callback on ogma.transformations:
js
ogma.transformations.onGroupsUpdated((groups) => {
return { layout: 'force', params: {...} };
});Alternatively, you can return a promise that resolves to the final nodes positions:
js
ogma.group({
groupIdFunction: (node) => ...,
showContents: true,
onGroupUpdated: (group) => {
return yourPlacementFunction(group);
}
});For more details you should refer to the API documentation of the grouping transformation options.
GPU layout
One of the main benefits of the new grouping API is that it allows you to use the GPU layout for the groups. This is done by returning the gpu layout in the onGroupUpdated callback:
js
ogma.group({
groupIdFunction: (node) => ...,
showContents: true,
onGroupUpdated: (group) => {
return { layout: 'force', params: { gpu: true, ...} };
}
});So in addition to the huge performance boost that is delivered by the new grouping API, you can also benefit from the GPU layout for the groups to make it even faster and bring better quality to the compound nodes.