Skip to content

Instantly share code, notes, and snippets.

@abhin4v
Created December 2, 2010 13:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save abhin4v/725331 to your computer and use it in GitHub Desktop.
Save abhin4v/725331 to your computer and use it in GitHub Desktop.
Shows Stack Overflow reputations, upvotes, downvotes and accepted answers for a user against the tags for the answers. See http://stackapps.com/questions/1828/socharts-charts-by-tags
; Copyright (c) Abhinav Sarkar. All rights reserved.
; The use and distribution terms for this software are covered by the
; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php)
; which can be found in the file epl-v10.html at the root of this distribution.
; By using this software in any fashion, you are agreeing to be bound by
; the terms of this license.
; You must not remove this notice, or any other, from this software.
(ns socharts.socharts
(:import [java.util.zip GZIPInputStream]
[javax.swing JPanel JFrame JButton JTextField JLabel JOptionPane
JProgressBar UIManager WindowConstants]
[java.awt GridBagLayout Insets Toolkit Font Dimension])
(:use [clojure.java.io :only (as-url input-stream)]
[clojure.string :only (join)]
[clojure.java.browse :only (browse-url)]
[clojure.contrib.json :only (read-json)]
[clojure.contrib.math :only (ceil)]
[clojure.contrib.core :only (-?>)]
[clojure.contrib.swing-utils :only
(do-swing add-action-listener add-key-typed-listener)])
(:gen-class))
;; Core
(defn wrap-text
[text columns]
(if (> (.length text) columns)
(str (first (re-seq (re-pattern (str ".{0," columns "} ")) text)) "...")
text))
(defn make-gzipped-input-stream
[url]
(GZIPInputStream. (input-stream (as-url url))))
(defn get-data
[url]
(with-open [is (make-gzipped-input-stream url)]
(read-json (slurp is) true)))
(def user-name-atm (atom nil))
(def status-msg-atm (atom ""))
(def total-answers-atm (atom 0))
(def tags-fetched-count-atm (atom 0))
(let [api-url-format "http://api.stackoverflow.com/1.0/users/%s?key=7eDzkIpVYEmJQj9z4NyE_w"]
(defn get-user-name
[user-id]
(let [data (get-data (format api-url-format user-id))
user-name (-?> data :users first :display_name)]
(println "Fetching username for id" user-id)
(reset! status-msg-atm "Fetching username")
(if (nil? user-name)
(throw (IllegalArgumentException.))
(reset! user-name-atm user-name)))))
(let [api-url-format
"http://api.stackoverflow.com/1.0/users/%s/answers?key=7eDzkIpVYEmJQj9z4NyE_w&pagesize=100"
paged-api-url-format
"http://api.stackoverflow.com/1.0/users/%s/answers?key=7eDzkIpVYEmJQj9z4NyE_w&pagesize=100&page=%s"]
(defn answer-seq
([user-id user-name]
(lazy-seq
(let [data (get-data (format api-url-format user-id))
total (reset! total-answers-atm (-> data :total))
page (-> data :page)
pagesize (-> data :pagesize)
pages (ceil (/ total pagesize))
answers (->> data :answers)]
(println "Fetching" total "answers by" user-name)
(println "Fetching answers"
(str (inc (* 100 (dec page))) "-" (min (* 100 page) total))
"by" user-name)
(reset! status-msg-atm "Fetching answers")
(if (= page pages)
answers
(lazy-cat answers
(answer-seq user-id user-name (inc page) pages total))))))
([user-id user-name page pages total]
(lazy-seq
(let [data (get-data (format paged-api-url-format user-id page))
answers (->> data :answers)]
(println "Fetching answers"
(str (inc (* 100 (dec page))) "-" (min (* 100 page) total))
"by" user-name)
(reset! status-msg-atm "Fetching answers")
(if (= page pages)
answers
(lazy-cat answers
(answer-seq user-id user-name (inc page) pages total))))))))
(let [api-url-format
"http://api.stackoverflow.com/1.0/questions/%s?key=7eDzkIpVYEmJQj9z4NyE_w"]
(defn get-tags
[answer]
(let [question-id (-> answer :question_id)
data (get-data (format api-url-format question-id))]
(println
(str "Fetching tags for answer to \"" (-> answer :title (wrap-text 60)) "\""))
(reset! status-msg-atm "Fetching tags")
(swap! tags-fetched-count-atm inc)
(->> data :questions first :tags (map keyword)))))
(defn add-reputation
[vote]
(-> vote
(assoc :reputation
(if (:community vote) 0
(+ (* 15 (:accept vote)) (* 10 (:up vote)) (* (- 5) (:down vote)))))
(dissoc :community)))
(defn get-votes
[user-id user-name]
(reduce
(fn [acc ans]
(do (Thread/sleep 250)
(merge-with
#(merge-with + %1 %2)
acc
(reduce #(assoc %1 %2
(add-reputation
{:up (:up_vote_count ans)
:down (:down_vote_count ans)
:accept (if (:accepted ans) 1 0)
:community (:community_owned ans)}))
{} (get-tags ans)))))
{} (answer-seq user-id user-name)))
(defn top-votes
[votes vote-type]
(take 15
(sort-by second > (map (fn [[k v]] (vector k (vote-type v))) votes))))
(let [chart-url-format
(str "http://chart.apis.google.com/chart?cht=bhs&chs=700x420&"
"chm=N,000000,0,-1,11,,e:2&"
"chd=t:%s&chds=0,%s&chxr=1,0,%s&chtt=%s&chxt=y,x,x&chxl=0:|%s|2:|%s&"
"chco=00A5C6|DEBDDE|C6D9FD&chxp=2,50&chbh=a,5&chma=55,25,40,40")]
(defn make-chart-url
[tags-votes user-name chart-type]
(let [tags (reverse (map #(-> % first name) tags-votes))
votes (map second tags-votes)
max-vote (apply max votes)]
(-> chart-url-format
(format
(join "," votes)
max-vote
max-vote
(str chart-type "+for+"
(clojure.string/replace user-name " " "+")
"+on+Stack+Overflow+by+Tags")
(join "|" tags)
chart-type)
(clojure.string/replace "|" "%7c")))))
(defn make-charts
[user-id]
(when-not (empty? (str user-id))
(let [user-name (get-user-name user-id)
votes (get-votes user-id user-name)]
(println "Opening charts in the browser")
(reset! status-msg-atm "Opening charts")
(browse-url
(make-chart-url (top-votes votes :up) user-name "Upvotes"))
(browse-url
(make-chart-url (top-votes votes :down) user-name "Downvotes"))
(browse-url
(make-chart-url (top-votes votes :accept) user-name "Accepted+Answers"))
(browse-url
(make-chart-url (top-votes votes :reputation) user-name "Reputations")))))
;;Core
;;GUI
(defmacro set-grid! [constraints field value]
`(set! (. ~constraints ~(symbol (name field)))
~(if (keyword? value)
`(. java.awt.GridBagConstraints ~(symbol (name value)))
value)))
(defmacro grid-bag-layout [container & body]
(let [c (gensym "c")
cntr (gensym "cntr")]
`(let [~c (new java.awt.GridBagConstraints)
~cntr ~container]
~@(loop [result '() body body]
(if (empty? body)
(reverse result)
(let [expr (first body)]
(if (keyword? expr)
(recur (cons `(set-grid! ~c ~expr ~(second body)) result)
(next (next body)))
(recur (cons `(.add ~cntr ~expr ~c) result)
(next body)))))))))
(defn center-window! [^java.awt.Component window]
(let [dim (.. Toolkit getDefaultToolkit getScreenSize)
w (.. window getSize width)
h (.. window getSize height)
window-x (/ (- (.width dim) w) 2)
window-y (/ (- (.height dim) h) 2)]
(doto window (.setLocation window-x window-y))))
(defn show-error-msg
[frame msg title]
(JOptionPane/showMessageDialog frame msg title JOptionPane/ERROR_MESSAGE))
(defn create-gui
[]
(let [frame (JFrame. "Stack Overflow Charts")
user-id-tf (JTextField. 25)
user-name-tf (doto (JTextField. 25) (.setEnabled false))
progress-bar (doto (JProgressBar.)
(.setMinimum 0)
(.setPreferredSize (Dimension. 200 23))
(.setStringPainted true))
status-lbl (JLabel. "Input your User Id and press <ENTER>")
reset-gui #((do-swing
(.setEnabled user-id-tf true)
(.setValue progress-bar 0))
(reset! total-answers-atm 1)
(reset! tags-fetched-count-atm 0)
(reset! status-msg-atm "Done!"))]
(add-watch user-name-atm :user-name-tf
(fn [_ _ _ n] (do-swing (.setText user-name-tf n))))
(add-watch total-answers-atm :progress-bar
(fn [_ _ _ n] (do-swing (.setMaximum progress-bar n))))
(add-watch tags-fetched-count-atm :progress-bar
(fn [_ _ _ n] (do-swing (.setValue progress-bar n))))
(add-watch status-msg-atm :status-lbl
(fn [_ _ _ n] (do-swing (.setText status-lbl n))))
(add-key-typed-listener user-id-tf
(fn [e]
(when (= (.getKeyChar e) \newline)
(let [user-id (.getText user-id-tf)]
(try
(Long/parseLong user-id)
(future
(try
(make-charts user-id)
(catch IllegalArgumentException e
(do-swing
(show-error-msg frame "Invalid User Id!" "Error")))
(catch Exception e
(do-swing
(show-error-msg
frame "Error Occured. Please try agin." "Error")))
(finally (reset-gui))))
(.setEnabled user-id-tf false)
(catch NumberFormatException e
(do-swing
(show-error-msg frame "Invalid User Id!" "Error"))))))))
(doto frame
(.setResizable false)
(.setDefaultCloseOperation WindowConstants/EXIT_ON_CLOSE)
(.setContentPane
(doto (JPanel. (GridBagLayout.))
(grid-bag-layout
:insets (Insets. 5 5 5 5)
:fill :HORIZONTAL
:gridy 0 :gridx 0 (JLabel. "User Id") :gridx 1 user-id-tf
:gridy 1 :gridx 0 (JLabel. "User Name") :gridx 1 user-name-tf
:gridwidth 2 :gridx 0
:gridy 2 progress-bar
:gridy 3 status-lbl)))
(.pack)
(.setVisible true)
(center-window!))))
(UIManager/setLookAndFeel (UIManager/getSystemLookAndFeelClassName))
;;GUI
;;main
(defn -main [& args]
(if-not (empty? args)
(make-charts (first args))
(do-swing (create-gui))))
;;main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment