引き続きmetrics-graphics.jsをhtmlwidgetsでRに取り込もうとしているわけですが、さっそくドキュメントを見ると
List of Options · mozilla/metrics-graphics Wiki · GitHub
オプション多すぎやろ!
というわけで、これはぜんぶのパラメータを一つの関数に渡すよりは、いくつかの単位に区切るとかした方が使い勝手が良くなる感じがします。
Javascriptの可視化をRに持ち込もうと志同じくする(とか言うと畏れ多すぎますけど...)dygraphs
(http://rstudio.github.io/dygraphs/)がどうやってるのかチラ見してみると、設定する用の関数はいい感じに分けられています。で、この例でmagrittrの%>%
が使われているのを見て、
dygraph(lungDeaths) %>% dySeries("mdeaths", label = "Male") %>% dySeries("fdeaths", label = "Female") %>% dyOptions(stackedGraph = TRUE) %>% dyRangeSelector(height = 20)
あれ? でもこれ%>%
使わなくてもggplot2で+
ってやってるみたいにつなげられることない?と思ったのがきっかけでちょっとなんとなくコード追ってみました。
+
ggplot2はggクラス用の+
を定義しています。この辺の挙動については、?`+.gg`
と打つとヘルプが出ます。
(関数の後ろに.
でクラス名をつなげる、というのはS3クラス用のメソッドのつくりかたです。このへんは長くなるので説明は省きますが、HadleyさんのAdvanced Rとか、このブログの昔の記事とかが参考になるかもしれません)
"+.gg" <- function(e1, e2) { # Get the name of what was passed in as e2, and pass along so that it # can be displayed in error messages e2name <- deparse(substitute(e2)) if (is.theme(e1)) add_theme(e1, e2, e2name) else if (is.ggplot(e1)) add_ggplot(e1, e2, e2name) }
theme
の場合は置いておいて、これはつまりgg
クラスどうしを+
したときは、実際にはadd_ggplot()
が呼ばれているということです。
add_ggplot()
は同じソースファイルのちょっと下に書いてあります。
add_ggplot()
長いので途中まで。こんな感じです。
add_ggplot <- function(p, object, objectname) { if (is.null(object)) return(p) p <- plot_clone(p) if (is.data.frame(object)) { p$data <- object } else if (is.theme(object)) { p$theme <- update_theme(p$theme, object) } else if (inherits(object, "scale")) { p$scales$add(object) } else if(inherits(object, "labels")) { p <- update_labels(p, object) } else if(inherits(object, "guides")) { p <- update_guides(p, object) ...snip...
よく分かりませんが。とりあえず、やっていることは、
- 元の
gg
オブジェクト(p
)をクローンする - 足し合わされたのが
scale
かlabels
かとかで場合分けがあって、それぞれに応じて用意されているupdate_XXX()
みたいな関数が呼ばれる
みたいです。
試しにひとつみてみます。
update_labels()
update_labels <- function(p, labels) { p <- plot_clone(p) p$labels <- defaults(labels, p$labels) p }
(https://github.com/hadley/ggplot2/blob/4bb9270ef4d5d5062353438fd99d17b6f6de98a2/R/labels.r#L12-L16)
defaults()
というのは、plyr
の関数らしいです。(plyr/defaults.r at master · hadley/plyr · GitHub)1つ目の引数に値のリストを、2つ目の引数にデフォルト値のリストを取って、1つ目に含まれていない要素はデフォルト値で埋めて返してくれる便利関数みたいです。
結局やってることは
p
をクローン- 新しい
labels
を元のp$labels
とマージ - 新しい値をセットした
p
を返す
だけです。
ということで、+
でつないで色んな値を更新していく、ということは分かりました。でもこれだけではただのクラスです。
qplot(1:10, runif(10), geom="point")
とか打っただけでグラフが表示されるのはどういう仕組みかというと、ggplot
用のprint()
が用意されているからです。
print.ggplot()
print.ggplot <- function(x, newpage = is.null(vp), vp = NULL, ...) { set_last_plot(x) if (newpage) grid.newpage() data <- ggplot_build(x) gtable <- ggplot_gtable(data) if (is.null(vp)) { grid.draw(gtable) } else { if (is.character(vp)) seekViewport(vp) else pushViewport(vp) grid.draw(gtable) upViewport() } invisible(data) }
(https://github.com/hadley/ggplot2/blob/master/R/plot-render.r#L179-L195)
たとえばこれを上書きしてやると、グラフは表示されず、オブジェクトが持つ要素が味気なくずらっと表示されるだけです。
> print.ggplot <- print.default > qplot(1:10,runif(10), geom="point") $data data frame with 0 columns and 0 rows $layers $layers[[1]] geom_point: stat_identity: position_identity: (width = NULL, height = NULL) ...
まとめ
ということで、S3のクラスをつくって+
とprint
を上書きしてやればたぶんggplot2
っぽい挙動が真似できそうな気がしてきました。