Specify monospace font in `menu`
Asked Answered
H

2

48

Language: R. Question: Can I specify fixed width font for the menu(..,graphics=T) function?

Explanation:

I recently asked this question on how to have a user select a row of a data frame interactively:

df <- data.frame(a=c(9,10),b=c('hello','bananas'))
df.text <- apply( df, 1, paste, collapse=" | " )
menu(df.text,graphics=T)

enter image description here

I'd like the | to line up. They don't at the moment; fair enough, I haven't padded out the columns to the same width. So I use format to get every column to the same width (later I'll write code to automagically determine the width per column, but let's ignore that for now):

df.padded <- apply(df,2,format,width=8)
df.padded.text <- apply( df.padded, 1, paste, collapse=" | ")
menu( df.padded.text,graphics=T )

enter image description here

See how it's still wonky? Yet, if I look at df.padded, I get:

> df.padded
     a            b           
[1,] " 9        " "hello     "
[2,] "10        " "bananas   "

So each cell is definitely padded out to the same length.

The reason for this is probably because the default font for this (on my system anyway, Linux) is not fixed width.

So my question is: Can I specify fixed width font for the menu(..,graphics=T) function?

Update

@RichieCotton noticed that if you look at menu with graphics=T it calls select.list, which in turn calls tcltk::tk_select.list.

So it looks like I'll have to modify tcltk options for this. From @jverzani:

library(tcltk)
tcl("option", "add", "*Listbox.font", "courier 10")
menu(df.padded.text,graphics=T)

enter image description here

Given that menu(...,graphics=T) calls tcltk::tk_select.list when graphics is TRUE, my guess is that this is a viable option, as any distro that would be capable of displaying the graphical menu in the first place would also have tcltk on it, since it needs to call tk_select.list.

(As an aside, I can't find anything in the documentation that would give me the hint to try tcl('option','add',...), let alone that the option was called *Listbox.font!)

Another update -- had a closer look at the select.list and menu code, and it turns out on Windows (or if .Platform$GUI=='AQUA' -- is that Mac?), the tcltk::tk_select.list isn't called at all, and it's just some internal code instead. So modifying '*Listbox.font' won't affect this.

I guess I'll just:

  • if tcltk is there, load it, set the *Listbox.font to courier, and use tcltk::tk_select.list explicitly
  • if it isn't there, try menu(...,graphics=T) to at least get a graphical interface (which won't be monospace, but is better than nothing)
  • if that fails too, then just fallback to menu(...,graphics=F), which will definitely work.

Thanks all.

