Graph Layout
Reactodia supports the usage of graph layout algorithms on the diagram content to re-position the elements and links.
Layout functions
LayoutFunction
in the library is defined as a pure async function transforming a graph + current layout state (element positions and bounds) into a computed layout state:
type LayoutFunction = (graph: LayoutGraph, state: LayoutState) => Promise<LayoutState>;
interface LayoutGraph {
nodes: { [id: string]: LayoutNode };
links: LayoutLink[];
}
interface LayoutState {
bounds: { [elementId: string]: Rect };
}
where LayoutNode
and LayoutLink
are plain objects representing graph Element
and Link
instances.
To apply a layout function, performLayout()
function from workspace context can be used:
const { view, performLayout } = Reactodia.useWorkspace();
const onClick = async () => {
await performLayout({
// Pass custom layout function or use the default one
layoutFunction: myGraphLayout ?? view.defaultLayout,
// Compute layout only for specific subset of elements
selectedElements: new Set([...]),
// Animate graph content movement when applying the layout
animate: true,
// Whether to fit the graph into viewport after the layout
zoomToFit: true,
});
};
performLayout()
also accepts additional options e.g. whether to animate the movement of graph after layout, or abort signal to cancel the layout computation if needed.
Default layout configuration
defaultLayout
is the required prop for the top-level <Workspace>
component and Reactodia provides two approaches to get a good default layout function:
Layout via Web Workers
Reactodia uses @reactodia/worker-proxy
package to transparently interact with Web Workers, see its documentation for to understand the protocol and how to implement custom transparent workers.
In the recommended configuration, Reactodia uses Web Workers to compute the graph layout in a non-blocking way.
The library provides multiple built-in layout functions from @reactodia/workspace/layout.worker
entry point (DefaultLayouts
). This entry point is a transparent worker proxy which can be used with defineLayoutWorker()
function to create a ref-counted worker and useWorker()
hook to use it from a React component:
import * as Reactodia from '@reactodia/workspace';
const Layouts = Reactodia.defineLayoutWorker(() => new Worker(
new URL('@reactodia/workspace/layout.worker', import.meta.url)
));
function Example() {
const {defaultLayout} = Reactodia.useWorker(Layouts);
...
return (
<Reactodia.Workspace defaultLayout={defaultLayout}>
...
</Reactodia.Workspace>
);
}
Popular front-end bundlers like Webpack, Vite, etc have a native support specifically for importing Web Workers, i.e. new Worker(new URL('...', import.meta.url))
will be built in a special way to create a worker instance with specified entry point. Depending on the bundler it might be necessary to specify { type: 'module' }
as second argument to Worker
constructor:
const Layouts = Reactodia.defineLayoutWorker(() => new Worker(
new URL('@reactodia/workspace/layout.worker', import.meta.url),
{type: 'module'}
));
Synchronous layout
Although the synchronous layout computation is usually fast for small-to-medium graphs, we highly encourage to use non-blocking layout functions (e.g. from a Web Worker) if possible to avoid browser "freezes" when computing a layout for a large graph.
The graph layout can be computed in a synchronous (blocking) way in case when the Web Workers is not available or very hard to setup. In that case it the default force-directed layout function blockingDefaultLayout
can be imported from a separate @reactodia/workspace/layout-sync
entry point:
import * as Reactodia from '@reactodia/workspace';
import { blockingDefaultLayout } from '@reactodia/workspace/layout-sync';
function Example() {
...
return (
<Reactodia.Workspace defaultLayout={blockingDefaultLayout}>
...
</Reactodia.Workspace>
);
}