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