Skip to content

Commit 8fd40f8

Browse files
authored
Toggle legends on visualization via CLI or settings file (#43)
* 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
1 parent 14bb44f commit 8fd40f8

File tree

6 files changed

+106
-4
lines changed

6 files changed

+106
-4
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ options:
9494
Specify the output path for the generated
9595
graph image.
9696
--open-image Open the generated image after visualization.
97+
--hide-legends Do not show legends in the visualization. (Default: False)
9798
```
9899

99100
## Sample output

docs/sample_settingsfile.json

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"node_size": 40,
3+
"edge_color": "blue",
4+
"with_labels": true,
5+
"font_size": 12,
6+
"alpha": 0.6,
7+
"hide_legends": false
8+
}

src/graphedexcel/cli.py

+15-2
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,15 @@ def parse_arguments():
6363
"--open-image",
6464
action="store_true",
6565
help="Open the generated image after visualization.",
66+
default=False,
67+
)
68+
69+
parser.add_argument(
70+
"--hide-legends",
71+
"-hl",
72+
action="store_true",
73+
help="Do not show legends in the visualization.",
74+
default=None,
6675
)
6776

6877
return parser.parse_args()
@@ -76,6 +85,8 @@ def main():
7685
# Check if the file exists
7786
if not os.path.exists(path_to_excel):
7887
logger.error(f"File not found: {path_to_excel}")
88+
print(f"File not found: {path_to_excel}", file=sys.stderr)
89+
7990
sys.exit(1)
8091

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

110121
# Visualize the dependency graph
111-
visualize_dependency_graph(dependency_graph, filename, config_path, layout)
122+
visualize_dependency_graph(
123+
dependency_graph, filename, config_path, layout, args.hide_legends
124+
)
112125

113126
logger.info(f"Dependency graph image saved to {filename}.")
114-
127+
print(f"Dependency graph image saved to {filename}.")
115128
# Open the image file if requested
116129
if args.open_image:
117130
try:

src/graphedexcel/graph_visualizer.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"with_labels": False, # whether to show the node labels
2121
"font_size": 10, # the size of the node labels
2222
"cmap": "tab20b", # the color map to use for coloring nodes
23+
"hide_legends": False, # whether to show the legend
2324
}
2425

2526
# Sized-based settings for small, medium, and large graphs
@@ -146,6 +147,7 @@ def visualize_dependency_graph(
146147
output_path: str = None,
147148
config_path: str = None,
148149
layout: str = "spring",
150+
hide_legends_override: bool = None,
149151
):
150152
"""
151153
Render the dependency graph using matplotlib and networkx.
@@ -158,6 +160,10 @@ def visualize_dependency_graph(
158160
f"Using the following settings for the graph visualization: {graph_settings}"
159161
)
160162

163+
hide_legends = graph_settings.pop("hide_legends")
164+
if hide_legends_override is not None:
165+
hide_legends = hide_legends_override
166+
161167
fig_size = calculate_fig_size(len(graph.nodes))
162168
logger.info(f"Calculated figure size: {fig_size}")
163169

@@ -195,7 +201,8 @@ def visualize_dependency_graph(
195201
**graph_settings,
196202
)
197203

198-
plt.legend(handles=legend_patches, title="Sheets", loc="upper left")
204+
if not hide_legends:
205+
plt.legend(handles=legend_patches, title="Sheets", loc="upper left")
199206

200207
plt.savefig(output_path, bbox_inches="tight")
201208
plt.close() # Close the figure to free memory

tests/test_cli.py

+67-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import os
2+
import tempfile
3+
from openpyxl import Workbook
14
import pytest
25
import sys
36

@@ -6,6 +9,61 @@
69
from unittest.mock import patch, MagicMock
710

811

12+
@pytest.fixture
13+
def create_excel_file(tmp_path):
14+
def _create_excel_file(data):
15+
file_path = tmp_path / "test.xlsx"
16+
wb = Workbook()
17+
for sheet_name, sheet_data in data.items():
18+
ws = wb.create_sheet(title=sheet_name)
19+
for row in sheet_data:
20+
ws.append(row)
21+
wb.save(file_path)
22+
return file_path
23+
24+
return _create_excel_file
25+
26+
27+
# test cli.main
28+
def test_main():
29+
# assert that main with no arguments raises SystemExit error
30+
test_args = ["graphedexcel"]
31+
with patch("sys.argv", test_args):
32+
with pytest.raises(SystemExit):
33+
main()
34+
35+
36+
def test_main_with_nonexistent_file(capsys):
37+
test_args = ["graphedexcel", "nonexistent_file.xlsx"]
38+
with patch("sys.argv", test_args):
39+
with pytest.raises(SystemExit) as exc_info:
40+
main()
41+
assert exc_info.value.code != 0
42+
captured = capsys.readouterr()
43+
assert "File not found:" in captured.err
44+
45+
46+
def test_main_with_test_xlsx_file(capsys):
47+
"""Test main with a test excel file to exercise the cli module"""
48+
with tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") as tmp_file:
49+
test_file_path = tmp_file.name
50+
try:
51+
wb = Workbook()
52+
ws = wb.active
53+
ws["A1"] = "Test Data"
54+
55+
wb.save(test_file_path)
56+
wb.close()
57+
test_args = ["graphedexcel", test_file_path]
58+
with patch("sys.argv", test_args):
59+
main()
60+
finally:
61+
print("here")
62+
wb.close()
63+
captured = capsys.readouterr()
64+
assert "Dependency graph image saved" in captured.out
65+
66+
967
def test_parse_arguments_required(monkeypatch):
1068
"""
1169
Test that the required positional argument is parsed correctly.
@@ -20,12 +78,19 @@ def test_parse_arguments_optional_flags():
2078
"""
2179
Test that optional flags are parsed correctly.
2280
"""
23-
test_args = ["graphedexcel", "test.xlsx", "--as-directed-graph", "--no-visualize"]
81+
test_args = [
82+
"graphedexcel",
83+
"test.xlsx",
84+
"--as-directed-graph",
85+
"--no-visualize",
86+
"--hide-legends",
87+
]
2488
with patch("sys.argv", test_args):
2589
args = parse_arguments()
2690
assert args.path_to_excel == "test.xlsx"
2791
assert args.as_directed_graph is True
2892
assert args.no_visualize is True
93+
assert args.hide_legends is True
2994

3095

3196
def test_parse_arguments_optional_arguments():
@@ -65,6 +130,7 @@ def test_parse_arguments_default_values():
65130
assert args.as_directed_graph is False
66131
assert args.no_visualize is False
67132
assert args.open_image is False
133+
assert args.hide_legends is None
68134

69135

70136
def test_parse_arguments_invalid():

tests/test_graph_visualizer.py

+7
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,13 @@ def test_all_layouts():
109109
visualize_dependency_graph(G, layout=layout, output_path=layout + "_layout.png")
110110

111111

112+
def test_provided_hide_legends_override():
113+
G = create_two_node_graph()
114+
visualize_dependency_graph(
115+
G, hide_legends_override=True, output_path="hide_legends.png"
116+
)
117+
118+
112119
def test_unknown_layout_will_fallback():
113120
G = create_two_node_graph()
114121
visualize_dependency_graph(G, layout="nosuchlayout", output_path="nosuchlayout.png")

0 commit comments

Comments
 (0)