Skip to content

Commit

Permalink
new feature: visualization supports edge coloring (#1160)
Browse files Browse the repository at this point in the history
* edge score plotting: general setup and cytoscape d

* 3d plotly, custom color palette

* add tests, wip 2d plotly

* Add support 2D Plotly rendering with edge scores.

* Use numpy arrays for edge tuple generation.

* Detect arbitrary ordering of edge tuples for edge scores.

* Add test for wrong inputs, fix missing dependencies on CI.

* Add helper method _getEdgeScore + format file with black formatter.

* Revise function for edgescore retrieval.

* Remove break from testing loop.

* CI: Add vizbridges packages for Python tests for coverage, Windows builds.

* Python: Rewrite testVizNodePartition test to use deterministic partitions.

---------

Co-authored-by: fabratu <fabbrandt@gmail.com>
  • Loading branch information
bernlu and fabratu committed Apr 6, 2024
1 parent b99805a commit fcb14f2
Show file tree
Hide file tree
Showing 5 changed files with 741 additions and 320 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Expand Up @@ -412,6 +412,7 @@ jobs:
pip install -e .
python -c 'import networkit'
pip install -r requirements.txt
pip install ipycytoscape seaborn plotly
python -m unittest discover -v networkit/test/
pip install ipycytoscape plotly seaborn
python notebooks/test_notebooks.py "notebooks"
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/scripts/core_sanitizers_coverage.sh
Expand Up @@ -2,7 +2,7 @@

python3 -m venv pyenv && . pyenv/bin/activate
pip3 install --upgrade pip
pip3 install coveralls cython gcovr matplotlib requests setuptools tabulate numpy networkx
pip3 install coveralls cython gcovr matplotlib requests setuptools tabulate numpy networkx ipycytoscape seaborn plotly

mkdir core_build && cd "$_"
export CPU_COUNT=$(python3 $GITHUB_WORKSPACE/.github/workflows/scripts/get_core_count.py)
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/scripts/full.sh
Expand Up @@ -39,6 +39,7 @@ NETWORKIT_PARALLEL_JOBS=$CPU_COUNT pip3 install -e .
python3 -c 'import networkit'

pip3 install -r requirements.txt
pip3 install plotly ipycytoscape seaborn matplotlib

python3 -m unittest discover -v networkit/test/

Expand Down
188 changes: 188 additions & 0 deletions networkit/test/test_vizbridges.py
@@ -0,0 +1,188 @@
#!/usr/bin/env python3

import networkit as nk
from networkit import vizbridges
import unittest
from collections import defaultdict


class TestVizbridges(unittest.TestCase):
def getSmallGraph(self, weighted=False, directed=False):
G = nk.Graph(4, weighted, directed)
G.addEdge(0, 1, 1.0)
G.addEdge(0, 2, 2.0)
G.addEdge(3, 1, 4.0)
G.addEdge(3, 2, 5.0)
G.addEdge(1, 2, 3.0)
if directed:
G.addEdge(2, 1, 6.0)

return G

def testVizNodeScores(self):
for dim, directed, weighted in zip(
vizbridges.Dimension, [True, False], [True, False]
):
with self.subTest(dim=dim, directed=directed, weighted=weighted):
G = self.getSmallGraph(weighted, directed)
vizbridges.widgetFromGraph(
G,
dimension=dim,
nodeScores=list(range(G.numberOfNodes())),
)

def testVizNodePartition(self):
for dim, directed, weighted in zip(
vizbridges.Dimension, [True, False], [True, False]
):
with self.subTest(dim=dim, directed=directed, weighted=weighted):
G = self.getSmallGraph(weighted, directed)
partition = nk.Partition(G.numberOfNodes())
partition.allToSingletons()
vizbridges.widgetFromGraph(
G,
dimension=dim,
nodePartition=partition,
)

def testVizNodePalette(self):
for dim, directed, weighted in zip(
vizbridges.Dimension, [True, False], [True, False]
):
with self.subTest(dim=dim, directed=directed, weighted=weighted):
G = self.getSmallGraph(weighted, directed)
vizbridges.widgetFromGraph(
G,
dimension=dim,
nodeScores=list(range(G.numberOfNodes())),
nodePalette=[(i / 10, i / 10, i / 10) for i in range(4)],
)

def testVizShowIds(self):
for dim, directed, weighted, show in zip(
vizbridges.Dimension, [True, False], [True, False], [True, False]
):
with self.subTest(dim=dim, directed=directed, weighted=weighted, show=show):
G = self.getSmallGraph(weighted, directed)
vizbridges.widgetFromGraph(
G,
dimension=dim,
nodeScores=list(range(G.numberOfNodes())),
showIds=show,
)

def testVizCustomSize(self):
for dim, directed, weighted in zip(
vizbridges.Dimension, [True, False], [True, False]
):
with self.subTest(dim=dim, directed=directed, weighted=weighted):
G = self.getSmallGraph(weighted, directed)
vizbridges.widgetFromGraph(
G,
dimension=dim,
nodeScores=list(range(G.numberOfNodes())),
customSize=50,
)

def testVizEdgeScoresList(self):
for dim, directed, weighted in zip(
vizbridges.Dimension, [True, False], [True, False]
):
with self.subTest(dim=dim, directed=directed, weighted=weighted):
G = self.getSmallGraph(weighted, directed)
G.indexEdges()
vizbridges.widgetFromGraph(
G,
dimension=dim,
edgeScores=list(range(G.numberOfEdges())),
)

def testVizEdgeScoresDict(self):
for dim, directed, weighted in zip(
vizbridges.Dimension, [True, False], [True, False]
):
with self.subTest(dim=dim, directed=directed, weighted=weighted):
G = self.getSmallGraph(weighted, directed)
G.indexEdges()
scores = {}
for i, [u, v] in enumerate(G.iterEdges()):
scores[u, v] = i
vizbridges.widgetFromGraph(
G,
dimension=dim,
edgeScores=scores,
)

def testVizEdgeScoresUndirectedReversed(self):
for dim, weighted in zip(
vizbridges.Dimension, [True, False]
):
with self.subTest(dim=dim, weighted=weighted):
G = self.getSmallGraph(weighted, False)
G.indexEdges()
scores = {}
for i, [u, v] in enumerate(G.iterEdges()):
scores[v, u] = i
vizbridges.widgetFromGraph(
G,
dimension=dim,
edgeScores=scores,
)

def testVizEdgeScoresDefaultdict(self):
for dim, directed, weighted in zip(
vizbridges.Dimension, [True, False], [True, False]
):
with self.subTest(dim=dim, directed=directed, weighted=weighted):
G = self.getSmallGraph(weighted, directed)
G.indexEdges()
scores = defaultdict(lambda: 0)
for u, v in G.iterEdges():
scores[u, v] = 1
vizbridges.widgetFromGraph(
G,
dimension=dim,
edgeScores=scores,
)

def testVizEdgePalette(self):
for dim, directed, weighted in zip(
vizbridges.Dimension, [True, False], [True, False]
):
with self.subTest(dim=dim, directed=directed, weighted=weighted):
G = self.getSmallGraph(weighted, directed)
G.indexEdges()
vizbridges.widgetFromGraph(
G,
dimension=dim,
edgeScores=list(range(G.numberOfEdges())),
edgePalette=[(i / 10, i / 10, i / 10) for i in range(4)],
)

def testNodeScoreAndPartitionExclusive(self):
G = self.getSmallGraph(False, False)
partition = nk.community.ClusteringGenerator(G).makeRandomClustering(G, 3)
with self.assertRaises(Exception):
vizbridges.widgetFromGraph(
G, nodeScores=list(range(G.numberOfNodes())), nodePalette=partition
)

def testCompleteNodeScores(self):
G = self.getSmallGraph(False, False)
with self.assertRaises(Exception):
vizbridges.widgetFromGraph(G, nodeScores=list(range(G.numberOfNodes() - 1)))

def testCompleteEdgeScores(self):
G = self.getSmallGraph(False, False)
with self.assertRaises(Exception):
vizbridges.widgetFromGraph(G, edgeScores=list(range(G.numberOfEdges() - 1)))

def testNoneInEdgeScores(self):
G = self.getSmallGraph(False, False)
scores = [None] * G.numberOfEdges()
with self.assertRaises(Exception):
vizbridges.widgetFromGraph(G, edgeScores=scores)


if __name__ == "__main__":
unittest.main()

0 comments on commit fcb14f2

Please sign in to comment.