Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to draw hypergraphs as a bipartite graph #492

Merged
merged 45 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
97013fe
add bipartite layout
nwlandry Oct 18, 2023
b41067f
Update draw.py
nwlandry Oct 18, 2023
8614660
update
nwlandry Nov 3, 2023
f4d6760
remove unused imports
nwlandry Nov 3, 2023
d660bc4
updates
nwlandry Jan 14, 2024
925c022
fix import error
nwlandry Feb 12, 2024
5d053b3
update with reduced functionality
nwlandry Feb 12, 2024
0185f2b
Update draw.py
nwlandry Feb 12, 2024
4f82531
fix unit tests and functionality
nwlandry Feb 13, 2024
c5dec10
removed unnecessary imports
nwlandry Feb 13, 2024
c21fe77
removed references to old drawing function
nwlandry Feb 13, 2024
6f64151
formatting
nwlandry Feb 13, 2024
557b82f
add instructions
nwlandry Feb 13, 2024
5096a79
Update HOW_TO_CONTRIBUTE.md
nwlandry Feb 13, 2024
2e34fb3
Update developer.txt
nwlandry Feb 13, 2024
41efd3e
Fix messy notebook diffs. Merge branch 'main' into add-draw-bipartite
nwlandry Feb 13, 2024
d5adfa7
add tests
nwlandry Feb 26, 2024
48e27f4
fix api and documentation
nwlandry Feb 26, 2024
98088fd
Merge branch 'main' into add-draw-bipartite
nwlandry Feb 26, 2024
a069b11
updated changelog and upversion
nwlandry Feb 29, 2024
7848f52
Revert "updated changelog and upversion"
nwlandry Feb 29, 2024
8548006
updates
nwlandry Mar 2, 2024
e931e8b
response to review
nwlandry Mar 2, 2024
66994ce
updated kernel
nwlandry Mar 8, 2024
249c371
Update Tutorial 7 - Directed Hypergraphs.ipynb
nwlandry Mar 8, 2024
08f6c06
updates
nwlandry Mar 8, 2024
7934570
Update docs
nwlandry Mar 8, 2024
9325619
Fix failing tests
nwlandry Mar 8, 2024
88c01b2
Merge branch 'main' into add-draw-bipartite
nwlandry Mar 8, 2024
105fd9b
Removed bad looking drawings
nwlandry Mar 8, 2024
950427a
added unit tests and updated code to match
nwlandry Mar 8, 2024
253e844
fixed a bug
nwlandry Mar 8, 2024
1307d55
response to review
nwlandry Mar 11, 2024
bf092f3
Added explanatory comment
nwlandry Apr 2, 2024
a2b90ae
Update xgi/drawing/draw.py
nwlandry Apr 17, 2024
7e2970b
Update xgi/drawing/draw.py
nwlandry Apr 18, 2024
9f2c6eb
Update xgi/drawing/draw.py
nwlandry Apr 18, 2024
d7c4554
Response to review
nwlandry Apr 18, 2024
acd7071
Remove broken conventional commits link
nwlandry Apr 18, 2024
6ffce2f
Update Tutorial 5 - Plotting.ipynb
nwlandry Apr 18, 2024
330f865
added and fixed tests
nwlandry Apr 18, 2024
c42a946
added documentation
nwlandry Apr 18, 2024
909489d
Fixed colormap
nwlandry Apr 22, 2024
362a591
fixed edge color issue.
nwlandry Apr 22, 2024
730bd74
Merge branch 'main' into add-draw-bipartite
nwlandry Apr 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 9 additions & 10 deletions HOW_TO_CONTRIBUTE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,15 @@ Please note we have a [code of conduct](/CODE_OF_CONDUCT.md), please follow it i
## Pull Request process

1. Download the dependencies in the developer [requirements file](/requirements/developer.txt).
2. [Optional, but STRONGLY preferred] Label commits according to [Conventional Commits](https://www.conventionalcommits.org) style.
3. [Optional, but STRONGLY preferred] Add unit tests for features being added or bugs being fixed.
4. [Optional, but STRONGLY preferred] Include any new method/function in the corresponding docs file.
5. Run `pytest` to verify all unit tests pass.
6. [Optional, but STRONGLY preferred] Run `pylint xgi/ --disable all --enable W0611` and remove any unnecessary dependencies.
7. [Optional, but STRONGLY preferred] Run `isort .` and `nbqa isort .` to sort any new import statements in the source code and tutorials.
8. [Optional, but STRONGLY preferred] Run `black .` for consistent styling.
9. Update the "Current Version" section of CHANGELOG.md with overview of changes to the interface and add the usernames of all contributors.
10. Submit Pull Request with a list of changes, links to issues that it addresses (if applicable)
11. You may merge the Pull Request in once you have the sign-off of at least one other developer, or if you do not have permission to do that, you may request the reviewer to merge it for you.
2. [Optional, but STRONGLY preferred] Add unit tests for features being added or bugs being fixed.
3. [Optional, but STRONGLY preferred] Include any new method/function in the corresponding docs file.
4. Run `pytest` to verify all unit tests pass.
5. [Optional, but STRONGLY preferred] Run `pylint xgi/ --disable all --enable W0611` and `nbqa pylint . --disable all --enable W0611` and remove any unnecessary dependencies.
6. [Optional, but STRONGLY preferred] Run `isort .` and `nbqa isort .` to sort any new import statements in the source code and tutorials.
7. [Optional, but STRONGLY preferred] Run `black .` for consistent styling.
8. Update the "Current Version" section of CHANGELOG.md with overview of changes to the interface and add the usernames of all contributors.
9. Submit Pull Request with a list of changes, links to issues that it addresses (if applicable)
10. You may merge the Pull Request in once you have the sign-off of at least one other developer, or if you do not have permission to do that, you may request the reviewer to merge it for you.

## New Version process
1. Make sure that the Github Actions workflow runs without any errors.
Expand Down
3 changes: 1 addition & 2 deletions benchmarks/hypernetx.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
"import time\n",
"\n",
"import hypernetx as hnx\n",
"import pandas as pd\n",
"\n",
"import xgi"
]
Expand Down Expand Up @@ -223,7 +222,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.6 | packaged by conda-forge | (main, Aug 22 2022, 20:38:29) [Clang 13.0.1 ]"
"version": "3.10.0"
},
"vscode": {
"interpreter": {
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/networkx.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.4"
"version": "3.10.0"
},
"orig_nbformat": 4
},
Expand Down
4 changes: 3 additions & 1 deletion docs/source/api/drawing/xgi.drawing.draw.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ xgi.drawing.draw
.. rubric:: Functions

.. autofunction:: draw
.. autofunction:: draw_dihypergraph
.. autofunction:: draw_bipartite
.. autofunction:: draw_multilayer
nwlandry marked this conversation as resolved.
Show resolved Hide resolved
.. autofunction:: draw_nodes
.. autofunction:: draw_hyperedges
.. autofunction:: draw_undirected_dyads
.. autofunction:: draw_directed_dyads
.. autofunction:: draw_simplices
.. autofunction:: draw_node_labels
.. autofunction:: draw_hyperedge_labels
1 change: 1 addition & 0 deletions docs/source/api/drawing/xgi.drawing.layout.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ xgi.drawing.layout
.. autofunction:: random_layout
.. autofunction:: pairwise_spring_layout
.. autofunction:: barycenter_spring_layout
.. autofunction:: edge_positions_from_barycenters
.. autofunction:: weighted_barycenter_spring_layout
.. autofunction:: pca_transform
.. autofunction:: circular_layout
Expand Down
4 changes: 2 additions & 2 deletions docs/source/api/recipes/recipes.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -530,8 +530,8 @@
"data": {
"text/plain": [
"(<Axes3DSubplot: >,\n",
" (<mpl_toolkits.mplot3d.art3d.Path3DCollection at 0x28cae3ad0>,\n",
" <mpl_toolkits.mplot3d.art3d.Poly3DCollection at 0x28caaaa50>))"
" (<mpl_toolkits.mplot3d.art3d.Path3DCollection at 0x144a8a690>,\n",
" <mpl_toolkits.mplot3d.art3d.Poly3DCollection at 0x144fe5bd0>))"
]
},
"execution_count": 20,
Expand Down
19 changes: 9 additions & 10 deletions docs/source/api/tutorials/In Depth 2 - Drawing hyperedges.ipynb

