Sha*_*pie 58
简单问题:有没有办法在pdf文件中绘制R图并包含工具提示?
总有办法.但是魔鬼在细节中,所以真正的问题是:你愿意弄脏你的手吗?
PDF确实支持某些类型的对象(如超链接)的工具提示.因此,如果有一种方法可以插入原始PDF语句,表明在绘图中的某个位置应该有类似超链接的对象,那么有一种方法可以弹出工具提示.
现在,我知道生成和编译原始PDF语句的唯一方法是使用TeX创建文档.肯定有其他方法可以做到,但这是我熟悉的方法.以下示例使用Cameron Bracken和我编写的图形设备tikzDevice,将R图形渲染为LaTeX代码.tikzDevice初步支持通过tikzAnnotate
函数将任意LaTeX代码注入图形输出流- 我们将使用它将PDF标注放入图中.
涉及的步骤是:
tikzAnnotate
在绘图中的特定点调用LaTeX宏.在下面的示例中,一个主要警告附加到第2步. 使用的坐标计算仅适用于基本图形,而不适用于ggplot2等网格图形.
tikzDevice允许您创建包含任意LaTeX命令执行的R图形.通常这样做是为了插入诸如$\alpha$
绘图标题之类的东西来生成希腊字母,α或复杂方程.在这里,我们将使用此功能来调用一些原始的PDF巫毒.
您希望在生成tikzDevice图形期间可用的任何LaTeX宏需要通过设置tikzLatexPackages
选项预先定义.在这里,我们将向该声明附加一些内容:
require(tikzDevice) # So that default options are set
options(tikzLatexPackages = c(
getOption('tikzLatexPackages'), # The original contents: required stuff
# Avert your eyes for a sec, all will be explained below
"\\def\\tooltiptarget{\\phantom{\\rule{1mm}{1mm}}}",
"\\newbox\\tempboxa\\setbox\\tempboxa=\\hbox{}\\immediate\\pdfxform\\tempboxa \\edef\\emptyicon{\\the\\pdflastxform}",
"\\newcommand\\tooltip[1]{\\pdfstartlink user{/Subtype /Text/Contents (#1)/AP <</N \\emptyicon\\space 0 R >>}\\tooltiptarget\\pdfendlink}"
))
Run Code Online (Sandbox Code Playgroud)
如果引用废话的所有人都被关心可读性的人写成LaTeX代码,那么它看起来像这样:
\def\tooltiptarget{\phantom{\rule{1mm}{1mm}}}
\newbox\tempboxa
\setbox\tempboxa=\hbox{}
\immediate\pdfxform\tempboxa
\edef\emptyicon{\the\pdflastxform}
\newcommand\tooltip[1]{%
\pdfstartlink user{%
/Subtype /Text
/Contents (#1)
/AP <<
/N \emptyicon\space 0 R
>>
}%
\tooltiptarget%
\pdfendlink%
}
Run Code Online (Sandbox Code Playgroud)
对于那些从未在野外散步并在TeX中进行过一些"编程"的程序员来说,这是对上述代码的一个打击(据我所知,无论如何,TeX可能变得非常奇怪 - 尤其是当你带着它进入战壕):
第1行:定义一个tooltiptarget
不可见的对象(一个幻像),是一个1mm x 1mm的矩形(规则).这将是我们将用于检测鼠标悬停的屏幕区域.
第2行:创建一个新框,类似于typset材质的"页面片段".可以包含几乎任何东西,包括其他盒子(有点像R列表).叫它tempboxa
.
第3行:分配内容tempboxa
以包含一个空框,该框使用水平布局排列其内容(这不重要,可能使用了vbox或其他框).
第4行:使用的内容创建PDF表单XObject tempboxa
.PDF文件可以使用Form XObject来存储可能反复使用的图形,如徽标.在这里,我们创建了一个"空白图标",我们可以在以后使用它来减少视觉混乱.TeX推迟输出操作,例如将对象写入PDF文件,直到满足某些条件 - 例如页面已填满.立即确保此操作不会延迟.
第5行:此行捕获一个整数值,该值用作对刚刚创建的PDF XObject的引用,并为其指定名称emptyicon
.
第6行:开始定义一个名为tooltip的新宏,它接受1个参数,该参数在宏体中被称为#1
.宏中的每一行都以注释字符结束%
,以防止TeX注意到为了可读性而添加的换行符(换行符可能在宏内部产生奇怪的效果).
第7行:输出原始PDF命令(pdfstartlink).这开始创建一个新的PDF注释对象(\Type \Annot
),其中有大约20种不同的子类型 - 其中有超链接.其后的每一行都包含带有几个TeX宏的原始PDF标记.
第8行:声明我们将要使用的注释子类型.在这里,我将使用纯文本注释,例如注释或便签.
第9行:声明注释的内容.这将是我们工具提示的内容,并设置为#1
工具提示宏的参数.
第10-12行: 通常文本注释用图标标记,例如粘滞便笺,以突出显示它们在文本中的位置.如果我们允许它在我们的图形上溅出小粘滞便笺,这种行为将导致视觉混乱.这里我们使用一个外观数组(\AP << >>
)将"普通"注释图标(\N
)设置为我们之前创建的空白图标.我们存储的整数emptyicon
与0 R
使用空框形成对我们在第4行上创建的Form XObject的引用.
第14行:如果我们正在制作一个普通的超链接,这里是文本/图像/将用作链接主体的任何内容.相反,我们插入tooltiptarget
,我们看不见的幻像对象,它不会出现在屏幕上但会对鼠标悬停做出反应.
好吧,既然我们已经告诉LaTeX如何创建工具提示,现在是时候让它们可以从R中使用了.这包括编写一个函数,它将在我们的图上取坐标,例如(1,1),并将它们转换为画布或"设备"坐标.在tikzDevice的情况下,所需的测量值是绘图区域绝对左下角的"TeX点"(1/72.27英寸).幸运的是,对于基本图形,有一些方便的功能可以为我们计算.网格图形的工作方式不同,因此这里的示例中采用的方法对它们不起作用.
我们的R函数的最后一个任务是调用tikzAnnotate
将TikZ"节点"插入到我们计算的坐标处的输出流中.节点可以包含任意TeX命令,在这种情况下我们将调用tooltip
.
这是一个包含上述功能的R函数:
place_PDF_tooltip <- function(x, y, text){
# Calculate coordinates
tikzX <- round(grconvertX(x, to = "device"), 2)
tikzY <- round(grconvertY(y, to = "device"), 2)
# Insert node
tikzAnnotate(paste(
"\\node at (", tikzX, ",", tikzY, ") ",
"{\\tooltip{", text, "}};",
sep = ''
))
invisible()
}
Run Code Online (Sandbox Code Playgroud)
在情节上试一试:
# standAlone creates a complete LaTeX document. Default output makes
# stripped-down graphs ment for inclusion in other LaTeX documents
tikz('tooltips_ahoy.tex', standAlone = TRUE)
plot(1,1)
place_PDF_tooltip(1,1, 'Hello, World!')
dev.off()
require(tools)
texi2dvi('tooltips_ahoy.tex', pdf = TRUE)
Run Code Online (Sandbox Code Playgroud)
看看结果(下载pdf):
那么,既然我们已经完成了简单的工具提示,为什么不把它打到11?在前面的示例中,我们使用空hbox
来删除工具提示图标.但是如果我们在那个盒子里放了一些东西,比如文字或绘画呢?如果有一种方法可以使图标仅在鼠标悬停事件期间出现,该怎么办?
下面的TeX宏边缘有点粗糙,但它表明这是可能的:
\usetikzlibrary{shapes.callouts}
\tikzset{tooltip/.style = {
rectangle callout,
draw,
callout absolute pointer = {(-2em, 1em)}
}}
\def\tooltiptarget{\phantom{\rule{1mm}{1mm}}}
\newbox\tempboxa
\newcommand\tooltip[1]{%
\def\tooltipcallout{\tikz{\node[tooltip]{#1};}}%
\setbox\tempboxa=\hbox{\phantom{\tooltipcallout}}%
\immediate\pdfxform\tempboxa%
\edef\emptyicon{\the\pdflastxform}%
\setbox\tempboxa=\hbox{\tooltipcallout}%
\immediate\pdfxform\tempboxa%
\edef\tooltipicon{\the\pdflastxform}%
\pdfstartlink user{%
/Subtype /Text
/Contents (#1)
/AP <<
/N \emptyicon\space 0 R
/R \tooltipicon\space 0 R
>>
}%
\tooltiptarget%
\pdfendlink%
}
Run Code Online (Sandbox Code Playgroud)
与简单的标注相比,进行了以下修改.
该shapes.callouts
库加载包含模板TikZ绘制标注框时使用.
tooltip
定义了一个样式,其中包含一些TikZ图形样板.它指定一个可见的矩形标注框(draw
).这项callout absolute pointer
业务非常糟糕,因为我已经有太多的啤酒来弄清楚如何使用动态生成的PDF原语来放置注释图标.这依赖于左上角默认的图标锚定,因此将标注框的指针拉向该位置.结果是框总是出现在指针的右下方,如果标注文本足够长,它们将看起来不正确.
在宏内部,使用tikz
填充到tooltipcallout
宏中的一次性命令生成工具提示.表单XObject是从其生成tooltipcallout
并分配给的tooltipicon
.
emptyicon
也是通过评估tooltipcallout
内部动态生成的phantom
.这是必需的,因为默认图标的大小显然设置了可用于翻转图标的视口.
生成PDF命令时,会向/AP
数组添加一个新行,/R
用于翻转,使用引用的XObject tooltipicon
.
准备使用R版本是:
require(tikzDevice)
options(tikzLatexPackages = c(
getOption('tikzLatexPackages'),
"\\usetikzlibrary{shapes.callouts}",
"\\tikzset{tooltip/.style = {rectangle callout,draw,callout absolute pointer = {(-2em, 1em)}}}",
"\\def\\tooltiptarget{\\phantom{\\rule{1mm}{1mm}}}",
"\\newbox\\tempboxa",
"\\newcommand\\tooltip[1]{\\def\\tooltipcallout{\\tikz{\\node[tooltip]{#1};}}\\setbox\\tempboxa=\\hbox{\\phantom{\\tooltipcallout}}\\immediate\\pdfxform\\tempboxa\\edef\\emptyicon{\\the\\pdflastxform}\\setbox\\tempboxa=\\hbox{\\tooltipcallout}\\immediate\\pdfxform\\tempboxa\\edef\\tooltipicon{\\the\\pdflastxform}\\pdfstartlink user{/Subtype /Text/Contents (#1)/AP <</N \\emptyicon\\space 0 R/R \\tooltipicon\\space 0 R>>}\\tooltiptarget\\pdfendlink}"
))
Run Code Online (Sandbox Code Playgroud)
R级代码不变.
让我们尝试一个稍微复杂的图表:
tikz('tooltips_with_callouts.tex', standAlone = TRUE)
x <- 1:10
y <- runif(10, 0, 10)
plot(x,y)
place_PDF_tooltip(x,y,x)
dev.off()
require(tools)
texi2dvi('tooltips_with_callouts.tex', pdf = TRUE)
Run Code Online (Sandbox Code Playgroud)
结果(下载PDF):
如您所见,工具提示和标注都显示出来.设置为\Contents ()
使工具提示具有空字符串将无济于事.这可以通过使用不同的注释类型来解决,但我现在不打算花更多的时间在它上面.
很多TeX命令都包含反斜杠,在R代码中表达内容时需要加倍反斜杠.
某些字符是特殊到TeX的,如_
,^
,%
,完整列表在这里,您将需要确保这些都是使用tikzDevice时正确转义.
尽管PDF应该优于HTML,因为它具有跨平台的一致呈现,但您的里程将根据使用的查看器而有很大差异.屏幕截图是在OS X上的Acrobat 8中拍摄的,预览也执行了可通过的作业,但没有呈现翻转标注.在Linux上,xpdf没有呈现任何内容,而okular显示了工具提示,但没有抑制工具提示图标并显示一个在图中间看起来有点花哨的股票图标.
cooltooltips和fancytooltips是LaTeX包,提供可能从tikzDevice使用的工具提示功能.上述示例中使用的一些想法来自cooltooltips.
这值得吗?好吧,到那天结束时,我确实学到了两件事:
我不是PDF专家,我不得不搜索几个邮件列表并消化700多页规范的某些部分,甚至回答"这可能吗?"的问题.更别说拿出实施了.
我不是SVG专家,但我知道SVG中的工具提示不是"这可能吗?"的问题.而是" 你想要它看起来多么性感? "
鉴于这一观察结果,我说现在是时候让PDF转到日落,然后拿出它的700页规格.我们需要一种新的页面描述标记语言,我个人希望SVG Print能够完成这项工作.如果规范足够可访问,我甚至会接受Adobe Mars.只需给我们一个基于XML的格式,可以充分利用当今JavaScript库的全部功能,然后开始真正的创新.LuaTeX后端将是一个不错的奖励.
\pdfstartlink
\immediate
实际工作原理.