Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 66 additions & 2 deletions src/_igraph/graphobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -8188,14 +8188,15 @@ PyObject *igraphmodule_Graph_layout_kamada_kawai(igraphmodule_GraphObject *
return NULL;
}

if (dim == 2)
if (dim == 2) {
ret = igraph_layout_kamada_kawai
(&self->g, &m, use_seed, maxiter, epsilon, kkconst,
weights, /*bounds*/ minx, maxx, miny, maxy);
else
} else {
ret = igraph_layout_kamada_kawai_3d
(&self->g, &m, use_seed, maxiter, epsilon, kkconst,
weights, /*bounds*/ minx, maxx, miny, maxy, minz, maxz);
}

DESTROY_VECTORS;

Expand All @@ -8207,6 +8208,19 @@ PyObject *igraphmodule_Graph_layout_kamada_kawai(igraphmodule_GraphObject *
return NULL;
}

/* Align layout, but only if no bounding box was specified. */
if (minx == NULL && maxx == NULL &&
miny == NULL && maxy == NULL &&
minz == NULL && maxz == NULL &&
igraph_vcount(&self->g) <= 1000) {
ret = igraph_layout_align(&self->g, &m);
if (ret) {
igraph_matrix_destroy(&m);
igraphmodule_handle_igraph_error();
return NULL;
}
}

result_o = igraphmodule_matrix_t_to_PyList(&m, IGRAPHMODULE_TYPE_FLOAT);
igraph_matrix_destroy(&m);
return (PyObject *) result_o;
Expand Down Expand Up @@ -8298,6 +8312,16 @@ PyObject* igraphmodule_Graph_layout_davidson_harel(igraphmodule_GraphObject *sel
return NULL;
}

/* Align layout */
if (igraph_vcount(&self->g)) {
retval = igraph_layout_align(&self->g, &m);
if (retval) {
igraph_matrix_destroy(&m);
igraphmodule_handle_igraph_error();
return NULL;
}
}

result_o = igraphmodule_matrix_t_to_PyList(&m, IGRAPHMODULE_TYPE_FLOAT);
igraph_matrix_destroy(&m);
return (PyObject *) result_o;
Expand Down Expand Up @@ -8520,6 +8544,19 @@ PyObject
return NULL;
}

/* Align layout, but only if no bounding box was specified. */
if (minx == NULL && maxx == NULL &&
miny == NULL && maxy == NULL &&
minz == NULL && maxz == NULL &&
igraph_vcount(&self->g) <= 1000) {
ret = igraph_layout_align(&self->g, &m);
if (ret) {
igraph_matrix_destroy(&m);
igraphmodule_handle_igraph_error();
return NULL;
}
}

#undef DESTROY_VECTORS

result_o = igraphmodule_matrix_t_to_PyList(&m, IGRAPHMODULE_TYPE_FLOAT);
Expand Down Expand Up @@ -8574,6 +8611,15 @@ PyObject *igraphmodule_Graph_layout_graphopt(igraphmodule_GraphObject *self,
return NULL;
}

/* Align layout */
if (igraph_vcount(&self->g) <= 1000) {
if (igraph_layout_align(&self->g, &m)) {
igraph_matrix_destroy(&m);
igraphmodule_handle_igraph_error();
return NULL;
}
}

result_o = igraphmodule_matrix_t_to_PyList(&m, IGRAPHMODULE_TYPE_FLOAT);
igraph_matrix_destroy(&m);
return (PyObject *) result_o;
Expand Down Expand Up @@ -8632,6 +8678,15 @@ PyObject *igraphmodule_Graph_layout_lgl(igraphmodule_GraphObject * self,
return NULL;
}

/* Align layout */
if (igraph_vcount(&self->g) <= 1000) {
if (igraph_layout_align(&self->g, &m)) {
igraph_matrix_destroy(&m);
igraphmodule_handle_igraph_error();
return NULL;
}
}

result_o = igraphmodule_matrix_t_to_PyList(&m, IGRAPHMODULE_TYPE_FLOAT);
igraph_matrix_destroy(&m);
return (PyObject *) result_o;
Expand Down Expand Up @@ -8697,6 +8752,15 @@ PyObject *igraphmodule_Graph_layout_mds(igraphmodule_GraphObject * self,
igraph_matrix_destroy(dist); free(dist);
}

/* Align layout */
if (igraph_vcount(&self->g) <= 1000) {
if (igraph_layout_align(&self->g, &m)) {
igraph_matrix_destroy(&m);
igraphmodule_handle_igraph_error();
return NULL;
}
}

