Interactive figure polishing for R/ggplot2 outputs, without repeatedly editing plot code.
The tool provides a browser-based editor for .rds ggplot objects. You can drag
text directly on the rendered figure, adjust common visual parameters, preview
the result in real time, and export updated PDF/SVG/PNG/TIFF files.
Late-stage scientific figures often need small visual changes: move a legend, increase a title font, adjust a bar width, recolor a group, or make a panel label line up cleanly. Doing that by repeatedly editing R scripts, rerunning the whole pipeline, and reopening exported files is slow.
This project turns that loop into a visual workflow:
open figure -> drag or edit controls -> preview -> save
- Direct text dragging for plot titles, axis labels, legends, facet strips, and data labels.
- Browser-based inline SVG preview with live rendering after edits.
- Controls for font size, color, angle, bar width, y-axis limits, canvas size, tick labels, facet labels, and discrete color palettes.
- Patchwork-aware editing, with per-subplot switching and combined patch saving.
- Basic support for
ggsurvplotKaplan-Meier figures by editing the main plot. - Non-destructive patch files: original
.rdsobjects are never modified. - Saved edits are written to
*.patches.jsonand exported as equivalent*.patches.Rcode that can be copied back into an analysis script. - Version-directory grouping for folders such as
v1,v2, andv3, with the newest version shown by default. - Persistent R rendering worker over a localhost socket, so switching figures does not restart R every time.
Your figures should be R/ggplot2 objects saved as .rds files:
saveRDS(p, "fig.rds")Python:
- Python 3
- Flask
R packages:
ggplot2jsonlitesvglitesurvminerfor Kaplan-Meier figures created withggsurvplot
For CJK text rendering in exported PDF/PNG files, install an appropriate system font such as SimHei or another CJK-compatible font.
pip install flask
export FIG_ROOT=/path/to/your/ggplot_figures
export RSCRIPT=/usr/bin/Rscript
python3 app.pyThen open:
http://localhost:5081
You can also restart the service by port:
bash restart.sh| Variable | Default | Description |
|---|---|---|
FIG_ROOT |
/path/to/your/ggplot_figures |
Root directory scanned recursively for .rds figure files |
RSCRIPT |
/usr/bin/Rscript |
Path to the Rscript executable |
FIG_EDITOR_PORT |
5081 |
HTTP server port |
Each .rds figure is treated as an immutable source object. Every visual edit
made in the browser is stored as a flat patch entry, for example:
text|plot.title|size
layer|2|geom_params|width
strip|<facet label>
Rendering follows this flow:
load .rds -> apply patches -> render preview/export
Text dragging is implemented by matching ggplotGrob text grobs with frontend
SVG <text> elements by order and content. Offsets are applied in point units
with unit(d, "pt"). When saved, patches are written to JSON, output files are
regenerated, and equivalent R patch code is emitted for reproducibility.
interactive_modify_figures_from_python/
├── app.py # Flask backend: figure list, inspect, render, save APIs
├── r_worker.R # Persistent R worker using a localhost socket and JSON lines
├── templates/ # Editor page
├── static/ # Frontend JS/CSS for panels, SVG dragging, and render debounce
├── renders/ # Preview render outputs
└── restart.sh # Port-aware restart helper
- Data-mapped
geom_textlabels, such as sample-size labels, cannot be edited one by one as text values. The layer font size and color can be changed, and individual labels can be repositioned, but changing label content requires editing the source data. - Manual scale colors are read from and written back to the scale environment
values, which is expected behavior for this implementation.
MIT