3 Representing ggplot plots as Clojure data
This is part of the Scicloj Clojure Data Tutorials. |
ns representing
(:require [clojisr.v1.r :as r :refer [r r$ r->clj]]
(:as plotting]
[clojisr.v1.applications.plotting :as kind]
[scicloj.kindly.v4.kind walk :as walk]
[clojure.:as tc]
[tablecloth.api :as editscript]
[editscript.core :as pp]
[clojure.pprint :as str])) [clojure.string
"ggplot2") (r/library
1] "ggplot2" "Rserve" "stats" "graphics" "grDevices" "utils"
[7] "datasets" "methods" "base"
[
(r/require-r '[base])
nil
3.1 A representation function
A ggplot object is an R list of ggproto objects. We recursively unwrap this structure and convert it to Clojure.
defn ggplot->clj
(
([r-obj]
(ggplot->clj r-obj {} []))
([r-obj:as options
{:keys [avoid]
:or {avoid #{"plot_env"}}}
path]
#_(prn path)
let [relevant-names (some->> r-obj
(
r.base/names
r->cljfilter (complement avoid)))]
(cond
(;;
;; a named list or a ggproto object
seq relevant-names) (->> relevant-names
(map (fn [nam]
(keyword nam) (-> r-obj
[(
(r$ nam)
(ggplot->clj optionsconj path nam)))]))
(into {}))
(;;
;; a ggproto method
-> r-obj
(
r.base/class
r->cljfirst
= "ggproto_method"))
(:ggproto-method
;;
;; an unnamed list
-> r-obj
(
r.base/is-list
r->cljfirst)
-> r-obj
(
r.base/length
r->cljfirst
range
->> (mapv (fn [i]
(-> r-obj
(inc i))
(r/brabra (
(ggplot->clj optionsconj path [i])))))))
(;;
try (r->clj r-obj)
(r.base/is-atomic r-obj) (catch Exception e
(-> r-obj println with-out-str)))
(:else r-obj))))
In the conversion, we avoid some big parts of the structure, e.g., the "plot_env"
member. We also do no report the toplevel "data"
member, which is simply the dataset.
3.2 An example
For example:
delay
(-> "(ggplot(mpg, aes(cty, hwy))
( + geom_point())"
r
ggplot->cljdissoc :data))) (
:labels {:x ["cty"], :y ["hwy"]},
{:coordinates
:expand [true],
{:clip ["on"],
:limits {:x nil, :y nil},
:super :ggproto-method,
:default [true]},
:layout {:super :ggproto-method},
:mapping {:x [~ cty], :y [~ hwy]},
:facet {:shrink [true], :super :ggproto-method},
:scales {:scales [], :super :ggproto-method},
:theme [],
:guides {:guides nil, :super :ggproto-method},
:layers
:aes_params [],
[{:stat {:compute_layer :ggproto-method, :super :ggproto-method},
:show.legend [nil],
:mapping nil,
:super :ggproto-method,
:inherit.aes [true],
:geom_params {:na.rm [false]},
:geom
:non_missing_aes ["size" "shape" "colour"],
{:draw_key :ggproto-method,
:default_aes
:shape [19.0],
{:colour ["black"],
:size [1.5],
:fill [nil],
:alpha [nil],
:stroke [0.5]},
:super :ggproto-method,
:required_aes ["x" "y"],
:draw_panel :ggproto-method},
:stat_params {:na.rm [false]},
:constructor [geom_point],
:position {:compute_layer :ggproto-method, :super :ggproto-method},
:data []}]}