Skip to content

Commit b3a7b6f

Browse files
authored
Merge branch 'main' into mk_docs
2 parents c33b0b4 + 9ee2b4c commit b3a7b6f

File tree

10 files changed

+469
-33
lines changed

10 files changed

+469
-33
lines changed

.gitignore

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,4 @@ quarto_report/
126126
UML_diagrams/
127127
Graphical_abstract/
128128
docs/presentations/
129-
test.py
130-
vuegen/yaml_generator.py
131-
MicW2Graph_config.yaml
129+
test.py
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
report:
2+
title: Micw2Graph
3+
description: ''
4+
graphical_abstract: ''
5+
logo: ''
6+
sections:
7+
- title: Exploratory Data Analysis
8+
description: ''
9+
subsections:
10+
- title: Abundance Data
11+
description: ''
12+
components:
13+
- component_type: plot
14+
plot_type: plotly
15+
title: Top Species Plot Biome Plotly
16+
file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/1_Abundance_data/1_top_species_plot_biome_plotly.json
17+
description: ''
18+
- component_type: plot
19+
plot_type: altair
20+
title: Multilineplot Altair
21+
file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/1_Abundance_data/2_multilineplot_altair.json
22+
description: ''
23+
- component_type: dataframe
24+
file_format: csv
25+
delimiter: ','
26+
title: Abundance Data Allbiomes
27+
file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/1_Abundance_data/3_abundance_data_allbiomes.csv
28+
description: ''
29+
- component_type: dataframe
30+
file_format: xls
31+
title: Abundance Data Allbiomes
32+
file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/1_Abundance_data/4_abundance_data_allbiomes.xls
33+
description: ''
34+
- title: Sample Data
35+
description: ''
36+
components:
37+
- component_type: plot
38+
plot_type: plotly
39+
title: Pie Plot Countries Plotly
40+
file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/2_Sample_data/1_pie_plot_countries_plotly.json
41+
description: ''
42+
- component_type: plot
43+
plot_type: plotly
44+
title: Pie Plots Biomes Plotly
45+
file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/2_Sample_data/2_pie_plots_biomes_plotly.json
46+
description: ''
47+
- component_type: plot
48+
plot_type: static
49+
title: Number Samples Per Study
50+
file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/2_Sample_data/3_number_samples_per_study.png
51+
description: ''
52+
- component_type: dataframe
53+
file_format: parquet
54+
title: Sample Info Allbiomes
55+
file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/2_Sample_data/4_sample_info_allbiomes.parquet
56+
description: ''
57+
- component_type: dataframe
58+
file_format: txt
59+
delimiter: \t
60+
title: Sample Info Allbiomes
61+
file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/2_Sample_data/5_sample_info_allbiomes.txt
62+
description: ''
63+
- title: Extra Info
64+
description: ''
65+
components:
66+
- component_type: markdown
67+
title: Test Md
68+
file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/1_Exploratory_Data_Analysis/3_Extra_info/1_test_md.md
69+
description: ''
70+
- title: Microbial Association Networks
71+
description: ''
72+
subsections:
73+
- title: Network Visualization1
74+
description: ''
75+
components:
76+
- component_type: plot
77+
plot_type: interactive_network
78+
title: Man Example
79+
file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/2_Microbial_Association_Networks/1_Network_visualization1/1_man_example.graphml
80+
description: ''
81+
- title: Network Visualization2
82+
description: ''
83+
components:
84+
- component_type: plot
85+
plot_type: interactive_network
86+
csv_network_format: edgelist
87+
title: Man Example Edgelist
88+
file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/2_Microbial_Association_Networks/2_Network_visualization2/1_man_example_edgelist.csv
89+
description: ''
90+
- title: Network Visualization3
91+
description: ''
92+
components:
93+
- component_type: plot
94+
plot_type: interactive_network
95+
title: Man Example
96+
file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/2_Microbial_Association_Networks/3_Network_visualization3/1_man_example.cyjs
97+
description: ''
98+
- title: Network Visualization4
99+
description: ''
100+
components:
101+
- component_type: plot
102+
plot_type: interactive_network
103+
title: Ckg Network
104+
file_path: /Users/asaru/Documents/DTU/MoNA/vuegen/example_data/MicW2Graph/2_Microbial_Association_Networks/4_Network_visualization4/1_ckg_network.html
105+
description: ''

vuegen/config_manager.py

Lines changed: 258 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import logging
2-
from typing import Optional
1+
import os
2+
from pathlib import Path
3+
from typing import Dict, List, Union, Tuple
34

