Exemple #1
0
    def show(self):
        """Saves the plot to a temporary file and shows it."""
        if not isinstance(self._surface, cairo.ImageSurface):
            sur = cairo.ImageSurface(cairo.FORMAT_ARGB32, int(self.bbox.width),
                                     int(self.bbox.height))
            ctx = cairo.Context(sur)
            self.redraw(ctx)
        else:
            sur = self._surface
            ctx = self._ctx
            if self._is_dirty:
                self.redraw(ctx)

        with named_temporary_file(prefix="igraph", suffix=".png") as tmpfile:
            sur.write_to_png(tmpfile)
            config = Configuration.instance()
            imgviewer = config["apps.image_viewer"]
            if not imgviewer:
                # No image viewer was given and none was detected. This
                # should only happen on unknown platforms.
                plat = platform.system()
                raise NotImplementedError("showing plots is not implemented " + \
                                          "on this platform: %s" % plat)
            else:
                os.system("%s %s" % (imgviewer, tmpfile))
                if platform.system() == "Darwin" or self._windows_hacks:
                    # On Mac OS X and Windows, launched applications are likely to
                    # fork and give control back to Python immediately.
                    # Chances are that the temporary image file gets removed
                    # before the image viewer has a chance to open it, so
                    # we wait here a little bit. Yes, this is quite hackish :(
                    time.sleep(5)
Exemple #2
0
    def show(self):
        """Saves the plot to a temporary file and shows it."""
        if not isinstance(self._surface, cairo.ImageSurface):
            sur = cairo.ImageSurface(cairo.FORMAT_ARGB32,
                    int(self.bbox.width), int(self.bbox.height))
            ctx = cairo.Context(sur)
            self.redraw(ctx)
        else:
            sur = self._surface
            ctx = self._ctx
            if self._is_dirty:
                self.redraw(ctx)

        with named_temporary_file(prefix="igraph", suffix=".png") as tmpfile:
            sur.write_to_png(tmpfile)
            config = Configuration.instance()
            imgviewer = config["apps.image_viewer"]
            if not imgviewer:
                # No image viewer was given and none was detected. This
                # should only happen on unknown platforms.
                plat = platform.system()
                raise NotImplementedError("showing plots is not implemented " + \
                                          "on this platform: %s" % plat)
            else:
                os.system("%s %s" % (imgviewer, tmpfile))
                if platform.system() == "Darwin" or self._windows_hacks:
                    # On Mac OS X and Windows, launched applications are likely to
                    # fork and give control back to Python immediately.
                    # Chances are that the temporary image file gets removed
                    # before the image viewer has a chance to open it, so
                    # we wait here a little bit. Yes, this is quite hackish :(
                    time.sleep(5)
def main():
    """The main entry point for igraph when invoked from the command
    line shell"""
    config = Configuration.instance()

    if config.filename:
        print("Using configuration from %s" % config.filename, file=sys.stderr)
    else:
        print("No configuration file, using defaults", file=sys.stderr)

    if "shells" in config:
        parts = [part.strip() for part in config["shells"].split(",")]
        shell_classes = []
        available_classes = dict(
            [
                (k, v)
                for k, v in globals().items()
                if isinstance(v, type) and issubclass(v, Shell)
            ]
        )
        for part in parts:
            cls = available_classes.get(part, None)
            if cls is None:
                print("Warning: unknown shell class `%s'" % part, file=sys.stderr)
                continue
            shell_classes.append(cls)
    else:
        shell_classes = [IPythonShell, ClassicPythonShell]
        import platform

        if platform.system() == "Windows":
            shell_classes.insert(0, IDLEShell)

    shell = None
    for shell_class in shell_classes:
        # pylint: disable-msg=W0703
        # W0703: catch "Exception"
        try:
            shell = shell_class()
            break
        except Exception:
            # Try the next one
            if "Classic" in str(shell_class):
                raise
            pass

    if isinstance(shell, Shell):
        if config["verbose"]:
            if shell.supports_progress_bar():
                set_progress_handler(shell.get_progress_handler())
            if shell.supports_status_messages():
                set_status_handler(shell.get_status_handler())
        shell()
    else:
        print("No suitable Python shell was found.", file=sys.stderr)
        print("Check configuration variable `general.shells'.", file=sys.stderr)
Exemple #4
0
def main():
    """The main entry point for igraph when invoked from the command
    line shell"""
    config = Configuration.instance()

    if config.filename:
        print >> sys.stderr, "Using configuration from %s" % config.filename
    else:
        print >> sys.stderr, "No configuration file, using defaults"

    if config.has_key("shells"):
        parts = [part.strip() for part in config["shells"].split(",")]
        shell_classes = []
        available_classes = dict([(k, v) for k, v in globals().iteritems()
                                  if isinstance(v, type) and issubclass(v, Shell)])
        for part in parts:
            klass = available_classes.get(part, None)
            if klass is None:
                print >> sys.stderr, "Warning: unknown shell class `%s'" % part
                continue
            shell_classes.append(klass)
    else:
        shell_classes = [IPythonShell, ClassicPythonShell]
        import platform
        if platform.system() == "Windows":
            shell_classes.insert(0, IDLEShell)

    shell = None
    for shell_class in shell_classes:
        # pylint: disable-msg=W0703
        # W0703: catch "Exception"
        try:
            shell = shell_class()
            break
        except StandardError:
            # Try the next one
            if "Classic" in str(shell_class):
                raise
            pass

    if isinstance(shell, Shell):
        if config["verbose"]:
            if shell.supports_progress_bar():
                set_progress_handler(shell.get_progress_handler())
            if shell.supports_status_messages():
                set_status_handler(shell.get_status_handler())
        shell()
    else:
        print >> sys.stderr, "No suitable Python shell was found."
        print >> sys.stderr, "Check configuration variable `general.shells'."
 def _get_response(self, path, params={}, compressed=False):
     """Sends a request to Nexus at the given path with the given parameters
     and returns a file-like object for the response. `compressed` denotes
     whether we accept compressed responses."""
     if self.url is None:
         url = Configuration.instance()["remote.nexus.url"]
     else:
         url = self.url
     url = "%s%s?%s" % (url, path, urlencode(params))
     request = urllib2.Request(url)
     if compressed:
         request.add_header("Accept-Encoding", "gzip")
     if self.debug:
         print "[debug] Sending request: %s" % url
     return self._opener.open(request)
 def _get_response(self, path, params={}, compressed=False):
     """Sends a request to Nexus at the given path with the given parameters
     and returns a file-like object for the response. `compressed` denotes
     whether we accept compressed responses."""
     if self.url is None:
         url = Configuration.instance()["remote.nexus.url"]
     else:
         url = self.url
     url = "%s%s?%s" % (url, path, urlencode(params))
     request = urllib.request.Request(url)
     if compressed:
         request.add_header("Accept-Encoding", "gzip")
     if self.debug:
         print("[debug] Sending request: %s" % url)
     return self._opener.open(request)