Hick answered 8/2, 2012 at 7:16 Comment(9)
There's no obvious option. menu calls select.list, which disappears into external code. You might have more luck hacking tcltk::tk_select.list.Leaseholder
@RichieCotton hmm, I thought that might be the case :(. I'll look into tcltk. Do you know if it is standard in all R distros? I'd just like to minimise the number of external packages users have to install (as I have quite a number already). Cheers.Hick
tcltk is standard on Windows but not Linux (unsure about OSX). You can test for support with capabilities("tcltk").Leaseholder
My cheap solution appears to have only displaced the problem :-( . For what it's worth, I'm on Ubuntu with R version 2.13.1 and capabilities("tcltk") yields TRUESiphonophore
You can hack this through the option command. Something like library(tcltk); tcl("option", "add", "*Listbox.font", "times 16 bold ") will do it.Asceticism
Thanks all - I guess I'll use tcl('option',...) (I'm on Fedora, R 2.14, and capabilitites('tcltk') is also TRUE for me, although I remember explicitly installing a whole bunch of gui packages). I had just hoped there was something in options I could manipulate without loading a gui library.Hick
@RichieCotton: Where did you get the 'not on Linux' from? Couldn't be further from the truth.Dele
@DirkEddelbuettel: Ok, bad language on my part. ‘menu‘ starts checking capabilities for Linux so there probably are some distros where tcltk isn't supported. Agreed that it should be there on most of them.Leaseholder
@Asceticism I know this is resurrecting things a bit, but if you'd like to post your comment as an answer I'm happy to accept it.Hick
S
1

Another approach to padding:

na.pad <- function(x,len){
    x[1:len]
}

makePaddedDataFrame <- function(l,...){
    maxlen <- max(sapply(l,length))
    data.frame(lapply(l,na.pad,len=maxlen),...)
}

x = c(rep("one",2))
y = c(rep("two",10))
z = c(rep("three",5))

makePaddedDataFrame(list(x=x,y=y,z=z))

The na.pad() function exploits the fact that R will automatically pad a vector with NAs if you try to index non-existent elements.

makePaddedDataFrame() just finds the longest one and pads the rest up to a matching length.

Schoolmistress answered 12/6, 2012 at 22:0 Comment(1)
This makes a data frame with max(length(x), length(y), length(z)) rows and NA for those row, columns where there are no values, but I'm not sure how it solves my problem of displaying in menu(vectorOfDataFrame, graphics=T) such that the font is either fixed width or appears so.Hick
B
0

I don't understand why you don't want to use View(df) (get the rowid, put the contents into temp. data frame and display it with the View command)

Edit: well, just use sprintf command

Create a function f to extract the strings from the data frame object

f <- function(x,sep1) {
 sep1=format(sep1,width=8)
 xa<-gsub(" ","",as.character(x[1]))
 a1 <- nchar(xa)
 xa=format(xa,width=8)
 xb=gsub(" ","",as.character(x[2]))
 b1 <- nchar(xb)
 xb=format(xb,width=8)
 format1=paste("%-",10-a1,"s%s%-",20-b1,"s",sep="")
 concat=sprintf(format1,xa,sep1,xb)
 concat
 }

df <- data.frame(a=c(9,10),b=c('hello','bananas'))

df.text <- apply( df, 1, f,sep1="|")

menu(df.text,graphics=T)

Of course the limits used in sprintf 10, 20 are maximum length for the number of characters in the data-frame column (a,b). You can change it to reflect it according to your data.

Bonitabonito answered 8/5, 2012 at 3:57 Comment(6)
Besides the fact that that wasn't my question (I specifically wanted to have monospace in menu): Note that the menu and View functions are fundamentally different in that the menu function is for selecting item(s) (so I can say "choose a row" and have the index/item returned), whereas the View function is for viewing only.Hick
Have you considered using sprintf command? -- see my modified edit aboveBonitabonito
This does not work when df$a goes into three digits (try data.frame(a=c(9,10,100),b=c('hello','bananas','x'))), but you've certainly made me curious - with the df in your answer I notice that nchar(df.text) is c(32,29) - how did you know that these strings of unequal length would align the '|' when it's the 10th character of the first string but only the 9th character of the second?Hick
I've done a bit more playing around, and I think you may have just got lucky with the example df I put in my question - when columns contain more general text, e.g. data.frame(a=c('green','eggs','wwww'),b=c('and','ham','lll')) this runs into trouble with non-monospace fonts because (for example) a w is very fat compared to a l. I think sprintf won't work in general for aligning data.frame columns unless the font is also monospace, hence my original question.Hick
It aligns, try a bigger value like 12 instead of 10 on sprintf in format1=paste("%-",10-a1,"s%s%-",20-b1,"s",sep=""). (12-a1) The values NEEDS to be changed depending upon your string length of characters. I chose a random number 10, but you can determine the size once you determine the maximum length of that column from the dataframe.Bonitabonito
I know. I hacked up a version that uses 2*maxWidth as the value which works great when it's just numbers in the first column and letters in the second, but the moment you put letters in the non-last column of varying width (e.g. fat 'w' vs little 'l'), you run into trouble unless the font is monospace or you can perfectly compensate for the extra width with spaces, which is font-specific.Hick

© 2022 - 2024 — McMap. All rights reserved.