mountain-goat 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. data/.gitignore +2 -0
  2. data/README.md +5 -4
  3. data/generators/mg/mg_generator.rb +2 -2
  4. data/generators/mg/templates/update_mountain_goat_tables_v2.rb +73 -0
  5. data/lib/mountain-goat/controllers/mg/choices_controller.rb +81 -0
  6. data/lib/mountain-goat/controllers/mg/goals_controller.rb +136 -0
  7. data/lib/mountain-goat/controllers/mg/records_controller.rb +46 -0
  8. data/lib/mountain-goat/controllers/mg/tests_controller.rb +139 -0
  9. data/lib/mountain-goat/mg_core.rb +403 -0
  10. data/lib/mountain-goat/models/mg/choice.rb +50 -0
  11. data/lib/mountain-goat/models/mg/gi_meta.rb +10 -0
  12. data/lib/mountain-goat/models/mg/goal.rb +183 -0
  13. data/lib/mountain-goat/models/mg/goal_meta_type.rb +20 -0
  14. data/lib/mountain-goat/models/mg/gs_meta.rb +10 -0
  15. data/lib/mountain-goat/models/mg/record.rb +59 -0
  16. data/lib/mountain-goat/models/mg/test.rb +31 -0
  17. data/lib/mountain-goat/public/g-funnel.js +168 -0
  18. data/lib/mountain-goat/switch_choice.rb +32 -0
  19. data/lib/mountain-goat/version.rb +1 -1
  20. data/lib/mountain-goat/views/mountain_goat/layouts/.tmp_mountain_goat.html.erb.50680~ +76 -0
  21. data/lib/mountain-goat/views/mountain_goat/mg/choices/_choice_form.html.erb +37 -0
  22. data/lib/mountain-goat/views/mountain_goat/mg/choices/edit.html.erb +13 -0
  23. data/lib/mountain-goat/views/mountain_goat/mg/choices/index.html.erb +34 -0
  24. data/lib/mountain-goat/views/mountain_goat/mg/choices/new.html.erb +15 -0
  25. data/lib/mountain-goat/views/mountain_goat/mg/choices/show.html.erb +30 -0
  26. data/lib/mountain-goat/views/mountain_goat/mg/goals/.tmp__goal_form.html.erb.5027~ +0 -0
  27. data/lib/mountain-goat/views/mountain_goat/mg/goals/.tmp__goal_meta_type_form.html.erb.39992~ +0 -0
  28. data/lib/mountain-goat/views/mountain_goat/mg/goals/.tmp_edit.html.erb.55874~ +0 -0
  29. data/lib/mountain-goat/views/mountain_goat/mg/goals/.tmp_index.html.erb.97274~ +36 -0
  30. data/lib/mountain-goat/views/mountain_goat/mg/goals/_goal_form.html.erb +28 -0
  31. data/lib/mountain-goat/views/mountain_goat/mg/goals/_goal_meta_type_form.html.erb +34 -0
  32. data/lib/mountain-goat/views/mountain_goat/mg/goals/edit.html.erb +12 -0
  33. data/lib/mountain-goat/views/mountain_goat/mg/goals/index.html.erb +37 -0
  34. data/lib/mountain-goat/views/mountain_goat/mg/goals/new.html.erb +12 -0
  35. data/lib/mountain-goat/views/mountain_goat/mg/goals/show.html.erb +32 -0
  36. data/lib/mountain-goat/views/mountain_goat/mg/records/_record.html.erb +16 -0
  37. data/lib/mountain-goat/views/mountain_goat/mg/records/_records.html.erb +5 -0
  38. data/lib/mountain-goat/views/mountain_goat/mg/records/_records_form.html.erb +21 -0
  39. data/lib/mountain-goat/views/mountain_goat/mg/records/edit.html.erb +13 -0
  40. data/lib/mountain-goat/views/mountain_goat/mg/records/index.html.erb +17 -0
  41. data/lib/mountain-goat/views/mountain_goat/mg/records/new.html.erb +13 -0
  42. data/lib/mountain-goat/views/mountain_goat/mg/records/show.html.erb +14 -0
  43. data/lib/mountain-goat/views/mountain_goat/mg/report_items/.tmp__chart.html.erb.13419~ +18 -0
  44. data/lib/mountain-goat/views/mountain_goat/mg/report_items/.tmp__funnel.html.erb.60493~ +18 -0
  45. data/lib/mountain-goat/views/mountain_goat/mg/report_items/.tmp__report_item_form.html.erb.87420~ +10 -0
  46. data/lib/mountain-goat/views/mountain_goat/mg/report_items/.tmp__report_item_pivot_form.html.erb.77056~ +14 -0
  47. data/lib/mountain-goat/views/mountain_goat/mg/report_items/.tmp_edit.html.erb.31048~ +19 -0
  48. data/lib/mountain-goat/views/mountain_goat/mg/report_items/.tmp_new.html.erb.36371~ +17 -0
  49. data/lib/mountain-goat/views/mountain_goat/mg/report_items/_funnel.html.erb +13 -0
  50. data/lib/mountain-goat/views/mountain_goat/mg/reports/.tmp__report_form.html.erb.76535~ +21 -0
  51. data/lib/mountain-goat/views/mountain_goat/mg/reports/.tmp__report_report_items.html.erb.26030~ +5 -0
  52. data/lib/mountain-goat/views/mountain_goat/mg/reports/.tmp_edit.html.erb.78064~ +36 -0
  53. data/lib/mountain-goat/views/mountain_goat/mg/reports/.tmp_index.html.erb.74591~ +23 -0
  54. data/lib/mountain-goat/views/mountain_goat/mg/reports/.tmp_show.html.erb.58427~ +21 -0
  55. data/lib/mountain-goat/views/mountain_goat/mg/tests/_test_form.html.erb +25 -0
  56. data/lib/mountain-goat/views/mountain_goat/mg/tests/edit.html.erb +12 -0
  57. data/lib/mountain-goat/views/mountain_goat/mg/tests/index.html.erb +48 -0
  58. data/lib/mountain-goat/views/mountain_goat/mg/tests/new.html.erb +12 -0
  59. data/lib/mountain-goat/views/mountain_goat/mg/tests/show.html.erb +63 -0
  60. metadata +69 -6
  61. data/lib/mountain-goat/metric_tracking.rb +0 -401
  62. data/lib/mountain-goat/switch_variant.rb +0 -32
