|
3 | 3 | (:require [om.core :as om :include-macros true]
|
4 | 4 | [om.dom :as dom :include-macros true]
|
5 | 5 | [cljs-om.util :as util]
|
| 6 | + [cljs-om.timeseries :as ts] |
| 7 | + [cljs-om.ajax :as ajax] |
6 | 8 | [cljs-om.ui :as ui]
|
| 9 | + [goog.events :as events] |
7 | 10 | [cljs-om.wordcount :as wc]
|
8 |
| - [ajax.core :refer [GET POST]] |
9 | 11 | [cljs.core.async :as async
|
10 |
| - :refer [<! >! chan close! sliding-buffer put! alts! timeout]])) |
| 12 | + :refer [<! >! chan close! sliding-buffer put! alts! timeout]]) |
| 13 | + (:import [goog.net XhrIo] |
| 14 | + goog.net.EventType |
| 15 | + [goog.events EventType])) |
11 | 16 |
|
12 | 17 | (enable-console-print!)
|
13 | 18 |
|
14 |
| -(defn sort-by [key-a key-b] |
15 |
| - "sorting function, initially comparing specified key and, if equal, favors higher ID" |
16 |
| - (fn [x y] |
17 |
| - (if (not (= (key-a x) (key-a y))) |
18 |
| - (> (key-a x) (key-a y)) |
19 |
| - (> (key-b x) (key-b y))))) |
20 |
| - |
21 |
| -(def initial-state {:count 0 :n 10 |
22 |
| - :tweets-map {} :rt-since-startup {} |
23 |
| - :search "*" :stream nil |
24 |
| - :sorted :by-followers |
25 |
| - :by-followers (sorted-set-by (sort-by :followers_count :id)) |
26 |
| - :by-retweets (sorted-set-by (sort-by :retweet_count :id)) |
27 |
| - :by-rt-since-startup (sorted-set-by (sort-by :count :id)) |
28 |
| - :by-favorites (sorted-set-by (sort-by :favorite_count :id)) |
29 |
| - :by-id (sorted-set-by >) |
30 |
| - :words {} |
31 |
| - :words-sorted-by-count (sorted-set-by (sort-by :value :key))}) |
32 |
| - |
33 |
| -(def app-state (atom initial-state)) |
| 19 | +(def app-state (atom (util/initial-state))) |
34 | 20 |
|
35 | 21 | (om/root ui/tweets-view app-state {:target (. js/document (getElementById "tweet-frame"))})
|
36 | 22 | (om/root ui/count-view app-state {:target (. js/document (getElementById "tweet-count"))})
|
37 | 23 | (om/root ui/search-view app-state {:target (. js/document (getElementById "search"))})
|
38 | 24 | (om/root ui/sort-buttons-view app-state {:target (. js/document (getElementById "sort-buttons"))})
|
39 | 25 |
|
40 |
| -(defn add-to-tweets-map [tweet] |
41 |
| - (swap! app-state assoc-in [:tweets-map (keyword (:id_str tweet))] (util/format-tweet tweet))) |
| 26 | +(defn add-to-tweets-map [tweets-map tweet] |
| 27 | + (swap! app-state assoc-in [tweets-map (keyword (:id_str tweet))] (util/format-tweet tweet))) |
42 | 28 |
|
43 | 29 | (defn mod-sort-set [app-key fun set-key val rt]
|
44 | 30 | (swap! app-state assoc app-key (fun (app-key @app-state) {set-key val :id (:id_str rt)})))
|
45 | 31 |
|
46 | 32 | (defn add-rt-status [tweet]
|
47 | 33 | "handles original, retweeted tweet"
|
48 | 34 | (if (contains? tweet :retweeted_status)
|
49 |
| - (let [rt (:retweeted_status tweet) |
| 35 | + (let [state @app-state |
| 36 | + rt (:retweeted_status tweet) |
50 | 37 | rt-id (keyword (:id_str rt))
|
51 |
| - prev (rt-id (:tweets-map @app-state)) |
52 |
| - prev-rt-count (rt-id (:rt-since-startup @app-state))] |
53 |
| - (if (not (nil? prev)) |
54 |
| - (do |
| 38 | + prev (rt-id (:retweets state)) |
| 39 | + prev-rt-count (rt-id (:rt-since-startup state))] |
| 40 | + (when (not (nil? prev)) |
55 | 41 | (mod-sort-set :by-retweets disj :retweet_count (:retweet_count prev) rt)
|
56 |
| - (mod-sort-set :by-favorites disj :favorite_count (:favorite_count prev) rt))) |
57 |
| - (if (not (nil? rt)) |
58 |
| - (do |
59 |
| - (if (not (nil? prev-rt-count)) |
60 |
| - (mod-sort-set :by-rt-since-startup disj :count prev-rt-count rt)) |
61 |
| - (swap! app-state assoc-in [:rt-since-startup rt-id] |
62 |
| - (inc (rt-id (:rt-since-startup @app-state)))) |
63 |
| - (mod-sort-set :by-rt-since-startup conj :count (rt-id (:rt-since-startup @app-state)) rt))) |
64 |
| - (add-to-tweets-map rt) |
| 42 | + (mod-sort-set :by-favorites disj :favorite_count (:favorite_count prev) rt)) |
| 43 | + (if (not (nil? prev-rt-count)) |
| 44 | + (mod-sort-set :by-rt-since-startup disj :count prev-rt-count rt)) |
| 45 | + (swap! app-state assoc-in [:rt-since-startup rt-id] |
| 46 | + (inc prev-rt-count)) |
| 47 | + (mod-sort-set :by-rt-since-startup conj :count (inc prev-rt-count) rt) |
| 48 | + (if (> (:retweet_count rt) (:retweet_count prev)) |
| 49 | + (swap! app-state assoc-in [:retweets (keyword (:id_str rt))] (util/format-tweet rt)) |
| 50 | + (swap! app-state assoc-in [:retweets :latest] rt) |
| 51 | + ) |
65 | 52 | (mod-sort-set :by-retweets conj :retweet_count (:retweet_count rt) rt)
|
66 | 53 | (mod-sort-set :by-favorites conj :favorite_count (:favorite_count rt) rt))))
|
67 | 54 |
|
68 | 55 | (def cloud-elem (. js/document (getElementById "wordCloud")))
|
69 | 56 | (def cloud-w (aget cloud-elem "offsetWidth"))
|
70 | 57 | (def word-cloud (.WordCloud js/BirdWatch cloud-w (* cloud-w 0.7) 250 (fn [e]) "#wordCloud"))
|
71 | 58 |
|
72 |
| -(def ts-elem (. js/document (getElementById "timeseries1"))) |
73 |
| -(def ts-w (aget ts-elem "offsetWidth")) |
74 |
| - |
75 |
| -#_(def ts-chart (js/Rickshaw.Graph. |
76 |
| - (clj->js {:element ts-elem |
77 |
| - :renderer "bar" |
78 |
| - :width ts-w |
79 |
| - :height 100 |
80 |
| - :series [{:color "steelblue" |
81 |
| - :name "Tweets" |
82 |
| - :data [{:x 100 :y 10} {:x 100 :y 110}]}]}))) |
83 |
| - |
84 |
| -#_(Rickshaw.Graph.Axis.Time. (clj->js {:graph ts-chart})) |
85 |
| -#_(.render ts-chart) |
86 |
| -#_(def hover-detail (Rickshaw.Graph.HoverDetail. (clj->js {:graph ts-chart}))) |
87 |
| - |
88 |
| -(defn random-data [] (let [series-data (array (array)) |
89 |
| - random (Rickshaw.Fixtures.RandomData. 150)] |
90 |
| - (dotimes [i 100] (.addData random series-data)) |
91 |
| - series-data)) |
92 |
| - |
93 |
| -;; https://gist.github.com/msgodf/8495781 |
94 |
| -(def graph-with-legend |
95 |
| - (let [series-data (array (array)) |
96 |
| - random (Rickshaw.Fixtures.RandomData. 150)] |
97 |
| - (dotimes [i 10] (.addData random series-data)) |
98 |
| - (doto |
99 |
| - (Rickshaw.Graph. (clj->js {:element ts-elem |
100 |
| - :renderer "bar" |
101 |
| - :width ts-w |
102 |
| - :height 100 |
103 |
| - :series [{:color "steelblue" |
104 |
| - :data (nth series-data 0) |
105 |
| - :name "Tweets"}]})) |
106 |
| - (.render)))) |
107 |
| - |
108 |
| -(defn update [chart] |
109 |
| - (aset graph-with-legend "series" "0" "data" (nth (random-data) 0)) |
110 |
| - (.update chart)) |
111 |
| - |
112 |
| -(js/setInterval #(update graph-with-legend) 5000) |
| 59 | +(js/setInterval #(ts/update ts/graph-with-legend) 5000) |
113 | 60 |
|
114 | 61 | (defn add-tweet [tweet]
|
115 | 62 | "increment counter, add tweet to tweets map and to sorted sets by id and by followers"
|
116 |
| - (swap! app-state assoc :count (inc (:count @app-state))) |
117 |
| - (add-to-tweets-map tweet) |
118 |
| - (add-rt-status tweet) |
119 |
| - (wc/process-tweet app-state (:text tweet)) |
120 |
| - (swap! app-state assoc :by-followers (conj (:by-followers @app-state) |
121 |
| - {:followers_count (:followers_count (:user tweet)) |
122 |
| - :id (:id_str tweet)})) |
123 |
| - (swap! app-state assoc :by-id (conj (:by-id @app-state) (:id_str tweet))) |
124 |
| - (. word-cloud (redraw (clj->js (take 250 (:words-sorted-by-count @app-state))))) |
125 |
| - (.updateBarchart js/BirdWatch (clj->js (take 25 (:words-sorted-by-count @app-state))))) |
126 |
| - |
127 |
| -(def tweet-chan (chan)) |
| 63 | + (let [state @app-state] |
| 64 | + (swap! app-state assoc :count (inc (:count state))) |
| 65 | + (add-to-tweets-map :tweets-map tweet) |
| 66 | + (add-rt-status tweet) |
| 67 | + (wc/process-tweet app-state (:text tweet)) |
| 68 | + (swap! app-state assoc :by-followers (conj (:by-followers state) |
| 69 | + {:followers_count (:followers_count (:user tweet)) |
| 70 | + :id (:id_str tweet)})) |
| 71 | + (swap! app-state assoc :by-id (conj (:by-id state) (:id_str tweet))) |
| 72 | + (. word-cloud (redraw (clj->js (take 250 (:words-sorted-by-count state))))) |
| 73 | + ; (.updateBarchart js/BirdWatch (clj->js (take 25 (:words-sorted-by-count state)))) |
| 74 | + )) |
| 75 | + |
| 76 | +(def tweets-chan (chan 100000)) |
| 77 | +(def prev-tweets-chan (chan 100000)) |
| 78 | +(def combined-tweets-chan (chan 1)) |
| 79 | + |
| 80 | + |
128 | 81 | (go-loop []
|
129 |
| - (add-tweet (<! tweet-chan)) |
| 82 | + (put! combined-tweets-chan (<! tweets-chan)) |
130 | 83 | (<! (timeout 0))
|
131 | 84 | (recur))
|
132 | 85 |
|
| 86 | +(go-loop [] |
| 87 | + (put! combined-tweets-chan (<! prev-tweets-chan)) |
| 88 | + (<! (timeout 10)) |
| 89 | + (recur)) |
| 90 | + |
| 91 | +(go-loop [] |
| 92 | + (add-tweet (<! combined-tweets-chan)) |
| 93 | + (recur)) |
| 94 | + |
| 95 | +(def ajax-results-chan (chan)) |
| 96 | +(go-loop [] |
| 97 | + (let [parsed (js->clj (JSON/parse (<! ajax-results-chan)) :keywordize-keys true)] |
| 98 | + (doseq [t (:hits (:hits parsed))] |
| 99 | + (put! prev-tweets-chan (:_source t))) |
| 100 | + (<! (timeout 1000)) |
| 101 | + (recur))) |
133 | 102 |
|
134 | 103 | (defn receive-sse [e]
|
135 | 104 | "callback, called for each item (tweet) received by SSE stream"
|
136 | 105 | (let [tweet (js->clj (JSON/parse (.-data e)) :keywordize-keys true)]
|
137 |
| - (put! tweet-chan tweet))) |
| 106 | + (put! tweets-chan tweet))) |
138 | 107 |
|
139 | 108 | (defn start-search [search]
|
140 | 109 | "initiate new search by starting SSE stream"
|
141 | 110 | (let [s (if (= search "") "*" search)]
|
142 | 111 | (if (not (nil? (:stream @app-state))) (.close (:stream @app-state)))
|
143 |
| - (reset! app-state initial-state) |
| 112 | + (reset! app-state (util/initial-state)) |
144 | 113 | (swap! app-state assoc :search s)
|
145 | 114 | (aset js/window "location" "hash" (js/encodeURIComponent s))
|
146 | 115 | (swap! app-state assoc :stream (js/EventSource. (str "/tweetFeed?q=" s)))
|
147 |
| - (.addEventListener (:stream @app-state) "message" (fn [e] (receive-sse e)) false))) |
148 |
| - |
149 |
| -(start-search (subs (js/decodeURIComponent (aget js/window "location" "hash")) 2)) |
150 |
| - |
151 |
| -(defn error-handler [err] |
152 |
| - (print err)) |
153 |
| - |
154 |
| -(defn handler [payload] |
155 |
| -#_(async/onto-chan tweet-chan (map #(:_source %) (:hits (:hits payload)))) |
156 |
| - (doseq [t (:hits (:hits payload))] |
157 |
| - (put! tweet-chan (:_source t)))) |
158 |
| - |
159 |
| -(defn query [query-string size from] |
160 |
| - {:size size :from from |
161 |
| - :query {:query_string {:default_field "text" :default_operator "AND" |
162 |
| - :query (str "(" query-string ") AND lang:en")}} |
163 |
| - :sort {:id "desc"}}) |
164 |
| -(defn prev-search [query-string size from] |
165 |
| - (POST "/tweets/search" |
166 |
| - {:params (query query-string size from) |
167 |
| - :handler handler |
168 |
| - :error-handler error-handler |
169 |
| - :format :json |
170 |
| - :response-format :json |
171 |
| - :keywords? true})) |
172 |
| - |
173 |
| -#_(POST "/tweets/search" |
174 |
| - {:params (query "*" 1000 0) |
175 |
| - :handler handler |
176 |
| - :error-handler error-handler |
177 |
| - :format :json |
178 |
| - :response-format :json |
179 |
| - :keywords? true}) |
180 |
| - |
181 |
| -(prev-search "*" 500 0) |
182 |
| -(prev-search "*" 500 500) |
| 116 | + (.addEventListener (:stream @app-state) "message" (fn [e] (receive-sse e)) false) |
| 117 | + (ajax/prev-search "*" 500 0 ajax-results-chan) |
| 118 | + (ajax/prev-search "*" 500 500 ajax-results-chan) |
| 119 | + (ajax/prev-search "*" 500 1000 ajax-results-chan) |
| 120 | + (ajax/prev-search "*" 500 1500 ajax-results-chan) |
| 121 | + (ajax/prev-search "*" 500 2000 ajax-results-chan) |
| 122 | + )) |
| 123 | + |
| 124 | +(start-search (util/search-hash)) |
| 125 | + |
0 commit comments