Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 146 lines (118 sloc) 5.631 kB
7097296 @cormullion added content
authored
1 #!/usr/bin/env newlisp
2
3 ;; @module Twitter-graph search plugin for Dragonfly
4 ;; @author cormullion
5
6 ;===============================================================================
7 ; !Loading plugin into Dragonfly context
8 ;===============================================================================
9
10 (context 'Twitter-graph)
11
12 ; graphing utility functions (warning: dynamic scoping ahead!)
13
14 (define (out str)
15 ; shortcut for sending html to buffer
16 (write-buffer html str))
17
18 (define (scale x)
19 ; quick scaling of numbers
20 (int (mul x (div graph-width how-many-days))))
21
22 (define (line-to x y)
23 (out (format "a_context.lineTo(%d, %d);\n" x y)))
24
25 (define (move-to x y)
26 (out (format "a_context.moveTo(%d, %d);\n" x y)))
27
28 (define (text-at str x y)
29 (out (format {a_context.fillText("%s", %d, %d);} str x y)))
30
31 (define (twitter-graph data search-string)
32 ; main function: data is a list of unix times of tweets
33 ; search string is the string searched for - we want that cos we're cacheing newLISP tweets
34 (let ((html {})
35 (graph-width 500)
36 (graph-height 120)
37 (nl "\n")
38 (lt (char 60)) ; avoids angle brackets ...
39 (gt (char 62))
40 (data-values '())
41 (start 0)
42 (end 0)
43 (start-of-day 0)
44 (zero-data-values '())
45 (how-many-days 0)
46 (canvas-coords '()))
47 ; if this is a newLISP search, then we want to use data from the cache
48 ; save values and reload from cache
49 (cond
50 ((and (= search-string "newlisp" )
51 (file? "databases/tweet-cache.lsp"))
52 (load "databases/tweet-cache.lsp")
53 (set 'data-values (unique (append data-values data))))
54 (true
55 (set 'data-values data)))
56
57 ; data-values is list of int-seconds values for each tweet
58
59 ; needs sorting again?
60 (sort data-values)
61
62 ; start it off
63 (out (format (string lt {canvas id="%s" width="%d" height="%d"} gt lt {/canvas} gt) "a" (+ 15 graph-width) graph-height))
64 (out (string lt {script type="text/javascript" language="javascript" charset="utf-8"} gt nl {var a_canvas = document.getElementById("a");} nl {var a_context = a_canvas.getContext("2d");} nl {a_context.font = "bold 10px sans-serif";} nl {a_canvas.background = "#000";} nl))
65
66 ; first value probably starts sometime during the day
67 ; so find the offset of the first event from midnight
68 (set 'start (Time (first data-values)) 'end (Time (last data-values)))
69
70 ; get int-seconds of start of first day
71 (set 'start-of-day (:unix-time (:midnight start)))
72
73 ; convert values so that they start at 0
74 (set 'zero-data-values (map (fn (x) (- x start-of-day)) data-values))
75
76 ; how many days are we doing?
77 (set 'how-many-days (+ (ceil (float (:show (Duration (:period start end))))) 1))
78
79 ; canvas coordinates are basically the seconds from the start,
80 ; scaled by total width of graph (number of days)
81 (set 'canvas-coords (map (fn (n) (div (mul graph-width (div n 86400)) how-many-days)) zero-data-values))
82
83 ; graph parameters
84 (set 'day-width (div graph-width how-many-days)
85 'top-data-line-y 5
86 'bottom-data-line-y 64
87 'top-day-marker-y 65
88 'bottom-day-marker-y 80
89 'top-6hour-marker-y 65
90 'bottom-6hour-marker-y 70
91 'day-number-text-y 90
92 ; we move month names up and down to prevent overlap
93 'month-offsets '(120 110 100))
94
95 ; draw the data lines
96 (dolist (x canvas-coords)
97 (move-to x top-data-line-y)
98 (line-to x bottom-data-line-y))
99
100 ; style and stroke the data lines
101 (out (string {a_context.lineWidth = "0.7";} nl {a_context.strokeStyle = "#f00";} nl {a_context.stroke();} nl ))
102
103 ; draw the dividers numbers etc.
104 (out (string {a_context.beginPath();} nl))
105
106 (for (d 0 how-many-days 0.25) ; something every 6 hours
107 ; which day is this?
108 (set 'day (:day (:shift (copy (:midnight start)) d "days")))
109 (cond
110 ((and (= day 1) (= d (round d)))
111 ; first day of month
112 ; draw divider
113 (move-to (scale d) top-day-marker-y)
114 (line-to (scale d) bottom-day-marker-y)
115 ; draw new month name
116 ; move months up and down so that they don't overlap
117 (text-at (:month-name (:shift (copy (:midnight start)) d "days")) (scale d) (first (rotate month-offsets)))
118 ; draw day-number if scale not too small
119 (if (> day-width 5) (text-at (string day) (scale d) day-number-text-y)))
120 ((= d (round d))
121 ; draw divider
122 (move-to (scale d) top-day-marker-y)
123 (line-to (scale d) bottom-day-marker-y)
124 ; for most days, draw day dividers if the scale's not too small
125 (if (> day-width 5) (text-at (string day) (scale d) day-number-text-y)))
126 ((> day-width 30)
127 ; draw 6 hour markers if scale permits
128 (move-to (scale d) top-6hour-marker-y)
129 (line-to (scale d) bottom-6hour-marker-y))))
130
131 ;style and stroke dividers etc
132 (out (string {a_context.lineWidth = "0.7";} {a_context.strokeStyle = "#000";} nl {a_context.stroke();} nl ))
133
134 ; always draw first month name (this will be a problem only when the first data point is on the first of the month)
135 (out (format {a_context.fillText("%s", 1 , %d);} (:month-name start) (first (rotate month-offsets))))
136
137 ; finish
138 (out (string lt {/script} gt))
139 (println html)
140
141 ; don't forget to save for next time
142 (if (= search-string "newlisp") (save "databases/tweet-cache.lsp" 'data-values))
143 ))
144
145 (context MAIN)
146 ; eof
Something went wrong with that request. Please try again.