Next Up Previous Contents

5 Assigning data to nodes or edges

Assigning data to nodes or edges

Obviously there is a necessity to assign some data, e.g. coordinates for drawing, to nodes and/or edges. Of course this could be done using map<node, some_class>, but since every node has an unique ID we implemented more efficient solutions for this task. The following could be used to assign an integer to every node (edge) of a given graph G:

   ...

   node_map<int> node_num(G, 0);
   edge_map<int> edge_num(G, 0);

   graph::nodes_iterator it = G.nodes_begin();
   graph::nodes_iterator end = G.nodes_end();

   while(it != end) {
      cout << *it << ": " << node_num[*it] << endl;
   }

   ...
Providing the optional second argument to the constructor determines the default value of the entries in the node_maps. It is important to know that these specialized maps can hold only nodes or edges of the same graph. So if some information has to be assigned to nodes (edges) in different graphs, map<node, ...> has to be used. As shown above for storing or retrieving the value of some node the []-operator can be used.

5.1 Customization

Customization

In any larger application it is inevitable to perform certain actions, e.g. init or destroy some datastructure, when a graph is modified. This is why we introduced the notion of a handler. This is basically a virtual, empty member function of graph called whenever the corresponding action is executed. To be precise for some action xyz, there are two handler with names pre_xyz_handler(...) and post_xyz_handler(...). In detail the following are provided:

As already mentioned these functions are virtual, which means that using this class as base class gives the possibility to override them and thus to react adequatly to some of these operations.

For instance, say that one wants to assign coordinates to the nodes in his graphs. Basically this can achieved by holding two node_map<int>, one for the X- and one for th Y-coordinate. But what is the right place for these maps to go? Of course, anyone designing the graph datastructure from scratch would position these maps into it. The good news is that one can do the same thing in GTL. The first thing to do is to use graph as baseclass and add the two maps:

   class my_graph : public graph {
   public:

      //
      // Constructor: do initialisation.
      // 
      my_graph() : graph () 
         { X.init (*this); Y.init (*this); }

      ...


   private:
      node_map<int> X;
      node_map<int> Y; 
   } 
Obviously this doesn't do anything useful so far. What we need, besides from access functions for the coordinates, are control over the creation and deletion, and perhaps over hiding and restoring. You can do this either by providing new member function like:
   class my_graph : public graph {
   public:
   ...
   new_node (int x, int y) {
      node n = new_node ();
      X[n] = x;
      Y[n] = y;
   }
   ...
   }
But then you have to take care that you only use these new functions in oder to prohibit the creation of a new node with undefined X- and Y-entries. Another thing to have in mind is that you can't use my_graph as graph.

So far we haven't used the handler-feature anywhere. Provided that the class my_graph has access functions for the X- and Y-coordinates (something like set_X (node n, int x) and get_X(node n), etc.), one can use the handler to give a new node a default entry in the maps. After that the user has to take care to set the values accordingly. Here this would look like that:

   class my_graph : public graph {
   public:
   ...
   virtual void post_new_node_handler(node n) {
      X[n] = 42;
      Y[n] = 42;   
   }
   ...
   }

Of course there are many more handler, which can be used to customize the behaviour of a subclass of graph. Through these rather complicated problems can be solved. We use this method in Graphlet, so this isn't just academic.


Next Up Previous Contents