diff --git a/NAMESPACE b/NAMESPACE
index 2071390f..b985d82a 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -10,6 +10,7 @@ export(plt_add)
 export(tinyplot)
 export(tinyplot_add)
 export(tpar)
+export(type_abline)
 export(type_area)
 export(type_boxplot)
 export(type_errorbar)
@@ -17,6 +18,7 @@ export(type_function)
 export(type_glm)
 export(type_hist)
 export(type_histogram)
+export(type_hline)
 export(type_jitter)
 export(type_lines)
 export(type_lm)
@@ -30,6 +32,7 @@ export(type_ribbon)
 export(type_segments)
 export(type_spineplot)
 export(type_spline)
+export(type_vline)
 importFrom(grDevices,adjustcolor)
 importFrom(grDevices,as.raster)
 importFrom(grDevices,col2rgb)
diff --git a/NEWS.md b/NEWS.md
index 869b1627..b998b8a5 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -12,8 +12,8 @@ Breaking changes:
 plot type) should no longer be passed via `...` in the main plotting function.
 Instead, these additional arguments must now be passed explicitly as part of the
 corresponding `type_*()` function in the `type` argument. So, for example, you
-should now use `plt(Nile, type = type_histogram(breaks = 30))` instead of
-`plt(Nile, type = "histogram", breaks = 30)` if you wanted to adjust the number
+should now use `tinyplot(Nile, type = type_histogram(breaks = 30))` instead of
+`tinyplot(Nile, type = "histogram", breaks = 30)` if you wanted to adjust the number
 of breaks. More details are provided below and also in the new dedicated
 [Plot types vignette](https://grantmcdermott.com/tinyplot/vignettes/types.html).
 The essential idea is that shortcut character types (here: `"histogram"`) all
@@ -23,7 +23,7 @@ behaviour by passing appropriate arguments. We're sorry to introduce a breaking
 change, but this new approach should ensure that users have better control of
 how their plots behave, and avoids guesswork on our side.
 - `ribbon.alpha` is deprecated in `tinyplot()`. Use the `alpha` argument of the
-`type_ribbon()` function instead: `plt(..., type = type_ribbon(alpha = 0.5))`
+`type_ribbon()` function instead: `tinyplot(..., type = type_ribbon(alpha = 0.5))`
 
 New features:
 
@@ -35,6 +35,12 @@ types enable a variety of additional features. (#222 @vincentarelbundock)
     - `type_lm()` (shortcut: `"lm"`)
     - `type_loess()` (shortcut: `"loess"`)
     - `type_spline()` (shortcut: `"spline"`)
+  - New function-based types
+    - `type_abline()`: line with intercept and slope
+    - `type_hline()`: horizontal line
+    - `type_vline()`: verticla line
+    - `type_function()` arbitrary function.
+  - `type_spineplot()` (shortcut: `"spineplot"`) type for producing spine
   - `type_function()` can trace arbitrary functions.
   - New `type_spineplot()` (shortcut: `"spineplot"`) type for producing spine
   plots and spinograms. These are modified versions of a histogram or mosaic
@@ -49,11 +55,11 @@ types enable a variety of additional features. (#222 @vincentarelbundock)
   on the website.)
 - The new `flip` argument allows for easily flipping (swapping) the orientation
   of the x and y axes. This should work regardless of plot type, e.g.
-  `plt(~Sepal.Length | Species, data = iris, type = "density", flip = TRUE)`.
+  `tinyplot(~Sepal.Length | Species, data = iris, type = "density", flip = TRUE)`.
   (#216 @grantmcdermott)
 - `tpar()` gains additional `grid.col`, `grid.lty`, and `grid.lwd` arguments for
   fine-grained control over the appearance of the default panel grid when
-  `plt(..., grid = TRUE)` is called. (#237 @grantmcdermott)
+  `tinyplot(..., grid = TRUE)` is called. (#237 @grantmcdermott)
 - The new `tinyplot_add()` (alias: `plt_add()`) convenience function allows
 easy layering of plots without having to specify repeat arguments. (#246
 @vincentarelbundock)
@@ -229,7 +235,7 @@ adding transparency to plot elements and colours. Example use:
 background fill by passing `bg` (or its alias, `fill`) a numeric in the range
 `[0,1]`. This feature has the same effect as `bg = "by"` except for the added
 transparency. Example use:
-`plt(lat ~ long | depth, data = quakes, pch = 21, cex = 2, bg = 0.2)`. (#129
+`tinyplot(lat ~ long | depth, data = quakes, pch = 21, cex = 2, bg = 0.2)`. (#129
 @grantmcdermott)
 
 
diff --git a/R/assertions.R b/R/assertions.R
index 118637e9..5e48f97d 100644
--- a/R/assertions.R
+++ b/R/assertions.R
@@ -29,6 +29,23 @@ assert_choice = function(x, choice, null.ok = FALSE, name = as.character(substit
   stop(msg, call. = FALSE)
 }
 
+check_true = function(x, null.ok = FALSE) {
+  if (is.null(x) && isTRUE(null.ok)) {
+    return(invisible(TRUE))
+  }
+  if (isTRUE(x)) {
+    return(invisible(TRUE))
+  }
+  return(FALSE)
+}
+
+assert_true = function(x, null.ok = FALSE, name = as.character(substitute(x))) {
+  msg = sprintf("`%s` must be true.", name)
+  if (!isTRUE(check_true(x, null.ok = null.ok))) {
+    stop(msg, call. = FALSE)
+  }
+}
+
 check_string = function(x, null.ok = FALSE) {
   if (is.null(x) && isTRUE(null.ok)) {
     return(invisible(TRUE))
diff --git a/R/type_abline.R b/R/type_abline.R
new file mode 100644
index 00000000..e7b6e146
--- /dev/null
+++ b/R/type_abline.R
@@ -0,0 +1,79 @@
+#' Add straight lines to a plot
+#'
+#' @inheritParams graphics::abline
+#' @inheritParams tinyplot
+#' @examples
+#' mod = lm(mpg ~ hp, data = mtcars)
+#' y = mtcars$mpg
+#' yhat = predict(mod)
+#' tinyplot(y, yhat, xlim = c(0, 40), ylim = c(0, 40))
+#' tinyplot_add(type = type_abline(a = 0, b = 1))
+#' @export
+type_abline = function(a = 0, b = 1, col = NULL, lty = NULL, lwd = NULL) {
+  data_abline = function(datapoints, ...) {
+    if (nrow(datapoints) == 0) {
+      msg = "`type_abline() only works on existing plots with x and y data points."
+      stop(msg, call. = FALSE)
+    }
+    return(list())
+  }
+  draw_abline = function() {
+    fun = function(ifacet, data_facet, icol, ilty, ilwd, ...) {
+      nfacets = length(data_facet)
+
+      if (length(a) == 1) {
+        a = rep(a, nfacets)
+      } else if (length(a) != nfacets) {
+        msg = "Length of 'a' must be 1 or equal to the number of facets"
+        stop(msg, call. = FALSE)
+      }
+
+      if (length(b) == 1) {
+        b = rep(b, nfacets)
+      } else if (length(b) != nfacets) {
+        msg = "Length of 'b' must be 1 or equal to the number of facets"
+        stop(msg, call. = FALSE)
+      }
+
+      if (is.null(col)) {
+        col = icol
+      }
+      if (length(col) == 1) {
+        col = rep(col, nfacets)
+      } else if (length(col) != nfacets) {
+        msg = "Length of 'col' must be 1 or equal to the number of facets"
+        stop(msg, call. = FALSE)
+      }
+
+      if (is.null(lty)) {
+        lty = if (!is.null(ilty)) ilty else 1
+      }
+      if (length(lty) == 1) {
+        lty = rep(lty, nfacets)
+      } else if (length(lty) != nfacets) {
+        msg = "Length of 'lty' must be 1 or equal to the number of facets"
+        stop(msg, call. = FALSE)
+      }
+
+      if (is.null(lwd)) {
+        lwd = if (!is.null(ilwd)) ilwd else 1
+      }
+      if (length(lwd) == 1) {
+        lwd = rep(lwd, nfacets)
+      } else if (length(lwd) != nfacets) {
+        msg = "Length of 'lwd' must be 1 or equal to the number of facets"
+        stop(msg, call. = FALSE)
+      }
+
+      abline(a = a[ifacet], b = b[ifacet], col = col[ifacet], lty = lty[ifacet], lwd = lwd[ifacet])
+    }
+    return(fun)
+  }
+  out = list(
+    draw = draw_abline(),
+    data = data_abline,
+    name = "abline"
+  )
+  class(out) = "tinyplot_type"
+  return(out)
+}
diff --git a/R/type_hline.R b/R/type_hline.R
new file mode 100644
index 00000000..77d81716
--- /dev/null
+++ b/R/type_hline.R
@@ -0,0 +1,70 @@
+#' Trace a horizontal line on the plot
+#'
+#' @param h y-value(s) for horizontal line(s). Numeric of length 1 or equal to the number of facets.
+#' @inheritParams graphics::abline
+#' @inheritParams tinyplot
+#' @examples
+#' tinyplot(mpg ~ hp | factor(cyl), facet = ~ factor(cyl), data = mtcars)
+#' tinyplot_add(type = type_hline(h = 12, col = "pink", lty = 3, lwd = 3))
+#' @export
+type_hline = function(h = 0, col = NULL, lty = NULL, lwd = NULL) {
+  data_hline = function(datapoints, ...) {
+    if (nrow(datapoints) == 0) {
+      msg = "`type_hline() only works on existing plots with x and y data points."
+      stop(msg, call. = FALSE)
+    }
+    return(list())
+  }
+  draw_hline = function() {
+    fun = function(ifacet, data_facet, icol, ilty, ilwd, ...) {
+      nfacets = length(data_facet)
+
+      if (length(h) == 1) {
+        h = rep(h, nfacets)
+      } else if (length(h) != nfacets) {
+        msg = "Length of 'h' must be 1 or equal to the number of facets"
+        stop(msg, call. = FALSE)
+      }
+
+      if (is.null(col)) {
+        col = icol
+      }
+      if (length(col) == 1) {
+        col = rep(col, nfacets)
+      } else if (length(col) != nfacets) {
+        msg = "Length of 'col' must be 1 or equal to the number of facets"
+        stop(msg, call. = FALSE)
+      }
+
+      if (is.null(lty)) {
+        lty = if (!is.null(ilty)) ilty else 1
+      }
+      if (length(lty) == 1) {
+        lty = rep(lty, nfacets)
+      } else if (length(lty) != nfacets) {
+        msg = "Length of 'lty' must be 1 or equal to the number of facets"
+        stop(msg, call. = FALSE)
+      }
+
+      if (is.null(lwd)) {
+        lwd = if (!is.null(ilwd)) ilwd else 1
+      }
+      if (length(lwd) == 1) {
+        lwd = rep(lwd, nfacets)
+      } else if (length(lwd) != nfacets) {
+        msg = "Length of 'lwd' must be 1 or equal to the number of facets"
+        stop(msg, call. = FALSE)
+      }
+
+      abline(h = h[ifacet], col = col[ifacet], lty = lty[ifacet], lwd = lwd[ifacet])
+    }
+    return(fun)
+  }
+  out = list(
+    draw = draw_hline(),
+    data = data_hline,
+    name = "hline"
+  )
+  class(out) = "tinyplot_type"
+  return(out)
+}
diff --git a/R/type_vline.R b/R/type_vline.R
new file mode 100644
index 00000000..a581487e
--- /dev/null
+++ b/R/type_vline.R
@@ -0,0 +1,77 @@
+#' Trace a vertical line on the plot
+#'
+#' @param v x-value(s) for vertical line(s). Numeric of length 1 or equal to the number of facets.
+#' @inheritParams tinyplot
+#' @examples
+#' tinyplot(mpg ~ hp, data = mtcars)
+#' tinyplot_add(type = type_vline(150))
+#'
+#' # facet-specify location and colors
+#' cols = c("black", "green", "orange")
+#' tinyplot(mpg ~ hp | factor(cyl), facet = ~ factor(cyl), data = mtcars, col = cols)
+#' tinyplot_add(type = type_vline(
+#'   v = c(100, 150, 200), col = cols, lty = 3, lwd = 3
+#' ))
+#' @export
+type_vline = function(v = 0, col = "black", lty = 1, lwd = 1) {
+  assert_numeric(v)
+  data_vline = function(datapoints, ...) {
+    if (nrow(datapoints) == 0) {
+      msg = "`type_vline() only works on existing plots with x and y data points."
+      stop(msg, call. = FALSE)
+    }
+    return(list())
+  }
+  draw_vline = function() {
+    fun = function(ifacet, data_facet, icol, ilty, ilwd, ...) {
+      nfacets = length(data_facet)
+
+      if (length(v) == 1) {
+        v = rep(v, nfacets)
+      } else if (length(v) != nfacets) {
+        msg = "Length of 'v' must be 1 or equal to the number of facets"
+        stop(msg, call. = FALSE)
+      }
+
+      if (is.null(col)) {
+        col = icol
+      }
+      if (length(col) == 1) {
+        col = rep(col, nfacets)
+      } else if (length(col) != nfacets) {
+        msg = "Length of 'col' must be 1 or equal to the number of facets"
+        stop(msg, call. = FALSE)
+      }
+
+      if (is.null(lty)) {
+        lty = if (!is.null(ilty)) ilty else 1
+      }
+      if (length(lty) == 1) {
+        lty = rep(lty, nfacets)
+      } else if (length(lty) != nfacets) {
+        msg = "Length of 'lty' must be 1 or equal to the number of facets"
+        stop(msg, call. = FALSE)
+      }
+
+      if (is.null(lwd)) {
+        lwd = if (!is.null(ilwd)) ilwd else 1
+      }
+      if (length(lwd) == 1) {
+        lwd = rep(lwd, nfacets)
+      } else if (length(lwd) != nfacets) {
+        msg = "Length of 'lwd' must be 1 or equal to the number of facets"
+        stop(msg, call. = FALSE)
+      }
+
+      abline(v = v[ifacet], col = col[ifacet], lty = lty[ifacet], lwd = lwd[ifacet])
+    }
+    return(fun)
+  }
+  out = list(
+    draw = draw_vline(),
+    data = data_vline,
+    name = "vline"
+  )
+  class(out) = "tinyplot_type"
+  return(out)
+}
diff --git a/altdoc/quarto_website.yml b/altdoc/quarto_website.yml
index b8a0a49f..dd2e97ca 100644
--- a/altdoc/quarto_website.yml
+++ b/altdoc/quarto_website.yml
@@ -23,7 +23,6 @@ website:
           file: vignettes/types.qmd
         - text: Gallery
           file: vignettes/gallery.qmd
-      # - section: $ALTDOC_MAN_BLOCK
       - section: Reference
         contents:
         - section: Plotting functions
@@ -36,12 +35,15 @@ website:
             file: man/draw_legend.qmd
         - section: Plot types
           contents:
-          #
           - section: Shapes
             contents:
-            - text: type_area, type_ribbon
+            - text: type_area
               file: man/type_ribbon.qmd
-            - text: type_errorbar, type_pointrange
+            - text: type_ribbon
+              file: man/type_ribbon.qmd
+            - text: type_errorbar
+              file: man/type_errorbar.qmd
+            - text: type_pointrange
               file: man/type_errorbar.qmd
             - text: type_lines
               file: man/type_lines.qmd
@@ -55,7 +57,6 @@ website:
               file: man/type_rect.qmd
             - text: type_segments
               file: man/type_segments.qmd
-          #
           - section: Visualizations
             contents:
             - text: type_boxplot
@@ -66,7 +67,14 @@ website:
               file: man/type_jitter.qmd
             - text: type_spineplot
               file: man/type_spineplot.qmd
-          #
+          - section: Functions
+            contents:
+            - text: type_abline
+              file: man/type_abline.qmd
+            - text: type_hline
+              file: man/type_hline.qmd
+            - text: type_vline
+              file: man/type_vline.qmd
           - section: Models
             contents:
             - text: type_glm
diff --git a/inst/tinytest/_tinysnapshot/hline.svg b/inst/tinytest/_tinysnapshot/hline.svg
new file mode 100644
index 00000000..af5b5c31
--- /dev/null
+++ b/inst/tinytest/_tinysnapshot/hline.svg
@@ -0,0 +1,232 @@
+<?xml version='1.0' encoding='UTF-8' ?>
+<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' class='svglite' width='504.00pt' height='504.00pt' viewBox='0 0 504.00 504.00'>
+<defs>
+  <style type='text/css'><![CDATA[
+    .svglite line, .svglite polyline, .svglite polygon, .svglite path, .svglite rect, .svglite circle {
+      fill: none;
+      stroke: #000000;
+      stroke-linecap: round;
+      stroke-linejoin: round;
+      stroke-miterlimit: 10.00;
+    }
+    .svglite text {
+      white-space: pre;
+    }
+  ]]></style>
+</defs>
+<rect width='100%' height='100%' style='stroke: none; fill: #FFFFFF;'/>
+<defs>
+  <clipPath id='cpMC4wMHw1MDQuMDB8MC4wMHw1MDQuMDA='>
+    <rect x='0.00' y='0.00' width='504.00' height='504.00' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpMC4wMHw1MDQuMDB8MC4wMHw1MDQuMDA=)'>
+<circle cx='467.48' cy='237.60' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='467.48' cy='252.00' r='1.78' style='stroke-width: 0.75; stroke: #DF536B;' />
+<circle cx='467.48' cy='266.40' r='1.78' style='stroke-width: 0.75; stroke: #61D04F;' />
+<text x='473.52' y='223.20' text-anchor='middle' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='52.68px' lengthAdjust='spacingAndGlyphs'>factor(cyl)</text>
+<text x='478.28' y='241.73' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='6.67px' lengthAdjust='spacingAndGlyphs'>4</text>
+<text x='478.28' y='256.13' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='6.67px' lengthAdjust='spacingAndGlyphs'>6</text>
+<text x='478.28' y='270.53' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='6.67px' lengthAdjust='spacingAndGlyphs'>8</text>
+</g>
+<defs>
+  <clipPath id='cpMC4wMHw0MzAuMDh8MC4wMHw1MDQuMDA='>
+    <rect x='0.00' y='0.00' width='430.08' height='504.00' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpMC4wMHw0MzAuMDh8MC4wMHw1MDQuMDA=)'>
+<text x='244.56' y='485.28' text-anchor='middle' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='13.35px' lengthAdjust='spacingAndGlyphs'>hp</text>
+<text transform='translate(12.96,244.80) rotate(-90)' text-anchor='middle' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='23.35px' lengthAdjust='spacingAndGlyphs'>mpg</text>
+</g>
+<defs>
+  <clipPath id='cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng=='>
+    <rect x='59.04' y='59.04' width='107.36' height='371.52' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng==)'>
+</g>
+<g clip-path='url(#cpMC4wMHw1MDQuMDB8MC4wMHw1MDQuMDA=)'>
+<line x1='62.31' y1='430.56' x2='150.13' y2='430.56' style='stroke-width: 0.75;' />
+<line x1='62.31' y1='430.56' x2='62.31' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='79.88' y1='430.56' x2='79.88' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='97.44' y1='430.56' x2='97.44' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='115.00' y1='430.56' x2='115.00' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='132.57' y1='430.56' x2='132.57' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='150.13' y1='430.56' x2='150.13' y2='435.31' style='stroke-width: 0.75;' />
+<text x='62.31' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>50</text>
+<text x='97.44' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='13.20px' lengthAdjust='spacingAndGlyphs'>150</text>
+<text x='132.57' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='13.20px' lengthAdjust='spacingAndGlyphs'>250</text>
+<line x1='59.04' y1='422.66' x2='59.04' y2='129.89' style='stroke-width: 0.75;' />
+<line x1='59.04' y1='422.66' x2='54.29' y2='422.66' style='stroke-width: 0.75;' />
+<line x1='59.04' y1='349.46' x2='54.29' y2='349.46' style='stroke-width: 0.75;' />
+<line x1='59.04' y1='276.27' x2='54.29' y2='276.27' style='stroke-width: 0.75;' />
+<line x1='59.04' y1='203.08' x2='54.29' y2='203.08' style='stroke-width: 0.75;' />
+<line x1='59.04' y1='129.89' x2='54.29' y2='129.89' style='stroke-width: 0.75;' />
+<text transform='translate(47.64,422.66) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>10</text>
+<text transform='translate(47.64,349.46) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>15</text>
+<text transform='translate(47.64,276.27) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>20</text>
+<text transform='translate(47.64,203.08) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>25</text>
+<text transform='translate(47.64,129.89) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>30</text>
+<rect x='59.04' y='43.20' width='107.36' height='15.84' style='stroke-width: 0.75; stroke: none;' />
+<text x='112.72' y='55.24' text-anchor='middle' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='6.67px' lengthAdjust='spacingAndGlyphs'>4</text>
+<polygon points='59.04,430.56 166.40,430.56 166.40,59.04 59.04,59.04 ' style='stroke-width: 0.75;' />
+</g>
+<defs>
+  <clipPath id='cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY='>
+    <rect x='190.88' y='59.04' width='107.36' height='371.52' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpMC4wMHw1MDQuMDB8MC4wMHw1MDQuMDA=)'>
+<line x1='194.15' y1='430.56' x2='281.97' y2='430.56' style='stroke-width: 0.75;' />
+<line x1='194.15' y1='430.56' x2='194.15' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='211.72' y1='430.56' x2='211.72' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='229.28' y1='430.56' x2='229.28' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='246.84' y1='430.56' x2='246.84' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='264.41' y1='430.56' x2='264.41' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='281.97' y1='430.56' x2='281.97' y2='435.31' style='stroke-width: 0.75;' />
+<text x='194.15' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>50</text>
+<text x='229.28' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='13.20px' lengthAdjust='spacingAndGlyphs'>150</text>
+<text x='264.41' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='13.20px' lengthAdjust='spacingAndGlyphs'>250</text>
+<line x1='190.88' y1='422.66' x2='190.88' y2='129.89' style='stroke-width: 0.75;' />
+<line x1='190.88' y1='422.66' x2='186.13' y2='422.66' style='stroke-width: 0.75;' />
+<line x1='190.88' y1='349.46' x2='186.13' y2='349.46' style='stroke-width: 0.75;' />
+<line x1='190.88' y1='276.27' x2='186.13' y2='276.27' style='stroke-width: 0.75;' />
+<line x1='190.88' y1='203.08' x2='186.13' y2='203.08' style='stroke-width: 0.75;' />
+<line x1='190.88' y1='129.89' x2='186.13' y2='129.89' style='stroke-width: 0.75;' />
+<text transform='translate(179.47,422.66) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>10</text>
+<text transform='translate(179.47,349.46) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>15</text>
+<text transform='translate(179.47,276.27) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>20</text>
+<text transform='translate(179.47,203.08) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>25</text>
+<text transform='translate(179.47,129.89) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>30</text>
+<rect x='190.88' y='43.20' width='107.36' height='15.84' style='stroke-width: 0.75; stroke: none;' />
+<text x='244.56' y='55.24' text-anchor='middle' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='6.67px' lengthAdjust='spacingAndGlyphs'>6</text>
+<polygon points='190.88,430.56 298.24,430.56 298.24,59.04 190.88,59.04 ' style='stroke-width: 0.75;' />
+</g>
+<defs>
+  <clipPath id='cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY='>
+    <rect x='322.72' y='59.04' width='107.36' height='371.52' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpMC4wMHw1MDQuMDB8MC4wMHw1MDQuMDA=)'>
+<line x1='325.99' y1='430.56' x2='413.81' y2='430.56' style='stroke-width: 0.75;' />
+<line x1='325.99' y1='430.56' x2='325.99' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='343.56' y1='430.56' x2='343.56' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='361.12' y1='430.56' x2='361.12' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='378.68' y1='430.56' x2='378.68' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='396.24' y1='430.56' x2='396.24' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='413.81' y1='430.56' x2='413.81' y2='435.31' style='stroke-width: 0.75;' />
+<text x='325.99' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>50</text>
+<text x='361.12' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='13.20px' lengthAdjust='spacingAndGlyphs'>150</text>
+<text x='396.24' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='13.20px' lengthAdjust='spacingAndGlyphs'>250</text>
+<line x1='322.72' y1='422.66' x2='322.72' y2='129.89' style='stroke-width: 0.75;' />
+<line x1='322.72' y1='422.66' x2='317.97' y2='422.66' style='stroke-width: 0.75;' />
+<line x1='322.72' y1='349.46' x2='317.97' y2='349.46' style='stroke-width: 0.75;' />
+<line x1='322.72' y1='276.27' x2='317.97' y2='276.27' style='stroke-width: 0.75;' />
+<line x1='322.72' y1='203.08' x2='317.97' y2='203.08' style='stroke-width: 0.75;' />
+<line x1='322.72' y1='129.89' x2='317.97' y2='129.89' style='stroke-width: 0.75;' />
+<text transform='translate(311.31,422.66) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>10</text>
+<text transform='translate(311.31,349.46) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>15</text>
+<text transform='translate(311.31,276.27) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>20</text>
+<text transform='translate(311.31,203.08) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>25</text>
+<text transform='translate(311.31,129.89) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>30</text>
+<rect x='322.72' y='43.20' width='107.36' height='15.84' style='stroke-width: 0.75; stroke: none;' />
+<text x='376.40' y='55.24' text-anchor='middle' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='6.67px' lengthAdjust='spacingAndGlyphs'>8</text>
+<polygon points='322.72,430.56 430.08,430.56 430.08,59.04 322.72,59.04 ' style='stroke-width: 0.75;' />
+</g>
+<g clip-path='url(#cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng==)'>
+<circle cx='77.42' cy='235.29' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='66.53' cy='211.86' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='78.12' cy='235.29' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='67.93' cy='94.76' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='63.02' cy='124.03' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='67.58' cy='72.80' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='78.82' cy='254.31' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='67.93' cy='169.41' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='76.72' cy='188.44' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='84.44' cy='124.03' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='83.04' cy='255.78' r='1.78' style='stroke-width: 0.75;' />
+</g>
+<g clip-path='url(#cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng==)'>
+</g>
+<g clip-path='url(#cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY=)'>
+<circle cx='215.23' cy='261.63' r='1.78' style='stroke-width: 0.75; stroke: #DF536B;' />
+<circle cx='215.23' cy='261.63' r='1.78' style='stroke-width: 0.75; stroke: #DF536B;' />
+<circle cx='215.23' cy='255.78' r='1.78' style='stroke-width: 0.75; stroke: #DF536B;' />
+<circle cx='213.47' cy='304.09' r='1.78' style='stroke-width: 0.75; stroke: #DF536B;' />
+<circle cx='219.79' cy='287.98' r='1.78' style='stroke-width: 0.75; stroke: #DF536B;' />
+<circle cx='219.79' cy='308.48' r='1.78' style='stroke-width: 0.75; stroke: #DF536B;' />
+<circle cx='238.06' cy='280.66' r='1.78' style='stroke-width: 0.75; stroke: #DF536B;' />
+</g>
+<g clip-path='url(#cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng==)'>
+</g>
+<g clip-path='url(#cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY=)'>
+<circle cx='369.90' cy='295.30' r='1.78' style='stroke-width: 0.75; stroke: #61D04F;' />
+<circle cx='394.49' cy='359.71' r='1.78' style='stroke-width: 0.75; stroke: #61D04F;' />
+<circle cx='371.66' cy='328.97' r='1.78' style='stroke-width: 0.75; stroke: #61D04F;' />
+<circle cx='371.66' cy='315.80' r='1.78' style='stroke-width: 0.75; stroke: #61D04F;' />
+<circle cx='371.66' cy='346.54' r='1.78' style='stroke-width: 0.75; stroke: #61D04F;' />
+<circle cx='380.44' cy='416.80' r='1.78' style='stroke-width: 0.75; stroke: #61D04F;' />
+<circle cx='383.95' cy='416.80' r='1.78' style='stroke-width: 0.75; stroke: #61D04F;' />
+<circle cx='389.22' cy='353.86' r='1.78' style='stroke-width: 0.75; stroke: #61D04F;' />
+<circle cx='361.12' cy='342.14' r='1.78' style='stroke-width: 0.75; stroke: #61D04F;' />
+<circle cx='361.12' cy='346.54' r='1.78' style='stroke-width: 0.75; stroke: #61D04F;' />
+<circle cx='394.49' cy='374.35' r='1.78' style='stroke-width: 0.75; stroke: #61D04F;' />
+<circle cx='369.90' cy='287.98' r='1.78' style='stroke-width: 0.75; stroke: #61D04F;' />
+<circle cx='401.16' cy='337.75' r='1.78' style='stroke-width: 0.75; stroke: #61D04F;' />
+<circle cx='426.10' cy='349.46' r='1.78' style='stroke-width: 0.75; stroke: #61D04F;' />
+</g>
+<defs>
+  <clipPath id='cpMTI5Ljk1fDM4MC4zMHwxMDguMTN8MzM3Ljgz'>
+    <rect x='129.95' y='108.13' width='250.35' height='229.70' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpMTI5Ljk1fDM4MC4zMHwxMDguMTN8MzM3Ljgz)'>
+</g>
+<defs>
+  <clipPath id='cpMzQ4LjI0fDQ5MS43NnwyNi42NHw0OTEuNzY='>
+    <rect x='348.24' y='26.64' width='143.52' height='465.12' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpMzQ4LjI0fDQ5MS43NnwyNi42NHw0OTEuNzY=)'>
+</g>
+<g clip-path='url(#cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng==)'>
+<line x1='59.04' y1='393.38' x2='166.40' y2='393.38' style='stroke-width: 2.25; stroke: #FFC0CB; stroke-dasharray: 3.00,9.00;' />
+</g>
+<g clip-path='url(#cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng==)'>
+</g>
+<g clip-path='url(#cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY=)'>
+<line x1='190.88' y1='393.38' x2='298.24' y2='393.38' style='stroke-width: 2.25; stroke: #FFC0CB; stroke-dasharray: 3.00,9.00;' />
+</g>
+<g clip-path='url(#cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng==)'>
+</g>
+<g clip-path='url(#cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY=)'>
+<line x1='322.72' y1='393.38' x2='430.08' y2='393.38' style='stroke-width: 2.25; stroke: #FFC0CB; stroke-dasharray: 3.00,9.00;' />
+</g>
+<defs>
+  <clipPath id='cpMTA1Ljg0fDQxMi4wOHw5MS40NHwzNjkuMzY='>
+    <rect x='105.84' y='91.44' width='306.24' height='277.92' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpMTA1Ljg0fDQxMi4wOHw5MS40NHwzNjkuMzY=)'>
+</g>
+</svg>
diff --git a/inst/tinytest/_tinysnapshot/vline_vector.svg b/inst/tinytest/_tinysnapshot/vline_vector.svg
new file mode 100644
index 00000000..98f670c8
--- /dev/null
+++ b/inst/tinytest/_tinysnapshot/vline_vector.svg
@@ -0,0 +1,232 @@
+<?xml version='1.0' encoding='UTF-8' ?>
+<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' class='svglite' width='504.00pt' height='504.00pt' viewBox='0 0 504.00 504.00'>
+<defs>
+  <style type='text/css'><![CDATA[
+    .svglite line, .svglite polyline, .svglite polygon, .svglite path, .svglite rect, .svglite circle {
+      fill: none;
+      stroke: #000000;
+      stroke-linecap: round;
+      stroke-linejoin: round;
+      stroke-miterlimit: 10.00;
+    }
+    .svglite text {
+      white-space: pre;
+    }
+  ]]></style>
+</defs>
+<rect width='100%' height='100%' style='stroke: none; fill: #FFFFFF;'/>
+<defs>
+  <clipPath id='cpMC4wMHw1MDQuMDB8MC4wMHw1MDQuMDA='>
+    <rect x='0.00' y='0.00' width='504.00' height='504.00' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpMC4wMHw1MDQuMDB8MC4wMHw1MDQuMDA=)'>
+<circle cx='467.48' cy='237.60' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='467.48' cy='252.00' r='1.78' style='stroke-width: 0.75; stroke: #00FF00;' />
+<circle cx='467.48' cy='266.40' r='1.78' style='stroke-width: 0.75; stroke: #FFA500;' />
+<text x='473.52' y='223.20' text-anchor='middle' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='52.68px' lengthAdjust='spacingAndGlyphs'>factor(cyl)</text>
+<text x='478.28' y='241.73' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='6.67px' lengthAdjust='spacingAndGlyphs'>4</text>
+<text x='478.28' y='256.13' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='6.67px' lengthAdjust='spacingAndGlyphs'>6</text>
+<text x='478.28' y='270.53' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='6.67px' lengthAdjust='spacingAndGlyphs'>8</text>
+</g>
+<defs>
+  <clipPath id='cpMC4wMHw0MzAuMDh8MC4wMHw1MDQuMDA='>
+    <rect x='0.00' y='0.00' width='430.08' height='504.00' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpMC4wMHw0MzAuMDh8MC4wMHw1MDQuMDA=)'>
+<text x='244.56' y='485.28' text-anchor='middle' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='13.35px' lengthAdjust='spacingAndGlyphs'>hp</text>
+<text transform='translate(12.96,244.80) rotate(-90)' text-anchor='middle' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='23.35px' lengthAdjust='spacingAndGlyphs'>mpg</text>
+</g>
+<defs>
+  <clipPath id='cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng=='>
+    <rect x='59.04' y='59.04' width='107.36' height='371.52' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng==)'>
+</g>
+<g clip-path='url(#cpMC4wMHw1MDQuMDB8MC4wMHw1MDQuMDA=)'>
+<line x1='62.31' y1='430.56' x2='150.13' y2='430.56' style='stroke-width: 0.75;' />
+<line x1='62.31' y1='430.56' x2='62.31' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='79.88' y1='430.56' x2='79.88' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='97.44' y1='430.56' x2='97.44' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='115.00' y1='430.56' x2='115.00' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='132.57' y1='430.56' x2='132.57' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='150.13' y1='430.56' x2='150.13' y2='435.31' style='stroke-width: 0.75;' />
+<text x='62.31' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>50</text>
+<text x='97.44' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='13.20px' lengthAdjust='spacingAndGlyphs'>150</text>
+<text x='132.57' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='13.20px' lengthAdjust='spacingAndGlyphs'>250</text>
+<line x1='59.04' y1='422.66' x2='59.04' y2='129.89' style='stroke-width: 0.75;' />
+<line x1='59.04' y1='422.66' x2='54.29' y2='422.66' style='stroke-width: 0.75;' />
+<line x1='59.04' y1='349.46' x2='54.29' y2='349.46' style='stroke-width: 0.75;' />
+<line x1='59.04' y1='276.27' x2='54.29' y2='276.27' style='stroke-width: 0.75;' />
+<line x1='59.04' y1='203.08' x2='54.29' y2='203.08' style='stroke-width: 0.75;' />
+<line x1='59.04' y1='129.89' x2='54.29' y2='129.89' style='stroke-width: 0.75;' />
+<text transform='translate(47.64,422.66) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>10</text>
+<text transform='translate(47.64,349.46) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>15</text>
+<text transform='translate(47.64,276.27) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>20</text>
+<text transform='translate(47.64,203.08) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>25</text>
+<text transform='translate(47.64,129.89) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>30</text>
+<rect x='59.04' y='43.20' width='107.36' height='15.84' style='stroke-width: 0.75; stroke: none;' />
+<text x='112.72' y='55.24' text-anchor='middle' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='6.67px' lengthAdjust='spacingAndGlyphs'>4</text>
+<polygon points='59.04,430.56 166.40,430.56 166.40,59.04 59.04,59.04 ' style='stroke-width: 0.75;' />
+</g>
+<defs>
+  <clipPath id='cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY='>
+    <rect x='190.88' y='59.04' width='107.36' height='371.52' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpMC4wMHw1MDQuMDB8MC4wMHw1MDQuMDA=)'>
+<line x1='194.15' y1='430.56' x2='281.97' y2='430.56' style='stroke-width: 0.75;' />
+<line x1='194.15' y1='430.56' x2='194.15' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='211.72' y1='430.56' x2='211.72' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='229.28' y1='430.56' x2='229.28' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='246.84' y1='430.56' x2='246.84' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='264.41' y1='430.56' x2='264.41' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='281.97' y1='430.56' x2='281.97' y2='435.31' style='stroke-width: 0.75;' />
+<text x='194.15' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>50</text>
+<text x='229.28' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='13.20px' lengthAdjust='spacingAndGlyphs'>150</text>
+<text x='264.41' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='13.20px' lengthAdjust='spacingAndGlyphs'>250</text>
+<line x1='190.88' y1='422.66' x2='190.88' y2='129.89' style='stroke-width: 0.75;' />
+<line x1='190.88' y1='422.66' x2='186.13' y2='422.66' style='stroke-width: 0.75;' />
+<line x1='190.88' y1='349.46' x2='186.13' y2='349.46' style='stroke-width: 0.75;' />
+<line x1='190.88' y1='276.27' x2='186.13' y2='276.27' style='stroke-width: 0.75;' />
+<line x1='190.88' y1='203.08' x2='186.13' y2='203.08' style='stroke-width: 0.75;' />
+<line x1='190.88' y1='129.89' x2='186.13' y2='129.89' style='stroke-width: 0.75;' />
+<text transform='translate(179.47,422.66) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>10</text>
+<text transform='translate(179.47,349.46) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>15</text>
+<text transform='translate(179.47,276.27) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>20</text>
+<text transform='translate(179.47,203.08) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>25</text>
+<text transform='translate(179.47,129.89) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>30</text>
+<rect x='190.88' y='43.20' width='107.36' height='15.84' style='stroke-width: 0.75; stroke: none;' />
+<text x='244.56' y='55.24' text-anchor='middle' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='6.67px' lengthAdjust='spacingAndGlyphs'>6</text>
+<polygon points='190.88,430.56 298.24,430.56 298.24,59.04 190.88,59.04 ' style='stroke-width: 0.75;' />
+</g>
+<defs>
+  <clipPath id='cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY='>
+    <rect x='322.72' y='59.04' width='107.36' height='371.52' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpMC4wMHw1MDQuMDB8MC4wMHw1MDQuMDA=)'>
+<line x1='325.99' y1='430.56' x2='413.81' y2='430.56' style='stroke-width: 0.75;' />
+<line x1='325.99' y1='430.56' x2='325.99' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='343.56' y1='430.56' x2='343.56' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='361.12' y1='430.56' x2='361.12' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='378.68' y1='430.56' x2='378.68' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='396.24' y1='430.56' x2='396.24' y2='435.31' style='stroke-width: 0.75;' />
+<line x1='413.81' y1='430.56' x2='413.81' y2='435.31' style='stroke-width: 0.75;' />
+<text x='325.99' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>50</text>
+<text x='361.12' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='13.20px' lengthAdjust='spacingAndGlyphs'>150</text>
+<text x='396.24' y='447.67' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='13.20px' lengthAdjust='spacingAndGlyphs'>250</text>
+<line x1='322.72' y1='422.66' x2='322.72' y2='129.89' style='stroke-width: 0.75;' />
+<line x1='322.72' y1='422.66' x2='317.97' y2='422.66' style='stroke-width: 0.75;' />
+<line x1='322.72' y1='349.46' x2='317.97' y2='349.46' style='stroke-width: 0.75;' />
+<line x1='322.72' y1='276.27' x2='317.97' y2='276.27' style='stroke-width: 0.75;' />
+<line x1='322.72' y1='203.08' x2='317.97' y2='203.08' style='stroke-width: 0.75;' />
+<line x1='322.72' y1='129.89' x2='317.97' y2='129.89' style='stroke-width: 0.75;' />
+<text transform='translate(311.31,422.66) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>10</text>
+<text transform='translate(311.31,349.46) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>15</text>
+<text transform='translate(311.31,276.27) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>20</text>
+<text transform='translate(311.31,203.08) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>25</text>
+<text transform='translate(311.31,129.89) rotate(-90)' text-anchor='middle' style='font-size: 7.92px; font-family: "Liberation Sans";' textLength='8.80px' lengthAdjust='spacingAndGlyphs'>30</text>
+<rect x='322.72' y='43.20' width='107.36' height='15.84' style='stroke-width: 0.75; stroke: none;' />
+<text x='376.40' y='55.24' text-anchor='middle' style='font-size: 12.00px; font-family: "Liberation Sans";' textLength='6.67px' lengthAdjust='spacingAndGlyphs'>8</text>
+<polygon points='322.72,430.56 430.08,430.56 430.08,59.04 322.72,59.04 ' style='stroke-width: 0.75;' />
+</g>
+<g clip-path='url(#cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng==)'>
+<circle cx='77.42' cy='235.29' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='66.53' cy='211.86' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='78.12' cy='235.29' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='67.93' cy='94.76' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='63.02' cy='124.03' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='67.58' cy='72.80' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='78.82' cy='254.31' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='67.93' cy='169.41' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='76.72' cy='188.44' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='84.44' cy='124.03' r='1.78' style='stroke-width: 0.75;' />
+<circle cx='83.04' cy='255.78' r='1.78' style='stroke-width: 0.75;' />
+</g>
+<g clip-path='url(#cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng==)'>
+</g>
+<g clip-path='url(#cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY=)'>
+<circle cx='215.23' cy='261.63' r='1.78' style='stroke-width: 0.75; stroke: #00FF00;' />
+<circle cx='215.23' cy='261.63' r='1.78' style='stroke-width: 0.75; stroke: #00FF00;' />
+<circle cx='215.23' cy='255.78' r='1.78' style='stroke-width: 0.75; stroke: #00FF00;' />
+<circle cx='213.47' cy='304.09' r='1.78' style='stroke-width: 0.75; stroke: #00FF00;' />
+<circle cx='219.79' cy='287.98' r='1.78' style='stroke-width: 0.75; stroke: #00FF00;' />
+<circle cx='219.79' cy='308.48' r='1.78' style='stroke-width: 0.75; stroke: #00FF00;' />
+<circle cx='238.06' cy='280.66' r='1.78' style='stroke-width: 0.75; stroke: #00FF00;' />
+</g>
+<g clip-path='url(#cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng==)'>
+</g>
+<g clip-path='url(#cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY=)'>
+<circle cx='369.90' cy='295.30' r='1.78' style='stroke-width: 0.75; stroke: #FFA500;' />
+<circle cx='394.49' cy='359.71' r='1.78' style='stroke-width: 0.75; stroke: #FFA500;' />
+<circle cx='371.66' cy='328.97' r='1.78' style='stroke-width: 0.75; stroke: #FFA500;' />
+<circle cx='371.66' cy='315.80' r='1.78' style='stroke-width: 0.75; stroke: #FFA500;' />
+<circle cx='371.66' cy='346.54' r='1.78' style='stroke-width: 0.75; stroke: #FFA500;' />
+<circle cx='380.44' cy='416.80' r='1.78' style='stroke-width: 0.75; stroke: #FFA500;' />
+<circle cx='383.95' cy='416.80' r='1.78' style='stroke-width: 0.75; stroke: #FFA500;' />
+<circle cx='389.22' cy='353.86' r='1.78' style='stroke-width: 0.75; stroke: #FFA500;' />
+<circle cx='361.12' cy='342.14' r='1.78' style='stroke-width: 0.75; stroke: #FFA500;' />
+<circle cx='361.12' cy='346.54' r='1.78' style='stroke-width: 0.75; stroke: #FFA500;' />
+<circle cx='394.49' cy='374.35' r='1.78' style='stroke-width: 0.75; stroke: #FFA500;' />
+<circle cx='369.90' cy='287.98' r='1.78' style='stroke-width: 0.75; stroke: #FFA500;' />
+<circle cx='401.16' cy='337.75' r='1.78' style='stroke-width: 0.75; stroke: #FFA500;' />
+<circle cx='426.10' cy='349.46' r='1.78' style='stroke-width: 0.75; stroke: #FFA500;' />
+</g>
+<defs>
+  <clipPath id='cpMTI5Ljk1fDM4MC4zMHwxMDguMTN8MzM3Ljgz'>
+    <rect x='129.95' y='108.13' width='250.35' height='229.70' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpMTI5Ljk1fDM4MC4zMHwxMDguMTN8MzM3Ljgz)'>
+</g>
+<defs>
+  <clipPath id='cpMzQ4LjI0fDQ5MS43NnwyNi42NHw0OTEuNzY='>
+    <rect x='348.24' y='26.64' width='143.52' height='465.12' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpMzQ4LjI0fDQ5MS43NnwyNi42NHw0OTEuNzY=)'>
+</g>
+<g clip-path='url(#cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng==)'>
+<line x1='79.88' y1='430.56' x2='79.88' y2='59.04' style='stroke-width: 2.25; stroke-dasharray: 3.00,9.00;' />
+</g>
+<g clip-path='url(#cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng==)'>
+</g>
+<g clip-path='url(#cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY=)'>
+<line x1='229.28' y1='430.56' x2='229.28' y2='59.04' style='stroke-width: 2.25; stroke: #00FF00; stroke-dasharray: 3.00,9.00;' />
+</g>
+<g clip-path='url(#cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpNTkuMDR8MTY2LjQwfDU5LjA0fDQzMC41Ng==)'>
+</g>
+<g clip-path='url(#cpMTkwLjg4fDI5OC4yNHw1OS4wNHw0MzAuNTY=)'>
+</g>
+<g clip-path='url(#cpMzIyLjcyfDQzMC4wOHw1OS4wNHw0MzAuNTY=)'>
+<line x1='378.68' y1='430.56' x2='378.68' y2='59.04' style='stroke-width: 2.25; stroke: #FFA500; stroke-dasharray: 3.00,9.00;' />
+</g>
+<defs>
+  <clipPath id='cpMTA1Ljg0fDQxMi4wOHw5MS40NHwzNjkuMzY='>
+    <rect x='105.84' y='91.44' width='306.24' height='277.92' />
+  </clipPath>
+</defs>
+<g clip-path='url(#cpMTA1Ljg0fDQxMi4wOHw5MS40NHwzNjkuMzY=)'>
+</g>
+</svg>
diff --git a/inst/tinytest/test-abline.R b/inst/tinytest/test-abline.R
new file mode 100644
index 00000000..98c4c92d
--- /dev/null
+++ b/inst/tinytest/test-abline.R
@@ -0,0 +1,37 @@
+source("helpers.R")
+using("tinysnapshot")
+
+expect_error(tinyplot(type = type_hline(h = 10)), pattern = "data points")
+expect_error(tinyplot(type = type_vline(v = 10)), pattern = "data points")
+expect_error(tinyplot(type = type_abline(a = 0, b = 1)), pattern = "data points")
+
+
+f = function() {
+  plt(mpg ~ hp | factor(cyl), facet = ~ factor(cyl), data = mtcars)
+  plt_add(type = type_hline(h = 12, col = "pink", lty = 3, lwd = 3))
+}
+expect_snapshot_plot(f, label = "hline")
+
+
+f = function() {
+  tinyplot(mpg ~ hp | factor(cyl),
+    facet = ~ factor(cyl), data = mtcars,
+    col = c("black", "green", "orange"))
+  tinyplot_add(type = type_vline(
+    v = c(100, 150, 200), lty = 3, lwd = 3,
+    col = c("black", "green", "orange")
+  ))
+}
+expect_snapshot_plot(f, label = "vline_vector")
+
+
+## TODO: uncomment this when ready to test. Probably after the tinyplot_add
+## refactor to save in an environment instead of global option
+# f = function() {
+#   mod = lm(mpg ~ hp, data = mtcars)
+#   y = mtcars$mpg
+#   yhat = predict(mod)
+#   tinyplot(y, yhat, type = type_abline(a = 0, b = 1), xlim = c(0, 40), ylim = c(0, 40))
+#   tinyplot_add(type = type_abline(a = 0, b = 1), xlim = c(0, 40), ylim = c(0, 40))
+# }
+# expect_snapshot_plot(f, label = "abline")
diff --git a/man/type_abline.Rd b/man/type_abline.Rd
new file mode 100644
index 00000000..a25b1c94
--- /dev/null
+++ b/man/type_abline.Rd
@@ -0,0 +1,43 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/type_abline.R
+\name{type_abline}
+\alias{type_abline}
+\title{Add straight lines to a plot}
+\usage{
+type_abline(a = 0, b = 1, col = NULL, lty = NULL, lwd = NULL)
+}
+\arguments{
+\item{a, b}{the intercept and slope, single values.}
+
+\item{col}{plotting color. Character, integer, or vector of length equal to
+the number of categories in the \code{by} variable. See \code{col}. Note that the
+default behaviour in \code{tinyplot} is to vary group colors along any variables
+declared in the \code{by} argument. Thus, specifying colors manually should not
+be necessary unless users wish to override the automatic colors produced by
+this grouping process. Typically, this would only be done if grouping
+features are deferred to some other graphical parameter (i.e., passing the
+"by" keyword to one of \code{pch}, \code{lty}, \code{lwd}, or \code{bg}; see below.)}
+
+\item{lty}{line type. Character, integer, or vector of length equal to the
+number of categories in the \code{by} variable. See \code{lty}. In addition, users
+can supply a special \code{lty = "by"} convenience argument, in which case the
+line type will automatically loop over the number groups. This automatic
+looping will begin at the global line type value (i.e., \code{par("lty")}) and
+recycle as necessary.}
+
+\item{lwd}{line width. Numeric scalar or vector of length equal to the
+number of categories in the \code{by} variable. See \code{lwd}. In addition, users
+can supply a special \code{lwd = "by"} convenience argument, in which case the
+line width will automatically loop over the number of groups. This
+automatic looping will be centered at the global line width value (i.e.,}
+}
+\description{
+Add straight lines to a plot
+}
+\examples{
+mod = lm(mpg ~ hp, data = mtcars)
+y = mtcars$mpg
+yhat = predict(mod)
+tinyplot(y, yhat, xlim = c(0, 40), ylim = c(0, 40))
+tinyplot_add(type = type_abline(a = 0, b = 1))
+}
diff --git a/man/type_hline.Rd b/man/type_hline.Rd
new file mode 100644
index 00000000..84469da7
--- /dev/null
+++ b/man/type_hline.Rd
@@ -0,0 +1,40 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/type_hline.R
+\name{type_hline}
+\alias{type_hline}
+\title{Trace a horizontal line on the plot}
+\usage{
+type_hline(h = 0, col = NULL, lty = NULL, lwd = NULL)
+}
+\arguments{
+\item{h}{y-value(s) for horizontal line(s). Numeric of length 1 or equal to the number of facets.}
+
+\item{col}{plotting color. Character, integer, or vector of length equal to
+the number of categories in the \code{by} variable. See \code{col}. Note that the
+default behaviour in \code{tinyplot} is to vary group colors along any variables
+declared in the \code{by} argument. Thus, specifying colors manually should not
+be necessary unless users wish to override the automatic colors produced by
+this grouping process. Typically, this would only be done if grouping
+features are deferred to some other graphical parameter (i.e., passing the
+"by" keyword to one of \code{pch}, \code{lty}, \code{lwd}, or \code{bg}; see below.)}
+
+\item{lty}{line type. Character, integer, or vector of length equal to the
+number of categories in the \code{by} variable. See \code{lty}. In addition, users
+can supply a special \code{lty = "by"} convenience argument, in which case the
+line type will automatically loop over the number groups. This automatic
+looping will begin at the global line type value (i.e., \code{par("lty")}) and
+recycle as necessary.}
+
+\item{lwd}{line width. Numeric scalar or vector of length equal to the
+number of categories in the \code{by} variable. See \code{lwd}. In addition, users
+can supply a special \code{lwd = "by"} convenience argument, in which case the
+line width will automatically loop over the number of groups. This
+automatic looping will be centered at the global line width value (i.e.,}
+}
+\description{
+Trace a horizontal line on the plot
+}
+\examples{
+tinyplot(mpg ~ hp | factor(cyl), facet = ~ factor(cyl), data = mtcars)
+tinyplot_add(type = type_hline(h = 12, col = "pink", lty = 3, lwd = 3))
+}
diff --git a/man/type_vline.Rd b/man/type_vline.Rd
new file mode 100644
index 00000000..d52a892c
--- /dev/null
+++ b/man/type_vline.Rd
@@ -0,0 +1,47 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/type_vline.R
+\name{type_vline}
+\alias{type_vline}
+\title{Trace a vertical line on the plot}
+\usage{
+type_vline(v = 0, col = "black", lty = 1, lwd = 1)
+}
+\arguments{
+\item{v}{x-value(s) for vertical line(s). Numeric of length 1 or equal to the number of facets.}
+
+\item{col}{plotting color. Character, integer, or vector of length equal to
+the number of categories in the \code{by} variable. See \code{col}. Note that the
+default behaviour in \code{tinyplot} is to vary group colors along any variables
+declared in the \code{by} argument. Thus, specifying colors manually should not
+be necessary unless users wish to override the automatic colors produced by
+this grouping process. Typically, this would only be done if grouping
+features are deferred to some other graphical parameter (i.e., passing the
+"by" keyword to one of \code{pch}, \code{lty}, \code{lwd}, or \code{bg}; see below.)}
+
+\item{lty}{line type. Character, integer, or vector of length equal to the
+number of categories in the \code{by} variable. See \code{lty}. In addition, users
+can supply a special \code{lty = "by"} convenience argument, in which case the
+line type will automatically loop over the number groups. This automatic
+looping will begin at the global line type value (i.e., \code{par("lty")}) and
+recycle as necessary.}
+
+\item{lwd}{line width. Numeric scalar or vector of length equal to the
+number of categories in the \code{by} variable. See \code{lwd}. In addition, users
+can supply a special \code{lwd = "by"} convenience argument, in which case the
+line width will automatically loop over the number of groups. This
+automatic looping will be centered at the global line width value (i.e.,}
+}
+\description{
+Trace a vertical line on the plot
+}
+\examples{
+tinyplot(mpg ~ hp, data = mtcars)
+tinyplot_add(type = type_vline(150))
+
+# facet-specify location and colors
+cols = c("black", "green", "orange")
+tinyplot(mpg ~ hp | factor(cyl), facet = ~ factor(cyl), data = mtcars, col = cols)
+tinyplot_add(type = type_vline(
+  v = c(100, 150, 200), col = cols, lty = 3, lwd = 3
+))
+}
