R Markdownとrevealjsとluaフィルタでチャンクやブロック要素をincrementalに表示する

カテゴリ: r lua

revealjs::revealjs_presentation関数では、incremental引数にTRUEを指定すると、箇条書きを一つずつ表示することができます。これと同じことを、チャンクとかその他のブロック要素(段落など)を一つずつ表示したいという需要があるようです。

上述のリンク先ではRで解決しようと、knitr::knit_hooksを駆使しています。しかし、コードが複雑にな上に、フック故の意図せぬ副作用の恐れがあります。また、htmlwidgetsクラスなど、独自にknitr::knit_print関数のメソッドを定義しているクラスへの対応が大変です。

そこでLuaフィルタを使ってみましょう。 R MarkdownはRmd -> md -> HTMLといった具合に段階的にファイルを変換しており、mdファイルから目的の形式に変換する際にはPandocというソフトウェアを使っています。このPandocが備えるLuaフィルタ機能を使うと、ブロック要素の段階的な表示がとても簡単にできます。

incremental表示の仕組み

これを実現するには、段階的に表示したいアイテムにfragmentクラスを与えます。例えば以下の通り。

<div class=fragment>
<p>foo</p>
</div>

fragmentクラスを持つ要素はデフォルトでフェードインしますが、オプションでハイライトを変更する、フェードアウトするなどと挙動を変えることも可能です(https://revealjs.com/fragments/)。

実装

後述のLuaフィルターさえ書いてしまえば、 Pandoc’s MarkdownのDivs記法を用いて、incremental-blocksクラスを作成し、その中にチャンクやブロック要素を記述するだけです。たとえば冒頭の動画GIFを作成するには以下のようなRmdファイルを作成します。

---
title: incremental blocks
output:
  revealjs::revealjs_presentation
    pandoc_args:
      - '--lua-filter'
      - 'incremental-blocks.lua'
---

# Iris

::: {.incremental-blocks}

```{r, fig.height=2}
names(iris)
ggplot2::qplot(iris$Sepal.Length, iris$Sepal.Width)
``

:::

luaフィルタ

先の例で指定したLuaフィルタはの内容は以下の通りです。 Divsincremental-blocksを持っている場合には、子要素をそれぞれfragmentクラスを持つDivsで囲っていきます。

-- 'incremental-blocks.lua'

function Div(div)
  -- Divがincremental-blocksを持っているか判定
  local incremental = false
  for _, class in ipairs(div.classes) do
    incremental = incremental or (class == 'incremental-blocks')
  end
  
  -- incrementalがtrueなら、Divのcontentをfragmentクラスを持つDivで囲う
  if incremental then
    for i,content in ipairs(div.content) do
      div.content[i] = pandoc.Div(content)
      div.content[i].classes = {'fragment'}
    end
  end
  
  -- 新しいdivを返す
  return(div)
end

結果

revealjsでチャンクをincrementalに表示した例

Enjoy!