Skip to content

Commit 8e60ac2

Browse files
authored
Streamlit and quarto relative paths, and fix plots resizing (#119)
* 🐛 Fix(streamlit_reportview.py): replace absolute by relative paths on python scripts for streamlit reports. Closes #71 * 🧪 Comment code to replace absolute paths on GA docs to test if update on the src code works * 🐛 Fix: add try-except block to solve rel path error for static plots on CI tests * 🐛 Fix: add try-except block to solve rel path error for all components * 🐛 Fix: create a function on utils to create relative path and apply it in streamlit and quarto reports * 🐛 Fix: sort imports properly * 🐛 Fix(quarot_reportview.py): apply as_posix just to paths, not to urls in _generate_image_content function * 🐛 Fix(quarto_reportview.py): Fix png size issue on quarto reports, now the images are resized keeping their ratio * 🐛 Fix(quarto_reportview.py): correct sizes for interactive plots on quarto reports. Closes #68 * 📝 Docs: update class diagram and section title of docs * 🐛 Fix(utils): remove redundant code on get_relative_file_path helper function
1 parent 4007e21 commit 8e60ac2

File tree

8 files changed

+120
-85
lines changed

8 files changed

+120
-85
lines changed

.github/workflows/docs.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ jobs:
5353
# path: docs/_build/
5454

5555
# --- Streamlit example deployment ---
56-
- name: Fix Absolute Paths in Streamlit Scripts
57-
run: |
58-
find docs/streamlit_report/sections -type f -name "*.py" -exec sed -i 's|/home/runner/work/vuegen/vuegen/docs/||g' {} +
56+
#- name: Fix Absolute Paths in Streamlit Scripts
57+
# run: |
58+
# find docs/streamlit_report/sections -type f -name "*.py" -exec sed -i 's|/home/runner/work/vuegen/vuegen/docs/||g' {} +
5959
- name: Publish Streamlit report to streamlit-example branch
6060
if: startsWith(github.ref, 'refs/tags')
6161
uses: peaceiris/actions-gh-pages@v4

.gitignore

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,5 @@ docs/images/UML_diagrams/
128128
docs/images/Graphical_abstract/
129129
docs/images/Nfcore_module_figure
130130
docs/presentations/
131-
docs/example_data/Earth_microbiome_vuegen_demo_notebook_test/
132-
docs/vuegen_case_study_earth_microbiome_test.ipynb
133-
test.py
131+
basic_example_vuegen_demo_notebook_config.yaml
132+
earth_microbiome_vuegen_demo_notebook_config.yaml
8.06 KB
Loading

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ vuegen_Chatbot_configfile
3535

3636
```{toctree}
3737
:maxdepth: 2
38-
:caption: Modules
38+
:caption: API Reference
3939
:hidden:
4040
4141
reference/vuegen

docs/vuegen_basic_case_study.ipynb

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,10 +139,9 @@
139139
"outputs": [],
140140
"source": [
141141
"# Imports\n",
142-
"import os\n",
143142
"import yaml\n",
144143
"from vuegen import report_generator\n",
145-
"from vuegen.utils import get_logger, load_yaml_config\n",
144+
"from vuegen.utils import load_yaml_config\n",
146145
"\n",
147146
"if IN_COLAB:\n",
148147
" import urllib"
@@ -444,7 +443,7 @@
444443
],
445444
"metadata": {
446445
"kernelspec": {
447-
"display_name": "vuegen_py312",
446+
"display_name": "Python 3 (ipykernel)",
448447
"language": "python",
449448
"name": "python3"
450449
},
@@ -458,9 +457,9 @@
458457
"name": "python",
459458
"nbconvert_exporter": "python",
460459
"pygments_lexer": "ipython3",
461-
"version": "3.12.9"
460+
"version": "3.12.6"
462461
}
463462
},
464463
"nbformat": 4,
465-
"nbformat_minor": 2
464+
"nbformat_minor": 4
466465
}

src/vuegen/quarto_reportview.py

Lines changed: 59 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import pandas as pd
99

1010
from . import report as r
11-
from .utils import create_folder, is_url, sort_imports
11+
from .utils import create_folder, get_relative_file_path, is_url, sort_imports
1212

1313

1414
class QuartoReportView(r.ReportView):
@@ -365,6 +365,7 @@ def _create_yaml_header(self) -> str:
365365
r.ReportType.PDF: """
366366
pdf:
367367
toc: false
368+
fig-align: center
368369
margin:
369370
- bottom=40mm
370371
include-in-header:
@@ -568,13 +569,13 @@ def _generate_plot_content(self, plot) -> List[str]:
568569
try:
569570
if plot.plot_type == r.PlotType.STATIC:
570571
plot_content.append(
571-
self._generate_image_content(plot.file_path, width=950)
572+
self._generate_image_content(plot.file_path, width="90%")
572573
)
573574
elif plot.plot_type == r.PlotType.PLOTLY:
574575
plot_content.append(self._generate_plot_code(plot))
575576
if self.is_report_static:
576577
plot_content.append(
577-
f"""fig_plotly.write_image("{static_plot_path.resolve().as_posix()}")\n```\n"""
578+
f"""fig_plotly.write_image("{static_plot_path.relative_to("quarto_report").as_posix()}")\n```\n"""
578579
)
579580
plot_content.append(self._generate_image_content(static_plot_path))
580581
else:
@@ -583,7 +584,7 @@ def _generate_plot_content(self, plot) -> List[str]:
583584
plot_content.append(self._generate_plot_code(plot))
584585
if self.is_report_static:
585586
plot_content.append(
586-
f"""fig_altair.save("{static_plot_path.resolve().as_posix()}")\n```\n"""
587+
f"""fig_altair.save("{static_plot_path.relative_to("quarto_report").as_posix()}")\n```\n"""
587588
)
588589
plot_content.append(self._generate_image_content(static_plot_path))
589590
else:
@@ -655,8 +656,9 @@ def _generate_plot_code(self, plot, output_file="") -> str:
655656
response.raise_for_status()
656657
plot_json = response.text\n"""
657658
else: # If it's a local file
659+
plot_rel_path = get_relative_file_path(plot.file_path, base_path="..")
658660
plot_code += f"""
659-
with open('{(Path("..") / plot.file_path).as_posix()}', 'r') as plot_file:
661+
with open('{plot_rel_path.as_posix()}', 'r') as plot_file:
660662
plot_json = json.load(plot_file)\n"""
661663
# Add specific code for each visualization tool
662664
if plot.plot_type == r.PlotType.PLOTLY:
@@ -669,13 +671,13 @@ def _generate_plot_code(self, plot, output_file="") -> str:
669671
plot_json_str = json.dumps(plot_json)\n
670672
# Create the plotly plot
671673
fig_plotly = pio.from_json(plot_json_str)
672-
fig_plotly.update_layout(width=950, height=500)\n"""
674+
fig_plotly.update_layout(autosize=False, width=950, height=400, margin=dict(b=50, t=50, l=50, r=50))\n"""
673675
elif plot.plot_type == r.PlotType.ALTAIR:
674676
plot_code += """
675677
# Convert JSON to string
676678
plot_json_str = json.dumps(plot_json)\n
677679
# Create the plotly plot
678-
fig_altair = alt.Chart.from_json(plot_json_str).properties(width=900, height=400)\n"""
680+
fig_altair = alt.Chart.from_json(plot_json_str).properties(width=900, height=370)\n"""
679681
elif plot.plot_type == r.PlotType.INTERACTIVE_NETWORK:
680682
# Generate the HTML embedding for interactive networks
681683
if is_url(plot.file_path) and plot.file_path.endswith(".html"):
@@ -734,16 +736,17 @@ def _generate_dataframe_content(self, dataframe) -> List[str]:
734736
)
735737

736738
# Build the file path (URL or local file)
737-
file_path = (
738-
dataframe.file_path
739-
if is_url(dataframe.file_path)
740-
else Path("..") / dataframe.file_path
741-
)
739+
if is_url(dataframe.file_path):
740+
df_file_path = dataframe.file_path
741+
else:
742+
df_file_path = get_relative_file_path(
743+
dataframe.file_path, base_path=".."
744+
)
742745

743746
# Load the DataFrame using the correct function
744747
read_function = read_function_mapping[file_extension]
745748
dataframe_content.append(
746-
f"""df = pd.{read_function.__name__}('{file_path.as_posix()}')\n"""
749+
f"""df = pd.{read_function.__name__}('{df_file_path.as_posix()}')\n"""
747750
)
748751

749752
# Display the dataframe
@@ -798,9 +801,10 @@ def _generate_markdown_content(self, markdown) -> List[str]:
798801
markdown_content = response.text\n"""
799802
)
800803
else: # If it's a local file
804+
md_rel_path = get_relative_file_path(markdown.file_path, base_path="..")
801805
markdown_content.append(
802806
f"""
803-
with open('{(Path("..") / markdown.file_path).as_posix()}', 'r') as markdown_file:
807+
with open('{md_rel_path.as_posix()}', 'r') as markdown_file:
804808
markdown_content = markdown_file.read()\n"""
805809
)
806810

@@ -822,6 +826,39 @@ def _generate_markdown_content(self, markdown) -> List[str]:
822826
)
823827
return markdown_content
824828

829+
def _show_dataframe(self, dataframe) -> List[str]:
830+
"""
831+
Appends either a static image or an interactive representation of a DataFrame to the content list.
832+
833+
Parameters
834+
----------
835+
dataframe : DataFrame
836+
The DataFrame object containing the data to display.
837+
838+
Returns
839+
-------
840+
list : List[str]
841+
The list of content lines for the DataFrame.
842+
"""
843+
dataframe_content = []
844+
if self.is_report_static:
845+
# Generate path for the DataFrame image
846+
df_image = (
847+
Path(self.static_dir) / f"{dataframe.title.replace(' ', '_')}.png"
848+
)
849+
dataframe_content.append(
850+
f"df.dfi.export('{Path(df_image).relative_to('quarto_report').as_posix()}', max_rows=10, max_cols=5, table_conversion='matplotlib')\n```\n"
851+
)
852+
# Use helper method to add centered image content
853+
dataframe_content.append(self._generate_image_content(df_image))
854+
else:
855+
# Append code to display the DataFrame interactively
856+
dataframe_content.append(
857+
"""show(df, classes="display nowrap compact", lengthMenu=[3, 5, 10])\n```\n"""
858+
)
859+
860+
return dataframe_content
861+
825862
def _generate_html_content(self, html) -> List[str]:
826863
"""
827864
Adds an HTML component to the report.
@@ -843,14 +880,13 @@ def _generate_html_content(self, html) -> List[str]:
843880

844881
try:
845882
# Embed the HTML in an iframe
846-
iframe_src = (
847-
html.file_path
848-
if is_url(html.file_path)
849-
else Path("..") / html.file_path
850-
)
883+
if is_url(html.file_path):
884+
html_file_path = html.file_path
885+
else:
886+
html_file_path = get_relative_file_path(html.file_path, base_path="..")
851887
iframe_code = f"""
852888
<div style="text-align: center;">
853-
<iframe src="{iframe_src}" alt="{html.title}" width="800px" height="630px"></iframe>
889+
<iframe src="{html_file_path.as_posix()}" alt="{html.title}" width="950px" height="530px"></iframe>
854890
</div>\n"""
855891
html_content.append(iframe_code)
856892

@@ -866,7 +902,7 @@ def _generate_html_content(self, html) -> List[str]:
866902
return html_content
867903

868904
def _generate_image_content(
869-
self, image_path: str, alt_text: str = "", width: int = 650, height: int = 400
905+
self, image_path: str, alt_text: str = "", width: str = "90%"
870906
) -> str:
871907
"""
872908
Adds an image to the content list in an HTML format with a specified width and height.
@@ -889,47 +925,10 @@ def _generate_image_content(
889925
"""
890926
if is_url(image_path):
891927
src = image_path
892-
return (
893-
f"""![]({src}){{fig-alt={alt_text} width={width} height={height}}}\n"""
894-
)
895928
else:
896-
src = Path(image_path).resolve()
897-
return (
898-
f"""![](/{src}){{fig-alt={alt_text} width={width} height={height}}}\n"""
899-
)
900-
901-
def _show_dataframe(self, dataframe) -> List[str]:
902-
"""
903-
Appends either a static image or an interactive representation of a DataFrame to the content list.
904-
905-
Parameters
906-
----------
907-
dataframe : DataFrame
908-
The DataFrame object containing the data to display.
929+
src = get_relative_file_path(image_path, base_path="..").as_posix()
909930

910-
Returns
911-
-------
912-
list : List[str]
913-
The list of content lines for the DataFrame.
914-
"""
915-
dataframe_content = []
916-
if self.is_report_static:
917-
# Generate path for the DataFrame image
918-
df_image = (
919-
Path(self.static_dir) / f"{dataframe.title.replace(' ', '_')}.png"
920-
)
921-
dataframe_content.append(
922-
f"df.dfi.export('{Path(df_image).resolve().as_posix()}', max_rows=10, max_cols=5, table_conversion='matplotlib')\n```\n"
923-
)
924-
# Use helper method to add centered image content
925-
dataframe_content.append(self._generate_image_content(df_image))
926-
else:
927-
# Append code to display the DataFrame interactively
928-
dataframe_content.append(
929-
"""show(df, classes="display nowrap compact", lengthMenu=[3, 5, 10])\n```\n"""
930-
)
931-
932-
return dataframe_content
931+
return f"""![]({src}){{fig-alt={alt_text} width={width}}}\n"""
933932

934933
def _generate_component_imports(self, component: r.Component) -> List[str]:
935934
"""

src/vuegen/streamlit_reportview.py

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from streamlit.web import cli as stcli
1010

1111
from . import report as r
12-
from .utils import create_folder, generate_footer, is_url
12+
from .utils import create_folder, generate_footer, get_relative_file_path, is_url
1313
from .utils.variables import make_valid_identifier
1414

1515

@@ -585,8 +585,9 @@ def _generate_plot_content(self, plot) -> List[str]:
585585
# Add content for the different plot types
586586
try:
587587
if plot.plot_type == r.PlotType.STATIC:
588+
plot_rel_path = get_relative_file_path(plot.file_path)
588589
plot_content.append(
589-
f"\nst.image('{plot.file_path}', caption='{plot.caption}', use_column_width=True)\n"
590+
f"\nst.image('{plot_rel_path.as_posix()}', caption='{plot.caption}', use_column_width=True)\n"
590591
)
591592
elif plot.plot_type == r.PlotType.PLOTLY:
592593
plot_content.append(self._generate_plot_code(plot))
@@ -601,7 +602,7 @@ def _generate_plot_content(self, plot) -> List[str]:
601602
# Otherwise, create and save a new pyvis network from the netowrkx graph
602603
html_plot_file = (
603604
Path(self.static_dir) / f"{plot.title.replace(' ', '_')}.html"
604-
)
605+
).resolve()
605606
_ = plot.create_and_save_pyvis_network(
606607
networkx_graph, html_plot_file
607608
)
@@ -616,13 +617,13 @@ def _generate_plot_content(self, plot) -> List[str]:
616617
f"""
617618
response = requests.get('{html_plot_file}')
618619
response.raise_for_status()
619-
html_data = response.text\n"""
620+
html_content = response.text\n"""
620621
)
621622
else:
622623
plot_content.append(
623624
f"""
624-
with open('{html_plot_file}', 'r') as f:
625-
html_data = f.read()\n"""
625+
with open('{Path(html_plot_file).relative_to(Path.cwd())}', 'r') as html_file:
626+
html_content = html_file.read()\n"""
626627
)
627628

628629
# Append the code for additional information (nodes and edges count)
@@ -669,8 +670,9 @@ def _generate_plot_code(self, plot) -> str:
669670
response.raise_for_status()
670671
plot_json = json.loads(response.text)\n"""
671672
else: # If it's a local file
673+
plot_rel_path = get_relative_file_path(plot.file_path)
672674
plot_code = f"""
673-
with open('{Path(plot.file_path).as_posix()}', 'r') as plot_file:
675+
with open('{plot_rel_path.as_posix()}', 'r') as plot_file:
674676
plot_json = json.load(plot_file)\n"""
675677

676678
# Add specific code for each visualization tool
@@ -693,7 +695,7 @@ def _generate_plot_code(self, plot) -> str:
693695
control_layout = st.checkbox('Add panel to control layout', value=True)
694696
net_html_height = 1200 if control_layout else 630
695697
# Load HTML into HTML component for display on Streamlit
696-
st.components.v1.html(html_data, height=net_html_height)\n"""
698+
st.components.v1.html(html_content, height=net_html_height)\n"""
697699
return plot_code
698700

699701
def _generate_dataframe_content(self, dataframe) -> List[str]:
@@ -739,8 +741,14 @@ def _generate_dataframe_content(self, dataframe) -> List[str]:
739741

740742
# Load the DataFrame using the correct function
741743
read_function = read_function_mapping[file_extension]
744+
745+
# Build the file path (URL or local file)
746+
if is_url(dataframe.file_path):
747+
df_file_path = dataframe.file_path
748+
else:
749+
df_file_path = get_relative_file_path(dataframe.file_path)
742750
dataframe_content.append(
743-
f"""df = pd.{read_function.__name__}('{dataframe.file_path}')\n"""
751+
f"""df = pd.{read_function.__name__}('{df_file_path.as_posix()}')\n"""
744752
)
745753

746754
# Displays a DataFrame using AgGrid with configurable options.
@@ -817,9 +825,10 @@ def _generate_markdown_content(self, markdown) -> List[str]:
817825
markdown_content = response.text\n"""
818826
)
819827
else: # If it's a local file
828+
md_rel_path = get_relative_file_path(markdown.file_path)
820829
markdown_content.append(
821830
f"""
822-
with open('{(Path("..") / markdown.file_path).as_posix()}', 'r') as markdown_file:
831+
with open('{md_rel_path.as_posix()}', 'r') as markdown_file:
823832
markdown_content = markdown_file.read()\n"""
824833
)
825834
# Code to display md content
@@ -875,11 +884,11 @@ def _generate_html_content(self, html) -> List[str]:
875884
response.raise_for_status()
876885
html_content = response.text\n"""
877886
)
878-
else:
879-
# If it's a local file
887+
else: # If it's a local file
888+
html_rel_path = get_relative_file_path(html.file_path)
880889
html_content.append(
881890
f"""
882-
with open('{(Path("..") / html.file_path).as_posix()}', 'r', encoding='utf-8') as html_file:
891+
with open('{html_rel_path.as_posix()}', 'r', encoding='utf-8') as html_file:
883892
html_content = html_file.read()\n"""
884893
)
885894

0 commit comments

Comments
 (0)