Added the choices position and get_posititons_info to graph controller. Right now...
[selectricity-live] / app / controllers / graph_controller.rb
1 require 'date'
2 class GraphController < ApplicationController
3   
4   class GruffGraff
5   
6     def initialize(options)
7       size = "700x400"
8       @graph = options[:graph_type].new(size)
9
10       @graph.theme = { :background_colors => ['#73BF26', '#ffffff'] }
11       @graph.font = File.expand_path('/usr/X11R6/lib/X11/fonts/TTF/Vera.ttf',
12                                    RAILS_ROOT)
13       
14       # fill in the data with the optional data name
15       @graph.data( options.fetch(:data_name, "Data"), options[:data] )
16
17       # set the labels or create an empty hash
18       @graph.labels = options[:interval_labels] \
19         if options.has_key?(:interval_labels) and \
20            options[:interval_labels].class == Hash
21       @graph.x_axis_label = options[:x_axis_label] \
22         if options.has_key?(:x_axis_label)
23       @graph.y_axis_label = options[:y_axis_label] \
24         if options.has_key?(:y_axis_label)
25       @graph.title = options[:title] if options.has_key?(:title)
26       
27       @graph.minimum_value = 0.0
28
29     end
30
31     def output
32       return([@graph.to_blob, {:disposition => 'inline', :type => 'image/png'}])
33     end
34
35   end
36
37   # produce a graph of votes per day during an election
38   def votes_per_day
39     @election = Election.find(params[:id])
40     data, labels = get_votes_per_day_data(@election)
41     
42     graph = GruffGraff.new( :graph_type => Gruff::Line,
43                             :data_name => @election.name,
44                             :data => data,
45                             :interval_labels => labels,
46                             :title => "Voters Per Day",
47                             :x_axis_label => "Data",
48                             :y_axis_label =>"Number of Votes")
49     send_data(*graph.output)
50   end
51   
52   #will place votes in a fixed number of intervals, and shows votes over time
53   def votes_per_interval
54     @election = Election.find(params[:id])
55     data, labels, scale = get_votes_per_interval_data(@election)
56     
57     graph = GruffGraff.new( :graph_type => Gruff::Line,
58                             :data_name => @election.name,
59                             :data => data,
60                             :interval_labels => labels,
61                             :title => "Voters Over Time",
62                             :x_axis_label => scale,
63                             :y_axis_label => "Number of Votes")
64     send_data(*graph.output)
65   end
66
67   def borda_bar
68     @election = Election.find(params[:id])
69     pref_tally = make_preference_tally(@election)
70     
71     @borda_result = BordaVote.new(pref_tally).result
72     data, labels = get_borda_points(@borda_result)
73     
74     graph = GruffGraff.new( :graph_type => Gruff::Bar,
75                             :data_name => @election.name,
76                             :data => data,
77                             :interval_labels => labels,
78                             :title => "Points Per Candidate",
79                             :y_axis_label => "Points",
80                             :x_axis_label => "Candidate")
81     send_data(*graph.output)
82   end
83  
84   def choices_positions
85     @election = Election.find(params[:id])
86     pref_tally = make_preference_tally(@election)
87     
88     fulldata = get_positions_info(@election)
89     (0...@election.candidates.size).each do |i|
90       sbar.data("Candidate#{i+1}", fulldata[i])
91     end
92     
93     sbar = Gruff::Bar.new
94     sbar.title = "Times Voted in Each Position"
95     #sbar.theme = { :background_colors => ['#73BF26', '#ffffff'] }
96     sbar.font = File.expand_path('/usr/X11R6/lib/X11/fonts/TTF/Vera.ttf',
97                                  RAILS_ROOT)
98     
99     graph = GruffGraff.new( :graph_type => Gruff::Bar,
100                             :data_name => @election.name,
101                             :data => data,
102                             :interval_labels => labels,
103                             :title => "Times Candidate Was Voted Each Position",
104                             :x_axis_label => "Rank",
105                             :y_axis_label => "Number of Times")
106                                        
107     
108    
109     
110     sbar.x_axis_label = "Rank"
111     sbar.y_axis_label = "Number of Times Ranked"
112      
113     sbar.minimum_value = 0.0
114     send_data(sbar.to_blob, :disposition => 'inline', :type => 'image/png')
115                               
116     
117     #data, labels = get_positions_info(@election)
118     
119   end
120  private 
121  
122   def get_positions_info(election)
123     buckets = Hash.new
124     buckets2= Hash.new
125     ranks = Hash.new
126   
127     election.candidates.each do |candidate|
128       buckets[candidate.id] = []
129       buckets2[candidate.id] = []
130     end
131      
132     election.votes.each do |vote|
133       vote.rankings.each do |ranking|
134         buckets[ranking.candidate_id] << ranking.rank
135       end
136     end
137        
138     buckets.each_pair do |id, array|
139       (1..election.candidates.size).each do |i|
140         buckets2[id] << (array.find_all {|rank| rank == i}).size
141       end
142     end
143     
144     return buckets2.values
145     
146   end
147    
148   # generate the data and labels for each graph
149   def get_votes_per_day_data(election)
150     voter_days = Array.new
151     unique_days = Array.new
152     total_per_day = Array.new
153     election_days = Hash.new
154     
155     #turn election startdate into date object, and create the range of election
156     startdate = Date.parse(election.startdate.to_s)
157     election_range = startdate..Date.today
158     
159     # create a hash with all the dates of the election in String format
160     # referenced by their order in the election
161     election_range.each_with_index do |day, index|
162       election_days[index] = day.to_s
163     end
164     
165     # Now I need to create an array with all the times votes were made
166     election.votes.each do |vote|
167         voter_days << Date.parse(vote.time.to_s)
168     end
169     voter_days.sort!
170     
171     # Now I need to count how many times each each date appears in voter_days,
172     # and put that number into a votes_per_day array, the 'data' for the graph    
173     #Create an array of unique days from voter_days
174     voter_days.each do |day|
175       unless unique_days.any? {|date| date.eql?(day)}
176         unique_days << day
177       end
178     end
179     unique_days.sort!
180     
181     #find all dates where those days = date at current index, put size of returned
182     #array into total_per_day
183     unique_days.each_with_index do |date, index|
184       total_per_day << (voter_days.select {|day| day.eql?(date)}).size
185     end    
186
187     # return the data and the labels
188     return total_per_day, election_days
189    
190   end
191   
192   def get_votes_per_interval_data(election)
193     labels_hash = Hash.new
194     buckets = Hash.new
195     total_per_interval = Array.new
196     interval_type = ""
197     
198     starttime = election.startdate
199     timedelta = Time.now - starttime
200     numcols = 10
201     interval_length = timedelta/numcols
202     
203     # Make a hash, buckets, indexed by time intervals and containing empty arrays
204     # The time object must come first in addition! 
205     # i would start at 0, i+1 goes from 0 up till numcols
206     numcols.times {|i| buckets[starttime + ((i+1)*interval_length)] = []}
207      
208     # Put votes into bucket according to the time interval to which they belong,
209     # referenced by their key
210     # Will build a graph over time, as each successive interval will have more
211     # vote objects  
212     election.votes.each do |vote|
213       buckets.keys.sort.each do |inter|
214         if vote.time < inter
215           buckets[inter] << vote
216         end
217       end
218     end
219   
220     total_per_interval = buckets.keys.sort.collect {|key| buckets[key].size}
221     
222     # Create the hash for the labels. Each graph has ten columns, and three
223     # will be labeled
224     if timedelta < 2.hours #under two hours use minutes for labels
225       labels_hash[0] = starttime.min.to_s
226       labels_hash[(numcols/2)-1] = (starttime + (timedelta/2)).min.to_s
227       labels_hash[numcols-1] = Time.now.min.to_s
228       interval_type = "Minute of the Hour"
229     elsif timedelta < 2.days #more than 2 hours means use hours for labels
230       labels_hash[0] = starttime.hour.to_s
231       labels_hash[(numcols/2)-1] = (starttime + (timedelta/2)).hour.to_s
232       labels_hash[numcols-1] = Time.now.hour.to_s
233       interval_type = "Hour of the Day on 24 hour scale"
234     else #more than 2 days means use dates for labels
235       labels_hash[0] = (Date.parse(starttime.to_s)).to_s
236       labels_hash[(numcols/2)-1] = (Date.parse((starttime + (timedelta/2)).to_s)).to_s
237       labels_hash[numcols-1] = (Date.today).to_s
238       interval_type = "The Date"
239     end
240     
241     # Make sure to return an array for data and hash for labels
242     return total_per_interval, labels_hash, interval_type   
243   end
244   
245   def get_borda_points(result)
246     #points holds how mnay points each candidate has received in array form
247     #becasue Gruff::Bar#data takes only an array
248     points = Array.new
249     labels = Hash.new
250
251     #Populate points with an sorted array from election.votes hash
252     #biggest to smallest will go from left to right
253     points = result.election.votes.sort do |a, b|
254       b[1] <=> a[1]
255     end.collect {|i| i[1]}
256
257     #make the labels  
258     result.ranked_candidates.each_with_index do |candidate, index|
259       labels[index] = Candidate.find(candidate).name
260     end
261
262     return points, labels
263   end
264
265   #most vote result objects require an array of vote arrays, which this will make
266   def make_preference_tally(election)
267     preference_tally = Array.new
268     @election.voters.each do |voter|
269       next unless voter.voted?
270       preference_tally << voter.vote.rankings.sort.collect \
271         { |ranking| ranking.candidate.id }
272     end
273   return preference_tally
274   end
275
276 end

Benjamin Mako Hill || Want to submit a patch?