Created
July 4, 2017 15:51
-
-
Save rafd/a5e78d8d70859a659cff8b732319d69e to your computer and use it in GitHub Desktop.
Graphing with Reagent + SVGs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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