python-igraph manual

For using igraph from Python

   Home       Trees       Indices       Help   
Package igraph :: Package drawing :: Module metamagic
[hide private]

Source Code for Module igraph.drawing.metamagic

  1  """Auxiliary classes for the default graph drawer in igraph. 
  2   
  3  This module contains heavy metaclass magic. If you don't understand 
  4  the logic behind these classes, probably you don't need them either. 
  5   
  6  igraph's default graph drawer uses various data sources to determine 
  7  the visual appearance of vertices and edges. These data sources 
  8  are the following (in order of precedence): 
  9   
 10    - The keyword arguments passed to the L{igraph.plot()} function 
 11      (or to L{igraph.Graph.__plot__()} as a matter of fact, since 
 12      L{igraph.plot()} just passes these attributes on). For instance, 
 13      a keyword argument named C{vertex_label} can be used to set 
 14      the labels of vertices. 
 15   
 16    - The attributes of the vertices/edges being drawn. For instance, 
 17      a vertex that has a C{label} attribute will use that label when 
 18      drawn by the default graph drawer. 
 19   
 20    - The global configuration of igraph. For instance, if the global 
 21      L{igraph.config.Configuration} instance has a key called 
 22      C{plotting.vertex_color}, that will be used as a default color 
 23      for the vertices. 
 24   
 25    - If all else fails, there is a built-in default; for instance, 
 26      the default vertex color is C{"red"}. This is hard-wired in the 
 27      source code. 
 28   
 29  The logic above can be useful in other graph drawers as well, not 
 30  only in the default one, therefore it is refactored into the classes 
 31  found in this module. Different graph drawers may inspect different 
 32  vertex or edge attributes, hence the classes that collect the attributes 
 33  from the various data sources are generated in run-time using a 
 34  metaclass called L{AttributeCollectorMeta}. You don't have to use 
 35  L{AttributeCollectorMeta} directly, just implement a subclass of 
 36  L{AttributeCollectorBase} and it will ensure that the appropriate 
 37  metaclass is used. With L{AttributeCollectorBase}, you can use a 
 38  simple declarative syntax to specify which attributes you are 
 39  interested in. For example:: 
 40   
 41      class VisualEdgeBuilder(AttributeCollectorBase): 
 42          arrow_size = 1.0 
 43          arrow_width = 1.0 
 44          color = ("black", palette.get) 
 45          width = 1.0 
 46   
 47      for edge in VisualEdgeBuilder(graph.es): 
 48          print edge.color 
 49   
 50  The above class is a visual edge builder -- a class that gives the 
 51  visual attributes of the edges of a graph that is specified at 
 52  construction time. It specifies that the attributes we are interested 
 53  in are C{arrow_size}, C{arrow_width}, C{color} and C{width}; the 
 54  default values are also given. For C{color}, we also specify that 
 55  a method called {palette.get} should be called on every attribute 
 56  value to translate color names to RGB values. For the other three 
 57  attributes, C{float} will implicitly be called on all attribute values, 
 58  this is inferred from the type of the default value itself. 
 59   
 60  @see: AttributeCollectorMeta, AttributeCollectorBase 
 61  """ 
 62   
 63  from ConfigParser import NoOptionError 
 64  from itertools import izip 
 65   
 66  from igraph.configuration import Configuration 
 67   
 68  __all__ = ["AttributeSpecification", "AttributeCollectorBase"] 
