diff --git a/CHANGELOG.md b/CHANGELOG.md index 66c4c295..08918917 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +4,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## Unreleased ### Work In Progress - Node: WeightedNode for weighted edge tree implementation. +## [0.3.1] - 2022-11-07 +### Added +- Tree Exporter and DAG Exporter: More customizations for Node to dot and DAGNode to dot. + ## [0.3.0] - 2022-11-05 ### Added - DAG Constructors: From list, nested dictionary, pandas DataFrame. @@ -31,7 +35,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Utility Iterators: Tree traversal methods. - Workflow To Do App: Tree use case with to-do list implementation. -[Unreleased]: https://github.com/kayjan/bigtree/compare/HEAD...v0.2.0 +[0.3.1]: https://github.com/kayjan/bigtree/compare/v0.3.0...v0.3.1 [0.3.0]: https://github.com/kayjan/bigtree/compare/v0.2.0...v0.3.0 [0.2.0]: https://github.com/kayjan/bigtree/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/kayjan/bigtree/releases/tag/v0.1.0 diff --git a/README.md b/README.md index 7d21bd9a..99453671 100644 --- a/README.md +++ b/README.md @@ -623,8 +623,8 @@ tree_to_dataframe( # 3 /a/b/e e b 35 # 4 /a/c c a 60 -graph = tree_to_dot(root, node_colour="gold") -graph.write_png("demo.png") +graph = tree_to_dot(root, rankdir="BT", node_colour="gold") +graph.write_png("assets/demo.png") ``` ![Sample Tree Output](../../assets/demo.png) diff --git a/assets/demo.png b/assets/demo.png index 5de006d1..b037df4a 100644 Binary files a/assets/demo.png and b/assets/demo.png differ diff --git a/bigtree/__init__.py b/bigtree/__init__.py index 0d1b7ea9..d01c1344 100644 --- a/bigtree/__init__.py +++ b/bigtree/__init__.py @@ -1,4 +1,4 @@ -__version__ = "0.3.0" +__version__ = "0.3.1" from bigtree.dag.construct import dataframe_to_dag, dict_to_dag, list_to_dag from bigtree.dag.export import dag_to_dot diff --git a/bigtree/dag/export.py b/bigtree/dag/export.py index 6fabd21e..51c8eece 100644 --- a/bigtree/dag/export.py +++ b/bigtree/dag/export.py @@ -6,6 +6,7 @@ def dag_to_dot( dag: DAGNode, + rankdir: str = "TB", bgcolor: str = None, node_colour: str = None, edge_colour: str = None, @@ -35,6 +36,7 @@ def dag_to_dot( Args: dag (DAGNode): tree to be exported + rankdir (str): set direction of graph layout, defaults to 'TB', can be 'BT, 'LR', 'RL' bgcolor (str): background color of image, defaults to None node_colour (str): fill colour of nodes, defaults to None edge_colour (str): colour of edges, defaults to None @@ -71,7 +73,9 @@ def dag_to_dot( dag = dag.copy() - _graph = pydot.Dot(graph_type="digraph", strict=True, **graph_style) + _graph = pydot.Dot( + graph_type="digraph", strict=True, rankdir=rankdir, **graph_style + ) for parent_node, child_node in dag_iterator(dag): parent_name = parent_node.name diff --git a/bigtree/tree/export.py b/bigtree/tree/export.py index d8c9fd64..90c59da8 100644 --- a/bigtree/tree/export.py +++ b/bigtree/tree/export.py @@ -568,6 +568,7 @@ def recursive_append(node, parent_dict): def tree_to_dot( tree: Node, directed: bool = True, + rankdir: str = "TB", bgcolor: str = None, node_colour: str = None, edge_colour: str = None, @@ -598,6 +599,7 @@ def tree_to_dot( Args: tree (Node): tree to be exported directed (bool): indicator whether graph should be directed or undirected, defaults to True + rankdir (str): set direction of graph layout, defaults to 'TB', can be 'BT, 'LR', 'RL' bgcolor (str): background color of image, defaults to None node_colour (str): fill colour of nodes, defaults to None edge_colour (str): colour of edges, defaults to None @@ -635,9 +637,13 @@ def tree_to_dot( tree = tree.copy() if directed: - _graph = pydot.Dot(graph_type="digraph", strict=True, **graph_style) + _graph = pydot.Dot( + graph_type="digraph", strict=True, rankdir=rankdir, **graph_style + ) else: - _graph = pydot.Dot(graph_type="graph", strict=True, **graph_style) + _graph = pydot.Dot( + graph_type="graph", strict=True, rankdir=rankdir, **graph_style + ) def recursive_create_node_and_edges(parent_name, child_node): child_name = child_node.node_name diff --git a/tests/tree/test_export.py b/tests/tree/test_export.py index 6df38a53..d6018f2d 100644 --- a/tests/tree/test_export.py +++ b/tests/tree/test_export.py @@ -695,7 +695,10 @@ def test_tree_to_dot(tree_node): expected = """strict digraph G {\na [label=a];\nb [label=b];\nb -> a;\nd [label=d];\nd -> b;\ne [label=e];\ne -> b;\ng [label=g];\ng -> e;\nh [label=h];\nh -> e;\nc [label=c];\nc -> a;\nf [label=f];\nf -> c;\n}\n""" actual = graph.to_string() graph.write_png("tests/tree.png") - assert expected == actual, "Graph string is wrong" + for expected_str in expected.split(): + assert ( + expected_str in actual + ), f"Expected {expected_str} not in actual string" @staticmethod def test_tree_to_dot_type_error(dag_node): @@ -709,7 +712,10 @@ def test_tree_to_dot_directed(tree_node): expected = """strict graph G {\na [label=a];\nb [label=b];\nb -- a;\nd [label=d];\nd -- b;\ne [label=e];\ne -- b;\ng [label=g];\ng -- e;\nh [label=h];\nh -- e;\nc [label=c];\nc -- a;\nf [label=f];\nf -- c;\n}\n""" actual = graph.to_string() graph.write_png("tests/tree_undirected.png") - assert expected == actual, "Graph string is wrong" + for expected_str in expected.split(): + assert ( + expected_str in actual + ), f"Expected {expected_str} not in actual string" @staticmethod def test_tree_to_dot_bg_color(tree_node): @@ -717,7 +723,10 @@ def test_tree_to_dot_bg_color(tree_node): expected = """strict digraph G {\nbgcolor=blue;\na [label=a];\nb [label=b];\nb -> a;\nd [label=d];\nd -> b;\ne [label=e];\ne -> b;\ng [label=g];\ng -> e;\nh [label=h];\nh -> e;\nc [label=c];\nc -> a;\nf [label=f];\nf -> c;\n}\n""" actual = graph.to_string() graph.write_png("tests/tree_bg.png") - assert expected == actual, "Graph string is wrong" + for expected_str in expected.split(): + assert ( + expected_str in actual + ), f"Expected {expected_str} not in actual string" @staticmethod def test_tree_to_dot_fill_color(tree_node): @@ -725,7 +734,10 @@ def test_tree_to_dot_fill_color(tree_node): expected = """strict digraph G {\na [fillcolor=gold, label=a, style=filled];\nb [fillcolor=gold, label=b, style=filled];\nb -> a;\nd [fillcolor=gold, label=d, style=filled];\nd -> b;\ne [fillcolor=gold, label=e, style=filled];\ne -> b;\ng [fillcolor=gold, label=g, style=filled];\ng -> e;\nh [fillcolor=gold, label=h, style=filled];\nh -> e;\nc [fillcolor=gold, label=c, style=filled];\nc -> a;\nf [fillcolor=gold, label=f, style=filled];\nf -> c;\n}\n""" actual = graph.to_string() graph.write_png("tests/tree_fill.png") - assert expected == actual, "Graph string is wrong" + for expected_str in expected.split(): + assert ( + expected_str in actual + ), f"Expected {expected_str} not in actual string" @staticmethod def test_tree_to_dot_edge_colour(tree_node): @@ -733,7 +745,10 @@ def test_tree_to_dot_edge_colour(tree_node): expected = """strict digraph G {\na [label=a];\nb [label=b];\nb -> a [color=red];\nd [label=d];\nd -> b [color=red];\ne [label=e];\ne -> b [color=red];\ng [label=g];\ng -> e [color=red];\nh [label=h];\nh -> e [color=red];\nc [label=c];\nc -> a [color=red];\nf [label=f];\nf -> c [color=red];\n}\n""" actual = graph.to_string() graph.write_png("tests/tree_edge.png") - assert expected == actual, "Graph string is wrong" + for expected_str in expected.split(): + assert ( + expected_str in actual + ), f"Expected {expected_str} not in actual string" @staticmethod def test_tree_to_dot_node_attr(tree_node_style): @@ -741,4 +756,7 @@ def test_tree_to_dot_node_attr(tree_node_style): expected = """strict digraph G {\na [fillcolor=gold, label=a, style=filled];\nb [fillcolor=blue, label=b, style=filled];\nb -> a;\nd [fillcolor=green, label=d, style=filled];\nd -> b;\ng [fillcolor=red, label=g, style=filled];\ng -> d;\ne [fillcolor=green, label=e, style=filled];\ne -> b;\nh [fillcolor=red, label=h, style=filled];\nh -> e;\nc [fillcolor=blue, label=c, style=filled];\nc -> a;\nf [fillcolor=green, label=f, style=filled];\nf -> c;\n}\n""" actual = graph.to_string() graph.write_png("tests/tree_style.png") - assert expected == actual, "Graph string is wrong" + for expected_str in expected.split(): + assert ( + expected_str in actual + ), f"Expected {expected_str} not in actual string"