Skip to content

Instantly share code, notes, and snippets.

@rafd
Created July 4, 2017 15:51
Show Gist options
  • Save rafd/a5e78d8d70859a659cff8b732319d69e to your computer and use it in GitHub Desktop.
Save rafd/a5e78d8d70859a659cff8b732319d69e to your computer and use it in GitHub Desktop.
Graphing with Reagent + SVGs
(ns dota2viz.client.graph
(:require
[clojure.string :as string]))
(defn interpolate
[x [x0 x1] [y0 y1]]
(+ y0 (/ (* (- x x0)
(- y1 y0))
(- x1 x0))))
(defn path-for
[points]
(string/join " "
(concat [(let [point (first points)]
(str "M" (point 0) " " (point 1)))]
(for [point (rest points)]
(str "L" (point 0) " " (point 1))))))
(defn calc-range [points]
[(apply min points) (apply max points)])
(defn indexes [data]
(range 0 (count data)))
(defn scale-fn [[domain-start domain-end] [range-start range-end]]
(fn [datum]
(interpolate datum
[domain-start domain-end]
[range-start range-end])))
(defn mean [data]
(/ (apply + data)
(count data)))
(defn round [value]
(/ (int (* value 100)) 100))
(defn exponential-moving-mean [ratio points]
(reduce (fn [memo i]
(conj memo
(+ (* (- 1 ratio) (last memo))
(* ratio i))))
[#_(first points)
(mean (take (int (/ 1 ratio)) points))]
(rest points)))
(defn graph-view [{:keys [data width height hero]}]
(let [data (vec data)
pad 20
x-size width
y-size height
x-vals (mapv first data)
y-vals (mapv second data)
y-smooth-vals (->> data
(map second)
(exponential-moving-mean 0.1))
x (scale-fn [(apply min x-vals)
(apply max x-vals)]
[0 x-size])
y (scale-fn [0 (apply max y-vals)]
[y-size 0])]
[:svg {:width (+ (* 2 pad) x-size) :height (+ (* 2 pad) y-size)}
[:g {:transform (str "translate(" pad "," pad ")")}
[:g {:class "mid-x-axis"}
[:rect {:x 0
:y (/ height 2)
:width width
:height 1
:fill "#eee"}]]
[:path {:d (path-for (for [index (indexes data)]
[(x (x-vals index)) (y (y-smooth-vals index))]))
:stroke "#dddddd"
:stroke-width 5
:stroke-linejoin "round"
:fill "none"}]
(let [filtered-kv (->> data
(filter (fn [[_ _ h]]
h))
(reduce (fn [memo [x y _]]
(conj memo [x y])) []))]
(when (seq filtered-kv)
(let [smoothed-v (exponential-moving-mean 0.1 (map second filtered-kv))
smoothed-filtered-kv (->> filtered-kv
(map-indexed (fn [i [k v]]
[k (smoothed-v i)])))]
[:path {:d (path-for (for [[k v] smoothed-filtered-kv]
[(x k) (y v)]))
:stroke "rgba(255,0,0,0.1)"
:stroke-width 5
:stroke-linejoin "round"
:fill "none"}])))
[:g
(for [index (indexes data)]
^{:key index}
[:circle {:cx (x (x-vals index))
:cy (y (y-vals index))
:on-click (fn []
(js/window.open (str "https://www.dotabuff.com/matches/" (get-in data [index 3 :match-id])) "_blank"))
:r 1.5
:style {:cursor "pointer"}
:fill (if (get-in data [index 2])
"red"
"blue")}])]
[:g
(for [v (range (apply min x-vals) (apply max x-vals) 604800)]
^{:key v}
[:rect {:x (x v)
:y (- (y 0) 0)
:width 1
:height 4
:fill "gray"}])]
[:g {:class "y-axis"}
[:rect {:x 0
:y 0
:width 1
:height height
:fill "black"}]
[:text {:x -2
:y (+ height 5)
:text-anchor "end"
:font-size 10}
0]
[:text {:x -2
:y (+ 0 5)
:text-anchor "end"
:font-size 10}
(round (apply max y-vals))]]
[:g {:class "x-axis"}
[:rect {
:x 0
:y height
:width width
:height 1
:fill "black"}]
[:text {:x 0
:y (+ height 12)
:text-anchor "middle"
:font-size 10}
0]
[:text {:x width
:y (+ height 12)
:text-anchor "middle"
:font-size 10}
(round (last x-vals))]]]]))
(defn rgba [color opacity]
(let [[r g b] (case color
:red [200 50 50]
:green [50 200 50]
:yellow [200 200 50]
:gray [200 200 200])]
(str "rgba(" r "," g "," b "," opacity ")")))
(defn confidence-interval-view [bar mean interval-50 interval-99 significant?]
(let [x (scale-fn [0 1] [0 100])
fill (cond
(not significant?)
(partial rgba :gray)
(< bar mean)
(partial rgba :green)
(< mean bar)
(partial rgba :red))]
[:svg {:width 102 :height 22}
[:g {:transform "translate(1,1)"}
[:rect {:class "interval"
:x (x (first interval-50))
:y 5
:width (x (- (last interval-50) (first interval-50)))
:height 10
:fill (fill 0.4)}]
[:rect {:class "interval"
:x (x (first interval-99))
:y 5
:width (x (- (last interval-99) (first interval-99)))
:height 10
:fill (fill 0.2)}]
[:rect {:class "mean"
:x (- (x mean) 2)
:y 5
:height 10
:width 4
:fill (fill 1.0)}]
[:rect {:class "50%"
:x (x 0.5)
:y 0
:height 20
:width 1
:fill "#000"}]
[:rect {:class "50%"
:x (x bar)
:y 3
:height 14
:width 1
:fill "rgba(0,0,0,0.25)"}]
(into [:g {:class "x-axis-dots"}]
(for [xpos (range 0 1.25 0.25)]
[:rect {:x (x xpos)
:y 10
:width 1
:height 1
:fill "#000"}]))]]))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment