Skip to content

Commit

Permalink
Refactor multilayer drawing function and add headers to convert module (
Browse files Browse the repository at this point in the history
#434)

* initial commit

* Refactor draw module (#435)

* refact: draw module

* style: black and isort

* fix #331 (#438)

* Added the ability for users to access the optional arguments of NetworkX layout functions. (#439)

* initial commit

* remove order_nodes

* streamline draw_multilayer function

* updated defaults and docstring

* added brief convert descriptions

* response to review

---------

Co-authored-by: Maxime Lucas <[email protected]>
  • Loading branch information
nwlandry and maximelucas authored Aug 3, 2023
1 parent 0b19dc1 commit 52e6aac
Show file tree
Hide file tree
Showing 14 changed files with 413 additions and 220 deletions.
189 changes: 46 additions & 143 deletions tests/drawing/test_draw.py
Original file line number Diff line number Diff line change
@@ -1,149 +1,10 @@
import matplotlib.pyplot as plt
import numpy as np
import pytest
from matplotlib import cm

import xgi
from xgi.drawing.draw import _CCW_sort, _color_arg_to_dict, _scalar_arg_to_dict
from xgi.exception import XGIError


def test_CCW_sort():
coords = [[0.919, 0.145], [0.037, 0.537], [0.402, 0.56]]
sorted_coords = _CCW_sort(coords)
assert np.all(
sorted_coords == np.array([[0.037, 0.537], [0.402, 0.56], [0.919, 0.145]])
)

coords = [[0.037, 0.537], [0.402, 0.56], [0.791, 0.91], [0.0, 0.868]]
sorted_coords = _CCW_sort(coords)
assert np.all(
sorted_coords
== np.array([[0.037, 0.537], [0.0, 0.868], [0.791, 0.91], [0.402, 0.56]])
)


def test_scalar_arg_to_dict(edgelist4):
ids = [1, 2, 3]
min_val = 1
max_val = 5

arg = 1
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)
assert d == {1: 1, 2: 1, 3: 1}

arg = 0.3
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)
assert d == {1: 0.3, 2: 0.3, 3: 0.3}

arg = [0.2, 3, 4]
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)
assert d == {1: 0.2, 2: 3, 3: 4}

arg = np.array([0.2, 3, 4])
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)
assert d == {1: 0.2, 2: 3, 3: 4}

arg = {1: 0.2, 2: 3, 3: 4}
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)
assert d == {1: 0.2, 2: 3, 3: 4}

H = xgi.Hypergraph(edgelist4)
arg = H.nodes.degree
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)
assert d == {1: 1.0, 2: 3.0, 3: 5.0}

with pytest.raises(TypeError):
arg = "2"
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)

with pytest.raises(TypeError):
arg = (1, 2, 3)
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)


def test_color_arg_to_dict(edgelist4):
ids = [1, 2, 3]

# single values
arg1 = "black"
arg2 = (0.1, 0.2, 0.3)
arg3 = (0.1, 0.2, 0.3, 0.5)

# test iterables of colors
arg4 = [(0.1, 0.2, 0.3), (0.1, 0.2, 0.4), (0.1, 0.2, 0.5)]
arg5 = ["blue", "black", "red"]
arg6 = np.array(["blue", "black", "red"])
arg7 = {0: (0.1, 0.2, 0.3), 1: (0.1, 0.2, 0.4), 2: (0.1, 0.2, 0.5)}
arg8 = {0: "blue", 1: "black", 2: "red"}

# test iterables of values
arg9 = [0, 0.1, 0.2]
arg10 = {1: 0, 2: 0.1, 3: 0.2}
arg11 = np.array([0, 0.1, 0.2])

# test single values
d = _color_arg_to_dict(arg1, ids, None)
assert d == {1: "black", 2: "black", 3: "black"}

