captioner を魔改造してみた

by
カテゴリ:
タグ:

はじめに

captioner を使うと相互参照に未対応な Rmd フォーマットも相互参照できるようになる (rmarkdown::html_document とか pkgdown とか……). 詳しくはテラモナギさんの記事を参照 (captionerパッケージで図・表に対する参照(レファレンス)を取得する).

ただ,リンクをつけようとすると結構面倒. あと,キャプション付きで出すときは fig_cap("figure-name", "hogehoge") で「図1」みたいに参照番号だけ出す時は fig_cap("figure-name", display = "cite") といった具合な仕様で,繰り返し同じ図を参照する時は毎度 display 引数を指定しなければならないのが不便だ. また,相互参照を実現するための関数を図,表,式など,それぞれについて作成しないといけない. このあたりの改善を試みる.

ついでに, gt::gt のような bookdown パッケージなどが相互参照に対応できていない関数も相互参照させられるようになる.

あとは任意の Rmd で内蔵の相互参照を無効化する機能さえあれば……!!

実装

captioner を初期化

fig_cap <- captioner::captioner("図") とすることで図を参照するための関数を作ることができる. 他に "表" とか "式" とか入れて fig_tbl とか fig_eq とか作ればいいわけだけれど,それは面倒なので, lapply を使おう.

captioners <- lapply(c(fig = "図", tbl = "表"), captioner)

これで一度に初期化できた. captioners では名前が長いが, cap などとしておけば cap$fig()cap$tbl() で相互参照できるので便利だ.

キャプションを振る時は相互参照に利用する名前を id に流用

相互参照に利用する名前をそのまま図表の id として利用できるように, captioners の引数を弄るかラッパー関数を作ろう.

Pandoc’s markdown では [hoge]{#fuga} とすることで 文字列 hoge に id fuga を与えられる. html に出力した時は <span id = "hoge">hoge</span> になる.

図表の name 引数に与えた文字列を利用して []{#name} という文字列を作り, caption 冒頭にでも入れてやればいい.

cap <- lapply(
    captioners,
    function(f) {
      formals(f) <- append(formals(f), alist(.caption = ), 1)
      formals(f)$caption <- quote(paste0("[]{#", name, "}", .caption))
      f
      # 以下のようにラッパー関数にしてもOK
      # function(name, caption = "", ...) {
      #   f(name, paste0("[]{#", name, "}", caption), ...)
      # }
    }
  )

参照する時は相互参照に利用する名前をリンクにする

先の cap の実装で,キャプションには id が振られている. この id にリンクされるよう,通常の captioner なら 図1 としか出力しないところを [図1]{#fuga} といった具合に出力されるよう,captioners をラッパーしよう.

cite <- lapply(
    captioners,
    function(f) {
      function(name, display = "cite", ...) {
        paste0("[", f(name, display = display, ...), "](#", name, ")")
      }
    }
  )

これで準備は完了だ.

図をテスト

plot()

`r cite$fig("plot-iris")`図 1 になる. ちゃんとリンクも貼れてるね. blogdown が図表の番号付けに対応しているせいで, Figure 1: 図 1: となってしまうのはご愛嬌.

```{r plot-iris, fig.cap = .cap}  
.cap <- cap$fig("plot-iris", "plot(iris) に図表番号とキャプション,idをつけた")  
plot(iris)
```
図  1: []{#plot-iris}plot(iris) に図表番号とキャプション,idをつけた

Figure 1: 図 1: plot(iris) に図表番号とキャプション,idをつけた

表をテスト

knitr::kable()

`r cite$tbl("kable-iris")`表 1 になる. ちゃんとリンクも貼れてるね.

library(knitr)
.cap <- cap$tbl("kable-iris", "kable(iris) に図表番号とキャプションとidをつけた.")
kable(iris[1:2, ], caption = .cap)
Table 1: 表 1: kable(iris) に図表番号とキャプションとidをつけた.
Sepal.LengthSepal.WidthPetal.LengthPetal.WidthSpecies
5.13.51.40.2setosa
4.93.01.40.2setosa

gt::gt()

gt は今のところ相互参照に対応していない (後述). 従って captioner を使う他ない.

gtが受け取る文字列を md や html として解釈させるには md()html() といった関数を用いる. しかし,この md() は Pandoc’s markdown を解釈してくれないようで,以下のように id 付けに失敗する.

id付け失敗

library(gt)
gt(iris[1, ]) %>%
  tab_header(
    title = md(cap$tbl(
        "gt-iris", 
        "gt(iris) に図表番号とキャプションをつけたが,idはうまくついていない."
      ))
    ) 
表 2: []{#gt-iris}gt(iris) に図表番号とキャプションをつけたが,idはうまくついていない.
Sepal.LengthSepal.WidthPetal.LengthPetal.WidthSpecies
5.13.51.40.2setosa

id付け成功例1

表 3 のように, gt の外でキャプションをつける. やや不恰好だが,とりあえず動く.

cap$tbl(
  "gt-iris2", 
  "gt(iris) に図表番号とキャプションをつけたが,idはうまくついていない."
)
gt(iris[1, ])

表 3: gt(iris) との一体感はなくなるが,idもつく

Sepal.LengthSepal.WidthPetal.LengthPetal.WidthSpecies
5.13.51.40.2setosa

id付け成功例2

Ppandoc’s markdown が通らないだけで html は通るので, cap を以下のように書き換える. すると 表 4 のように, 見栄えを gt で完結できる.

cap <- lapply(
    captioners,
    function(f) {
      function(name, caption = "", ...) {
        f(name, paste0("<span id = '", name, "'></span>", caption), ...)
      }
    }
  )

gt(iris[1, ]) %>%
  tab_header(
    title = html(cap$tbl(
        "gt-iris3", 
        "gt(iris) に図表番号とキャプションをつけたが,idはうまくついていない."
      ))
    ) 
表 4: gt(iris) に図表番号とキャプションをつけたが,idはうまくついていない.
Sepal.LengthSepal.WidthPetal.LengthPetal.WidthSpecies
5.13.51.40.2setosa

gt は相互参照未対応であることを確認

fig.cap を指定してもタイトルが表示されない.

```{r gt-iris, fig.cap = "gt with fig.cap", echo = FALSE}  
gt(iris[1, ])  
```
Sepal.LengthSepal.WidthPetal.LengthPetal.WidthSpecies
5.13.51.40.2setosa

tab_header(title = ) を指定するとタイトルは表示されるが,図表番号は表示されない.

```{r gt-iris-titled, echo = FALSE}
gt(iris[1, ]) %>%
  tab_header(title = "gt with title")
```
gt with title
Sepal.LengthSepal.WidthPetal.LengthPetal.WidthSpecies
5.13.51.40.2setosa