X <- function(x=NULL, data=d, filter=NULL,

    by=NULL,
    facet=NULL, n_row=NULL, n_col=NULL, aspect="fill",

    form = c("histogram", "freq_poly", "density",
             "violin", "box", "strip", "bs", "vbs"), # violin, box, strip

    stat=c("count", "proportion"),

    theme=getOption("theme"),
    fill=getOption("bar_fill_cont"),
    color=getOption("bar_color_cont"),
    transparency=getOption("trans_bar_fill"),
    area_fill="on",  # freq_poly: fill under each polygon; "off" for lines only

    counts=FALSE,
    bin_start=NULL, bin_width=NULL, bin_end=NULL, breaks="Sturges",

    cumulate=c("off", "on", "both"), reg="snow2",

    show_histogram=TRUE,
    bandwidth=NULL, kind=c("general", "normal", "both"),
    full_curve=TRUE,
    fill_normal=NULL, fill_hist=getOption("se_fill"),
    color_normal="gray20", line_width=NULL,
    x_pt=NULL, y_axis=FALSE,
    rug=FALSE, color_rug="black", size_rug=0.5,

    vbs_plot="vbs", vbs_ratio=0.9, bw=NULL, bw_iter=10,
    violin_fill=getOption("violin_fill"),
    box_fill=getOption("box_fill"),
    pt_size=NULL,
    vbs_pt_fill="black",
    vbs_mean=FALSE, fences=FALSE, k=1.5, box_adj=FALSE, a=-4, b=3,
    jitter_x=NULL, jitter_y=NULL,

    ID="row.name", ID_size=0.60,
    out_cut=0, out_shape="circle", out_size=1,

    xlab=NULL, ylab=NULL, main=NULL, sub=NULL,
    lab_adjust=c(0,0), margin_adjust=c(0,0,0,0),

    rotate_x=getOption("rotate_x"), rotate_y=getOption("rotate_y"),
    offset=getOption("offset"),
    scale_x=NULL,
    axis_fmt=c("K", ",", ".", ""), axis_x_pre="", axis_y_pre="",
    font_size=1,

    add=NULL, x1=NULL, y1=NULL, x2=NULL, y2=NULL,

    quiet=getOption("quiet"), do_plot=TRUE,
    use_plotly=getOption("lessR.use_plotly"),
    pdf_file=NULL, width=6.5, height=6,
    digits_d=NULL, Rmd=NULL,

    eval_df=NULL, fun_call=NULL, ...) {


  # limit actual argument to alternatives, perhaps abbreviated
  cumulate <- match.arg(cumulate)
  stat <- match.arg(stat)
  form <- match.arg(form)
  kind <- match.arg(kind)
  if (is.null(fun_call)) fun_call <- match.call(expand.dots=TRUE)
  if (nzchar(axis_fmt[1])) axis_fmt <- match.arg(axis_fmt)

  if (font_size != 1) {
    .fs_main   <- getOption("main_size", 1.1)
    .fs_lab    <- getOption("lab_size",  1.0)
    .fs_axis   <- getOption("axis_size", 0.76)
    .fs_axis_x <- getOption("axis_x_size")
    .fs_axis_y <- getOption("axis_y_size")
    options(main_size = .fs_main * font_size,
            lab_size  = .fs_lab  * font_size,
            axis_size = .fs_axis * font_size)
    if (!is.null(.fs_axis_x))
      options(axis_x_size = .fs_axis_x * font_size)
    if (!is.null(.fs_axis_y))
      options(axis_y_size = .fs_axis_y * font_size)
    on.exit({
      options(main_size = .fs_main,
              lab_size  = .fs_lab,
              axis_size = .fs_axis)
      if (!is.null(.fs_axis_x)) options(axis_x_size = .fs_axis_x)
      if (!is.null(.fs_axis_y)) options(axis_y_size = .fs_axis_y)
    }, add = TRUE)
  }

  # collected plotly style options, passed down to plotly internals so they
  # don't reach into getOption() themselves
  style.opts <- .plotly_style()

  darkred <- "\033[38;5;88m"   # 256-color "dark red"
  reset   <- "\033[0m"

  if (stat == "density") {
    message("The new and future way to request a density plot\n",
            "  is with the form parameter: ",
            darkred, "form = \"density\"", reset, ".\n",
            "This setting is done for you now but in the future\n",
            "  the stat alternative for this setting will be dropped.\n\n")
    form <- "density"
  }

  proportion <- stat == "proportion"   # old signal
  trans <- transparency

  fill.miss <- missing(fill)
  box.fill.miss <- missing(box_fill)
  violin.fill.miss <- missing(violin_fill)
  trans.miss <- missing(transparency)
  color.miss <- missing(color)
  out.size.miss <- missing(out_size)
  n.col.miss <- missing(n_col)
  n.row.miss <- missing(n_row)
  out.shape.miss <- missing(out_shape)
  vbs.plot.miss <- missing(vbs_plot)
  by.miss <- missing(by)
  facet.miss <- missing(facet)

  # differentiate from later use of deparse(substitute(fill))
  user.fill.name <- deparse(substitute(fill))

  # a rug, or rug styling, implies a density plot
  if (!missing(color_rug)  ||  !missing(size_rug)) rug <- TRUE
  if (rug && form != "density") form <- "density"

  if (theme != getOption("theme")) {
    old_theme <- getOption("theme")
    style(theme)
    on.exit(style(old_theme), add=TRUE)
    style.opts <- .plotly_style()  # re-capture under new theme
    if (font_size != 1) {  # style() reset font sizes; re-scale
      options(main_size = getOption("main_size", 1.1) * font_size,
              lab_size  = getOption("lab_size",  1.0) * font_size,
              axis_size = getOption("axis_size", 0.76) * font_size)
      .new_x <- getOption("axis_x_size")
      .new_y <- getOption("axis_y_size")
      if (!is.null(.new_x)) options(axis_x_size = .new_x * font_size)
      if (!is.null(.new_y)) options(axis_y_size = .new_y * font_size)
    }
    sty <- style(theme, reset=FALSE)
    if (fill.miss) fill <- sty$bar$bar_fill_cont
    if (color.miss) color <- sty$bar$bar_color_cont
    if (trans.miss) trans <- sty$bar$trans_fill
    if (violin.fill.miss) violin_fill <- sty$VBS$violin_fill
    if (box.fill.miss) box_fill <- sty$VBS$box_fill
  }

  # Plotly renders the interactive histogram/density, but not a faceted plot
  #   (base R / lattice), so do not announce Plotly when faceting
  if (!quiet && use_plotly && (form %in% c("histogram", "density")) &&
      is.null(substitute(facet))) {
    txt <- "[Interactive plot from the Plotly R package (Sievert, 2020)]"
    cat(txt, "\n\n")
  }

  breaks.miss <- missing(breaks)
  bw.miss <- missing(bandwidth)


  # ------------ Old Stuff ----------------------------------

  # Check for deprecated parameter names using the unevaluated call —
  # must use match.call() not names(list(...)) to avoid evaluating
  # expressions like rows=(Gender=="M") in the wrong environment.
  .mc_nms <- names(match.call())
  if ("facet1" %in% .mc_nms || "facet2" %in% .mc_nms) {
    stop(call. = FALSE, "\n------\n",
         "The arguments 'facet1' and 'facet2' are replaced.\n",
         "Please use 'facet' instead.\n",
         "For two facets, specify:  facet = c(Var1, Var2)\n\n")
  }
  if ("rows" %in% .mc_nms) {
    stop(call. = FALSE, "\n------\n",
         "Parameter  rows  has been removed.\n",
         "Use  filter  instead, e.g., filter = (Age > 30)\n\n")
  }
  if ("n_cat" %in% .mc_nms) {
    stop(call. = FALSE, "\n------\n",
         "Parameter  n_cat  has been removed.\n",
         "Convert categorical integer variables to factors instead:\n",
         "  e.g., d$Item1 <- factor(d$Item1)\n\n")
  }

  # a dot in a parameter name to an underscore and more
  dots <- list(...)
  .style.trap(names(dots))

  if (length(dots) > 0) {
    for (i in seq_along(dots)) {
      if (names(dots)[i] == "type") {
        cat("\n"); stop(call.=FALSE, "\n","------\n",
            "Parameter  type  renamed  form\n\n")
      }
      if (names(dots)[i] == "dn.hist") show_histogram <- dots[[i]]
      if (names(dots)[i] == "fill_gen") fill <- dots[[i]]
      if (names(dots)[i] == "fill_nrm") fill_normal <- dots[[i]]
      if (names(dots)[i] == "color_nrm") color_normal <- dots[[i]]
      if (length(grep(".", names(dots)[i], fixed=TRUE)) > 0) {
        nm <- gsub(".", "_", names(dots)[i], fixed=TRUE)
        assign(nm, dots[[i]])
      }
    }
  }

  # ---------------------------------------------------------


  if (!breaks.miss  &&  (form == "density"))  {
    cat("\n"); stop(call.=FALSE, "\n------\n",
      "When plotting density, parameter  breaks  does not apply.\n",
      "Bins must be equal width; instead use bin_start and bin_width.\n\n")
  }


  # frequency polygon. Single group (no by): delegate to XY() before any side
  #   effects here, such as opening a graphics device or writing an Rmd file.
  #   XY() renders the base R polygon and, by default, the plotly version too
  #   (as bar charts do). Grouped (by=) frequency polygons are plotly only,
  #   handled in the variable loop below via freq_poly.plotly().
  if (form == "freq_poly"  &&  facet.miss) {  # faceted freq poly: base-R grid

    if (!missing(by)  &&  !use_plotly) {
      cat("\n"); stop(call.=FALSE, "\n------\n",
        "A grouped frequency polygon with  by  requires plotly.\n",
        "Set  use_plotly=TRUE  (the default).\n\n")
    }

    if (missing(by)) {  # single group
      # base R static polygon via XY(); the interactive plotly is rendered
      #   below in the variable loop by freq_poly.plotly(), so area_fill
      #   applies and the single polygon matches the grouped (by=) version
      cl <- fun_call
      cl[[1L]] <- as.name("XY")   # X(...) -> XY(...)
      cl$form  <- NULL            # drop visualization selector
      cl$form  <- NULL            # drop deprecated selector alias, if used
      cl$area_fill <- NULL        # XY uses ts_area_fill, not area_fill
      cl[["use_plotly"]] <- FALSE # base R here; plotly via freq_poly.plotly

      nm <- names(cl)
      if (is.null(nm)) nm <- character(length(cl))

      # map X's stat -> XY's stat_x
      if ("stat" %in% nm && !("stat_x" %in% nm)) {
        cl[["stat_x"]] <- cl[["stat"]]
        cl[["stat"]]   <- NULL
      }

      # default for freq poly is count if user didn't supply anything
      if (!("stat_x" %in% names(cl)))
        cl[["stat_x"]] <- "count"

      invisible(eval.parent(cl))  # render base R, print text output

      # without plotly, base R is the whole plot; otherwise fall through to
      #   the variable loop for the interactive freq_poly.plotly()
      if (!use_plotly) return(invisible())
    }
    # fall through to freq_poly.plotly() in the variable loop
  }

  fill[which(fill == "off")] <- "transparent"
  color[which(color == "off")] <- "transparent"

  xlab.adj <- lab_adjust[1];   ylab.adj <- lab_adjust[2]
  tm.adj <- margin_adjust[1];  rm.adj <- margin_adjust[2]
  bm.adj <- margin_adjust[3];  lm.adj <- margin_adjust[4]

  .param.old(..., fun.name = "X")


  # --------- data frame stuff

  data.d <- missing(data)

  # let deprecated mydata work as default
  dfs <- .getdfs()
  mydata.ok <- FALSE
  if (!is.null(dfs)) {
    if ("mydata" %in% dfs  &&  !("d" %in% dfs)) {
      d <- mydata
      rm(mydata)
      df.name <- "mydata"
      mydata.ok <- TRUE
      options(dname = df.name)
    }
  }

  # get name of data table
  if (!mydata.ok) {
    df.name <- deparse(substitute(data))
    options(dname = df.name)
  }

  shiny <- FALSE
  if (!is.null(sys.call(-1)))  # is NULL when called directly from R console
    if (sys.call(-1) == "renderPlot()") {  # from shiny, user or interact()
      shiny <- TRUE
      data <- eval(substitute(data), envir=parent.frame())
    }

  # if a tibble, convert to data frame
  if (!shiny) {
    if (exists(df.name, envir=.GlobalEnv)) {
      if (any(grepl("tbl", class(data), fixed=TRUE)))
        data <- data.frame(data)
    }
  }
  else  { # no check for existence of df.name
    if (any(grepl("tbl", class(data), fixed=TRUE)))
      data <- data.frame(data)
  }

  x.name <- deparse(substitute(x), width.cutoff = 120L)
  if (length(x.name) > 1L)
    x.name <- if (!is.null(xlab) && length(xlab)==1L && nzchar(xlab))
                  xlab else "x"
  options(xname = x.name)

  # classify x: a computational expression evaluates directly, with a
  # data-column reference resolved from the data frame, then the
  # caller's environment; tryCatch avoids forcing the data=d promise
  x.expr    <- substitute(x)
  x.is.expr <- !missing(x) && .is_expr_arg(x.expr)
  x.uses.df <- FALSE
  if (x.is.expr)
    x.uses.df <- df.name != "NULL" && length(all.vars(x.expr)) > 0L &&
                 any(all.vars(x.expr) %in%
                     tryCatch(names(data), error=function(e) character(0)))

  if (!x.is.expr) {
    if (!(shiny && df.name == "NULL")) .in.global(x.name, quiet)
    x.in.global <- if (shiny && df.name == "NULL") TRUE
                   else df.or.global(x.name, df.name, role="x",
                                     fun.name="X")
  }
  else
    x.in.global <- !x.uses.df

  if (!x.in.global) {
    # force evaluation (not lazy) if data not specified, relies on default d
    if (data.d) {
      if (!mydata.ok) .nodf(df.name)  # check to see if df exists
      # the 1.201 comes from Shiny, need to reset
      # l.cex and l.axc are set in interact() before shiny run
      if (getOption("lab_size") == 1.201) {
       if (getOption("l.cex") != 1.201) {
          style(lab_size=getOption("l.cex"))
          style(axis_size=getOption("l.axc"))
        }
        else
          style()
      }
    }
  }

  # respect a caller-supplied eval_df, e.g., CountAll() passes FALSE
  if (is.null(eval_df)) eval_df <- !x.in.global



# subset filter -------------------------------------------------------

  if (!missing(filter))
    data <- .apply_filter(data,
                          deparse(substitute(filter)),
                          parent.frame(),
                          x.in.global, quiet)


  # -----------------------------------------------------------
  # establish if a data frame, if not then identify variable(s)
  # x can be missing entirely, with a data frame passed instead
  # if x a vector, then x.name not in data, but also not in global

  if (!missing(x)) {

    # x is a computational expression, evaluate directly
    if (x.is.expr) {
      val <- .expr_eval(x.expr, x.name, if (x.uses.df) data else NULL,
                        parent.frame(), role="x", fun.name="X")
      if (is.data.frame(val))
        data.x <- val
      else {
        if (!is.numeric(val)) {
          cat("\n"); stop(call.=FALSE, "\n------\n",
            "A histogram is only computed from a numeric variable\n",
            "To tabulate the values of a categorical variable:\n\n",
            "  Chart(", x.name, ", stat=\"count\")\n\n", sep="")
        }
        data.x <- data.frame(val)
        names(data.x) <- x.name  # after data.frame(): no name mangling
      }
    }

    # x not in global env, in df, specify data= forces to data frame
    else if (!x.in.global) {
      if (eval_df) {
        if (!mydata.ok) .nodf(df.name)  # does data frame container exist?
        .xcheck(x.name, df.name, names(data))  # x-vars in df?
      }
      data.vars <- as.list(seq_along(data))
      names(data.vars) <- names(data)

      ind <- eval(substitute(x), envir=data.vars)  # col num of each var
      if (!("list" %in% class(data))) {
        data.x <- data[, ind]
        if (length(ind) == 1) {  # x is 1 var
          if (!is.numeric(data.x)) {
            cat("\n"); stop(call.=FALSE, "\n------\n",
              "A histogram is only computed from a numeric variable\n",
              "To tabulate the values of a categorical variable:\n\n",
              "  Chart(", x.name, ", stat=\"count\")\n\n", sep="")
          }
          data.x <- data.frame(data.x)
          names(data.x) <- x.name
        }
      }
      else {  # class of data is "list"
        data.x <- data.frame(data[[ind]], stringsAsFactors=TRUE)
        names(data.x) <- x.name
      }
    }  # end x not in global

    # x is in the global environment (vector or data frame)
    else {
      if (is.data.frame(x))  # x a data frame
        data.x <- x
      else {  # x a vector in global, already checked by .in.global()
        if (!is.function(x))
          data.x <- data.frame(x, stringsAsFactors=TRUE)  # x is 1 var
        else
          data.x <- data.frame(eval(substitute(data$x)), stringsAsFactors=TRUE)
        names(data.x) <- x.name
      }
    }  # x is in global
  }

  # evaluate by
  #------------
  # cannot directly evaluate is.null(by) if by is present as a variable
  # so instead process as below to either get by.call or it is NULL
  # can get by.name
  if (!missing(by)) {

    # get variable name before potential call of data$x
    by.name <- paste(deparse(substitute(by), width.cutoff=120L),
                     collapse="")
    .quoted_varname(by.name, "by")

    # X() permits only one  by  variable, e.g., by=c(V1, V2) is not allowed
    by.expr <- substitute(by)
    if (is.call(by.expr) && identical(by.expr[[1L]], as.name("c"))) {
      cat("\n"); stop(call.=FALSE, "\n------\n",
        "Only one  by  variable is permitted, but more than one specified:\n",
        "  by = ", by.name, "\n",
        "  Additional grouping variables are not permitted, as the resulting\n",
        "  visual clutter compromises interpretability.\n",
        "  To display a second grouping variable, use  facet.\n\n")
    }

    options(byname = by.name)

    if (.is_expr_arg(by.expr)) {  # by is a computational expression
      uses.df <- df.name != "NULL" &&
                 any(all.vars(by.expr) %in%
                     tryCatch(names(data),
                              error=function(e) character(0)))
      by.call <- .expr_eval(by.expr, by.name,
                            if (uses.df) data else NULL,
                            parent.frame(), role="by", fun.name="X")
      if (!missing(x)  &&  length(by.call) != nrow(data.x)) {
        cat("\n"); stop(call.=FALSE, "\n------\n",
          "x has ", nrow(data.x), " values, but the  by  expression\n",
          "  ", by.name, "  yields ", length(by.call), " values\n\n")
      }
    }
    else {
      # get conditions and check for data existing
      by.in.global <- df.or.global(by.name, df.name, role="by",
                                   fun.name="X")

      if (!by.in.global) {
        if (eval_df)
          .xcheck(by.name, df.name, names(data))
        by.call <- eval(substitute(data$by))
      }
      else {  # vars that are function names get assigned to global
        by.call <- by
        if (is.function(by.call)) by.call <- eval(substitute(data$by))
      }
    }
    by.call <- factor(by.call)

    n.by <- length(unique(by.call))
  }  # end having by
  else {
    n.by <- 1  # i.e., just one group
    by.call <- NULL
    by.name <- NULL
  }


# --- resolve facet --------------------------------------------------

  # Initialize legacy internal vars
  facet1.call <- facet2.call <- NULL
  facet1.name <- facet2.name <- NULL
  facet1.miss <- facet2.miss <- TRUE

  if (!missing(facet)) {
    facet.expr <- substitute(facet)
    facet.vars <- all.vars(facet.expr)
    caller.env <- parent.frame()

    if (.is_expr_arg(facet.expr)) {  # a computational expression
      f.name <- paste(deparse(facet.expr, width.cutoff=120L),
                      collapse="")
      uses.df <- df.name != "NULL" &&
                 any(facet.vars %in%
                     tryCatch(names(data),
                              error=function(e) character(0)))
      v <- .expr_eval(facet.expr, f.name,
                      if (uses.df) data else NULL,
                      caller.env, role="facet", fun.name="X")
      facet1.call <- droplevels(factor(v))
      facet1.name <- f.name
      facet1.miss <- FALSE
      if (!missing(x)  &&  length(facet1.call) != nrow(data.x)) {
        cat("\n"); stop(call.=FALSE, "\n------\n",
          "x has ", nrow(data.x), " values, but the  facet  expression\n",
          "  ", f.name, "  yields ", length(facet1.call), " values\n\n")
      }
    }
    else {

    get.col <- function(nm)
      .resolve_named_var(nm, df.name, data, caller.env,
                         role = "facet", fun.name = "X")

    # Resolve symbols to factors
    fac.list <- lapply(facet.vars, function(nm) {
      v <- get.col(nm)
      if (!is.factor(v)) v <- factor(v)
      droplevels(v)
    })

    # Map into legacy internal vars
    if (length(fac.list) >= 1L) {
      facet1.call <- fac.list[[1L]]
      facet1.name <- facet.vars[1L]
      facet1.miss <- FALSE
    }
    if (length(fac.list) >= 2L) {
      facet2.call <- fac.list[[2L]]
      facet2.name <- facet.vars[2L]
      facet2.miss <- FALSE
    }
    if (length(fac.list) > 2L) {
      message("`facet` has ", length(fac.list),
              " variables; using the first two: ",
              paste(facet.vars[1:2], collapse = ", "), ".")
      fac.list   <- fac.list[1:2]
      facet.vars <- facet.vars[1:2]
    }
    }  # end facet is variable name(s), not an expression
  }


  # evaluate ID  (for VBS)
  #------------
  get.ID <- FALSE
  if (!is.null(add)) if (add[1] == "labels") get.ID <- TRUE
  if (out_cut > 0) get.ID <- TRUE

  if (get.ID) {
    # ID.name is the actual var name if specified directly,
    #  or the name of the var that contains the var name
    ID.name <- deparse(substitute(ID))
    ID.name <- gsub("\"", "", ID.name)  # remove extra quotes if "row.name"
    if (ID.name == "row.name") ID.name <- "row.names"
    if (!x.in.global) {
  # if x is in a data frame, then in the function call it is a name
  # if x is in global, then in the function its name is a variable direct
  # ID.col is the actual var column if specified directly,
  #  or the var name if a variable that contains the name was entered
      if (ID.name != "row.names") {
        # data.vars only built when x was specified and is in the data frame
        if (!exists("data.vars", inherits=FALSE)) {
          data.vars <- as.list(seq_along(data))
          names(data.vars) <- names(data)
        }
        ID.col <- eval(substitute(ID), envir=data.vars, parent.frame())
        if (!is.numeric(ID.col)) {
          ID.col <- which(names(data) == ID.col)
          ID.name <- names(data)[ID.col]
        }
      .xcheck(ID.name, df.name, names(data))  # var exists in data frame?
      ID.call <- data[, ID.col]
      }  # end not row.names
      else  # ID is row.names
        ID.call <- row.names(data)
    } # end !x.in.global

    else {  # in global
      ID.call <- eval(substitute(ID), parent.frame())
    }
  }  # end get.ID

  else  # no ID to get
    ID.call <- NULL

# ---- resolve pt_size (NULL, numeric value, or a variable in data) ----
  # Same pattern as XY() — avoid forcing the lazy promise on a bare name
  # like `Pre` that lives in the data frame, not in the calling environment.
  if (!missing(pt_size)) {
    size.name <- deparse(substitute(pt_size))
    if (size.name == "NULL") {
      pt_size <- NULL
    } else {
      suppressWarnings(size.num <- as.numeric(size.name))
      if (is.na(size.num)) {
        # variable name — caller frame first (Shiny pre-evaluated case)
        if (exists(size.name, envir=parent.frame(), inherits=TRUE) &&
            is.numeric(get(size.name, envir=parent.frame(),
                            inherits=TRUE))) {
          pt_size <- get(size.name, envir=parent.frame(), inherits=TRUE)
        } else {
          in.global <- df.or.global(size.name, df.name,
                                    role="pt_size", fun.name="X")
          if (!in.global) {
            data.vars <- as.list(seq_along(data))
            names(data.vars) <- names(data)
            size.col <- eval(substitute(pt_size), envir=data.vars)
            pt_size  <- data[, size.col]
            if (!is.numeric(pt_size)) {
              cat("\n"); stop(call.=FALSE, "\n------\n",
                "Variable ", size.name, " must be numeric\n\n",
                "Perhaps use: by=", size.name, "\n\n")
            }
          }
        }
      }
      # else: numeric constant — pt_size is already that value
    }
  }

  # -----------------------------------------------------------
  # -----------  x, y, by, and facet variables established ----
  # -----------------------------------------------------------

  Trellis <- !facet.miss

  if (Trellis && form=="histogram") {
    if (do_plot) {
      if (fill.miss) {
        if (theme == "colors")
          fill <- rgb(150,170,195, maxColorValue=255)
        else
          fill <- .color_range(.get_fill(theme), 1)
      }
      .bar.lattice(data.x[,1], facet1.call, facet2.call, n_row, n_col,
             aspect, proportion, fill, color, trans, size.pt=NULL,
             xlab, ylab, main, rotate_x, offset,
             axis_fmt, axis_x_pre, axis_y_pre,
             width, height, pdf_file,
             segments_x=NULL, breaks, bin_start, bin_width, bin_end,
             T.type="hist", quiet)
    }
    return(invisible(NULL))
  }


  if (!missing(x)) data <- data.x

  # set up graphics
  manage.gr <- .graphman()  # manage graphics?
  if (manage.gr) {
    i.win <- 0
    for (i in 1:ncol(data)) {
      if (is.numeric(data[,i])  &&  !.is.num.cat(data[,i], 0))
        i.win <- i.win + 1
    }
    .graphwin(i.win, d.w=width, d.h=height)
    open.win <- 2
  }
  plot.i <- 0  # keep track of generated graphics
  plot.title  <- character(length=0)

  # no suggestions if multiple variables
  if (ncol(data) > 1) {
    sug <- getOption("suggest")
    options(suggest = FALSE)
  }

# evaluate specified fill (NULL, numeric constant, or a variable) ---------
  if (!color.miss && !is.null(by.call)) {
    if (!.is.palette(color[1]))
      color  <- .align_vector(color, by.call)  # convert named color to legacy
  }
  if (!fill.miss) {
    if (length(user.fill.name) == 1) {
      if (exists(df.name, where=.GlobalEnv))
        in.df <- user.fill.name %in% names(data)
      else
        in.df <- FALSE
    }
    else in.df <- FALSE

    # only works for y given, not tabulated
    if (in.df) {  # fill is a variable
    # need to aggregate cat var x and set fill.val to those limited values
    # currently, fill.val consists of all data values of variable fill
      fill.val <- eval(substitute(data$fill))  # fill is a variable in data
      fill <- .getColC(fill.val, fill_name=user.fill.name)
    }

    n.fill <- ifelse (by.miss, 12, n.by)  # 12 is an arbitrary hack
    if (.is.palette(fill[1])) {
      fill <- .color_range(fill, n.fill)
    }

    # evaluate getColors at the time of the function call
    # re-evaluate here by setting fill with the specified value of n
    if (substr(user.fill.name[1], 1, 9) == "getColors") {
      n.clr <- if (!is.null(by.call)) n.by else 2  # 2 specifies endpoints
      fill <- .resolve_getColors_n(user.fill.name, n.clr)
    }
  }  # end !fill.miss

  else {
    if (is.null(by.call)) {
      if (theme == "colors")
        fill <- rgb(150,170,195, maxColorValue=255)
      else
        fill <- getOption("bar_fill_cont")
    }
    else
      fill <- .color_range(.get_fill(theme), n.by)
  }


  for (i in 1:ncol(data)) {  # data only contains data to be analyzed
    nu <- length(unique(na.omit(data[,i])))

    x.name <- names(data)[i]
    options(xname = x.name)

    plt.word <- switch(form, histogram = "Histogram",
                       density = "Density", "VBS plot")

    if (is.numeric(data[,i])) {
      # let 1 variable go through, even if num.cat
      if (ncol(data) == 1  ||  !.is.num.cat(data[,i], 0)) {

      # track the generated graphics across multiple variables
      if (ncol(data) > 1) {
        plot.i <- plot.i + 1
        plot.title[plot.i] <- paste(plt.word, " of ", x.name, sep="")
        if (manage.gr) {
          open.win <- open.win + 1
          dev.set(which = open.win)
        }
      }

      # a faceted freq poly is a base-R grid here, so it needs a device like
      #   histogram/density; a non-faceted freq poly had its device (and pdf)
      #   handled by the XY() delegation above
      fp.facet <- form == "freq_poly" && !facet.miss

      if (!is.null(pdf_file)  &&  (form != "freq_poly" || fp.facet)) {
        if (!grepl(".pdf", pdf_file))
          pdf_file <- paste(pdf_file, ".pdf", sep="")
        pdf(file=pdf_file, width=width, height=height, onefile=FALSE)
      }
      else {
        # VBS plots: .plt.lattice opens its own graphics device
        if (df.name != "NULL"  &&  do_plot  &&  # not dev.new for shiny
            (form %in% c("histogram", "density") || fp.facet))
          .opendev(pdf_file, width, height)
      }

      txkfl <- ""
      if (!is.null(Rmd)) {
        if (!grepl(".Rmd", Rmd)) Rmd <- paste(Rmd, ".Rmd", sep="")
        txknt <- .dist.Rmd(x.name, df.name, fun_call, digits_d)
        cat(txknt, file=Rmd, sep="\n")
        txkfl <- .showfile2(Rmd, "R Markdown instructions")
      }  # end R Markdown

      ssstuff <- .ss.numeric(data[,i], digits_d=digits_d, brief=TRUE)
      txss <- ssstuff$tx


    # drop all rows with any missing data across x, y, by, and size -----------

    # 1) Build a temporary data frame that contains all relevant columns
      df <- data

      if (!is.null(by.call) && length(by.call) > 0L)
        df$..by <- by.call

      if (!is.null(facet1.call) && length(facet1.call) > 0L)
        df$..facet1 <- facet1.call

      if (!is.null(facet2.call) && length(facet2.call) > 0L)
        df$..facet2 <- facet2.call  # fixed name

      # 2) Compute complete cases, then delete rows with an NA
      cc <- stats::complete.cases(df)

      # 3) Delete rows with missing data
      if (!all(cc)) {
        data <- data[cc, , drop = FALSE]

        if (!is.null(by.call) && length(by.call) > 0L)
          by.call <- by.call[cc]

        if (!is.null(facet1.call) && length(facet1.call) > 0L)
          facet1.call <- facet1.call[cc]

        if (!is.null(facet2.call) && length(facet2.call) > 0L)
          facet2.call <- facet2.call[cc]
      }

      rm(df)


# histogram ---------------------------------------------------------------

    if (form == "histogram") {

      digits.d <- if (is.null(digits_d)) .max.dd(data[,i]) else digits_d
      # process plotly and by=1 Plots
      stuff <- .hst.main(data[,i], by.call, by.name, n.by,
          fill, color, trans, reg,
          rotate_x, rotate_y, offset,
          breaks, bin_start, bin_width,
          bin_end, proportion, counts, cumulate, xlab, ylab, main, sub,
          xlab.adj, ylab.adj, bm.adj, lm.adj, tm.adj, rm.adj,
          add, x1, x2, y1, y2,
          scale_x, axis_fmt, axis_x_pre, axis_y_pre, use_plotly,
          digits.d, quiet, do_plot, fun_call=fun_call,
          style_opts=style.opts, ...)

      txsug <- stuff$txsug
      if (is.null(txsug)) txsug <- ""
      txdst <- stuff$ttx
      if (is.null(txdst)) txdst <- ""

      txotl <- .bx.stats(data[,i])$txotl
      if (txotl[1] == "") txotl <- "No (Box plot) outliers"

      if (ncol(data) > 1  &&  !quiet) { # for var range, print text output
        class(txss) <- "out"
        class(txdst) <- "out"
        class(txotl) <- "out"
        output <- list(out_ss=txss, out_freq=txdst, out_outliers=txotl)
        class(output) <- "out_all"
        print(output)
      }



      # n.by == 1: full structured return (histogram-specific)
      if (n.by == 1 && ncol(data) == 1) {
        output <- list(form="Histogram",
          call=fun_call,
          out_suggest=txsug, out_ss=txss, out_outliers=txotl, out_freq=txdst,
          out_file=txkfl,
          bin_width=stuff$bin_width, n_bins=stuff$n.bins,
          breaks=stuff$breaks,
          mids=stuff$mids, counts=stuff$counts, prop=stuff$prop,
          cumulate=stuff$counts_cum, cprop=stuff$prop_cum)
        class(output) <- "out_all"
        if (!quiet) print(output)

        stuff <- list(out_suggest=txsug, out_summary=txss,
                out_freq=txdst, out_outliers=txotl,
                bin_width=stuff$bin_width, n_bins=stuff$n.bins,
                breaks=stuff$breaks, mids=stuff$mids,
                counts=stuff$counts, prop=stuff$prop,
                cumulate=stuff$counts_cum, cprop=stuff$prop_cum)

        if (!is.null(pdf_file)) {
          dev.off()
          if (!quiet && df.name!="NULL") .showfile(pdf_file, "Histogram")
        }
        if (use_plotly && isTRUE(getOption("knitr.in.progress"))
            && !isTRUE(quiet)) {
          plt.last <- getOption("lessR.last_plotly", NULL)
          if (inherits(plt.last, "htmlwidget")) print(plt.last)
        }
        return(invisible(stuff))
      }  # end n.by == 1
      # n.by >= 2: fall through to shared grouped-stats block
    } # end histogram


# density -----------------------------------------------------------------

    else if (form == "density") {
      if (bw.miss) bandwidth <- .band.width(data[,i], ...)  # band width

      clr <- getOption("theme")  # color theme not used except monochrome

      if (fill.miss) {
          fill <- rgb(80,150,200, alpha=80, maxColorValue=255)
        if (clr == "gray" ||
           (getOption("theme") == "gray"  &&
            getOption("sub_theme") == "black")) {
          fill <- rgb(.5,.5,.5, .5)
        }
      }
      else {  # add some transparency to a named color
        for (i.clr in seq_along(fill)) {
          if (fill[i.clr] %in% colors()) {
            fg.rgb <- col2rgb(fill[i.clr])
            fill[i.clr] <- rgb(fg.rgb[1], fg.rgb[2], fg.rgb[3],
                                alpha=80, maxColorValue=255)
          }
        }
      }

      if (missing(fill_normal)) {
          fill_normal <- rgb(250,210,230, alpha=80, maxColorValue=255)
        if (clr == "gray" ||
           (getOption("theme") == "gray"  &&
            getOption("sub_theme") == "black")) {
          fill_normal <- ifelse (kind %in% c("normal", "both"),
                                 rgb(.75,.75,.75, .5), "transparent")
        }
      }
      else {  # add some transparency to a named color
        if (fill_normal %in% colors()) {
          fg.rgb <- col2rgb(fill_normal)
          fill_normal <- rgb(fg.rgb[1], fg.rgb[2], fg.rgb[3],
                              alpha=80, maxColorValue=255)
        }
      }

      x.min <- NULL
      x.max <- NULL
      if (!is.null(scale_x)) {
        x.min <- scale_x[1]
        x.max <- scale_x[2]
      }

      # area_fill toggles filling the space under the density curve
      dn.area <- !(area_fill[1] %in% c("off", "transparent"))

      # faceted density: base-R panel grid with a shared x-range and density
      #   scale, reusing the single-plot density estimation and styling
      if (!facet1.miss) {
        if (do_plot)
          .plt.dist.facet(data[,i], facet1.call, facet2.call,
            facet1.name, facet2.name, render="density",
            bandwidth=bandwidth, kind=kind, histo=show_histogram,
            fill_hist=fill_hist, col_nrm=color_normal, fill_nrm=fill_normal,
            col_gen=color, fill_gen=fill, lwd=line_width,
            rug=rug, color_rug=color_rug, size_rug=size_rug,
            full_curve=full_curve, theme=theme, x.min=x.min, x.max=x.max,
            axis_fmt=axis_fmt, axis_x_pre=axis_x_pre, axis_y_pre=axis_y_pre,
            x.lab=if (!is.null(xlab)) xlab else x.name, y_axis=y_axis,
            n_row=n_row, n_col=n_col, quiet=quiet)
        if (!is.null(pdf_file)) {
          dev.off()
          if (!quiet && df.name!="NULL") .showfile(pdf_file, "Density")
        }
        return(invisible(NULL))
      }

      # need this for dn.plotly even if not for Plots window
      stuff <- .dn.main(data[,i], by.call, by.name, n.by,
            bandwidth, kind, show_histogram, theme,
            bin_start, bin_width, fill.miss,
            fill_hist, color_normal, color,
            fill_normal, fill, line_width,
            rotate_x, rotate_y, offset,
            axis_fmt, axis_x_pre, axis_y_pre,
            x_pt, xlab, main, sub, y_axis, x.min, x.max,
            rug, color_rug, size_rug,
            use_plotly, quiet, full_curve=full_curve,
            fncl=fun_call, style_opts=style.opts, do_plot=do_plot,
            fill_area=dn.area, ...)

      if (n.by == 1) {  # stat output
        txdst <- stuff$tx  # should be named txbw

        txotl <- .bx.stats(data[,i])$txotl
        if (txotl[1] == "") txotl <- "No (Box plot) outliers"

        txsug <- stuff$txsug

        class(txdst) <- "out"
        class(txotl) <- "out"
        class(txsug) <- "out"

        output <- list(form="Density",
          out_suggest=txsug, out_stats=txdst,
          out_ss=txss, out_outliers=txotl,
          out_file=txkfl,
          bw=stuff$bw, n=stuff$n, n_miss=stuff$n.miss)

        class(output) <- "out_all"
        if (!quiet) print(output)

        if (.allow.interactive())
          .viewer_notice_once(plot_name="density plot", window_target="Both")

        # with multiple variables, continue the loop to the next variable
        if (ncol(data) == 1) {
          if (!is.null(pdf_file)) {
            dev.off()
            if (!quiet && df.name!="NULL") .showfile(pdf_file, "Density")
          }
          return(invisible(output))
        }
      }  # end n.by is 1

    }  # end density


# freq_poly (plotly) ------------------------------------------------------

    else if (form == "freq_poly") {  # plotly only; base graphics via XY above

      # fill is the per-group line/area color; if off/transparent fall back
      #   to the theme palette so the lines stay visible and distinct
      fill_off <- length(fill) == 1L && fill[1] == "transparent"
      fp.fill <- if (fill.miss || fill_off) .color_range(.get_fill(theme), n.by)
                 else                       .color_range(fill, n.by)

      # area_fill toggles filling the space under each polygon
      fp.area <- !(area_fill[1] %in% c("off", "transparent"))

      # X's stat is count/proportion/density; density is not a freq poly stat
      fp.stat <- if (stat == "density") "count" else stat

      # faceted freq poly: base-R panel grid with shared bins and count scale
      if (!facet1.miss) {
        if (do_plot)
          .plt.dist.facet(data[,i], facet1.call, facet2.call,
            facet1.name, facet2.name, render="freq_poly",
            col_gen=fp.fill[1], fill_gen=fp.fill[1], lwd=line_width,
            fill_area=fp.area, stat=fp.stat,
            breaks=if (breaks.miss) NULL else breaks,
            bin_start=bin_start, bin_width=bin_width, bin_end=bin_end,
            theme=theme, axis_fmt=axis_fmt,
            axis_x_pre=axis_x_pre, axis_y_pre=axis_y_pre,
            x.lab=if (!is.null(xlab)) xlab else x.name,
            n_row=n_row, n_col=n_col, quiet=quiet)
        if (!is.null(pdf_file)) {
          dev.off()
          if (!quiet && df.name!="NULL") .showfile(pdf_file, "Frequency Polygon")
        }
        return(invisible(NULL))
      }

      plt <- freq_poly.plotly(
        x         = data[, i],  by = by.call,
        x_name    = x.name,     by_name = by.name,
        fill      = fp.fill,    fill_area = fp.area,
        x_lab     = if (!is.null(xlab)) xlab else x.name,
        stat      = fp.stat,
        breaks    = if (breaks.miss) NULL else breaks,
        bin_start = bin_start, bin_width = bin_width, bin_end = bin_end,
        digits_d  = if (is.null(digits_d)) 3 else digits_d,
        style_opts = style.opts)

      if (do_plot && .allow.interactive()) print(plt)

      # for by=, print summary stats here; for a single group the text
      #   output was already printed by the base R XY() call above
      if (n.by > 1) {
        txotl <- .bx.stats(data[, i])$txotl
        if (txotl[1] == "") txotl <- "No (Box plot) outliers"
        class(txss) <- "out"; class(txotl) <- "out"
        output <- list(form="Frequency Polygon",
          out_ss=txss, out_outliers=txotl)
        class(output) <- "out_all"
        if (!quiet) print(output)
      }

      if (ncol(data) == 1) return(invisible())
    }  # end freq_poly


# ---- shared grouped-stats output for histogram and density (n.by >= 2) ----
    if (form %in% c("histogram", "density") && n.by >= 2) {
      if (.allow.interactive()) {
        target <- if (n.by == 2) "Both" else "Viewer"
        .viewer_notice_once(
          plot_name = if (form == "histogram") "histogram" else "density plot",
          window_target = target)
      }

      # descriptive statistics by group, only computed for this output
      desc.stats <- .ss.pivot(
        x = data[, i],
        by = by.call,
        data       = data,
        x.name     = x.name,
        by.name    = by.name,
        print_result = FALSE
      )

      cat(x.name, "by", by.name, "\n\n")
      print(desc.stats, row.names = FALSE)

      # n.by == 2: also offer/run the t-test
      if (n.by == 2) {
        cat("\nFor inferential analysis of the mean difference:\n")
        message("> ttest(", x.name, " ~ ", by.name, ")\n")

        df.small <- data.frame(data[, i], by.call)
        names(df.small) <- c(x.name, by.name)

        calls <- sys.calls()
        top <- calls[[1L]]; fun <- top[[1L]]
        if (identical(fun, quote(X)) || identical(fun, as.name("X"))) {
          tmp.name <- ".lessR_ttest_df"
          assign(tmp.name, df.small, envir = parent.frame())
          on.exit(if (exists(tmp.name, envir=parent.frame()))
                    rm(list=tmp.name, envir=parent.frame()), add = TRUE)
          frm <- reformulate(by.name, response = x.name)
          tt.call <- bquote(
            lessR::ttest(.(frm), data=.(as.name(tmp.name)),
                         quiet=TRUE, graph=TRUE, line_chart=FALSE,
                         area_fill=.(area_fill)))
          eval(tt.call, envir = parent.frame())
        }
      } else {
        cat("\n")
      }

      # with multiple variables, continue the loop to the next variable
      if (ncol(data) == 1) {
        if (!is.null(pdf_file)) {
          dev.off()
          show <- if (form == "histogram") "Histogram" else "Density"
          if (!quiet && df.name!="NULL") .showfile(pdf_file, show)
        }
        return(invisible(NULL))
      }
    }


# VBS ---------------------------------------------------------------------

    else if (form %in% c("vbs", "bs", "violin", "box", "strip")) {

      # resolve the plot components: violin, box, strip points
      # vbs_plot is deprecated; when supplied it overrides form
      vbs_plot <- tolower(vbs_plot)
      if (vbs.plot.miss)
        vbs_plot <- switch(form, violin="v", box="b", strip="s", form)
      violin <- grepl("v", vbs_plot, fixed=TRUE)
      box    <- grepl("b", vbs_plot, fixed=TRUE)

        lx <- length(data[,i])
        iter.details <- !missing(bw_iter)
        n.ux <- length(unique(data[,i]))
      txt <- ifelse (!is.null(facet1.call),
                     "[Trellis (facet)", "[Violin/Box/Scatterplot")
      if (!quiet)
        cat(paste(txt, "graphics from Deepayan Sarkar's lattice package]\n\n"))

      # total number of facets
      n.facet1 <- length(levels(facet1.call))
      n.facet2 <- ifelse (facet2.miss, 1, length(levels(facet2.call)))
      n.lvl <- n.facet1 * n.facet2
      ord.by.call <- is.ordered(facet1.call)

      # n.lvl == 0 when facet1.call is NULL (no facets); single-panel default
      if (box.fill.miss  &&  vbs_plot == "b"  &&  n.lvl == 0)
          box_fill <- getOption("box_fill")
      else if (grepl("b", vbs_plot))
          box_fill <- .plt.fill(box_fill, box.fill.miss, ord.by.call,
                                n.facet1, n.lvl, theme)

      if (violin.fill.miss  &&  vbs_plot == "v") {
        if (n.lvl == 0) {
          if (theme != "gray")
            violin_fill <- getOption("box_fill")
          else
            violin_fill <- "gray75"
        }
        else {
          if (theme != "gray")
            violin_fill <- .plt.fill(box_fill, box.fill.miss, ord.by.call,
                                  n.facet1, n.lvl, theme)
          else
            for (i.lvl in seq_len(n.lvl)) violin_fill[i.lvl] <- "gray75"
        }
      }
      else {
        if (!violin.fill.miss)  # otherwise specified value
          violin_fill <- .plt.fill(violin_fill, violin.fill.miss, ord.by.call,
                                   n.facet1, n.lvl, theme)
        else
          for (i.lvl in seq_len(n.lvl)) violin_fill[i.lvl] <- violin_fill[1]
      }

        if (n.by > 1) {
          if (color.miss) {
            if (getOption("theme") %in% c("gray", "white"))
              pt.color <- getColors("grays", n=n.by)
            else
              pt.color <- getColors("hues", n=n.by, l=40)
          }
          else
            pt.color <- color
          for (i.by in 1:n.by) {
            if (fill.miss) {
              if (getOption("theme") %in% c("gray", "white"))
                pt.fill <- getColors("grays", n=n.by)
              else
                pt.fill <- getColors("hues", n=n.by, l=40)
            }
            else
              pt.fill <- fill
            pt.fill[i.by] <- .maketrans(pt.fill[i.by], (1-trans)*256)
            if (box.fill.miss)
              box_fill[i.by] <- .maketrans(box_fill[i.by], 0.6*256)
          }
        }  # n.by > 1
        else {  # n.by is 0 or 1
          if ("black" %in% vbs_pt_fill) {
            if (fill.miss) pt.fill <- "black"
            if (trans.miss) pt.trans <- 0.1
            if (color.miss) pt.color <- "black"
          }
          else if ("default" %in% vbs_pt_fill) {
            if (fill.miss) pt.fill <- "black"
            if (trans.miss) pt.trans <- getOption("trans_pt_fill")
            if (color.miss) pt.color <- "black"
          }
          else {
            if (fill.miss) pt.fill <- vbs_pt_fill
            if (trans.miss) pt.trans <- getOption("trans_pt_fill")
            if (color.miss) pt.color <- getOption("pt_color")
          }
          # user-specified values apply regardless of vbs_pt_fill
          if (!fill.miss)  pt.fill  <- fill
          if (!trans.miss) pt.trans <- trans
          if (!color.miss) pt.color <- color
          if (pt.trans > 0  &&  pt.fill[1] != "transparent")
            pt.fill <- .maketrans(pt.fill, (1-pt.trans)*256)
        }  # end n.by is 0 or 1

        # grayscale
        if (getOption("sub_theme") == "black") {
          if (fill.miss) pt.fill <- "black"
          if (color.miss) pt.color <- "black"
        }

        if (df.name == "NULL"  &&  out_size == 1) out.size.miss <- TRUE
        k.iqr <- k   # k is a function name, so do not use internally

        # get some VBS parameters, including: pt_size, jitter, bw
        # ss.numeric called there
        output <- NULL
        VBS <- .param.VBS(data[,i], ID.call, facet1.call, facet1.miss,
                by.call, by.miss, bw, bw.miss, bw_iter, iter.details,
                lx, n.ux, k.iqr, box_adj, a, b,
                x.name, facet1.name, by.name, vbs_plot,
                n.col.miss, n.row.miss,
                size = pt_size, out_size = out_size, out_size.miss = out.size.miss,
                jitter_x=jitter_x, jitter_y=jitter_y,
                bin = FALSE, breaks = "Sturges", bin_start = NULL,
                bin_width = NULL, bin_end = NULL,
                proportion, digits_d, quiet, fun_call, ...)

        if (is.null(pt_size)) {
          pt.size <- VBS$pt.size
        } else if (length(pt_size) > 1) {
          # vector pt_size: rescale to a usable cex range for lattice
          rng <- range(pt_size, na.rm=TRUE)
          if (diff(rng) > 0)
            pt.size <- 0.5 + 2.5 * (pt_size - rng[1]) / diff(rng)
          else
            pt.size <- rep(1.5, length(pt_size))
        } else {
          pt.size <- pt_size  # user-supplied scalar
        }

        # grayscale: only now that pt.size is known
        if (theme %in% c("gray", "white"))
          if (any(pt.size > 0.4)) if (out.shape.miss) out_shape <- 23
        pt.out.size <- VBS$out_size
        jitter_y <- VBS$jitter_y
        jitter_x <- VBS$jitter_x
        bw <- VBS$bw
        adj.bx.ht <- VBS$adj.bx.ht

        output$out_stats <- VBS$output  # text output
        if (!quiet) {
          cat("\n")
          print(output$out_stats)
        }

      if (!is.null(jitter_x)) if (jitter_x > 0)  # not available in stripplot
        data[,i] <- jitter(data[,i], factor=jitter_x)

      # no strip component, so no plotted points
      if (!grepl("s", vbs_plot, fixed=TRUE)) pt.size <- 0

      # n_col is null for X(x), X(x, by=), X(x, facet=)
      if (n.col.miss && n.row.miss && !is.null(facet1.call))
        n_col <- 1  # default n_col for Trellis


# pivot tables ------------------------------------------------------------

      x_vec <- data[[ x.name ]]   # e.g., data[["Salary"]]
      group.vars <- character(0)

      if (!quiet) cat("\n\n---------- Summary Statistics for", x.name)

      if (!by.miss) {
        group.vars <- c(group.vars, by.name)
        out.by <- .vbs_summary_table(
          x_vec    = x_vec,
          grp_vec  = by.call,
          grp.label = by.name,
          digits_d  = .max.dd(x_vec)
        )
        if (!quiet) {
          cat("\n\n")
          print(out.by)
        }
      }

      if (!facet.miss) {
          # facet.vars already limited to at most two variables
          facet.calls <- list(facet1.call, facet2.call)[seq_along(facet.vars)]
          out.facet <- vector("list", length(facet.vars))

          for (.fi in seq_along(facet.vars)) {
            group.vars <- c(group.vars, facet.vars[.fi])
            out.facet[[.fi]] <- .vbs_summary_table(
              x_vec     = x_vec,
              grp_vec   = facet.calls[[.fi]],
              grp.label = facet.vars[.fi],
              digits_d  = .max.dd(x_vec)
            )
            if (!quiet) {
              cat("\n\n")
              print(out.facet[[.fi]])
            }
          }  # end .fi
        }

      if (length(group.vars) == 0) {
        out.all <- .vbs_summary_table(
          x_vec  = x_vec,
          grp_vec = NULL,
          grp.label = x.name,
          digits_d  = .max.dd(x_vec)
        )
        if (!quiet) {
          cat("\n\n")
          print(out.all)
        }
      }


# do the plot -------------------------------------------------------------

      options(facet1name=facet1.name)

      # vary strip-point shape across by groups, as XY() does; one shape if
      #   no by variable
      vbs.shape <- if (!is.null(by.call))
                     .plt.shapes("vary", out_shape, n.by=n.by)$shape
                   else
                     "circle"

      if (do_plot)
      .plt.lattice(
        x = data[,i], y = NULL,
        facet1 = facet1.call, facet2 = facet2.call, by = by.call,
        adj.bx.ht = adj.bx.ht,
        object = "point",
        n_row = n_row, n_col = n_col, asp = aspect,

        fill = pt.fill, area_fill = "transparent", color = pt.color,
        panel_fill = getOption("panel_fill"),
        panel_color = getOption("panel_color"),

        size.pt = pt.size, size.ln = line_width,

        xlab = xlab, ylab = ylab, main = main,
        shape = vbs.shape,
        lab_size = getOption("lab_size"), axis_size = getOption("axis_size"),

        lvl = 0, ellipse_color = NULL, ellipse_lwd = NULL,

        fit.ln = "off", fit_power = 1,
        fit_color = NULL, fit_lwd = NULL, fit_se = NULL,

        plot_errors = FALSE,
        origin = NULL,
        jitter = jitter_y,

        violin = violin, violin_fill = violin_fill,
        box = box, box_fill = box_fill,

        bw = bw, vbs_ratio = vbs_ratio, box_adj = box_adj,
        a = a, b = b, k.iqr = k.iqr,
        fences = fences, vbs_mean = vbs_mean,

        out_shape = out_shape, out_size = pt.out.size,
        out_fill = getOption("out_fill"),
        out_color = getOption("out_color"),
        out2_fill = getOption("out2_fill"),
        out2_color = getOption("out2_color"),

        ID = ID.call, out_cut = out_cut, ID_color = "black", ID_size = ID_size,

        axis_fmt = axis_fmt, axis_x_pre = axis_x_pre, axis_y_pre = axis_y_pre,

        rotate_x = rotate_x, rotate_y = rotate_y,

        x_n_axis_skip = 0, y_n_axis_skip = 0,

        width = width, height = height, pdf_file = pdf_file,
        T.type = "cont",
        quiet = quiet, ...
      ) 

      # reset for next analysis
      options(xname=NULL)
      options(yname=NULL)
      options(facet1name=NULL)
      options(facet2name=NULL)
      options(byname=NULL)

    }  # end vbs


    } # end ncol(data) ---------

    else {  # numeric categorical variable, no plot
      if (!quiet) .ncat(plt.word, x.name, nu, 0)
    }

    }  # is.numeric(data[,i])
  }  # end for i from 1 to ncol

  if (ncol(data) > 1) {
    options(suggest = sug)
    if (is.null(pdf_file)  &&  plot.i > 0)
      if (is.null(options()$knitr.in.progress))
        .plotList(plot.i, plot.title)
  }

}
