6

我正在尝试在 R 中创建一个交互式直方图,可以通过移动滑块或在文本框中输入一个值来调整其 bin 宽度。除此之外,我还想为用户提供一个选项,可以为特定的 bin 宽度保存绘图。

为此,我发现 'aplpack' 库的 'gslider' 函数是一个很好的起点。我试图修改它以满足我的目的,并了解更多关于 Tcl/Tk 结构的信息。但是我现在卡住了,无法继续,主要是因为我还没有完全理解如何捕获滑块值并在函数之间传输。

以下是我还没有真正理解的代码片段。这些来自“gslider”函数的源代码。

# What is the rationale behind using the 'assign' function here and at 
# other instances in the code?

  img <- tkrplot::tkrplot(gr.frame, newpl, vscale = 1, hscale = 1)
  tkpack(img, side = "top")
  assign("img", img, envir = slider.env)

# I understand the below lines when considered individually. But collectively,
# I am having a difficult time comprehending them. Most importantly, where 
# exactly is the slider movement captured here?

  sc <- tkscale(fr, from = sl.min, to = sl.max, 
              showvalue = TRUE, resolution = sl.delta, orient = "horiz")
  assign("sc", sc, envir = slider.env)
  eval(parse(text = "tkconfigure(sc, variable=inputbw1)"), envir = slider.env)
  sl.fun <- sl.function
  if (!is.function(sl.fun)) 
    sl.fun <- eval(parse(text = paste("function(...){", 
                                    sl.fun, "}")))
    fname <- 'tkrrsl.fun1'
    eval(parse(text = c(paste(fname, " <-"), " function(...){", 
                    "tkrreplot(get('img',envir=slider.env),fun=function()", 
                    deparse(sl.fun)[-1], ")", "}")))
    eval(parse(text = paste("environment(", fname, ")<-parent.env")))
    if (prompt) 
      tkconfigure(sc, command = get(fname))
    else tkbind(sc, "<ButtonRelease>", get(fname))

  if (exists("tkrrsl.fun1")) {
    get("tkrrsl.fun1")()
  } 
  assign("slider.values.old", sl.default, envir = slider.env)

感谢大家提供不同范围的答案。Juba 和 Greg 的答案是我可以编写以下代码的答案:

slider_txtbox <- function (x, col=1, sl.delta, title) 
{
  ## Validations
  require(tkrplot)
  pos.of.panel <- 'bottom'
  if(is.numeric(col))
    col <- names(x)[col]
  x <- x[,col, drop=FALSE]
  if (missing(x) || is.null(dim(x))) 
     return("Error: insufficient x values")
  sl.min <- sl.delta # Smarter initialization required
  sl.max <- max(x)
  xrange <- (max(x)-min(x))
  sl.default <- xrange/30
  if (!exists("slider.env")) {
    slider.env <<- new.env(parent = .GlobalEnv)    
  }
  if (missing(title)) 
    title <- "Adjust parameters"

  ## Creating initial dialogs
  require(tcltk)
  nt <- tktoplevel()
  tkwm.title(nt, title)
  if(.Platform$OS.type == 'windows')
    tkwm.geometry(nt, "390x490+0+10")
  else if(.Platform$OS.type == 'unix')
     tkwm.geometry(nt, "480x600+0+10")
  assign("tktop.slider", nt, envir = slider.env)
  "relax"
  nt.bak <- nt
  sl.frame <- tkframe(nt)
  gr.frame <- tkframe(nt)
  tx.frame <- tkframe(nt)
  tkpack(sl.frame, tx.frame, gr.frame, side = pos.of.panel)

  ## Function to create and refresh the plot
  library(ggplot2)
  library(gridExtra)
  makeplot <- function(bwidth, save) {
    if(bwidth <= 0) {
      df <- data.frame('x'=1:10, 'y'=1:10)
       histplot <- ggplot(df, aes(x=x, y=y)) + geom_point(size=0) + xlim(0, 10) +  ylim(0, 100) + 
    geom_text(aes(label='Invalid binwidth...', x=5, y=50), size=9)
    } else {

    histplot <- ggplot(data=x, aes_string(x=col)) +
  geom_histogram(binwidth=bwidth, aes(y = ..density..), fill='skyblue') + 
  theme(axis.title.x=element_text(size=15), axis.title.y=element_text(size=15), 
        axis.text.x=element_text(size=10, colour='black'),
        axis.text.y=element_text(size=10, colour='black'))
    }
    print(histplot)
    if(save){
  filename <- tkgetSaveFile(initialfile=paste('hist_bw_', bwidth, sep=''), 
                            filetypes='{{PNG files} {.png}} {{JPEG files} {.jpg .jpeg}}
                            {{PDF file} {.pdf}} {{Postscript file} {.ps}}')
  filepath <- as.character(filename)
  splitpath <- strsplit(filepath, '/')[[1]]
  flname <- splitpath[length(splitpath)]
  pieces <- strsplit(flname, "\\.")[[1]]
  ext <- tolower(pieces[length(pieces)])
  if(ext != 'png' && ext != 'jpeg' && ext != 'jpg' && ext != 'pdf' && ext != 'ps') {
    ext <- 'png'
    filepath <- paste(filepath, '.png', sep='')
    filename <- tclVar(filepath)
  }
  if(ext == 'ps')
    ext <- 'postscript'
  eval(parse(text=paste(ext, '(file=filepath)', sep='')))
  eval(parse(text='print(histplot)'))
  dev.off()
}
  }
  img <- tkrplot::tkrplot(gr.frame, makeplot(sl.default, FALSE), vscale = 1, hscale = 1)
  tkpack(img, side = "top")
  assign("img", img, envir = slider.env)

  ## Creating slider, textbox and labels
  parent.env <- sys.frame(sys.nframe() - 1)
  tkpack(fr <- tkframe(sl.frame), side = 'top')
  sc <- tkscale(fr, from = sl.min, to = sl.max, 
            showvalue = TRUE, resolution = sl.delta,
            orient = "horiz")
  tb <- tkentry(fr, width=4)
  labspace <- tklabel(fr, text='\t\t\t')
  tkpack(sc, labspace, tb, side = 'left')

  tkpack(textinfo <- tkframe(tx.frame), side = 'top')
  lab <- tklabel(textinfo, text = '                    Move slider', width = "20")
  orlabel <- tklabel(textinfo, text='          OR', width='10')
  txtboxmsg <- tklabel(textinfo, text = 'Enter binwidth', width='20')
  tkpack(txtboxmsg, orlabel, lab, side='right')

  tkpack(f.but <- tkframe(sl.frame))
  tkpack(tklabel(f.but, text=''))
  tkpack(tkbutton(f.but, text = "Exit", command = function() tkdestroy(nt)), 
     side='right')
  tkpack(tkbutton(f.but, text = "Save", command = function(...) {
    bwidth <- as.numeric(tclvalue(get('inputtb', envir=slider.env)))
    tkrreplot(get('img',envir=slider.env),fun=function() { makeplot(bwidth, TRUE); sync_slider()})
  }), side='right')

  ## Creating objects and variables associated with slider and textbox
  assign("sc", sc, envir = slider.env)
  eval(parse(text = "assign('inputsc', tclVar(sl.default), envir=slider.env)"))
  eval(parse(text = "tkconfigure(sc, variable=inputsc)"), envir = slider.env)

  assign("tb", tb, envir = slider.env)
  eval(parse(text = "assign('inputtb', as.character(tclVar(sl.default)),
         envir=slider.env)"))
  eval(parse(text = "tkconfigure(tb, textvariable=inputtb)"), envir = slider.env)

  ## Function to update the textbox value when the slider has changed
  sync_textbox <- function() {
  bwidth_sl <- tclvalue(get('inputsc', envir=slider.env))
  assign('inputtb', tclVar(bwidth_sl), envir=slider.env)
  eval(parse(text = "tkconfigure(tb, textvariable=inputtb)"), envir = slider.env)
}

 ## Function to update the slider value when the textbox has changed
 sync_slider <- function() {
 bwidth_tb <- tclvalue(get('inputtb', envir=slider.env))
 assign('inputsc', tclVar(bwidth_tb), envir=slider.env)
 eval(parse(text = "tkconfigure(sc, variable=inputsc)"), envir = slider.env)
}  

  ## Bindings : association of certain functions to certain events for the slider
  ## and the textbox

  tkbind(sc, "<ButtonRelease>", function(...) {
    bwidth <- as.numeric(tclvalue(get('inputsc', envir=slider.env)))
    tkrreplot(get('img',envir=slider.env),fun=function() { makeplot(bwidth, FALSE); sync_textbox()})
  })

  tkbind(tb, "<Return>", function(...) {
    bwidth <- as.numeric(tclvalue(get('inputtb', envir=slider.env)))
    if(bwidth > sl.max && !is.na(bwidth)) {
      bwidth <- sl.max
      assign('inputtb', tclVar(bwidth), envir=slider.env)
      eval(parse(text = "tkconfigure(tb, textvariable=inputtb)"), envir = slider.env)
     } else
    if(bwidth < sl.min || is.na(bwidth)) {
      bwidth <- sl.min
      assign('inputtb', tclVar(bwidth), envir=slider.env)
      eval(parse(text = "tkconfigure(tb, textvariable=inputtb)"), envir = slider.env)
     }
  tkrreplot(get('img',envir=slider.env),fun=function() { makeplot(bwidth, FALSE);    sync_slider()})
})

}

library(ggplot2)
slider_txtbox(movies, 'rating', 0.1, 'Adjust binwidth') 
4

4 回答 4

2

如果您不坚持使用本地解决方案,您可以尝试rapporter.net,它可以让您使用任意数量的可调整滑块轻松指定此类任务。好的,足够的营销:)

这是一个快速演示: 交互式直方图mtcars,如下所示:

rapporter.net 上的交互式直方图演示

在那里,您可以选择一个众所周知的变量mtcars,但当然您可以提供任何要在此处使用的数据框,或者在免费注册后调整上述表格。


它是怎么做的?我刚刚创建了一个快速的rapport模板并让它rapplicate。模板的主体是用 brew 风格编写的(有关详细信息,请参阅上面的“rapport” URL):

<%=
evalsOptions('width', width)
evalsOptions('height', height)
%>

# Histogram

<%=
set.caption(paste('Histogram of', var.name))
hist(var, breaks=seq(min(var), max(var), diff(range(var))/round(binwidth)), main = paste('Histogram of', var.name), xlab = '')
%>

## Parameters

Provided parameters were:

  * variable: <%=var.name%> (<%=var.label%>)
  * bin-width of histogram: <%=binwidth%>
  * height of generated images: <%=height%>
  * width of generated images: <%=width%>

# Kernel density plot

<%=
set.caption('A kernel density plot')
plot(density(var), main = '', xlab = '')
%>

但是一个简单的任务示例也可以通过一个简单的单行模板来解决:

<%=hist(var, breaks=seq(min(var), max(var), diff(range(var))/round(binwidth)))%>

在那里,您只需要创建一个新模板,单击添加两种输入类型(numeric任何数据集的一个变量和一个保存直方图的number输入字段),就可以开始了。binwidth

于 2013-01-31T09:23:33.680 回答
2

这是一个基于您首次提交的完整代码的带有注释的最小工作示例。由于我远不是 tcl/tk 方面的专家,因此可能有更清洁或更好的方法来做到这一点。而且它非常不完整(例如,应该检查文本框的值是否在滑块的范围内等):

library(ggplot2)
library(gridExtra)
title <- "Default title"
data(movies)

## Init dialog
require(tkrplot)
if (!exists("slider.env")) slider.env <<- new.env(parent = .GlobalEnv)
require(tcltk)
nt <- tktoplevel()
tkwm.title(nt, title)
tkwm.geometry(nt, "480x600+0+10")
assign("tktop.slider", nt, envir = slider.env)
"relax"
nt.bak <- nt
sl.frame <- tkframe(nt)
gr.frame <- tkframe(nt)
tx.frame <- tkframe(nt)
tkpack(sl.frame, tx.frame, gr.frame, side = "bottom")
## First default plot
newpl <- function(...) {
  dummydf <- data.frame('x'=1:10, 'y'=1:10)
  dummy <- ggplot(dummydf, aes(x=x, y=y)) + geom_point(size=0) + xlim(0, 10) + ylim(0, 100) + 
    geom_text(aes(label='Generating plot...', x=5, y=50), size=9)
  print(dummy)
  }
img <- tkrplot::tkrplot(gr.frame, newpl, vscale = 1, hscale = 1)
tkpack(img, side = "top")
assign("img", img, envir = slider.env)
tkpack(fr <- tkframe(sl.frame), side = 'top')

## Creating slider, textbox and labels
sc <- tkscale(fr, from = 0, to = 5, showvalue = TRUE, resolution = 0.1, orient = "horiz")
tb <- tkentry(fr, width=4)
lab <- tklabel(fr, text = 'Select binwidth ', width = "16")
orlabel <- tklabel(fr, text=' or ', width='4')
tkpack(lab, sc, orlabel, tb, side = 'left')
tkpack(textinfo <- tkframe(tx.frame), side = 'top')


## Creating objects and variables associated with slider and textbox
assign("sc", sc, envir = slider.env)
assign("tb", tb, envir = slider.env)
assign('inputsc', tclVar(2.5), envir=slider.env)
assign('inputtb', tclVar('2.5'), envir=slider.env)
eval(parse(text = "tkconfigure(sc, variable=inputsc)"), envir = slider.env)
eval(parse(text = "tkconfigure(tb, textvariable=inputtb)"), envir = slider.env)

## Function to update the textbox value when the slider has changed
sync_textbox <- function() {
  bwidth_sl <- tclvalue(get('inputsc', envir=slider.env))
  assign('inputtb', tclVar(bwidth_sl), envir=slider.env)
  eval(parse(text = "tkconfigure(tb, textvariable=inputtb)"), envir = slider.env)
}

## Function to update the slider value when the textbox has changed
sync_slider <- function() {
  bwidth_tb <- tclvalue(get('inputtb', envir=slider.env))
  assign('inputsc', tclVar(bwidth_tb), envir=slider.env)
  eval(parse(text = "tkconfigure(sc, variable=inputsc)"), envir = slider.env)
}

## Function to refresh the plot
refresh <- function(bwidth) {
  histplot <- ggplot(data=movies, aes_string(x="rating")) +
     geom_histogram(binwidth=bwidth, 
                    aes(y = ..density..), fill='skyblue') + 
                      theme(axis.title.x=element_text(size=15), axis.title.y=element_text(size=15), 
                            axis.text.x=element_text(size=10, colour='black'),
                            axis.text.y=element_text(size=10, colour='black'))
  print(histplot)
}

## Bindings : association of certain functions to certain events for the slider
## and the textbox

tkbind(sc, "<ButtonRelease>", function(...) {
  bwidth <- as.numeric(tclvalue(get('inputsc', envir=slider.env)))
  tkrreplot(get('img',envir=slider.env),fun=function() { refresh(bwidth); sync_textbox()})
})

tkbind(tb, "<Return>", function(...) {
  bwidth <- as.numeric(tclvalue(get('inputtb', envir=slider.env)))
  tkrreplot(get('img',envir=slider.env),fun=function() { refresh(bwidth); sync_slider()})
})
于 2013-01-31T13:08:33.820 回答
2

您可能想查看 R 包“rpanel”——它在后台使用 tcltk,但使用起来更简单:

面板

面板参考

于 2013-01-31T18:11:18.870 回答
1

我不知道 gslider 功能,无法帮助您,但这里有一些替代方案:

一个简单的选择是使用tkexampTeachingDemos 包中的函数,这是一种方法:

library(TeachingDemos)

myhist <- function(x, s.width, e.width, ...) {
    if( missing(e.width) || is.null(e.width) || is.na(e.width) ) {
        e.width<- s.width
    }
    b <- seq( min(x)-e.width/2, max(x)+e.width, by=e.width )
    hist(x, b, ...)
}

mylist <- list( s.width=list('slider', init=1, from=1, to=10, resolution=1),
    e.width=list('numentry', init='', width=7)
)

sampdata <- rnorm(100, 50, 5)
tkexamp(myhist(sampdata), mylist)

这将使用您的直方图、滑块和条目小部件创建一个快速 GUI。条的宽度由条目小部件中的值确定,如果为空白(默认),则为滑块的值。不幸的是,滑块和条目小部件不会相互更新。有一个按钮可以打印当前调用,因此可以从默认或当前绘图设备中的命令行重新创建相同的绘图。您可以编辑mylist上面的变量以使控件更适合您的数据。

如果您希望条目和滑块相互更新,那么您可以更直接地对其进行编程。这是一个使用的基本功能tkrplot

mytkhist <- function(x, ...) {

    width <- tclVar()
    tclvalue(width) <- 1

    replot <- function(...) {
        width <- as.numeric(tclvalue(width))
        b <- seq( min(x) - width/2, max(x)+width, by=width )
        hist(x,b,...)
    }

    tt <- tktoplevel()
    img <- tkrplot(tt, replot)
    tkpack(img, side='top')

    tkpack( tkscale(tt, variable=width, from=1, to=10,
        command=function(...) tkrreplot(img),
        orient='horizontal'), side='top' )
    tkpack( e <- tkentry(tt, textvariable=width), side='top' )
    tkbind(e, "<KeyRelease>", function(...) tkrreplot(img))
}

mytkhist(sampdata)

滑块(比例)和条目小部件都使用相同的变量这一事实使它们自动相互更新(无需调用assign)。commandintkscale和调用的参数tkbind意味着对滑块或条目的任何更改都将更新绘图。这没有任何东西可以保存当前绘图,但您应该能够添加该部分以及您想要使用的任何其他控件。

于 2013-01-31T18:06:16.023 回答