d = _color_arg_to_dict(arg2, ids, None)
assert d == {1: (0.1, 0.2, 0.3), 2: (0.1, 0.2, 0.3), 3: (0.1, 0.2, 0.3)}

d = _color_arg_to_dict(arg3, ids, None)
for i in d:
assert np.allclose(d[i], np.array([0.1, 0.2, 0.3, 0.5]))

# Test iterables of colors
d = _color_arg_to_dict(arg4, ids, None)
assert d == {1: (0.1, 0.2, 0.3), 2: (0.1, 0.2, 0.4), 3: (0.1, 0.2, 0.5)}

d = _color_arg_to_dict(arg5, ids, None)
assert d == {1: "blue", 2: "black", 3: "red"}

d = _color_arg_to_dict(arg6, ids, None)
assert d == {1: "blue", 2: "black", 3: "red"}

d = _color_arg_to_dict(arg7, ids, None)
assert d == {1: (0.1, 0.2, 0.4), 2: (0.1, 0.2, 0.5)}

d = _color_arg_to_dict(arg8, ids, None)
assert d == {1: "black", 2: "red"}

# Test iterables of values
cdict = {
1: np.array([[0.89173395, 0.93510188, 0.97539408, 1.0]]),
2: np.array([[0.41708574, 0.68063053, 0.83823145, 1.0]]),
3: np.array([[0.03137255, 0.28973472, 0.57031911, 1.0]]),
}
d = _color_arg_to_dict(arg9, ids, cm.Blues)
for i in d:
assert np.allclose(d[i], cdict[i])

d = _color_arg_to_dict(arg10, ids, cm.Blues)
for i in d:
assert np.allclose(d[i], cdict[i])

d = _color_arg_to_dict(arg11, ids, cm.Blues)
for i in d:
assert np.allclose(d[i], cdict[i])

H = xgi.Hypergraph(edgelist4)
arg = H.nodes.degree
d = _color_arg_to_dict(arg, ids, cm.Reds)
assert np.allclose(d[1], np.array([[0.99692426, 0.89619377, 0.84890427, 1.0]]))
assert np.allclose(d[2], np.array([[0.98357555, 0.41279508, 0.28835063, 1.0]]))
assert np.allclose(d[3], np.array([[0.59461745, 0.0461361, 0.07558631, 1.0]]))

# Test bad calls
with pytest.raises(TypeError):
arg = 0.3
d = _color_arg_to_dict(arg, ids, None)

with pytest.raises(TypeError):
arg = 1
d = _color_arg_to_dict(arg, ids, None)


def test_draw(edgelist8):
H = xgi.Hypergraph(edgelist8)

Expand Down Expand Up @@ -244,18 +105,48 @@ def test_correct_number_of_collections_draw_multilayer(edgelist8):
# hypergraph
H = xgi.Hypergraph(edgelist8)
ax1 = xgi.draw_multilayer(H)
assert xgi.max_edge_order(H) * 4 - 1 == len(ax1.collections)
sizes = xgi.unique_edge_sizes(H)
num_planes = max(sizes) - min(sizes) + 1
num_node_collections = max(sizes) - min(sizes) + 1
num_edge_collections = H.num_edges
num_thru_lines_collections = 1

assert (
num_planes
+ num_node_collections
+ num_edge_collections
+ num_thru_lines_collections
== len(ax1.collections)
)
plt.close()

# max_order parameter
max_order = 2
ax2 = xgi.draw_multilayer(H, max_order=max_order)
assert max_order * 4 - 1 == len(ax2.collections)
sizes = [2, 3]
num_planes = max(sizes) - min(sizes) + 1
num_node_collections = max(sizes) - min(sizes) + 1
num_edge_collections = len(H.edges.filterby("size", [2, 3], "between"))
num_thru_lines_collections = 1

assert (
num_planes
+ num_node_collections
+ num_edge_collections
+ num_thru_lines_collections
== len(ax2.collections)
)
plt.close()

