#  File src/library/graphics/R/barplot.R
#  Part of the R package, https://www.R-project.org
#
#  Copyright (C) 1995-2026 The R Core Team
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  A copy of the GNU General Public License is available at
#  https://www.R-project.org/Licenses/

barplot <- function(height, ...) UseMethod("barplot")

barplot.default <-
function(height, width = 1, space = NULL, names.arg = NULL,
	legend.text = NULL, beside = FALSE, horiz = FALSE,
	density = NULL, angle = 45,
	col = NULL, border = par("fg"),
	main = NULL, sub = NULL, xlab = NULL, ylab = NULL,
	xlim = NULL, ylim = NULL, xpd = TRUE, log = "",
	axes = TRUE, axisnames = TRUE,
	cex.axis = par("cex.axis"), cex.names = par("cex.axis"),
	inside = TRUE, plot = TRUE, axis.lty = 0, offset = 0, add = FALSE,
	ann = !add && par("ann"),
	args.legend = NULL,
	orderH = c("none", "incr", "decr"),
	panel.first = NULL, panel.last = NULL,
	...)
{
    if (!missing(inside)) .NotYetUsed("inside", error = FALSE)# -> help(.)

    if (is.null(space))
	space <- if (is.matrix(height) && beside) c(0, 1) else 0.2
    space <- space * mean(width)

    if (plot && axisnames && is.null(names.arg))
	names.arg <-
	    if(is.matrix(height)) colnames(height) else names(height)

    vectorInput <- (is.vector(height)
	|| (is.array(height) && (length(dim(height)) == 1)))
	## Treat vectors and 1-d arrays the same.
    if (vectorInput) {
	height <- cbind(height) # 1-column matrix
	beside <- TRUE
	## The above may look strange, but in particular makes color
	## specs work as most likely expected by the users.
	if(is.null(col)) col <- "grey"
    } else if (is.matrix(height)) {
	## In the matrix case, we use "colors" by default.
	if(is.null(col))
	    col <- gray.colors(nrow(height))
    }
    else
	stop("'height' must be a vector or a matrix")

    if(is.logical(legend.text))
      legend.text <-
	if(isTRUE(legend.text) && is.matrix(height)) rownames(height) else NULL

    stopifnot(is.character(log))
    logx <- logy <- FALSE
    if (log != "") {
	logx <- length(grep("x", log)) > 0L
	logy <- length(grep("y", log)) > 0L
    }
    ## Cannot use rect(*, density=.) when log scales used
    if ((logx || logy) && !is.null(density))
      stop("Cannot use shading lines in bars when log scale is used")

    NR <- nrow(height)
    NC <- ncol(height)

    if (beside) {
	if (length(space) == 2 && !vectorInput)
	    space <- rep.int(c(space[2L], rep.int(space[1L], NR - 1)), NC)
	width <- rep_len(width, NR)
    } else {
	width <- rep_len(width, NC)
    }

    offset <- rep_len(as.vector(offset), length(width))

    delta <- width / 2
    w.r <- cumsum(space + width)
    w.m <- w.r - delta
    w.l <- w.m - delta

    log.dat <- (logx && horiz) || (logy && !horiz)# log scale in data direction
    ## check height + offset if using log scale to prevent log(<=0) error
    if (log.dat) {
	if (min(height + offset, na.rm = TRUE) <= 0)
	    stop("log scale error: at least one 'height + offset' value <= 0")
	if (logx && !is.null(xlim) && min(xlim) <= 0)
	    stop("log scale error: 'xlim' <= 0")
	if (logy && !is.null(ylim) && min(ylim) <= 0)
	    stop("log scale error: 'ylim' <= 0")

	## if axis limit is set to < above, adjust bar base value
	## to draw a full bar
	rectbase <-
	    if	    (logy && !horiz && !is.null(ylim))	ylim[1L]
	    else if (logx && horiz  && !is.null(xlim))	xlim[1L]
	    else 0.9 * min(height, na.rm = TRUE)
    } else rectbase <- 0

    ## if stacked bar, set up base/cumsum levels, adjusting for log scale
    if (!beside) {
        orderH <- match.arg(orderH)
        if(orderH != "none") {
            decr <- (orderH == "decr")
            orderHgt <- apply(height, 2L, order, decreasing = decr)
        }
	iC <- 1L:NC
        height <-
            rbind(rectbase,
                  apply(switch(orderH,
                               "decr" =,
                               "incr" = vapply(iC, function(j) height[orderHgt[,j], j], numeric(NR)),
                               "none" = height),
                        2L, cumsum))
    }

    rAdj <- offset + (if (log.dat) 0.9 * height else -0.01 * height)

    delta <- width / 2
    w.r <- cumsum(space + width)
    w.m <- w.r - delta
    w.l <- w.m - delta
    if (horiz) {
	if (is.null(xlim)) xlim <- range(rAdj, height + offset, na.rm = TRUE)
	if (is.null(ylim)) ylim <- c(min(w.l), max(w.r))
    } else {
	if (is.null(xlim)) xlim <- c(min(w.l), max(w.r))
	if (is.null(ylim)) ylim <- range(rAdj, height + offset, na.rm = TRUE)
    }
    if (beside)
	w.m <- matrix(w.m, ncol = NC)
    if(plot) { ##-------- Plotting :
        dev.hold()
	opar <-
	    if (horiz)	par(xaxs = "i")
	    else	par(yaxs = "i")
	on.exit({dev.flush();par(opar)})

	if (!add) {
	    plot.new()
	    plot.window(xlim, ylim, log = log, ...)
	}
	panel.first
	opar <- c(opar, par(xpd = xpd))

	xyrect <- function(x1,y1, x2,y2, horizontal = TRUE, ...) {
	    if(horizontal)
		rect(x1,y1, x2,y2, ...)
	    else
		rect(y1,x1, y2,x2, ...)
	}
	if (beside)
	    xyrect(rectbase + offset, w.l, c(height) + offset, w.r,
		   horizontal = horiz,
		   angle = angle, density = density,
                   col = col, border = border)
	else {
	    ## noInside <- NC > 1 && !inside # outside border, but not inside
	    ## bordr <- if(noInside) 0 else border
            hNR <- height[1L:NR, , drop=FALSE]
	    for (i in iC) {
		xyrect(hNR[, i] + offset[i], w.l[i],
		       height[ -1,  i] + offset[i], w.r[i],
		       horizontal = horiz, angle = angle, density = density,
		       col = if(orderH != "none") col[orderHgt[,i]] else col,
		       border = border)# = bordr
		## if(noInside)
		##  xyrect(min(height[, i]), w.l[i], max(height[, i]), w.r[i],
		##	   horizontal = horiz, border= border)
	    }
	}
	panel.last
	if (axisnames && !is.null(names.arg)) { # specified or from {col}names
	    at.l <- if (length(names.arg) != length(w.m)) {
		if (length(names.arg) == NC) # i.e. beside (!)
		    colMeans(w.m)
		else
		    stop("incorrect number of names")
	    } else w.m
	    axis(if(horiz) 2 else 1, at = at.l, labels = names.arg,
		 lty = axis.lty, cex.axis = cex.names, ...)
	}
	if(!is.null(legend.text)) {
	    legend.col <- rep_len(col, length(legend.text))
	    if((horiz && beside) || (!horiz && !beside)) {
		legend.text <- rev(legend.text)
		legend.col  <- rev(legend.col)
		density <- rev(density)
		angle <- rev(angle)
	    }
	    xy <- par("usr")
            if(is.null(args.legend)) {
                legend(xy[2L] - xinch(0.1), xy[4L] - yinch(0.1),
                       legend = legend.text, angle = angle, density = density,
                       fill = legend.col, xjust = 1, yjust = 1)
            } else {
                args.legend1 <- list(x = xy[2L] - xinch(0.1),
                                     y = xy[4L] - yinch(0.1),
                                     legend = legend.text,
                                     angle = angle, density = density,
                                     fill = legend.col, xjust = 1, yjust = 1)
                args.legend1[names(args.legend)] <- args.legend
                do.call("legend", args.legend1)
            }
	}
	if(ann) title(main = main, sub = sub, xlab = xlab, ylab = ylab, ...)
	if(axes) axis(if(horiz) 1 else 2, cex.axis = cex.axis, ...)
	invisible(w.m)
    } else w.m
}

