This document shows the steps needed for an application to contribute and use its own custom nodes to Diagram representations.
We will use the example of a "Ellipse" shape to illustrate this.
While technically optional, this step is highly recommended as it will allow node style to be configurable using the View DSL like the rest of the core node types.
First, create your own Ecore model.
It must define sub-types of the NodeStyleDescription
EClass from diagram.ecore
(org.eclipse.sirius.components.view.diagram.NodeStyleDescription
) with the appropriate configuration attributes.
With this information, it becomes possible to create instances of your node’s View-based description class (e.g. EllipseNodeStyle
) inside a View-based DiagramDescription.
The final step is to tell the system how to convert these modeled node style description into their corresponding core implementation.
This is done by declaring a INodeStyleProvider
:
@Service
public class EllipseNodeStyleProvider implements INodeStyleProvider {
@Override
public Optional<String> getNodeType(NodeStyleDescription nodeStyle) {
if (nodeStyle instanceof EllipseNodeStyleDescription) {
return Optional.of(NODE_ELLIPSE);
}
return Optional.empty();
}
@Override
public Optional<INodeStyle> createNodeStyle(NodeStyleDescription nodeStyle, Optional<String> optionalEditingContextId) {
// ...
return Optional.of(EllipseNodeStyle.newEllipseNodeStyle().build());
}
}
You will need to declare your new nodeStyle in the global GraphQL Schema.
This is done by simply providing a .graphqls
schema file.
Note that the file must be in a schema
folder in the classpath to be detected.
For the Ellipse node we have /sirius-web-sample-application/src/main/resources/schema/customnodes.graphqls
with the following content:
type EllipseNodeStyle {
borderColor: String!
borderSize: Int!
borderStyle: LineStyle!
color: String!
}
extend union INodeStyle = EllipseNodeStyle
Important
|
Make sure the type of the nodeStyle declared in the GraphQL Schema (type EllipseNodeStyle above) matches the name of the Java class implementing the node (EllipseNodeStyle.java ).
|
-
Declare the
GQLNodeStyle
concrete sub-type for you new node:
// File: packages/sirius-web/frontend/sirius-web/src/nodes/EllipseNode.types.ts
import { GQLNodeStyle } from '@eclipse-sirius/sirius-components-diagrams-reactflow';
export interface GQLEllipseNodeStyle extends GQLNodeStyle {
color: string;
borderColor: string;
borderStyle: string;
borderSize: string;
}
-
Implement the actual frontend React-flow node type for your new shape.
// File: packages/sirius-web/frontend/sirius-web/src/nodes/EllipseNode.tsx
export const EllipseNode = memo(({ data, isConnectable, id, selected }: NodeProps<EllipseNodeData>) => {
// ...
});
-
Implement the handler in charge of the conversion.
// File: packages/sirius-web/frontend/sirius-web/src/nodes/EllipseNodeConverter.ts
export class EllipseNodeConverter implements INodeConverter {
// ...
}
The canHandle
predicate, check on what gqlNode
the converter should be used.
canHandle(gqlNode: GQLNode) {
return gqlNode.style.__typename === 'EllipseNodeStyle';
}
The handle
method, converts a GQLNode
to a concrete Node
and adds it to the nodes array, remember to convert borderNodes and childNodes if necessary.
handle(
convertEngine: IConvertEngine,
gqlNode: GQLNode,
parentNode: GQLNode | null,
isBorderNode: boolean,
nodes: Node[]
) {
nodes.push(toEllipseNode(gqlNode, parentNode, isBorderNode));
convertEngine.convertNodes(gqlNode.borderNodes ?? [], gqlNode, nodes);
convertEngine.convertNodes(gqlNode.childNodes ?? [], gqlNode, nodes);
}
-
Implement the handler in charge of the layout.
// File: packages/sirius-web/frontend/sirius-web/src/nodes/EllipseNodeLayoutHandler.ts
export class EllipseNodeLayoutHandler implements INodeLayoutHandler<NodeData> {
// ...
}
The canHandle
predicate, check on what node
the layout should be used.
canHandle(node: Node<NodeData, DiagramNodeType>) {
return node.type === 'ellipseNode';
}
The handle
method, layout a node by setting its size and position, remember to take into account borderNodes and childNodes if necessary.
handle(
layoutEngine: ILayoutEngine,
previousDiagram: Diagram | null,
node: Node<NodeData>,
visibleNodes: Node<NodeData, DiagramNodeType>[],
directChildren: Node<NodeData, DiagramNodeType>[],
forceWidth?: number
) {
// ...
}
-
Declare the custom graphql fields to be retrieved for your custom node
// File: packages/sirius-web/frontend/sirius-web-application/src/extension/ElipseNodeDocumentTransform.ts
export const ellipseNodeStyleDocumentTransform = new DocumentTransform((document) => {
// ...
const ellipseNodeStyleInlineFragment: InlineFragmentNode = {
kind: Kind.INLINE_FRAGMENT,
selectionSet: {
kind: Kind.SELECTION_SET,
selections: [borderColorField, borderSizeField, borderStyleField, backgroundField],
},
typeCondition: {
kind: Kind.NAMED_TYPE,
name: {
kind: Kind.NAME,
value: 'EllipseNodeStyle',
},
},
};
// ...
});
In this example we’re looking for borderColorField
, borderSizeField
, borderStyleField
and backgroundField
fields.
-
Finally, you have to contribute all these elements to the
NodeTypeRegistry
given in props toDiagramRepresentationConfiguration
component.
// File: packages/sirius-web/frontend/sirius-web/src/index.tsx
const nodeTypeRegistry: NodeTypeRegistry = {
nodeLayoutHandlers: [new EllipseNodeLayoutHandler()],
nodeConverters: [new EllipseNodeConverter()],
nodeTypeContributions: [<NodeTypeContribution component={EllipseNode} type={'ellipseNode'} />],
};
Important
|
Make sure the type of the nodeTypeContributions matches the type set during the conversion (in EllipseNodeConverterHandler ).
|