Skip to content

Add headless exportAMRVisualizations() to render all dashboard figures as pdf, png, jpg, or svg without running the app - #41

Merged
eboyer221 merged 8 commits into
mainfrom
dev-headless-export
Jul 31, 2026
Merged

Add headless exportAMRVisualizations() to render all dashboard figures as pdf, png, jpg, or svg without running the app#41
eboyer221 merged 8 commits into
mainfrom
dev-headless-export

Conversation

@eboyer221

@eboyer221 eboyer221 commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

What this adds
A new function, exportAMRVisualizations(), that renders every visualization in the amRviz dashboard to image files without launching the Shiny app. You install the package, point it at your results (or use the built-in demo data), and get a folder of figures for every panel: metadata distributions, model performance, feature importance, cross-model holdouts, and the drug–feature network.
The goal: if someone just wants the figures for a report or paper and doesn't want to click through the dashboard, one command gives them everything.

How to use it

library(amRviz)

Demo data → ./amRviz_exports/ as PNG + PDF:

exportAMRVisualizations()

Your own results, choose formats, output folder, and species:

exportAMRVisualizations(
  output_dir   = "figures",
  formats      = c("png", "pdf", "jpg"),
  results_root = "/path/to/your/amRml/results"
)

One figure set per species, using the same default selections the dashboard opens with. Cross-species overviews land in _overview/ and _across_species/.
Formats are the caller's choice: png, pdf, jpg (svg is best-effort). PDF is vector that is best for publication. Raster resolution is set by a scale argument (default gives ~340 dpi; scale = 4 gives ~680 dpi).

Every dashboard plot is an interactive web widget, so there's no built-in way to save it as an image. Export takes a screenshot of each one using a headless Chrome (via the webshot2 package). Reviewers need Google Chrome or Chromium installed and the function stops with a clear message if it can't find a browser.

How to test it in review

from the package root:

pkgload::load_all(".") # or devtools::load_all(".")

fastest test: one species, PNG only:

exportAMRVisualizations(output_dir = "review_test", formats = "png",
                        species = "Shigella_flexneri")

Then:

Open review_test/ and spot-check a few images. They should show real plots, not blank pages.
Try formats = c("png", "pdf") and confirm you get both file types per figure.
Run the tests: devtools::test() (the browser-dependent test auto-skips if no Chrome is found).
Expected: the printed summary shows all figures rendered. A couple of the "holdout" panels will show a "needs models trained on 2+ countries" message, which is correct for the demo data, which only has one country's holdout; your own multi-country data fills them in.

Notes
Adds webshot2 + htmlwidgets to Suggests (used only by this function).
README and the usage vignette have a new section covering export.

@eboyer221 eboyer221 self-assigned this Jul 23, 2026
@eboyer221 eboyer221 added the enhancement New feature or request label Jul 23, 2026

@amcim amcim left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this looks good functionally. I made some changes to remove warnings from the new tests that were added for the export feature. The most prominent is that I added a helper function in utils_misc.R which returns a placeholder plotly with an explanatory title, since a common warning was empty plot_ly()being returned as a "no data" placeholder. The helper sets an explicit trace type and hides the axes, so it also renders as a cleaner empty canvas instead of fake axes.

I also removed a deprecation warning, dropped NA-metric rows in makeModelPerformancePlot up front, and moved size/alpha in makeMCCStripPlot out of aes() into geom_jitter

Before approval, one thing worth discussing: the export currently hardcodes every dashboard control to its default value. So the network image is always exported without cluster/COG tiers (top_n = 5), feature importance is always top 10 on the genes scale, etc. Is this intended, or would we want a way for the caller to override these when they export?

@eboyer221

Copy link
Copy Markdown
Contributor Author

Thanks @amcim

I checked with @AbhirupaGhosh and she confirmed that being able to adjust the feature count would be helpful, so I just added that.
What is in my most recent push:

  • top_n_features (default 10) controls how many features show in the feature-importance panels (across-drug, across-species, and the cross-model holdout panel).
  • network_top_n (default 5) controls how many top features per drug go into the drug-feature network.
  • Both default to the current dashboard values unless a caller overrides them. They're validated (must be a positive number) and covered by a couple of new tests.

Beyond adjusting the counts - you're right that everything else is still pinned to dashboard defaults (molecular scale, data type, network cluster/COG tiers, etc.). The same param-passthrough pattern could extend to any of those. I kept this PR scoped to the feature count since that's the only override the team has actually confirmed would be useful right now, but happy to add more if needed later.

@amcim

amcim commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

@eboyer221 I made quite a few changes because I tested with the other extensions (jpg, pdf, svg) and found issues . The first is that the drug_feature_network image was cutting off when a large number of features was requested. Additionally I noticed the pdfs were cutting off the images as well, svgs weren't exporting correctly, and once I used JS to resize the network there were issues with whitespace and the size of the canvas that needed to be solved.. Basically before this commit, png and jpgs looked good, but svg only worked for plotly images with a Python dependency (Kaleido) and pdf was cutting off multiple images.

Ultimately I restructured how export works. Instead of one render call per format plus a Python dependent SVG branch that only handled plotly, the export now runs a single webshot PNG that every raster format (jpg, pdf) re-encodes from, plus a chromote-based SVG extraction that works for any widget type. I went this route because I saw earlier the PNG exports already looked great, anchoring the others to it means jpg and pdf inherit the same crop, dimensions, and layout for free and create consistency. Further, the python dependency is gone.

For getting the network image sized correctly, that involved writing some JavaScript which onRender hook that fires after the force simulation settles, measures the bounding box of the drawn nodes, and rewrites the SVG's viewBox to that box. This was fairly straightforward, the only annoying part was trimming the whitespace, had to add magick dependency.

I did notice one thing. For cog_categories.<ext>, the rows have a lot of NA labels. I just want to record the second here as i'm not sure if its an issue with this code or the data, or if its even an issue at all.

One other thing to note is that with this export feature and how it works, this requires a user to have Chrome. We will have to declare that for Bioconductor.

@eboyer221

Copy link
Copy Markdown
Contributor Author

Thank you for all the testing and the fixes you made @amcim. Anchoring jpg/pdf to a single webshot PNG for consistent crop/dimensions is a nice fix, as is cutting the kaleido/Python dependency for SVG. I like the new DOM-based SVG that has the true vector output for any widget.

I did just push the following commits based on your changes:

  1. Regenerated man/exportAMRVisualizations.Rd - it was out of sync with the roxygen edits and still said PDF was vector and SVG needed kaleido. Now it matches the actual pipeline (this also would've failed the R CMD check / BiocCheck consistency check).
  2. Fixed the README (Rmd + md) - the SVG section still described the old kaleido approach; updated it to match the vignette.
  3. Added SystemRequirements: Google Chrome or Chromium to DESCRIPTION per your Bioconductor note (the function already errors clearly if no browser is found).

Looked into the cog_categories NA labels and it seems like an upstream data issue, not something the export code introduced. cluster_feature_COG.parquet stores unnamed COGs as the literal string "NA," which gets joined into COG_name (e.g. "Alanine racemase NA NA NA"). Since makeCogBarChart() is shared with the live dashboard, the interactive plot shows the same thing. I'm submitting a separate small PR to remove the NA tokens.

@amcim amcim left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since export itself looks good, approved for merge

@eboyer221
eboyer221 merged commit 82f451b into main Jul 31, 2026
@eboyer221
eboyer221 deleted the dev-headless-export branch July 31, 2026 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants