R/xaxis.R

Defines functions xaxis_biased xaxis_labels

#----------------------------------------------#
# Author: Laurent Berge
# Date creation: Sun Mar 22 09:04:17 2020
# ~: Only edit in fplot
#----------------------------------------------#


# Input of line.min / line.max => the default bottom margin goes from 0 to 4 (5 lines)
# Starts at 0!
# Beware: we return line => but in the axis sense! (input 0 is output -1)
xaxis_labels = function(at, labels, line.min = 0, line.max = 2, minCex = 0.8, add_ticks = FALSE, trunc = 20, trunc.method = "auto", only.params = FALSE, ...){
    # This function automates the placement of labels into the axis 1
    # It first put the cex to the appropritate level to insert the 1st
    # label into the frame, then
    # It uses the vertical placement to fit all the labels
    # if only.params => nothing is plotted

    labels = as.character(labels)

    # only 1 value => we do nothing
    if(length(at) == 1){
        if(only.params){
            res = list(cex = 1, line = line.min - 1)
            return(res)
        }
        axis(1, at=at, labels = labels, tick = add_ticks, line = line.min - 1)
        return(invisible(NULL))
    }

    # To send into a function: myat & lab
    myOrder = order(at)
    myLabels = labels[myOrder]
    myAt = at[myOrder]

    n = length(myAt)

    # # Truncation of the items
    # myLabels = substr(myLabels, 1, trunc)
    # # we replace the trucated element with dots to show that there is more
    # qui = nchar(myLabels) == trunc & nchar(labels[myOrder]) > trunc
    # myLabels[qui] = gsub("..$", "\\.\\.", myLabels[qui])
    if(!"call" %in% class(myLabels)){
        if(any(nchar(myLabels) > trunc)){
            # myLabels = truncate_string(myLabels, trunc, method = trunc.method)
        }

    } else {
        myLabels = gsub(" *phantom\\([\\)]*\\) *", "", deparse(myLabels))
    }


    # We compute the space that is left to display the label
    # 1st into the plot
    largeur = diff(par("usr")[1:2])
    half_plotSpace_in = (myAt[1] - par("usr")[1]) / largeur * par("pin")[1]
    # 2nd using the margin
    total_half_space = half_plotSpace_in + min(par("mai")[c(2,4)])

    # If it is too large, we reduce it:
    myCex = 1
    while(myCex > minCex && strwidth(myLabels[1], units = "in", cex = myCex)/2 > total_half_space){
        myCex = myCex * 0.95
    }

    if(myCex < minCex) myCex = minCex

    # Now we order the vertical space where the labels will appear
    # line_step = max(strheight(myLabels, units = "in", cex = myCex)) / max(strheight(myLabels, units = "in"))
    # we use the fact that:
    # 1) smaller characters can be more stacked vertically
    # 2) a line height is almost equivalent to character height

    # all_lines = -1:line.max
    ok = FALSE
    failed = FALSE
    while(!ok){
        ok = TRUE # if !ok there's a problem, we need to reduce cex

        all_width = strwidth(myLabels, units = "in", cex = myCex)

        # there can be more lines than expected depending on cex
        line_step = max(strheight(myLabels, units = "in", cex = myCex)) / max(strheight(myLabels, units = "in"))
        all_lines = seq(line.min, line.max, by = line_step)

        myLine = current_Line = line.min
        for(i in 2:n){
            # for each element, we find out the line where to write it
            for(line_index in all_lines){
                # we get the closest index with that Line level
                if(line_index %in% myLine){
                    index = max(which(myLine == line_index))
                    # we look at the distance between the two elements
                    at_first = myAt[index]
                    at_second = myAt[i]
                    # the distance in inches between the two 'at' + the space of one letter
                    dist_in = (at_second - at_first) * par("pin")[1] / largeur - strwidth("O", units = "in", cex = 1)
                    # the half sizes of the two strings
                    half_sums_in = (all_width[index] + all_width[i]) / 2
                    if(half_sums_in > dist_in){
                        # we go to the next line_index
                    } else {
                        myLine = c(myLine, line_index)
                        break
                    }
                } else {
                    # this line item has not been used already
                    myLine = c(myLine, line_index)
                    break
                }

                if(line_index == max(all_lines)) {
                    # Means it does not fit => we need to reduce cex
                    if(myCex <= minCex){
                        # already at the minimum, we keep going then
                        myLine = c(myLine, line_index)
                        failed = TRUE
                    } else {
                        # we get out totally and reduce the cex
                        ok = FALSE
                        myCex = myCex * 0.95
                    }
                }
            }

            if(!ok){
                # This means we've been into a non solvable situation
                break
            }
        }
    }

    if(only.params){
        # we substract 1 to make it "axis compatible"
        res = list(cex = myCex, line = myLine - 1 + 0.2, failed = failed)
        line_height = par("mai")[1] / par("mar")[1]
        res$height_in = (max(myLine) + 1.2) * line_height
        res$height_line = max(myLine) + 1.2
        return(res)
    }

    # We draw the ticks
    if(add_ticks){
        # 1) drawing them
        for(line in unique(myLine)){
            qui = which(myLine == line)
            axis(1, at = myAt[qui], labels = NA, tcl = -1-line, lwd = 0, lwd.ticks = 1)

        }

        # 2) "Cleaning" them
        n = length(myLabels)
        mid_width = strwidth(myLabels, units = "user") / 2

        # ceux qui 'debordent' a droite
        qui = which(mid_width[-n] > diff(myAt))
        if(length(qui) > 0){
            for(i in qui){
                # axis(1, at = myAt[i+1], labels = NA, lwd = 0, col = "white", line = myLine[i], lwd.ticks = 1.5)
                axis(1, at = myAt[i+1], labels = "|", lwd = 0, col.axis = "white", cex.axis = 1.5, line = myLine[i], lwd.ticks = 0)
            }
        }

        # ceux qui 'debordent' a gauche
        qui = which(mid_width[-1] > diff(myAt))
        if(length(qui) > 0){
            for(i in qui){
                # axis(1, at = myAt[i+1], labels = NA, lwd = 0, col = "white", line = myLine[i], lwd.ticks = 1.5)
                axis(1, at = myAt[i], labels = "|", lwd = 0, col.axis = "white", cex.axis = 1.5, line = myLine[i+1], lwd.ticks = 0)
            }
        }

    }

    # We draw the labels
    myLine = myLine + 0.2 - 1 # -1 to make it axis compatible
    for(line in unique(myLine)){
        qui = which(myLine == line)
        # the labels
        axis(1, at = myAt[qui], labels = myLabels[qui], line = line, cex.axis = myCex, lwd = 0, gap.axis = 0.01)

    }

    res = list(cex = myCex, line = myLine)
    line_height = par("mai")[1] / par("mar")[1]
    res$height_in = (max(myLine) + 2) * line_height
    res$height_line = max(myLine) + 2
    return(res)

}

# line: goes from 0 to 4 in a standard plot
xaxis_biased = function(at, labels, angle, cex, line.min = 0, line.max = 2, yadj = 0.5, trunc = 20, trunc.method = "auto", only.params = FALSE, ...){

    check_arg(angle, "null numeric vector no na")
    check_arg(cex, "null numeric vector no na")

    if(line.max < line.min){
        message("xaxis_biased: line.max < line.min (i.e. ", line.max, " < ", line.min, ") line.max set to ", line.min, ".")
        line.max = line.min
    }


    dots = list(...)
    dots$x = at

    if(!"call" %in% class(labels)){
        if(any(nchar(labels) > trunc)){
            # labels_trunc = truncate_string(labels, trunc = trunc, method = trunc.method)
            labels_trunc = labels
        } else {
            labels_trunc = labels
        }
    } else {
        labels_trunc = gsub(" *phantom\\([\\)]*\\) *", "", deparse(labels))
    }

    dots$labels = labels_trunc

    # setting automatically the cex and angle
    DO_ALGO = FALSE
    if(missnull(angle)){
        angle2check = c(45, 40, 35)
        DO_ALGO = TRUE
    } else {
        angle2check = angle
        DO_ALGO = length(angle) > 1
    }

    if(missnull(cex)){
        cex2check = c(1, 0.9, 0.8)
        DO_ALGO = TRUE
    } else {
        cex2check = cex
        DO_ALGO = length(cex) > 1
    }

    line_height = par("mai")[1] / par("mar")[1]

    if(DO_ALGO){
        lab_max = labels_trunc[which.max(strwidth(labels_trunc))]
        n_angle = length(angle2check)
        w_all = rep(sapply(cex2check, function(x) strwidth(lab_max, "in", cex = x)), n_angle)*1.05
        SH_all = rep(sapply(cex2check, function(x) strheight("W", "in", cex = x)), n_angle)
        angle_all = rep(angle2check, each = length(cex2check))
        h_all = SH_all / cos(angle_all / 360 * 2 * pi)
        longueur_cote = sin(angle_all / 360 * 2 * pi) * w_all + h_all

        total_height = line_height * (line.max - line.min + 1 - yadj)

        qui = longueur_cote <= total_height
        if(any(qui)){
            i = which.max(qui)
            angle = angle_all[i]
            cex = rep(cex2check, n_angle)[i]
            height_in = longueur_cote[i]
        } else {
            # message("xaxis_biased: Labels could not fit.")
            angle = tail(angle_all, 1)
            cex = tail(cex2check, 1)
            height_in = tail(longueur_cote, 1)
        }

    } else if(only.params) {
        lab_max = labels_trunc[which.max(strwidth(labels_trunc))]
        n_angle = length(angle2check)
        w_all = rep(sapply(cex2check, function(x) strwidth(lab_max, "in", cex = x)), n_angle)*1.05
        SH_all = rep(sapply(cex2check, function(x) strheight("W", "in", cex = x)), n_angle)
        angle_all = rep(angle2check, each = length(cex2check))
        h_all = SH_all / cos(angle_all / 360 * 2 * pi)
        longueur_cote = sin(angle_all / 360 * 2 * pi) * w_all + h_all

        height_in = longueur_cote
    }

    line_height_usr = line_height / par("pin")[2] * diff(par("usr")[3:4])

    if(only.params){
        res = list(cex = cex, angle = angle, height_in = height_in + strheight("W", units = "in", cex = cex))
        res$height_usr = height_in / par("pin")[2] * diff(par("usr")[3:4])
        res$height_line = height_in / line_height
        return(res)
    }

    dots$cex = cex
    SH = strheight("WWW", units = "user")
    dots$y = par("usr")[3] - yadj*SH - (line.min) * line_height_usr
    dots$srt = angle
    dots$xpd = TRUE
    dots$adj = 1

    do.call("text", dots)

    return(invisible(list(cex=cex, angle=angle)))
}

Try the fixest package in your browser

Any scripts or data that you put into this service are public.

fixest documentation built on Nov. 24, 2023, 5:11 p.m.