require 'uri'
module GoogleOtg
DEFAULT_RANGE = 30 # 30 min
def over_time_graph(hits, args = {})
height = args.has_key?(:height) ? args[:height] : 125
src = args.has_key?(:src) ? args[:src] : "http://www.google.com/analytics/static/flash/OverTimeGraph.swf"
range = hits_to_range(hits, args)
vars = range_to_flashvars(range)
html = <<-eos
eos
return html
end
def google_pie(hits, label_fn, args = {})
height = args.has_key?(:height) ? args[:height] : 125
width = args.has_key?(:width) ? args[:width] : 125
pie_values = extract_pct_values(hits, label_fn, args)
vars = pie_to_flashvars(pie_values, args)
src = args.has_key?(:src) ? args[:src] : "http://www.google.com/analytics/static/flash/pie.swf"
html = <<-eos
eos
return html
end
def pie_to_flashvars(args = {})
labels = args[:labels]
raw_values = args[:raw_values]
percent_values = args[:percent_values]
options = {
:Pie => {
:Id => "Pie",
:Compare => false,
:HasOtherSlice => false,
:RawValues => raw_values,
:Format => "DASHBOARD",
:PercentValues => percent_values
}
}
return URI::encode(options.to_json)
end
protected :pie_to_flashvars
def extract_pct_values(hits, label_fn, args = {})
limit = args.has_key?(:limit) ? args[:limit] : 0.0
total = 0.0
other = 0.0
percent_values = []
raw_values = []
labels = []
values = []
hits.each{|hit|
total += hit.count.to_f
}
hits.each{|hit|
ct = hit.count.to_f
pct = (ct / total)
if pct > limit
percent_values.push([pct, sprintf("%.2f%%", pct * 100)])
raw_values.push([ct, ct])
label = label_fn.call(hit)
meta = args.has_key?(:meta) ? args[:meta].call(hit) : nil
labels.push(label)
values.push({:label => label, :meta => meta, :percent_value => [pct, sprintf("%.2f%%", pct * 100)], :raw_value => ct})
else
other += ct
end
}
if other > 0.0
pct = other / total
percent_values.push([pct, sprintf("%.2f%%", pct * 100)])
raw_values.push([other, other])
labels.push("Other")
values.push({:label => "Other", :percent_value => [pct, sprintf("%.2f%%", pct * 100)], :raw_value => other})
end
return {:labels => labels, :raw_values => raw_values, :percent_values => percent_values, :values => values}
end
protected :extract_pct_values
def flto10(val)
return ((val / 10) * 10).to_i
end
protected :flto10
def hits_to_range(hits, args = {})
return nil unless hits
hits.map{|h|
if !h.respond_to?("created_at") || !h.respond_to?("count")
raise ArgumentError, "Invalid object type. All objects must respond to 'count' and 'created_at'"
end
}
tz = args.has_key?(:time_zone) ? args[:time_zone] : ActiveSupport::TimeZone['UTC']
Time.zone = tz
label = args.has_key?(:label) ? args[:label] : "Value"
time_fn = args.has_key?(:time_fn) ? args[:time_fn] : lambda {|h| h.created_at }
range = args.has_key?(:range) ? args[:range] : DEFAULT_RANGE
x_label_format = args.has_key?(:x_label_format) ? args[:x_label_format] : "%A %I:%M%p"
max_y = 0
hits_dict = {}
hits.each { |h|
hits_dict[time_fn.call(h)] = h
}
total = 0
points = []
now_floored = Time.at((Time.now.to_i/(60*range))*(60*range))
current = hits.length > 0 ? time_fn.call(hits[0]) : now_floored
while (current <= now_floored && range > 0) do
if hits_dict[current]
count = hits_dict[current].count.to_i
max_y = count if count > max_y
date = time_fn.call(hits_dict[current])
date_key = date.to_i
date_value = date.strftime(x_label_format)
points.push({:Value => [count, count], :Label => [date_key, date_value]})
total += count
else
date = current
date_key = date.to_i
date_value = date.strftime(x_label_format)
points.push({:Value => [0, 0], :Label => [date_key, date_value]})
end
current = current + range.minutes
if points.length > 100
break
end
end
max_y = args.has_key?(:max_y) ? (args[:max_y] > max_y ? args[:max_y] : max_y) : max_y
mid_y = self.flto10(max_y / 2)
top_y = self.flto10(max_y)
if (top_y == 0)
mid_y = max_y / 2
top_y = max_y
end
y_labels = [ [mid_y, mid_y], [top_y, top_y] ]
x_labels = []
if points.length > 0
for i in 0..3
hit = points[i * (points.length / 4)]
date_key = hit[:Label][0]
date_value = hit[:Label][1]
x_labels.push([date_key, date_value])
end
end
return {:x_labels => x_labels, :y_labels => y_labels, :label => label, :points => points, :total => total}
end
def range_to_flashvars(args = {})
x_labels = args[:x_labels]
y_labels = args[:y_labels]
label = args[:label]
points = args[:points]
raise ArgumentError unless x_labels
raise ArgumentError unless y_labels
raise ArgumentError unless label
raise ArgumentError unless points
# this is the structure necessary to support the Google Analytics OTG
options = {:Graph => {
:Id => "Graph",
:ShowHover => true,
:Format => "NORMAL",
:XAxisTitle => "Day",
:Compare => false,
:XAxisLabels => x_labels,
:HoverType => "primary_compare",
:SelectedSeries => ["primary", "compare"],
:Series => [
{
:SelectionStartIndex => 0,
:SelectionEndIndex => points.length,
:Style =>
{
:PointShape => "CIRCLE",
:PointRadius => 9,
:FillColor => 30668,
:FillAlpha => 10,
:LineThickness => 4,
:ActiveColor => 30668,
:InactiveColor => 11654895
},
:Label => label,
:Id => "primary",
:YLabels => y_labels,
:ValueCategory => "visits",
:Points => points
}]
} # end graph
} # end options
return URI::encode(options.to_json)
end
protected :range_to_flashvars
end