Documentation

This file is part of GraphTool.

GraphTool is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

GraphTool is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with GraphTool; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA


GraphTool is a tool for the creation and visual edition of any kind of graph written in Python.

It makes use of DiaCanvas2, a diagramming library for GNOME. DiaCanvas2 provides a canvas on which we can put our nodes and arcs and change their visual aspect. Additionnaly, we automatically can have a SVG output of our graph.

The official website is http://www.nongnu.org/graphtool/.

GraphTool is written by Samuel Poujol, Camille Huot, Aurélien Campéas and Evelyne Zahn.

It has started by a school project managed by Alain Jean-Marie at the Université Montpellier II. Thus, The Université Montpellier II is the copyright holder.

You can download the latest version of GraphTool on Savannah (the official website) at http://www.nongnu.org/graphtool/. Please note that GraphTool runs on a modified version of DiaCanvas2, so you have to patch Diacanvas before installing graphtool (I hope we will get rid of this patch some day).

When you use GraphTool, chances are you won't use the editor as is. You'll probably want to define your own graph to make use of your specifics algorithms.

To do this, you have to make your own Python classes, that must inherit our bases classes: Graph, Node and Arc (or Edge for non-oriented edges).

To illustrate the theory, here is a simple graph specialisation.

We'll write a specialized graph which counts its nodes and edges. Its specialized nodes and edges will name themselves.

Example 1. A complete and usable example

# we have to specify the location of GraphTool
import sys
sys.path.append('/nfs1/etu/info/mait/chuot/ter/cam/graphtool') # please change this

# we import the modules containing the objects we'll extend
import Graph
import Node
import Arc

# the primitives module contains useful functions to interact with the user
import primitives

class MyGraph(Graph.Graph):
    def __init__(self):
        # mandatory call to superclass instance init func
        Graph.Graph.__init__(self)

        # set the node and arc default type
        self._node_type = MyNode
        self._arc_type = MyEdge

        # variable to count nodes
        self.nb_nodes = 0

        # variable to count edges
        self.nb_edges = 0

        # register methods to allow the user read theses values with GraphTool
        self.add_accessor('Number of nodes', self.get_nb_nodes, None)
        self.add_accessor('Number of edges', self.get_nb_edges, None)

    # we intercept the event to take action

    def on_node_creation(self, node):
        self.nb_nodes = self.nb_nodes + 1

    def on_arc_creation(self, arc):
        self.nb_edges = self.nb_edges + 1


    # we now have to write our method to read the values

    def get_nb_nodes(self):
        return self.nb_nodes
    def get_nb_edges(self):
        return self.nb_edges

# we now make our node class (inherits Node of Node module)
class MyNode(Node.Node):
    def __init__(self, graph, name):
        # mandatory
        Node.Node.__init__(self, graph, name)

        # we ask the user to name the node
        self.name = primitives.prompt_user(self._graph._frame, "Enter the node's name")
        
        # we add an accessor to be able to modify this name later
        self.make_default_accessors()
        # this method adds an accessor for each object's variable
        # that does not begin with a '_'

    # when the node is deleted
    def on_delete(self):
        self.graph.nb_nodes = self.nb_nodes - 1
	# we have to return True so that the node will be really deleted
        return True

# and finally our edge class
class MyEdge(Arc.Arc):
    def __init__(self, graph):
        # mandatory
        Arc.Arc.__init__(self, graph)

        # we ask the user to name the arc
        self.name = primitives.prompt_user(self, "Enter the arc's name")

        # and we add the accessor
        self.make_default_accessors()

    def on_delete(self):
        self.graph.nb_edges = self.nb_edges - 1
        return True

# see how it is easy ?
    

To install the newly created graph: run GraphTool, choose 'Types' then 'Add', choose the file MyGraph.py wherever it is, then click 'OK'. You can now create a new MyGraph by clicking 'File', 'New', 'MyGraph'.

Fell free to try it, compare the utilisation with the base classes and modify this file to experiment or use it as a skeleton to create your own classes.

As a graph developer, you should know some hints.

You should take in consideration that our base graph contains oriented arcs. If you want to make a graph with non-oriented arcs, you have to make your own Edge class.

The class Graph inherits from the diacanvas.Canvas class. For more information on this, please read the DiaCanvas2 documentation, and more precisely http://diacanvas.sourceforge.net/ref/DiaCanvas.html. At any time, if you want to force the screen refresh, you can apply an update_now() on the graph.

Each node or arc in the graph has got an unique name, automatically generated at creation time. This name has to be read-only for implementation needs. Indeed this unique name acts as a key in the dictionary that contains all nodes or all arcs. In addition, the name acts as the "GraphXML id" and has to be unique for this purpose too.

We now know that a graph has two Python dictionaries, one for nodes, another for arcs. The key of the dictionary is the unique name, and the value is the node or arc object himself.

When a node (the full story is OK for arcs too) is created, its constraints are checked (see below) and the object is put on the canvas. Then a user definable method is called: graph.on_node_creation(node) (for arcs it is graph.on_arc_creation(arc). By default this method does nothing. You can customize it as you want to take action on node creation. (See A complete and usable example).

You can modify the default shape of a Node object, or add new shapes, by modifying the _shapes list of shapes of the object.

By default, _shapes[0] (the first shape of the list) contains an ellipse of 30 pixels width, filled with white color.

The update X signal automatically refresh all the shapes contained in this list when requested by X. So you just have to add your shape to the list.

For example, if you want to change the ellipse, and use a PNG picture instead, you have to do:

mynode._shapes[0] = dia.shape.Image()
mynode._shapes[0].image(gtk.gdk.pixbuf_new_from_file(filename='mypng.png'))

To add a second ellipse:

mynode._shapes.append(dia.shape.Ellipse())
mynode._shapes[-1].ellipse(center=(self.width/2, self.height/2), width=self.width, height=self.height))

mynode._shapes[-1] being the last shape of the list, so the one you just appended.

This is the node.init_shapes() method that initializes the shape. If you redefine this method the default ellipse will go away and your shape will be shown.