In our our basic introduction to automatic layout in Eclipse, we have seen how the diagram layout engine needs someone to extract a proper ElkGraph from whatever layout is invoked on. This is what you, as a tool developer, have to supply. While there are already implementations for different graph editing frameworks to build upon, this page describes how doing so from scratch works.
To connect to ELK, there are two things you will have to do:
ILayoutSetup
implementation and register it with our extension point.IDiagramLayoutConnector
implementation.The rest of this page will look at each of these in turn.
An ILayoutSetup
implementation consists of just two methods:
boolean supports(Object object);
Injector createInjector(Module defaultModule);
To be able to implement the interface, you may have to add a dependency to org.eclipse.elk.core.service
to your project. Then register your layout setup with the org.eclipse.elk.core.service.layoutConnectors
extension point.
supports(...)
MethodThis method is called by the diagram layout engine to determine if a given setup instance supports extracting an ElkGraph from the given object. The question now of course becomes what that object is. There are two cases. In the first case, it will be an implementation of IWorkbenchPart
. This is the case if layout is invoked on a diagram editor. If the setup states that it supports that workbench part, the diagram layout connector it supplies knows how to get its hands at the editor’s content and how to turn that content into an ElkGraph. In the second case, the object passed to supports(...)
will be an object from that editor’s content. For GMF editors, this will be an implementation of IGraphicalEditPart
.
A typical implementation of this method will look something like this:
public boolean supports(final Object object) {
// This method may be invoked on a whole collection of elements selected
// in an editor
if (object instanceof Collection) {
// Check if we support layout on at least one of the selected objects
for (Object o : (Collection<?>) object) {
if (o instanceof SomeDiagramElementClassWeSupport) {
return true;
}
}
return false;
}
// If it is not a collection, it may be either a workbench part we support
// or the diagram element class we already checked for above
return object instanceof WorkbenchPartImplementation
|| object instanceof SomeDiagramClassWeSupport;
}
createInjector(...)
MethodIf the diagram layout engine has determined which setup supports layout on a given object, it will use that setup to get its hands on an injector that can supply implementations of the different components involved in the layout process. The most important of these is an implementation of IDiagramLayoutConnector
, which we will look at in a minute. A typical implementation will look something like this:
public Injector createInjector(final Module defaultModule) {
// Modules basically provide a mapping between types and implementations
// to instantiate whenever an instance of the type is requested. We use
// the default module supplied by ELK and override that with custom
// overrides to get our IDiagramLayoutConnector to enter the picture.
return Guice.createInjector(
Modules.override(defaultModule).with(new AwesomeLayoutModule()));
}
public static class AwesomeLayoutModule implements Module {
@Override
public void configure(final Binder binder) {
// This is the most important binding
binder.bind(IDiagramLayoutConnector.class)
.to(MyAwesomeDiagramLayoutConnector.class);
}
}
An implementation can of course add more bindings. See our section on dependency injection for more information on what makes sense here.
An IDiagramLayoutConnector
implementation consists of the following methods:
LayoutMapping buildLayoutGraph(IWorkbenchPart workbenchPart, Object diagramPart);
void applyLayout(LayoutMapping mapping, IPropertyHolder settings);
These methods pretty much correspond to the beginning and the end of the layout process: extracting the layout graph from whatever layout is invoked on, and applying the layout information back to the diagram. Let’s take a look at how these methods should be implemented.
buildLayoutGraph(...)
MethodThis is the method that turns a diagram into an ElkGraph that ELK can then work with. It produces an instance of the LayoutMapping
class, which contains information so important to the layout process that we should list them all:
IGraphicalEditPart
for GMF).Since this method determines the structure of the graph layout algorithms will be run on, it is what has the most impact on what your results will look like. It is thus a good idea to spend some time (and thought) on how to implement it. You should think about the following two aspects: the layout graph’s structure, and the layout configuration.
Regarding the layout graph’s structure, the main task is to decide which of your diagram elements map to which kinds of layout graph elements. Should an element be represented as a node? Is it a label? Should my nodes have explicit points for edges to attach to? Do certain nodes contain other nodes? Which diagram elements need to be represented in the layout graph in the first place?
Regarding the layout configuration, your layout connector is expected to build a fully configured ElkGraph (except for some advanced configuration issues, which we will look at on another page). This primarily includes setting the correct layout options that yield the results you want. However, this may also include writing the current coordinates of diagram elements into the ElkGraph. This is mostly important for what we call interactive layout algorithms: layout algorithms that take current positions into account when calculating new ones instead of simply calculating new coordinates from scratch.
applyLayout(...)
MethodThis method accepts two arguments: the LayoutMapping
created by buildLayoutGraph(...)
, and an IPropertyHolder
that may hold additional options controlling the layout process. These options will usually include things such as whether the application of the layout should be animated and whether the diagram zoomed and positioned such that it is completely visible in the editor.
The most important thing the applyLayout(...)
implementation will do is to iterate over the diagram elements and to apply the layout results back to them. At best, this just means copying values over. At worst, this will include transforming coordinates from the ElkGraph coordinate system back to whatever coordinate system the diagram editor uses.