Decorating Graph Elements
Many aspects of the user interaction and of the visualization of selection, focus, and custom highlight can be customized by implementing a small dedicated interface. To use such a customization, you can decorate the whole graph or any of its elements with your implementation.
IGraph provides an instance of GraphDecorator for this purpose. You can get it from the graph with the IGraph.decorator property.
GraphDecorator is organized in two levels. GraphDecorator itself provides a decorator for each graph element type, namely NodeDecorator, EdgeDecorator, LabelDecorator, PortDecorator, and BendDecorator. Then, each of these type-specific decorators provides properties to define a certain aspect of user interaction or visualization for its element type. The following example will make this more clear.
For example, the following decorator is responsible for the INodeSizeConstraintProvider interface:
The read-only properties of these type-specific decorators actually are factory methods, so new LookupDecorator<TDecoratedType,TInterface> instances are returned each time the properties are accessed. The Examples show the advantages and pitfalls of this behavior.
Aside from the information provided in this section, you typically don’t need to care about the details of the lookup concept and its related types to decorate graph elements.
Explore the available decorators for each element type by simple code completion or looking into the API documentation.
Setting a Decoration
Once you got a specific decorator, you can decorate an item in the following ways:
- Set a single implementation
- Wrap the default implementation, i.e. the implementation that would be used without your decoration
- Set a factory that creates item-specific implementations
- Hide the default implementation
For each of these options, you can either decorate all elements of that type or specify a predicate that selects the items that are actually decorated.
All corresponding methods are specified by LookupDecorator<TDecoratedType,TInterface> and listed in the following.
- setImplementation(implementation: TInterface): IContextLookupChainLink
- setImplementation(predicate: Predicate<TDecoratedType>, implementation: TInterface): IContextLookupChainLink
- Decorates all items (the selected items) with a single implementation.
- setFactory(factory: Factory<TDecoratedType, TInterface>): IContextLookupChainLink
- setFactory(predicate: Predicate<TDecoratedType>, factory: Func2<TDecoratedType, TInterface>): IContextLookupChainLink
- Decorates all items (the selected items) with a factory method of the desired implementations. In this case, the only parameter of the factory method is the graph element.
For setImplementation and
setFactory the
nullIsFallback property determines how null
values
are handled that have been returned by the implementation or factory.
- setImplementationWrapper(factory: WrapperFactory<TDecoratedType, TInterface>): IContextLookupChainLink
- Decorates all items (the selected items) with a factory method of the desired implementations. In this case, the parameters of the factory method are the graph element and the default implementation, i.e. the implementation that would be used without this decoration.
For setImplementationWrapper the
decorateNulls property determines if the implementation
wrapper is called at all when the remaining lookup chain returns null
for this interface. Per default the wrapper
is not called when the wrapped implementation would be null
.
- hideImplementation(predicate: Predicate<TDecoratedType>): IContextLookupChainLink
- Hides the default implementation, i.e. the implementation that would be used without this decoration.
Removing a Decoration
All of the above methods that set a decoration return an instance of IContextLookupChainLink. Store this instance if you intend to remove the decoration later, and simply ignore it otherwise.
To remove a decoration, get the graph’s ILookupDecorator and use its removeLookup method to remove the chain link instance. The following example shows how to remove a node size constraint provider.
Examples
This first example shows how to disable size constraints for nodes.
The next example shows how to set the same size constraint provider for all nodes.
If the implementation needs to know the item it handles, set a factory method which is invoked for each item. The following example shows how to constrain a node’s size to half its current size at minimum and double its current size at maximum.
Another common use case is to modify the default implementation. This can be done by decorating the default implementation in a wrapper implementation. The following example shows how to constrain a node’s size to half the values the default implementation does.
Note that per default this wrapper implementation is not called if wrapped
is null
. If null
should be decorated
as well, this has to be enabled and the code has to handle the possible null
value:
To change the decorateNulls value for a custom wrapper, the LookupDecorator<TDecoratedType,TInterface> instance has to be stored in a local variable as accessing the decorator properties twice results in two different instances. Therefore the following code would not work as expected:
As already mentioned, all methods have an overload which uses a given predicate to decorate only items that meet the predicate’s conditions. The following example shows how to constrain only nodes which are “normal” nodes, i.e., not group nodes:
Because each decorator property access creates a new LookupDecorator<TDecoratedType,TInterface>, it is easy to configure different behavior for different graph elements independent of each other. The following example first disables the reshape handles for all nodes by hiding the default implementation and after that adds a customized reshape behavior only for group nodes: