Skip to content

Latest commit

 

History

History
782 lines (675 loc) · 30.9 KB

demos.org

File metadata and controls

782 lines (675 loc) · 30.9 KB

This file is archived and only kept for reference - DO NOT edit

Contents

geom.viz examples

This section shows some basic example outputs and general usage patterns for all currently implemented visualization methods. The section after then describes the various options in more detail.

Running all examples from REPL

See instructions in examples/all.org about running all the examples in this module from the REPL. Note, these examples are all for Clojure, but (apart from outputting SVG files), the same functionality is available in Clojurescript (with or without React.js).

Namespaces required by all examples

(require '[thi.ng.geom.viz.core :as viz] :reload)
(require '[thi.ng.geom.svg.core :as svg])
(require '[thi.ng.geom.vector :as v])
(require '[thi.ng.color.core :as col])
(require '[thi.ng.math.core :as m :refer [PI TWO_PI]])

Scatter plot

http://media.thi.ng/geom/viz/scatter-linear-3.svghttp://media.thi.ng/geom/viz/scatter-log-3.svg
Logarithmic X-axis, linear YLog X, Log Y
<<example-imports>>

(defn export-viz
  [spec path]
  (->> spec
       (viz/svg-plot2d-cartesian)
       (svg/svg {:width 600 :height 600})
       (svg/serialize)
       (spit path)))

(def spec
  {:x-axis (viz/log-axis
            {:domain [1 201]
             :range  [50 590]
             :pos    550})
   :y-axis (viz/linear-axis
            {:domain      [0.1 100]
             :range       [550 20]
             :major       10
             :minor       5
             :pos         50
             :label-dist  15
             :label-style {:text-anchor "end"}})
   :grid   {:attribs {:stroke "#caa"}
            :minor-x true
            :minor-y true}
   :data   [{:values  (map (juxt identity #(Math/sqrt %)) (range 0 200 2))
             :attribs {:fill "#0af" :stroke "none"}
             :layout  viz/svg-scatter-plot}
            {:values  (map (juxt identity #(m/random %)) (range 0 200 2))
             :attribs {:fill "none" :stroke "#f60"}
             :shape   (viz/svg-triangle-down 6)
             :layout  viz/svg-scatter-plot}]})

(export-viz spec "scatter-linear.svg")

(-> spec
    (assoc :y-axis (viz/log-axis
                    {:domain      [0.1 101]
                     :range       [550 20]
                     :pos         50
                     :label-dist  15
                     :label-style {:text-anchor "end"}}))
    (export-viz "scatter-log.svg"))

Line & area plot

http://media.thi.ng/geom/viz/lineplot-3.svghttp://media.thi.ng/geom/viz/areaplot-3.svg
Line plot (cartesian)Area plot (cartesian)
<<example-imports>>

(defn test-equation
  [t] (let [x (m/mix* (- PI) PI t)] [x (* (Math/cos (* 0.5 x)) (Math/sin (* x x x)))]))

(defn export-viz
  [viz path] (->> viz (svg/svg {:width 600 :height 320}) (svg/serialize) (spit path)))

(def viz-spec
  {:x-axis (viz/linear-axis
            {:domain [(- PI) PI]
             :range  [50 580]
             :major  (/ PI 2)
             :minor  (/ PI 4)
             :pos    250})
   :y-axis (viz/linear-axis
            {:domain      [-1 1]
             :range       [250 20]
             :major       0.2
             :minor       0.1
             :pos         50
             :label-dist  15
             :label-style {:text-anchor "end"}})
   :grid   {:attribs {:stroke "#caa"}
            :minor-y true}
   :data   [{:values  (map test-equation (m/norm-range 200))
             :attribs {:fill "none" :stroke "#0af"}
             :layout  viz/svg-line-plot}]})

(-> viz-spec
    (viz/svg-plot2d-cartesian)
    (export-viz "lineplot.svg"))

;; same spec, just update style attribs & layout method
(-> viz-spec
    (update-in [:data 0] merge {:attribs {:fill "#0af"} :layout viz/svg-area-plot})
    (viz/svg-plot2d-cartesian)
    (export-viz "areaplot.svg"))

Same overall visualization setup, only using polar coordinate transform and redefined axis ranges (in radians)…

http://media.thi.ng/geom/viz/lineplot-polar-3.svghttp://media.thi.ng/geom/viz/areaplot-polar-3.svg
Line plot (polar)Area plot (polar)
(def viz-spec-polar
  {:x-axis (viz/linear-axis
            {:domain [(- PI) PI]
             :range  [(* 1.1 PI) (* 1.9 PI)]
             :major  (/ PI 2)
             :minor  (/ PI 16)
             :pos    280})
   :y-axis (viz/linear-axis
            {:domain [-1 1]
             :range  [60 280]
             :major  0.5
             :minor  0.25
             :pos    (* 1.1 PI)})
   :origin (v/vec2 300 310)
   :grid   {:attribs {:stroke "#caa" :fill "none"}
            :minor-x true
            :minor-y true}
   :data   [{:values  (map test-equation (m/norm-range 200))
             :attribs {:fill "none" :stroke "#0af"}
             :layout  viz/svg-line-plot}]})

(-> viz-spec-polar (viz/svg-plot2d-polar) (export-viz "lineplot-polar.svg"))

;; same spec, just update style attribs & layout method
(-> viz-spec-polar
    (update-in [:data 0] merge {:attribs {:fill "#0af"} :res 20 :layout viz/svg-area-plot})
    (viz/svg-plot2d-polar)
    (export-viz "areaplot-polar.svg"))

Bar graph

http://media.thi.ng/geom/viz/bars-3.svghttp://media.thi.ng/geom/viz/bars-interleave-3.svg
Single value per domain position3 interleaved values (datasets) per domain position
<<example-imports>>

(defn export-viz
  [viz path] (->> viz (svg/svg {:width 600 :height 320}) (svg/serialize) (spit path)))

(defn bar-spec
  [num width]
  (fn [idx col]
    {:values     (map (fn [i] [i (m/random 100)]) (range 2000 2016))
     :attribs    {:stroke       col
                  :stroke-width (str (dec width) "px")}
     :layout     viz/svg-bar-plot
     :interleave num
     :bar-width  width
     :offset     idx}))

(def viz-spec
  {:x-axis (viz/linear-axis
            {:domain [1999 2016]
             :range  [50 580]
             :major  1
             :pos    280
             :label  (viz/default-svg-label int)})
   :y-axis (viz/linear-axis
            {:domain      [0 100]
             :range       [280 20]
             :major       10
             :minor       5
             :pos         50
             :label-dist  15
             :label-style {:text-anchor "end"}})
   :grid   {:minor-y true}})

(-> viz-spec
    (assoc :data [((bar-spec 1 20) 0 "#0af")])
    (viz/svg-plot2d-cartesian)
    (export-viz "bars.svg"))

(-> viz-spec
    (assoc :data (map-indexed (bar-spec 3 6) ["#0af" "#fa0" "#f0a"]))
    (viz/svg-plot2d-cartesian)
    (export-viz "bars-interleave.svg"))

Radar plot

http://media.thi.ng/geom/viz/radarplot-3.svghttp://media.thi.ng/geom/viz/radarplot-minmax.svg
6 categories, 3 data sets, single values6 categories, 3 data sets, min-max intervals
<<example-imports>>

(def category->domain (zipmap [:C1 :C2 :C3 :C4 :C5 :C6] (range)))
(def domain->category (reduce-kv #(assoc % %3 %2) {} category->domain))

(defn random-radar-spec
  "Generates radar plot data spec w/ random values for each category in the form:
   {:C1 0.8 :C2 0.2 ...}"
  [color]
  {:values   (zipmap (keys category->domain) (repeatedly #(m/random 0.25 1)))
   :item-pos (fn [[k v]] [(category->domain k) v])
   :attribs  {:fill (col/rgba color)}
   :layout   viz/svg-radar-plot})

(defn random-radar-spec-minmax
  "Generates radar plot data spec w/ random value intervals for each category in the form:
   {:C1 [0.5 0.8] :C2 [0.12 0.2] ...}"
  [color]
  {:values       (zipmap
                  (keys category->domain)
                  (repeatedly #(let [x (m/random 0.5 1)] [(* x (m/random 0.25 0.75)) x])))
   :item-pos-min (fn [[k v]] [(category->domain k) (first v)])
   :item-pos-max (fn [[k v]] [(category->domain k) (peek v)])
   :attribs      {:fill (col/rgba color)}
   :layout       viz/svg-radar-plot-minmax})

(def viz-spec
  {:x-axis (viz/linear-axis
            {:domain     [0 5]
             :range      [0 (* (/ 5 6) TWO_PI)]
             :major      1
             :label-dist 20
             :pos        260
             :label      (viz/default-svg-label (comp name domain->category))})
   :y-axis (viz/linear-axis
            {:domain      [0 1.05]
             :range       [0 260]
             :major       0.5
             :minor       0.1
             :pos         (/ PI 2)
             :label-style {:text-anchor "start"}
             :label       (viz/default-svg-label viz/format-percent)})
   :grid   {:minor-x true :minor-y true}
   :origin (v/vec2 300 300)
   :circle true})

(->> (assoc viz-spec :data (mapv random-radar-spec [[0 0.66 1 0.33] [1 0.5 0 0.33] [1 0 0.8 0.33]]))
     (viz/svg-plot2d-polar)
     (svg/svg {:width 600 :height 600})
     (svg/serialize)
     (spit "radarplot.svg"))

(->> (assoc viz-spec :data (mapv random-radar-spec-minmax [[0 0.66 1 0.33] [1 0.5 0 0.33] [1 0 0.8 0.33]]))
     (viz/svg-plot2d-polar)
     (svg/svg {:width 600 :height 600})
     (svg/serialize)
     (spit "radarplot-minmax.svg"))

Stacked intervals

Plain intervals

http://media.thi.ng/geom/viz/intervals-3.svg

<<example-imports>>

(->> {:x-axis (viz/linear-axis
               {:domain [-10 310]
                :range  [50 550]
                :major  100
                :minor  50
                :pos    150})
      :y-axis (viz/linear-axis
               {:domain  [0 4]
                :range   [50 150]
                :visible false})
      :data   [{:values  [[0 100] [10 90] [80 200] [250 300] [150 170] [110 120]
                          [210 280] [180 280] [160 240] [160 170]]
                :attribs {:stroke-width "10px" :stroke-linecap "round" :stroke "#0af"}
                :layout  viz/svg-stacked-interval-plot}]}
     (viz/svg-plot2d-cartesian)
     (svg/svg {:width 600 :height 200})
     (svg/serialize)
     (spit "intervals.svg"))

Categorized timeline

This more complex example shows how to use structured data (here project descriptions) to create a timeline and visualize each item using a custom shape function w/ linear gradients (based on item type).

http://media.thi.ng/geom/viz/timeline-3.svg

<<example-imports>>
(require '[thi.ng.color.core :as col])
(import '[java.util Calendar GregorianCalendar])

(def items
  [{:title "toxiclibs"          :from #inst "2006-03" :to #inst "2013-06" :type :oss}
   {:title "thi.ng/geom"        :from #inst "2011-08" :to #inst "2015-10" :type :oss}
   {:title "thi.ng/trio"        :from #inst "2012-12" :to #inst "2015-06" :type :oss}
   {:title "thi.ng/fabric"      :from #inst "2014-12" :to #inst "2015-09" :type :oss}
   {:title "thi.ng/simplecl"    :from #inst "2012-10" :to #inst "2013-06" :type :oss}
   {:title "thi.ng/raymarchcl"  :from #inst "2013-02" :to #inst "2013-05" :type :oss}
   {:title "thi.ng/structgen"   :from #inst "2012-10" :to #inst "2013-02" :type :oss}
   {:title "thi.ng/luxor"       :from #inst "2013-10" :to #inst "2015-06" :type :oss}
   {:title "thi.ng/morphogen"   :from #inst "2014-03" :to #inst "2015-06" :type :oss}
   {:title "thi.ng/color"       :from #inst "2014-09" :to #inst "2015-10" :type :oss}
   {:title "thi.ng/validate"    :from #inst "2014-05" :to #inst "2015-06" :type :oss}
   {:title "thi.ng/ndarray"     :from #inst "2015-05" :to #inst "2015-06" :type :oss}
   {:title "thi.ng/tweeny"      :from #inst "2013-10" :to #inst "2015-01" :type :oss}
   {:title "Co(De)Factory"      :from #inst "2013-12" :to #inst "2014-08" :type :project}
   {:title "Chrome WebLab"      :from #inst "2011-05" :to #inst "2012-11" :type :project}
   {:title "ODI"                :from #inst "2013-07" :to #inst "2013-10" :type :project}
   {:title "LCOM"               :from #inst "2012-06" :to #inst "2013-05" :type :project}
   {:title "V&amp;A Ornamental" :from #inst "2010-12" :to #inst "2011-05" :type :project}
   {:title "Engine26"           :from #inst "2010-08" :to #inst "2010-12" :type :project}
   {:title "Resonate"           :from #inst "2012-04" :to #inst "2012-04" :type :workshop}
   {:title "Resonate"           :from #inst "2013-03" :to #inst "2013-03" :type :workshop}
   {:title "Resonate"           :from #inst "2014-04" :to #inst "2014-04" :type :workshop}
   {:title "Resonate"           :from #inst "2015-04" :to #inst "2015-04" :type :workshop}
   {:title "Resonate"           :from #inst "2012-04" :to #inst "2012-04" :type :talk}
   {:title "Resonate"           :from #inst "2013-03" :to #inst "2013-03" :type :talk}
   {:title "Resonate"           :from #inst "2014-04" :to #inst "2014-04" :type :talk}
   {:title "Resonate"           :from #inst "2015-04" :to #inst "2015-04" :type :talk}
   {:title "Retune"             :from #inst "2014-09" :to #inst "2014-09" :type :talk}
   {:title "Bezalel"            :from #inst "2011-04" :to #inst "2011-04" :type :workshop}
   {:title "V&amp;A"            :from #inst "2011-01" :to #inst "2011-03" :type :workshop}
   {:title "HEAD"               :from #inst "2010-10" :to #inst "2010-10" :type :workshop}
   {:title "ETH"                :from #inst "2010-11" :to #inst "2010-11" :type :workshop}
   {:title "SAC"                :from #inst "2012-11" :to #inst "2012-11" :type :workshop}
   {:title "SAC"                :from #inst "2014-12" :to #inst "2014-12" :type :workshop}
   {:title "MSA"                :from #inst "2013-04" :to #inst "2013-04" :type :workshop}
   {:title "Young Creators"     :from #inst "2014-06" :to #inst "2014-06" :type :workshop}
   {:title "EYEO"               :from #inst "2013-06" :to #inst "2013-06" :type :talk}
   {:title "Reasons"            :from #inst "2014-02" :to #inst "2014-02" :type :talk}
   {:title "Reasons"            :from #inst "2014-09" :to #inst "2014-09" :type :talk}])

(def item-type-colors {:project "#0af" :oss "#63f" :workshop "#9f0" :talk "#f9f"})

(def month (* (/ (+ (* 3 365) 366) 4.0 12.0) 24 60 60 1000))
(def year  (* month 12))

(defn ->epoch [^java.util.Date d] (.getTime d))

;; http://stackoverflow.com/questions/9001384/java-date-rounding
(defn round-to-year
  [epoch]
  (let [cal (GregorianCalendar.)]
    (doto cal
      (.setTimeInMillis (long epoch))
      (.add Calendar/MONTH 6)
      (.set Calendar/MONTH 0)
      (.set Calendar/DAY_OF_MONTH 1)
      (.set Calendar/HOUR 0)
      (.set Calendar/MINUTE 0)
      (.set Calendar/SECOND 0)
      (.set Calendar/MILLISECOND 0))
    (.get cal Calendar/YEAR)))

(defn make-gradient
  [[id base]]
  (let [base (col/as-hsva (col/css base))]
    (svg/linear-gradient
     id {} [0 base] [1 (col/adjust-saturation base -0.66)])))

(defn item-range [i] [(->epoch (:from i)) (->epoch (:to i))])

(defn timeline-spec
  [type offset]
  {:values     (if type (filter #(= type (:type %)) items) items)
   :offset     offset
   :item-range item-range
   :attribs    {:fill "white"
                :stroke "none"
                :font-family "Arial"
                :font-size 10}
   :shape      (viz/labeled-rect-horizontal
                {:h         14
                 :r         7
                 :min-width 30
                 :base-line 3
                 :label     :title
                 :fill      #(str "url(#" (name (:type %)) ")")})
   :layout     viz/svg-stacked-interval-plot})

;; Create stacked timeline with *all* items
(->> {:x-axis (viz/linear-axis
               {:domain [(->epoch #inst "2010-09") (->epoch #inst "2015-06")]
                :range  [10 950]
                :pos    160
                :major  year
                :minor  month
                :label  (viz/default-svg-label round-to-year)})
      :y-axis (viz/linear-axis
               {:domain  [0 9]
                :range   [10 160]
                :visible false})
      :grid   {:minor-x true}
      :data   [(timeline-spec nil 0)]}
     (viz/svg-plot2d-cartesian)
     (svg/svg
      {:width 960 :height 200}
      (apply svg/defs (map make-gradient item-type-colors)))
     (svg/serialize)
     (spit "timeline.svg"))

We can also group items by their :type property and arrange them separately along the Y-axis. This creates a less compact result, but better legibility. The example also shows how to re-use visualization spec fragments (via the use of our timeline-spec fn).

http://media.thi.ng/geom/viz/timeline-separate-3.svg

;; Create stacked timeline vertically grouped by item type
(->> {:x-axis (viz/linear-axis
               {:domain [(->epoch #inst "2010-09") (->epoch #inst "2015-06")]
                :range  [10 950]
                :pos    220
                :major  year
                :minor  month
                :label  (viz/default-svg-label round-to-year)})
      :y-axis (viz/linear-axis
               {:domain  [0 13]
                :range   [10 220]
                :visible false})
      :grid   {:minor-x true}
      :data   [(timeline-spec :project 0)
               (timeline-spec :oss 2)
               (timeline-spec :workshop 10)
               (timeline-spec :talk 11)]}
     (viz/svg-plot2d-cartesian)
     (svg/svg
      {:width 960 :height 245}
      (apply svg/defs (map make-gradient item-type-colors)))
     (svg/serialize)
     (spit "timeline-separate.svg"))

Heatmap

http://media.thi.ng/geom/viz/hm-rainbow2.svghttp://media.thi.ng/geom/viz/hm-orange-blue.svg
:rainbow2 gradient preset:orange-blue gradient preset

This demo uses procedural gradients provided by thi.ng/color. See link for list of available presets & how to define new gradients.

<<example-imports>>
(require '[thi.ng.color.gradients :as grad])
(require '[thi.ng.geom.core :as g])
(require '[thi.ng.geom.utils :as gu])
(require '[thi.ng.math.noise :as n])

(def test-matrix
  (->> (for [y (range 10) x (range 50)] (n/noise2 (* x 0.1) (* y 0.25)))
       (viz/matrix-2d 50 10)))

(defn heatmap-spec
  [id]
  {:matrix        test-matrix
   :value-domain  (viz/value-domain-bounds test-matrix)
   :palette       (->> id grad/cosine-schemes (grad/cosine-gradient 100))
   :palette-scale viz/linear-scale
   :layout        viz/svg-heatmap})

(defn cartesian-viz
  [prefix id & [opts]]
  (->> {:x-axis (viz/linear-axis
                 {:domain [0 50]
                  :range [50 550]
                  :major 10
                  :minor 5
                  :pos 280})
        :y-axis (viz/linear-axis
                 {:domain      [0 10]
                  :range       [280 20]
                  :major       1
                  :pos         50
                  :label-dist  15
                  :label-style {:text-anchor "end"}})
        :data   [(merge (heatmap-spec id) opts)]}
       (viz/svg-plot2d-cartesian)
       (svg/svg {:width 600 :height 300})
       (svg/serialize)
       (spit (str prefix "-" (name id) ".svg"))))

(cartesian-viz "hm" :rainbow2)
(cartesian-viz "hm" :orange-blue)
http://media.thi.ng/geom/viz/hmp-yellow-magenta-cyan.svghttp://media.thi.ng/geom/viz/hmp-green-magenta.svg
:yellow-magenta-cyan, polar projection:green-magenta, polar projection
(defn polar-viz
  [prefix id & [opts]]
  (->> {:x-axis (viz/linear-axis
                 {:domain [0 50]
                  :range [(* 1.1 PI) (* 1.9 PI)]
                  :major 10
                  :minor 5
                  :pos 280})
        :y-axis (viz/linear-axis
                 {:domain     [0 10]
                  :range      [90 280]
                  :major      5
                  :pos        (* 1.1 PI)
                  :major-size 10
                  :label-dist 20})
        :origin (v/vec2 300)
        :data   [(merge (heatmap-spec id) opts)]}
       (viz/svg-plot2d-polar)
       (svg/svg {:width 600 :height 320})
       (svg/serialize)
       (spit (str prefix "-" (name id) ".svg"))))

(polar-viz "hmp" :yellow-magenta-cyan)
(polar-viz "hmp" :green-magenta)
http://media.thi.ng/geom/viz/hms-rainbow2.svghttp://media.thi.ng/geom/viz/hmsp-rainbow2.svg
:rainbow2 w/ custom shape fn:rainbow2, polar projection, custom shape fn
;; using custom shape function applied for each matrix cell
;; (a circle fitting within the 4 points defining a grid cell)
(cartesian-viz "hms" :rainbow2 {:shape viz/circle-cell})
(polar-viz "hmsp" :rainbow2 {:shape viz/circle-cell})

Github commit history

Note: This example can optionally use raynes/tentacles 0.3.0 (needs to be manually added to your project) to download the commit history of a given project.

This example downloads the commit history for this project from GitHub and produces a similar activity heatmap as shown on GH user pages (each column = 1 week).

Btw. You can use the local repo by switching the lines calling load-commits-fs (default) and load-commits-gh

http://media.thi.ng/geom/viz/commit-history.svg

;;(require '[tentacles.repos :as repos])
(require '[thi.ng.ndarray.core :as nd])
(require '[clojure.string :as str])
(require '[clojure.java.shell :refer [sh]])

(def day         (* 24 60 60 1000))
(def week        (* 7 day))
(def fmt-iso8601 (java.text.SimpleDateFormat. "yyyy-MM-dd'T'HH:mm:ssX"))
(def fmt-month   (java.text.SimpleDateFormat. "MMM"))
(def fmt-year    (java.text.SimpleDateFormat. "yyyy"))
(def ->epoch #(try (.getTime (.parse fmt-iso8601 %)) (catch Exception e)))

(defn month-or-year
  [from]
  #(let [d (java.util.Date. (long (+ from (* % week))))]
     (.format (if (zero? (.getMonth d)) fmt-year fmt-month) d)))

;; currently disabled to due CLJ 1.8 incompatibility
#_(defn load-commits-gh
  [user repo]
  (prn (str "loading GH commit history: " user "/" repo))
  (->> (repos/commits user repo {:all-pages true})
       (map #(->epoch (get-in % [:commit :author :date])))
       (filter identity)))

(defn load-commits-fs
  [repo-path]
  (->> (sh "git" "log" "--pretty=format:%aI" :dir repo-path)
       :out
       str/split-lines
       (map ->epoch)
       (filter identity)))

(defn commits-per-week-day
  [t0 commits]
  (->> (for [c commits
             :let [t (- c t0)
                   w (int (/ t week))
                   d (int (/ (rem t week) day))]]
         [w d])
       (frequencies)
       (sort-by first)))

(defn commits->matrix
  [commits]
  (let [weeks (inc (- (ffirst (last commits)) (first (ffirst commits))))
        mat (nd/ndarray :int8 (byte-array (* 7 weeks)) [7 weeks])]
    (doseq [[[w d] n] commits] (nd/set-at mat d w n))
    mat))

(let [commits   (load-commits-fs ".")
      ;;commits   (load-commits-gh "thi-ng" "geom")
      [from to] (viz/value-domain-bounds commits)
      from      (* (long (/ from week)) week)
      to        (* (inc (long (/ to week))) week)
      mat       (commits->matrix (commits-per-week-day from commits))
      weeks     (last (nd/shape mat))
      max-x     (+ 50 (* weeks 10))]
  (->> {:x-axis (viz/linear-axis
                 {:domain [0 weeks]
                  :range  [50 max-x]
                  :major  4
                  :minor  1
                  :pos    85
                  :label  (viz/default-svg-label (month-or-year from))})
        :y-axis (viz/linear-axis
                 {:domain  [0 7]
                  :range   [10 80]
                  :visible false})
        :data   [{:matrix        mat
                  :value-domain  [1 (reduce max mat)]
                  :palette       (->> :yellow-red grad/cosine-schemes (grad/cosine-gradient 100))
                  :palette-scale viz/linear-scale
                  :layout        viz/svg-heatmap
                  :shape         viz/circle-cell}]}
       (viz/svg-plot2d-cartesian)
       (svg/svg {:width (+ 70 max-x) :height 120})
       (svg/serialize)
       (spit "commit-history.svg")))

Contour plot

http://media.thi.ng/geom/viz/contours-4.svghttp://media.thi.ng/geom/viz/contours-outline-4.svg
linear X/Y filledlinear X/Y outline
http://media.thi.ng/geom/viz/contours-log-4.svghttp://media.thi.ng/geom/viz/contours-log-outline-4.svg
log X/Y filledlog X/Y outline
<<example-imports>>
(require '[thi.ng.math.noise :as n])

(def viz-spec
  {:x-axis (viz/linear-axis
            {:domain [0 63]
             :range  [50 550]
             :major  8
             :minor  2
             :pos    550})
   :y-axis (viz/linear-axis
            {:domain      [0 63]
             :range       [550 50]
             :major       8
             :minor       2
             :pos         50
             :label-dist  15
             :label-style {:text-anchor "end"}})
   :data   [{:matrix       (->> (for [y (range 64) x (range 64)] (n/noise2 (* x 0.06) (* y 0.06)))
                                (viz/contour-matrix 64 64))
             :levels       (range -1 1 0.05)
             :value-domain [-1.0 1.0]
             :attribs      {:fill "none" :stroke "#0af"}
             :layout       viz/svg-contour-plot}]})

(def viz-spec-log
  (merge viz-spec
         {:x-axis (viz/log-axis
                   {:domain [0 64]
                    :range [50 550]
                    :base 2
                    :pos 555})
          :y-axis (viz/log-axis
                   {:domain      [0 64]
                    :range       [550 50]
                    :base        2
                    :pos         45
                    :label-dist  15
                    :label-style {:text-anchor "end"}})}))

(def fill-attribs {:fill (col/rgba 0.0 0.66 1.0 0.05) :stroke "#fff"})

(defn export-viz
  [viz path] (->> viz (svg/svg {:width 600 :height 600}) (svg/serialize) (spit path)))

(->> {"contours-outline.svg"     [viz-spec false]
      "contours.svg"             [viz-spec true]
      "contours-log-outline.svg" [viz-spec-log false]
      "contours-log.svg"         [viz-spec-log true]}
     (run!
      (fn [[path [spec fill?]]]
        (-> (if fill? (assoc-in spec [:data 0 :attribs] fill-attribs) spec)
            (viz/svg-plot2d-cartesian)
            (export-viz path)))))

An animated variation with polar coordinates:

http://media.thi.ng/geom/viz/contours-polar.gif

GIS terrain contours w/ elevation color gradient

http://media.thi.ng/geom/viz/terrain-24.svghttp://media.thi.ng/geom/viz/terrain-18.svg
contour delta = 24contour delta = 18
http://media.thi.ng/geom/viz/terrain-12.svghttp://media.thi.ng/geom/viz/terrain-6.svg
contour delta = 12contour delta = 6
<<example-imports>>
(require '[thi.ng.color.gradients :as grad])

(defn load-image
  [path]
  (let [img (javax.imageio.ImageIO/read (java.io.File. path))
        w   (.getWidth img)
        h   (.getHeight img)
        rgb (.getRGB img 0 0 w h (int-array (* w h)) 0 w)]
    (viz/contour-matrix w h (map #(bit-and % 0xff) rgb))))

(def viz-spec
  {:x-axis (viz/linear-axis
            {:domain [0 79]
             :range [50 550]
             :major 8
             :minor 2
             :pos 550})
   :y-axis (viz/linear-axis
            {:domain      [0 79]
             :range       [50 550]
             :major       8
             :minor       2
             :pos         50
             :label-dist  15
             :label-style {:text-anchor "end"}})
   :data   [{:matrix          (load-image "../assets/california-detail-gis.png")
             :value-domain    [0.0 255.0]
             :attribs         {:fill "none"}
             :palette         (->> :orange-blue grad/cosine-schemes (grad/cosine-gradient 100))
             :contour-attribs (fn [col] {:stroke col})
             :layout          viz/svg-contour-plot}]})

(doseq [res [6 12 18 24]]
 (->> (assoc-in viz-spec [:data 0 :levels] (range 0 255 res))
      (viz/svg-plot2d-cartesian)
      (svg/svg {:width 600 :height 600})
      (svg/serialize)
      (spit (str "terrain-" res ".svg"))))