Exponential Phase Model (30–210)

# Load required libraries
library(plotly)

# 1) Full Data + Subset for Exponential Phase
time <- c(0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300)
od   <- c(0.35, 0.260, 0.447, 0.619, 0.704, 0.838, 0.991, 1.112, 1.168, 1.238, 1.339)

# Adjust OD by subtracting the blank measurement
blank_od <- 0.0071
od_adj <- od - blank_od

# Full dataset on ln(OD)
ln_od_all <- log(od_adj)

# Exponential subset: time=30..210
# Indices 2..8 => 30, 60, 90, 120, 150, 180, 210
time_sub <- time[2:8]
od_sub   <- od_adj[2:8]
ln_od_sub <- log(od_sub)

# 2) Fit Linear Model on 30..210 min
model_ln <- lm(ln_od_sub ~ time_sub)

# Extract slope, intercept, R^2 for annotation
slope_ln     <- coef(model_ln)[2]
intercept_ln <- coef(model_ln)[1]
r2_ln        <- summary(model_ln)$r.squared

# 3) Prepare for Plot
# Define a new time vector for predictions: from 30..210
time_pred <- seq(30, 210, length.out = 100)
pred_ln   <- predict(model_ln, newdata = data.frame(time_sub = time_pred), interval = "confidence")

# Calculate y-axis range to match base plot
all_y <- c(ln_od_all, pred_ln[, "upr"], pred_ln[, "lwr"])  # Include data, upper, and lower CI
yrange <- extendrange(all_y)  # Adds default padding like base plot

# 4) Create Interactive Plot with Plotly
p <- plot_ly() %>%
  # Add all data points (0..300)
  add_markers(
    x = time,
    y = ln_od_all,
    marker = list(
      size = 10,
      color = "#3498DB",
      line = list(color = "#2C3E50", width = 1)
    ),
    name = "Data Points",
    hovertemplate = "Time: %{x} min<br>ln(OD): %{y:.2f}"
  ) %>%
  # Add confidence band as a filled area
  add_ribbons(
    x = time_pred,
    ymin = pred_ln[, "lwr"],
    ymax = pred_ln[, "upr"],
    fillcolor = "rgba(230, 126, 34, 0.2)",  # Semi-transparent orange
    line = list(color = "transparent"),
    name = "Confidence Interval",
    hoverinfo = "skip"  # Disable hover for the band
  ) %>%
  # Add regression line
  add_lines(
    x = time_pred,
    y = pred_ln[, "fit"],
    line = list(color = "#E67E22", width = 3.5, dash = "dash"),
    name = "Linear Fit"
  ) %>%
  # Customize layout
  layout(
    title = list(
      text = "Exponential Phase Model (30–210)",
      font = list(size = 20, family = "sans", color = "#000000")
    ),
    xaxis = list(
      title = "Time (min)",
      titlefont = list(size = 16, family = "sans"),
      tickfont = list(size = 14, family = "sans"),
      gridcolor = "#DDE4E6"
    ),
    yaxis = list(
      title = "ln(OD)",
      titlefont = list(size = 16, family = "sans"),
      tickfont = list(size = 14, family = "sans"),
      range = yrange,       # Fixed range matching base plot
      fixedrange = TRUE,    # Prevent zooming on y-axis
      gridcolor = "#DDE4E6"
    ),
    showlegend = TRUE,
    legend = list(
      x = 0.02,
      y = 0.98,
      font = list(size = 13, family = "sans"),
      bgcolor = "rgba(0,0,0,0)"
    ),
    paper_bgcolor = "#FAFAFA",
    plot_bgcolor = "#FAFAFA",
    shapes = list(
      list(
        type = "rect",
        x0 = 120 - 30,
        x1 = 120 + 30,
        y0 = max(ln_od_all) - 0.15,
        y1 = max(ln_od_all) - 0.05,
        fillcolor = "rgba(0,0,0,0)",
        line = list(color = "rgba(0,0,0,0)")
      )
    ),
    annotations = list(
      list(
        x = 120,
        y = max(ln_od_all) - 0.1,
        text = paste("R² =", format(r2_ln, digits = 3)),
        showarrow = FALSE,
        font = list(size = 14, family = "sans", color = "#555555")
      )
    )
  )

# Display the interactive plot
p

Growthcurver Model Code

library(growthcurver)

# 1) Data
data <- data.frame(
  time = c(0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300),
  od   = c(0.35, 0.260, 0.447, 0.619, 0.704, 0.838, 0.991, 1.112, 1.168, 1.238, 1.339)
)

# 2) Fit Logistic Model with Growthcurver
gc_fit <- SummarizeGrowth(data$time, data$od)

# 3) Plot with Custom Axis Labels and Title
plot(
  gc_fit,
  xlab   = "Time (min)",
  ylab   = "OD600",
  main   = "Growth Curve (Logistic Model)",
  pch    = 16,      # solid circles for data points
  colData  = "black",
  colModel = "red",
  lwdModel = 2     # thicker logistic curve
)