Exemple #7
0
    def __init__(self, target=None, bbox=None, palette=None, background=None):
        """Creates a new plot.

        @param target: the target surface to write to. It can be one of the
          following types:

            - C{None} -- an appropriate surface will be created and the object
              will be plotted there.

            - C{cairo.Surface} -- the given Cairo surface will be used.

            - C{string} -- a file with the given name will be created and an
              appropriate Cairo surface will be attached to it.

        @param bbox: the bounding box of the surface. It is interpreted
          differently with different surfaces: PDF and PS surfaces will
          treat it as points (1 point = 1/72 inch). Image surfaces will
          treat it as pixels. SVG surfaces will treat it as an abstract
          unit, but it will mostly be interpreted as pixels when viewing
          the SVG file in Firefox.

        @param palette: the palette primarily used on the plot if the
          added objects do not specify a private palette. Must be either
          an L{igraph.drawing.colors.Palette} object or a string referring
          to a valid key of C{igraph.drawing.colors.palettes} (see module
          L{igraph.drawing.colors}) or C{None}. In the latter case, the default
          palette given by the configuration key C{plotting.palette} is used.

        @param background: the background color. If C{None}, the background
          will be transparent. You can use any color specification here that
          is understood by L{igraph.drawing.colors.color_name_to_rgba}.
        """
        self._filename = None
        self._surface_was_created = not isinstance(target, cairo.Surface)
        self._need_tmpfile = False

        # Several Windows-specific hacks will be used from now on, thanks
        # to Dale Hunscher for debugging and fixing all that stuff
        self._windows_hacks = "Windows" in platform.platform()

        if bbox is None:
            self.bbox = BoundingBox(600, 600)
        elif isinstance(bbox, tuple) or isinstance(bbox, list):
            self.bbox = BoundingBox(bbox)
        else:
            self.bbox = bbox

        if palette is None:
            config = Configuration.instance()
            palette = config["plotting.palette"]
        if not isinstance(palette, Palette):
            palette = palettes[palette]
        self._palette = palette

        if target is None:
            self._need_tmpfile = True
            self._surface = cairo.ImageSurface(
                cairo.FORMAT_ARGB32, int(self.bbox.width), int(self.bbox.height)
            )
        elif isinstance(target, cairo.Surface):
            self._surface = target
        else:
            self._filename = target
            _, ext = os.path.splitext(target)
            ext = ext.lower()
            if ext == ".pdf":
                self._surface = cairo.PDFSurface(
                    target, self.bbox.width, self.bbox.height
                )
            elif ext == ".ps" or ext == ".eps":
                self._surface = cairo.PSSurface(
                    target, self.bbox.width, self.bbox.height
                )
            elif ext == ".png":
                self._surface = cairo.ImageSurface(
                    cairo.FORMAT_ARGB32, int(self.bbox.width), int(self.bbox.height)
                )
            elif ext == ".svg":
                self._surface = cairo.SVGSurface(
                    target, self.bbox.width, self.bbox.height
                )
            else:
                raise ValueError("image format not handled by Cairo: %s" % ext)

        self._ctx = cairo.Context(self._surface)
        self._objects = []
        self._is_dirty = False

        self.background = background
Exemple #8
0
def plot(obj, target=None, bbox=(0, 0, 600, 600), *args, **kwds):
    """Plots the given object to the given target.

    Positional and keyword arguments not explicitly mentioned here will be
    passed down to the C{__plot__} method of the object being plotted.
    Since you are most likely interested in the keyword arguments available
    for graph plots, see L{Graph.__plot__} as well.

    @param obj: the object to be plotted
    @param target: the target where the object should be plotted. It can be one
      of the following types:

        - C{matplotib.axes.Axes} -- a matplotlib/pyplot axes in which the
          graph will be plotted. Drawing is delegated to the chosen matplotlib
          backend, and you can use interactive backends and matplotlib
          functions to save to file as well.

        - C{string} -- a file with the given name will be created and an
          appropriate Cairo surface will be attached to it. The supported image
          formats are: PNG, PDF, SVG and PostScript.

        - C{cairo.Surface} -- the given Cairo surface will be used. This can
          refer to a PNG image, an arbitrary window, an SVG file, anything that
          Cairo can handle.

        - C{None} -- a temporary file will be created and the object will be
          plotted there. igraph will attempt to open an image viewer and show
          the temporary file. This feature is deprecated from python-igraph
          version 0.9.1 and will be removed in 0.10.0.

    @param bbox: the bounding box of the plot. It must be a tuple with either
      two or four integers, or a L{BoundingBox} object. If this is a tuple
      with two integers, it is interpreted as the width and height of the plot
      (in pixels for PNG images and on-screen plots, or in points for PDF,
      SVG and PostScript plots, where 72 pt = 1 inch = 2.54 cm). If this is
      a tuple with four integers, the first two denotes the X and Y coordinates
      of a corner and the latter two denoting the X and Y coordinates of the
      opposite corner.

    @keyword opacity: the opacity of the object being plotted. It can be
      used to overlap several plots of the same graph if you use the same
      layout for them -- for instance, you might plot a graph with opacity
      0.5 and then plot its spanning tree over it with opacity 0.1. To
      achieve this, you'll need to modify the L{Plot} object returned with
      L{Plot.add}.

    @keyword palette: the palette primarily used on the plot if the
      added objects do not specify a private palette. Must be either
      an L{igraph.drawing.colors.Palette} object or a string referring
      to a valid key of C{igraph.drawing.colors.palettes} (see module
      L{igraph.drawing.colors}) or C{None}. In the latter case, the default
      palette given by the configuration key C{plotting.palette} is used.

    @keyword margin: the top, right, bottom, left margins as a 4-tuple.
      If it has less than 4 elements or is a single float, the elements
      will be re-used until the length is at least 4. The default margin
      is 20 on each side.

    @keyword inline: whether to try and show the plot object inline in the
      current IPython notebook. Passing C{None} here or omitting this keyword
      argument will look up the preferred behaviour from the
      C{shell.ipython.inlining.Plot} configuration key.  Note that this keyword
      argument has an effect only if igraph is run inside IPython and C{target}
      is C{None}.

    @return: an appropriate L{Plot} object.

    @see: Graph.__plot__
    """
    _, plt = find_matplotlib()

    if hasattr(plt, "Axes") and isinstance(target, plt.Axes):
        result = MatplotlibGraphDrawer(ax=target)
        result.draw(obj, *args, **kwds)
        return

    if not isinstance(bbox, BoundingBox):
        bbox = BoundingBox(bbox)

    result = Plot(target, bbox, background=kwds.get("background", "white"))

    if "margin" in kwds:
        bbox = bbox.contract(kwds["margin"])
        del kwds["margin"]
    else:
        bbox = bbox.contract(20)
    result.add(obj, bbox, *args, **kwds)

    if target is None and _is_running_in_ipython():
        # Get the default value of the `inline` argument from the configuration if
        # needed
        inline = kwds.get("inline")
        if inline is None:
            config = Configuration.instance()
            inline = config["shell.ipython.inlining.Plot"]

        # If we requested an inline plot, just return the result and IPython will
        # call its _repr_svg_ method. If we requested a non-inline plot, show the
        # plot in a separate window and return nothing
        if inline:
            return result
        else:
            result.show()
            return

    # We are either not in IPython or the user specified an explicit plot target,
    # so just show or save the result
    if target is None:
        result.show()
    elif isinstance(target, str):
        result.save()

    # Also return the plot itself
    return result
