-
Notifications
You must be signed in to change notification settings - Fork 17
Code refactor: Refactor code and create custom class for node, edge, graph, animation etc, #18
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
base: master
Are you sure you want to change the base?
Changes from all commits
dce39ef
fc3baec
a580179
fee7fb9
6a6312c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from visualiser import Edge, Node | ||
|
||
""" | ||
Test case class for nodes | ||
""" | ||
|
||
|
||
class TestEdge: | ||
def test_create_new_edge(self): | ||
# Create Node | ||
A = Node('A', color='red', style='filled') | ||
B = Node('B', label='B label', color='red', style='filled') | ||
|
||
# Create Edge | ||
edge = Edge(A, B, label='Test Label') | ||
|
||
assert edge.label == 'Test Label' | ||
assert edge.source_node.label == 'A' | ||
assert edge.source_node == A |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,35 @@ | ||||||||||||||||||||||||||||||||||
from visualiser import Node, Edge, Graph | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
class TestGraph: | ||||||||||||||||||||||||||||||||||
def test_normal_graph_flow(self): | ||||||||||||||||||||||||||||||||||
graph = Graph('hello') | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
# Create Node | ||||||||||||||||||||||||||||||||||
A = Node('A', color='red') | ||||||||||||||||||||||||||||||||||
B = Node('B', color='green') | ||||||||||||||||||||||||||||||||||
C = Node('C', color='yellow') | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
# Add node | ||||||||||||||||||||||||||||||||||
graph.add_node(A) | ||||||||||||||||||||||||||||||||||
graph.add_node(B) | ||||||||||||||||||||||||||||||||||
graph.add_node(C) | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
# Make edge | ||||||||||||||||||||||||||||||||||
edge1 = Edge(A, B) | ||||||||||||||||||||||||||||||||||
edge2 = Edge(A, C) | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
# Add node and graph to edge | ||||||||||||||||||||||||||||||||||
graph.add_edge(edge1) | ||||||||||||||||||||||||||||||||||
graph.add_edge(edge2) | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
assert graph.to_string() == 'digraph G {\nA [label="A", color="red"];\nB [label="B", color="green"];\nC [label="C", color="yellow"];\nA -> B [];\nA -> C [];\n}' | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
def test_node_methods(self): | ||||||||||||||||||||||||||||||||||
pass | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
def test_edge_methods(self): | ||||||||||||||||||||||||||||||||||
pass | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
def test_mutations(self): | ||||||||||||||||||||||||||||||||||
pass | ||||||||||||||||||||||||||||||||||
Comment on lines
+28
to
+35
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Consider adding test cases for the test_node_methods, test_edge_methods, and test_mutations methods. Currently, these methods are empty and do not test anything.
Suggested change
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
from visualiser import * | ||
|
||
""" | ||
Test case class for nodes | ||
""" | ||
|
||
|
||
class TestNode: | ||
def test_create_new_node(self): | ||
node = Node('bishal', 'Bishal label', color='red', style='filled') | ||
|
||
assert node.label == 'Bishal label' | ||
assert node.name == 'bishal' | ||
assert node.get_attribute('color') == 'red' | ||
assert node.get_attribute('style') == 'filled' | ||
assert node.to_string() == 'bishal [label="Bishal label", color="red", style="filled"];' | ||
|
||
def test_set_attributes(self): | ||
node = Node('bishal', 'Bishal label', color='red', style='filled') | ||
assert node.get_attribute('color') == 'red' | ||
node.set_attribute('color', 'green') | ||
assert node.get_attribute('color') == 'green' | ||
node.set_attribute('background', 'grey') | ||
assert node.get_attribute('background') == 'grey' | ||
assert node.to_string() == 'bishal [label="Bishal label", color="green", style="filled", background="grey"];' | ||
|
||
def test_rename_name_label(self): | ||
node = Node('bishal', 'Bishal label', color='red', style='filled') | ||
|
||
node.set_attribute('color', 'green') | ||
|
||
assert node.label == 'Bishal label' | ||
node.label = 'Bishal renamed label' | ||
assert node.label == 'Bishal renamed label' | ||
|
||
assert node.name == 'bishal' | ||
node.name = 'Bishal renamed' | ||
assert node.name == 'Bishal renamed' | ||
|
||
assert node.to_string() == 'Bishal renamed [label="Bishal renamed label", color="green", style="filled"];' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,11 @@ | ||
|
||
# Version of the recursion-visualiser | ||
__version__ = "1.0.1" | ||
__version__ = "1.0.1" | ||
|
||
# Maintain this order to avoid circular import | ||
# because we are using Node inside Edge for type annotations. | ||
from .node import Node | ||
from .edge import Edge | ||
from .graph import Graph | ||
from .animation import Animation | ||
|
||
from visualiser import * |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,68 @@ | ||||||||||||||
import copy | ||||||||||||||
|
||||||||||||||
import pydot | ||||||||||||||
import glob | ||||||||||||||
import os | ||||||||||||||
import shutil | ||||||||||||||
import imageio | ||||||||||||||
|
||||||||||||||
from visualiser import Node, Edge, Graph | ||||||||||||||
|
||||||||||||||
|
||||||||||||||
class Animation: | ||||||||||||||
def __init__(self): | ||||||||||||||
self.frames = [] | ||||||||||||||
self._frame_id = 0 | ||||||||||||||
|
||||||||||||||
def next_step(self, frame): | ||||||||||||||
self.frames.append(copy.deepcopy(frame)) | ||||||||||||||
self._frame_id += 1 | ||||||||||||||
|
||||||||||||||
@staticmethod | ||||||||||||||
def make_directory(): | ||||||||||||||
if not os.path.exists("frames"): | ||||||||||||||
os.makedirs("frames") | ||||||||||||||
|
||||||||||||||
def next_frame(self): | ||||||||||||||
pass | ||||||||||||||
|
||||||||||||||
def previous_frame(self): | ||||||||||||||
pass | ||||||||||||||
|
||||||||||||||
def get_frame(self, frame_id): | ||||||||||||||
return self.frames[frame_id] | ||||||||||||||
Comment on lines
+32
to
+33
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Consider adding error handling for when the frame_id is not of type int. This will prevent the program from crashing when an invalid type is passed.
Suggested change
|
||||||||||||||
|
||||||||||||||
def get_frames(self): | ||||||||||||||
return self.frames[::] | ||||||||||||||
|
||||||||||||||
def write_frame(self, frame_id): | ||||||||||||||
frame = self.get_frame(frame_id) | ||||||||||||||
|
||||||||||||||
dot_graph, *rest = pydot.graph_from_dot_data(frame) | ||||||||||||||
dot_graph.write_png(f"frames/temp_{frame_id}.png") | ||||||||||||||
|
||||||||||||||
def write_file(self): | ||||||||||||||
self.make_directory() | ||||||||||||||
|
||||||||||||||
for frame_id in range(len(self.get_frames())): | ||||||||||||||
self.write_frame(frame_id) | ||||||||||||||
|
||||||||||||||
def write_gif(self, name="out.gif", delay=3): | ||||||||||||||
self.write_file() | ||||||||||||||
|
||||||||||||||
images = [] | ||||||||||||||
|
||||||||||||||
# sort frames images in ascending order to number in image filename | ||||||||||||||
# image filename: frames/temp_1.png | ||||||||||||||
sorted_images = sorted( | ||||||||||||||
glob.glob("frames/*.png"), | ||||||||||||||
key=lambda fn: int(fn.split("_")[1].split(".")[0]) | ||||||||||||||
) | ||||||||||||||
|
||||||||||||||
for filename in sorted_images: | ||||||||||||||
images.append(imageio.imread(filename)) | ||||||||||||||
|
||||||||||||||
imageio.mimsave(name, images, duration=delay) | ||||||||||||||
# Delete temporary directory | ||||||||||||||
shutil.rmtree("frames") | ||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,123 @@ | ||||||||||||||||||||||||||
import copy | ||||||||||||||||||||||||||
from typing import Union | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
from visualiser import Node | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
class Edge: | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def __init__(self, source_node: Union[Node, str], destination_node: Union[Node, str], label: str = '', | ||||||||||||||||||||||||||
**attrs: str) -> None: | ||||||||||||||||||||||||||
self._source_node = Node(source_node) if isinstance(source_node, str) else copy.deepcopy(source_node) | ||||||||||||||||||||||||||
self._destination_node = Node(destination_node) if isinstance(destination_node, str) else copy.deepcopy( | ||||||||||||||||||||||||||
destination_node) | ||||||||||||||||||||||||||
Comment on lines
+9
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Consider adding error handling for when the source_node or destination_node is not of type Node or str. This will prevent the program from crashing when an invalid type is passed.
Suggested change
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
self._name = f"{self._source_node.name} -> {self._destination_node.name}" | ||||||||||||||||||||||||||
self._label = label | ||||||||||||||||||||||||||
self._attrs = attrs | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@property | ||||||||||||||||||||||||||
def label(self) -> str: | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
Get label for edge. | ||||||||||||||||||||||||||
:return: str | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
return self._label | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@label.setter | ||||||||||||||||||||||||||
def label(self, _label: str) -> None: | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
Set label for edge. | ||||||||||||||||||||||||||
:param _label: str | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
self._label = _label | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@property | ||||||||||||||||||||||||||
def name(self) -> str: | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
Get name for edge. | ||||||||||||||||||||||||||
:return: str | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
return self._name | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@name.setter | ||||||||||||||||||||||||||
def name(self, _name: str) -> None: | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
Set label for edge. | ||||||||||||||||||||||||||
:param _name: str | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
self._name = _name | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@property | ||||||||||||||||||||||||||
def source_node(self) -> Node: | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
Get source node | ||||||||||||||||||||||||||
:return: Node | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
return self._source_node | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@source_node.setter | ||||||||||||||||||||||||||
def source_node(self, _source_node: Node) -> None: | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
Set source node. | ||||||||||||||||||||||||||
:param _source_node: Node | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
self._source_node = _source_node | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@property | ||||||||||||||||||||||||||
def destination_node(self) -> Node: | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
Get destination node. | ||||||||||||||||||||||||||
:return: Node | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
return self._destination_node | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
@destination_node.setter | ||||||||||||||||||||||||||
def destination_node(self, _destination_node: Node) -> None: | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
Sets destination node. | ||||||||||||||||||||||||||
:param _destination_node: Node | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
self._destination_node = _destination_node | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def get_attribute(self, key: str) -> str: | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
Get attribute for edge. | ||||||||||||||||||||||||||
:param key: str | ||||||||||||||||||||||||||
:return: The value of attribute with key | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
return self._attrs.get(key) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def set_attribute(self, key: str, value: str) -> None: | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
Set attribute for edge | ||||||||||||||||||||||||||
:param key: str | ||||||||||||||||||||||||||
:param value: str | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
self._attrs[key] = value | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def remove_attribute(self, key: str) -> None: | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
Remove attribute from edge. | ||||||||||||||||||||||||||
:param key: str | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
if self._attrs.get(key): | ||||||||||||||||||||||||||
del self._attrs[key] | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def get_attributes_string(self) -> str: | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
Get attributes string enclosed in [] | ||||||||||||||||||||||||||
:return: | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
if len(self._label) == 0: | ||||||||||||||||||||||||||
return '[' + ', '.join([f'{key}="{value}"' for key, value in self._attrs.items()]) + '];' | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
return '[' + f'label="{self._label}", ' + ', '.join( | ||||||||||||||||||||||||||
[f'{key}="{value}"' for key, value in self._attrs.items()]) + '];' | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def to_string(self) -> str: | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
Converts dot string equivalent of the current edge. | ||||||||||||||||||||||||||
:return: Str | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
return f'{self._source_node.name} -> {self._destination_node.name} {self.get_attributes_string()}' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggestion: Consider adding more assertions to the test case to ensure that all properties of the Edge class are functioning as expected.