Skip to content

Commit 5fb9049

Browse files
committed
🎨 textwrap code or html blocks
- sometime indent and dedent has to be used - one typo (altair plot, not plotly plot corrected) - new line before python code block in qmd files
1 parent 9d3528e commit 5fb9049

File tree

9 files changed

+277
-171
lines changed

9 files changed

+277
-171
lines changed

src/vuegen/quarto_reportview.py

Lines changed: 198 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -309,122 +309,150 @@ def _create_yaml_header(self) -> str:
309309
A formatted YAML header string customized for the specified output format.
310310
"""
311311
# Base YAML header with title
312-
yaml_header = f"""---
313-
title: {self.report.title}
314-
fig-align: center
315-
execute:
316-
echo: false
317-
output: asis
318-
jupyter: python3
319-
format:"""
320-
312+
yaml_header = textwrap.dedent(
313+
f"""\
314+
---
315+
title: {self.report.title}
316+
fig-align: center
317+
execute:
318+
echo: false
319+
output: asis
320+
jupyter: python3
321+
format:"""
322+
)
321323
# Define format-specific YAML configurations
322324
format_configs = {
323-
r.ReportType.HTML: """
324-
html:
325-
toc: true
326-
toc-location: left
327-
toc-depth: 3
328-
page-layout: full
329-
self-contained: true
330-
include-in-header:
331-
text: |
332-
<style type="text/css">
333-
.footer {
334-
position: relative;
335-
left: 0;
336-
width: 100%;
337-
text-align: center;
338-
margin-top: 20px;
339-
}
340-
</style>
341-
include-after-body:
342-
text: |
343-
<footer class="footer">
344-
This report was generated with
345-
<a href="https://github.com/Multiomics-Analytics-Group/vuegen" target="_blank">
346-
<img src="https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/vuegen_logo.svg" alt="VueGen" width="65px">
347-
</a>
348-
| Copyright 2025 <a href="https://github.com/Multiomics-Analytics-Group" target="_blank">Multiomics Network Analytics Group (MoNA)</a>
349-
</footer>""",
350-
r.ReportType.PDF: """
351-
pdf:
352-
toc: false
353-
fig-align: center
354-
margin:
355-
- bottom=40mm
356-
include-in-header:
357-
text: |
358-
\\usepackage{scrlayer-scrpage}
359-
\\usepackage{hyperref}
360-
\\clearpairofpagestyles
361-
\\lofoot{This report was generated with \\href{https://github.com/Multiomics-Analytics-Group/vuegen}{VueGen} | \\copyright{} 2025 \\href{https://github.com/Multiomics-Analytics-Group}{Multiomics Network Analytics Group}}
362-
\\rofoot{\\pagemark}""",
363-
r.ReportType.DOCX: """
364-
docx:
365-
toc: false""",
366-
r.ReportType.ODT: """
367-
odt:
368-
toc: false""",
369-
r.ReportType.REVEALJS: """
370-
revealjs:
371-
toc: false
372-
smaller: true
373-
controls: true
374-
navigation-mode: vertical
375-
controls-layout: bottom-right
376-
output-file: quarto_report_revealjs.html
377-
include-in-header:
378-
text: |
379-
<style type="text/css">
380-
.footer {
381-
position: fixed;
382-
left: 0;
383-
bottom: 0;
384-
width: 100%;
385-
text-align: center;
386-
}
387-
</style>
388-
include-after-body:
389-
text: |
390-
<footer class="footer">
391-
This report was generated with
392-
<a href="https://github.com/Multiomics-Analytics-Group/vuegen" target="_blank">
393-
<img src="https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/vuegen_logo.svg" alt="VueGen" width="65px">
394-
</a>
395-
| Copyright 2025 <a href="https://github.com/Multiomics-Analytics-Group" target="_blank">Multiomics Network Analytics Group (MoNA)</a>
396-
</footer>""",
397-
r.ReportType.PPTX: """
398-
pptx:
399-
toc: false
400-
output: true""",
401-
r.ReportType.JUPYTER: """
402-
html:
403-
toc: true
404-
toc-location: left
405-
toc-depth: 3
406-
page-layout: full
407-
self-contained: true
408-
include-in-header:
409-
text: |
410-
<style type="text/css">
411-
.footer {
412-
position: relative;
413-
left: 0;
414-
width: 100%;
415-
text-align: center;
416-
margin-top: 20px;
417-
}
418-
</style>
419-
include-after-body:
420-
text: |
421-
<footer class="footer">
422-
This report was generated with
423-
<a href="https://github.com/Multiomics-Analytics-Group/vuegen" target="_blank">
424-
<img src="../docs/images/vuegen_logo.svg" alt="VueGen" width="65px">
425-
</a>
426-
| Copyright 2025 <a href="https://github.com/Multiomics-Analytics-Group" target="_blank">Multiomics Network Analytics Group (MoNA)</a>
427-
</footer>""",
325+
r.ReportType.HTML: textwrap.dedent(
326+
"""
327+
html:
328+
toc: true
329+
toc-location: left
330+
toc-depth: 3
331+
page-layout: full
332+
self-contained: true
333+
include-in-header:
334+
text: |
335+
<style type="text/css">
336+
.footer {
337+
position: relative;
338+
left: 0;
339+
width: 100%;
340+
text-align: center;
341+
margin-top: 20px;
342+
}
343+
</style>
344+
include-after-body:
345+
text: |
346+
<footer class="footer">
347+
This report was generated with
348+
<a href="https://github.com/Multiomics-Analytics-Group/vuegen" target="_blank">
349+
<img src="https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/vuegen_logo.svg" alt="VueGen" width="65px">
350+
</a>
351+
| Copyright 2025 <a href="https://github.com/Multiomics-Analytics-Group" target="_blank">Multiomics Network Analytics Group (MoNA)</a>
352+
</footer>"""
353+
),
354+
r.ReportType.PDF: textwrap.indent(
355+
textwrap.dedent(
356+
"""
357+
pdf:
358+
toc: false
359+
fig-align: center
360+
margin:
361+
- bottom=40mm
362+
include-in-header:
363+
text: |
364+
\\usepackage{scrlayer-scrpage}
365+
\\usepackage{hyperref}
366+
\\clearpairofpagestyles
367+
\\lofoot{This report was generated with \\href{https://github.com/Multiomics-Analytics-Group/vuegen}{VueGen} | \\copyright{} 2025 \\href{https://github.com/Multiomics-Analytics-Group}{Multiomics Network Analytics Group}}
368+
\\rofoot{\\pagemark}"""
369+
),
370+
" ",
371+
),
372+
r.ReportType.DOCX: textwrap.indent(
373+
textwrap.dedent(
374+
"""
375+
docx:
376+
toc: false"""
377+
),
378+
" ",
379+
),
380+
r.ReportType.ODT: textwrap.indent(
381+
textwrap.dedent(
382+
"""
383+
odt:
384+
toc: false"""
385+
),
386+
" ",
387+
),
388+
r.ReportType.REVEALJS: textwrap.dedent(
389+
"""
390+
revealjs:
391+
toc: false
392+
smaller: true
393+
controls: true
394+
navigation-mode: vertical
395+
controls-layout: bottom-right
396+
output-file: quarto_report_revealjs.html
397+
include-in-header:
398+
text: |
399+
<style type="text/css">
400+
.footer {
401+
position: fixed;
402+
left: 0;
403+
bottom: 0;
404+
width: 100%;
405+
text-align: center;
406+
}
407+
</style>
408+
include-after-body:
409+
text: |
410+
<footer class="footer">
411+
This report was generated with
412+
<a href="https://github.com/Multiomics-Analytics-Group/vuegen" target="_blank">
413+
<img src="https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuegen/main/docs/images/vuegen_logo.svg" alt="VueGen" width="65px">
414+
</a>
415+
| Copyright 2025 <a href="https://github.com/Multiomics-Analytics-Group" target="_blank">Multiomics Network Analytics Group (MoNA)</a>
416+
</footer>"""
417+
),
418+
r.ReportType.PPTX: textwrap.indent(
419+
textwrap.dedent(
420+
"""
421+
pptx:
422+
toc: false
423+
output: true"""
424+
),
425+
" ",
426+
),
427+
r.ReportType.JUPYTER: textwrap.dedent(
428+
"""
429+
html:
430+
toc: true
431+
toc-location: left
432+
toc-depth: 3
433+
page-layout: full
434+
self-contained: true
435+
include-in-header:
436+
text: |
437+
<style type="text/css">
438+
.footer {
439+
position: relative;
440+
left: 0;
441+
width: 100%;
442+
text-align: center;
443+
margin-top: 20px;
444+
}
445+
</style>
446+
include-after-body:
447+
text: |
448+
<footer class="footer">
449+
This report was generated with
450+
<a href="https://github.com/Multiomics-Analytics-Group/vuegen" target="_blank">
451+
<img src="../docs/images/vuegen_logo.svg" alt="VueGen" width="65px">
452+
</a>
453+
| Copyright 2025 <a href="https://github.com/Multiomics-Analytics-Group" target="_blank">Multiomics Network Analytics Group (MoNA)</a>
454+
</footer>"""
455+
),
428456
}
429457
# Create a key based on the report type and format
430458
key = self.report_type
@@ -632,41 +660,57 @@ def _generate_plot_code(self, plot, output_file="") -> str:
632660
The generated plot code as a string.
633661
"""
634662
# Initialize plot code with common structure
635-
plot_code = f"""```{{python}}
636-
#| label: '{plot.title} {plot.id}'
637-
#| fig-cap: ""
638-
"""
663+
plot_code = textwrap.dedent(
664+
f"""
665+
```{{python}}
666+
#| label: '{plot.title} {plot.id}'
667+
#| fig-cap: ""
668+
"""
669+
)
639670
# If the file path is a URL, generate code to fetch content via requests
640671
if is_url(plot.file_path):
641-
plot_code += f"""
642-
response = requests.get('{plot.file_path}')
643-
response.raise_for_status()
644-
plot_json = response.text\n"""
672+
plot_code += textwrap.dedent(
673+
f"""
674+
response = requests.get('{plot.file_path}')
675+
response.raise_for_status()
676+
plot_json = response.text
677+
"""
678+
)
645679
else: # If it's a local file
646680
plot_rel_path = get_relative_file_path(
647681
plot.file_path, relative_to=self.output_dir
648682
).as_posix()
649-
plot_code += f"""
683+
plot_code += textwrap.dedent(
684+
f"""
650685
with open(report_dir /'{plot_rel_path}', 'r') as plot_file:
651-
plot_json = json.load(plot_file)\n"""
686+
plot_json = json.load(plot_file)
687+
"""
688+
)
652689
# Add specific code for each visualization tool
653690
if plot.plot_type == r.PlotType.PLOTLY:
654-
plot_code += """
655-
# Keep only 'data' and 'layout' sections
656-
plot_json = {key: plot_json[key] for key in plot_json if key in ['data', 'layout']}\n
657-
# Remove 'frame' section in 'data'
658-
plot_json['data'] = [{k: v for k, v in entry.items() if k != 'frame'} for entry in plot_json.get('data', [])]\n
659-
# Convert JSON to string
660-
plot_json_str = json.dumps(plot_json)\n
661-
# Create the plotly plot
662-
fig_plotly = pio.from_json(plot_json_str)
663-
fig_plotly.update_layout(autosize=False, width=950, height=400, margin=dict(b=50, t=50, l=50, r=50))\n"""
691+
plot_code += textwrap.dedent(
692+
"""
693+
# Keep only 'data' and 'layout' sections
694+
plot_json = {key: plot_json[key] for key in plot_json if key in ['data', 'layout']}\n
695+
# Remove 'frame' section in 'data'
696+
plot_json['data'] = [{k: v for k, v in entry.items() if k != 'frame'} for entry in plot_json.get('data', [])]\n
697+
# Convert JSON to string
698+
plot_json_str = json.dumps(plot_json)\n
699+
# Create the plotly plot
700+
fig_plotly = pio.from_json(plot_json_str)
701+
fig_plotly.update_layout(autosize=False, width=950, height=400, margin=dict(b=50, t=50, l=50, r=50))
702+
"""
703+
)
664704
elif plot.plot_type == r.PlotType.ALTAIR:
665-
plot_code += """
666-
# Convert JSON to string
667-
plot_json_str = json.dumps(plot_json)\n
668-
# Create the plotly plot
669-
fig_altair = alt.Chart.from_json(plot_json_str).properties(width=900, height=370)\n"""
705+
plot_code += textwrap.dedent(
706+
"""
707+
# Convert JSON to string
708+
plot_json_str = json.dumps(plot_json)
709+
710+
# Create the altair plot
711+
fig_altair = alt.Chart.from_json(plot_json_str).properties(width=900, height=370)
712+
"""
713+
)
670714
elif plot.plot_type == r.PlotType.INTERACTIVE_NETWORK:
671715
# Generate the HTML embedding for interactive networks
672716
if is_url(plot.file_path) and plot.file_path.endswith(".html"):
@@ -677,10 +721,13 @@ def _generate_plot_code(self, plot, output_file="") -> str:
677721
)
678722

679723
# Embed the HTML file in an iframe
680-
plot_code = f"""
681-
<div style="text-align: center;">
682-
<iframe src="{iframe_src}" alt="{plot.title} plot" width="800px" height="630px"></iframe>
683-
</div>\n"""
724+
plot_code = textwrap.dedent(
725+
f"""
726+
<div style="text-align: center;">
727+
<iframe src="{iframe_src}" alt="{plot.title} plot" width="800px" height="630px"></iframe>
728+
</div>
729+
"""
730+
)
684731
return plot_code
685732

686733
def _generate_dataframe_content(self, dataframe) -> List[str]:
@@ -942,10 +989,14 @@ def _generate_html_content(self, html) -> List[str]:
942989
html_file_path = get_relative_file_path(
943990
html.file_path, relative_to=self.output_dir
944991
)
945-
iframe_code = f"""
946-
<div style="text-align: center;">
947-
<iframe src="{html_file_path.as_posix()}" alt="{html.title}" width="950px" height="530px"></iframe>
948-
</div>\n"""
992+
iframe_code = textwrap.dedent(
993+
f"""
994+
<div style="text-align: center;">
995+
<iframe src="{html_file_path.as_posix()}" alt="{html.title}"
996+
width="950px" height="530px"></iframe>
997+
</div>
998+
"""
999+
)
9491000
html_content.append(iframe_code)
9501001

9511002
except Exception as e:

0 commit comments

Comments
 (0)