Exemple #9
0
    def draw(self, graph, palette, *args, **kwds):
        # Some abbreviations for sake of simplicity
        directed = graph.is_directed()
        context = self.context

        # Calculate/get the layout of the graph
        layout = self.ensure_layout(kwds.get("layout", None), graph)

        # Determine the size of the margin on each side
        margin = kwds.get("margin", 0)
        try:
            margin = list(margin)
        except TypeError:
            margin = [margin]
        while len(margin) < 4:
            margin.extend(margin)

        # Contract the drawing area by the margin and fit the layout
        bbox = self.bbox.contract(margin)
        layout.fit_into(bbox, keep_aspect_ratio=kwds.get("keep_aspect_ratio", False))

        # Decide whether we need to calculate the curvature of edges
        # automatically -- and calculate them if needed.
        autocurve = kwds.get("autocurve", None)
        if autocurve or (
            autocurve is None
            and "edge_curved" not in kwds
            and "curved" not in graph.edge_attributes()
            and graph.ecount() < 10000
        ):
            from igraph import autocurve

            default = kwds.get("edge_curved", 0)
            if default is True:
                default = 0.5
            default = float(default)
            kwds["edge_curved"] = autocurve(graph, attribute=None, default=default)

        # Construct the vertex, edge and label drawers
        vertex_drawer = self.vertex_drawer_factory(context, bbox, palette, layout)
        edge_drawer = self.edge_drawer_factory(context, palette)
        label_drawer = self.label_drawer_factory(context)

        # Construct the visual vertex/edge builders based on the specifications
        # provided by the vertex_drawer and the edge_drawer
        vertex_builder = vertex_drawer.VisualVertexBuilder(graph.vs, kwds)
        edge_builder = edge_drawer.VisualEdgeBuilder(graph.es, kwds)

        # Determine the order in which we will draw the vertices and edges
        vertex_order = self._determine_vertex_order(graph, kwds)
        edge_order = self._determine_edge_order(graph, kwds)

        # Draw the highlighted groups (if any)
        if "mark_groups" in kwds:
            mark_groups = kwds["mark_groups"]

            # Deferred import to avoid a cycle in the import graph
            from igraph.clustering import VertexClustering, VertexCover

            # Figure out what to do with mark_groups in order to be able to
            # iterate over it and get memberlist-color pairs
            if isinstance(mark_groups, dict):
                # Dictionary mapping vertex indices or tuples of vertex
                # indices to colors
                group_iter = iter(mark_groups.items())
            elif isinstance(mark_groups, (VertexClustering, VertexCover)):
                # Vertex clustering
                group_iter = ((group, color) for color, group in enumerate(mark_groups))
            elif hasattr(mark_groups, "__iter__"):
                # Lists, tuples, iterators etc
                group_iter = iter(mark_groups)
            else:
                # False
                group_iter = iter({}.items())

            # We will need a polygon drawer to draw the convex hulls
            polygon_drawer = PolygonDrawer(context, bbox)

            # Iterate over color-memberlist pairs
            for group, color_id in group_iter:
                if not group or color_id is None:
                    continue

                color = palette.get(color_id)

                if isinstance(group, VertexSeq):
                    group = [vertex.index for vertex in group]
                if not hasattr(group, "__iter__"):
                    raise TypeError("group membership list must be iterable")

                # Get the vertex indices that constitute the convex hull
                hull = [group[i] for i in convex_hull([layout[idx] for idx in group])]

                # Calculate the preferred rounding radius for the corners
                corner_radius = 1.25 * max(vertex_builder[idx].size for idx in hull)

                # Construct the polygon
                polygon = [layout[idx] for idx in hull]

                if len(polygon) == 2:
                    # Expand the polygon (which is a flat line otherwise)
                    a, b = Point(*polygon[0]), Point(*polygon[1])
                    c = corner_radius * (a - b).normalized()
                    n = Point(-c[1], c[0])
                    polygon = [a + n, b + n, b - c, b - n, a - n, a + c]
                else:
                    # Expand the polygon around its center of mass
                    center = Point(
                        *[sum(coords) / float(len(coords)) for coords in zip(*polygon)]
                    )
                    polygon = [
                        Point(*point).towards(center, -corner_radius)
                        for point in polygon
                    ]

                # Draw the hull
                context.set_source_rgba(color[0], color[1], color[2], color[3] * 0.25)
                polygon_drawer.draw_path(polygon, corner_radius=corner_radius)
                context.fill_preserve()
                context.set_source_rgba(*color)
                context.stroke()

        # Construct the iterator that we will use to draw the edges
        es = graph.es
        if edge_order is None:
            # Default edge order
            edge_coord_iter = zip(es, edge_builder)
        else:
            # Specified edge order
            edge_coord_iter = ((es[i], edge_builder[i]) for i in edge_order)

        # Draw the edges
        if directed:
            drawer_method = edge_drawer.draw_directed_edge
        else:
            drawer_method = edge_drawer.draw_undirected_edge
        for edge, visual_edge in edge_coord_iter:
            src, dest = edge.tuple
            src_vertex, dest_vertex = vertex_builder[src], vertex_builder[dest]
            drawer_method(visual_edge, src_vertex, dest_vertex)

        # Construct the iterator that we will use to draw the vertices
        vs = graph.vs
        if vertex_order is None:
            # Default vertex order
            vertex_coord_iter = zip(vs, vertex_builder, layout)
        else:
            # Specified vertex order
            vertex_coord_iter = (
                (vs[i], vertex_builder[i], layout[i]) for i in vertex_order
            )

        # Draw the vertices
        drawer_method = vertex_drawer.draw
        context.set_line_width(1)
        for vertex, visual_vertex, coords in vertex_coord_iter:
            drawer_method(visual_vertex, vertex, coords)

        # Decide whether the labels have to be wrapped
        wrap = kwds.get("wrap_labels")
        if wrap is None:
            wrap = Configuration.instance()["plotting.wrap_labels"]
        wrap = bool(wrap)

        # Construct the iterator that we will use to draw the vertex labels
        if vertex_order is None:
            # Default vertex order
            vertex_coord_iter = zip(vertex_builder, layout)
        else:
            # Specified vertex order
            vertex_coord_iter = ((vertex_builder[i], layout[i]) for i in vertex_order)

        # Draw the vertex labels
        for vertex, coords in vertex_coord_iter:
            if vertex.label is None:
                continue

            # Set the font family, size, color and text
            context.select_font_face(
                vertex.font, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL
            )
            context.set_font_size(vertex.label_size)
            context.set_source_rgba(*vertex.label_color)
            label_drawer.text = vertex.label

            if vertex.label_dist:
                # Label is displaced from the center of the vertex.
                _, yb, w, h, _, _ = label_drawer.text_extents()
                w, h = w / 2.0, h / 2.0
                radius = vertex.label_dist * vertex.size / 2.0
                # First we find the reference point that is at distance `radius'
                # from the vertex in the direction given by `label_angle'.
                # Then we place the label in a way that the line connecting the
                # center of the bounding box of the label with the center of the
                # vertex goes through the reference point and the reference
                # point lies exactly on the bounding box of the vertex.
                alpha = vertex.label_angle % (2 * pi)
                cx = coords[0] + radius * cos(alpha)
                cy = coords[1] - radius * sin(alpha)
                # Now we have the reference point. We have to decide which side
                # of the label box will intersect with the line that connects
                # the center of the label with the center of the vertex.
                if w > 0:
                    beta = atan2(h, w) % (2 * pi)
                else:
                    beta = pi / 2.0
                gamma = pi - beta
                if alpha > 2 * pi - beta or alpha <= beta:
                    # Intersection at left edge of label
                    cx += w
                    cy -= tan(alpha) * w
                elif alpha > beta and alpha <= gamma:
                    # Intersection at bottom edge of label
                    try:
                        cx += h / tan(alpha)
                    except:
                        pass  # tan(alpha) == inf
                    cy -= h
                elif alpha > gamma and alpha <= gamma + 2 * beta:
                    # Intersection at right edge of label
                    cx -= w
                    cy += tan(alpha) * w
                else:
                    # Intersection at top edge of label
                    try:
                        cx -= h / tan(alpha)
                    except:
                        pass  # tan(alpha) == inf
                    cy += h
                # Draw the label
                label_drawer.draw_at(cx - w, cy - h - yb, wrap=wrap)
            else:
                # Label is exactly in the center of the vertex
                cx, cy = coords
                half_size = vertex.size / 2.0
                label_drawer.bbox = (
                    cx - half_size,
                    cy - half_size,
                    cx + half_size,
                    cy + half_size,
                )
                label_drawer.draw(wrap=wrap)

        # Construct the iterator that we will use to draw the edge labels
        es = graph.es
        if edge_order is None:
            # Default edge order
            edge_coord_iter = zip(es, edge_builder)
        else:
            # Specified edge order
            edge_coord_iter = ((es[i], edge_builder[i]) for i in edge_order)

        # Draw the edge labels
        for edge, visual_edge in edge_coord_iter:
            if visual_edge.label is None:
                continue

            # Set the font family, size, color and text
            context.select_font_face(
                visual_edge.font, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL
            )
            context.set_font_size(visual_edge.label_size)
            context.set_source_rgba(*visual_edge.label_color)
            label_drawer.text = visual_edge.label

            # Ask the edge drawer to propose an anchor point for the label
            src, dest = edge.tuple
            src_vertex, dest_vertex = vertex_builder[src], vertex_builder[dest]
            (x, y), (halign, valign) = edge_drawer.get_label_position(
                edge, src_vertex, dest_vertex
            )

            # Measure the text
            _, yb, w, h, _, _ = label_drawer.text_extents()
            w /= 2.0
            h /= 2.0

            # Place the text relative to the edge
            if halign == TextAlignment.RIGHT:
                x -= w
            elif halign == TextAlignment.LEFT:
                x += w
            if valign == TextAlignment.BOTTOM:
                y -= h - yb / 2.0
            elif valign == TextAlignment.TOP:
                y += h

            # Draw the edge label
            label_drawer.halign = halign
            label_drawer.valign = valign
            label_drawer.bbox = (x - w, y - h, x + w, y + h)
            label_drawer.draw(wrap=wrap)
