Analemma is a Clojure library for generating charts and Scalable Vector Graphics (SVG). The code is available on Github

* In astronomy, an analemma is the figure drawn in the sky by the different positions of the Sun recorded at the same time and location during the course of a year.

Architecture

* The above animated SVG image may not be visible in Firefox, and has not been tested in IE.

Analemma was originally developed as an extended example to use when teaching Clojure programming, and so it's been designed to be lightweight and have no dependencies other than Clojure.

Analemma is designed as a stack of three simple libraries, each building on the one below it. At the bottom is analemma.xml, which provides a lightweight DSL for generating XML from Clojure data structures; if you've used James Reeves' Hiccup library for generating HTML, then the syntax will look familiar.

The next level up is analemma.svg, which provides a set of functions for generating and transforming SVG images and animations. SVG supports an enormous number of tags, most of which are not represented in this library; when an unimplemented tag is required, just drop down into analemma.xml and create the necessary SVG XML with Clojure data structures.

The final layer is analemma.charts, which provides basic charting functionality with a syntax similar to Incanter, and a visual theme similar to Hadley Wickham's ggplot2 library for R. The development of this library has just begun, and it is missing a lot of functionality; when additional features are required, just drop down into analemma.svg or analemma.xml.

analemma.charts examples

Plotting Analemma Data

(spit "analemma.svg"
      (emit-svg
        (-> (xy-plot :xmin -30 :maxx 10,
                     :ymin -30 :maxy 30
                     :height 500 :width 500)
	     (add-points analemma-data))))

The full source code and data for this example can be found on github. The data was obtained from this site.

Plotting Sine and Cosine

(let [x (range -5 5 0.05)
      y1 (map #(Math/cos %) x)
      y2 (map #(Math/sin %) x)]
  (spit "sin-cos-small.svg"
    (emit-svg
      (-> (xy-plot :width 450 :height 200
                   :xmin -5 :xmax 5
                   :ymin -1.5 :ymax 1.5)
          (add-points [x y1]
                      :transpose-data? true
                      :size 1)
          (add-points [x y2]
                      :transpose-data? true
                      :size 1
                      :fill (rgb 255 0 0))))))

The full source code and data for this example can be found on github.

Labeling Data Points

(let [x (repeatedly 25 #(rand-int 100))
      y (repeatedly 25 #(rand-int 100))]
 (spit "rand-plot.svg"
       (emit-svg
          (-> (xy-plot :width 500 :height 500
                       :label-points? true)
              (add-points [x y] :transpose-data? true)))))

The full source code and data for this example can be found on github.

analemma.svg examples

Analemma Logo

The full source code and data for this example can be found on github.

(emit
  (svg
    (apply group
           (-> (text "Analemma")
               (add-attrs :x 120 :y 60)
               (style :fill #"000066"
                      :font-family "Garamond"
                      :font-size "75px"
                      :alignment-baseline :middle))
           (for [[x y] analemma-data]
             (circle (translate-value x -30 5 0 125)
                     (translate-value y -25 30 125 0)
                     2 :fill "#000066")))))

Animated Analemma Stack

* The above animated SVG image may not be visible in Firefox, and has not been tested in IE.

The full source code and data for this example can be found on github.

(defn txt-box [txt x y fill]
  (let [box-width 300
	box-height 50]
    (-> (svg
	  (group
	    (-> (rect 0 0 box-height box-width :rx 5 :ry 5)
	        (style :stroke fill :fill fill))
	    (-> (text txt)
                (add-attrs :x (/ box-width 2)
                           :y (/ box-height 2)})
	        (style :fill "#ffffff"
                       :font-size "25px"
                       :font-family "Verdana"
                       :alignment-baseline :middle
                       :text-anchor :middle))))
        (add-attrs :x x :y y))))

(defn analemma-stack [directory]
  (spit (str directory "analemma-stack.svg")
	(emit
	 (svg
	  (-> (group
	       (-> (txt-box "analemma.charts" 0 10 "#006600")
		   (add-attrs :visibility :hidden)
		   (animate :visibility :to :visible :begin 5)
		   (animate :y :begin 5 :dur 1 :from 0 :to 10))
	       (-> (txt-box "analemma.svg" 0 65 "#660000")
		   (add-attrs :visibility :hidden)
		   (animate :visibility :to :visible :begin 3)
		   (animate :y :begin 3 :dur 2 :from 0 :to 65))
	       (-> (txt-box "analemma.xml" 0 120 "#000066")
		   (add-attrs :visibility :hidden)
		   (animate :visibility :to :visible :begin 1)
		   (animate :y :begin 1 :dur 4 :from 0 :to 120)))
	      (translate 10 10))))))
  

analemma.xml examples

The following code uses analemma.xml to produce a snippet of SVG XML.
(emit
  [:svg
    [:g {:x 100, :y 100}
      [:rect {:x 0, :y 0, :height 50, :width 300
              :style "stroke: #660000; fill: #660000;"}]
      [:text {:style "fill: #660000; font-size: 25px; font-family: Verdana"
              :x 150 :y 25}]]])

XML tags are represented as Clojure vectors. The first value in the vector represents the tag's name. If the second value is a Clojure map, it is treated as the tag's attributes. Any remaining values will be vectors representing child elements.

© 2011 David Edgar Liebke