Contribute custom diagram nodes
This guide explains how an application can contribute and use its own custom nodes inside diagram representations. The example below illustrates an “Ellipse” shape, but the same steps apply to other node types.
1. Backend implementation
1.1. View DSL integration
This optional step is highly recommended so studio makers can configure the node style from the View DSL, just like the core node types.
-
Create an Ecore model defining a subtype of
diagram.ecore’sNodeStyleDescription(org.eclipse.sirius.components.view.diagram.NodeStyleDescription) with the attributes required by your node. -
Provide tooling (View-based DiagramDescription) so authors can instantiate the new description (e.g.,
EllipseNodeStyle). -
Declare an
INodeStyleProviderthat converts the modeled style description into the runtime implementation:@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()); } } -
Extend the GraphQL schema with your style:
type EllipseNodeStyle { borderColor: String! borderSize: Int! borderStyle: LineStyle! color: String! } extend union INodeStyle = EllipseNodeStyle
the GraphQL type (EllipseNodeStyle) must match the Java implementation class (EllipseNodeStyle.java).
|
2. Frontend implementation
-
Declare the
GQLNodeStylesubtype for your new node:import { GQLNodeStyle } from '@eclipse-sirius/sirius-components-diagrams-reactflow'; export interface GQLEllipseNodeStyle extends GQLNodeStyle { color: string; borderColor: string; borderStyle: string; borderSize: string; } -
Implement the React Flow component (e.g.,
EllipseNode.tsx) and any helpers required to render your shape. -
Provide an
INodeConverterthat convertsGQLNodeinstances into React Flow nodes:export class EllipseNodeConverter implements INodeConverter { canHandle(gqlNode: GQLNode) { return gqlNode.style.__typename === 'EllipseNodeStyle'; } 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 an
INodeLayoutHandlerfor the layout logic:export class EllipseNodeLayoutHandler implements INodeLayoutHandler<NodeData> { canHandle(node: Node<NodeData, DiagramNodeType>) { return node.type === 'ellipseNode'; } handle( layoutEngine: ILayoutEngine, previousDiagram: Diagram | null, node: Node<NodeData>, visibleNodes: Node<NodeData, DiagramNodeType>[], directChildren: Node<NodeData, DiagramNodeType>[], forceWidth?: number ) { // ... } } -
Extend the GraphQL document with the extra fields required by your node (for example through a
DocumentTransformcontributed to Apollo). -
Finally, register everything in the
NodeTypeRegistrypassed toDiagramRepresentationConfiguration:const nodeTypeRegistry: NodeTypeRegistry = { nodeLayoutHandlers: [new EllipseNodeLayoutHandler()], nodeConverters: [new EllipseNodeConverter()], nodeTypeContributions: [<NodeTypeContribution component={EllipseNode} type={'ellipseNode'} />], };
the nodeTypeContributions type must match the value assigned during conversion (ellipseNode above).
|