|
1 | 1 | (ns birdwatch.wordcount-barchart
|
2 | 2 | (:require [birdwatch.util :as util]
|
| 3 | + [birdwatch.stats.regression :as reg] |
3 | 4 | [reagent.core :as r :refer [atom]]))
|
4 | 5 |
|
5 | 6 | (enable-console-print!)
|
6 | 7 |
|
7 | 8 | (def items (atom []))
|
| 9 | +(def pos-trends (atom {})) |
| 10 | +(def pos-items (atom {})) |
| 11 | +(def ratio-trends (atom {})) |
| 12 | +(def ratio-items (atom {})) |
| 13 | + |
8 | 14 | (def ts-elem (util/by-id "wordcount-barchart"))
|
9 | 15 | (def ts-w (aget ts-elem "offsetWidth"))
|
10 | 16 | (def text-defaults {:stroke "none" :fill "#DDD" :fontWeight 500 :fontSize "0.8em" :dy ".35em" :textAnchor "end"})
|
|
25 | 31 | [:polygon {:transform arrowTrans :stroke "none" :fill color :points points}]))
|
26 | 32 |
|
27 | 33 | (defn bar [text cnt y h w idx]
|
28 |
| - [:g |
29 |
| - [:text {:y (+ y 8) :x 138 :stroke "none" :fill "black" :dy ".35em" :textAnchor "end"} text] |
30 |
| - [arrow 146 y :RIGHT-UP] |
31 |
| - [arrow 160 y :RIGHT] |
32 |
| - [:rect {:y y :x 168 :height 15 :width w :stroke "white" :fill "#428bca"}] |
33 |
| - (if (> w 50) |
34 |
| - [:text (merge text-defaults {:y (+ y 8) :x (+ w 160)}) cnt] |
35 |
| - [:text (merge text-defaults {:y (+ y 8) :x (+ w 171) :fill "#666" :textAnchor "start"}) cnt])]) |
| 34 | + (let [pos-slope (get @pos-trends text)] |
| 35 | + [:g |
| 36 | + [:text {:y (+ y 8) :x 138 :stroke "none" :fill "black" :dy ".35em" :textAnchor "end"} text] |
| 37 | + [arrow 146 y (cond |
| 38 | + (pos? pos-slope) :UP |
| 39 | + (neg? pos-slope ) :DOWN |
| 40 | + :else :RIGHT)] |
| 41 | + [arrow 160 y :RIGHT] |
| 42 | + [:rect {:y y :x 168 :height 15 :width w :stroke "white" :fill "#428bca"}] |
| 43 | + (if (> w 50) |
| 44 | + [:text (merge text-defaults {:y (+ y 8) :x (+ w 160)}) cnt] |
| 45 | + [:text (merge text-defaults {:y (+ y 8) :x (+ w 171) :fill "#666" :textAnchor "start"}) cnt])])) |
36 | 46 |
|
37 | 47 | (defn wordcount-barchart []
|
38 |
| - (let [items @items |
39 |
| - indexed (map-indexed vector items) |
40 |
| - mx (apply max (map (fn [[k v]] v) items)) |
41 |
| - cnt (count items)] |
| 48 | + (let [indexed @items |
| 49 | + mx (apply max (map (fn [[idx [k v]]] v) indexed)) |
| 50 | + cnt (count indexed)] |
42 | 51 | [:div
|
43 | 52 | [:svg {:width ts-w :height (+ (* cnt 15) 5)}
|
44 | 53 | [:g
|
45 |
| - (doall (for [[idx [text cnt]] indexed] |
46 |
| - [bar text cnt (* idx 15) 15 (* (- ts-w 190) (/ cnt mx)) idx])) |
| 54 | + (for [[idx [text cnt]] indexed] |
| 55 | + ^{:key text}[bar text cnt (* idx 15) 15 (* (- ts-w 190) (/ cnt mx)) idx]) |
47 | 56 | [:line {:transform "translate(168, 0)" :y 0 :y2 (* cnt 15) :stroke "black"}]]]
|
48 | 57 | [:p.legend [:strong "1st trend indicator:"] " position changes in last "
|
49 | 58 | [:select {:defaultValue 300000}
|
50 |
| - (for [[v t] opts1] [:option {:value (* v 1000)} t])]] |
| 59 | + (for [[v t] opts1] ^{:key v} [:option {:value (* v 1000)} t])]] |
51 | 60 | [:p.legend [:strong "2nd trend indicator:"] " ratio change termCount / totalTermsCounted over last "
|
52 | 61 | [:select {:defaultValue 100}
|
53 |
| - (for [[v t] opts2] [:option {:value v} t])]]])) |
| 62 | + (for [[v t] opts2] ^{:key v} [:option {:value v} t])]]])) |
54 | 63 |
|
55 | 64 | (r/render-component [wordcount-barchart] ts-elem)
|
56 | 65 |
|
57 | 66 | (defn update-words
|
58 | 67 | "update wordcount chart"
|
59 | 68 | [words]
|
60 |
| - (reset! items words)) |
| 69 | + (reset! items (vec (map-indexed vector words))) |
| 70 | + (doseq [[idx [text cnt]] @items] |
| 71 | + (let [slope (get (reg/linear-regression (take 3 (get @pos-items text))) 1)] |
| 72 | + (swap! pos-items update-in [text] conj idx) |
| 73 | + (swap! pos-trends assoc-in [text] slope)))) |
61 | 74 |
|
0 commit comments