Exemple #10
0
    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
Exemple #11
0
    def __init__(self, target=None, bbox=None, palette=None, background=None):
        """Creates a new plot.

        @param target: the target surface to write to. It can be one of the
          following types:

            - C{None} -- an appropriate surface will be created and the object
              will be plotted there.

            - C{cairo.Surface} -- the given Cairo surface will be used.

            - C{string} -- a file with the given name will be created and an
              appropriate Cairo surface will be attached to it.

        @param bbox: the bounding box of the surface. It is interpreted
          differently with different surfaces: PDF and PS surfaces will
          treat it as points (1 point = 1/72 inch). Image surfaces will
          treat it as pixels. SVG surfaces will treat it as an abstract
          unit, but it will mostly be interpreted as pixels when viewing
          the SVG file in Firefox.

        @param palette: the palette primarily used on the plot if the
          added objects do not specify a private palette. Must be either
          an L{igraph.drawing.colors.Palette} object or a string referring
          to a valid key of C{igraph.drawing.colors.palettes} (see module
          L{igraph.drawing.colors}) or C{None}. In the latter case, the default
          palette given by the configuration key C{plotting.palette} is used.

        @param background: the background color. If C{None}, the background
          will be transparent. You can use any color specification here that
          is understood by L{igraph.drawing.colors.color_name_to_rgba}.
        """
        self._filename = None
        self._surface_was_created = not isinstance(target, cairo.Surface)
        self._need_tmpfile = False

        # Several Windows-specific hacks will be used from now on, thanks
        # to Dale Hunscher for debugging and fixing all that stuff
        self._windows_hacks = "Windows" in platform.platform()

        if bbox is None:
            self.bbox = BoundingBox(600, 600)
        elif isinstance(bbox, tuple) or isinstance(bbox, list):
            self.bbox = BoundingBox(bbox)
        else:
            self.bbox = bbox

        if palette is None:
            config = Configuration.instance()
            palette = config["plotting.palette"]
        if not isinstance(palette, Palette):
            palette = palettes[palette]
        self._palette = palette

        if target is None:
            self._need_tmpfile = True
            self._surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, \
                int(self.bbox.width), int(self.bbox.height))
        elif isinstance(target, cairo.Surface):
            self._surface = target
        else:
            self._filename = target
            _, ext = os.path.splitext(target)
            ext = ext.lower()
            if ext == ".pdf":
                self._surface = cairo.PDFSurface(target, self.bbox.width, \
                                                 self.bbox.height)
            elif ext == ".ps" or ext == ".eps":
                self._surface = cairo.PSSurface(target, self.bbox.width, \
                                                self.bbox.height)
            elif ext == ".png":
                self._surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, \
                    int(self.bbox.width), int(self.bbox.height))
            elif ext == ".svg":
                self._surface = cairo.SVGSurface(target, self.bbox.width, \
                                                 self.bbox.height)
            else:
                raise ValueError("image format not handled by Cairo: %s" % ext)

        self._ctx = cairo.Context(self._surface)
        self._objects = []
        self._is_dirty = False

        self.background = background
Exemple #12
0
def plot(obj, target=None, bbox=(0, 0, 600, 600), *args, **kwds):
    """Plots the given object to the given target.

    Positional and keyword arguments not explicitly mentioned here will be
    passed down to the C{__plot__} method of the object being plotted.
    Since you are most likely interested in the keyword arguments available
    for graph plots, see L{Graph.__plot__} as well.

    @param obj: the object to be plotted
    @param target: the target where the object should be plotted. It can be one
      of the following types:
      
        - C{None} -- an appropriate surface will be created and the object will
          be plotted there.

        - C{cairo.Surface} -- the given Cairo surface will be used. This can
          refer to a PNG image, an arbitrary window, an SVG file, anything that
          Cairo can handle.

        - C{string} -- a file with the given name will be created and an
          appropriate Cairo surface will be attached to it. The supported image
          formats are: PNG, PDF, SVG and PostScript.
          
    @param bbox: the bounding box of the plot. It must be a tuple with either
      two or four integers, or a L{BoundingBox} object. If this is a tuple
      with two integers, it is interpreted as the width and height of the plot
      (in pixels for PNG images and on-screen plots, or in points for PDF,
      SVG and PostScript plots, where 72 pt = 1 inch = 2.54 cm). If this is
      a tuple with four integers, the first two denotes the X and Y coordinates
      of a corner and the latter two denoting the X and Y coordinates of the
      opposite corner.

    @keyword opacity: the opacity of the object being plotted. It can be
      used to overlap several plots of the same graph if you use the same
      layout for them -- for instance, you might plot a graph with opacity
      0.5 and then plot its spanning tree over it with opacity 0.1. To
      achieve this, you'll need to modify the L{Plot} object returned with
      L{Plot.add}.

    @keyword palette: the palette primarily used on the plot if the
      added objects do not specify a private palette. Must be either
      an L{igraph.drawing.colors.Palette} object or a string referring
      to a valid key of C{igraph.drawing.colors.palettes} (see module
      L{igraph.drawing.colors}) or C{None}. In the latter case, the default
      palette given by the configuration key C{plotting.palette} is used.

    @keyword margin: the top, right, bottom, left margins as a 4-tuple.
      If it has less than 4 elements or is a single float, the elements
      will be re-used until the length is at least 4. The default margin
      is 20 on each side.

    @keyword inline: whether to try and show the plot object inline in the
      current IPython notebook. Passing ``None`` here or omitting this keyword
      argument will look up the preferred behaviour from the
      C{shell.ipython.inlining.Plot} configuration key.  Note that this keyword
      argument has an effect only if igraph is run inside IPython and C{target}
      is C{None}.

    @return: an appropriate L{Plot} object.

    @see: Graph.__plot__
    """
    if not isinstance(bbox, BoundingBox):
        bbox = BoundingBox(bbox)

    result = Plot(target, bbox, background="white")

    if "margin" in kwds:
        bbox = bbox.contract(kwds["margin"])
        del kwds["margin"]
    else:
        bbox = bbox.contract(20)
    result.add(obj, bbox, *args, **kwds)

    if IN_IPYTHON and target is None:
        # Get the default value of the `inline` argument from the configuration if
        # needed
        inline = kwds.get("inline")
        if inline is None:
            config = Configuration.instance()
            inline = config["shell.ipython.inlining.Plot"]

        # If we requested an inline plot, just return the result and IPython will
        # call its _repr_svg_ method. If we requested a non-inline plot, show the
        # plot in a separate window and return nothing
        if inline:
            return result
        else:
            result.show()
            return

    # We are either not in IPython or the user specified an explicit plot target,
    # so just show or save the result
    if target is None:
        result.show()
    elif isinstance(target, basestring):
        result.save()

    # Also return the plot itself
    return result
