Простой вопрос: есть ли способ построить график из R в файле PDF и включить всплывающие подсказки?
Всегда есть способ. Но дьявол кроется в деталях, поэтому реальный вопрос: готовы ли вы запачкать руки?
PDF поддерживает всплывающие подсказки для определенных типов объектов, таких как гиперссылки. Таким образом, если есть способ вставить необработанные операторы PDF, указывающие на то, что в некоторой позиции на вашем графике должен быть объект, похожий на гиперссылку, то есть способ вызвать всплывающую подсказку.
Теперь единственный известный мне способ генерации и компиляции необработанных операторов PDF - это создание документа с использованием TeX. Есть определенно другие способы сделать это, но это тот, с которым я знаком. В следующих примерах используется графическое устройство, которое мы с Кэмероном Бракеном написали tikzDevice , для рендеринга R-графики в код LaTeX. У tikzDevice есть предварительная поддержка для вставки произвольного кода LaTeX в поток вывода графики через функцию tikzAnnotate
- мы будем использовать это для удаления выносок PDF на график.
Шаги:
- Настройка макроса LaTeX для генерации команд PDF, необходимых для создания выноски.
- Настройка функции R, которая использует
tikzAnnotate
для вызова макроса LaTeX в определенных точках графика.
- ???
- Profit!
В следующих примерах к шагу 2 прикреплено одно важное предупреждение. Используемые вычисления координат будут работать только с базовой графикой, а не с сеточной графикой, такой как ggplot2 .
Простые подсказки
Шаг 1
tikzDevice позволяет вам создавать R графику, которая включает в себя выполнение произвольных команд LaTeX. Обычно это делается для вставки таких вещей, как $\alpha$
в заголовки сюжета, для создания греческих букв, α или сложных уравнений. Здесь мы собираемся использовать эту функцию для вызова некоторого необработанного PDF вуду.
Любые макросы LaTeX, которые вы хотите быть доступными во время генерации графики tikzDevice, должны быть определены заранее, установив опцию 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
, который невидим (фантом) и представляет собой прямоугольник 1 мм х 1 мм (правило). Это будет область экрана, которую мы будем использовать для обнаружения наведения мыши.
Строка 2: Создайте новый блок, который будет похож на «фрагмент страницы» из набора текста. Может содержать почти все, включая другие поля (вроде списка R). Назовите это tempboxa
.
Строка 3: Назначьте содержимое tempboxa
для пустого поля, в котором его содержимое размещено с использованием горизонтальной разметки (что неважно, возможно, использовался vbox или другой блок).
Строка 4: Создание PDF-формы XObject с использованием содержимого tempboxa
. Форма XObject может использоваться файлами PDF для хранения графики, например логотипов, которые можно использовать снова и снова. Здесь мы создаем «пустой значок», который мы можем использовать позже, чтобы сократить визуальный беспорядок. TeX откладывает операции вывода, такие как запись объектов в файл PDF, до тех пор, пока не будут выполнены определенные условия - например, заполнение страницы. Немедленно убедитесь, что эта операция не отложена.
Строка 5: Эта строка содержит целочисленное значение, которое служит ссылкой на только что созданный PDF-объект XObject, и присваивает ему имя emptyicon
.
Строка 6: Начинает определение нового макроса с именем tooltip, который принимает 1 аргумент, который в теле макроса упоминается как #1
. Каждая строка в макросе заканчивается символом комментария %
, чтобы TeX не замечал новые строки, добавленные для удобства чтения (новые строки могут иметь странные эффекты внутри макросов).
Строка 7: Выводить сырые команды PDF (pdfstartlink). Начинается создание нового объекта аннотации PDF (\Type \Annot
), в котором имеется около 20 различных подтипов - среди них гиперссылки. Каждая следующая за этим строка содержит необработанную разметку PDF с парой макросов TeX.
Строка 8: Объявите подтип аннотации, который мы будем использовать. Здесь я иду с простой текстовой аннотацией, такой как комментарий или заметка.
Строка 9: Объявите содержание аннотации. Это будет содержимое нашей всплывающей подсказки и будет установлено в #1
, аргумент макроса всплывающей подсказки.
Строки 10-12: Обычно текстовые аннотации помечаются значком, например, заметкой, чтобы выделить их местоположение в тексте. Такое поведение вызовет визуальный беспорядок, если мы позволим ему разбрызгивать маленькие заметки по всем нашим графикам. Здесь мы используем массив внешнего вида (\AP << >>
), чтобы установить «нормальный» значок аннотации (\N
) в качестве пустого значка, который мы создали ранее. Целое число, которое мы сохранили в emptyicon
вместе с 0 R
, образует ссылку на объект XObject, который мы создали в строке 4, используя пустое поле.
Строка 14: Если бы мы делали нормальную гиперссылку, то вот куда должен идти текст / изображение / все, что будет служить телом ссылки. Вместо этого мы вставляем tooltiptarget
, наш невидимый фантомный объект, который не отображается на экране, но реагирует на наведение мыши.
Шаг 2
Хорошо, теперь, когда мы рассказали 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()
}
Шаг 3
Попробуйте на сюжете:
# 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)
Шаг 4
Вот результат ( скачать 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
- это хак, потому что к этому моменту у меня было слишком много пива, чтобы понять, как размещать значки аннотаций с использованием динамически генерируемых примитивов 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}"
))
Шаг 2
Код уровня R. без изменений.
Шаг 3
Давайте попробуем немного более сложный график:
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)
Шаг 4
результат ( скачать PDF ):
Как видите, существует проблема с отображаемой подсказкой и выноской.Установка \Contents ()
так, чтобы во всплывающей подсказке была пустая строка, ничего не поможет.Вероятно, это можно решить, используя другой тип аннотации, но я не собираюсь сейчас уделять ему больше времени.
Предостережения
Многие команды TeX содержат обратную косую черту, вам потребуется удвоить обратную косую черту при выражении вещей в коде R.
Некоторые символы являются специальными для TeX, например _
, ^
, %
, полный список здесь , вам необходимо убедиться, что они правильно экранированы при использовании tikzDevice.
Даже если PDF-файл превосходитHTML в том смысле, что он имеет постоянный рендеринг на разных платформах, ваш пробег будет значительно различаться в зависимости от того, какой просмотрщик используется.Снимки экрана были сделаны в Acrobat 8 на OS X. Предварительный просмотр также выполнил сносную работу, но не отрисовывал ролловер.В Linux xpdf ничего не рендерил, и okular показывал всплывающую подсказку, но не подавлял значок всплывающей подсказки и отображал иконку со склада, которая выглядела немного кричащей в середине графика.
Альтернативные реализации
cooltooltips и fancytooltips - это пакеты LaTeX, которые предоставляют функциональность всплывающей подсказки, которую, вероятно, можно использовать из tikzDevice.Некоторые идеи, использованные в приведенных выше примерах, были взяты из cooltooltips.
Заключительные мысли
Стоило ли это того?Ну, к концу дня я узнал две вещи:
Я не эксперт по PDF , и мне пришлось поискать пару списков рассылки и переварить некоторыечасти спецификации 700+ страниц, чтобы даже ответить на вопрос "это возможно?"не говоря уже о том, чтобы придумать реализацию.
Я не эксперт по SVG и все же я знаю, что всплывающие подсказки в SVG - это не вопрос "возможно ли это?"а скорее " как сексуально ты хочешь, чтобы это выглядело? ".
Учитывая это наблюдение, я говорю, что для PDF настало время уйти в закат и взять с собой спецификации на 700 страниц.Нам нужен новый язык разметки описания страниц, и лично я надеюсь, что SVG Print будет достаточно для выполнения этой работы.Я бы даже принял Adobe Mars , если спецификация достаточно доступна.Просто предоставьте нам формат на основе XML, который может использовать всю мощь современных библиотек JavaScript, и тогда могут начаться некоторые реальные инновации.Бэкэнд LuaTeX был бы хорошим бонусом.
Ссылки
- tikzDevice : Устройство для вывода графики R в виде LaTeXкод.
- comp.text.tex : Источник большого понимания эзотерических деталей TeX.Сообщения от Герберта Восса и Майкрофта предоставили идеи реализации.
- Руководство по pdfTeX : Источник информации о командах TeXкоторый может генерировать необработанный код PDF, такой как
\pdfstartlink
- TeX по теме : Руководство по низкоуровневым деталям TeX, например, как
\immediate
на самом деле работает. - Спецификация Adobe PDF : Логово подробностей, касающихся примитивов PDF.
- Руководство TikZ : Возможно, самый лучший пример документации с открытым исходным кодом, когда-либо написанной.