简单问题:有没有办法在 pdf 文件中从 R 绘制图形并包含工具提示?
总有办法。但魔鬼在细节中,所以真正的问题是:你愿意动手吗?
PDF 确实支持某些类型的对象(例如超链接)的工具提示。因此,如果有一种方法可以插入原始 PDF 语句,指示在绘图中的某个位置应该有一个类似超链接的对象,那么就有一种方法可以弹出工具提示。
现在,我知道生成和编译原始 PDF 语句的唯一方法是使用 TeX 创建文档。肯定有其他方法可以做到这一点,但这是我熟悉的一种。以下示例使用我和 Cameron Bracken 编写的图形设备 tikzDevice,将 R 图形渲染为 LaTeX 代码。 tikzDevice 初步支持通过tikzAnnotate 函数将任意 LaTeX 代码注入图形输出流——我们将使用它来将 PDF 标注放入绘图中。
涉及的步骤是:
- 设置 LaTeX 宏以生成生成标注所需的 PDF 命令。
- 设置一个 R 函数,该函数使用
tikzAnnotate 在绘图中的特定点调用 LaTeX 宏。
- ???
- 利润!
在下面的示例中,第 2 步附有一个主要警告。所使用的坐标计算仅适用于基本图形,不适用于 ggplot2 等网格图形。
简单的工具提示
步骤 1
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}"
))
如果所有引用的废话都由关心可读性的人写成 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%
}
对于那些从未涉足过并在 TeX 中进行过一些“编程”的程序员,这里是对上述代码的逐个介绍(无论如何,据我所知,TeX 会变得非常奇怪—— - 尤其是当你带着它进入战壕时):
第 1 行: 定义一个不可见的对象 tooltiptarget,它是一个 1mm x 1mm 的矩形(规则)。这将是我们将用来检测鼠标悬停的屏幕区域。
第 2 行: 创建一个新框,类似于排版材料的“页面片段”。可以包含几乎任何东西,包括其他框(有点像 R 列表)。叫它tempboxa。
第 3 行: 分配 tempboxa 的内容以包含一个使用水平布局排列其内容的空框(这并不重要,可以使用 vbox 或其他框) .
第 4 行: 使用 tempboxa 的内容创建 PDF 表单 XObject。 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()
}
第三步
在情节上尝试一下:
# 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)
第四步
看结果(download a pdf):
高级工具提示
步骤 1
既然我们已经有了简单的工具提示,为什么不把它调到 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%
}
与简单标注相比,做了以下修改。
shapes.callouts 库已加载,其中包含 TikZ 在绘制标注框时使用的模板。
定义了一个 tooltip 样式,其中包含一些 TikZ 图形样板。它指定一个可见的矩形标注框 (draw)。 callout absolute pointer 业务是一个 hack,因为此时我已经喝了太多啤酒,无法弄清楚如何使用动态生成的 PDF 原语放置注释图标。这依赖于图标在其左上角的默认锚定,因此将标注框的指针拉向该位置。结果是这些框将始终显示在指针的右下方,如果标注文本足够长,它们将看起来不正确。
在宏内部,工具提示是使用填充到tooltipcallout 宏中的一次性tikz 命令生成的。从tooltipcallout 生成一个表单XObject 并分配给tooltipicon。
emptyicon 也是通过评估 phantom 内部的 tooltipcallout 动态生成的。这是必需的,因为默认图标的大小显然设置了可用于翻转图标的视口。
生成 PDF 命令时,会在 /AP 数组中添加一个新行,/R 用于翻转,它使用 tooltipicon 引用的 XObject。
准备消费的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}"
))
第二步
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)
第四步
结果(download a PDF):
如您所见,显示的工具提示和标注都存在问题。设置\Contents () 使工具提示有一个空字符串不会有任何帮助。这可能可以通过使用不同的注释类型来解决,但我现在不会花更多的时间。
注意事项
很多 TeX 命令都包含反斜杠,在 R 代码中表达内容时需要将反斜杠加倍。
-
某些字符是 TeX 的特殊字符,例如_、^、%、complete list here,您需要确保在使用 tikzDevice 时正确转义这些字符。
尽管 PDF 应该优于 HTML,因为它具有跨平台的一致呈现,但根据使用的查看器,您的工作量会有很大差异。屏幕截图是在 OS X 上的 Acrobat 8 中拍摄的,Preview 也做得还不错,但没有呈现翻转标注。在 Linux 上,xpdf 不渲染任何内容,并且 okular 显示了一个工具提示,但没有抑制工具提示图标,而是显示了一个在绘图中间看起来有点花哨的股票图标。
替代实现
cooltooltips 和 fancytooltips 是提供工具提示功能的 LaTeX 包,可能可以从 tikzDevice 使用。上述示例中使用的一些想法来自cooltooltips。
结论性想法
这值得吗?好吧,到最后我确实学到了两件事:
鉴于这一观察,我说现在是时候让 PDF 进入日落并采用 700 页的规范了。我们需要一种新的页面描述标记语言,我个人希望SVG Print 能够完成这项工作。如果规范足够好,我什至会接受Adobe Mars。只要给我们一个基于 XML 的格式,它可以利用当今 JavaScript 库的全部功能,然后就可以开始一些真正的创新。 LuaTeX 后端将是一个不错的奖励。
参考文献