The Arakhnê.org Network Editor (NetEditor) is a free Java component that permits to edit and show connected-graphs. NetEditor is only composed by a drawing area in which you can draw nodes and edges.
NetEditor supports the following features:
This version of NetEditor is an original idea of Mahdi HANNOUN and Stéphane GALLAND. In 2001, we decide to develop a new graph-editing library because existing ones don't support our needs, or are to difficult to extend.

Note that the groupId and the artifactId of Maven modules to include can be found in the API document.
Because NetEditor is a Maven project, we recommend to use our Maven repository as explained in the page on the Arakhnê.org Maven repository.
If you do not want to use Maven, you should dowload the Jar file manually:
NetEditor is designed to create editors for Visual Languages. NetEditor assumes the language constructs is separated than the graphical representations of these constructs. NetEditor also assumes all the language constructs is expressed with a graph composed with the three following concepts:
The language of the diagram that should be edited by NetEditor must be defined with Java classes that are extending the node, edge and anchor classes.
Each language construct (node, edge and anchor) may be associated to one graphical representation (also named the view of the construct). This graphical representation is in charge of the rendering of the construct's information on on a Java panel.
This section contains a tutorial that permits to create a simple Finite State Machine based on the NetEditor library that is illustrated by the figure above. The definition of a new language's editor should follow the steps:
The maven module related to this step is: org.arakhne.neteditor.fsm:fsm-constructs.
The Finite State Machine language is composed of the constructs: state, transitions, start point, end point.
The FSM diagram contains three types of nodes: state, end point, and start point. It is recommended to create an abstract class that is common to all the implementations of these nodes. Each node of an FSM contains only one anchor.
package org.arakhne.neteditor.fsm.constructs ; import org.arakhne.neteditor.formalism.standard.StandardMonoAnchorNode; public class AbstractFSMNode extends StandardMonoAnchorNode<FiniteStateMachine,AbstractFSMNode,FSMAnchor,FSMTransition> { public AbstractFSMNode() { super(new Anchor()); } }
The AbstractFSMNode class extends the class StandardMonoAnchorNode, which is the implementation of a node with a single anchor inside. The generic parameters are the types of the graph, node, anchor and transition supported by the implementation (here the FSM classes). The constructor of the super class takes an instance of anchor as parameter. The FSMAnchor is used.
The AbstractFSMNode class permits to simplify the use of the generic parameters.
The FSM state is defined in the class FSMState. A state has a name, an action to execute when entering inside the state, and action to execute when exiting from the state, and an action when staying in the state.
package org.arakhne.neteditor.fsm.constructs ; import java.io.IOException; import java.util.Map; public class FSMState extends AbstractFSMNode { private String enterAction = null; private String insideAction = null; private String exitAction = null; public FSMState() { super(); } public String getEnterAction() { return this.enterAction; } public void setEnterAction(String action) { String na = (action==null || action.isEmpty()) ? null : action; if ((this.enterAction==null && na!=null)|| (this.enterAction!=null && !this.enterAction.equals(na))) { String old = this.enterAction; this.enterAction = na; firePropertyChanged("enterAction", old, this.enterAction); } } public String getExitAction() { return this.exitAction; } public void setExitAction(String action) { String na = (action==null || action.isEmpty()) ? null : action; if ((this.exitAction==null && na!=null)|| (this.exitAction!=null && !this.exitAction.equals(na))) { String old = this.exitAction; this.exitAction = na; firePropertyChanged("exitAction", old, this.exitAction); } } public String getAction() { return this.insideAction; } public void setAction(String action) { String na = (action==null || action.isEmpty()) ? null : action; if ((this.insideAction==null && na!=null)|| (this.insideAction!=null && !this.insideAction.equals(na))) { String old = this.insideAction; this.insideAction = na; firePropertyChanged("insideAction", old, this.insideAction); } } @Override public Map<String, Object> getProperties() { Map<String,Object> properties = super.getProperties(); properties.put("enterAction", this.enterAction); properties.put("insideAction", this.insideAction); properties.put("exitAction", this.exitAction); return properties; } @Override public void setProperties(Map<String, Object> properties) throws IOException { super.setProperties(properties); if (properties!=null) { setEnterAction(propGet(String.class, "enterAction", this.enterAction, properties)); setExitAction(propGet(String.class, "exitAction", this.exitAction, properties)); setAction(propGet(String.class, "insideAction", this.insideAction, properties)); } } }
FSMState extends the abstract FSM node class.FSMState. If the setter functions are firing property-change events when the value of an attribute is changing.FSMState class from an external file, we must override the functions getProperties and setProperties. The values of the three attributes must be replied and set by these functions, respectively. Note that the function propGet is an utility function provided by the super class. It permits to retreive the properties and cast it to the proper type in one call. Do not forget to invoke the super implementations.The FSM transition is defined in the class FSMTransition. A transition has a name, an action to execute when transition is traversed, and a guard (a condition) that must be true to traverse the transition.
package org.arakhne.neteditor.fsm.constructs ; import java.io.IOException; import java.util.Map; import org.arakhne.neteditor.formalism.standard.StandardEdge; public class FSMTransition extends StandardEdge<FiniteStateMachine,AbstractFSMNode,FSMAnchor,FSMTransition> { private String action = null; private String guard = null; public FSMTransition() { super(); } public String getAction() { return this.action; } public void setAction(String action) { String na = (action==null || action.isEmpty()) ? null : action; if ((this.action==null && na!=null) || (this.action!=null && !this.action.equals(na))) { String old = this.action; this.action = na; firePropertyChanged("action", old, this.action); } } public String getGuard() { return this.guard; } public void setGuard(String guard) { String ng = (guard==null || guard.isEmpty()) ? null : guard; if ((this.guard==null && ng!=null) || (this.guard!=null && !this.guard.equals(ng))) { String old = this.guard; this.guard = ng; firePropertyChanged("guard", old, this.guard); } } @Override public Map<String, Object> getProperties() { Map<String,Object> properties = super.getProperties(); properties.put("action", this.action); properties.put("guard", this.guard); return properties; } @Override public void setProperties(Map<String, Object> properties) throws IOException { super.setProperties(properties); if (properties!=null) { setAction(propGet(String.class, "action", null, properties)); setGuard(propGet(String.class, "guard", null, properties)); } } @Override public String getExternalLabel() { StringBuilder label = new StringBuilder(); String guard = getGuard(); if (guard!=null) { label.append(guard); } String action = getAction(); if (action!=null) { label.append("/"); label.append(action); } return label.toString(); } public void setStartNode(FSMState node) { FSMAnchor anchor = null; if (node!=null && !node.getAnchors().isEmpty()) { anchor = node.getAnchors().get(0); } setStartAnchor(anchor); } public void setEndNode(FSMState node) { FSMAnchor anchor = null; if (node!=null && !node.getAnchors().isEmpty()) { anchor = node.getAnchors().get(0); } setEndAnchor(anchor); } }
FSMTransition class extends the class StandardEdge, which is the implementation of an edge. The generic parameters are the types of the graph, node, anchor and transition supported by the implementation (here the FSM classes).FSMTransition. If the setter functions are firing property-change events when the value of an attribute is changing.FSMTransition class from an external file, we must override the functions getProperties and setProperties. The values of the three attributes must be replied and set by these functions, respectively. Note that the function propGet is an utility function provided by the super class. It permits to retreive the properties and cast it to the proper type in one call. Do not forget to invoke the super implementations.FSMTransition to two FSMState instances, two helpful functions are added: setStartNode and setEndNode. These functions set the start anchor and the end anchor of the edge to the single anchor of given nodes.The FSM start point is the point from which the simulation of the FSM must start. It is not a state by itself, but it is an AbstractFSMNode because it should be link to other FSM nodes with FSM transitions.
package org.arakhne.neteditor.fsm.constructs ; public class FSMStartPoint extends AbstractFSMNode { public FSMStartPoint() { super(); } }
FSMStartPoint extends the abstract FSM node class.The FSM end point is the point at which the simulation of the FSM is stopping. It is not a state by itself, but it is an AbstractFSMNode because it should be link to other FSM nodes with FSM transitions.
package org.arakhne.neteditor.fsm.constructs ; public class FSMEndPoint extends AbstractFSMNode { public FSMEndPoint() { super(); } }
FSMEndPoint extends the abstract FSM node class.The anchor is a construct provided by the NetEditor library to help to define how the edges and the nodes are connected. The implementation of the anchor must defined functions that permits to test if a node and an edge could be connected together.
package org.arakhne.neteditor.fsm.constructs ; import org.arakhne.neteditor.formalism.standard.StandardAnchor; public class FSMAnchor extends StandardAnchor<FiniteStateMachine,AbstractFSMNode,FSMAnchor,FSMTransition> { public FSMAnchor() { super(); } @Override public boolean canConnectAsEndAnchor(FSMTransition edge, FSMAnchor startAnchor) { if (getNode() instanceof FSMStartPoint) { return false; } if (getNode() instanceof FSMEndPoint) { if (startAnchor!=null) { return startAnchor.getNode() instanceof FSMState; } } return true; } @Override public boolean canConnectAsStartAnchor(FSMTransition edge, FSMAnchor endAnchor) { if (getNode() instanceof FSMEndPoint) { return false; } if (getNode() instanceof FSMStartPoint) { if (endAnchor!=null) { return endAnchor.getNode() instanceof FSMState; } } return true; } }
FSMAnchor extends the StandardAnchor, which is providing a standard implementation for anchors.canConnectAsStartAnchor must be overridden to specify when an AbstractFSMNode can be connected to the start of a FSMTransition. The start of a FSMTransition cannot be a FSMEndPoint. If the current anchor is binded to a FSMStartPoint then the other side of the FSMTransition must be a FSMState to accept the connection. In all the other cases, the connection is accepted.canConnectAsEndAnchor must be overridden to specify when an AbstractFSMNode can be connected to the end of a FSMTransition. The end of a FSMTransition cannot be a FSMStartPoint. If the current anchor is binded to a FSMEndPoint then the other side of the FSMTransition must be a FSMState to accept the connection. In all the other cases, the connection is accepted.The class that is representing the FSM is FiniteStateMachine. According to the NetEditor library, the class FiniteStateMachine is a type of graph and it extends StandardGraph with the appropriate generic parameters.
package org.arakhne.neteditor.fsm.constructs ; import org.arakhne.neteditor.formalism.standard.StandardGraph; public class FiniteStateMachine extends StandardGraph<FiniteStateMachine,AbstractFSMNode,FSMAnchor,FSMTransition> { public FiniteStateMachine() { super(); } }
The maven module related to this step is: org.arakhne.neteditor.fsm:fsm-figures. All the language constructs should have a figure to be rendered.
The figure class FSMEndPoint extends the class CircleNodeFigure which the implementation of a circle provided by the NetEditor library.
package org.arakhne.neteditor.fsm.figures ; import java.awt.Color; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; import java.util.UUID; import org.arakhne.neteditor.fig.figure.node.CircleNodeFigure; import org.arakhne.neteditor.fig.graphics.ViewGraphics2D; import org.arakhne.neteditor.fsm.constructs.FSMAnchor; import org.arakhne.neteditor.fsm.constructs.FSMEndPoint; public class FSMEndPointFigure extends CircleNodeFigure<FSMEndPoint,FSMAnchor> { public FSMEndPointFigure(UUID viewId, float x, float y) { super(viewId, x, y); setResizeDirections(); setMinimalDimension(20, 20); setMaximalDimension(20, 20); } public FSMEndPointFigure(UUID viewId) { this(viewId, 0, 0); } @Override protected void paintNode(ViewGraphics2D g) { g.beginGroup(); super.paintNode(g); Rectangle2D figureBounds = g.getCurrentViewBounds(); Ellipse2D oval = new Ellipse2D.Float( (float)figureBounds.getX() + 5, (float)figureBounds.getY() + 5, (float)figureBounds.getWidth() - 10, (float)figureBounds.getHeight() - 10); g.setInteriorPainted(true); g.setOutlineDrawn(false); Color old = g.setFillColor(g.getOutlineColor()); g.draw(oval); g.setFillColor(old); g.endGroup(); } }
paintNode must be overridden to draw the end point in a proper way.Graphics2D instance, but is is a ViewGraphics2D instance. Why? First, Graphics2D is not dedicated to vectorial drawing, and ViewGraphics2D is. Second, Graphics2D is an AWT implementation, and ViewGraphics2D is platform-independent (AWT, Android...).ViewGraphics2D graphical context distinguishes the outline and the interior of a draw. The function draw is able to draw the outline and the interior at the same time. The functions setInteriorPainted and setOutlineDrawn permits to enable or disable the drawing of the interior and the outline, respectively. In opposite to the famous Graphics2D, which has one current color, the ViewGraphics2D distinguishes the color used to draw the outline and the color used to draw the interior.getCurrentViewBounds permits to retreive the bounds of the figure.beginGroup and endGroup permits to group the drawn element into a single draw. Grouping is very useful when the figure should be exported into vectorial formats such as SVG.The figures for the FSMStartPoint, FSMState, FSMTransition, and FSMAnchor are coded in a similar way.
The maven module related to this step is: org.arakhne.neteditor.fsm:fsm-figures. The NetEditor viewer requires to have a figure factory to create the figures when they are not yet associated to graph elements that may be rendered. The class FSMFigureFactory is the implementation of a figure factory dedicated to the FSM classes. It extends the class AbstractStandardFigureFactory, whish provides a standard implementation for factories.
package org.arakhne.neteditor.fsm.figures ; import java.awt.geom.Dimension2D; import java.util.UUID; import org.arakhne.neteditor.awt.DoubleDimension; import org.arakhne.neteditor.fig.anchor.AnchorFigure; import org.arakhne.neteditor.fig.anchor.InvisibleCircleAnchorFigure; import org.arakhne.neteditor.fig.anchor.InvisibleRoundRectangularAnchorFigure; import org.arakhne.neteditor.fig.factory.AbstractStandardFigureFactory; import org.arakhne.neteditor.fig.factory.FigureFactoryException; import org.arakhne.neteditor.fig.figure.Figure; import org.arakhne.neteditor.fig.subfigure.SubFigure; import org.arakhne.neteditor.fig.view.ViewComponentConstants; import org.arakhne.neteditor.formalism.ModelObject; import org.arakhne.neteditor.formalism.Node; import org.arakhne.neteditor.fsm.constructs.FSMAnchor; import org.arakhne.neteditor.fsm.constructs.FSMEndPoint; import org.arakhne.neteditor.fsm.constructs.FSMStartPoint; import org.arakhne.neteditor.fsm.constructs.FSMState; import org.arakhne.neteditor.fsm.constructs.FSMTransition; import org.arakhne.neteditor.fsm.constructs.FiniteStateMachine; public class FSMFigureFactory extends AbstractStandardFigureFactory<FiniteStateMachine> { /** */ public FSMFigureFactory() { super(); } @Override public Figure createFigureFor(UUID viewID, FiniteStateMachine graph, ModelObject object, float x, float y) throws FigureFactoryException { Figure fig = null; FSMAnchor anchor = null; if (object instanceof FSMState) { FSMState node = (FSMState) object; anchor = node.getAnchors().get(0); FSMStateFigure figure = new FSMStateFigure(viewID, x, y); figure.setModelObject(node); fig = figure; } else if (object instanceof FSMStartPoint) { FSMStartPoint node = (FSMStartPoint) object; anchor = node.getAnchors().get(0); FSMStartPointFigure figure = new FSMStartPointFigure(viewID, x, y); figure.setModelObject(node); fig = figure; } else if (object instanceof FSMEndPoint) { FSMEndPoint node = (FSMEndPoint) object; anchor = node.getAnchors().get(0); FSMEndPointFigure figure = new FSMEndPointFigure(viewID, x, y); figure.setModelObject(node); fig = figure; } if (fig!=null && anchor!=null) { AnchorFigure<FSMAnchor> subfig; if (fig instanceof FSMStateFigure) { subfig = createStateAnchorFigure(viewID, fig.getWidth(), fig.getHeight()); } else { subfig = createEndAnchorFigure(viewID, Math.max(fig.getWidth(), fig.getHeight())); } subfig.setModelObject(anchor); return fig; } throw new FigureFactoryException(); } @Override public SubFigure createSubFigureInside(UUID viewID, FiniteStateMachine graph, Figure parent, ModelObject object) { if (object instanceof FSMAnchor) { if ((parent instanceof FSMStartPointFigure) ||(parent instanceof FSMEndPointFigure)) { AnchorFigure<FSMAnchor> subfig = createEndAnchorFigure(viewID, Math.max(parent.getWidth(), parent.getHeight())); subfig.setModelObject((FSMAnchor)object); return subfig; } if (parent instanceof FSMStateFigure) { AnchorFigure<FSMAnchor> subfig = createStateAnchorFigure(viewID, parent.getWidth(), parent.getHeight()); subfig.setModelObject((FSMAnchor)object); return subfig; } } throw new FigureFactoryException(); } @Override public Figure createFigureFor(UUID viewID, FiniteStateMachine graph, ModelObject object, float x1, float y1, float x2, float y2) throws FigureFactoryException { if (object instanceof FSMTransition) { FSMTransitionFigure figure = new FSMTransitionFigure(viewID, x1, y1, x2, y2); figure.setModelObject((FSMTransition)object); return figure; } throw new FigureFactoryException(); } private static AnchorFigure<FSMAnchor> createEndAnchorFigure(UUID viewID, float size) { AnchorFigure<FSMAnchor> figure = new InvisibleCircleAnchorFigure<>( viewID, 0, 0, size/2f); return figure; } private static AnchorFigure<FSMAnchor> createStateAnchorFigure(UUID viewID, float width, float height) { AnchorFigure<FSMAnchor> figure = new InvisibleRoundRectangularAnchorFigure<>( viewID, 0, 0, width, height); return figure; } @Override protected Dimension2D getPreferredNodeSize(Node<?, ?, ?, ?> node) { return new DoubleDimension( ViewComponentConstants.DEFAULT_MINIMAL_SIZE, ViewComponentConstants.DEFAULT_MINIMAL_SIZE); } }
createFigureFor(UUID, FiniteStateMachine, ModelObject, float, float) is invoked to create a figure for the given model object (which is always a node) at the specified position. The following implementation creates the corresponding figure for the FSM elements. If the figure to create is for a FSMState, then the function is also creating the figures for the FSMAnchor.createFigureFor(UUID, FiniteStateMachine, ModelObject, float, float, float, float) is invoked to create a figure for the given model object (which is always an edge) from the specified point to the given point. The following implementation creates the corresponding figure for the FSM transition.createSubFigureInside(UUID, FiniteStateMachine, Figure, ModelObject) is invoked to create the subfigure inside the given figure for the specified FSM element (usually an FSMAnchor).The maven module related to this step is: org.arakhne.neteditor.fsm:fsm-editor. The FSM editor is a frame that contains the NetEditor viewer, which is an instance of JFigureViewer. The standard way to create the editor is illustrated in the following code.
package org.arakhne.neteditor.fsm ; public class FSMEditor extends JFrame { private final JFigureViewer<FiniteStateMachine> figurePanel; private FiniteStateMachine stateMachine; public FSMEditor() throws IOException { super(Locale.getString("TITLE")); //... this.stateMachine = new FiniteStateMachine(); this.figurePanel = new JFigureViewer<>( FiniteStateMachine.class, this.stateMachine, new FSMFigureFactory(), true); this.figurePanel.setAxisDrawn(true); add(BorderLayout.CENTER, this.figurePanel); //... } //... }