Added a new bar graph, that counts how many points the borda system of
[selectricity] / app / controllers / graph_controller.rb
1 require 'date'
2 class GraphController < ApplicationController
3   # produce a graph of votes per day during an election
4   def votes_per_day
5     @election = Election.find(params[:id])
6     data, labels = get_votes_per_day_data(@election)
7     
8     line = Gruff::Line.new("700x400")
9     line.theme = { :background_colors => ['#73BF26', '#ffffff'] }
10     line.title = "Voters Per Day"
11     line.font = File.expand_path('/usr/X11R6/lib/X11/fonts/TTF/Vera.ttf',
12                                  RAILS_ROOT)
13
14     line.data( "#{@election.name}", data )
15     line.labels = labels
16
17     line.x_axis_label = "Date"
18     line.y_axis_label = "Number of Votes"
19     line.minimum_value = 0.0
20
21     send_data(line.to_blob, :disposition => 'inline', :type => 'image/png')
22   end
23   
24   #will place votes in a fixed number of intervals, and shows votes over time
25   def votes_per_interval
26     @election = Election.find(params[:id])
27     data, labels, scale = get_votes_per_interval_data(@election)
28     
29     line = Gruff::Line.new("700x400")
30     line.theme = { :background_colors => ['#73BF26', '#ffffff'] }
31     line.title = "Voters Over Time"
32     line.font = File.expand_path('/usr/X11R6/lib/X11/fonts/TTF/Vera.ttf',
33                                RAILS_ROOT)
34     
35     line.data("#{@election.name}", data )
36     line.labels = labels
37     
38     line.x_axis_label = scale
39     line.y_axis_label = "Number of Votes"
40     line.minimum_value = 0.0
41                                
42     send_data(line.to_blob, :disposition => 'inline', :type => 'image/png')  
43   end
44  
45   def quickvote_bar
46     @election = Election.find(params[:id])
47   end
48   
49   def borda_bar
50     @election = Election.find(params[:id])
51     pref_tally = make_preference_tally(@election)
52     
53     @borda_result = BordaVote.new(pref_tally).result
54     data, labels = get_borda_points(@borda_result)
55     
56     bar = Gruff::Bar.new("700x400")
57     bar.theme = { :background_colors => ['#73BF26', '#ffffff'] }
58     bar.title = "Points Per Candidate"
59     bar.font = File.expand_path('/usr/X11R6/lib/X11/fonts/TTF/Vera.ttf',
60                                RAILS_ROOT)
61                       
62     bar.data("#{@election.name}", data)
63     bar.labels = labels
64     
65     bar.y_axis_label = "Points"
66     bar.x_axis_label = "Candidate"
67     bar.minimum_value = 0.0
68     send_data(bar.to_blob, :disposition => 'inline', :type => 'image/png')
69   end
70  
71  private 
72    
73   # generate the data and labels for each graph
74   def get_votes_per_day_data(election)
75     voter_days = Array.new
76     unique_days = Array.new
77     total_per_day = Array.new
78     election_days = Hash.new
79     
80     #turn election startdate into date object, and create the range of election
81     startdate = Date.parse(election.startdate.to_s)
82     election_range = startdate..Date.today
83     
84     # create a hash with all the dates of the election in String format
85     # referenced by their order in the election
86     election_range.each_with_index do |day, index|
87       election_days[index] = day.to_s
88     end
89     
90     # Now I need to create an array with all the times votes were made
91     election.votes.each do |vote|
92         voter_days << Date.parse(vote.time.to_s)
93     end
94     voter_days.sort!
95     
96     # Now I need to count how many times each each date appears in voter_days,
97     # and put that number into a votes_per_day array, the 'data' for the graph    
98     #Create an array of unique days from voter_days
99     voter_days.each do |day|
100       unless unique_days.any? {|date| date.eql?(day)}
101         unique_days << day
102       end
103     end
104     unique_days.sort!
105     
106     #find all dates where those days = date at current index, put size of returned
107     #array into total_per_day
108     unique_days.each_with_index do |date, index|
109       total_per_day << (voter_days.select {|day| day.eql?(date)}).size
110     end    
111
112     # return the data and the labels
113     return total_per_day, election_days
114    
115   end
116   
117   def get_votes_per_interval_data(election)
118     labels_hash = Hash.new
119     buckets = Hash.new
120     total_per_interval = Array.new
121     interval_type = ""
122     
123     starttime = election.startdate
124     timedelta = Time.now - starttime
125     numcols = 10
126     interval_length = timedelta/numcols
127     
128     # Make a hash, buckets, indexed by time intervals and containing empty arrays
129     # The time object must come first in addition! 
130     # i would start at 0, i+1 goes from 0 up till numcols
131     numcols.times {|i| buckets[starttime + ((i+1)*interval_length)] = []}
132      
133     # Put votes into bucket according to the time interval to which they belong,
134     # referenced by their key
135     # Will build a graph over time, as each successive interval wil lhave more
136     # vote objects  
137     election.votes.each do |vote|
138       buckets.keys.sort.each do |inter|
139         if vote.time < inter
140           buckets[inter] << vote
141         end
142       end
143     end
144   
145     total_per_interval = buckets.keys.sort.collect {|key| buckets[key].size}
146     
147     # Create the hash for the labels. Each graph has ten columns, and three
148     # will be labeled
149     if timedelta < 2.hours #under two hours use minutes for labels
150       labels_hash[0] = starttime.min.to_s
151       labels_hash[(numcols/2)-1] = (starttime + (timedelta/2)).min.to_s
152       labels_hash[numcols-1] = Time.now.min.to_s
153       interval_type = "Minutes"
154     elsif timedelta < 2.days #more than 2 hours means use hours for labels
155       labels_hash[0] = starttime.hour.to_s
156       labels_hash[(numcols/2)-1] = (starttime + (timedelta/2)).hour.to_s
157       labels_hash[numcols-1] = Time.now.hour.to_s
158       interval_type = "Hours"
159     else #more than 2 days means use dates for labels
160       labels_hash[0] = (Date.parse(starttime.to_s)).to_s
161       labels_hash[(numcols/2)-1] = (Date.parse(starttime + (timedelta/2))).to_s
162       labels_hash[numcols-1] = (Date.today).to_s
163       interval_type = "Days"
164     end
165     
166     # Make sure to return an array for data and hash for labels
167     return total_per_interval, labels_hash, interval_type   
168   end
169   
170   def get_borda_points(result)
171     #points holds how mnay points each candidate has received in array form
172     #becasue Gruff::Bar#data takes only an array
173     points = Array.new
174     labels = Hash.new
175
176     #Populate points with an sorted array from election.votes hash
177     #biggest to smallest will go from left to right
178     points = result.election.votes.sort do |a, b|
179       b[1] <=> a[1]
180     end.collect {|i| i[1]}
181
182     #make the labels  
183     result.ranked_candidates.each_with_index do |candidate, index|
184       labels[index] = Candidate.find(candidate).name
185     end
186
187     return points, labels
188   end
189
190   #most vote result objects require an array of vote arrays, which this will make
191   def make_preference_tally(election)
192     preference_tally = Array.new
193     @election.voters.each do |voter|
194       next unless voter.voted?
195       preference_tally << voter.vote.rankings.sort.collect \
196         { |ranking| ranking.candidate.id }
197     end
198   return preference_tally
199   end
200
201 end

Benjamin Mako Hill || Want to submit a patch?