Large diffs are not rendered by default.

224 changes: 94 additions & 130 deletions docs/source/api/tutorials/In Depth 3 - Drawing DiHypergraphs.ipynb

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
"import random\n",
"\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"import xgi"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,6 @@
"source": [
"import random\n",
"\n",
"import numpy as np\n",
"import pandas as pd\n",
"\n",
"import xgi"
]
},
Expand Down
345 changes: 282 additions & 63 deletions docs/source/api/tutorials/Tutorial 5 - Plotting.ipynb

Large diffs are not rendered by default.

42 changes: 27 additions & 15 deletions docs/source/api/tutorials/Tutorial 7 - Directed Hypergraphs.ipynb

Large diffs are not rendered by default.

1 change: 0 additions & 1 deletion docs/source/api/tutorials/XGI in 15 minutes.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"import xgi"
]
Expand Down
1 change: 0 additions & 1 deletion docs/source/api/tutorials/XGI in 5 minutes.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
"outputs": [],
"source": [
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"\n",
"import xgi"
]
Expand Down
169 changes: 126 additions & 43 deletions tests/drawing/test_draw.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import numpy as np
import pytest
import seaborn as sb
from matplotlib.patches import FancyArrowPatch

import xgi
from xgi.exception import XGIError
Expand Down Expand Up @@ -519,63 +520,63 @@ def test_draw_multilayer(edgelist8):
plt.close("all")


def test_draw_dihypergraph(diedgelist2, edgelist8):
def test_draw_bipartite(diedgelist2, edgelist8):
DH = xgi.DiHypergraph(diedgelist2)

fig1, ax1 = plt.subplots()
ax1, collections = xgi.draw_dihypergraph(DH, ax=ax1)
node_coll, phantom_node_coll = collections
ax1, collections1 = xgi.draw_bipartite(DH, ax=ax1)
node_coll1, edge_marker_coll1 = collections1
fig2, ax2 = plt.subplots()
ax2, collections2 = xgi.draw_dihypergraph(
ax2, collections2 = xgi.draw_bipartite(
DH,
ax=ax2,
node_fc="red",
node_ec="blue",
node_lw=2,
node_size=20,
lines_fc="blue",
lines_lw=2,
dyad_color="blue",
dyad_lw=2,
edge_marker_fc="red",
edge_marker_lw=2,
edge_marker_size=20,
)
node_coll2, phantom_node_coll2 = collections2
node_coll2, edge_marker_coll2 = collections2

# number of elements
assert len(node_coll.get_offsets()) == 6 # number of original nodes
assert len(phantom_node_coll.get_offsets()) == 3 # number of original edges
assert len(node_coll1.get_offsets()) == 6 # number of original nodes
assert len(edge_marker_coll1.get_offsets()) == 3 # number of original edges
assert len(ax1.patches) == 11 # number of lines

# node face colors
assert np.all(node_coll.get_facecolor() == np.array([[1, 1, 1, 1]])) # white
assert np.all(node_coll1.get_facecolor() == np.array([[1, 1, 1, 1]])) # white
assert np.all(node_coll2.get_facecolor() == np.array([[1, 0, 0, 1]])) # red

# node edge colors
assert np.all(node_coll.get_edgecolor() == np.array([[0, 0, 0, 1]])) # black
assert np.all(node_coll1.get_edgecolor() == np.array([[0, 0, 0, 1]])) # black
assert np.all(node_coll2.get_edgecolor() == np.array([[0, 0, 1, 1]])) # blue

# node_lw
assert np.all(node_coll.get_linewidth() == np.array([1]))
assert np.all(node_coll1.get_linewidth() == np.array([1]))
assert np.all(node_coll2.get_linewidth() == np.array([2]))

# node_size
assert np.all(node_coll.get_sizes() == np.array([7**2]))
assert np.all(node_coll1.get_sizes() == np.array([7**2]))
assert np.all(node_coll2.get_sizes() == np.array([20**2]))

# edge face colors
assert np.all(phantom_node_coll2.get_facecolor() == np.array([[1, 0, 0, 1]])) # red
assert np.all(edge_marker_coll2.get_facecolor() == np.array([[1, 0, 0, 1]])) # red

# edge _lw
assert np.all(phantom_node_coll.get_linewidth() == np.array([1]))
assert np.all(phantom_node_coll2.get_linewidth() == np.array([2]))
assert np.all(edge_marker_coll1.get_linewidth() == np.array([1]))
assert np.all(edge_marker_coll2.get_linewidth() == np.array([2]))

# edge_size
assert np.all(phantom_node_coll.get_sizes() == np.array([7**2]))
assert np.all(phantom_node_coll2.get_sizes() == np.array([20**2]))
assert np.all(edge_marker_coll1.get_sizes() == np.array([7**2]))
assert np.all(edge_marker_coll2.get_sizes() == np.array([20**2]))

# line lw
for patch in ax1.patches: # lines
assert np.all(patch.get_linewidth() == np.array([1.5]))
assert np.all(patch.get_linewidth() == np.array([1]))
for patch in ax2.patches: # lines
assert np.all(patch.get_linewidth() == np.array([2]))

Expand All @@ -584,29 +585,79 @@ def test_draw_dihypergraph(diedgelist2, edgelist8):
assert np.all(patch.get_facecolor() == np.array([[0, 0, 1, 1]]))

# zorder
assert node_coll.get_zorder() == 4
assert phantom_node_coll.get_zorder() == 2
assert node_coll1.get_zorder() == 2
assert edge_marker_coll1.get_zorder() == 1
for patch in ax1.patches: # lines
assert patch.get_zorder() == 0

# test toggle for edges
fig, ax2 = plt.subplots()
ax2, collections = xgi.draw_dihypergraph(DH, edge_marker_toggle=False, ax=ax2)
node_coll, phantom_node_coll = collections
assert len(ax2.collections) == 1
assert phantom_node_coll is None
plt.close("all")

H = xgi.Hypergraph(edgelist8)

fig3, ax3 = plt.subplots()
ax3, collections3 = xgi.draw_bipartite(H, ax=ax3)
node_coll3, edge_marker_coll3, dyad_coll3 = collections3
fig4, ax4 = plt.subplots()
ax4, collections4 = xgi.draw_bipartite(
H,
ax=ax4,
node_fc="red",
node_ec="blue",
node_lw=2,
node_size=20,
dyad_color="blue",
dyad_lw=2,
edge_marker_fc="red",
edge_marker_lw=2,
edge_marker_size=20,
)
node_coll4, edge_marker_coll4, dyad_coll4 = collections4

# number of elements
assert len(node_coll3.get_offsets()) == 7 # number of original nodes
assert len(edge_marker_coll3.get_offsets()) == 9 # number of original edges
assert len(dyad_coll3._paths) == 26 # number of lines

# # node face colors
assert np.all(node_coll3.get_facecolor() == np.array([[1, 1, 1, 1]])) # white
assert np.all(node_coll4.get_facecolor() == np.array([[1, 0, 0, 1]])) # red

# node edge colors
assert np.all(node_coll3.get_edgecolor() == np.array([[0, 0, 0, 1]])) # black
assert np.all(node_coll4.get_edgecolor() == np.array([[0, 0, 1, 1]])) # blue

# # node_lw
assert np.all(node_coll3.get_linewidth() == np.array([1]))
assert np.all(node_coll4.get_linewidth() == np.array([2]))

# # node_size
assert np.all(node_coll3.get_sizes() == np.array([7**2]))
assert np.all(node_coll4.get_sizes() == np.array([20**2]))

# # edge face colors
assert np.all(edge_marker_coll4.get_facecolor() == np.array([[1, 0, 0, 1]])) # red

# # edge _lw
assert np.all(edge_marker_coll3.get_linewidth() == np.array([1]))
assert np.all(edge_marker_coll4.get_linewidth() == np.array([2]))

# # edge_size
assert np.all(edge_marker_coll3.get_sizes() == np.array([7**2]))
assert np.all(edge_marker_coll4.get_sizes() == np.array([20**2]))

plt.close("all")

# test XGI ERROR raise
# test type
with pytest.raises(XGIError):
H = xgi.Hypergraph(edgelist8)
fig, ax3 = plt.subplots()
ax3 = xgi.draw_dihypergraph(H, ax=ax3)
plt.close()
xgi.draw_bipartite([0, 1, 2])

# test gca
fig3, ax = plt.subplots()
ax_gca, collections3 = xgi.draw_bipartite(H)
assert ax == ax_gca


def test_draw_dihypergraph_with_str_labels_and_isolated_nodes():
def test_draw_bipartite_with_str_labels_and_isolated_nodes():
DH1 = xgi.DiHypergraph()
DH1.add_nodes_from(["one", "two", "three", "four", "five", "six"])
DH1.add_edges_from(
Expand All @@ -617,12 +668,46 @@ def test_draw_dihypergraph_with_str_labels_and_isolated_nodes():
)

fig, ax4 = plt.subplots()
ax4, collections4 = xgi.draw_dihypergraph(DH1, ax=ax4)
node_coll4, phantom_node_coll4 = collections4
ax4, collections4 = xgi.draw_bipartite(DH1, ax=ax4)
node_coll4, edge_marker_coll4 = collections4
assert len(node_coll4.get_offsets()) == 6 # number of original nodes
assert len(phantom_node_coll4.get_offsets()) == 2 # number of original edges
assert len(edge_marker_coll4.get_offsets()) == 2 # number of original edges
assert len(ax4.patches) == 7 # number of lines
plt.close()
plt.close("all")


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

fig, ax = plt.subplots()
ax, dyad_collection = xgi.draw_undirected_dyads(H)
assert len(dyad_collection._paths) == 26 # number of lines

with pytest.raises(ValueError):
fig, ax = plt.subplots()
ax, dyad_collection = xgi.draw_undirected_dyads(H, dyad_lw=-1)

fig, ax = plt.subplots()
ax, dyad_collection = xgi.draw_undirected_dyads(
H, dyad_color=np.random.random(H.num_edges)
)
assert len(np.unique(dyad_collection.get_color())) == 28
plt.close("all")


def test_draw_directed_dyads(diedgelist1):
H = xgi.DiHypergraph(diedgelist1)

fig, ax = plt.subplots()
ax = xgi.draw_directed_dyads(H)

with pytest.raises(ValueError):
fig, ax = plt.subplots()
ax = xgi.draw_directed_dyads(H, dyad_lw=-1)

fig, ax = plt.subplots()
ax = xgi.draw_directed_dyads(H, dyad_color=np.random.random(H.num_edges))
plt.close("all")


def test_issue_499(edgelist8):
Expand All @@ -632,11 +717,11 @@ def test_issue_499(edgelist8):

with warnings.catch_warnings():
warnings.simplefilter("error")
ax, collections = xgi.draw(H, ax=ax, node_fc="black")
xgi.draw(H, ax=ax, node_fc="black")

with warnings.catch_warnings():
warnings.simplefilter("error")
ax, collections = xgi.draw(H, ax=ax, node_fc=["black"] * H.num_nodes)
xgi.draw(H, ax=ax, node_fc=["black"] * H.num_nodes)

plt.close("all")

Expand All @@ -646,12 +731,10 @@ def test_issue_515(edgelist8):

with warnings.catch_warnings():
warnings.simplefilter("error")
ax, (node_coll, edge_coll) = xgi.draw_multilayer(H, node_fc="black")
xgi.draw_multilayer(H, node_fc="black")

with warnings.catch_warnings():
warnings.simplefilter("error")
ax, (node_coll, edge_coll) = xgi.draw_multilayer(
H, node_fc=["black"] * H.num_nodes
)
xgi.draw_multilayer(H, node_fc=["black"] * H.num_nodes)

plt.close("all")
Loading
Loading