Skip to content

Commit

Permalink
Toggle legends on visualization via CLI or settings file (#43)
Browse files Browse the repository at this point in the history
* Adding --hide-legends switch

* Make CLI provided --hide-legends override configured "hide_legends" settting

* Better cli test coverage

* Test that a hide_legends_override works

* CLI tested with actual testfile
  • Loading branch information
dalager authored Nov 9, 2024
1 parent 14bb44f commit 8fd40f8
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 4 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ options:
Specify the output path for the generated
graph image.
--open-image Open the generated image after visualization.
--hide-legends Do not show legends in the visualization. (Default: False)
```

## Sample output
Expand Down
8 changes: 8 additions & 0 deletions docs/sample_settingsfile.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"node_size": 40,
"edge_color": "blue",
"with_labels": true,
"font_size": 12,
"alpha": 0.6,
"hide_legends": false
}
17 changes: 15 additions & 2 deletions src/graphedexcel/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ def parse_arguments():
"--open-image",
action="store_true",
help="Open the generated image after visualization.",
default=False,
)

parser.add_argument(
"--hide-legends",
"-hl",
action="store_true",
help="Do not show legends in the visualization.",
default=None,
)

return parser.parse_args()
Expand All @@ -76,6 +85,8 @@ def main():
# Check if the file exists
if not os.path.exists(path_to_excel):
logger.error(f"File not found: {path_to_excel}")
print(f"File not found: {path_to_excel}", file=sys.stderr)

sys.exit(1)

# Build the dependency graph and gather statistics
Expand Down Expand Up @@ -108,10 +119,12 @@ def main():
filename = f"{base_name}_dependency_graph.png"

# Visualize the dependency graph
visualize_dependency_graph(dependency_graph, filename, config_path, layout)
visualize_dependency_graph(
dependency_graph, filename, config_path, layout, args.hide_legends
)

logger.info(f"Dependency graph image saved to {filename}.")

print(f"Dependency graph image saved to {filename}.")
# Open the image file if requested
if args.open_image:
try:
Expand Down
9 changes: 8 additions & 1 deletion src/graphedexcel/graph_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"with_labels": False, # whether to show the node labels
"font_size": 10, # the size of the node labels
"cmap": "tab20b", # the color map to use for coloring nodes
"hide_legends": False, # whether to show the legend
}

# Sized-based settings for small, medium, and large graphs
Expand Down Expand Up @@ -146,6 +147,7 @@ def visualize_dependency_graph(
output_path: str = None,
config_path: str = None,
layout: str = "spring",
hide_legends_override: bool = None,
):
"""
Render the dependency graph using matplotlib and networkx.
Expand All @@ -158,6 +160,10 @@ def visualize_dependency_graph(
f"Using the following settings for the graph visualization: {graph_settings}"
)

hide_legends = graph_settings.pop("hide_legends")
if hide_legends_override is not None:
hide_legends = hide_legends_override

fig_size = calculate_fig_size(len(graph.nodes))
logger.info(f"Calculated figure size: {fig_size}")

Expand Down Expand Up @@ -195,7 +201,8 @@ def visualize_dependency_graph(
**graph_settings,
)

plt.legend(handles=legend_patches, title="Sheets", loc="upper left")
if not hide_legends:
plt.legend(handles=legend_patches, title="Sheets", loc="upper left")

plt.savefig(output_path, bbox_inches="tight")
plt.close() # Close the figure to free memory
68 changes: 67 additions & 1 deletion tests/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import os
import tempfile
from openpyxl import Workbook
import pytest
import sys

Expand All @@ -6,6 +9,61 @@
from unittest.mock import patch, MagicMock


@pytest.fixture
def create_excel_file(tmp_path):
def _create_excel_file(data):
file_path = tmp_path / "test.xlsx"
wb = Workbook()
for sheet_name, sheet_data in data.items():
ws = wb.create_sheet(title=sheet_name)
for row in sheet_data:
ws.append(row)
wb.save(file_path)
return file_path

return _create_excel_file


# test cli.main
def test_main():
# assert that main with no arguments raises SystemExit error
test_args = ["graphedexcel"]
with patch("sys.argv", test_args):
with pytest.raises(SystemExit):
main()


def test_main_with_nonexistent_file(capsys):
test_args = ["graphedexcel", "nonexistent_file.xlsx"]
with patch("sys.argv", test_args):
with pytest.raises(SystemExit) as exc_info:
main()
assert exc_info.value.code != 0
captured = capsys.readouterr()
assert "File not found:" in captured.err


def test_main_with_test_xlsx_file(capsys):
"""Test main with a test excel file to exercise the cli module"""
with tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") as tmp_file:
test_file_path = tmp_file.name
try:
wb = Workbook()
ws = wb.active
ws["A1"] = "Test Data"

wb.save(test_file_path)
wb.close()
test_args = ["graphedexcel", test_file_path]
with patch("sys.argv", test_args):
main()
finally:
print("here")
wb.close()
captured = capsys.readouterr()
assert "Dependency graph image saved" in captured.out


def test_parse_arguments_required(monkeypatch):
"""
Test that the required positional argument is parsed correctly.
Expand All @@ -20,12 +78,19 @@ def test_parse_arguments_optional_flags():
"""
Test that optional flags are parsed correctly.
"""
test_args = ["graphedexcel", "test.xlsx", "--as-directed-graph", "--no-visualize"]
test_args = [
"graphedexcel",
"test.xlsx",
"--as-directed-graph",
"--no-visualize",
"--hide-legends",
]
with patch("sys.argv", test_args):
args = parse_arguments()
assert args.path_to_excel == "test.xlsx"
assert args.as_directed_graph is True
assert args.no_visualize is True
assert args.hide_legends is True


def test_parse_arguments_optional_arguments():
Expand Down Expand Up @@ -65,6 +130,7 @@ def test_parse_arguments_default_values():
assert args.as_directed_graph is False
assert args.no_visualize is False
assert args.open_image is False
assert args.hide_legends is None


def test_parse_arguments_invalid():
Expand Down
7 changes: 7 additions & 0 deletions tests/test_graph_visualizer.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,13 @@ def test_all_layouts():
visualize_dependency_graph(G, layout=layout, output_path=layout + "_layout.png")


def test_provided_hide_legends_override():
G = create_two_node_graph()
visualize_dependency_graph(
G, hide_legends_override=True, output_path="hide_legends.png"
)


def test_unknown_layout_will_fallback():
G = create_two_node_graph()
visualize_dependency_graph(G, layout="nosuchlayout", output_path="nosuchlayout.png")
Expand Down

0 comments on commit 8fd40f8

Please sign in to comment.