Slider Animations with Clojure

This is an implementation of the Gapminder animation using Clojure and the Clojure viewer API in Nextjournal that allows us to use Plotly Javascript graphing library.

To implement this, we followed the example of the Plotly documentation on animating sliders.

{:deps {org.clojure/clojure {:mvn/version "1.10.1"}
        org.clojure/data.csv {:mvn/version "1.0.0"}
        cheshire/cheshire {:mvn/version "5.10.0"}
        ;; complient is used for autocompletion
        ;; add your libs here (and restart the runtime to pick up changes)
        compliment/compliment {:mvn/version "0.3.9"}}}
deps.edn
Extensible Data Notation

Data

curl https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv > data.csv
2.4s
data downloadingBash in Clojure

Parsing

(require '[clojure.data.csv :as csv])
(def data (csv/read-csv (slurp "/data.csv")))
;; Courtesy of the data.csv authors (https://github.com/clojure/data.csv)
(defn csv-data->maps [csv-data]
  (map zipmap
       (->> (first csv-data) ;; First row is the header
            (map keyword) ;; Drop if you want string keys instead
            repeat)
	   (rest csv-data)))
(def parsed-data (csv-data->maps data))
1.5s
data parsingClojure

Get data by year and continent

(def years (sort (set (map :year parsed-data))))
(def continents (set (map :continent parsed-data)))
(def grouped-by-year (group-by :year parsed-data))
(def grouped-by-year-and-continent
  (into {}
    (map (fn [year]
           {year (group-by :continent (get grouped-by-year year))}) years)))
(defn get-data [year continent]
  (get-in grouped-by-year-and-continent [year continent]))
0.2s
utilitiesClojure
(defn trace [year continent]
  (let [continent-year-data (get-data year continent)]
    {:name continent
     :x (map :lifeExp continent-year-data)
     :y (map :gdpPercap continent-year-data)
     :text (map :country continent-year-data)
     :id (map :country continent-year-data)
     :mode "markers"
     :marker {:size (map :pop continent-year-data)
              :sizemode "area"
              :sizeref 200000}}))
(defn frame [year]
  {:name year
   :data (map #(trace year %) continents)})
(def frames (map frame years))
(def first-trace (map #(trace (first years) %) continents))
0.2s
frames & tracesClojure

Slider steps definition

(defn sliderstep [year]
  {:method "animate"
   :label year
   :args [[year] {:mode "immediate"
                  :transition {:duration 200}
                  :frame {:duration 300 :redraw false}}]})
(def slidersteps (map sliderstep years))
0.1s
slider stepsClojure

Layout definition

(def layout {:xaxis {:title "Life exp"
                     :range [30,85]
                     :showgrid false}
             :yaxis {:title "GDP"
                     :type "log"
                     :showgrid false}
             :hovermode "closest"
             :updatemenus [{:x 0
                            :y 0
                            :yanchor "top"
                            :xanchor "left"
                            :showactive false
                            :direction "left"
                            :type "buttons"
                            :pad {:t 87 :r 10}
                            :buttons [{:method "animate"
                                       :args [nil,{:mode "immediate"
                                                    :fromcurrent true
                                                    :transition {:duration 300}
                                                    :frame {:duration 500
                                                            :redraw false}}]
                                       :label "Play"}
                                      {:method "animate"
                                       :args [[nil] {:mode "immediate"
                                                      :transition {:duration 0
                                                                   :redraw false}}]
                                       :label "Pause"}]}]
             :sliders [{:pad {:l 130 :t 55}
                        :currentvalue {:visible true
                                       :prefix "Year "
                                       :xanchor "right"
                                       :font {:size 20 :color "#666"}}
                        :steps slidersteps}]})
0.1s
layoutClojure

Plot

^{:nextjournal/viewer :plotly} 
{:data first-trace
 :layout layout
 :frames frames}
0.7s
plotClojure
Runtimes (1)