@@ -0,0 +1,10 @@
1
+ class Mg::GiMeta < ActiveRecord::Base
2
+ set_table_name :mg_gi_metas
3
+
4
+ belongs_to :mg_goal_meta_type, :class_name => "Mg::GoalMetaType"
5
+ belongs_to :mg_record, :class_name => "Mg::Record"
6
+
7
+ validates_presence_of :mg_goal_meta_type_id
8
+ validates_presence_of :mg_record_id
9
+
10
+ end
@@ -0,0 +1,183 @@
1
+ # Mg::Goal represents a target for user interaction (E.g. Sign-up)
2
+ #
3
+ # Attributes
4
+ # goal_type:: A symbol uniquely identifying this goal type (for code interactions)
5
+ # name:: A name for this goal
6
+ # deleted_at:: Is this goal deleted? (MG Console)
7
+ # is_hidden:: Is this goal hidden? (MG Console)
8
+ class Mg::Goal < ActiveRecord::Base
9
+ set_table_name :mg_goals
10
+
11
+ # ActiveRecord Associations
12
+ has_many :mg_records, :class_name => "Mg::Record", :foreign_key => "mg_goal_id"
13
+ has_many :mg_goal_meta_types, :class_name => "Mg::GoalMetaType", :foreign_key => "mg_goal_id"
14
+ has_many :gi_metas, :through => :mg_goal_meta_types, :class_name => "Mg::GiMeta", :foreign_key => "mg_goal_id"
15
+ has_many :gs_metas, :through => :mg_goal_meta_types, :class_name => "Mg::GsMeta", :foreign_key => "mg_goal_id"
16
+ has_many :report_items, :as => :reportable, :class_name => "Mg::ReportItem"
17
+
18
+ # Secondary Associations
19
+ accepts_nested_attributes_for :mg_goal_meta_types, :reject_if => lambda { |a| a[:name].blank? || a[:var].blank? || a[:meta_type].blank? }, :allow_destroy => true
20
+
21
+ # Validations
22
+ validates_presence_of :name
23
+ validates_format_of :goal_type, :with => /[a-z0-9_]{3,50}/i, :message => "must be between 3 and 30 characters, alphanumeric with underscores"
24
+ validates_uniqueness_of :goal_type
25
+
26
+ # Member Functions
27
+
28
+ # Helper function to retrieve a goal by symbol
29
+ def self.by_type(s)
30
+ Mg::Goal.find( :first, :conditions => { :mg_goal_type => s.to_s } )
31
+ end
32
+
33
+ # Get all records for given meta (e.g. for "Referer": { "Youtube" => Record1, } ...)
34
+ def records_for_meta(var)
35
+ gmt = self.mg_goal_meta_types.find_by_var( var.to_s )
36
+ return {} if gmt.nil?
37
+ gmt.meta.map { |m| { m.data => m.record } }
38
+ end
39
+
40
+ # Get all records for a given meta value (e.g. for "Referer", "Facebook": [ Record1, Record2, Record3 ] )
41
+ def records_for_meta_val(var, data)
42
+ gmt = self.mg_goal_meta_types.find_by_var( var.to_s )
43
+ return [] if gmt.nil?
44
+ gmt.meta.find(:all, :conditions => { :data => data } ).map { |m| m.record }
45
+ end
46
+
47
+ # Get all records pivoted by given meta (e.g. "Youtube" => [ Date, Date, Date ], "Facebook" => [ Date, Date, Date ])
48
+ def records_pivot(pivot)
49
+ res = {}
50
+ gmt_pivot = self.mg_goal_meta_types.find_by_var( pivot.to_s )
51
+ return {} if gmt_pivot.nil?
52
+ gmt_pivot.meta.map { |c| { :created_at => c.created_at, :pivot => c.data } }.each do |c|
53
+ if !res.include?(c[:pivot])
54
+ res[c[:pivot]] = []
55
+ end
56
+
57
+ res[c[:pivot]].push c[:created_at]
58
+ end
59
+
60
+ res.each { |k,v| v.sort! }
61
+ res
62
+ end
63
+
64
+ def records_for_meta_val_pivot(var, data, pivot)
65
+ res = {}
66
+ gmt = self.mg_goal_meta_types.find_by_var( var.to_s )
67
+ gmt_pivot = self.mg_goal_meta_types.find_by_var( pivot.to_s )
68
+ return {} if gmt.nil? || gmt_pivot.nil?
69
+ gmt.meta.find(:all, :select => "`#{gmt.meta.table_name}`.created_at, gm.data AS pivot", :conditions => { :data => data }, :joins => "LEFT JOIN `#{gmt_pivot.meta.table_name}` gm ON gm.mg_goal_meta_type_id = #{gmt_pivot.id} AND gm.mg_record_id = `#{gmt.meta.table_name}`.mg_record_id").each do |c|
70
+ if !res.include?(c.pivot)
71
+ res[c.pivot] = []
72
+ end
73
+
74
+ res[c.pivot].push c.created_at
75
+ end
76
+
77
+ res.each { |k,v| v.sort! }
78
+ res
79
+ end
80
+
81
+ # Pivot by given meta data by value (TODO: Document better)
82
+ def records_for_meta_val_pivot_item(var, data, pivot, item)
83
+ res = {}
84
+ gmt = self.mg_goal_meta_types.find_by_var( var.to_s )
85
+ gmt_pivot = self.mg_goal_meta_types.find_by_var( pivot.to_s )
86
+ gmt_item = self.mg_goal_meta_types.find_by_var( item.to_s )
87
+ return {} if gmt.nil? || gmt_pivot.nil? || gmt_item.nil?
88
+ gmt.meta.find(:all, :select => "`#{gmt.meta.table_name}`.created_at, gm.data AS pivot, gi.data as item", :conditions => { :data => data }, :joins => "LEFT JOIN `#{gmt_pivot.meta.table_name}` gm ON gm.mg_goal_meta_type_id = #{gmt_pivot.id} AND gm.mg_record_id = `#{gmt.meta.table_name}`.mg_record_id LEFT JOIN `#{gmt_item.meta.table_name}` gi ON gi.mg_goal_meta_type_id = #{gmt_item.id} AND gi.mg_record_id = `#{gmt.meta.table_name}`.mg_record_id").each do |c|
89
+ if !res.include?(c.pivot)
90
+ res[c.pivot] = []
91
+ end
92
+
93
+ if gmt_item.meta_type == 'gi_meta'
94
+ c.item.to_i.times { res[c.pivot].push c.created_at }
95
+ else
96
+ res[c.pivot].push c.created_at if c.item == 'yes' #what else?
97
+ end
98
+ end
99
+
100
+ res.each { |k,v| v.sort! }
101
+ res.delete_if { |k,v| v.count == 0 }
102
+ res
103
+ end
104
+
105
+ # Get recent records based on start date
106
+ def recent_records(time_frame)
107
+ self.mg_records.find( :all, :conditions => [ "CREATED_AT > ?", time_frame.ago ] )
108
+ end
109
+
110
+ # Tally we have given a reward
111
+ def tally_reward_given( reward )
112
+ self.transaction do
113
+ self.update_attribute(:rewards_total, 0) if self.rewards_total.nil? #we should merge this with the next line, but whatever
114
+ Mg::Goal.update_counters(self.id, :rewards_given => 1)
115
+ Mg::Goal.update_counters(self.id, :rewards_total => reward)
116
+ end
117
+
118
+ return self.reload
119
+ end
120
+
121
+ #For Reportable
122
+
123
+ # Title in report charts
124
+ def reportable_title(pivot)
125
+ if pivot.nil?
126
+ return self.name
127
+ elsif pivot.instance_of?(Mg::GoalMetaType)
128
+ return "#{self.name} by #{pivot.name}"
129
+ end
130
+ end
131
+
132
+ #
133
+ def reportable_chart_items(pivot)
134
+ #let's look for pageviews by day by source
135
+ #rallies_for_meta_val( :clique_id, @clique.id )
136
+ #logger.debug "sources: #{sources.inspect}"
137
+ #We now have a map of { source => [ date1, date2, ... ], ... }
138
+
139
+ #Now, we just need to insert missing data (and group)
140
+ #Let's transpose that into { source => [ :x => day0, y => count ] }
141
+ if pivot.nil?
142
+ return Analytics.pivot_by_date( { :"Records" => self.mg_records.map { |r| r.created_at } }, self.created_at )
143
+ elsif pivot.instance_of?(Mg::GoalMetaType)
144
+ sources = self.records_pivot( pivot.var )
145
+ logger.warn "sources: #{sources}"
146
+ return Analytics.pivot_by_date(sources, self.created_at)
147
+ end
148
+ end
149
+
150
+ #
151
+ def reportable_gerbil_chart(pivot)
152
+ #chart = GerbilCharts::Charts::LineChart.new( :width => 350, :height => 200, :style => 'brushmetal.css', :circle_data_points => true )
153
+
154
+ if pivot.nil?
155
+ data = Analytics.pivot_by_date( { :"Records" => self.mg_records.map { |r| r.created_at } }, self.created_at )
156
+ elsif pivot.instance_of?(Mg::GoalMetaType)
157
+ sources = self.records_pivot( pivot.var )
158
+ data = Analytics.pivot_by_date(sources, self.created_at)
159
+ end
160
+ #logger.warn "data: #{data.inspect}"
161
+ #logger.warn "ts: #{data.map { |line| line[1].map { |d| d[:x] }.to_a }[0].inspect}"
162
+ #logger.warn "val: #{data.map { |line| [ line[0] ] + line[1].map { |d| d[:y] }.to_a }[0].inspect}"
163
+ #chart.modelgroup = GerbilCharts::Models::SimpleTimeSeriesModelGroup.new(
164
+ # :title => "Rallies",
165
+ # :timeseries => data.map { |line| line[1].map { |d| d[:x].to_time }.to_a }[0],
166
+ # :models => data.map { |line| [ line[0].to_s ] + line[1].map { |d| d[:y] }.to_a }
167
+ #)
168
+ #fields = { }
169
+ #fields_simple = []
170
+ #fields.merge!({ d[:x].to_time.to_i => d[:x].to_s } ); fields_simple.push(d[:x])
171
+ graph = SVG::Graph::TimeSeries.new( :height => 350, :width => 700, :show_data_labels => false, :x_label_format => "%m/%d/%y", :graph_title => self.reportable_title(pivot), :show_graph_title => true, :show_data_values => false, :show_data_points => false, :area_fill => true )
172
+ data.each do |line|
173
+ res = []
174
+ line[1].each { |d| res.push(d[:x].strftime('%m/%d/%y')).push(d[:y]) }
175
+ graph.add_data :data => res, :title => line[0].to_s
176
+ end
177
+
178
+ #logger.warn "Fields: #{fields.inspect}"
179
+ #logger.warn "Fields Simple: #{fields_simple.inspect}"
180
+
181
+ graph
182
+ end
183
+ end
@@ -0,0 +1,20 @@
1
+ class Mg::GoalMetaType < ActiveRecord::Base
2
+ set_table_name :mg_goal_meta_types
3
+
4
+ belongs_to :mg_goal, :class_name => "Mg::Goal"
5
+ has_many :gi_metas, :dependent => :destroy, :class_name => "Mg::GiMeta", :foreign_key => "mg_goal_meta_type_id"
6
+ has_many :gs_metas, :dependent => :destroy, :class_name => "Mg::GsMeta", :foreign_key => "mg_goal_meta_type_id"
7
+
8
+ validates_presence_of :name
9
+ validates_presence_of :var
10
+ validates_presence_of :meta_type
11
+
12
+ def meta
13
+ case self.meta_type
14
+ when 'gi_meta', 'ci_meta'
15
+ return self.gi_metas
16
+ when 'gs_meta', 'cs_meta'
17
+ return self.gs_metas
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,10 @@
1
+ class Mg::GsMeta < ActiveRecord::Base
2
+ set_table_name :mg_gs_metas
3
+
4
+ belongs_to :mg_goal_meta_type, :class_name => "Mg::GoalMetaType"
5
+ belongs_to :mg_record, :class_name => "Mg::Record"
6
+
7
+ validates_presence_of :mg_goal_meta_type_id
8
+ validates_presence_of :mg_record_id
9
+
10
+ end
@@ -0,0 +1,59 @@
1
+ class Mg::Record < ActiveRecord::Base
2
+ set_table_name :mg_records
3
+
4
+ belongs_to :mg_goal, :class_name => "Mg::Goal"
5
+ has_many :mg_goal_meta_types, :through => :mg_goal, :class_name => "Mg::GoalMetaType"
6
+ #has_many :mg_gi_metas, :through => :mg_goal_meta_types
7
+ #has_many :mg_gs_metas, :through => :mg_goal_meta_types
8
+
9
+ #Set meta data for applicable options-- don't store nil data (waste of space)
10
+ def set_meta_data(options)
11
+ options.each do |option|
12
+ gmt = self.mg_goal_meta_types.find_by_var(option[0].to_s)
13
+
14
+ #Create cmt if it doesn't current exist (unless nil)
15
+ if gmt.nil? && !option[1].nil? && ( option[1].is_a?(Integer) || option[1].is_a?(String) )
16
+ #infer type
17
+ meta_type = "gi_meta" if option[1].is_a?(Integer)
18
+ meta_type = "gs_meta" if option[1].is_a?(String)
19
+ gmt = mg_goal.mg_goal_meta_types.create!(:name => option[0].to_s, :var => option[0].to_s, :meta_type => meta_type)
20
+ end
21
+
22
+ if !gmt.nil? #only if we can do it
23
+ gmt.meta.create!(:mg_record_id => self.id, :data => option[1]) if !option[1].nil?
24
+ end
25
+ end
26
+ end
27
+
28
+ def meta_for( var )
29
+ gmt = self.mg_goal_meta_types.find_by_var( var.to_s )
30
+ return nil if gmt.nil?
31
+ m = gmt.meta.find_by_mg_record_id( self.id )
32
+ return nil if m.nil?
33
+ return m.data
34
+ end
35
+
36
+ def all_metas
37
+ res = {}
38
+ self.mg_goal_meta_types.each do |gmt|
39
+ r = res.count
40
+ begin
41
+ if gmt.var =~ /(\w+)[_]id/i
42
+ if Kernel.const_get($1.classify)
43
+ item = Kernel.const_get($1.classify).find( self.meta_for( gmt.var ) )
44
+ if item.respond_to?(:name)
45
+ res.merge!({ $1 => item.name })
46
+ elsif item.respond_to?(:title)
47
+ res.merge!({ $1 => item.title })
48
+ end
49
+ end
50
+ end
51
+ rescue
52
+ end
53
+
54
+ res.merge!({ gmt.var => self.meta_for( gmt.var ) }) if res.count == r
55
+ end
56
+
57
+ res
58
+ end
59
+ end
@@ -0,0 +1,31 @@
1
+ # Mg::Test represents something you are 'a/b testing'
2
+ #
3
+ # Attributes
4
+ # test_type:: Symbol uniquely identifying this test (for code interaction)
5
+ # title:: Title of the test (E.g. Banner text)
6
+ # tally_each_serve:: Should we count each view by a user as a hit, or just first-serve to that user?
7
+ # is_switch:: Are we implementing a code-switch as opposed to a text substitution
8
+ # deleted_at:: Is this test deleted? (MG Console)
9
+ # is_hidden:: Is this test hidden? (MG Console)
10
+ class Mg::Test < ActiveRecord::Base
11
+ set_table_name :mg_tests
12
+
13
+ # ActiveRecord Associations
14
+ has_many :mg_choices, :class_name => "Mg::Choice", :foreign_key => "mg_test_id"
15
+
16
+ # Validations
17
+ validates_format_of :test_type, :with => /[a-z0-9_]{3,50}/i, :message => "must be between 3 and 30 characters, alphanumeric with underscores"
18
+ validates_uniqueness_of :test_type
19
+
20
+ # Member Functions
21
+
22
+ # Get total reward of all choices for this test
23
+ def total_reward
24
+ self.mg_choices.map { |choice| choice.reward || 0 }.sum
25
+ end
26
+
27
+ # Get total served of all choices for this test
28
+ def total_served
29
+ self.mg_choices.map { |choice| choice.served || 0 }.sum
30
+ end
31
+ end
@@ -0,0 +1,168 @@
1
+ /*
2
+ * g.Raphael 0.4.1 - Charting library, based on Raphaël
3
+ *
4
+ * Copyright (c) 2009 Dmitry Baranovskiy (http://g.raphaeljs.com)
5
+ * 2011 Geoffrey Hayes (http://github.com/hayesgm)
6
+ * Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license.
7
+ */
8
+ Raphael.fn.g.funnelchart = function (cx, cy, cw, ch, values, opts) {
9
+ opts = opts || {};
10
+ var paper = this,
11
+ sectors = [],
12
+ covers = this.set(),
13
+ chart = this.set(),
14
+ series = this.set(),
15
+ insetLabels = this.set(),
16
+ order = [],
17
+ len = values.length,
18
+ angle = 0,
19
+ total = 0,
20
+ others = 0,
21
+ cut = 9,
22
+ defcut = true,
23
+ max = 0.0,
24
+ sh = ch / ( len + 0.0 );
25
+ chart.covers = covers;
26
+
27
+ function trap(x0, y0, w0, w1, h) {
28
+ path = ['M', x0, y0, 'H', x0 + w0, 'L', ( w0 - w1 ) / 2.0 + x0 + w1, y0 + h, 'H', ( w0 - w1 ) / 2.0 + x0, 'Z'];
29
+ return path;
30
+ };
31
+
32
+ var labeltext;
33
+ for (var i = 0; i < len; i++) {
34
+ total += values[i];
35
+ max = max > values[i] ? max : values[i] + 0.0;
36
+
37
+ if (opts.legend && opts.legend.length > i)
38
+ labeltext = opts.legend[i];
39
+
40
+ values[i] = {value: values[i], order: i, valueOf: function () { return this.value; }, labeltext: labeltext};
41
+ }
42
+
43
+ //values.sort(function (a, b) {
44
+ // return b.value - a.value;
45
+ //});
46
+
47
+ for (i = 0; i < len; i++) {
48
+
49
+ var sx0 = ( cx + (cw / 2.0 ) - cw * ( values[i] / ( 2.0 * max ) ) );
50
+ var sy0 = cy + sh * i;
51
+ var w0 = values[i] * cw / max;
52
+ var w1 = ( i < len - 1 ) ? values[i + 1] * cw / max : 0;
53
+
54
+ if (opts.init) {
55
+ var ipath = trap(sx0, sy0, w0, w1, sh).join(",");
56
+ }
57
+ //console.log([sx0, sy0, w0, w1, sh]);
58
+ var path = trap(sx0, sy0, w0, w1, sh);
59
+ var p = this.path(path).attr({fill: opts.colors && opts.colors[i] || this.g.colors[i] || "#666", stroke: opts.stroke || "#fff", "stroke-width": (opts.strokewidth == null ? 1 : opts.strokewidth), "stroke-linejoin": "round"});
60
+ if (values[i].labeltext)
61
+ p.insetLabel = this.text(sx0 + w0 / 2.0, sy0 + sh / 3.0, values[i].labeltext + ( i > 0 ? ( " " + ( values[i] - values[i - 1] / values[i - 1] ) + "%" ) : "")).attr({font: "20px Helvetica", stroke: "#fff", fill: "#eee", "fill-opacity": 0.95, "stroke-opacity": 0 });
62
+
63
+ p.value = values[i];
64
+ sectors.push(p);
65
+ series.push(p);
66
+ opts.init && p.animate({path: path.join(",")}, (+opts.init - 1) || 1000, ">");
67
+ }
68
+
69
+ for (i = 0; i < len; i++) {
70
+ p = paper.path(sectors[i].attr("path")).attr(this.g.shim);
71
+ opts.href && opts.href[i] && p.attr({href: opts.href[i]});
72
+ p.attr = function () {};
73
+ covers.push(p);
74
+ series.push(p);
75
+ }
76
+
77
+ chart.hover = function (fin, fout) {
78
+ fout = fout || function () {};
79
+ var that = this;
80
+ for (var i = 0; i < len; i++) {
81
+ (function (sector, cover, j) {
82
+ var o = {
83
+ sector: sector,
84
+ cover: cover,
85
+ cx: cx,
86
+ cy: cy,
87
+ cw: cw,
88
+ ch: ch,
89
+ value: values[j],
90
+ total: total,
91
+ label: that.labels && that.labels[j]
92
+ };
93
+ cover.mouseover(function () {
94
+ fin.call(o);
95
+ }).mouseout(function () {
96
+ fout.call(o);
97
+ });
98
+ })(series[i], covers[i], i);
99
+ }
100
+ return this;
101
+ };
102
+ // x: where label could be put
103
+ // y: where label could be put
104
+ // value: value to show
105
+ // total: total number to count %
106
+ chart.each = function (f) {
107
+ var that = this;
108
+ for (var i = 0; i < len; i++) {
109
+ (function (sector, cover, j) {
110
+ var o = {
111
+ sector: sector,
112
+ cover: cover,
113
+ cx: cx,
114
+ cy: cy,
115
+ x: sector.middle.x,
116
+ y: sector.middle.y,
117
+ mangle: sector.mangle,
118
+ r: r,
119
+ value: values[j],
120
+ total: total,
121
+ label: that.labels && that.labels[j]
122
+ };
123
+ f.call(o);
124
+ })(series[i], covers[i], i);
125
+ }
126
+ return this;
127
+ };
128
+ chart.inject = function (element) {
129
+ element.insertBefore(covers[0]);
130
+ };
131
+ var legend = function (labels, otherslabel, mark, dir) {
132
+ var x = cx + cw * 1.7,
133
+ y = cy,
134
+ h = y + 10;
135
+ labels = labels || [];
136
+ dir = (dir && dir.toLowerCase && dir.toLowerCase()) || "east";
137
+ mark = paper.g.markers[mark && mark.toLowerCase()] || "disc";
138
+ chart.labels = paper.set();
139
+ for (var i = 0; i < len; i++) {
140
+ var clr = series[i].attr("fill"),
141
+ j = values[i].order,
142
+ txt;
143
+ values[i].others && (labels[j] = otherslabel || "Others");
144
+ labels[j] = paper.g.labelise(labels[j], values[i], total);
145
+ chart.labels.push(paper.set());
146
+ chart.labels[i].push(paper.g[mark](x + 5, h, 5).attr({fill: clr, stroke: "none"}));
147
+ chart.labels[i].push(txt = paper.text(x + 20, h, labels[j] || values[j]).attr(paper.g.txtattr).attr({fill: opts.legendcolor || "#000", "text-anchor": "start"}));
148
+ covers[i].label = chart.labels[i];
149
+ h += txt.getBBox().height * 1.2;
150
+ }
151
+ var bb = chart.labels.getBBox(),
152
+ tr = {
153
+ east: [0, -bb.height / 2],
154
+ west: [-bb.width - cw - 20, -bb.height / 2],
155
+ north: [-cw - bb.width / 2, -cw - bb.height - 10],
156
+ south: [- ( cw / 2 ) - bb.width / 2, ( cw / 2 ) + 10]
157
+ }[dir];
158
+ chart.labels.translate.apply(chart.labels, tr);
159
+ chart.push(chart.labels);
160
+ };
161
+ if (opts.legend) {
162
+ legend(opts.legend, opts.legendothers, opts.legendmark, opts.legendpos);
163
+ }
164
+ chart.push(series, covers);
165
+ chart.series = series;
166
+ chart.covers = covers;
167
+ return chart;
168
+ };