In this article, I show how to create a simple graph editor, using MVC like approach and XML serialization via XStream.
Example can be found here: Launch JNLP, Browse on GitHub.
Application brings graph editing functionality and maximum network flow computation using Ford Fulkerson algorithm
Architecture is divided into Model, View and Controller. I made a slight upgrade to classic understanding of MVC. Classic View was responsible for displaying data only. Here, View consists also of UI parts, which include editable labels or combo boxes. Also, in Classic MVC, Model was responsible for refreshing View after Model changes by sending events to registered views. Here, Controller is responsible for refreshing View after Model changes and Model is just a plain POJO structure. I found such approach easier to implement.
Model consists of Three Java classes (non JavaFX), which represent structure of a graph: MNode, MShape, MConnection. Separating those classes from UI gives benefit of easier serialization. In this case using XStream::toXML(model) does the job. Sample output is like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<netflow.model.Model id="1"> | |
<nodes id="2"> | |
<netflow.model.MShape id="3"> | |
<flow>10.0</flow> | |
<capacity>10.0</capacity> | |
<name>node-1</name> | |
<type>SOURCE</type> | |
<x>171.0</x> | |
<y>142.0</y> | |
</netflow.model.MShape> | |
<netflow.model.MLine id="4"> | |
<flow>-10.0</flow> | |
<capacity>10.0</capacity> | |
<a id="5"> | |
<flow>10.0</flow> | |
<capacity>10.0</capacity> | |
<name>node-2</name> | |
<type>SINK</type> | |
<x>394.0</x> | |
<y>157.0</y> | |
</a> | |
<b reference="3"/> | |
</netflow.model.MLine> | |
<netflow.model.MShape reference="5"/> | |
</nodes> | |
</netflow.model.Model> |
View consists of corresponding UI implementations for Model elements, which are UINode, UILine, UIShape. Here, UINode is connected to MNode through model property. This is Bridge Pattern like approach for splitting class hierarchy of nodes into two.
UINode classes refer to controller to perform user input actions, like delete node.
Controller implements user action logic. This includes add, delete, and drag node. It is also responsible for refreshing UI after model changes. In order to do that easily, it uses Weak Hash Map, which keys are Model nodes and values are UI Nodes. Update function is like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public function render(i:MNode):UINode { | |
if (controller.renderedItems.containsKey(i)) { | |
return controller.renderedItems.get(i) as UINode; | |
} else { | |
var uiNode: UINode; | |
if (i instanceof MShape) { | |
uiNode = UIShape { | |
model: i | |
controller: this | |
} | |
} else if (i instanceof MLine) { | |
uiNode = UILine { | |
model: i | |
controller: this | |
} | |
} | |
controller.renderedItems.put(i, uiNode); | |
return uiNode; | |
} | |
} | |
public function update():Void { | |
for (i:MNode in controller.model.nodes) { | |
if (i instanceof MShape) { | |
var s:UIShape = render(i) as UIShape; | |
} | |
} | |
for (i:MNode in controller.model.nodes) { | |
if (i instanceof MLine) { | |
render(i); | |
} | |
} | |
content = for (i:Object in controller.model.nodes) { | |
render(i as MNode) | |
} | |
requestLayout(); | |
} |
JavaFX SVG support and Layouts make it easy to render quite good looking nodes and connections.