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.

Community
  • 1
  • 1
mathematical.coffee
  • 55,977
  • 11
  • 154
  • 194
  • 2
    There's no obvious option. `menu` calls `select.list`, which disappears into external code. You might have more luck hacking `tcltk::tk_select.list`. – Richie Cotton Feb 08 '12 at 10:59
  • @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. – mathematical.coffee Feb 08 '12 at 12:43
  • `tcltk` is standard on Windows but not Linux (unsure about OSX). You can test for support with `capabilities("tcltk")`. – Richie Cotton Feb 08 '12 at 14:08
  • 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 TRUE – tim riffe Feb 08 '12 at 15:25
  • 3
    You can hack this through the option command. Something like `library(tcltk); tcl("option", "add", "*Listbox.font", "times 16 bold ")` will do it. – jverzani Feb 08 '12 at 19:32
  • 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. – mathematical.coffee Feb 08 '12 at 23:16
  • @RichieCotton: Where did you get the 'not on Linux' from? Couldn't be further from the truth. – Dirk Eddelbuettel Feb 08 '12 at 23:49
  • @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. – Richie Cotton Feb 09 '12 at 12:03
  • @jverzani 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. – mathematical.coffee Apr 05 '12 at 05:37

2 Answers2

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.

reuf
  • 550
  • 4
  • 12
  • 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. – mathematical.coffee Jun 12 '12 at 23:09
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.

Subs
  • 529
  • 2
  • 9
  • 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. – mathematical.coffee May 08 '12 at 04:02
  • Have you considered using `sprintf` command? -- see my modified edit above – Subs May 08 '12 at 09:59
  • 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? – mathematical.coffee May 08 '12 at 23:19
  • 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. – mathematical.coffee May 09 '12 at 00:11
  • 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. – Subs May 09 '12 at 00:14
  • 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. – mathematical.coffee May 09 '12 at 00:24