Exemple #13
0
    def draw(self, graph, palette, *args, **kwds):
        # Some abbreviations for sake of simplicity
        directed = graph.is_directed()
        context = self.context

        # Calculate/get the layout of the graph
        layout = self.ensure_layout(kwds.get("layout", None), graph)

        # Determine the size of the margin on each side
        margin = kwds.get("margin", 0)
        try:
            margin = list(margin)
        except TypeError:
            margin = [margin]
        while len(margin)<4:
            margin.extend(margin)

        # Contract the drawing area by the margin and fit the layout
        bbox = self.bbox.contract(margin)
        layout.fit_into(bbox, keep_aspect_ratio=kwds.get("keep_aspect_ratio", False))

        # Decide whether we need to calculate the curvature of edges
        # automatically -- and calculate them if needed.
        autocurve = kwds.get("autocurve", None)
        if autocurve or (autocurve is None and \
                "edge_curved" not in kwds and "curved" not in graph.edge_attributes() \
                and graph.ecount() < 10000):
            from igraph import autocurve
            default = kwds.get("edge_curved", 0)
            if default is True:
                default = 0.5
            default = float(default)
            kwds["edge_curved"] = autocurve(graph, attribute=None, default=default)

        # Construct the vertex, edge and label drawers
        vertex_drawer = self.vertex_drawer_factory(context, bbox, palette, layout)
        edge_drawer = self.edge_drawer_factory(context, palette)
        label_drawer = self.label_drawer_factory(context)

        # Construct the visual vertex/edge builders based on the specifications
        # provided by the vertex_drawer and the edge_drawer
        vertex_builder = vertex_drawer.VisualVertexBuilder(graph.vs, kwds)
        edge_builder = edge_drawer.VisualEdgeBuilder(graph.es, kwds)

        # Determine the order in which we will draw the vertices and edges
        vertex_order = self._determine_vertex_order(graph, kwds)
        edge_order   = self._determine_edge_order(graph, kwds)

        # Draw the highlighted groups (if any)
        if "mark_groups" in kwds:
            mark_groups = kwds["mark_groups"]

            # Deferred import to avoid a cycle in the import graph
            from igraph.clustering import VertexClustering, VertexCover

            # Figure out what to do with mark_groups in order to be able to
            # iterate over it and get memberlist-color pairs
            if isinstance(mark_groups, dict):
                # Dictionary mapping vertex indices or tuples of vertex
                # indices to colors
                group_iter = mark_groups.iteritems()
            elif isinstance(mark_groups, (VertexClustering, VertexCover)):
                # Vertex clustering
                group_iter = (
                    (group, color) for color, group in enumerate(mark_groups)
                )
            elif hasattr(mark_groups, "__iter__"):
                # Lists, tuples, iterators etc
                group_iter = iter(mark_groups)
            else:
                # False
                group_iter = {}.iteritems()

            # We will need a polygon drawer to draw the convex hulls
            polygon_drawer = PolygonDrawer(context, bbox)

            # Iterate over color-memberlist pairs
            for group, color_id in group_iter:
                if not group or color_id is None:
                    continue

                color = palette.get(color_id)

                if isinstance(group, VertexSeq):
                    group = [vertex.index for vertex in group]
                if not hasattr(group, "__iter__"):
                    raise TypeError("group membership list must be iterable")

                # Get the vertex indices that constitute the convex hull
                hull = [group[i] for i in convex_hull([layout[idx] for idx in group])]

                # Calculate the preferred rounding radius for the corners
                corner_radius = 1.25 * max(vertex_builder[idx].size for idx in hull)

                # Construct the polygon
                polygon = [layout[idx] for idx in hull]

                if len(polygon) == 2:
                    # Expand the polygon (which is a flat line otherwise)
                    a, b = Point(*polygon[0]), Point(*polygon[1])
                    c = corner_radius * (a-b).normalized()
                    n = Point(-c[1], c[0])
                    polygon = [a + n, b + n, b - c, b - n, a - n, a + c]
                else:
                    # Expand the polygon around its center of mass
                    center = Point(*[sum(coords) / float(len(coords))
                                      for coords in zip(*polygon)])
                    polygon = [Point(*point).towards(center, -corner_radius)
                               for point in polygon]

                # Draw the hull
                context.set_source_rgba(color[0], color[1], color[2],
                                        color[3]*0.25)
                polygon_drawer.draw_path(polygon, corner_radius=corner_radius)
                context.fill_preserve()
                context.set_source_rgba(*color)
                context.stroke()

        # Construct the iterator that we will use to draw the edges
        es = graph.es
        if edge_order is None:
            # Default edge order
            edge_coord_iter = izip(es, edge_builder)
        else:
            # Specified edge order
            edge_coord_iter = ((es[i], edge_builder[i]) for i in edge_order)

        # Draw the edges
        if directed:
            drawer_method = edge_drawer.draw_directed_edge
        else:
            drawer_method = edge_drawer.draw_undirected_edge
        for edge, visual_edge in edge_coord_iter:
            src, dest = edge.tuple
            src_vertex, dest_vertex = vertex_builder[src], vertex_builder[dest]
            drawer_method(visual_edge, src_vertex, dest_vertex)

        # Construct the iterator that we will use to draw the vertices
        vs = graph.vs
        if vertex_order is None:
            # Default vertex order
            vertex_coord_iter = izip(vs, vertex_builder, layout)
        else:
            # Specified vertex order
            vertex_coord_iter = ((vs[i], vertex_builder[i], layout[i])
                    for i in vertex_order)

        # Draw the vertices
        drawer_method = vertex_drawer.draw
        context.set_line_width(1)
        for vertex, visual_vertex, coords in vertex_coord_iter:
            drawer_method(visual_vertex, vertex, coords)

        # Decide whether the labels have to be wrapped
        wrap = kwds.get("wrap_labels")
        if wrap is None:
            wrap = Configuration.instance()["plotting.wrap_labels"]
        wrap = bool(wrap)

        # Construct the iterator that we will use to draw the vertex labels
        if vertex_order is None:
            # Default vertex order
            vertex_coord_iter = izip(vertex_builder, layout)
        else:
            # Specified vertex order
            vertex_coord_iter = ((vertex_builder[i], layout[i])
                    for i in vertex_order)

        # Draw the vertex labels
        for vertex, coords in vertex_coord_iter:
            if vertex.label is None:
                continue

            # Set the font family, size, color and text
            context.select_font_face(vertex.font, \
                cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
            context.set_font_size(vertex.label_size)
            context.set_source_rgba(*vertex.label_color)
            label_drawer.text = vertex.label

            if vertex.label_dist:
                # Label is displaced from the center of the vertex.
                _, yb, w, h, _, _ = label_drawer.text_extents()
                w, h = w/2.0, h/2.0
                radius = vertex.label_dist * vertex.size / 2.
                # First we find the reference point that is at distance `radius'
                # from the vertex in the direction given by `label_angle'.
                # Then we place the label in a way that the line connecting the
                # center of the bounding box of the label with the center of the
                # vertex goes through the reference point and the reference
                # point lies exactly on the bounding box of the vertex.
                alpha = vertex.label_angle % (2*pi)
                cx = coords[0] + radius * cos(alpha)
                cy = coords[1] - radius * sin(alpha)
                # Now we have the reference point. We have to decide which side
                # of the label box will intersect with the line that connects
                # the center of the label with the center of the vertex.
                if w > 0:
                    beta = atan2(h, w) % (2*pi)
                else:
                    beta = pi/2.
                gamma = pi - beta
                if alpha > 2*pi-beta or alpha <= beta:
                    # Intersection at left edge of label
                    cx += w
                    cy -= tan(alpha) * w
                elif alpha > beta and alpha <= gamma:
                    # Intersection at bottom edge of label
                    try:
                        cx += h / tan(alpha)
                    except:
                        pass    # tan(alpha) == inf
                    cy -= h
                elif alpha > gamma and alpha <= gamma + 2*beta:
                    # Intersection at right edge of label
                    cx -= w
                    cy += tan(alpha) * w
                else:
                    # Intersection at top edge of label
                    try:
                        cx -= h / tan(alpha)
                    except:
                        pass    # tan(alpha) == inf
                    cy += h
                # Draw the label
                label_drawer.draw_at(cx-w, cy-h-yb, wrap=wrap)
            else:
                # Label is exactly in the center of the vertex
                cx, cy = coords
                half_size = vertex.size / 2.
                label_drawer.bbox = (cx - half_size, cy - half_size,
                                     cx + half_size, cy + half_size)
                label_drawer.draw(wrap=wrap)

        # Construct the iterator that we will use to draw the edge labels
        es = graph.es
        if edge_order is None:
            # Default edge order
            edge_coord_iter = izip(es, edge_builder)
        else:
            # Specified edge order
            edge_coord_iter = ((es[i], edge_builder[i]) for i in edge_order)

        # Draw the edge labels
        for edge, visual_edge in edge_coord_iter:
            if visual_edge.label is None:
                continue

            # Set the font family, size, color and text
            context.select_font_face(visual_edge.font, \
                cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
            context.set_font_size(visual_edge.label_size)
            context.set_source_rgba(*visual_edge.label_color)
            label_drawer.text = visual_edge.label

            # Ask the edge drawer to propose an anchor point for the label
            src, dest = edge.tuple
            src_vertex, dest_vertex = vertex_builder[src], vertex_builder[dest]
            (x, y), (halign, valign) = \
                    edge_drawer.get_label_position(edge, src_vertex, dest_vertex)

            # Measure the text
            _, yb, w, h, _, _ = label_drawer.text_extents()
            w /= 2.0
            h /= 2.0

            # Place the text relative to the edge
            if halign == TextAlignment.RIGHT:
                x -= w
            elif halign == TextAlignment.LEFT:
                x += w
            if valign == TextAlignment.BOTTOM:
                y -= h - yb / 2.0
            elif valign == TextAlignment.TOP:
                y += h

            # Draw the edge label
            label_drawer.halign = halign
            label_drawer.valign = valign
            label_drawer.bbox = (x-w, y-h, x+w, y+h)
            label_drawer.draw(wrap=wrap)
Exemple #14
0
    def draw(self, graph, palette, *args, **kwds):
        # Some abbreviations for sake of simplicity
        directed = graph.is_directed()
        context = self.context

        # Calculate/get the layout of the graph
        layout = self.ensure_layout(kwds.get("layout", None), graph)

        # Determine the size of the margin on each side
        margin = kwds.get("margin", 0)
        try:
            margin = list(margin)
        except TypeError:
            margin = [margin]
        while len(margin)<4:
            margin.extend(margin)
        # margin = [x + 20. for x in margin[:4]]

        # Contract the drawing area by the margin and fit the layout
        bbox = self.bbox.contract(margin)
        layout.fit_into(bbox, keep_aspect_ratio=False)

        # Decide whether we need to calculate the curvature of edges
        # automatically -- and calculate them if needed.
        autocurve = kwds.get("autocurve", None)
        if autocurve or (autocurve is None and \
                "edge_curved" not in kwds and "curved" not in graph.edge_attributes() \
                and graph.ecount() < 10000):
            from igraph import autocurve
            default = kwds.get("edge_curved", 0)
            if default is True:
                default = 0.5
            default = float(default)
            kwds["edge_curved"] = autocurve(graph, attribute=None, default=default)

        # Construct the visual vertex/edge builders
        class VisualVertexBuilder(AttributeCollectorBase):
            """Collects some visual properties of a vertex for drawing"""
            _kwds_prefix = "vertex_"
            color = ("green", palette.get)
            label = None
            label_angle = -pi/2
            label_dist  = 0.0
            label_color = ("black", palette.get)
            label_size  = 14.0
            position = dict(func=layout.__getitem__)
            shape = ("circle", ShapeDrawerDirectory.resolve_default)
            size  = 20.0
            # RON EDIT ---------------------
            edge_color = ("#444", palette.get)
            edge_size = 2.0
            # ------------------------------

        class VisualEdgeBuilder(AttributeCollectorBase):
            """Collects some visual properties of an edge for drawing"""
            _kwds_prefix = "edge_"
            arrow_size  = 1.0
            arrow_width = 1.0
            color       = ("#444", palette.get)
            curved      = (0.0, ArrowEdgeDrawer._curvature_to_float)
            width       = 1.0

        vertex_builder = VisualVertexBuilder(graph.vs, kwds)
        edge_builder = VisualEdgeBuilder(graph.es, kwds)

        # Draw the highlighted groups (if any)
        if "mark_groups" in kwds:
            mark_groups = kwds["mark_groups"]

            # Figure out what to do with mark_groups in order to be able to
            # iterate over it and get memberlist-color pairs
            if isinstance(mark_groups, dict):
                group_iter = mark_groups.iteritems()
            elif hasattr(mark_groups, "__iter__"):
                # Lists, tuples, iterators etc
                group_iter = iter(mark_groups)
            else:
                # False
                group_iter = {}.iteritems()

            # We will need a polygon drawer to draw the convex hulls
            polygon_drawer = PolygonDrawer(context, bbox)

            # Iterate over color-memberlist pairs
            for group, color_id in group_iter:
                if not group or color_id is None:
                    continue

                color = palette.get(color_id)

                if isinstance(group, VertexSeq):
                    group = [vertex.index for vertex in group]
                if not hasattr(group, "__iter__"):
                    raise TypeError("group membership list must be iterable")

                # Get the vertex indices that constitute the convex hull
                hull = [group[i] for i in convex_hull([layout[idx] for idx in group])]

                # Calculate the preferred rounding radius for the corners
                corner_radius = 1.25 * max(vertex_builder[idx].size for idx in hull)

                # Construct the polygon
                polygon = [layout[idx] for idx in hull]

                if len(polygon) == 2:
                    # Expand the polygon (which is a flat line otherwise)
                    a, b = Point(*polygon[0]), Point(*polygon[1])
                    c = corner_radius * (a-b).normalized()
                    n = Point(-c[1], c[0])
                    polygon = [a + n, b + n, b - c, b - n, a - n, a + c]
                else:
                    # Expand the polygon around its center of mass
                    center = Point(*[sum(coords) / float(len(coords))
                                      for coords in zip(*polygon)])
                    polygon = [Point(*point).towards(center, -corner_radius)
                               for point in polygon]

                # Draw the hull
                context.set_source_rgba(color[0], color[1], color[2],
                                        color[3]*0.25)
                polygon_drawer.draw_path(polygon, corner_radius=corner_radius)
                context.fill_preserve()
                context.set_source_rgba(*color)
                context.stroke()

        # Draw the edges
        edge_drawer = self.edge_drawer_factory(context)
        if directed:
            drawer_method = edge_drawer.draw_directed_edge
        else:
            drawer_method = edge_drawer.draw_undirected_edge
        for edge, visual_edge in izip(graph.es, edge_builder):
            src, dest = edge.tuple
            src_vertex, dest_vertex = vertex_builder[src], vertex_builder[dest]
            drawer_method(visual_edge, src_vertex, dest_vertex)

        # Calculate the desired vertex order
        if "vertex_order" in kwds:
            # Vertex order specified explicitly
            vertex_order = kwds["vertex_order"]
        elif kwds.get("vertex_order_by") is not None:
            # Vertex order by another attribute
            vertex_order_by = kwds["vertex_order_by"]
            if isinstance(vertex_order_by, tuple):
                vertex_order_by, reverse = vertex_order_by
                if isinstance(reverse, basestring) and reverse.lower().startswith("asc"):
                    reverse = False
                else:
                    reverse = bool(reversed)
            else:
                reverse = False
            attrs = graph.vs[vertex_order_by]
            vertex_order = sorted(range(graph.vcount()), key=attrs.__getitem__,
                    reverse=reverse)
            del attrs
        else:
            # Default vertex order
            vertex_order = None

        if vertex_order is None:
            # Default vertex order
            vertex_coord_iter = izip(vertex_builder, layout)
        else:
            # Specified vertex order
            vertex_coord_iter = ((vertex_builder[i], layout[i])
                    for i in vertex_order)

        # RON EDIT ---------------------
        # Draw the vertices
        context.set_line_width(1)
        for vertex, coords in vertex_coord_iter:
            vertex.shape.draw_path(context, coords[0], coords[1], vertex.size)
            context.set_source_rgba(*vertex.color)
            context.fill_preserve()
            #print vertex.edge_color
            context.set_source_rgba(*vertex.edge_color)
            context.set_line_width(vertex.edge_size)
            context.stroke()
        # ------------------------------

        # Draw the vertex labels
        context.select_font_face("sans-serif", cairo.FONT_SLANT_NORMAL, \
            cairo.FONT_WEIGHT_NORMAL)

        wrap = kwds.get("wrap_labels", None)
        if wrap is None:
            wrap = Configuration.instance()["plotting.wrap_labels"]
        else:
            wrap = bool(wrap)

        if vertex_order is None:
            # Default vertex order
            vertex_coord_iter = izip(vertex_builder, layout)
        else:
            # Specified vertex order
            vertex_coord_iter = ((vertex_builder[i], layout[i])
                    for i in vertex_order)

        label_drawer = self.label_drawer_factory(context)
        for vertex, coords in vertex_coord_iter:
            if vertex.label is None:
                continue

            context.set_font_size(vertex.label_size)
            context.set_source_rgba(*vertex.label_color)
            label_drawer.text = vertex.label

            if vertex.label_dist:
                # Label is displaced from the center of the vertex.
                _, yb, w, h, _, _ = label_drawer.text_extents()
                w, h = w/2.0, h/2.0
                radius = vertex.label_dist * vertex.size / 2.
                # First we find the reference point that is at distance `radius'
                # from the vertex in the direction given by `label_angle'.
                # Then we place the label in a way that the line connecting the
                # center of the bounding box of the label with the center of the
                # vertex goes through the reference point and the reference
                # point lies exactly on the bounding box of the vertex.
                alpha = vertex.label_angle % (2*pi)
                cx = coords[0] + radius * cos(alpha)
                cy = coords[1] - radius * sin(alpha)
                # Now we have the reference point. We have to decide which side
                # of the label box will intersect with the line that connects
                # the center of the label with the center of the vertex.
                if w > 0:
                    beta = atan2(h, w) % (2*pi)
                else:
                    beta = pi/2.
                gamma = pi - beta
                if alpha > 2*pi-beta or alpha <= beta:
                    # Intersection at left edge of label
                    cx += w
                    cy -= tan(alpha) * w
                elif alpha > beta and alpha <= gamma:
                    # Intersection at bottom edge of label
                    try:
                        cx += h / tan(alpha)
                    except:
                        pass    # tan(alpha) == inf
                    cy -= h
                elif alpha > gamma and alpha <= gamma + 2*beta:
                    # Intersection at right edge of label
                    cx -= w
                    cy += tan(alpha) * w
                else:
                    # Intersection at top edge of label
                    try:
                        cx -= h / tan(alpha)
                    except:
                        pass    # tan(alpha) == inf
                    cy += h
                # Draw the label
                label_drawer.draw_at(cx-w, cy-h-yb, wrap=wrap)
            else:
                # Label is exactly in the center of the vertex
                cx, cy = coords
                half_size = vertex.size / 2.
                label_drawer.bbox = (cx - half_size, cy - half_size,
                                     cx + half_size, cy + half_size)
                label_drawer.draw(wrap=wrap)
    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 range(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 range(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 range(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
Exemple #16
0
    def draw(self, graph, palette, *args, **kwds):
        # Some abbreviations for sake of simplicity
        directed = graph.is_directed()
        context = self.context

        # Calculate/get the layout of the graph
        layout = self.ensure_layout(kwds.get("layout", None), graph)

        # Determine the size of the margin on each side
        margin = kwds.get("margin", 0)
        try:
            margin = list(margin)
        except TypeError:
            margin = [margin]
        while len(margin) < 4:
            margin.extend(margin)
        # margin = [x + 20. for x in margin[:4]]

        # Contract the drawing area by the margin and fit the layout
        bbox = self.bbox.contract(margin)
        layout.fit_into(bbox, keep_aspect_ratio=False)

        # Decide whether we need to calculate the curvature of edges
        # automatically -- and calculate them if needed.
        autocurve = kwds.get("autocurve", None)
        if autocurve or (autocurve is None and \
                "edge_curved" not in kwds and "curved" not in graph.edge_attributes() \
                and graph.ecount() < 10000):
            from igraph import autocurve
            default = kwds.get("edge_curved", 0)
            if default is True:
                default = 0.5
            default = float(default)
            kwds["edge_curved"] = autocurve(graph,
                                            attribute=None,
                                            default=default)

        # Construct the visual vertex/edge builders
        class VisualVertexBuilder(AttributeCollectorBase):
            """Collects some visual properties of a vertex for drawing"""
            _kwds_prefix = "vertex_"
            color = ("red", palette.get)
            frame_color = ("black", palette.get)
            frame_width = 1.0
            label = None
            label_angle = -pi / 2
            label_dist = 0.0
            label_color = ("black", palette.get)
            label_size = 14.0
            position = dict(func=layout.__getitem__)
            shape = ("circle", ShapeDrawerDirectory.resolve_default)
            size = 20.0

        class VisualEdgeBuilder(AttributeCollectorBase):
            """Collects some visual properties of an edge for drawing"""
            _kwds_prefix = "edge_"
            arrow_size = 1.0
            arrow_width = 1.0
            color = ("#444", palette.get)
            curved = (0.0, ArrowEdgeDrawer._curvature_to_float)
            width = 1.0

        vertex_builder = VisualVertexBuilder(graph.vs, kwds)
        edge_builder = VisualEdgeBuilder(graph.es, kwds)

        # Draw the highlighted groups (if any)
        if "mark_groups" in kwds:
            mark_groups = kwds["mark_groups"]

            # Figure out what to do with mark_groups in order to be able to
            # iterate over it and get memberlist-color pairs
            if isinstance(mark_groups, dict):
                group_iter = mark_groups.iteritems()
            elif hasattr(mark_groups, "__iter__"):
                # Lists, tuples, iterators etc
                group_iter = iter(mark_groups)
            else:
                # False
                group_iter = {}.iteritems()

            # We will need a polygon drawer to draw the convex hulls
            polygon_drawer = PolygonDrawer(context, bbox)

            # Iterate over color-memberlist pairs
            for group, color_id in group_iter:
                if not group or color_id is None:
                    continue

                color = palette.get(color_id)

                if isinstance(group, VertexSeq):
                    group = [vertex.index for vertex in group]
                if not hasattr(group, "__iter__"):
                    raise TypeError("group membership list must be iterable")

                # Get the vertex indices that constitute the convex hull
                hull = [
                    group[i]
                    for i in convex_hull([layout[idx] for idx in group])
                ]

                # Calculate the preferred rounding radius for the corners
                corner_radius = 1.25 * max(vertex_builder[idx].size
                                           for idx in hull)

                # Construct the polygon
                polygon = [layout[idx] for idx in hull]

                if len(polygon) == 2:
                    # Expand the polygon (which is a flat line otherwise)
                    a, b = Point(*polygon[0]), Point(*polygon[1])
                    c = corner_radius * (a - b).normalized()
                    n = Point(-c[1], c[0])
                    polygon = [a + n, b + n, b - c, b - n, a - n, a + c]
                else:
                    # Expand the polygon around its center of mass
                    center = Point(*[
                        sum(coords) / float(len(coords))
                        for coords in zip(*polygon)
                    ])
                    polygon = [
                        Point(*point).towards(center, -corner_radius)
                        for point in polygon
                    ]

                # Draw the hull
                context.set_source_rgba(color[0], color[1], color[2],
                                        color[3] * 0.25)
                polygon_drawer.draw_path(polygon, corner_radius=corner_radius)
                context.fill_preserve()
                context.set_source_rgba(*color)
                context.stroke()

        # Draw the edges
        edge_drawer = self.edge_drawer_factory(context)
        if directed:
            drawer_method = edge_drawer.draw_directed_edge
        else:
            drawer_method = edge_drawer.draw_undirected_edge
        for edge, visual_edge in izip(graph.es, edge_builder):
            src, dest = edge.tuple
            src_vertex, dest_vertex = vertex_builder[src], vertex_builder[dest]
            drawer_method(visual_edge, src_vertex, dest_vertex)

        # Calculate the desired vertex order
        if "vertex_order" in kwds:
            # Vertex order specified explicitly
            vertex_order = kwds["vertex_order"]
        elif kwds.get("vertex_order_by") is not None:
            # Vertex order by another attribute
            vertex_order_by = kwds["vertex_order_by"]
            if isinstance(vertex_order_by, tuple):
                vertex_order_by, reverse = vertex_order_by
                if isinstance(
                        reverse,
                        basestring) and reverse.lower().startswith("asc"):
                    reverse = False
                else:
                    reverse = bool(reversed)
            else:
                reverse = False
            attrs = graph.vs[vertex_order_by]
            vertex_order = sorted(range(graph.vcount()),
                                  key=attrs.__getitem__,
                                  reverse=reverse)
            del attrs
        else:
            # Default vertex order
            vertex_order = None

        if vertex_order is None:
            # Default vertex order
            vertex_coord_iter = izip(vertex_builder, layout)
        else:
            # Specified vertex order
            vertex_coord_iter = ((vertex_builder[i], layout[i])
                                 for i in vertex_order)

        # Draw the vertices
        context.set_line_width(1)
        for vertex, coords in vertex_coord_iter:
            vertex.shape.draw_path(context, \
                    coords[0], coords[1], vertex.size)
            context.set_source_rgba(*vertex.color)
            context.fill_preserve()
            context.set_source_rgba(*vertex.frame_color)
            context.set_line_width(vertex.frame_width)
            context.stroke()

        # Draw the vertex labels
        context.select_font_face("sans-serif", cairo.FONT_SLANT_NORMAL, \
            cairo.FONT_WEIGHT_NORMAL)

        wrap = kwds.get("wrap_labels", None)
        if wrap is None:
            wrap = Configuration.instance()["plotting.wrap_labels"]
        else:
            wrap = bool(wrap)

        if vertex_order is None:
            # Default vertex order
            vertex_coord_iter = izip(vertex_builder, layout)
        else:
            # Specified vertex order
            vertex_coord_iter = ((vertex_builder[i], layout[i])
                                 for i in vertex_order)

        label_drawer = self.label_drawer_factory(context)
        for vertex, coords in vertex_coord_iter:
            if vertex.label is None:
                continue

            context.set_font_size(vertex.label_size)
            context.set_source_rgba(*vertex.label_color)
            label_drawer.text = vertex.label

            if vertex.label_dist:
                # Label is displaced from the center of the vertex.
                _, yb, w, h, _, _ = label_drawer.text_extents()
                w, h = w / 2.0, h / 2.0
                radius = vertex.label_dist * vertex.size / 2.
                # First we find the reference point that is at distance `radius'
                # from the vertex in the direction given by `label_angle'.
                # Then we place the label in a way that the line connecting the
                # center of the bounding box of the label with the center of the
                # vertex goes through the reference point and the reference
                # point lies exactly on the bounding box of the vertex.
                alpha = vertex.label_angle % (2 * pi)
                cx = coords[0] + radius * cos(alpha)
                cy = coords[1] - radius * sin(alpha)
                # Now we have the reference point. We have to decide which side
                # of the label box will intersect with the line that connects
                # the center of the label with the center of the vertex.
                if w > 0:
                    beta = atan2(h, w) % (2 * pi)
                else:
                    beta = pi / 2.
                gamma = pi - beta
                if alpha > 2 * pi - beta or alpha <= beta:
                    # Intersection at left edge of label
                    cx += w
                    cy -= tan(alpha) * w
                elif alpha > beta and alpha <= gamma:
                    # Intersection at bottom edge of label
                    try:
                        cx += h / tan(alpha)
                    except:
                        pass  # tan(alpha) == inf
                    cy -= h
                elif alpha > gamma and alpha <= gamma + 2 * beta:
                    # Intersection at right edge of label
                    cx -= w
                    cy += tan(alpha) * w
                else:
                    # Intersection at top edge of label
                    try:
                        cx -= h / tan(alpha)
                    except:
                        pass  # tan(alpha) == inf
                    cy += h
                # Draw the label
                label_drawer.draw_at(cx - w, cy - h - yb, wrap=wrap)
            else:
                # Label is exactly in the center of the vertex
                cx, cy = coords
                half_size = vertex.size / 2.
                label_drawer.bbox = (cx - half_size, cy - half_size,
                                     cx + half_size, cy + half_size)
                label_drawer.draw(wrap=wrap)