DocumentationCopyright © 2003 Université Montpellier II 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 Table of Contents 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/. Before starting to create a new graph editor, we searched a long time for an editor that could use any kind of graph, In a completely generic way. We wanted a graph editor that could apply algorithms on a specific graph, without having to use a specific programm each time. The perfect (but entirely deprecated) model was GSS, an old generic graph editor written in C. This editor doesn't work on any actual system, so we were unable to test it. Other good stuff like Graphlet (very restrictive license) or Grace (an editor generator) have their owns problems and so we decided to write a new one. We hope you'll find it nice and easy to use. Feel free to ask us questions if you have. 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). In order to run GraphTool, you have to install DiaCanvas2 + its bindings for Python. To install DiaCanvas2 (please report to DiaCanvas2 installation files) you have to get and install:
Then we can install our patched version of DiaCanvas2: $ tar xzf diacanvas2-X.Y.Z.tar.gz $ cd diacanvas2-X.Y.Z $ gzip -dc ../diacanvas2-X.Y.Z-graphtool.patch.gz | patch -p1 $ automake $ autoconf Once we've got the patched diacanvas, we can process the usual way: $ ./configure --help $ ./configure [--your-options-here] --enable-python=yes At the end of the configure, be sure there is the line: Python wrapper: yes We can finish the installation: $ make # make install We only tested GraphTool on Linux, if you can run it under an other OS, please feed back (it shouldn't cause any problems). 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). The graph model defined in GraphTool is quite simple. We have a Graph class that represents the canvas, it contains lists of all nodes and edges. You have to redefine it to make your own graph. Next we have the Node and Arc classes, which respectively represents a node and an arc on the graph. You can redefine these classes to add new properties/methods to the objects. You can also change the shape of the object, the color, etc. This diagram shows us relations between Graph, Node and Arc:
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 you create your graph, you have the possibility to redefine the buttons in the toolbars in an easy way. When a user open a new graph project, the GUI constructs the toolbar with informations gathered from the graph itself. The GUI does: items = graph.get_toolbar() And in the Graph class, there is a default get_toolbar method: def get_toolbar (self): return [['IN','add_node.png',self.make_node], ['IE','add_arc.png',self.make_arc]] In fact this method simply return a list of buttons. These buttons are represented by a list containing a string 'IN' or 'IE' (respectively "Instanciate Node" or "Instanciate Edge"), followed by an icon name and followed by a callback to use to instanciate the object. So you can customize this toolbar putting as many buttons you want, even non-creating buttons [work in progress]. 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. Example 3. Changing shape example from PetriNet's Transition node def init_shapes (self): self.set (height = 40, width = 40) self._shapes = [] self._shapes.append (dia.shape.Path ()) self._shapes[0].rectangle ((13,0), (27,self.height)) self._shapes[0].set_fill (1) self._shapes[0].set_fill_color (dia.color (250,250,250)) self._shapes[0].set_line_width (1) self.init_labels () Note that self.init_labels() is defined in the Petri_Node so you have to remove this line if you want to be able to use this code. We think the arcs are not yet very customizable. In fact they are not even finished (we can't do a loop with only one node). Three types of constraints are defined in the Graph class.A Python dictionary exists for each of those types, to save the rlated constraints.
A constraint in the dictionary is in the form: self._type_constraint = {'constraint_name' : [constraint_function, (parameters)]} TODO: write an example and continue explanations | |