The error message plot.Because of that, understanding why this happens requires a grasp of how R’s base graphics system manages the "current device" and the plotting region. It typically appears when you attempt to add elements to a plot—such as points, lines, text, or a legend—before a plotting window or device has actually been initialized. Here's the thing — new has not been called yet is one of the most common stumbling blocks for R users, ranging from absolute beginners to seasoned data scientists. This article breaks down the causes, the mechanics behind the scenes, and the practical solutions to resolve and prevent this frustrating error.
Understanding the Base Graphics Pipeline
To fix the error, you first need to understand the architecture of R’s base graphics system. Unlike ggplot2, which builds a complete plot object before rendering, base R graphics operate on a stateful, imperative pipeline. This means functions execute immediately and modify the current active graphics device.
The pipeline generally follows this sequence:
- Device Initialization: A graphics device is opened (e.Region Setup:
plot.,windows(),quartz(),x11(),pdf(),png(), or automatically via RStudio’s plot pane). Consider this: 2. new()sets up the coordinate system, margins, and the plotting region. High-Level Plot Call: A function likeplot(),hist(),boxplot(), orbarplot()is called. new()internally. In practice, 4. 3. This triggersplot.g.Low-Level Annotations: Functions likepoints(),lines(),text(),abline(),legend(),axis(), andtitle()add layers to the existing region.
Not obvious, but once you see it — you'll see it everywhere.
The error plot.new has not been called yet fires explicitly at step 4 if step 2 (or a manual plot.new() call) has not successfully completed.
Common Scenarios That Trigger the Error
While the root cause is always a missing active plot region, the specific coding patterns that lead there vary. Here are the most frequent culprits:
1. Calling Low-Level Functions First
This is the classic beginner mistake. You open a script and immediately type abline(h=0) or text(1, 1, "Label") before calling plot().
# ❌ ERROR: No plot exists yet
abline(h = 0, col = "red")
text(0.5, 0.5, "Center")
Fix: Always start with a high-level plotting function.
# ✅ CORRECT
plot(1, 1, type = "n", xlim = c(0, 1), ylim = c(0, 1)) # Sets up the canvas
abline(h = 0, col = "red")
text(0.5, 0.5, "Center")
2. The "Empty Plot" Trap (type = "n")
Users often create an empty canvas using plot(x, y, type = "n") intending to build a custom plot layer by layer. If the plot() call fails silently or is commented out during debugging, the subsequent points() or lines() calls will throw the error.
3. Device Closure or Switching
This is the most insidious cause for intermediate users. If you explicitly close the graphics device using dev.off() or switch devices using dev.set(), the current device becomes null (or switches to a device that hasn't had plot.new called on it yet).
plot(1:10)
dev.off() # Closes the active device
lines(1:10, 10:1) # ❌ ERROR: The device is gone; no plot.new active
In RStudio, this often happens when the "Plots" pane is cleared manually (the broom icon) or when a script runs graphics.off() at the top to clean the slate, but the subsequent code assumes a device is still open from a previous run.
4. Errors Inside High-Level Plot Calls
If your plot() call throws an error before it finishes setting up the region (e.g., invalid data types, missing values causing limits calculation to fail), plot.new() might have been called internally, but the environment state might be left in a limbo, or the error halts execution before you reach your annotation code. On the flip side, strictly speaking, if plot() errors out, you usually see that error first. The "plot.new not called" error usually implies the high-level function didn't run at all or ran on a different device Worth keeping that in mind..
5. Non-Interactive Scripts and Rscript
When running R scripts via command line (Rscript my_script.R), no interactive graphics device opens by default. If your script contains plot() followed by lines(), it works because plot() opens a default device (usually a PDF file named Rplots.pdf in the working directory). That said, if you only have low-level commands, or if you manually open a device like pdf("out.pdf") but forget to call plot() before lines(), you hit this error No workaround needed..
Deep Dive: What plot.new() Actually Does
The function plot.new() is the gatekeeper of the plotting region. When invoked (usually implicitly by plot(), hist(), etc The details matter here. And it works..
- Checks/Opens Device: If no device is open, it attempts to open the default one (screen device on interactive, PDF on batch).
- Resets Graphics Parameters: It resets most
par()settings to defaults unlesspar("new") = TRUE. - Calculates Plot Region: It computes
usr(user coordinates),plt(plot region as fraction of figure), andfig(figure region as fraction of device) based on margins (mar/mai) and axis requirements. - Advances Frame: In multi-figure layouts (
mfrow,mfcol), it advances to the next figure region.
If you call plot.You must then manually define the coordinate system using plot.Still, window(xlim, ylim) before adding axes or data. On the flip side, new() manually, you get a completely blank slate with default margins. This is rarely done manually unless building highly custom grid-based graphics.
Practical Solutions and Workflows
Solution A: The Defensive Coding Pattern
Always structure your base R plotting code in a strict order: Device -> High-Level Plot -> Low-Level Annotations -> Close Device (if file).
# 1. Setup Device (Explicit is better for scripts)
pdf("my_plot.pdf", width = 7, height = 5)
# 2. High-Level Plot (Triggers plot.new)
plot(mtcars$wt, mtcars$mpg,
main = "MPG vs Weight",
xlab = "Weight (1000 lbs)",
ylab = "Miles Per Gallon",
pch = 19, col = "steelblue")
# 3. Low-Level Annotations (Safe to call now)
abline(lm(mpg ~ wt, data = mtcars), col = "red", lwd = 2)
legend("topright", legend = c("Data", "Linear Fit"),
col = c("steelblue", "red"), pch = c(19, NA), lty = c(NA, 1))
# 4. Close Device
dev.off()
Solution B: Checking for an Active Device
If you are writing a function that adds elements to a plot (like a custom add_confidence_interval() function), you should defensively check if a plot exists That's the part that actually makes a difference..
add_my_line <- function(slope, intercept) {
# Check if a graphics device is active AND a plot region exists
if (dev.cur() == 1) {
# dev.cur() returns 1 (null device) if no