├── .gitignore ├── README.textile ├── images ├── analemma-logo.png ├── analemma-logo.svg ├── analemma-stack.svg ├── analemma.png ├── analemma.svg ├── rand-plot.svg ├── sin-cos-small.svg ├── sin-cos.png └── sin-cos.svg ├── index.html ├── project.clj └── src ├── analemma ├── charts.cljc ├── svg.cljc └── xml.cljc └── examples ├── analemma.clj ├── charts.clj └── svg.clj /.gitignore: -------------------------------------------------------------------------------- 1 | pom.xml 2 | *jar 3 | lib 4 | classes 5 | .#* 6 | target 7 | -------------------------------------------------------------------------------- /README.textile: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Analemma is a Clojure-based SVG DSL and charting library. 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.

7 | 8 | 9 | 10 |

Example Charts

11 | 12 |

Plotting Analemma Data

13 | 14 |

15 | (spit "analemma.svg"
16 |       (emit-svg
17 |         (-> (xy-plot :xmin -30 :maxx 10,
18 |                      :ymin -30 :maxy 30
19 |                      :height 500 :width 500)
20 | 	     (add-points analemma-data))))
21 | 
22 | 23 | 24 | 25 | 26 |

Plotting Sine and Cosine

27 | 28 |

29 | (spit "sin-cos.svg"
30 |       (emit-svg
31 |         (let [x (range -5 5 0.05)
32 |               y1 (map #(Math/cos %) x)
33 |               y2 (map #(Math/sin %) x)]
34 |           (-> (xy-plot :minx -5 :xmax 5 :ymin -1.5 :maxy 1.5)
35 |               (add-points [x y1] :transpose-data?? true)
36 |               (add-points [x y2] :transpose-data?? true
37 |                           :color (rgb 255 0 0))))))
38 | 
39 | 40 | 41 | 42 | 43 |

Creating the Analemma logo

44 | 45 |
(emit
46 |   (svg
47 |     (apply group
48 |            (rect 0 0 160 500 :fill "#ffffff")
49 |            (-> (text {:x 120 :y 60} "Analemma")
50 |                (style :fill #"000066"
51 |                       :font-family "Garamond"
52 |                       :font-size "75px"
53 |                       :alignment-baseline :middle))
54 |            (for [[x y] analemma-data]
55 |              (circle (translate-value x -30 5 0 125)
56 |                      (translate-value y -25 30 125 0)
57 |                      2 :fill "#000066")))))
58 | 
59 | 60 | 61 | 62 | 63 | 64 |

See the Analemma website for more examples.

65 | 66 | 67 | Copyright (C) 2011 David Edgar Liebke 68 | 69 | Distributed under the Eclipse Public License, the same as Clojure. 70 | -------------------------------------------------------------------------------- /images/analemma-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liebke/analemma/d746c9d042dd9640c852b9e12c5dbfa01d606e27/images/analemma-logo.png -------------------------------------------------------------------------------- /images/analemma-logo.svg: -------------------------------------------------------------------------------- 1 | Analemma -------------------------------------------------------------------------------- /images/analemma-stack.svg: -------------------------------------------------------------------------------- 1 | analemma.chartsanalemma.svganalemma.xml -------------------------------------------------------------------------------- /images/analemma.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liebke/analemma/d746c9d042dd9640c852b9e12c5dbfa01d606e27/images/analemma.png -------------------------------------------------------------------------------- /images/analemma.svg: -------------------------------------------------------------------------------- 1 | -30-22-14-6210-18-661830 -------------------------------------------------------------------------------- /images/rand-plot.svg: -------------------------------------------------------------------------------- 1 | 0.020.040.060.080.0100.020.040.060.080.0100.068.0,8.026.0,57.017.0,23.059.0,84.028.0,54.064.0,79.063.0,63.073.0,89.069.0,14.073.0,47.079.0,81.091.0,0.098.0,23.011.0,2.032.0,84.056.0,37.018.0,33.034.0,21.039.0,64.083.0,2.056.0,12.087.0,89.093.0,62.034.0,44.085.0,99.0 -------------------------------------------------------------------------------- /images/sin-cos-small.svg: -------------------------------------------------------------------------------- 1 | -5.0-3.0-1.01.03.05.0-0.9-0.30.30.91.5 -------------------------------------------------------------------------------- /images/sin-cos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liebke/analemma/d746c9d042dd9640c852b9e12c5dbfa01d606e27/images/sin-cos.png -------------------------------------------------------------------------------- /images/sin-cos.svg: -------------------------------------------------------------------------------- 1 | -5.0-3.0-1.01.03.05.0-0.9-0.30.30.91.5 -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Analemma: Clojure Charts and SVG Library 5 | 31 | 32 | 45 | 46 | 47 | 48 |
49 | 50 | 51 | 54 | 55 |

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

60 | 61 |
* 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.
62 | 63 | 66 | 67 |

Architecture

68 | 69 | 72 | 73 |
* The above animated SVG image may not be visible in 74 | Firefox, and has not been tested in IE.
75 | 76 |

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

80 | 81 |

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

86 | 87 |

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

92 | 93 |

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

101 | 102 |

analemma.charts examples

103 | 104 |

Plotting Analemma Data

105 | 106 |
107 |
(spit "analemma.svg"
108 |       (emit-svg
109 |         (-> (xy-plot :xmin -30 :maxx 10,
110 |                      :ymin -30 :maxy 30
111 |                      :height 500 :width 500)
112 | 	     (add-points analemma-data))))
113 | 
114 |
115 | 116 |

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

122 | 123 | 126 | 127 | 128 |

Plotting Sine and Cosine

129 | 130 |
131 |
(let [x (range -5 5 0.05)
132 |       y1 (map #(Math/cos %) x)
133 |       y2 (map #(Math/sin %) x)]
134 |   (spit "sin-cos-small.svg"
135 |     (emit-svg
136 |       (-> (xy-plot :width 450 :height 200
137 |                    :xmin -5 :xmax 5
138 |                    :ymin -1.5 :ymax 1.5)
139 |           (add-points [x y1]
140 |                       :transpose-data?? true
141 |                       :size 1)
142 |           (add-points [x y2]
143 |                       :transpose-data?? true
144 |                       :size 1
145 |                       :fill (rgb 255 0 0))))))
146 | 
147 |
148 | 149 |

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

153 | 154 | 157 | 158 | 159 |

Labeling Data Points

160 | 161 |
162 |
(let [x (repeatedly 25 #(rand-int 100))
163 |       y (repeatedly 25 #(rand-int 100))]
164 |  (spit "rand-plot.svg"
165 |        (emit-svg
166 |           (-> (xy-plot :width 500 :height 500
167 |                        :label-points? true)
168 |               (add-points [x y] :transpose-data?? true)))))
169 | 
170 |
171 | 172 |

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

176 | 177 | 180 | 181 | 182 |

analemma.svg examples

183 | 184 |

Analemma Logo

185 | 186 | 189 | 190 |

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

194 | 195 |
196 |
(emit
197 |   (svg
198 |     (apply group
199 |            (-> (text "Analemma")
200 |                (add-attrs :x 120 :y 60)
201 |                (style :fill #"000066"
202 |                       :font-family "Garamond"
203 |                       :font-size "75px"
204 |                       :alignment-baseline :middle))
205 |            (for [[x y] analemma-data]
206 |              (circle (translate-value x -30 5 0 125)
207 |                      (translate-value y -25 30 125 0)
208 |                      2 :fill "#000066")))))
209 | 
210 | 211 |

Animated Analemma Stack

212 | 213 | 216 | 217 |
* The above animated SVG image may not be visible in 218 | Firefox, and has not been tested in IE.
219 | 220 |

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

224 | 225 |
226 |
(defn txt-box [txt x y fill]
227 |   (let [box-width 300
228 | 	box-height 50]
229 |     (-> (svg 
230 | 	  (group 
231 | 	    (-> (rect 0 0 box-height box-width :rx 5 :ry 5)
232 | 	        (style :stroke fill :fill fill))
233 | 	    (-> (text txt)
234 |                 (add-attrs :x (/ box-width 2)
235 |                            :y (/ box-height 2)})
236 | 	        (style :fill "#ffffff"
237 |                        :font-size "25px"
238 |                        :font-family "Verdana"
239 |                        :alignment-baseline :middle
240 |                        :text-anchor :middle))))
241 |         (add-attrs :x x :y y))))
242 | 
243 | (defn analemma-stack [directory]
244 |   (spit (str directory "analemma-stack.svg")
245 | 	(emit
246 | 	 (svg
247 | 	  (-> (group
248 | 	       (-> (txt-box "analemma.charts" 0 10 "#006600")
249 | 		   (add-attrs :visibility :hidden)
250 | 		   (animate :visibility :to :visible :begin 5)
251 | 		   (animate :y :begin 5 :dur 1 :from 0 :to 10))
252 | 	       (-> (txt-box "analemma.svg" 0 65 "#660000")
253 | 		   (add-attrs :visibility :hidden)
254 | 		   (animate :visibility :to :visible :begin 3)
255 | 		   (animate :y :begin 3 :dur 2 :from 0 :to 65))
256 | 	       (-> (txt-box "analemma.xml" 0 120 "#000066")
257 | 		   (add-attrs :visibility :hidden)
258 | 		   (animate :visibility :to :visible :begin 1)
259 | 		   (animate :y :begin 1 :dur 4 :from 0 :to 120)))
260 | 	      (translate 10 10))))))
261 |   
262 |
263 | 264 |

analemma.xml examples

265 | 266 | The following code uses analemma.xml to produce a snippet of SVG XML. 267 |
268 |
(emit
269 |   [:svg
270 |     [:g {:x 100, :y 100}
271 |       [:rect {:x 0, :y 0, :height 50, :width 300
272 |               :style "stroke: #660000; fill: #660000;"}]
273 |       [:text {:style "fill: #660000; font-size: 25px; font-family: Verdana"
274 |               :x 150 :y 25}]]])
275 | 
276 |
277 | 278 |

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

281 | 282 |
© 2011 David Edgar Liebke
283 | 284 |
285 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject analemma "1.1.0" 2 | :description "An SVG DSL for Clojure and ClojureScript" 3 | :url "https://github.com/liebke/analemma" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.10.1"]]) 7 | -------------------------------------------------------------------------------- /src/analemma/charts.cljc: -------------------------------------------------------------------------------- 1 | (ns analemma.charts 2 | (:require [analemma.svg :refer [rect style line text group 3 | translate circle rgb svg 4 | translate-value]] 5 | [analemma.xml :refer [emit]] 6 | #?@(:cljs [[goog.string :as gstring] 7 | [goog.string.format]]))) 8 | 9 | (def default-chart-props {:x 50, :y 50, 10 | :height 500, :width 750, 11 | :xmin 0, :xmax 100, 12 | :ymin 0, :ymax 100, 13 | :grid-lines 10, 14 | :label-points?? false, 15 | :points [] 16 | :major-grid-color (rgb 255 255 255) 17 | :minor-grid-color (rgb 245 245 245) 18 | :background-color (rgb 225 225 225) 19 | :major-grid-width 2 20 | :minor-grid-width 1 21 | :axis-font-family "Verdana" 22 | :axis-font-size "12px" 23 | :axis-number-format "%.1f" 24 | :label-font-family "Verdana" 25 | :label-font-size "10px" 26 | :label-number-format "%.1f"}) 27 | 28 | (defn chart-background [{:keys [height width background-color 29 | major-grid-color major-grid-width]}] 30 | (-> (rect 0 0 height width) 31 | (style :fill background-color 32 | :stroke major-grid-color 33 | :stroke-width major-grid-width))) 34 | 35 | (defn x-grid [{:keys [height width 36 | grid-lines 37 | major-grid-color minor-grid-color 38 | major-grid-width minor-grid-width]}] 39 | (let [grid-x-space (/ width grid-lines)] 40 | (for [i (range 1 grid-lines)] 41 | (-> (line (* i grid-x-space) 0 (* i grid-x-space) height) 42 | (style :stroke (if (even? i) major-grid-color minor-grid-color) 43 | :stroke-width (if (even? i) major-grid-width minor-grid-width)))))) 44 | 45 | (defn y-grid [{:keys [height width 46 | grid-lines 47 | major-grid-color minor-grid-color 48 | major-grid-width minor-grid-width]}] 49 | (let [grid-y-space (/ height grid-lines)] 50 | (for [i (range 1 grid-lines)] 51 | (-> (line 0 (* i grid-y-space) width (* i grid-y-space)) 52 | (style :stroke (if (even? i) major-grid-color minor-grid-color) 53 | :stroke-width (if (even? i) major-grid-width minor-grid-width)))))) 54 | 55 | (defn x-axis [{:keys [height width 56 | xmin xmax 57 | grid-lines 58 | axis-font-family axis-font-size 59 | axis-number-format]}] 60 | (let [grid-x-space (/ width grid-lines)] 61 | (for [i (range 0 (inc grid-lines)) :when (even? i)] 62 | (-> (text {:x (* i grid-x-space) :y (+ 20 height)} 63 | (#?(:clj format :cljs gstring/format) axis-number-format 64 | (translate-value (* i grid-x-space) 65 | 0 width xmin xmax))) 66 | (style :fill (rgb 150 150 150) 67 | :font-family axis-font-family 68 | :font-size axis-font-size 69 | :text-anchor :middle))))) 70 | 71 | (defn y-axis [{:keys [height 72 | ymin ymax 73 | grid-lines 74 | axis-font-family axis-font-size 75 | axis-number-format]}] 76 | (let [grid-y-space (/ height grid-lines)] 77 | (for [i (range 1 (inc grid-lines)) :when (even? i)] 78 | (-> (text {:x 0 :y (- height (* i grid-y-space))} 79 | (#?(:clj format :cljs gstring/format) axis-number-format 80 | (translate-value (* i grid-y-space) 81 | 0 height ymin ymax))) 82 | (style :fill (rgb 150 150 150) 83 | :font-family axis-font-family 84 | :font-size axis-font-size 85 | :text-anchor :end 86 | :alignment-baseline :middle))))) 87 | 88 | (defn xy-plot [& options] 89 | (let [props (merge default-chart-props (apply hash-map options))] 90 | {:properties props 91 | :svg (-> (group (chart-background props)) 92 | (translate (:x props) (:y props)) 93 | (concat 94 | (x-grid props) 95 | (y-grid props) 96 | (x-axis props) 97 | (y-axis props)))})) 98 | 99 | (defn point-label [{:keys [label-font-family 100 | label-font-size 101 | label-number-format]} 102 | x* y* x y r & _] 103 | (-> (text {:x (+ x* r) :y (- y* r)} 104 | (str (#?(:clj format :cljs gstring/format) label-number-format (float x)) "," 105 | (#?(:clj format :cljs gstring/format) label-number-format (float y)))) 106 | (style :fill (rgb 100 100 150) 107 | :font-family label-font-family 108 | :font-size label-font-size))) 109 | 110 | (defn add-point [chart x y r & options] 111 | (let [props (:properties chart) 112 | {:keys [height width 113 | xmin xmax 114 | ymin ymax 115 | label-points?]} props 116 | x* (translate-value x xmin xmax 0 width) 117 | y* (- height (translate-value y ymin ymax 0 height)) 118 | point (apply circle x* y* r options) 119 | label (point-label props x* y* x y r)] 120 | (-> chart 121 | (update-in [:points] (fn [old] (conj old (apply assoc {:x x, :y y, :r r} options)))) 122 | (assoc :svg (concat (:svg chart) (if label-points? [point label] [point])))))) 123 | 124 | (defn points->xy [points] 125 | (reduce (fn [[x y] [p1 p2]] [(conj x p1) (conj y p2)]) 126 | [[] []] points)) 127 | 128 | (defn xy->points [[x y]] 129 | (map (fn [p1 p2] [p1 p2]) x y)) 130 | 131 | (defn add-points [chart data & {:keys [size sizes colors transpose-data?? fill]}] 132 | (let [[x y] (if transpose-data?? data (points->xy data)) 133 | sizes (or sizes (repeat (count x) (or size 3))) 134 | colors (or colors (repeat (count x) (or fill (rgb 0 0 255)))) 135 | data (map (fn [x y r color] [x y r color]) x y sizes colors)] 136 | (reduce (fn [svg [x y r color]] (add-point svg x y r :fill color)) 137 | chart data))) 138 | 139 | (defn emit-svg [chart] 140 | (emit (svg (:svg chart)))) 141 | -------------------------------------------------------------------------------- /src/analemma/svg.cljc: -------------------------------------------------------------------------------- 1 | (ns analemma.svg 2 | (:require [analemma.xml :as xml] 3 | [clojure.string :as s])) 4 | 5 | 6 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 7 | ;; SVG FUNCTIONS 8 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 9 | 10 | (defn svg [& content] 11 | (let [xmlns {"xmlns:svg" "http://www.w3.org/2000/svg" 12 | "xmlns" "http://www.w3.org/2000/svg" 13 | "xmlns:xlink" "http://www.w3.org/1999/xlink" 14 | "version" "1.0"} 15 | attrs (if (map? (first content)) (first content) {}) 16 | content (if (map? (first content)) (rest content) content)] 17 | (concat [:svg (merge xmlns attrs)] content))) 18 | 19 | (defn style-map [elem props] 20 | (let [styling (when (seq props) 21 | (reduce (fn [s [k v]] 22 | (str s " " (name k) ": " 23 | (if (keyword? v) 24 | (name v) 25 | v) 26 | "; ")) 27 | "" props))] 28 | (xml/add-attrs elem :style styling))) 29 | 30 | (defn style [elem & properties] 31 | (style-map elem (apply hash-map properties))) 32 | 33 | (defn line [x1 y1 x2 y2 & options] 34 | (let [attrs (apply hash-map options)] 35 | [:line (apply merge {:x1 x1, :y1 y1, :x2 x2, :y2 y2} attrs)])) 36 | 37 | (defn rect [x y height width & options] 38 | (let [attrs (apply hash-map options)] 39 | [:rect (apply merge {:x x, :y y, :height height, :width width} attrs)])) 40 | 41 | (defn circle [cx cy r & options] 42 | (let [attrs (apply hash-map options)] 43 | [:circle (apply merge {:cx cx, :cy cy, :r r} attrs)])) 44 | 45 | (defn ellipse [cx cy rx ry & options] 46 | (let [attrs (apply hash-map options)] 47 | [:ellipse (apply merge {:cx cx, :cy cy, :rx rx, :ry ry} attrs)])) 48 | 49 | (defn polygon [[& points] & options] 50 | (let [attrs (apply hash-map options) 51 | points (reduce (fn [s [x y]] (str s " " x "," y)) 52 | "" (partition 2 points))] 53 | [:polygon (apply merge {:points points} 54 | attrs)])) 55 | 56 | (defn text [& content] 57 | (concat [:text] content)) 58 | 59 | (defn group [& content] 60 | (cons :g content)) 61 | 62 | (defn draw [& commands] 63 | (reduce (fn [s [cmd args]] (str s " " (name cmd) (apply str (interpose "," args)))) 64 | "" (partition 2 commands))) 65 | 66 | (defn path [draw-commands & options] 67 | (let [attrs (apply hash-map options)] 68 | [:path (merge {:d (apply draw draw-commands)} attrs)])) 69 | 70 | (defn tref [id] 71 | [:tref {"xlink:href" (str "#" (name id))}]) 72 | 73 | (defn rgb [r g b] 74 | (str "rgb(" r "," g "," b ")")) 75 | 76 | (defn animate [elem attr & attrs] 77 | (-> elem 78 | (xml/add-content (-> [:animate {:attributeName (name attr), 79 | :begin 0, :fill "freeze"}] 80 | (xml/merge-attrs (apply hash-map attrs)))))) 81 | 82 | (defn animate-motion [elem & attrs] 83 | (-> elem 84 | (xml/add-content (-> [:animateMotion {:begin 0, :fill "freeze"}] 85 | (xml/merge-attrs (apply hash-map attrs)))))) 86 | 87 | (defn animate-color [elem attr & attrs] 88 | (-> elem 89 | (xml/add-content (-> [:animateColor {:attributeName (name attr), 90 | :begin 0, :fill "freeze"}] 91 | (xml/merge-attrs (apply hash-map attrs)))))) 92 | 93 | (defn animate-transform [elem & attrs] 94 | (-> elem 95 | (xml/add-content (-> [:animateTransform {:attributeName "transform" 96 | :begin 0, :fill "freeze"}] 97 | (xml/merge-attrs (apply hash-map attrs)))))) 98 | 99 | (defn transform [elem trans] 100 | (let [attrs (xml/get-attrs elem) 101 | trans (if (:transform attrs) 102 | (str (:transform attrs) " " trans) 103 | trans)] 104 | (xml/add-attrs elem :transform trans))) 105 | 106 | (defn rotate [elem angle x y] 107 | (transform elem (str "rotate(" angle "," x "," y ")"))) 108 | 109 | (defn translate 110 | ([elem x] (transform elem (str "translate(" x ")"))) 111 | ([elem x y] (transform elem (str "translate(" x "," y ")")))) 112 | 113 | (defn defs [[& bindings]] 114 | (let [bindings (partition 2 bindings) 115 | f (fn [defs-tag [id tag]] 116 | (conj defs-tag 117 | (xml/add-attrs tag :id (name id))))] 118 | (reduce f [:defs] bindings))) 119 | 120 | (defn text-path [text path-id] 121 | [:textPath {"xlink:href" (str "#" (name path-id))} text]) 122 | 123 | (defn tspan [& content] 124 | (concat [:tspan] content)) 125 | 126 | (defn image [href & options] 127 | (let [attrs (apply hash-map options)] 128 | [:image (merge {"xlink:href" href} attrs)])) 129 | 130 | 131 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 132 | ;; UTILITY FUNCTIONS 133 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 134 | 135 | (defn translate-value [v from-min from-max to-min to-max] 136 | (let [scale (/ (- to-max to-min) 137 | (- from-max from-min)) 138 | trans (- to-min (* from-min scale))] 139 | (float (+ (* v scale) trans)))) 140 | 141 | (defn parse-inline-css [css-str] 142 | (reduce (fn [m [k v]] (assoc m (keyword k) v)) 143 | {} (map #(s/split % #":") (s/split css-str #";")))) 144 | 145 | (defn add-style [elem & styling] 146 | (let [css-str (or (:style (xml/get-attrs elem)) "")] 147 | (style-map elem (apply merge (parse-inline-css css-str) (apply hash-map styling))))) -------------------------------------------------------------------------------- /src/analemma/xml.cljc: -------------------------------------------------------------------------------- 1 | (ns analemma.xml 2 | (:require #?(:clj [clojure.xml :as xml]) 3 | [clojure.zip :as z]) 4 | #?(:clj (:import [java.io ByteArrayInputStream]))) 5 | 6 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 7 | ;; XML FUNCTIONS 8 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 9 | 10 | (defn has-attrs? [tag] 11 | (map? (second tag))) 12 | 13 | (defn has-content? [tag] 14 | (if (has-attrs? tag) 15 | (> (count tag) 2) 16 | (> (count tag) 1))) 17 | 18 | (defn get-name [tag] 19 | (if-let [n (first tag)] 20 | (name n))) 21 | 22 | (defn get-attrs [tag] 23 | (if (has-attrs? tag) (second tag) {})) 24 | 25 | (defn get-content [tag] 26 | (if (has-attrs? tag) 27 | (drop 2 tag) 28 | (rest tag))) 29 | 30 | (defn set-attrs [tag attrs] 31 | (concat [(get-name tag) attrs] 32 | (get-content tag))) 33 | 34 | (defn set-content [tag & content] 35 | (concat [(get-name tag) (get-attrs tag)] 36 | content)) 37 | 38 | (defn add-attrs [tag & attrs] 39 | (concat [(get-name tag) 40 | (apply assoc (get-attrs tag) attrs)] 41 | (get-content tag))) 42 | 43 | (defn merge-attrs [tag attrs] 44 | (concat [(get-name tag) 45 | (merge (get-attrs tag) attrs)] 46 | (get-content tag))) 47 | 48 | (defn add-content [tag & content] 49 | (concat [(get-name tag) 50 | (get-attrs tag)] 51 | (concat (get-content tag) 52 | content))) 53 | 54 | (defn update-attrs [tag [& keys] update-fn & args] 55 | (set-attrs tag (apply update-in (get-attrs tag) keys update-fn args))) 56 | 57 | (defn emit-attrs [attrs] 58 | (when attrs 59 | (reduce (fn [s [k v]] 60 | (str s (name k) "=\"" (if (keyword? v) (name v) v) "\" ")) 61 | "" attrs))) 62 | 63 | (defn emit-tag [tag] 64 | (if-let [n (get-name tag)] 65 | (str "<" n " " 66 | (emit-attrs (get-attrs tag)) 67 | (if (seq (get-content tag)) 68 | (str ">" (apply str (map #(if (string? %) % (emit-tag %)) (get-content tag))) 69 | "") 70 | "/>")))) 71 | 72 | (defn emit [& tags] 73 | (str "" 74 | (reduce #(str %1 (emit-tag %2)) "" tags))) 75 | 76 | 77 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 78 | ;; FUNCTIONS FOR PARSING XML FILES: CLOJURE ONLY 79 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 80 | 81 | #?(:clj (defn get-xml-map [xml-string] 82 | (xml/parse (ByteArrayInputStream. (.getBytes xml-string "UTF-8"))))) 83 | 84 | #?(:clj (defn parse-xml-map [xml-map] 85 | (if (map? xml-map) 86 | (let [{:keys [tag attrs content]} xml-map] 87 | (lazy-cat (if attrs [tag attrs] [tag]) 88 | (map parse-xml-map content))) 89 | xml-map))) 90 | 91 | #?(:clj (defn parse-xml [xml-string] 92 | (parse-xml-map (get-xml-map xml-string)))) 93 | 94 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 95 | ;; FUNCTIONS FOR FILTERING AND TRANSFORMING PARSED XML 96 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 97 | 98 | (defn- select-loc? 99 | "Provides selector functionality used by filter-xml and transform-xml. 100 | 101 | Examples of selectors: 102 | :tag-name 103 | {attr-name val} 104 | [:and :tag-name {:attr val}] 105 | [:or :tag1 :tag2] 106 | [:and [:not {attr val}] [:or :tag1 :tag2]] 107 | 108 | " 109 | ([loc selector] 110 | (when (z/branch? loc) 111 | (let [node (z/node loc)] 112 | (cond 113 | (map? selector) 114 | (and (has-attrs? node) 115 | (= (select-keys (get-attrs node) (keys selector)) 116 | selector)) 117 | (or (string? selector) (keyword? selector)) 118 | (= (get-name node) (name selector)) 119 | (coll? selector) 120 | (condp = (first selector) 121 | :and (reduce #(and %1 (select-loc? loc %2)) true (next selector)) 122 | :or (reduce #(or %1 (select-loc? loc %2)) false (next selector)) 123 | :not (not (select-loc? loc [:or (second selector)])))))))) 124 | 125 | (defn filter-xml [xml-seq [& selectors]] 126 | (letfn [(filter-xml* [zip-loc [selector & child-selectors]] 127 | (loop [nodes [] loc zip-loc] 128 | (if (z/end? loc) 129 | nodes 130 | (recur 131 | (if (select-loc? loc selector) 132 | (if (seq child-selectors) 133 | (filter-xml* loc child-selectors) 134 | (conj nodes (z/node loc))) 135 | nodes) 136 | (z/next loc)))))] 137 | (filter-xml* (z/seq-zip xml-seq) selectors))) 138 | 139 | (defn transform-xml [xml-seq [& selectors] f & args] 140 | (letfn [(transform-xml* [zip-loc [selector & child-selectors] f & args] 141 | (loop [loc zip-loc] 142 | (if (z/end? loc) 143 | loc 144 | (recur 145 | (z/next 146 | (if (select-loc? loc selector) 147 | (if (seq child-selectors) 148 | (apply transform-xml* loc child-selectors f args) 149 | (apply z/edit loc f args)) 150 | loc))))))] 151 | (z/root (apply transform-xml* (z/seq-zip xml-seq) selectors f args)))) 152 | -------------------------------------------------------------------------------- /src/examples/analemma.clj: -------------------------------------------------------------------------------- 1 | (ns examples.analemma 2 | (:use [analemma.charts :only [emit-svg xy-plot add-points]] 3 | [analemma.svg] 4 | [analemma.xml] 5 | [clojure.java.io :only [file]])) 6 | 7 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 8 | ;; ANALEMMA CHART 9 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 10 | 11 | ;; http://www.wsanford.com/~wsanford/exo/sundials/analemma_calc.html 12 | (def analemma-data 13 | [[-15.165 -23.07] 14 | [-17.016 -22.70] 15 | [-19.171 -22.08] 16 | [-21.099 -21.27] 17 | [-22.755 -20.30] 18 | [-24.107 -19.16] 19 | [-25.446 -17.33] 20 | [-25.914 -16.17] 21 | [-26.198 -14.62] 22 | [-26.158 -12.96] 23 | [-25.814 -11.21] 24 | [-25.194 -9.39] 25 | [-24.520 -7.89] 26 | [-23.708 -6.37] 27 | [-22.529 -4.42] 28 | [-21.205 -2.45] 29 | [-19.777 -0.48] 30 | [-18.289 1.50] 31 | [-16.185 4.24] 32 | [-15.009 5.78] 33 | [-13.605 7.66] 34 | [-12.309 9.49] 35 | [-11.153 11.26] 36 | [-10.169 12.94] 37 | [-9.250 14.85] 38 | [-8.811 16.04] 39 | [-8.469 17.43] 40 | [-8.364 18.69] 41 | [-8.493 19.83] 42 | [-8.847 20.82] 43 | [-9.685 21.96] 44 | [-10.317 22.47] 45 | [-11.231 22.96] 46 | [-12.243 23.28] 47 | [-13.308 23.43] 48 | [-14.378 23.41] 49 | [-15.599 23.16] 50 | [-16.339 22.86] 51 | [-17.139 22.33] 52 | [-17.767 21.64] 53 | [-18.191 20.80] 54 | [-18.387 19.81] 55 | [-18.253 18.20] 56 | [-17.956 17.17] 57 | [-17.361 15.78] 58 | [-16.529 14.28] 59 | [-15.474 12.68] 60 | [-14.221 11.01] 61 | [-12.183 8.54] 62 | [-10.901 7.07] 63 | [-9.212 5.20] 64 | [-7.462 3.29] 65 | [-5.693 1.36] 66 | [-3.946 -0.59] 67 | [-1.938 -2.93] 68 | [-0.686 -4.48] 69 | [0.742 -6.39] 70 | [1.982 -8.28] 71 | [2.993 -10.11] 72 | [3.742 -11.88] 73 | [4.290 -14.23] 74 | [4.318 -15.49] 75 | [4.044 -16.97] 76 | [3.420 -18.33] 77 | [2.446 -19.55] 78 | [1.135 -20.63] 79 | [-0.852 -21.71] 80 | [-2.398 -22.29] 81 | [-4.538 -22.86] 82 | [-6.855 -23.24] 83 | [-9.286 -23.42] 84 | [-11.761 -23.41] 85 | [-14.691 -23.14]]) 86 | 87 | (defn analemma [filename] 88 | (spit filename 89 | (emit-svg 90 | (-> (xy-plot :xmin -30 :xmax 10, 91 | :ymin -30 :ymax 30 92 | :height 500 :width 500) 93 | (add-points analemma-data))))) 94 | 95 | 96 | (defn analemma-logo [filename] 97 | (spit filename 98 | (emit 99 | (svg 100 | (apply group 101 | (-> (text "Analemma") 102 | (add-attrs :x 120 :y 60) 103 | (style :fill #"000066" 104 | :font-family "Garamond" 105 | :font-size "75px" 106 | :alignment-baseline :middle)) 107 | (for [[x y] analemma-data] 108 | (circle (translate-value x -30 5 0 125) 109 | (translate-value y -25 30 125 0) 110 | 2 :fill "#000066"))))))) 111 | 112 | (defn rotating-analemma-logo [filename] 113 | (spit filename 114 | (emit 115 | (svg 116 | (-> (image "file:images/analemma-logo.svg" 117 | :width 500 :height 700) 118 | (animate-transform :begin 0 119 | :dur 20 120 | :type :rotate 121 | :from "0 200 150" 122 | :to "360 200 150" 123 | :repeatCount :indefinite)))))) 124 | 125 | 126 | (defn query-and-transform-analemma [filename] 127 | (let [logo (parse-xml (slurp (file "images/analemma-logo.svg"))) 128 | red-analemma (-> (concat [:g] (filter-xml logo [:g :circle])) 129 | (transform-xml [:circle] #(add-attrs % :fill "#FF0000")))] 130 | (spit filename 131 | (emit 132 | (svg 133 | (-> red-analemma 134 | (animate-transform :begin 0 135 | :dur 20 136 | :type :rotate 137 | :from "0 100 100" 138 | :to "360 100 100" 139 | :repeatCount :indefinite))))))) -------------------------------------------------------------------------------- /src/examples/charts.clj: -------------------------------------------------------------------------------- 1 | (ns examples.charts 2 | (:use [analemma.charts :only [emit-svg xy-plot add-points]] 3 | [analemma.svg :only [rgb]])) 4 | 5 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 6 | ;; BASIC CHART EXAMPLES 7 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 8 | 9 | (defn rand-plot [directory] 10 | (let [x (repeatedly 25 #(rand-int 100)) 11 | y (repeatedly 25 #(rand-int 100))] 12 | (spit (str directory "/rand-plot.svg") 13 | (emit-svg 14 | (-> (xy-plot :width 500 :height 500 :label-points? true) 15 | (add-points [x y] :transpose-data?? true)))))) 16 | 17 | (defn sin-cos-plot [directory] 18 | (let [x (range -5 5 0.05) 19 | y1 (map #(Math/cos %) x) 20 | y2 (map #(Math/sin %) x)] 21 | (spit (str directory "/sin-cos-small.svg") 22 | (emit-svg 23 | (-> (xy-plot :width 450 :height 200 24 | :xmin -5 :xmax 5 25 | :ymin -1.5 :ymax 1.5) 26 | (add-points [x y1] :transpose-data?? true 27 | :size 1) 28 | (add-points [x y2] :transpose-data?? true 29 | :size 1 30 | :fill (rgb 255 0 0))))))) 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/examples/svg.clj: -------------------------------------------------------------------------------- 1 | (ns examples.svg 2 | (:use [analemma.xml :only [emit add-content add-attrs 3 | parse-xml transform-xml filter-xml]] 4 | analemma.svg 5 | [clojure.java.io :only [file]])) 6 | 7 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 8 | ;; ANALEMMA SVG EXAMPLES 9 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 10 | 11 | (def ex-group (svg 12 | (svg {:x 50 :y 50} 13 | (-> (group 14 | (-> (line 10 10 85 10) 15 | (style :stroke "#006600")) 16 | (-> (rect 10 20 50 75) 17 | (style :stroke "#006600" :fill "#006600")) 18 | (-> (text {:x 10 :y 90} "Text grouped with shapes") 19 | (style :stroke "#660000" :fill "660000"))) 20 | (rotate 45 50 50))))) 21 | 22 | ;; http://tutorials.jenkov.com/svg/rect-element.html 23 | (def ex-round-rects 24 | (svg 25 | (-> (rect 10 10 50 50 :rx 5 :ry 5) 26 | (style :stroke "#006600" :fill "#00cc00")) 27 | (-> (rect 70 10 50 50 :rx 10 :ry 10) 28 | (style :stroke "#006600" :fill "#00cc00")) 29 | (-> (rect 130 10 50 50 :rx 15 :ry 15) 30 | (style :stroke "#006600" :fill "#00cc00")))) 31 | 32 | (def ex-circle (svg 33 | (-> (circle 40 40 24) 34 | (style :stroke "#006600" :fill "#00cc00")))) 35 | 36 | (def ex-ellipse (svg 37 | (-> (ellipse 40 40 30 15) 38 | (style :stroke "#006600" :fill "#00cc00")))) 39 | 40 | (def ex-tri (svg 41 | (-> (polygon [0,0 30,0 15,30 0,0]) 42 | (style :stroke "#006600" :fill "#33cc33")))) 43 | 44 | (def ex-oct (svg 45 | (-> (polygon [50,05 100,5 125,30 125,80 100,105 50,105 25,80 25,30]) 46 | (style :stroke "#660000" 47 | :fill "#cc3333" 48 | :stroke-width 3)))) 49 | 50 | ;; http://tutorials.jenkov.com/svg/path-element.html 51 | (def ex-path (svg 52 | (-> (path [:M [50,50] 53 | :A [30,30 0 0,1 35,20] 54 | :L [100,100] 55 | :M [110,110] 56 | :L [100,0]]) 57 | (style :stroke "#660000" :fill :none)))) 58 | 59 | (def ex-text (svg 60 | (text {:x 20 :y 40} "Example SVG text 1") 61 | (-> (line 10 40 150 40) 62 | (style :stroke "#000000")))) 63 | 64 | (def ex-text2 (svg 65 | (-> (text {:x 20 :y 40} "Rotated SVG text") 66 | (style :stroke :none :fill "#000000") 67 | (rotate 30 20 40)))) 68 | 69 | (def ex-text3 (svg 70 | (-> (text {:x 20 :y 40} "Styled SVG text") 71 | (style :font-family "Arial" 72 | :font-size 34 73 | :stroke "#000000" 74 | :fill "#00ff00")))) 75 | 76 | (def ex-text4 (svg 77 | (text {:x 20 :y 10} 78 | (tspan "tspan line 1") 79 | (tspan "tspan line 2") 80 | (tspan "tspan line 3")))) 81 | 82 | (def ex-text5 (svg 83 | (text {:y 10} 84 | (tspan {:x 0} "tspan line 1") 85 | (tspan {:x 0 :dy 15} "tspan line 2") 86 | (tspan {:x 0 :dy 15} "tspan line 3")))) 87 | 88 | (def ex-tref (svg 89 | (defs [:the-text (text "A text that is referenced.")]) 90 | (text {:x 20 :y 10} (tref :the-text)) 91 | (text {:x 30 :y 30} (tref :the-text)))) 92 | 93 | (def ex-text-path (svg 94 | (defs [:my-path (path [:M [75,20] 95 | :a [1,1 0 0,0 100,0]])]) 96 | (-> (text {:x 10 :y 100} 97 | (text-path "Text along a curved path..." :my-path)) 98 | (style :stroke "#000000")))) 99 | 100 | (def ex-text-path2 (svg 101 | (defs [:the-text (text "Text ref along a curved path...") 102 | :my-path (path [:M [75,20] 103 | :a [1,1 0 0,0 100,0]])]) 104 | (-> (text {:x 10 :y 100} 105 | (text-path (tref :the-text) :my-path)) 106 | (style :stroke "#000000")))) 107 | 108 | (def ex-img (svg 109 | (-> (rect 10 10 130 500) 110 | (style :fill "#000000")) 111 | (image "http://jenkov.com/images/layout/top-bar-logo.png" 112 | :x 20 :y 20 :width 300 :height 80) 113 | (-> (line 25 80 350 80) 114 | (style :stroke "#ffffff" :stroke-width 3)))) 115 | 116 | (def ex-trans (svg 117 | (-> (rect 50 50 110 110) 118 | (style :stroke "#ff0000" :fill "#ccccff") 119 | (translate 30) 120 | (rotate 45 50 50)) 121 | (-> (text {:x 70 :y 100} "Hello World") 122 | (translate 30) 123 | (rotate 45 50 50)))) 124 | 125 | (def ex-animate (svg 126 | (-> (rect 10 10 110 110) 127 | (style :stroke "#ff0000" :fill "#0000ff") 128 | (animate-transform :begin 0 129 | :dur 20 130 | :type :rotate 131 | :from "0 60 60" 132 | :to "360 60 60" 133 | :repeatCount :indefinite)))) 134 | 135 | ;; http://www.w3.org/TR/SVG/animate.html 136 | (def ex-anim2 (svg 137 | (-> (rect 1 1 298 798) 138 | (style :fill "none" :stroke "blue" :stroke-width 2)) 139 | 140 | (-> (rect 300 100 300 100) 141 | (style :fill (rgb 255 255 0)) 142 | (animate :x :begin 0 :dur 9 :from 300 :to 0) 143 | (animate :y :begin 0 :dur 9 :from 100 :to 0) 144 | (animate :width :begin 0 :dur 9 :from 300 :to 800) 145 | (animate :height :begin 0 :dur 9 :from 100 :to 300)) 146 | 147 | (-> (group 148 | (-> (text "It's alive") 149 | (style :font-family :Verdana 150 | :font-size 35.27 151 | :visibility :hidden) 152 | (animate :visibility :to :visible :begin 3) 153 | (animate-motion :path (draw :M [0 0] :L [100 100]) 154 | :begin 3 :dur 6) 155 | (animate-color :fill :from (rgb 0 0 255) :to (rgb 128 0 0) 156 | :begin 3 :dur 6) 157 | (animate-transform :type :rotate :from -30 :to 0 :begin 3 :dur 6) 158 | (animate-transform :type :scale :from 1 :to 3 :additive :sum 159 | :begin 3 :dur 6))) 160 | (translate 100 100)))) 161 | 162 | (def ex-anim-logo (svg 163 | (-> (image "http://clojure.org/space/showimage/clojure-icon.gif" 164 | :width 100 :height 100) 165 | (animate :x :begin 0 :dur 9 :from 0 :to 300) 166 | (animate :y :begin 0 :dur 9 :from 0 :to 300)))) 167 | 168 | (def ex-anim-logo2 (svg 169 | (-> (image "http://clojure.org/space/showimage/clojure-icon.gif" 170 | :width 100 :height 100) 171 | (animate-transform :type :rotate :from -30 :to 0 :begin 0 :dur 6)))) 172 | 173 | (def ex-anim-logo3 (svg 174 | (-> (group 175 | (-> (image "http://clojure.org/space/showimage/clojure-icon.gif" 176 | :width 100 :height 100) 177 | (animate-transform :type :rotate 178 | :begin 0 179 | :dur 20 180 | :from "0 50 50" 181 | :to "360 50 50" 182 | :repeatCount :indefinite))) 183 | 184 | (translate 100 100)))) 185 | 186 | (defn txt-box [txt x y fill] 187 | (let [box-width 300 188 | box-height 50] 189 | (-> (svg 190 | (group 191 | (-> (rect 0 0 box-height box-width :rx 5 :ry 5) 192 | (style :stroke fill :fill fill)) 193 | (-> (text txt) 194 | (add-attrs :x (/ box-width 2) 195 | :y (/ box-height 2)) 196 | (style :fill "#ffffff" 197 | :font-size "25px" 198 | :font-family "Verdana" 199 | :alignment-baseline :middle 200 | :text-anchor :middle)))) 201 | (add-attrs :x x :y y)))) 202 | 203 | (defn analemma-stack [directory] 204 | (spit (str directory "/analemma-stack.svg") 205 | (emit 206 | (svg 207 | (-> (group 208 | (-> (txt-box "analemma.charts" 0 10 "#006600") 209 | (add-attrs :visibility :hidden) 210 | (animate :visibility :to :visible :begin 5) 211 | (animate :y :begin 5 :dur 1 :from 0 :to 10)) 212 | (-> (txt-box "analemma.svg" 0 65 "#660000") 213 | (add-attrs :visibility :hidden) 214 | (animate :visibility :to :visible :begin 3) 215 | (animate :y :begin 3 :dur 2 :from 0 :to 65)) 216 | (-> (txt-box "analemma.xml" 0 120 "#000066") 217 | (add-attrs :visibility :hidden) 218 | (animate :visibility :to :visible :begin 1) 219 | (animate :y :begin 1 :dur 4 :from 0 :to 120))) 220 | (translate 10 10)))))) 221 | 222 | 223 | (defn parse-us-map [] 224 | (parse-xml (slurp "http://upload.wikimedia.org/wikipedia/commons/3/32/Blank_US_Map.svg"))) 225 | 226 | (defn hide-california [filename] 227 | (spit filename 228 | (emit 229 | (transform-xml (parse-us-map) 230 | [{:id "CA"}] 231 | #(add-attrs % :visibility "hidden"))))) 232 | 233 | (defn color-maryland [filename] 234 | (spit filename 235 | (emit 236 | (transform-xml (parse-us-map) 237 | [{:id "MD"}] 238 | (fn [elem] 239 | (-> (add-style elem :fill "#0000ff") 240 | (add-attrs :transform "scale(1.10)"))))))) 241 | 242 | 243 | (defn select-maryland [filename] 244 | (spit filename 245 | (emit 246 | (svg (-> (apply svg {:x -600 :y -200} 247 | (filter-xml (parse-us-map) 248 | [[:or "sodipodi:namedview" :defs {:id "MD"}]])) 249 | (transform-xml [:svg] 250 | #(add-attrs % 251 | "xmlns:sodipodi" "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" 252 | "xmlns:inkscape" "http://www.inkscape.org/namespaces/inkscape")) 253 | (transform-xml [{:id "MD"}] 254 | (fn [elem] 255 | (add-attrs elem :transform "scale(1.5)")))))))) 256 | 257 | (defn to-hex-string [n] (str "#" (Integer/toHexString n))) 258 | 259 | ;;(to-hex-string (translate-value 0.5 0 1 0 16777215)) 260 | 261 | (def us-states 262 | {"AK" "ALASKA" 263 | "AL" "ALABAMA" 264 | "AR" "ARKANSAS" 265 | "AS" "AMERICAN SAMOA" 266 | "AZ" "ARIZONA" 267 | "CA" "CALIFORNIA" 268 | "CO" "COLORADO" 269 | "CT" "CONNECTICUT" 270 | "DC" "WASHINGTON, DC" 271 | "DE" "DELAWARE" 272 | "FL" "FLORIDA" 273 | "FM" "FEDERATED STATES OF MICRONESIA" 274 | "GA" "GEORGIA" 275 | "GU" "GUAM" 276 | "HI" "HAWAII" 277 | "IA" "IOWA" 278 | "ID" "IDAHO" 279 | "IL" "ILLINOIS" 280 | "IN" "INDIANA" 281 | "KS" "KANSAS" 282 | "KY" "KENTUCKY" 283 | "LA" "LOUISIANA" 284 | "MA" "MASSACHUSETTS" 285 | "MD" "MARYLAND" 286 | "ME" "MAINE" 287 | "MH" "MARSHALL ISLANDS" 288 | "MI" "MICHIGAN" 289 | "MN" "MINNESOTA" 290 | "MO" "MISSOURI" 291 | "MP" "NORTHERN MARIANA ISLANDS" 292 | "MS" "MISSISSIPPI" 293 | "MT" "MONTANA" 294 | "NC" "NORTH CAROLINA" 295 | "ND" "NORTH DAKOTA" 296 | "NE" "NEBRASKA" 297 | "NH" "NEW HAMPSHIRE" 298 | "NJ" "NEW JERSEY" 299 | "NM" "NEW MEXICO" 300 | "NV" "NEVADA" 301 | "NY" "NEW YORK" 302 | "OH" "OHIO" 303 | "OK" "OKLAHOMA" 304 | "OR" "OREGON" 305 | "PA" "PENNSYLVANIA" 306 | "PR" "PUERTO RICO" 307 | "PW" "PALAU" 308 | "RI" "RHODE ISLAND" 309 | "SC" "SOUTH CAROLINA" 310 | "SD" "SOUTH DAKOTA" 311 | "TN" "TENNESSEE" 312 | "TX" "TEXAS" 313 | "UT" "UTAH" 314 | "VA" "VIRGINIA" 315 | "VI" "VIRGIN ISLANDS" 316 | "VT" "VERMONT" 317 | "WA" "WASHINGTON" 318 | "WI" "WISCONSIN" 319 | "WV" "WEST VIRGINIA" 320 | "WY" "WYOMING"}) 321 | 322 | (defn color-states [filename] 323 | (spit filename 324 | (emit 325 | (transform-xml (parse-us-map) 326 | [[:and [:not {:id "path57"}] [:or :g :path]]] 327 | (fn [elem] 328 | (add-style elem :fill (to-hex-string (rand 16777215)))))))) 329 | 330 | --------------------------------------------------------------------------------