barplot.formula <- function(formula, data, subset, na.action,
                            horiz = FALSE, xlab = NULL, ylab = NULL, ...)
{
    if (missing(formula) || length(formula) != 3L)
        stop("'formula' missing or incorrect")
    m <- match.call(expand.dots = FALSE)
    if (is.matrix(eval(m$data, parent.frame())))
        m$data <- as.data.frame(data)
    m$... <- m$horiz <- m$xlab <- m$ylab <- NULL
    m[[1L]] <- quote(stats::model.frame)

    mf <- eval(m, parent.frame())
    if (ncol(mf[-1L]) == 0L || ncol(mf[-1L]) >= 3L)
        stop("formula must specify 1 or 2 categorical variables")
    if (anyDuplicated(mf[-1L]))
        stop("duplicated categorical values - try another formula or subset")
    if (horiz) {
        if(is.null(ylab)) ylab <- names(mf)[ncol(mf)]
    } else
        if(is.null(xlab)) xlab <- names(mf)[ncol(mf)]
    if (is.matrix(mf[[1L]])) { ## LHS is cbind()
        if (ncol(mf[-1L]) != 1L)
            stop("formula with cbind() must specify 1 categorical variable")
        lhs <- t(mf[[1L]])
        colnames(lhs) <- mf[[ncol(mf)]]
        barplot.default(lhs, horiz = horiz, xlab = xlab, ylab = ylab, ...)
    } else {
	if (horiz) {
	    if(is.null(xlab)) xlab <- names(mf)[1L]
	} else
	    if(is.null(ylab)) ylab <- names(mf)[1L]
	barplot.default(stats::xtabs(mf, addNA = TRUE),
			horiz = horiz, xlab = xlab, ylab = ylab, ...)
  }
}