45
from . import report as r
56
from .utils import assert_enum_value, get_logger
@@ -20,6 +21,261 @@ def __init__(self, logger: Optional[logging.Logger]=None):
2021
"""
2122
self.logger = logger or get_logger("report")
2223

24+
def _create_title_fromdir(self, file_dirname: str) -> str:
25+
"""
26+
Infers title from a file or directory, removing leading numeric prefixes.
27+
28+
Parameters
29+
----------
30+
file_dirname : str
31+
The file or directory name to infer the title from.
32+
33+
Returns
34+
-------
35+
str
36+
A title generated from the file or directory name.
37+
"""
38+
# Remove leading numbers and underscores if they exist
39+
name = os.path.splitext(file_dirname)[0]
40+
parts = name.split("_", 1)
41+
title = parts[1] if parts[0].isdigit() and len(parts) > 1 else name
42+
return title.replace("_", " ").title()
43+
44+
def _create_component_config_fromfile(self, file_path: Path) -> Dict[str, str]:
45+
"""
46+
Infers a component config from a file, including component type, plot type, and additional fields.
47+
48+
Parameters
49+
----------
50+
file_path : Path
51+
The file path to analyze.
52+
53+
Returns
54+
-------
55+
component_config : Dict[str, str]
56+
A dictionary containing inferred component configuration.
57+
"""
58+
file_ext = file_path.suffix.lower()
59+
component_config = {}
60+
61+
# Infer component config
62+
if file_ext in [r.DataFrameFormat.CSV.value_with_dot, r.DataFrameFormat.TXT.value_with_dot]:
63+
# Check for CSVNetworkFormat keywords
64+
if "edgelist" in file_path.stem.lower():
65+
component_config["component_type"] = r.ComponentType.PLOT.value
66+
component_config["plot_type"] = r.PlotType.INTERACTIVE_NETWORK.value
67+
component_config ["csv_network_format"] = r.CSVNetworkFormat.EDGELIST.value
68+
elif "adjlist" in file_path.stem.lower():
69+
component_config ["component_type"] = r.ComponentType.PLOT.value
70+
component_config ["plot_type"] = r.PlotType.INTERACTIVE_NETWORK.value
71+
component_config ["csv_network_format"] = r.CSVNetworkFormat.ADJLIST.value
72+
# Fill the config with dataframe content
73+
else:
74+
component_config ["component_type"] = r.ComponentType.DATAFRAME.value
75+
component_config ["file_format"] = r.DataFrameFormat.CSV.value if file_ext == r.DataFrameFormat.CSV.value_with_dot else r.DataFrameFormat.TXT.value
76+
component_config ["delimiter"] = "," if file_ext == r.DataFrameFormat.CSV.value_with_dot else "\\t"
77+
# Check other DataframeFormats than csv and txt
78+
elif file_ext in [fmt.value_with_dot for fmt in r.DataFrameFormat if fmt not in [r.DataFrameFormat.CSV, r.DataFrameFormat.TXT]]:
79+
component_config ["component_type"] = r.ComponentType.DATAFRAME.value
80+
component_config ["file_format"] = next(fmt.value for fmt in r.DataFrameFormat if fmt.value_with_dot == file_ext)
81+
# Check for network formats
82+
elif file_ext in [fmt.value_with_dot for fmt in r.NetworkFormat]:
83+
component_config ["component_type"] = r.ComponentType.PLOT.value
84+
if file_ext in [
85+
r.NetworkFormat.PNG.value_with_dot,
86+
r.NetworkFormat.JPG.value_with_dot,
87+
r.NetworkFormat.JPEG.value_with_dot,
88+
r.NetworkFormat.SVG.value_with_dot,
89+
]:
90+
component_config ["plot_type"] = r.PlotType.STATIC.value
91+
else:
92+
component_config ["plot_type"] = r.PlotType.INTERACTIVE_NETWORK.value
93+
# Check for interactive plots
94+
elif file_ext == ".json":
95+
component_config ["component_type"] = r.ComponentType.PLOT.value
96+
if "plotly" in file_path.stem.lower():
97+
component_config ["plot_type"] = r.PlotType.PLOTLY.value
98+
elif "altair" in file_path.stem.lower():
99+
component_config ["plot_type"] = r.PlotType.ALTAIR.value
100+
else:
101+
component_config ["plot_type"] = "unknown"
102+
elif file_ext == ".md":
103+
component_config ["component_type"] = r.ComponentType.MARKDOWN.value
104+
else:
105+
error_msg = (
106+
f"Unsupported file extension: {file_ext}. "
107+
f"Supported extensions include:\n"
108+
f" - Network formats: {', '.join(fmt.value_with_dot for fmt in r.NetworkFormat)}\n"
109+
f" - DataFrame formats: {', '.join(fmt.value_with_dot for fmt in r.DataFrameFormat)}"
110+
)
111+
#self.logger.error(error_msg)
112+
raise ValueError(error_msg)
113+
114+
return component_config
115+
116+
def _sort_paths_by_numprefix(self, paths: List[Path]) -> List[Path]:
117+
"""
118+
Sorts a list of Paths by numeric prefixes in their names, placing non-numeric items at the end.
119+
120+
Parameters
121+
----------
122+
paths : List[Path]
123+
The list of Path objects to sort.
124+
125+
Returns
126+
-------
127+
List[Path]
128+
The sorted list of Path objects.
129+
"""
130+
def get_sort_key(path: Path) -> tuple:
131+
parts = path.name.split("_", 1)
132+
if parts[0].isdigit():
133+
numeric_prefix = int(parts[0])
134+
else:
135+
# Non-numeric prefixes go to the end
136+
numeric_prefix = float('inf')
137+
return numeric_prefix, path.name.lower()
138+
139+
return sorted(paths, key=get_sort_key)
140+
141+
def _create_subsect_config_fromdir(self, subsection_dir_path: Path) -> Dict[str, Union[str, List[Dict]]]:
142+
"""
143+
Creates subsection config from a directory.
144+
145+
Parameters
146+
----------
147+
subsection_dir_path : Path
148+
Path to the subsection directory.
149+
150+
Returns
151+
-------
152+
Dict[str, Union[str, List[Dict]]]
153+
The subsection config.
154+
"""
155+
subsection_config = {
156+
"title": self._create_title_fromdir(subsection_dir_path.name),
157+
"description": "",
158+
"components": [],
159+
}
160+
161+
# Sort files by number prefix
162+
sorted_files = self._sort_paths_by_numprefix(list(subsection_dir_path.iterdir()))
163+
164+
for file in sorted_files:
165+
if file.is_file():
166+
component_config = self._create_component_config_fromfile(file)
167+
168+
# Ensure the file path is absolute
169+
file_path = file.resolve()
170+
171+
component_config_updt = {
172+
"title": self._create_title_fromdir(file.name),
173+
"file_path": str(file_path),
174+
"description": "",
175+
}
176+
177+
# Update inferred config information
178+
component_config.update(component_config_updt)
179+
180+
subsection_config["components"].append(component_config)
181+
182+
return subsection_config
183+
184+
def _create_sect_config_fromdir(self, section_dir_path: Path) -> Dict[str, Union[str, List[Dict]]]:
185+
"""
186+
Creates section config from a directory.
187+
188+
Parameters
189+
----------
190+
section_dir_path : Path
191+
Path to the section directory.
192+
193+
Returns
194+
-------
195+
Dict[str, Union[str, List[Dict]]]
196+
The section config.
197+
"""
198+
section_config = {
199+
"title": self._create_title_fromdir(section_dir_path.name),
200+
"description": "",
201+
"subsections": [],
202+
}
203+
204+
# Sort subsections by number prefix
205+
sorted_subsections = self._sort_paths_by_numprefix(list(section_dir_path.iterdir()))
206+
207+
for subsection_dir in sorted_subsections:
208+
if subsection_dir.is_dir():
209+
section_config["subsections"].append(self._create_subsect_config_fromdir(subsection_dir))
210+
211+
return section_config
212+
213+
214+
def _resolve_base_dir(self, base_dir: str) -> Path:
215+
"""
216+
Resolves the provided base directory to an absolute path from the root, accounting for relative paths.
217+
218+
Parameters
219+
----------
220+
base_dir : str
221+
The relative or absolute path to the base directory.
222+
223+
Returns
224+
-------
225+
Path
226+
The absolute path to the base directory.
227+
"""
228+
# Check if we are in a subdirectory and need to go up one level
229+
project_dir = Path(__file__).resolve().parents[1]
230+
231+
# If the base_dir is a relative path, resolve it from the project root
232+
base_dir_path = project_dir / base_dir
233+
234+
# Make sure the resolved base directory exists
235+
if not base_dir_path.is_dir():
236+
raise ValueError(f"Base directory '{base_dir}' does not exist or is not a directory.")
237+
238+
return base_dir_path
239+
240+
241+
def create_yamlconfig_fromdir(self, base_dir: str) -> Tuple[Dict[str, Union[str, List[Dict]]], Path]:
242+
"""
243+
Generates a YAML-compatible config file from a directory. It also returns the resolved folder path.
244+
245+
Parameters
246+
----------
247+
base_dir : str
248+
The base directory containing section and subsection folders.
249+
250+
Returns
251+
-------
252+
Tuple[Dict[str, Union[str, List[Dict]]], Path]
253+
The YAML config and the resolved directory path.
254+
"""
255+
# Get absolute path from base directory
256+
base_dir_path = self._resolve_base_dir(base_dir)
257+
258+
# Generate the YAML config
259+
yaml_config = {
260+
"report": {
261+
"title": self._create_title_fromdir(base_dir_path.name),
262+
"description": "",
263+
"graphical_abstract": "",
264+
"logo": "",
265+
},
266+
"sections": [],
267+
}
268+
269+
# Sort sections by their number prefix
270+
sorted_sections = self._sort_paths_by_numprefix(list(base_dir_path.iterdir()))
271+
272+
# Generate sections and subsections config
273+
for section_dir in sorted_sections:
274+
if section_dir.is_dir():
275+
yaml_config["sections"].append(self._create_sect_config_fromdir(section_dir))
276+
277+
return yaml_config, base_dir_path
278+
23279
def initialize_report(self, config: dict) -> tuple[r.Report, dict]:
24280
"""
25281
Extracts report metadata from a YAML config file and returns a Report object and the raw metadata.

0 commit comments

Comments
 (0)