# conn_lines parameter
ax3 = xgi.draw_multilayer(H, conn_lines=False)
assert xgi.max_edge_order(H) * 3 == len(ax3.collections)
sizes = xgi.unique_edge_sizes(H)
num_planes = max(sizes) - min(sizes) + 1
num_node_collections = max(sizes) - min(sizes) + 1
num_edge_collections = H.num_edges
assert num_planes + num_node_collections + num_edge_collections == len(
ax3.collections
)
plt.close()

# custom parameters
Expand All @@ -274,7 +165,19 @@ def test_correct_number_of_collections_draw_multilayer(edgelist8):
v_angle=15,
sep=2,
)
assert xgi.max_edge_order(H) * 4 - 1 == len(ax4.collections)
sizes = xgi.unique_edge_sizes(H)
num_planes = max(sizes) - min(sizes) + 1
num_node_collections = max(sizes) - min(sizes) + 1
num_edge_collections = H.num_edges
num_thru_lines_collections = 1

assert (
num_planes
+ num_node_collections
+ num_edge_collections
+ num_thru_lines_collections
== len(ax4.collections)
)
plt.close()


Expand Down
142 changes: 142 additions & 0 deletions tests/drawing/test_draw_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import numpy as np
import pytest
from matplotlib import cm

import xgi
from xgi.drawing.draw import _CCW_sort, _color_arg_to_dict, _scalar_arg_to_dict


def test_CCW_sort():
coords = [[0.919, 0.145], [0.037, 0.537], [0.402, 0.56]]
sorted_coords = _CCW_sort(coords)
assert np.all(
sorted_coords == np.array([[0.037, 0.537], [0.402, 0.56], [0.919, 0.145]])
)

coords = [[0.037, 0.537], [0.402, 0.56], [0.791, 0.91], [0.0, 0.868]]
sorted_coords = _CCW_sort(coords)
assert np.all(
sorted_coords
== np.array([[0.037, 0.537], [0.0, 0.868], [0.791, 0.91], [0.402, 0.56]])
)


def test_scalar_arg_to_dict(edgelist4):
ids = [1, 2, 3]
min_val = 1
max_val = 5

arg = 1
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)
assert d == {1: 1, 2: 1, 3: 1}

arg = 0.3
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)
assert d == {1: 0.3, 2: 0.3, 3: 0.3}

arg = [0.2, 3, 4]
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)
assert d == {1: 0.2, 2: 3, 3: 4}

arg = np.array([0.2, 3, 4])
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)
assert d == {1: 0.2, 2: 3, 3: 4}

arg = {1: 0.2, 2: 3, 3: 4}
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)
assert d == {1: 0.2, 2: 3, 3: 4}

H = xgi.Hypergraph(edgelist4)
arg = H.nodes.degree
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)
assert d == {1: 1.0, 2: 3.0, 3: 5.0}

with pytest.raises(TypeError):
arg = "2"
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)

with pytest.raises(TypeError):
arg = (1, 2, 3)
d = _scalar_arg_to_dict(arg, ids, min_val, max_val)


def test_color_arg_to_dict(edgelist4):
ids = [1, 2, 3]

# single values
arg1 = "black"
arg2 = (0.1, 0.2, 0.3)
arg3 = (0.1, 0.2, 0.3, 0.5)

# test iterables of colors
arg4 = [(0.1, 0.2, 0.3), (0.1, 0.2, 0.4), (0.1, 0.2, 0.5)]
arg5 = ["blue", "black", "red"]
arg6 = np.array(["blue", "black", "red"])
arg7 = {0: (0.1, 0.2, 0.3), 1: (0.1, 0.2, 0.4), 2: (0.1, 0.2, 0.5)}
arg8 = {0: "blue", 1: "black", 2: "red"}

# test iterables of values
arg9 = [0, 0.1, 0.2]
arg10 = {1: 0, 2: 0.1, 3: 0.2}
arg11 = np.array([0, 0.1, 0.2])

# test single values
d = _color_arg_to_dict(arg1, ids, None)
assert d == {1: "black", 2: "black", 3: "black"}

d = _color_arg_to_dict(arg2, ids, None)
assert d == {1: (0.1, 0.2, 0.3), 2: (0.1, 0.2, 0.3), 3: (0.1, 0.2, 0.3)}

d = _color_arg_to_dict(arg3, ids, None)
for i in d:
assert np.allclose(d[i], np.array([0.1, 0.2, 0.3, 0.5]))

# Test iterables of colors
d = _color_arg_to_dict(arg4, ids, None)
assert d == {1: (0.1, 0.2, 0.3), 2: (0.1, 0.2, 0.4), 3: (0.1, 0.2, 0.5)}

d = _color_arg_to_dict(arg5, ids, None)
assert d == {1: "blue", 2: "black", 3: "red"}

d = _color_arg_to_dict(arg6, ids, None)
assert d == {1: "blue", 2: "black", 3: "red"}

d = _color_arg_to_dict(arg7, ids, None)
assert d == {1: (0.1, 0.2, 0.4), 2: (0.1, 0.2, 0.5)}

d = _color_arg_to_dict(arg8, ids, None)
assert d == {1: "black", 2: "red"}

# Test iterables of values
cdict = {
1: np.array([[0.89173395, 0.93510188, 0.97539408, 1.0]]),
2: np.array([[0.41708574, 0.68063053, 0.83823145, 1.0]]),
3: np.array([[0.03137255, 0.28973472, 0.57031911, 1.0]]),
}
d = _color_arg_to_dict(arg9, ids, cm.Blues)
for i in d:
assert np.allclose(d[i], cdict[i])

d = _color_arg_to_dict(arg10, ids, cm.Blues)
for i in d:
assert np.allclose(d[i], cdict[i])

d = _color_arg_to_dict(arg11, ids, cm.Blues)
for i in d:
assert np.allclose(d[i], cdict[i])

H = xgi.Hypergraph(edgelist4)
arg = H.nodes.degree
d = _color_arg_to_dict(arg, ids, cm.Reds)
assert np.allclose(d[1], np.array([[0.99692426, 0.89619377, 0.84890427, 1.0]]))
assert np.allclose(d[2], np.array([[0.98357555, 0.41279508, 0.28835063, 1.0]]))
assert np.allclose(d[3], np.array([[0.59461745, 0.0461361, 0.07558631, 1.0]]))

# Test bad calls
with pytest.raises(TypeError):
arg = 0.3
d = _color_arg_to_dict(arg, ids, None)

with pytest.raises(TypeError):
arg = 1
d = _color_arg_to_dict(arg, ids, None)
2 changes: 2 additions & 0 deletions xgi/convert/bipartite_edges.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Methods for converting to and from bipartite edgelists."""

from ..generators import empty_hypergraph

__all__ = ["from_bipartite_edgelist", "to_bipartite_edgelist"]
Expand Down
2 changes: 2 additions & 0 deletions xgi/convert/bipartite_graph.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Methods for converting to and from bipartite graphs."""

import networkx as nx
from networkx import bipartite

Expand Down
2 changes: 2 additions & 0 deletions xgi/convert/graph.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Method for projecting a hypergraph to a graph."""

import networkx as nx

from ..linalg import adjacency_matrix
Expand Down
2 changes: 2 additions & 0 deletions xgi/convert/higher_order_network.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Methods for converting to higher-order network objects."""

from copy import deepcopy

import pandas as pd
Expand Down
2 changes: 2 additions & 0 deletions xgi/convert/hyperedges.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"""Methods for converting to and from hyperedge lists."""

from ..core import SimplicialComplex
from ..generators import empty_hypergraph

Expand Down
Loading

0 comments on commit 52e6aac

Please sign in to comment.