--- title: "Drawing plasmid maps with plasmidplot" output: rmarkdown::html_vignette vignette: > %\VignetteIndexEntry{Drawing plasmid maps with plasmidplot} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ```{r setup, include = FALSE} knitr::opts_chunk$set( collapse = TRUE, comment = "#>", fig.width = 6, fig.height = 6, dpi = 110 ) library(plasmidplot) ``` `plasmidplot` draws circular and linear maps of plasmids and other DNA molecules using `grid`. It has no dependencies beyond base R. ## A map from scratch A map is a `plasmid()` plus features added with `pp_marker()`. Positions are base pairs, and `arrow` shows which strand a feature is on: `"end"` points clockwise (forward strand), `"start"` counter-clockwise (reverse). ```{r scratch} p <- plasmid("pBR322", 4361) |> pp_marker(86, 1276, label = "TcR", arrow = "end") |> pp_marker(1915, 2106, label = "rop", arrow = "start") |> pp_marker(2535, 3122, label = "ori") |> pp_marker(3293, 4153, label = "AmpR", arrow = "start") plot(p) ``` Labels are placed by an algorithm that spreads them apart, so features close together on the backbone still get readable callouts. A feature whose `end` is before its `start` wraps the origin, which is something only a circular molecule can do: ```{r wrap} plot(pp_marker(plasmid("pWrap", 3000), 2800, 300, label = "crosses origin", arrow = "end")) ``` To add many features at once, `pp_features()` takes a data frame with `start` and `end` columns and any of `label`, `color`, `group`, `arrow`, `offset` and `width`: ```{r features} feats <- data.frame( start = c(86, 2535, 3293), end = c(1276, 3122, 4153), label = c("TcR", "ori", "AmpR"), arrow = c("end", "none", "start") ) plot(pp_features(plasmid("pBR322", 4361), feats)) ``` ## Reading a file `read_plasmid()` detects the format from the file's *content* rather than its extension, so a `.dna` file is read as SnapGene or as plain sequence depending on what it actually contains, and a GenBank record saved as `.txt` still works. ```{r read} gb <- system.file("extdata", "pDemo.gb", package = "plasmidplot") p <- read_plasmid(gb) p ``` GenBank, EMBL, FASTA, SnapGene and bare sequence are all recognized. Each also has a direct reader (`read_genbank()`, `read_embl()`, `read_fasta()`, `read_snapgene()`) when you want to be explicit. The readers handle `complement(...)` for strand, `join(...)` for features that cross the origin, and the `<`/`>` partial-boundary markers. Useful arguments: ```{r read-args, eval = FALSE} read_genbank(gb, types = "CDS", # keep only these feature types skip_types = "source", # drop these (source spans the whole molecule) label_from = c("label", "gene", "product"), # qualifier priority color_by = "feature", # or "type": one color per feature type colors = "style", # or "file": keep the colors stored in the file sequence = TRUE # keep the sequence for pp_find_sites() ) ``` The sequence is kept on the object as `p$sequence`. If it disagrees with the length the record declares -- a truncated file -- it is refused with a warning rather than used, because every position computed from it would be wrong. ## Restriction sites With a sequence in hand, `pp_find_sites()` locates cut positions and adds them as labeled ticks: ```{r sites, fig.height = 6.5} p <- pp_find_sites(p) plot(p) ``` By default only enzymes that cut **once** are shown. That is the classic plasmid-map convention: an enzyme cutting a dozen times adds no information and buries the map in labels. Relax it with `unique_only = FALSE` and `max_sites`. You can name enzymes, or give your own recognition sequences, IUPAC ambiguity codes included: ```{r sites-args, eval = FALSE} pp_find_sites(p, c("EcoRI", "BamHI")) pp_find_sites(p, c(MyEnz = "GGWCC")) pp_find_sites(p, unique_only = FALSE, max_sites = 3) ``` `pp_enzymes()` lists the built-in table. On a circular molecule the search wraps the origin, so a site straddling position 1 is not missed. ## Styles are shape parameters A style is seven **shape** parameters. The presets are named combinations of them and nothing more -- anything a preset expresses can be written by hand. | parameter | meaning | |---|---| | `layout` | `"auto"` (follow the molecule), `"circular"`, `"linear"` | | `anchor` | features `"center"`ed on the backbone, `"outside"` it, or `"inside"` | | `backbone` | `"ring"` (a band), `"line"` (a stroke), `"none"` | | `radius` | how much room the map takes | | `track` | backbone thickness | | `arc` | feature thickness | | `gap` | clearance when `anchor` is not `"center"` | ```{r style-scratch} plot(p, style = pp_style(anchor = "outside", backbone = "line", radius = 0.26, arc = 0.05)) ``` Or start from a preset and change one thing: ```{r style-preset} plot(p, style = pp_style("minimal", arc = 0.05)) ``` `pp_style()` with no arguments lists the presets: ```{r presets} pp_style() ``` Styles never paint a background -- whatever the device or surrounding viewport has shows through, so a map dropped into a panel layout will not cover its neighbours with a white rectangle. The dark presets need a dark ground to be legible, and `pp_canvas()` returns the one they were designed against: ```{r canvas} plot(p, style = "neon", bg = pp_canvas("neon")) ``` ## Circular and linear `layout = "auto"` follows the molecule's topology, which the readers parse. Force it either way when you want to: ```{r linear, fig.width = 8, fig.height = 4} plot(p, style = pp_style("angular", layout = "linear")) ``` The shape parameters mean the same thing in both layouts -- `anchor = "outside"` is outward on a circle and above the line on a linear map -- so a style stays recognizable when the layout changes. A feature that wraps the origin is drawn as the two pieces it would occupy, since a linear molecule cannot have one. ## Palettes are a separate choice Colors are not part of the style's shape. Any style can take any palette: ```{r palettes} pp_palette() plot(p, style = pp_style("angular", palette = "jewel")) ``` `palette` also accepts a plain vector of colors, or a **function** of `n` returning `n` colors -- which is the shape `ggsci`, `RColorBrewer`, `scales` and `viridis` all expose, so those drop in with no adapter: ```{r external, eval = FALSE} plot(p, style = pp_style("angular", palette = ggsci::pal_npg("nrc"))) plot(p, style = pp_style("angular", palette = RColorBrewer::brewer.pal(8, "Dark2"))) ``` A function is preferable to a fixed vector, because it is called with the number of colors the map actually needs: twelve features get twelve distinct colors instead of a cycled eight. ### Checking a palette The built-in palettes were checked pair by pair under simulated protanopia, deuteranopia and tritanopia. `pp_check_palette()` holds any palette you bring to the same bar: ```{r check} pp_check_palette("default") ``` Two of the five checks -- CVD separation and the normal-vision floor -- decide whether a reader can tell two features apart at all. The other three govern how the palette looks against the surface. The printed summary says which kind failed, because palettes from other packages often miss the cosmetic bands while remaining perfectly readable. ## Panels `newpage = FALSE` draws into an existing viewport: ```{r panels, fig.width = 8, fig.height = 4} grid::grid.newpage() grid::pushViewport(grid::viewport(layout = grid::grid.layout(1, 2))) grid::pushViewport(grid::viewport(layout.pos.col = 1)) plot(p, style = pp_style("angular", show_title = FALSE), newpage = FALSE) grid::popViewport() grid::pushViewport(grid::viewport(layout.pos.col = 2)) plot(p, style = pp_style("minimal", show_title = FALSE), newpage = FALSE) grid::popViewport(2) ``` ## Non-ASCII labels Labels take any Unicode text, but the `png()` device bundled with Windows fails to render it outside a UTF-8 locale. Use `ragg` or a cairo device instead: ```{r unicode, eval = FALSE} ragg::agg_png("plasmid.png", width = 1400, height = 1400, res = 220) plot(p) dev.off() ```