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]]
[clojisr.v1.applications.plotting :as plotting]
[scicloj.kindly.v4.kind :as kind]
[clojure.walk :as walk]
[tablecloth.api :as tc]
[editscript.core :as editscript]
[clojure.pprint :as pp]
[clojure.string :as str]))(r/library "ggplot2")[1] "ggplot2" "Rserve" "stats" "graphics" "grDevices" "utils"
[7] "datasets" "methods" "base"
(r/require-r '[base])nil3.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->clj
(filter (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 options
(conj path nam)))]))
(into {}))
;;
;; a ggproto method
(-> r-obj
r.base/class
r->clj
first
(= "ggproto_method"))
:ggproto-method
;;
;; an unnamed list
(-> r-obj
r.base/is-list
r->clj
first)
(-> r-obj
r.base/length
r->clj
first
range
(->> (mapv (fn [i]
(-> r-obj
(r/brabra (inc i))
(ggplot->clj options
(conj path [i])))))))
;;
(r.base/is-atomic r-obj) (try (r->clj 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->clj
(dissoc :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 []}]}