result_o = igraphmodule_matrix_t_to_PyList(&m, IGRAPHMODULE_TYPE_FLOAT);
igraph_matrix_destroy(&m);
return (PyObject *) result_o;
Expand Down
36 changes: 36 additions & 0 deletions src/_igraph/igraphmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,38 @@ PyObject* igraphmodule_set_status_handler(PyObject* self, PyObject* o) {
Py_RETURN_NONE;
}

PyObject* igraphmodule_align_layout(PyObject* self, PyObject* args, PyObject* kwds) {
static char* kwlist[] = {"graph", "layout", NULL};
PyObject *graph_o, *layout_o;
PyObject *res;
igraph_t *graph;
igraph_matrix_t layout;

if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO", kwlist, &graph_o, &layout_o)) {
return NULL;
}

if (igraphmodule_PyObject_to_igraph_t(graph_o, &graph)) {
return NULL;
}

if (igraphmodule_PyObject_to_matrix_t(layout_o, &layout, "layout")) {
return NULL;
}

if (igraph_layout_align(graph, &layout)) {
igraphmodule_handle_igraph_error();
igraph_matrix_destroy(&layout);
return NULL;
}

res = igraphmodule_matrix_t_to_PyList(&layout, IGRAPHMODULE_TYPE_FLOAT);

igraph_matrix_destroy(&layout);

return res;
}

PyObject* igraphmodule_convex_hull(PyObject* self, PyObject* args, PyObject* kwds) {
static char* kwlist[] = {"vs", "coords", NULL};
PyObject *vs, *o, *o1 = 0, *o2 = 0, *o1_float, *o2_float, *coords = Py_False;
Expand Down Expand Up @@ -790,6 +822,10 @@ static PyMethodDef igraphmodule_methods[] =
METH_VARARGS | METH_KEYWORDS,
"_power_law_fit(data, xmin=-1, force_continuous=False, p_precision=0.01)\n--\n\n"
},
{"_align_layout", (PyCFunction)igraphmodule_align_layout,
METH_VARARGS | METH_KEYWORDS,
"_align_layout(graph, layout)\n--\n\n"
},
{"convex_hull", (PyCFunction)igraphmodule_convex_hull,
METH_VARARGS | METH_KEYWORDS,
"convex_hull(vs, coords=False)\n--\n\n"
Expand Down
1 change: 1 addition & 0 deletions src/igraph/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@
from igraph.io.images import _write_graph_to_svg
from igraph.layout import (
Layout,
align_layout,
_layout,
_layout_auto,
_layout_sugiyama,
Expand Down
22 changes: 22 additions & 0 deletions src/igraph/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

__all__ = (
"Layout",
"align_layout",
"_layout",
"_layout_auto",
"_layout_sugiyama",
Expand Down Expand Up @@ -534,6 +535,27 @@ def _layout(graph, layout=None, *args, **kwds):
return layout


def align_layout(graph, layout):
"""Aligns a graph layout with the coordinate axes

This function centers a vertex layout on the coordinate system origin and
rotates the layout to achieve a visually pleasing alignment with the coordinate
axes. Doing this is particularly useful with force-directed layouts such as
L{Graph.layout_fruchterman_reingold}. Layouts in arbitrary dimensional spaces
are supported.

@param graph: the graph that the layout is associated with.
@param layout: the L{Layout} object containing the vertex coordinates
to align.
@return: a new aligned L{Layout} object.
"""
from igraph._igraph import _align_layout

if not isinstance(layout, Layout):
layout = Layout(layout)

return Layout(_align_layout(graph, layout.coords))

def _layout_auto(graph, *args, **kwds):
"""Chooses and runs a suitable layout function based on simple
topological properties of the graph.
Expand Down
Binary file modified tests/drawing/cairo/baseline_images/clustering_directed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/drawing/cairo/baseline_images/graph_basic.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/drawing/cairo/baseline_images/graph_directed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/drawing/cairo/baseline_images/graph_mark_groups_directed.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 8 additions & 1 deletion tests/test_layouts.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import unittest
from math import hypot
from igraph import Graph, Layout, BoundingBox, InternalError
from igraph import Graph, Layout, BoundingBox, InternalError, align_layout
from igraph import umap_compute_weights


Expand Down Expand Up @@ -452,6 +452,13 @@ def testDRL(self):
lo = g.layout("drl")
self.assertTrue(isinstance(lo, Layout))

def testAlign(self):
g = Graph.Ring(3, circular=False)
lo = Layout([[1,1], [2,2], [3,3]])
lo = align_layout(g, lo)
self.assertTrue(isinstance(lo, Layout))
self.assertTrue(all(abs(lo[i][1]) < 1e-10 for i in range(3)))


def suite():
layout_suite = unittest.defaultTestLoader.loadTestsFromTestCase(LayoutTests)
Expand Down
Loading