69 70 # pylint: disable-msg=R0903 71 # R0903: too few public methods 72 -class AttributeSpecification(object):
73 """Class that describes how the value of a given attribute should be 74 retrieved. 75 76 The class contains the following members: 77 78 - C{name}: the name of the attribute. This is also used when we 79 are trying to get its value from a vertex/edge attribute of a 80 graph. 81 82 - C{alt_name}: alternative name of the attribute. This is used 83 when we are trying to get its value from a Python dict or an 84 L{igraph.Configuration} object. If omitted at construction time, 85 it will be equal to C{name}. 86 87 - C{default}: the default value of the attribute when none of 88 the sources we try can provide a meaningful value. 89 90 - C{transform}: optional transformation to be performed on the 91 attribute value. If C{None} or omitted, it defaults to the 92 type of the default value. 93 94 - C{func}: when given, this function will be called with an 95 index in order to derive the value of the attribute. 96 """ 97 98 __slots__ = ("name", "alt_name", "default", "transform", "accessor", 99 "func") 100
101 - def __init__(self, name, default=None, alt_name=None, transform=None, 102 func=None):
103 if isinstance(default, tuple): 104 default, transform = default 105 106 self.name = name 107 self.default = default 108 self.alt_name = alt_name or name 109 self.transform = transform or None 110 self.func = func 111 self.accessor = None 112 113 if self.transform and not hasattr(self.transform, "__call__"): 114 raise TypeError, "transform must be callable" 115 116 if self.transform is None and self.default is not None: 117 self.transform = type(self.default)
118
119 120 -class AttributeCollectorMeta(type):
121 """Metaclass for attribute collector classes 122 123 Classes that use this metaclass are intended to collect vertex/edge 124 attributes from various sources (a Python dict, a vertex/edge sequence, 125 default values from the igraph configuration and such) in a given 126 order of precedence. See the module documentation for more details. 127 This metaclass enables the user to use a simple declarative syntax 128 to specify which attributes he is interested in. For each vertex/edge 129 attribute, a corresponding class attribute must be defined with a 130 value that describes the default value of that attribute if no other 131 data source provides us with any suitable value. The default value 132 can also be a tuple; in that case, the first element of the tuple 133 is the actual default value, the second element is a converter 134 function that will convert the attribute values to a format expected 135 by the caller who uses the class being defined. 136 137 There is a special class attribute called C{_kwds_prefix}; this is 138 not used as an attribute declaration. It can contain a string which 139 will be used to derive alternative names for the attributes when 140 the attribute is accessed in a Python dict. This is useful in many 141 situations; for instance, the default graph drawer would want to access 142 the vertex colors using the C{color} vertex attribute, but when 143 it looks at the keyword arguments passed to the original call of 144 L{igraph.Graph.__plot__}, the C{vertex_color} keyword argument should 145 be looked up because we also have colors for the edges. C{_kwds_prefix} 146 will be prepended to the attribute names when they are looked up in 147 a dict of keyword arguments. 148 149 If you require a more fine-tuned behaviour, you can assign an 150 L{AttributeSpecification} instance to a class attribute directly. 151 152 @see: AttributeCollectorBase 153 """ 154
155 - def __new__(mcs, name, bases, attrs):
156 attr_specs = [] 157 for attr, value in attrs.iteritems(): 158 if attr.startswith("_") or hasattr(value, "__call__"): 159 continue 160 if isinstance(value, AttributeSpecification): 161 attr_spec = value 162 elif isinstance(value, dict): 163 attr_spec = AttributeSpecification(attr, **value) 164 else: 165 attr_spec = AttributeSpecification(attr, value) 166 attr_specs.append(attr_spec) 167 168 prefix = attrs.get("_kwds_prefix", None) 169 if prefix: 170 for attr_spec in attr_specs: 171 if attr_spec.name == attr_spec.alt_name: 172 attr_spec.alt_name = "%s%s" % (prefix, attr_spec.name) 173 174 attrs["_attributes"] = attr_specs 175 attrs["Element"] = mcs.record_generator( 176 "%s.Element" % name, 177 (attr_spec.name for attr_spec in attr_specs) 178 ) 179 180 return super(AttributeCollectorMeta, mcs).__new__(mcs, \ 181 name, bases, attrs) 182 183 @classmethod
184 - def record_generator(mcs, name, slots):
185 """Generates a simple class that has the given slots and nothing else""" 186 class Element(object): 187 """A simple class that holds the attributes collected by the 188 attribute collector""" 189 __slots__ = tuple(slots) 190 def __init__(self, attrs=()): 191 for attr, value in attrs: 192 setattr(self, attr, value)
193 Element.__name__ = name 194 return Element
195
196 197 -class AttributeCollectorBase(object):
198 """Base class for attribute collector subclasses. Classes that inherit 199 this class may use a declarative syntax to specify which vertex or edge 200 attributes they intend to collect. See L{AttributeCollectorMeta} for 201 the details. 202 """ 203 204 __metaclass__ = AttributeCollectorMeta 205
206 - def __init__(self, seq, kwds = None):
207 """Constructs a new attribute collector that uses the given 208 vertex/edge sequence and the given dict as data sources. 209 210 @param seq: an L{igraph.VertexSeq} or L{igraph.EdgeSeq} class 211 that will be used as a data source for attributes. 212 @param kwds: a Python dict that will be used to override the 213 attributes collected from I{seq} if necessary. 214 """ 215 elt = self.__class__.Element 216 self._cache = [elt() for _ in xrange(len(seq))] 217 218 self.seq = seq 219 self.kwds = kwds or {} 220 221 for attr_spec in self._attributes: 222 values = self._collect_attributes(attr_spec) 223 attr_name = attr_spec.name 224 for cache_elt, val in izip(self._cache, values): 225 setattr(cache_elt, attr_name, val) 226
227 - def _collect_attributes(self, attr_spec, config=None):
228 """Collects graph visualization attributes from various sources. 229 230 This method can be used to collect the attributes required for graph 231 visualization from various sources. Attribute value sources are: 232 233 - A specific value of a Python dict belonging to a given key. This dict 234 is given by the argument M{self.kwds} at construction time, and 235 the name of the key is determined by the argument specification 236 given in M{attr_spec}. 237 238 - A vertex or edge sequence of a graph, given in M{self.seq} 239 240 - The global configuration, given in M{config} 241 242 - A default value when all other sources fail to provide the value. 243 This is also given in M{attr_spec}. 244 245 @param attr_spec: an L{AttributeSpecification} object which contains 246 the name of the attribute when it is coming from a 247 list of Python keyword arguments, the name of the 248 attribute when it is coming from the graph attributes 249 directly, the default value of the attribute and an 250 optional callable transformation to call on the values. 251 This can be used to ensure that the attributes are of 252 a given type. 253 @param config: a L{Configuration} object to be used for determining the 254 defaults if all else fails. If C{None}, the global 255 igraph configuration will be used 256 @return: the collected attributes 257 """ 258 kwds = self.kwds 259 seq = self.seq 260 261 n = len(seq) 262 263 # Special case if the attribute name is "label" 264 if attr_spec.name == "label": 265 if attr_spec.alt_name in kwds and kwds[attr_spec.alt_name] is None: 266 return [None] * n 267 268 # If the attribute uses an external callable to derive the attribute 269 # values, call it and store the results 270 if attr_spec.func is not None: 271 func = attr_spec.func 272 result = [func(i) for i in xrange(n)] 273 return result 274 275 # Get the configuration object 276 if config is None: 277 config = Configuration.instance() 278 279 # Fetch the defaults from the vertex/edge sequence 280 try: 281 attrs = seq[attr_spec.name] 282 except KeyError: 283 attrs = None 284 285 # Override them from the keyword arguments (if any) 286 result = kwds.get(attr_spec.alt_name, None) 287 if attrs: 288 if not result: 289 result = attrs 290 else: 291 if isinstance(result, str): 292 result = [result] * n 293 try: 294 len(result) 295 except TypeError: 296 result = [result] * n 297 result = [result[idx] or attrs[idx] \ 298 for idx in xrange(len(result))] 299 300 # Special case for string overrides, strings are not treated 301 # as sequences here 302 if isinstance(result, str): 303 result = [result] * n 304 305 # If the result is still not a sequence, make it one 306 try: 307 len(result) 308 except TypeError: 309 result = [result] * n 310 311 # If it is not a list, ensure that it is a list 312 if not hasattr(result, "extend"): 313 result = list(result) 314 315 # Ensure that the length is n 316 while len(result) < n: 317 if len(result) <= n/2: 318 result.extend(result) 319 else: 320 result.extend(result[0:(n-len(result))]) 321 322 # By now, the length of the result vector should be n as requested 323 # Get the configuration defaults 324 try: 325 default = config["plotting.%s" % attr_spec.alt_name] 326 except NoOptionError: 327 default = None 328 329 if default is None: 330 default = attr_spec.default 331 332 # Fill the None values with the default values 333 for idx in xrange(len(result)): 334 if result[idx] is None: 335 result[idx] = default 336 337 # Finally, do the transformation 338 if attr_spec.transform is not None: 339 transform = attr_spec.transform 340 result = [transform(x) for x in result] 341 342 return result 343 344
345 - def __getitem__(self, index):
346 """Returns the collected attributes of the vertex/edge with the 347 given index.""" 348 # pylint: disable-msg=E1101 349 # E1101: instance has no '_attributes' member 350 return self._cache[index] 351
352 - def __len__(self):
353 return len(self.seq)
354
   Home       Trees       Indices       Help